@kaged/daemon

Long-lived HTTP+WebSocket server that routes operator sessions, dispatches primary agents through the harness, and orchestrates plugins, providers, and project lifecycles

139
source files
88
test files
~69.3k
lines
✓ 171 pass
tests
pass
typecheck
3w
lint

Test results 171

buildLaunchUrl (ADR-0040) > co-located (no ui_url) prints the daemon-local /launch URL [0.690ms]
route ↔ handler sync > every contract handler has a route definition [0.250ms]
route ↔ handler sync > known exceptions actually exist in route definitions [0.100ms]
route ↔ handler sync > known exceptions do NOT have contract handlers (or remove them from exceptions) [0.090ms]
context estimate handler > returns 503 when storage is unavailable [19.75ms]
context estimate handler > returns 404 when session is not found [23.83ms]
context estimate handler > returns the context estimate payload [33.36ms]
context estimate handler > honors session modelOverride when resolving route [23.83ms]
context estimate handler > throws when session modelOverride uses an unconfigured provider [20.10ms]
context estimate handler > registers GET /api/v1/sessions/:id/context-estimate in contract handlers [11.03ms]
parsePorcelainV2 — branch headers > parses a normal branch with upstream and ahead/behind [4.06ms]
parsePorcelainV2 — branch headers > parses an unborn branch (initial — oid null) [0.130ms]
parsePorcelainV2 — branch headers > parses detached HEAD [0.090ms]
parsePorcelainV2 — branch headers > branch with no upstream sets upstream null and ahead/behind 0 [0.090ms]
parsePorcelainV2 — ordinary entries (type 1) > parses an unstaged modification (index='.', worktree='M') [0.200ms]
parsePorcelainV2 — ordinary entries (type 1) > parses a staged + unstaged modification (MM) [0.100ms]
parsePorcelainV2 — ordinary entries (type 1) > parses a staged addition (A.) [0.060ms]
parsePorcelainV2 — ordinary entries (type 1) > path with spaces is preserved [0.060ms]
parsePorcelainV2 — rename entries (type 2) > parses a rename with origPath in the next NUL element [0.110ms]
parsePorcelainV2 — rename entries (type 2) > rename origPath does not appear as a separate entry [0.090ms]
parsePorcelainV2 — unmerged entries (type u) > parses UU as both-modified [0.110ms]
parsePorcelainV2 — unmerged entries (type u) > parses AA as both-added [0.070ms]
parsePorcelainV2 — unmerged entries (type u) > parses AU as added-by-us [0.050ms]
parsePorcelainV2 — unmerged entries (type u) > parses UA as added-by-them [0.050ms]
parsePorcelainV2 — unmerged entries (type u) > parses DU as deleted-by-us [0.050ms]
parsePorcelainV2 — unmerged entries (type u) > parses UD as deleted-by-them [0.050ms]
parsePorcelainV2 — unmerged entries (type u) > parses DD as both-deleted [0.050ms]
parsePorcelainV2 — untracked entries > parses an untracked file [0.060ms]
parsePorcelainV2 — untracked entries > untracked path with spaces is preserved [0.090ms]
parsePorcelainV2 — ignored entries are dropped > ! entries do not appear in the result [0.110ms]
parsePorcelainV2 — mixed fixture > parses a realistic mixed status output [0.240ms]
computeCounts > empty entries → all zero [0.140ms]
computeCounts > counts staged, unstaged, untracked, conflicted independently [0.070ms]
computeCounts > conflicted entries are excluded from staged/unstaged [0.040ms]
computeCounts > untracked entries are excluded from staged/unstaged [0.030ms]
isPathInsideRoot > accepts a path inside the root [0.110ms]
isPathInsideRoot > accepts the root directory itself [0.040ms]
isPathInsideRoot > rejects a sibling that shares a prefix (prefix-collision guard) [0.030ms]
isPathInsideRoot > rejects a completely different path [0.030ms]
isPathInsideRoot > handles trailing slashes on root [0.030ms]
isPathInsideRoot > handles trailing slashes on candidate [0.030ms]
isPathInsideRoot > rejects the parent of the root [0.030ms]
isRelativePathSafe > accepts a simple relative path [0.130ms]
isRelativePathSafe > accepts a nested relative path [0.040ms]
isRelativePathSafe > rejects an absolute path [0.030ms]
isRelativePathSafe > rejects ../ at the start (escapes root) [0.030ms]
isRelativePathSafe > rejects mid-path ../ that escapes [0.030ms]
isRelativePathSafe > accepts ../ that stays within root (a/../../b resolves to ../b) [0.030ms]
isRelativePathSafe > accepts internal ../ that doesn't escape [0.030ms]
isRelativePathSafe > rejects empty string [0.020ms]
isRelativePathSafe > accepts . segments (current dir) [0.030ms]
isRelativePathSafe > rejects NUL byte injection [0.030ms]
mapGitError > index.lock exists → git.busy [0.260ms]
mapGitError > 'not a git repository' → git.not_a_repo [0.050ms]
mapGitError > SSH publickey denied → git.auth_failed [0.060ms]
mapGitError > HTTP authentication failed → git.auth_failed [0.050ms]
mapGitError > host key verification failed → git.host_key_unknown [0.040ms]
mapGitError > non-fast-forward push → git.non_fast_forward [0.040ms]
mapGitError > rejected push → git.non_fast_forward [0.040ms]
mapGitError > generic non-zero exit → git.command_failed [0.110ms]
mapGitError > stderr is truncated in the message for very long output [0.080ms]
GitService integration — core loop > getRootSummaries on a clean repo returns valid root with zero counts [30.67ms]
GitService integration — core loop > getStatus shows untracked files [23.54ms]
GitService integration — core loop > stage → commit loop works end-to-end [36.36ms]
GitService integration — core loop > commit with nothing staged throws git.nothing_staged [26.41ms]
GitService integration — core loop > diff on an unstaged modification returns text diff [21.88ms]
GitService integration — core loop > stage → unstage returns the file to unstaged [35.38ms]
GitService integration — core loop > stash + stashPop round-trips a change [51.30ms]
GitService integration — core loop > requireRoot throws git.root_not_configured for unknown scope [20.84ms]
GitService integration — core loop > path with ../ traversal is rejected before any git call [15.68ms]
GitService integration — core loop > getRootSummaries marks a non-repo directory as valid: false [18.90ms]
happy path > dispatchPrimary integration > run completes with state 'done' [23.71ms]
happy path > dispatchPrimary integration > assistant message persisted to storage [18.86ms]
happy path > dispatchPrimary integration > persisted message has provider metadata [19.03ms]
happy path > dispatchPrimary integration > session transitions back to idle [18.90ms]
happy path > dispatchPrimary integration > runPrimary receives correct provider route [18.27ms]
happy path > dispatchPrimary integration > runPrimary receives reconstructed operator message [16.28ms]
happy path > dispatchPrimary integration > runPrimary receives system prompt content [16.85ms]
happy path > dispatchPrimary integration > run state transitions through 'running' before 'done' [15.14ms]
dispatchPrimary integration > config-missing: run fails when localConfigPath is null [7.52ms]
dispatchPrimary integration > project-not-loaded: run fails when project not in config [12.49ms]
dispatchPrimary integration > alias-not-found: run fails when model alias missing [11.96ms]
dispatchPrimary integration > invalid-alias-target: run fails when target missing colon [7.78ms]
dispatchPrimary integration > provider-not-configured: run fails when provider entry missing [12.82ms]
dispatchPrimary integration > api-key-unresolved: run fails when provider has no key [8.19ms]
dispatchPrimary integration > prompt-unreadable: run fails when prompt file doesn't exist [13.00ms]
dispatchPrimary integration > dsl-invalid: run fails when project.yaml is malformed [7.66ms]
error mid-stream > dispatchPrimary integration > run marked failed when runPrimary returns error [13.22ms]
error mid-stream > dispatchPrimary integration > session still transitions to idle after error [8.63ms]
error mid-stream > dispatchPrimary integration > error assistant message is still persisted [14.01ms]
dispatchPrimary integration > runPrimary-throws: run fails gracefully [12.81ms]
dispatchPrimary integration > runPrimary-throws: session transitions to idle [8.58ms]
dispatchPrimary integration > api-key-env: resolves key from environment variable [13.41ms]
dispatchPrimary integration > reconstructs full message history [8.42ms]
dispatchPrimary integration > skips superseded messages in history [12.50ms]
dispatchPrimary integration > all dispatch errors leave session in idle state [6.75ms]
model override > dispatchPrimary integration > override bypasses alias resolution and routes directly [11.77ms]
model override > dispatchPrimary integration > override works even when alias is missing [13.77ms]
model override > dispatchPrimary integration > override with unknown provider fails [7.67ms]
model override > dispatchPrimary integration > override with invalid format fails [11.18ms]
model override > dispatchPrimary integration > override with no api key fails [11.36ms]
abort controller registry > dispatchPrimary integration > controller is registered while runPrimary is executing [8.34ms]
abort controller registry > dispatchPrimary integration > controller is deregistered after successful completion [13.50ms]
abort controller registry > dispatchPrimary integration > controller is deregistered after error result [8.30ms]
abort controller registry > dispatchPrimary integration > controller is deregistered after runPrimary throws [14.25ms]
abort controller registry > dispatchPrimary integration > abort signal is passed to runPrimaryFn [13.25ms]
abort controller registry > dispatchPrimary integration > getRunController returns undefined for unknown run [6.29ms]
list project sessions summary > summary exposes kind for every session [7.71ms]
list project sessions summary > summary resolves created_by_user from created_by [8.56ms]
list project sessions summary > created_by_user is null when creator does not resolve [6.93ms]
list project sessions summary > ?kind=workflow filters the list to workflow sessions [5.99ms]
list project sessions summary > ?kind=chat returns only chat sessions [8.55ms]
message regenerate handler > happy path unthrottled returns fresh run_id and supersedes later messages [8.72ms]
message regenerate handler > returns 409 when target message is not operator role [8.55ms]
message regenerate handler > returns 409 when session state is running [6.89ms]
message regenerate handler > returns 409 when session state is queued [5.85ms]
message regenerate handler > returns 409 when session state is paused [8.33ms]
message regenerate handler > returns 409 when session state is ended [7.16ms]
message regenerate handler > returns 409 when session state is failed [5.76ms]
message regenerate handler > returns 404 for unknown session or unknown message [8.56ms]
message regenerate handler > throttled path returns queued receipt and does not dispatch primary [7.99ms]
message regenerate handler > mapMessageItem includes run error fields only for failed primary messages [8.13ms]
previousRunEndedAtToolBoundary > empty history is not a boundary [0.090ms]
previousRunEndedAtToolBoundary > history ending in an operator message is not a boundary [0.080ms]
previousRunEndedAtToolBoundary > primary message ending on text is not a boundary [0.080ms]
previousRunEndedAtToolBoundary > primary message carrying a tool call is a boundary [0.080ms]
previousRunEndedAtToolBoundary > an injected system reminder after a tool-call primary does not mask the boundary [0.040ms]
previousRunEndedAtToolBoundary > a superseded tool-call primary is ignored; older text primary wins [0.030ms]
previousRunEndedAtToolBoundary > a disabled tool-call primary is ignored [0.030ms]
previousRunEndedAtToolBoundary > most recent primary decides: text after a prior tool call is not a boundary [0.040ms]
postMessageCore continue-suppression > continuation with a tool-call tail suppresses the operator message [7.94ms]
postMessageCore continue-suppression > continuation with a text tail persists the operator message [9.51ms]
postMessageCore continue-suppression > no continuation flag always persists the message (baseline) [7.30ms]
postMessageCore continue-suppression > an operator literally typing 'continue' without the flag is never suppressed [9.12ms]
postMessageCore continue-suppression > suppressed continuation still starts a new run [7.66ms]
loadNativeBindings > tier 1: loads from KAGED_NATIVES_PATH when it points to a valid .node file [98.43ms]
loadNativeBindings > tier 2: loads from beside execPath when .node is there [119.71ms]
loadNativeBindings > tier 3: falls through to loadNative() when no .node file is found [0.360ms]
loadNativeBindings > prefers tier 1 (KAGED_NATIVES_PATH) over tier 2 (beside execPath) [142.93ms]
loadNativeBindings > caches bindings after the first successful call [86.67ms]
loadNativeBindings > falls through to loadNative() when resolveNativesPath throws [0.300ms]
loadNativeBindings > propagates require() error when a resolved .node file is corrupt [0.800ms]
notification presence gate > zero sockets returns 0 [0.240ms]
notification presence gate > matching session socket counts [0.230ms]
notification presence gate > non-matching session socket does not count [0.110ms]
notification presence gate > session and system sockets roll up together [0.240ms]
notification router > live operator sends only in-app [1.69ms]
notification router > away operator sends all eligible tier-3 channels [0.350ms]
notification router > silent bell suppresses tier-3 when away [0.200ms]
notification router > run.completed is suppressed by attention-only routing [0.270ms]
notification router > throwing channel is converted to failed outcome [0.420ms]
notification router > rate limits duplicate attention events within five seconds [0.380ms]
notification router > resolveRouting uses set-not-merge layers [0.240ms]
notification normaliser > buildAttentionEvent creates a valid notification event [2.40ms]
notification normaliser > buildCompletionEvent creates a valid completion notification [0.100ms]
VAPID resolution > resolves inline PEM from local config [2.66ms]
VAPID resolution > resolves PEM from env when config is absent [0.750ms]
VAPID resolution > reads generated file when present [1.07ms]
VAPID resolution > auto-generates a 0600 key file on first need [1.05ms]
notification handlers > lists registered channels [0.210ms]
notification handlers > returns VAPID public key when available [0.170ms]
notification handlers > creates a push subscription [0.380ms]
notification handlers > lists subscriptions for current operator [0.210ms]
notification handlers > deletes a push subscription [0.220ms]
notification handlers > sets session bell and returns updated session [0.410ms]
notification handlers > dispatches synthetic notification test events [0.470ms]
web push channel > encryptPushPayload produces a deterministic RFC8188 payload [1.74ms]
web push channel > successful delivery returns delivered and sets VAPID headers [2.80ms]
web push channel > 410 responses reap expired subscriptions [1.49ms]
web push channel > 429 respects Retry-After backoff [1.71ms]
web push channel > non-429 client errors are non-retryable [1.52ms]

