Documentation
¶
Index ¶
- Variables
- type Config
- type Server
- func (s *Server) Handler() http.Handler
- func (s *Server) PublishEvent(ev eventbus.Event)
- func (s *Server) Serve(ln net.Listener) error
- func (s *Server) ServeAuth(ln net.Listener, resolver *auth.Resolver) error
- func (s *Server) ServeAuthTsnet(ln net.Listener, resolver *auth.Resolver) error
- func (s *Server) ServeTLS(ln net.Listener, certFile, keyFile string) error
- func (s *Server) SetNodeKeyResolver(fn func(ctx context.Context, remoteAddr string) (string, error))
- func (s *Server) SetOnAgentForceReclaimed(fn func(ctx context.Context, agentID string))
- func (s *Server) SetOnAgentReleasedAsSource(fn func(ctx context.Context, agentID string))
- func (s *Server) SetOnAgentRuntimePurged(fn func(ctx context.Context, agentID string))
- func (s *Server) SetOnAgentSyncFinalized(...)
- func (s *Server) SetOnAgentSynced(fn func(ctx context.Context, agentID string) error)
- func (s *Server) SetRestartTrigger(fn func() bool)
- func (s *Server) SetSelfNodeKey(nk string)
- func (s *Server) SetTLSConfig(tlsCfg *tls.Config)
- func (s *Server) Shutdown(ctx context.Context) error
- func (s *Server) SlackHub() *slackbot.Hub
- func (s *Server) StartIdempotencySweep(ctx context.Context)
- type WSAttachmentMsg
- type WSExitMsg
- type WSInputMsg
- type WSMessage
- type WSOutputMsg
- type WSResizeMsg
- type WSScrollbackMsg
- type WSYoloDebugMsg
Constants ¶
This section is empty.
Variables ¶
var ErrNoPendingChunkedSync = errors.New("no pending chunked upload for op_id")
ErrNoPendingChunkedSync sentinel used by orchestrator helpers when a /chunked/{op_id} call returns 404. Exported via type assertion on the dispatch error path so the source can distinguish "target lost our pending entry" from a generic HTTP failure.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
Addr string
DevMode bool
Logger *slog.Logger
StaticFS fs.FS // embedded web/dist files for production
Version string
NotifyManager *notify.Manager
AgentManager *agent.Manager
GroupDMManager *agent.GroupDMManager
// BlobStore is optional; when nil, /api/v1/blob/... routes are not
// registered. Phase 3 wires it in cmd/kojo/main.go.
BlobStore *blob.Store
// MaxBlobPutBytes overrides the per-PUT body cap. 0 = use the
// package default (256MB). Tests pass a small cap so they can
// exercise the 413 path without allocating hundreds of megabytes.
MaxBlobPutBytes int64
// EventBus is the invalidation broadcaster. When non-nil, the server
// registers `GET /api/v1/events` (WebSocket) and exposes
// Server.PublishEvent for write handlers. When nil, both the route
// and PublishEvent become no-ops.
EventBus *eventbus.Bus
// Store is the kv-backed handle the session.Manager uses for
// runtime persistence (Phase 2c-2 slice 28). Pass nil to disable
// session persistence — tests that exercise non-session routes
// can leave it nil; the production wiring in cmd/kojo/main.go
// always passes the AgentManager's *store.Store handle.
Store *store.Store
// PeerIdentity is the local peer's stable {device_id, pubkey,
// privkey} (Phase G). When non-nil the server registers
// /api/v1/peers and uses DeviceID to flag the "self" row in list
// responses + reject self-deletion. Nil disables the entire peers
// API (test wiring + builds where peer identity load failed).
PeerIdentity *peer.Identity
// RequireIfMatch enables the docs §3.5 transition: every write
// path that supports optimistic concurrency refuses requests
// without an If-Match header (428 Precondition Required) instead
// of allowing the legacy "best-effort" pass-through. Off by
// default — operators flip it once their clients have all caught
// up. cmd/kojo/main.go reads $KOJO_REQUIRE_IF_MATCH=1 to set this.
RequireIfMatch bool
// PeerEvents is the in-memory pub/sub for peer_registry
// status mutations (docs §3.10). When non-nil the server
// registers /api/v1/peers/events and the registrar /
// OfflineSweeper publish to it on every status change.
// Nil disables the cross-peer push surface.
PeerEvents *peer.EventBus
// PeerPresence is the in-memory active-connection set the events
// WS handler maintains and the OfflineSweeper consults. Optional;
// when nil the handler still touches last_seen periodically (so
// the sample-based aging path keeps working) but the sweeper has
// no way to skip an active connection on a flaky uplink.
PeerPresence *peer.Presence
// PeerOnly switches the server into the daemon-mode shape used
// by `kojo --peer`. The §3.7 device-switch slice promoted this
// from "minimal peer surface" to a full peer: agent runtime,
// agent-facing routes, sessions, files, git, the inter-peer
// surface — all registered — that's how a switch
// can land the agent CLI on this host. What stays Hub-only
// and 404s on a peer:
// - peer-registry mutation (POST/PATCH/DELETE on
// /api/v1/peers): pairing is an operator workflow, not
// something an agent or peer drives.
// - static SPA / dev proxy: peer hosts have no Web UI.
// The auth middleware chain stays the same — RolePeer requests
// arriving over tsnet get stamped by TailnetIdentityMiddleware
// (peer_registry hit) via ServeAuthTsnet, and the agent
// loopback listener keeps its Bearer-based AuthMiddleware.
PeerOnly bool
// V0LegacyDir, when non-empty, enables the session store's v0
// fallback: on kv miss + no v1-dir sessions.json, Load() reads
// <V0LegacyDir>/sessions.json and mirrors it into kv so the v0
// → v1 cutover can reattach live tmux panes. The v0 file is
// never unlinked from this path (rollback + `kojo --clean v0`
// territory). Empty disables v0 fallback — supply "" on --fresh
// / pure new-install paths where v0 data must not leak in.
// cmd/kojo/main.go fills this from configdir.V0Path() iff the
// startup gate observed v1Complete=true (migration done).
V0LegacyDir string
// PendingSyncKEK is the 32-byte envelope key used to seal
// per-op state in pendingAgentSyncs into kv so the raw
// $KOJO_AGENT_TOKEN survives a daemon restart between
// agent-sync and finalize. main.go threads in the same KEK
// it loaded via secretcrypto.LoadOrCreateKEK for the peer
// identity row. Nil disables persistence (in-memory map
// only); a restart with a pending op forces the
// orchestrator to re-run the whole switch.
PendingSyncKEK []byte
// Unsafe disables Tailscale identity verification. Every caller
// is admitted as RolePeer on a peer daemon, or as RoleOwner on
// the public Hub listener. Intended for LAN dev / docker / CI
// deployments. cmd/kojo gates this on the --unsafe flag and
// refuses to start when --unsafe is OFF but no NodeKeyResolver
// is available (i.e. the operator forgot to opt in).
Unsafe bool
// RepoDir is the source checkout POST /api/v1/system/rebuild runs
// `make build` in, then copies the built binary over
// os.Executable() (in-place deploy). Empty disables the endpoint
// (returns 409). cmd/kojo reads $KOJO_REPO_DIR to set this.
RepoDir string
}
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
func (*Server) PublishEvent ¶ added in v0.101.0
PublishEvent is the Server-side hook for write handlers. Calls into the bus when configured, no-op otherwise. Always returns nil to keep call sites trivial — the bus reports its own internal errors via metrics (Bus.Dropped).
func (*Server) ServeAuth ¶ added in v0.17.0
ServeAuth serves the agent-facing auth-required listener using the supplied resolver. The handler chain is AuthMiddleware (sets Principal from Authorization header) → EnforceMiddleware (denies non-Owner principals on routes outside the allowlist) → mux.
Intended use: bind to 127.0.0.1 only, expose to local PTY processes via $KOJO_API_BASE. For a tsnet listener that ALSO needs to accept inter-peer traffic (peer-mode primary listener), use ServeAuthTsnet instead — it wires TailnetIdentityMiddleware ahead of AuthMiddleware so a paired peer's WhoIs-resolved identity reaches the policy gate as RolePeer / RoleOwner. The public listener (Serve) bypasses auth and keeps the user UX intact.
func (*Server) ServeAuthTsnet ¶ added in v0.101.0
ServeAuthTsnet serves the auth-required listener on a tsnet listener that also receives inter-peer traffic (peer-mode primary listener). Chain order: TailnetIdentityMiddleware → AuthMiddleware → EnforceMiddleware → … → mux. PromoteUnknownTailnetToOwner is false — only registered peers get a non-Guest principal via WhoIs; unknown tailnet callers fall through to AuthMiddleware so an agent on the same tailnet (rare) can still present a Bearer. The legacy Ed25519-signing path is retired; Tailnet identity (peer_registry membership over a tsnet listener) is the sole inter-peer trust signal here. The --unsafe escape hatch stamps RoleOwner via UnsafeAsHub=true for LAN / docker / CI deployments — peer↔peer handlers gate on the source_device_id carried in the request body, so Owner skips the equality gates rather than failing on an empty PeerID.
func (*Server) SetNodeKeyResolver ¶ added in v0.101.0
func (s *Server) SetNodeKeyResolver(fn func(ctx context.Context, remoteAddr string) (string, error))
SetNodeKeyResolver wires (or rewires) the tsnet WhoIs resolver. cmd/kojo calls this AFTER tsnet.Server.LocalClient is available (Hub) or once the OS tailscaled handshake completes (--peer). Passing nil clears the resolver — every caller becomes Guest unless Unsafe is set, which is the safe default on --local / --dev where no tsnet boundary exists.
func (*Server) SetOnAgentForceReclaimed ¶ added in v0.101.0
SetOnAgentForceReclaimed installs the operator-driven force- reclaim hook. See onAgentForceReclaimed for the contract.
func (*Server) SetOnAgentReleasedAsSource ¶ added in v0.101.0
onAgentReleasedAsSource is fired by the §3.7 switch_device handler after a successful complete + finalize. The hook drops the agent from this peer's AgentLockGuard.desired set so target's lease expiry doesn't trigger a re-Acquire from this peer (which would steal the lock back with stale state). Set via SetOnAgentReleasedAsSource.
func (*Server) SetOnAgentRuntimePurged ¶ added in v0.101.0
SetOnAgentRuntimePurged installs the inter-peer state-probe self-heal hook. See onAgentRuntimePurged for the contract.
func (*Server) SetOnAgentSyncFinalized ¶ added in v0.101.0
func (s *Server) SetOnAgentSyncFinalized(fn func(ctx context.Context, agentID, rawToken, sourceDeviceID, opID string) (bool, error))
SetOnAgentSyncFinalized installs the post-complete hook that the orchestrator's /api/v1/peers/agent-sync/finalize endpoint invokes. See onAgentSyncFinalized for the contract.
func (*Server) SetOnAgentSynced ¶ added in v0.101.0
SetOnAgentSynced installs the post-agent-sync hook that handlePeerAgentSync invokes after a successful row write. The hook MUST be set on the target peer before it can receive a §3.7 device-switch agent-sync payload — without it, the synced rows land in kojo.db but no one tells the agent_lock guard or the in-memory agent.Manager that a new agent has arrived.
Threadsafe wrt the handler: handlePeerAgentSync reads the field after the Server's mux has been built, by which point New() has long returned and any cmd/kojo/main.go wiring is in place. We don't bother with a mutex — the field is set exactly once at boot and never mutated under load.
func (*Server) SetRestartTrigger ¶ added in v0.111.0
SetRestartTrigger wires the shutdown-for-restart callback. cmd/kojo passes a closure that marks the restart intent and cancels the signal context, funneling into the same ordered graceful-shutdown path as SIGINT; after that drain main re-execs the binary in place. The closure reports whether the intent was accepted (false = a signal shutdown already won the race). When never set (tests, unsupported platforms), the handler returns 501.
func (*Server) SetSelfNodeKey ¶ added in v0.101.0
SetSelfNodeKey records the local kojo's own NodeKey so the tsnet identity middleware can promote requests from the same host to RoleOwner. Same wiring contract as SetNodeKeyResolver.
func (*Server) SetTLSConfig ¶
func (*Server) SlackHub ¶ added in v0.101.0
SlackHub returns the wired-in slackbot.Hub or nil when the server was built without one (--peer mode or test fixtures). Used by cmd/kojo/main.go's source-release hook to stop a migrated agent's Slack bot on the source peer.
func (*Server) StartIdempotencySweep ¶ added in v0.101.0
StartIdempotencySweep launches a goroutine that periodically deletes expired idempotency_keys rows. Cancelling ctx stops the goroutine. Safe to call from multiple sites — the second call is a no-op.
type WSAttachmentMsg ¶ added in v0.5.0
type WSAttachmentMsg struct {
Type string `json:"type"`
Attachments []*session.Attachment `json:"attachments"`
}
type WSInputMsg ¶
type WSMessage ¶
type WSMessage struct {
Type string `json:"type"`
Data json.RawMessage `json:"data,omitempty"`
}
WebSocket message types
type WSOutputMsg ¶
type WSResizeMsg ¶
type WSScrollbackMsg ¶
type WSYoloDebugMsg ¶
Source Files
¶
- agent_file_handlers.go
- agent_handlers.go
- agent_proxy_middleware.go
- agent_sync_chunked_dispatch.go
- agent_ws.go
- agent_ws_proxy.go
- apikey_handlers.go
- blob_handlers.go
- cache_control_middleware.go
- changes_handler.go
- etag.go
- events_ws.go
- export_handlers.go
- file_handlers.go
- force_reclaim_handler.go
- git_handlers.go
- groupdm_handlers.go
- handoff_handler.go
- handoff_queue_handlers.go
- idempotency_middleware.go
- kv_handlers.go
- mcp_handler.go
- memory_entry_handlers.go
- memory_handlers.go
- mirror_refresher.go
- oplog_handler.go
- peer_agent_sync_chunk.go
- peer_agent_sync_finalize_handler.go
- peer_agent_sync_handler.go
- peer_agent_sync_state_handler.go
- peer_blob_handler.go
- peer_blob_ingest_handler.go
- peer_events_handler.go
- peer_handlers.go
- peer_pending_handlers.go
- peer_proxy.go
- peer_pull_handler.go
- persona_handlers.go
- proc_unix.go
- push_handlers.go
- remote_agent_settings.go
- server.go
- session_handlers.go
- session_proxy_middleware.go
- slackbot_handlers.go
- stt_handlers.go
- switch_device_handler.go
- system_handlers.go
- tts_handlers.go
- websocket.go
- workspace_files_handlers.go