summaryrefslogtreecommitdiffhomepage
path: root/packages
AgeCommit message (Collapse)Author
2026-06-28fix(vision): tell vision agents not to use tools, just describe images directlyAdam Malczewski
Kimi was trying to use Python tools to analyze images rather than just describing them. Updated both vision system prompts (consult_vision and image compaction) to explicitly instruct: do not use any tools unless specifically asked to — just use your vision to see the image and describe it directly.
2026-06-28fix(conversation-store): msgIdx collision merges messages across turns + ↵Adam Malczewski
reconcile drops thinking-only messages Root cause of tool-calls-in-thinking bug: append() assigns msgIdx as a LOCAL index (reset to 0 per call), but load() grouped chunks by msgIdx alone. Since the orchestrator persists messages one at a time (append([user]) at turn start, then append([assistant, ...toolResults]) per step), all single-message appends share msgIdx=0 and collapse into one giant user-role message. The model loses its prior assistant responses and tool-call history, falls back to text-based tool-call syntax inside reasoning_content, and the turn ends with finish_reason stop (no structured tool_calls detected). Fix 1 (store.ts load()): split message boundaries on role change too, not just msgIdx. Handles the alternating user/assistant/tool pattern correctly. Fix 2 (reconcile.ts hasContent): include thinking chunks as valid content so thinking-only assistant messages are not silently dropped on load. The buggy seq-14 output (assistant, thinking-only) was being deleted by reconcile, destroying evidence of the bug. Verified: load() on the affected conversation now produces 9 correct messages (was 3 merged). All 1999 tests pass. See notes/tool-call-in-thinking-bug.md.
2026-06-28fix(predev): resolve merge conflicts between concurrency-fixes and ↵Adam Malczewski
workspace-star
2026-06-28Merge branch 'feature/workspace-star' into predevAdam Malczewski
# Conflicts: # packages/provider-concurrency/src/concurrency-manager.ts # packages/provider-concurrency/src/extension.ts
2026-06-28fix(concurrency): usage-gate fast-path overshoot + fetchUsage rejection ↵Adam Malczewski
safety + persist auto-reduce notice Bug 1 (overshoot): acquire() fast-path now queues while a recycle-poll is in flight (gatePolling), and the recycle defers its inFlight decrement until the usage poll resolves — holding inFlight inflated through the poll window so a concurrent caller cannot sneak through before upstream confirms room. The gated path admits exactly one waiter per fresh poll (grantOne), robust against stale upstream counts. Common-case throughput preserved: the fast-path still grants immediately when no poll is in flight. Bug 2 (unhandled rejection): fetchUsage is wrapped in safeFetchUsage — a throwing getUsage() is caught, treated as undefined (cooldown-only fallback), and reported via the new onUsagePollError opt (warn-level). No unhandled promise rejection can crash the process. Bug 3 (lost notice): loadLimits now calls restoreLimit (a new lower-level state-seeding method that does NOT clear autoReduced) instead of setLimit (which is a manual user action that clears the notice). The auto-reduce marker (autoReducedFrom) is persisted under auto-reduce:<providerId> and restored on activate via loadAutoReduce, so the frontend banner survives a restart. +6 tests covering each bug.
2026-06-28fix(workspace-star): clean up starred cache on workspace delete + warn when ↵Adam Malczewski
concurrency service absent Bug 1 (MEDIUM): In-memory starred cache leaks IDs for deleted workspaces. The starredWorkspaces Set in the concurrency manager never cleaned up when a workspace was deleted. FIX: the DELETE /workspaces/:id route now calls concurrencyService.notifyWorkspaceStarred(id, false) after deleting, so the deleted workspace ID is removed from the in-memory cache (preventing stale IDs and preventing a re-created workspace with the same slug from inheriting the old starred state). Bug 2 (MEDIUM): Stale in-memory priority when concurrency extension is absent. If provider-concurrency is not loaded, the star toggle persisted but the in-memory priority cache was never updated (the optional chaining ?. silently skipped the call). Already-queued agents kept their old priority until restart. FIX: the star/unstar routes now check if concurrencyService is defined and log a warning when it is absent, making the degraded behavior visible. The starred state still persists correctly — it just does not affect in-memory scheduling until the extension is loaded. Tests: +5 (star round-trip with concurrency notification, invalid slug 400, delete cleans up cache, absent-service warning log). All 1970 tests pass.
2026-06-28Merge branch 'dev' into feature/concurrency-fixesAdam Malczewski
2026-06-28merge: bring dev crash fixes into feature/workspace-starAdam Malczewski
Merge dev to restore production-critical crash fixes that were committed after the branch was cut: SSH pool permanent error listener, uncaughtException/ unhandledRejection guards in host-bin, MemoryMax=24G circuit breaker, memory telemetry logging, LSP disable precaution, and crash investigation notes. The provider-wrapper.test.ts:102 test passes — it was already fixed by the acquire() signature change (adding workspaceId parameter) that realigned the test arguments. No additional test fix needed. Verification: typecheck 0 errors, 1965 tests pass, biome clean.
2026-06-28Merge branch 'dev' into feature/workspace-starAdam Malczewski
2026-06-28feat(workspace-star): starred workspace priority for concurrency limitingAdam Malczewski
2026-06-28feat(concurrency-fixes): usage-gate + adaptive headroom + configurable cooldownAdam Malczewski
2026-06-28fix(ssh,host-bin): permanent pooled-client error listener + ↵Adam Malczewski
uncaughtException/unhandledRejection guards Root cause of the live production crash (exit-1 'Timed out while waiting for handshake'): the pooled ssh2.Client had no permanent 'error' listener after connect, so a post-connect ssh2 error escaped as an uncaught EventEmitter 'error' with no process-level guard. See notes/crash-investigation-findings.md §1. - packages/ssh/src/pool.ts: attach a permanent 'error' listener to the pooled client in buildConnection that sets state=error, logs (alias, message, level), and does not throw; cleanup() no longer removes it. - packages/host-bin/src/main.ts: add process.on('uncaughtException') (graceful shutdown after logging) and process.on('unhandledRejection') (log + continue), both logging message/stack, memory snapshot, activeConversations count, and timestamp so the failure site is observable.
2026-06-28fix: disable LSP + change memory telemetry interval to 15sAdam Malczewski
- Disable LSP extension (import + CORE_EXTENSIONS) due to crashes - Make transport-http tolerate LSP being absent (optional getService) - Remove lsp from transport-http dependsOn manifest - Change memory telemetry sample interval from 60s to 15s
2026-06-28feat(observability): periodic memory-usage logging to localize leak sourceAdam Malczewski
2026-06-27fix(lsp): bound pushDiagnostics cache — evict oldest entries for unopened ↵Adam Malczewski
background files
2026-06-27fix(lsp): fix crashes (optional chaining, fs.watch error) + memory leak ↵Adam Malczewski
(document lifecycle) + leaked init promises
2026-06-27Merge branch 'feature/vision-handoff' into devAdam Malczewski
# Conflicts: # packages/session-orchestrator/src/extension.ts # packages/session-orchestrator/src/orchestrator.ts
2026-06-27feat(vision): prefix consultation tab titles with 'IMAGE - 'Adam Malczewski
When a non-vision model (e.g. GLM) calls consult_vision, the new Kimi consultation tab now shows 'IMAGE - <question>' instead of the bare question-derived title, making image-consultation tabs visually distinguishable from normal conversation tabs. - Add formatConsultationTitle(question) pure helper (pure.ts): prefixes 'IMAGE - ' and truncates the question to 80 chars (matching the conversation store's TITLE_MAX) with an ellipsis. - Add setConversationTitle dep to VisionHandoffDeps, wired in the extension to the conversation store's setConversationTitle. - Call it in consultVision BEFORE the turn starts so the title is correct from the first moment (the store keeps a non-'Untitled' title on first message append). Best-effort: a title-write failure logs a warning but does not break the consultation. - Tests: 4 pure + 2 service (title set + optional-dep graceful).
2026-06-27feat(vision): store images in tmp dir instead of SQLite — compact URLs + ↵Adam Malczewski
purge on compaction/close
2026-06-27feat(provider-concurrency): persist concurrency limits across reboots via ↵Adam Malczewski
host storage
2026-06-27feat(concurrency): add "queued" ConversationStatus — emit when request ↵Adam Malczewski
blocks on acquire, re-emit "active" when slot granted
2026-06-27feat(vision): image compaction for vision-capable models + global vision ↵Adam Malczewski
settings
2026-06-27fix(vision): detect umans kimi + qwen models as vision-capable (not just kimi)Adam Malczewski
2026-06-27feat(vision-handoff): model-directed consult_vision tool replacing ↵Adam Malczewski
auto-transcription
2026-06-27fix(vision-handoff): omit temperature on vision transcription call (Kimi ↵Adam Malczewski
rejects temperature: 0) The vision handoff hardcoded temperature: 0 for the transcription sub-call, but the Moonshot/Kimi vision model only allows temperature: 1 (or omitted), causing an HTTP 400 "invalid temperature: only 1 is allowed for this model" that blocked the entire image analysis for non-vision models like GLM 5.2. Fix: omit temperature entirely so each vision provider uses its own default — the truly universal, provider-agnostic approach (different providers have different temperature constraints).
2026-06-27feat(provider-concurrency): add release cooldown (200ms) to prevent N+1 ↵Adam Malczewski
overshoot from provider accounting lag
2026-06-27feat(vision-handoff): implement vision for capable models and universal ↵Adam Malczewski
vision handoff
2026-06-27feat(provider-concurrency): implement per-provider in-memory concurrency ↵Adam Malczewski
limits with oldest-agent-first scheduling
2026-06-27style: reformat heartbeat merge to 2-space indentationAdam Malczewski
2026-06-27Merge branch 'dev' into feature/heartbeatAdam Malczewski
# Conflicts: # packages/host-bin/package.json # packages/host-bin/src/main.ts # packages/session-orchestrator/src/orchestrator.ts # packages/system-prompt/src/service.test.ts # packages/system-prompt/src/service.ts # packages/system-prompt/src/types.ts # packages/transport-contract/package.json # packages/transport-http/package.json # packages/transport-http/src/app.test.ts # packages/transport-http/src/app.ts # packages/transport-http/src/extension.ts # packages/transport-http/tsconfig.json # tsconfig.json
2026-06-27feat(heartbeat): GET /workspaces/:id/heartbeat/next-run endpoint (CR-HB-3)Adam Malczewski
2026-06-27feat(heartbeat): send heartbeat conversations to a dedicated heartbeat workspaceAdam Malczewski
2026-06-26style: switch from tabs to 2-space indentationAdam Malczewski
2026-06-26feat(heartbeat): resolve empty systemPrompt to global default (CR-HB-2)Adam Malczewski
2026-06-26feat(heartbeat): resolve [type:name] variables in heartbeat prompts (CR-HB-1)Adam Malczewski
2026-06-26fix(kernel): disable MAX_STEPS limit (0 = unlimited)Adam Malczewski
Agents were being cut off mid-task at 50 steps. The MAX_STEPS=50 hardcoded limit was silently terminating turns while the model was actively making tool calls, leaving conversations idle with a dangling tool-result as the last chunk. Setting MAX_STEPS to 0 disables the limit — the loop runs until the model stops making tool calls naturally or the abort signal fires. The max-steps code path is preserved for when MAX_STEPS > 0.
2026-06-26feat(heartbeat): workspace heartbeat loop with configurable AI monitoringAdam Malczewski
2026-06-25fix(ssh): POST /computers/:alias/test hangs after successful SSH connectAdam Malczewski
The test endpoint's runProbe() waited for the ssh2 stream's 'close' event, which some SSH servers never emit for short-lived exec channels (the command 'true' exits instantly). This caused the promise to hang forever — the HTTP response never returned, and the FE's Test spinner spun indefinitely. Three fixes: 1. runProbe now resolves on the 'exit' event (not 'close') — the command has finished and the exit code is available. 'close' is kept as a fallback. Stream data/stderr are drained to prevent buffer deadlocks. 2. runProbe has a 15s timeout safety net — if the exec callback or 'exit' event never fires (e.g. server requires a pty for exec), the probe resolves false instead of hanging forever. 3. The entire test() method is wrapped in a 30s Promise.race timeout — even if pool.acquire() or pool.drop() hangs, the endpoint ALWAYS responds with { ok, error? }. The probe is fully non-interactive (no blocking prompts). tsc EXIT 0, biome clean, 1756 tests pass.
2026-06-25feat(ssh): discover computers from ~/.ssh/known_hosts + remote system-promptAdam Malczewski
Two improvements to the SSH support feature: 1. KNOWN_HOSTS DISCOVERY (packages/ssh): Computers are now auto-discovered from ~/.ssh/known_hosts (every hostname you've ever connected to) in ADDITION to ~/.ssh/config (explicit Host aliases). Config entries take precedence (full params); known_hosts entries get defaulted params (User=defaultUser, IdentityFile=null→pool probes default keys, Port from [host]:port or 22, knownHost=true). Zero-config — no ~/.ssh/config file needed; hosts just appear. Reject list: dispatch.toml [ssh].reject = [...] (glob patterns like github.com, *.ts.net) filters noise from the catalog. Read from both the global ~/.config/dispatch/dispatch.toml and the project dispatch.toml. Parsed with Bun.TOML.parse (zero deps). Only filters discovery (catalog); specific lookups (getComputer/getStatus/test/connect) ignore the reject list (it's a visibility filter, not access control). New pure functions: parseKnownHosts(), isRejected(), globMatch(). +26 tests. tsc EXIT 0, biome clean, 1756 tests pass. 2. REMOTE SYSTEM-PROMPT AWARENESS (packages/system-prompt): When a conversation has a computerId set (remote turn), the system prompt now resolves system:os, system:hostname, git:branch/git:status, and file: reads against the REMOTE machine — not the local host. Previously the prompt always said 'Arch Linux (WSL)' + local hostname even when the agent was connected to a remote Artix Linux machine. The ResolverAdapters' hostname()/platform() are now async (so a remote adapter can run 'hostname'/'uname -s' over SSH). The system-prompt extension builds remote adapters from the ExecBackend (readFile→SFTP, spawn→SSH exec). Cache invalidation now checks computerId (switching computers rebuilds the prompt). The compaction path also threads computerId. @dispatch/system-prompt now depends on @dispatch/exec-backend.
2026-06-25Merge branch 'dev' into feature/ssh-supportAdam Malczewski
Brings dev's retry-with-backoff (the transient `provider-retry` AgentEvent the web frontend consumes) + the LSP-dead-server per-edit-hang fix into the SSH feature branch, alongside the SSH waves 0-5c. All code files auto-merged cleanly (run-turn.ts, orchestrator.ts, runtime.ts, wire/index.ts, tool-edit-file/extension.ts, run-turn.test.ts — both computerId threading and retry-with-backoff coexist). Only tasks.md conflicted (status section — orchestrator-resolved; both feature sections kept). Verified post-merge: tsc -b EXIT 0, biome clean (391 files), 1730 vitest pass +6 sshd-integration skipped (was 1690; +40 from dev's retry/LSP tests). Wire dist rebuilt so the FE can re-sync the pinned @dispatch/wire dep and pick up BOTH provider-retry AND the SSH Computer/defaultComputerId types. No merge or push (into dev or otherwise).
2026-06-25Merge branch 'feature/lsp-bugfix' into devAdam Malczewski
2026-06-25fix(lsp): stop per-edit hangs on dead/slow servers (10s cap + skip + self-heal)Adam Malczewski
The LSP diagnostics path hung up to 60s per edit whenever a configured Ruby language server was dead or slow (the reported Steep langserver case): a killed/crashed server was never detected (stayed "connected" forever), servers were queried sequentially with a 60s budget each, and a corrupted-but-alive server (Steep's ~3h phantom-SyntaxError drift) had no recovery. Four fixes, all in packages/lsp/ (the tool-edit-file call site lowered to 10s): 1. Dead-process detection: SpawnedProcess.onExit (Bun proc.exited) + stdout-end defence flip the client to error, dispose the rpc, kill the proc. The manager re-spawns a fresh server after the 30s backoff. Dead servers are now skipped (0s) instead of polled for 60s. 2. Concurrent fan-out + 10s hard cap: new aggregateDiagnostics queries all matching servers at once, each capped at 10s. A non-responder is skipped with "LSP took too long (>10s), skipped — raise this to the user" instead of blocking the fast server's results. Replaces the vague "unusually long" warning (now structurally impossible: slow is always false). 3. Corruption self-heal: a detector flags a server re-emitting identical non-empty diagnostics despite the file changing; after 5 repeats the client is marked broken and re-spawned. Clean files never trip it. (Acknowledged false-positive risk on persistent unfixed errors; CLI type-check gate stays authoritative.) 4. sendRequest timeout: hover/definition/references cap at 10s so they can't hang the turn against a dead server; the initialize handshake keeps its 45s race. Verification: typecheck clean; 1573 tests pass (96 files), +15 new LSP tests (86 in packages/lsp); biome clean. No kernel/contract changes; onExit is internal to packages/lsp.
2026-06-25feat(kernel): retry-with-backoff on retryable provider errorsAdam Malczewski
When the upstream LLM API returns a retryable error (HTTP 429 / 5xx "overloaded"), the kernel now retries provider.stream() with a stepped backoff, visibly, until the 8h cumulative-sleep budget is exhausted — then emits the final error and seals the turn. Retries fire only when no content was emitted yet this step (safety invariant: never duplicate partial output). - wire: new transient TurnProviderRetryEvent AgentEvent variant (emitted before each sleep; not persisted to model history). - kernel contracts: RetryStrategy (pure delayFor + injected sleep) + optional retry? on RunTurnInput (omit = no retry, backward-compatible). - kernel run-turn: retry loop in executeStep; providerRetryEvent constructor. Kernel imports no timer (sleep injected). - session-orchestrator: concrete schedule (5s..30m, repeat 30m, 8h budget) + abortable setTimeout sleep, wired into RunTurnInput.retry. tsc -b EXIT 0; biome clean; 1574 vitest pass (+16 new: 11 kernel retry tests with injected fake sleep + pure delayFor, zero @dispatch/* mocks; 5 schedule tests). Transports unchanged (transport-ws forwards AgentEvent verbatim in chat.delta; transport-http is generic JSON.stringify). Plan: notes/retry-with-backoff-plan.md. tasks.md updated with milestone + optional CLI-renderer roadmap follow-up.
2026-06-25feat(ssh): wave 5c — host-bin registers exec-backend + ssh; transport-http ↵Adam Malczewski
barrel Wave 5c (final wiring) of transparent SSH support. - host-bin: register exec-backend + ssh in CORE_EXTENSIONS (exec-backend before the tool extensions that dependsOn it; ssh after, provides the remote-backend factory + ComputerService at boot). +@dispatch/exec-backend/@dispatch/ssh deps + tsconfig refs. - transport-http: CR-5 — re-export computerServiceHandle + ComputerService type from the package barrel (src/index.ts), mirroring lsp/mcp handles, so ssh imports the typed symbol cleanly (no more dist/seam.js subpath workaround). - orchestrator: added the @dispatch/exec-backend dep the host-bin agent missed + bun install. LIVE-VERIFIED: bun packages/host-bin/src/main.ts boots clean ('Dispatch booted', no disabled extensions) — exec-backend + ssh + all tool extensions load together. Verified: tsc -b EXIT 0, biome clean, 1690 vitest pass (+6 sshd-integration skipped). DEFERRED (CR-6): listComputers usageCount stays 0 until a conversation-store count-by-alias helper is added (non-blocking). Refs: notes/ssh-support-plan.md. No merge or push.
2026-06-25feat(ssh): wave 5b — the ssh package (remote ExecBackend over ssh2)Adam Malczewski
Wave 5b of transparent SSH support. NEW standard extension @dispatch/ssh makes remote execution actually work over SSH, transparently. ssh2 verified to run under Bun (load-bearing decision #1 confirmed: connects to local sshd :22 + execs). - config.ts: ~/.ssh/config reader via ssh-config -> Computer[]/ComputerEntry[] (read-only discovery; resolves hostName/port/user/identityFile/knownHost). - hostkey.ts: known_hosts auto-trust-and-pin (present->verify/reject-on-mismatch, absent->accept+append; the accept-new analog). - errors.ts: pure ssh2/SFTP -> node:fs-style .code error mapping (so tools' existing ENOENT branches work unchanged). - pool.ts: SshConnectionPool (per-alias ssh2.Client, lazy connect, keep-alive, idle reap ~15m); key-only auth from ~/.ssh (config IdentityFile or default id_ed25519/id_rsa); no agent-forwarding, no PTY. - backend.ts: SshExecBackend implements ExecBackend (spawn via client.exec with shell-quoted cwd; fs via SFTP). - service.ts + extension.ts: activate provides BOTH handles the other units consume — remoteExecBackendFactoryHandle (exec-backend: computerId->SshExecBackend) AND computerServiceHandle (transport-http: listComputers/getComputer/getStatus/test). - orchestrator: added packages/ssh to root tsconfig.json refs + bun install. Tests: 45 pass + 6 sshd-integration skipped (it.skipIf(!process.env.SSH_TEST_HOST)). Verified: tsc -b EXIT 0, biome clean, 1690 vitest pass (was 1641, +49). CRs for wave 5c: host-bin registration; CR-5 transport-http barrel re-export; CR-6 usageCount wiring (deferred-ok, defaults to 0). Refs: notes/ssh-support-plan.md (decisions §0.5/§13). No merge or push.
2026-06-25feat(ssh): wave 5a — exec-backend remote-backend factory handleAdam Malczewski
exec-backend declares remoteExecBackendFactoryHandle (a consumer-defined ServiceHandle<(computerId) => ExecBackend>) that the ssh package will provide (standard→core layering). The resolver's computerId-set branch now lazy-looks-up this factory (at tool-execute time, runtime) and calls it; if ssh isn't loaded, getService throws → a clear 'SSH remote execution is not configured' error. The computerId-undefined (local) branch is byte-identical to before. This is the seam wave 5b (the ssh package) plugs into. +tests for both branches. Verified: tsc -b EXIT 0, biome clean. No merge or push.
2026-06-25feat(ssh): wave 4 — computer HTTP/WS endpoints + chat computerId threadingAdam Malczewski
Wave 4 of transparent SSH support (3 parallel owner-agents on disjoint packages). - transport-http: computer routes — GET /computers, GET /computers/:alias, GET /computers/:alias/status, POST /computers/:alias/test (all delegate to a new ComputerService seam, graceful []/disconnected when ssh not loaded); GET/PUT/DELETE /conversations/:id/computer; PUT /workspaces/:id/default-computer (mirror the cwd/default-cwd routes); /chat threads computerId into the orchestrator. Defines ComputerService interface + computerServiceHandle (defineService<ComputerService>('ssh')) in seam.ts — the seam the ssh package provides via host.provideService in wave 5. - transport-ws: chat.send + chat.queue thread computerId onto the route result (mirrors cwd/workspaceId), forwarded to the orchestrator input. - mcp: CR-1 fix — filterMcpTools now preserves computerId on the returned ToolAssembly (mirrors cwd preservation), so the filter chain stays consistent. - orchestrator: added @dispatch/wire dep to transport-http (build/config, my lane) so its seam.ts Computer/ComputerEntry import resolves. Verified: tsc -b EXIT 0, biome clean, 1641 vitest pass (was 1620, +21). Refs: notes/ssh-support-plan.md (decisions §0.5/§13). No merge or push.
2026-06-25feat(ssh): wave 3 — session-orchestrator computerId threading + ↵Adam Malczewski
transport-contract API types Wave 3 of transparent SSH support (2 parallel owner-agents on disjoint packages). - session-orchestrator: thread computerId end-to-end through the turn, mirroring cwd exactly — StartTurnInput/EnqueueInput/handleMessage/TurnLifecyclePayload gain computerId; runTurnDetached resolves effectiveComputerId via conversationStore.getEffectiveComputer(convId, override), persists the override, threads into RunTurnInput + ToolAssembly. Register a remote-degradation tools-filter (filterRemoteIncompatibleTools) that, when assembly.computerId is set (REMOTE), drops the 'lsp' tool + any '__'-namespaced MCP tool (local processes that can't see remote files); LOCAL (computerId undefined) is a passthrough — byte-identical to today. +21 tests. - transport-contract: + computerId on ChatRequest (flows to ChatSendMessage) + computer endpoint API types (ComputerListResponse, ComputerResponse, ComputerStatusResponse, SetConversationComputerRequest, ConversationComputerResponse, SetWorkspaceDefaultComputerRequest, TestComputerResponse) — mirrors the cwd/workspace endpoint types. - CR-1 (non-blocking, folded into wave 4): MCP filter doesn't preserve computerId on the returned ToolAssembly. - cache-warming computerId threading intentionally DEFERRED (user request) — noted as a known performance-only limitation in tasks.md. Verified: tsc -b EXIT 0, biome clean, 1620 vitest pass (was 1599, +21). Refs: notes/ssh-support-plan.md (decisions §0.5/§13). No merge or push.
2026-06-25feat(ssh): wave 2 — route filesystem/shell tools behind ExecBackendAdam Malczewski
Wave 2 of transparent SSH support (4 parallel owner-agents on disjoint tool packages). The tools now resolve an ExecBackend per-call from ctx.computerId and call backend.spawn / backend.readFile / etc. instead of node:fs and node:child_process directly — so they are transport-agnostic (local now; remote over SSH later, transparent to the agent). Still LOCAL-ONLY this wave (computerId always undefined -> LocalExecBackend, behavior-identical). - tool-shell: factory takes resolveBackend; execute calls backend.spawn. spawn.ts DELETED (realSpawn was a verbatim duplicate of exec-backend's LocalExecBackend.spawn — logic moved to the sanctioned shared package). manifest dependsOn:[exec-backend]; host.getService at activation. - tool-read-file: readFile/stat/readdir -> backend.* (pure logic untouched; ENOENT .code branches kept). - tool-write-file: exists/stat/writeFile -> backend.* (pure logic untouched). - tool-edit-file: readFile/writeFile -> backend.* + forward-compatible REMOTE diagnostics skip (ctx.computerId set -> skip LSP, return empty — plan §6.1; local path byte-identical to today). LSP lookup stays lazy. - orchestrator: pre-wired @dispatch/exec-backend dep into the 4 tool package.jsons + bun install (build/config, my lane) so isolated verify resolved cleanly; agents added the ../exec-backend tsconfig ref. Verified: tsc -b EXIT 0, biome clean, 1599 vitest pass (was 1592). Refs: notes/ssh-support-plan.md (decisions §0.5/§13). No merge or push.
2026-06-25feat(ssh): wave 1 — ExecBackend + computer data model + runtime threadingAdam Malczewski
Wave 1 of transparent SSH support (parallel owner-agents on disjoint packages, plus the orchestrator-authored kernel contract seam from wave 0): - packages/wire: + Computer/ComputerEntry (read-only view over ~/.ssh/config Host aliases) + Workspace.defaultComputerId (string|null, null=local). Types only; 3 conformance tests. - packages/exec-backend (NEW core extension): the ExecBackend abstraction (spawn + minimal fs surface) the bundled tools will program against instead of node:fs/child_process. LocalExecBackend wraps today's node calls (behavior-identical; node:fs-style .code errors). execBackendHandle + ExecBackendResolver (sync; computerId undefined -> local; set -> throws until the ssh package wires remote resolution in wave 5). 20 tests. - packages/kernel (runtime only): thread computerId through dispatch.ts + run-turn.ts exactly as cwd is threaded (opaque, forwarded to ToolExecuteContext; absent = local = byte-identical to today). +2 tests. - packages/conversation-store: computer (SSH alias) assignment + resolution mirroring cwd — WorkspaceRow.defaultComputerId + setWorkspaceDefaultComputerId + getComputerId/setComputerId/clearComputerId + getEffectiveComputer (override -> per-conv -> workspace default -> null/local). Fixes the 3 Workspace literal sites the new required wire field broke. +18 tests. - orchestrator: root tsconfig.json ref for exec-backend + bun install. Verified: tsc -b EXIT 0, biome clean, 1592 vitest pass (was 1549, +43). Refs: notes/ssh-support-plan.md (decisions §0.5/§13). No merge or push.