Mentioned in

Type Document
adr ADR-0004: Runtime is Bun + TypeScript
adr ADR-0010: kaged supports per-user and system-wide deployment, equally
adr ADR-0011: Projects are portable; operator-local concerns live in local config
adr ADR-0016: Streaming-first UI — live data and operator abort are non-negotiable
adr ADR-0017: Guests are first-class identities with daemon-managed credentials
adr ADR-0022: Agents are recursive; tools and cage are per-agent
adr ADR-0030: Log streaming via Server-Sent Events
adr ADR-0031: An assistant turn is an ordered transcript of parts, not a flattened bubble
adr ADR-0033: Tool dot-namespace convention
adr ADR-0036: Unified user identity — shared SSO, three-tier roles, and route-scope authorization
adr ADR-0039: @kaged/wire is the normative wire contract; daemon and UI must consume it
adr ADR-0040: Multi-daemon operator UI — runtime daemon registry, cross-origin hosting, bearer transport
adr ADR-0041: Containerised daemon — single-binary build, distribution image, Kubernetes operation
adr ADR-0042: Package distribution, CI, and the release pipeline
adr ADR-0045: Project wiki — markdown knowledge base as a DSL surface
adr ADR-0049: Providers are dynamically-loaded modules from an operator-local store; catalog from a self-hosted models.dev mirror; @kaged/llm becomes resolver + loader + middleware
spec Spec: Agent Tooling
spec Spec: Agent Harness
spec Spec: Daemon
spec Spec: HTTP + WebSocket API
spec Spec: Issues
spec Spec: LLM Provider Interface
spec Spec: Local config
spec Spec: Operational Logging
spec Spec: Session notifications (daemon side)
spec Spec: Plugin Host
spec Spec: Project DSL
spec Spec: project.recipe
spec Spec: Project Terminals
spec Release pipeline
spec Spec: Sandbox
spec Spec: Session Manager
spec Spec: Shared SSO Relay (@kaged/sso)
spec Spec: Task Runner
spec Spec: Users, Roles, and Project Grants
spec Spec: @kaged/wire
spec Spec: Workflows