| Age | Commit message (Collapse) | Author |
|
|
|
|
|
agents)
A new per-workspace heartbeat setting `inactiveOnly` (default true): when on, the
heartbeat SKIPS a scheduled fire whenever the configured workspace has any active
agents — a conversation whose persisted status is "active" (driving a turn) or
"queued" (waiting on the message queue). The fire is silently skipped (no run
recorded); the scheduler re-arms and retries at the next interval. Set false to
fire unconditionally (the prior behavior).
The check is wired against conversationStore.listConversations({ workspaceId,
status: ["active","queued"] }) at fire time — the orchestrator sets "active" on
turn start and "idle" on settle, so it is the live busy/idle signal. The spawned
heartbeat conversation lives in the dedicated heartbeat workspace, so a heartbeat
run never counts as an active agent of the configured workspace (no self-block).
- transport-contract: add inactiveOnly to HeartbeatConfig + UpdateHeartbeatRequest
- heartbeat config-store: default true, apply/persist, legacy-parse default true
- heartbeat service: injectable hasActiveAgents dep + skip logic in fire()
- heartbeat extension: wire hasActiveAgents against the conversation store
- transport-http: validate inactiveOnly (boolean) on PUT /workspaces/:id/heartbeat
- tests: config-store (+4), heartbeat service (+7), transport-http (+3)
- notes/heartbeat-setting-handoff.md: API contract for the frontend
Verification: typecheck EXIT 0; tests 2013 passed | 6 skipped; biome EXIT 0.
|
|
Setting the title in the HTTP /chat route BEFORE the turn started pre-created
the conversation meta, which made the orchestrator's getConversationMeta ===
null newness check falsely report an EXISTING conversation. As a result
ensureWorkspace / setWorkspaceId / the first-turn systemPromptService.construct
were ALL skipped for summoned conversations with a title — leaving them with no
assigned workspace and breaking downstream cwd resolution + system-prompt init.
Fix: the title is no longer set in the route. It is threaded through
StartTurnInput -> handleMessage -> startTurn -> runTurnDetached and persisted
via setConversationTitle INSIDE workspaceSetupPromise, AFTER the newness check
+ workspace assignment (so init still fires for new conversations) and BEFORE
the first message append (so the append's auto-derived title does not overwrite
it). The title set is best-effort: a failure is logged but does not break the
turn (the workspace setup already succeeded).
The /chat route now forwards title into the orchestrator input instead of
calling setConversationTitle itself.
Tests:
- app.test.ts: route tests rewritten to assert the route forwards title to the
orchestrator and does NOT call setConversationTitle itself.
- orchestrator.test.ts: 5 new regression tests, including the critical 'titled
new conversation still gets workspace assigned + system-prompt constructed +
title set', an ordering test (title set after getMeta/ensureWorkspace/
setWorkspaceId), and a resilience test (turn completes if setConversationTitle
throws).
Verification: typecheck EXIT 0; test 2026 passed | 6 skipped | 0 failed;
check EXIT 0 (12 pre-existing warnings in untouched files).
|
|
|
|
Add the ability to cancel/close a single queued message so it never runs:
while a turn is GENERATING and a user message sits in the steering queue
(waiting for delivery at the next tool-result boundary or carry into a new
turn), a client can remove one message by id. The cancelled message is never
delivered as steering and never carried into a new turn. Complements the
existing chat.queue enqueue (enqueue adds; cancel removes one).
Layers (all additive; nothing existing breaks):
- message-queue pure core: `cancel(state, conversationId, messageId)` —
splices a single message out by id, returns the post-cancel snapshot.
Idempotent (no-op if not found). Drops the key when the queue empties
(mirrors drain).
- message-queue service: `MessageQueueService.cancel()` — wraps the pure op,
pushes a surface update ONLY on a real change (queue shrank); a missing-id
cancel is a no-op with no surface push (mirrors drain's no-notify-on-empty).
- session-orchestrator: `cancelQueuedMessage({ conversationId, messageId })`
-> `{ cancelled, queue }`. The single entry transports call; resolves the
queue lazily (same as enqueue). Degrades to `{ cancelled: false, queue: [] }`
when the message-queue extension isn't loaded. `cancelled` is derived from
the queue length delta (true iff a message was removed).
API contract (documented for the frontend agent; see
frontend-cancel-queued-message-handoff.md):
HTTP: DELETE /conversations/:id/queue/:messageId
-> 200 QueueCancelResponse { conversationId, cancelled, queue }
(cancelled:false is a 200 idempotent no-op, not an error)
WS: chat.queue.cancel { type:"chat.queue.cancel", conversationId, messageId }
(additive to WsClientMessage). Fire-and-forget like chat.queue: success
is confirmed by the message-queue SURFACE updating (the cancelled
message leaves payload.messages). A missing-id cancel is a silent
no-op (no surface update, no error). Malformed (empty conversationId/
messageId) -> chat.error.
No new AgentEvent: a cancelled message never appears in the transcript (it
never runs). The existing message-queue surface already reflects the
post-cancel snapshot. Race-safe by construction: if the kernel drains the
queue (steering) or carries it into a new turn before the cancel runs, the
message is already gone -> cancel returns cancelled:false (a no-op).
Version bump: @dispatch/transport-contract 0.23.0 -> 0.24.0 (additive:
QueueCancelResponse + ChatQueueCancelMessage added to WsClientMessage).
@dispatch/wire unchanged (QueuedMessage.id is the cancel target).
No CLI command added: the CLI has no queue-listing affordance to discover a
messageId, so a CLI cancel would have no input source. The HTTP DELETE is
available for any non-WS client that knows the id (e.g. from a prior enqueue
response's queue[]).
Verification: tsc -b EXIT 0; vitest 2024 passed / 6 skipped (25 new tests);
biome EXIT 0 (0 errors).
|
|
Add a --title <title> flag to the dispatch CLI so a summoned agent's
conversation/tab gets a human-readable title at creation time instead of the
auto-derived default (Untitled until the first message append).
The title is carried as an additive optional ChatRequest.title field (the
HTTP client-server contract), parsed + trimmed server-side in parseChatBody,
and persisted via the conversation store's setConversationTitle in the POST
/chat route BEFORE the turn starts — so the title is set atomically with
creation and before --open signals the frontend to open the tab. No second
HTTP round-trip is needed.
A whitespace-only title is treated as absent (auto-derive); a non-string
title yields HTTP 400. A title-set failure is logged but never blocks the
turn (the title is a nicety, the answer is not). The change is purely
additive — no existing behavior or assertion changes.
Verification: typecheck EXIT 0; test 2021 passed | 6 skipped | 0 failed;
check EXIT 0 (12 pre-existing warnings in untouched files).
|
|
# Conflicts:
# packages/provider-concurrency/src/concurrency-manager.ts
# packages/provider-concurrency/src/extension.ts
|
|
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.
|
|
|
|
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.
|
|
|
|
|
|
|
|
- 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
|
|
# Conflicts:
# packages/session-orchestrator/src/extension.ts
# packages/session-orchestrator/src/orchestrator.ts
|
|
purge on compaction/close
|
|
blocks on acquire, re-emit "active" when slot granted
|
|
settings
|
|
vision handoff
|
|
limits with oldest-agent-first scheduling
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
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.
|
|
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.
|
|
Mirrors the existing GET /conversations/:id/lsp route exactly: gates on the
persisted then effective cwd (null → empty servers), returns 503 when the
MCP service isn't loaded, and maps McpServerStatus → McpServerInfo
(conditionally including `error` per exactOptionalPropertyTypes).
Wires mcpService into CreateServerOptions + extension activate via a plain
host.getService (mirroring lspService; "mcp" added to dependsOn, route added
to contributes.routes), adds the @dispatch/mcp workspace dep, and re-exports
mcpServiceHandle / McpService / McpServerStatus from seam.ts. Adds 4 tests
mirroring the LSP status tests.
|
|
Two issues found by decompiling the running dispatch-server binary
(handoff from a ruby-lsp setup in raylib-jamstack):
Issue 2 (blocker): a failed LSP server was "broken" FOREVER — the
manager's broken set was cleared only in shutdownAll(), so a server
that failed (bad env, missing binary, or a since-fixed config) stayed
state:"error" for the whole process. For an agent running *inside*
dispatch the only recovery (server restart) kills its own session.
Now a broken server self-heals when its resolved config changes since
it was marked broken (discrete event → no retry storm), with a bounded
backoff for transient failures.
Issue 1: .dispatch/lsp.json silently shadowed opencode.json's lsp key
with no warning and no source attribution. Now: shadow warning via
host.logger when both declare lsp; configSource populated on status
(.dispatch/lsp.json / opencode.json / built-in); spawn-failure error
strings name the config source.
Contract: additive configSource?: string on LspServerInfo
(@dispatch/transport-contract 0.20.0→0.21.0). transport-http passes it
through to the wire (was a field-by-field map that dropped it — CR
resolved by the transport-http owner).
tsc -b EXIT 0, biome clean, 1443 vitest pass.
|
|
A chat's selected provider + model is now persisted per conversation (like cwd
and reasoningEffort). Opening a conversation in a new browser recalls the
originally selected model instead of defaulting.
- transport-contract 0.19.0→0.20.0: ModelResponse + SetModelRequest types
for GET/PUT /conversations/:id/model.
- conversation-store: getModel/setModel (model:<id> key, mirrors
getReasoningEffort/setReasoningEffort); forkHistory copies model; empty
string clears.
- session-orchestrator: resolve model from persisted store when no per-turn
override; persist the resolved model so it sticks; warm path parity.
- transport-http: GET/PUT /conversations/:id/model endpoints with validation.
1433 vitest pass; tsc + biome clean.
|
|
- @dispatch/transport-contract 0.18.0 -> 0.19.0:
add workspaceId: string to ConversationOpenMessage and ConversationStatusChangedMessage
- session-orchestrator: include persisted workspaceId in conversationOpened/
conversationStatusChanged payloads
- transport-ws: forward workspaceId in WS broadcasts
- transport-http: POST /conversations/:id/open resolves workspaceId before emit
- FE handoff to 29ae: frontend-workspace-open-handoff.md
|
|
session-orchestrator:
- Wire systemPromptService as optional dep (lazy via host.getService)
- Regular turn: construct on first turn (new conversation), get on subsequent
turns, set on providerOpts.systemPrompt (cache-safe)
- Compaction: construct (fresh resolve) + append COMPACTION_SYSTEM_PROMPT
- 12 new tests (construct/get/service-unavailable/compaction)
transport-http:
- GET /system-prompt (returns template or DEFAULT_TEMPLATE)
- PUT /system-prompt (validate + setTemplate, 503 when unavailable)
- GET /system-prompt/variables (static catalog, always available)
- 6 new tests
system-prompt service: added getTemplate/setTemplate to interface + impl.
1396 vitest pass. typecheck + biome clean.
|
|
GET /conversations/:id/lsp was calling getEffectiveCwd directly, which falls
through to serverDefaultCwd (process.cwd()) when no conversation cwd is set.
Now gates on getCwd first: returns {cwd:null, servers:[]} when no cwd
persisted; only resolves via getEffectiveCwd + calls lspService.status when
a persisted cwd exists.
PUT /conversations/:id/cwd now accepts optional workspaceId — validates with
isValidWorkspaceSlug, then ensureWorkspace → setWorkspaceId → setCwd (assigns
the workspace before persisting cwd, so getEffectiveCwd resolves relative
cwds against the workspace defaultCwd, not the server default).
transport-contract 0.16.0→0.17.0 (additive SetCwdRequest.workspaceId;
LspStatusResponse.cwd comment updated).
1332 vitest pass.
|
|
cli (Wave 2+3)
session-orchestrator: workspaceId on StartTurnInput/EnqueueInput; effective cwd
resolution (getCwd → getEffectiveCwd); auto-create workspace on turn start;
warm parity (same effective cwd). 93 tests (+8).
transport-http: workspace routes (GET/PUT/DELETE /workspaces, title, default-cwd);
workspaceId threading on POST /chat + queue; ?workspaceId= filter on
GET /conversations; DELETE /conversations/:id/cwd (clears explicit cwd);
GET /conversations/:id/lsp uses effective cwd; slug validation. 166 tests.
transport-ws: workspaceId threading on chat.send + chat.queue. 32 tests.
cli: --workspace/-w flag; ConversationMeta test fakes fixed. 123 tests.
Full typecheck EXIT 0, biome clean. 1283 vitest + 199 transport bun pass
(1 pre-existing tool-shell failure unrelated to workspaces).
|
|
ModelInfo (kernel contract):
- Add contextWindow?: number field
OpenAI-stream listModels:
- Parse contextWindow from common field names (context_length,
context_window, max_context_length, max_tokens)
Transport-contract:
- ModelsResponse: add optional modelInfo map (model name → { contextWindow? })
- Add ModelMetadata type
- Rename CompactThresholdResponse → CompactPercentResponse
- Rename SetCompactThresholdRequest → SetCompactPercentRequest
Credential store:
- Add getModelInfo(modelName) method — resolves full ModelInfo
(including contextWindow) for a <credential>/<model> string
Transport-http:
- GET /models now includes modelInfo with contextWindow per model
- Rename compact-threshold endpoints → compact-percent
Session-orchestrator:
- Auto-compact now uses contextSize (not overcounted usage.inputTokens)
compared against contextWindow * (percent / 100)
- Default percent: 85 (was flat 350000)
- resolveModelInfo dep added to look up contextWindow
- Passes modelName from the settled turn to the compaction service
Conversation store:
- Rename getCompactThreshold/setCompactThreshold → getCompactPercent/setCompactPercent
- compactThresholdKey → compact-percent key
|
|
Add stopTurn to the orchestrator: aborts the in-flight turn's
AbortController without changing conversation status. The turn
seals normally (finishReason: 'aborted'), partial messages are
persisted, and the conversation transitions active → idle via the
normal settle path.
Distinct from closeConversation which marks the conversation closed.
- POST /conversations/:id/stop endpoint
- dispatch stop <id> CLI command
- FE handoff: frontend-stop-generation-handoff.md
|
|
compactedFrom
Reworked compaction to match the confirmed design:
- The compacted conversation KEEPS its original ID (messaging between
agents is unaffected — the ID never changes)
- The old full history is forked to a new archive conversation (new UUID)
- The archive inherits the source's compactedFrom, creating a chain:
A → Y → X (walk compactedFrom backward)
- A's history is replaced with [summary + recent N]
- A.compactedFrom = archive ID
forkHistory: inherit compactedFrom from source (not set to sourceId),
so archives chain backward to previous archives.
FE: no tab switching needed — the ID doesn't change. Just reload history.
|
|
Compaction now preserves the full pre-compaction history:
1. Forks the conversation to a new archive ID (complete copy: chunks,
metadata, cwd, reasoning-effort). Archive gets status=closed,
title='Archive: <original>', compactedFrom=<originalId>.
2. Replaces the original conversation's history with [system: summary]
+ recent N messages (same as before).
3. Sets compactedFrom=<archiveId> on the original conversation's metadata.
The original history is never destroyed. The archive is accessible via
GET /conversations/:id using the archive ID.
Wire/contract changes:
- ConversationMeta: add compactedFrom?: string
- CompactionResult: add archiveId: string
- ConversationCompactedMessage: add archiveId
- CompactResponse: add archiveId
Conversation store:
- forkHistory(sourceId, targetId): copies all chunks + metadata to a
new conversation ID
- setCompactedFrom(conversationId, archiveId): marks the conversation
|
|
Implement roadmap item 10: conversation compaction to reclaim context
window without losing the thread.
Wire (0.11.0):
- Add CompactionResult type
- Add ConversationCompactedMessage WS event
Transport-contract (0.15.0):
- Add CompactResponse, CompactThresholdResponse, SetCompactThresholdRequest
- Add ConversationCompactedMessage to WsServerMessage union
- Re-export CompactionResult
Conversation-store:
- replaceHistory: delete all chunks, reset seq, append new messages
- getCompactThreshold / setCompactThreshold (per-conversation setting)
- compactThresholdKey added to keys.ts
Session-orchestrator:
- CompactionService interface + compactionHandle
- conversationCompacted hook descriptor
- createCompactionService: load history, split old/recent, call provider
to summarize, replaceHistory with [system: summary] + recent N
- Auto-trigger: resolveCompaction lazy dep, fires after turn settles
(checks threshold, non-blocking)
- Hook declared in manifest contributes.hooks + services
Transport-http:
- POST /conversations/:id/compact (manual trigger)
- GET /conversations/:id/compact-threshold (read setting)
- PUT /conversations/:id/compact-threshold (set setting)
Transport-ws:
- Subscribe to conversationCompacted hook
- Broadcast conversation.compacted WS message
CLI:
- dispatch compact <conversationId> command
FE handoff: frontend-compaction-handoff.md
|
|
Implement roadmap item 9: tab persistence across devices.
Wire (0.10.0):
- Add ConversationStatus type (active | idle | closed)
- Add status field to ConversationMeta
Transport-contract (0.14.0):
- Add conversation.statusChanged WS message to WsServerMessage union
- Re-export ConversationStatus
Conversation-store:
- Track status in ConversationMetaRow (default: idle)
- getConversationStatus / setConversationStatus methods
- listConversations accepts { status: ConversationStatus[] } filter
- Old meta rows without status default to idle on read
Session-orchestrator:
- conversationStatusChanged hook descriptor
- Emit on transitions: idle→active (turn start), active→idle (turn settle),
→closed (closeConversation)
- Persist status to store as fire-and-forget side effect
- Declare hook in manifest contributes.hooks
Transport-ws:
- Subscribe to conversationStatusChanged hook
- Broadcast conversation.statusChanged WS message to all clients
Transport-http:
- GET /conversations?status=active,idle filter (parseStatusFilter pure helper)
- POST /conversations/:id/close now sets status to closed
CLI:
- dispatch list defaults to active,idle (excludes closed)
- --status <state> flag to filter by single status
- --all flag to include closed
FE handoff: frontend-conversation-lifecycle-handoff.md
|
|
The /chat endpoint was buffering the entire turn before returning
the response, which meant X-Conversation-Id was not available until
the turn finished. This prevented the CLI --open flag from firing
until after the turn completed.
Now the response is a ReadableStream that:
- Returns X-Conversation-Id header immediately
- Streams NDJSON events as they arrive from the orchestrator
- Closes the stream when the turn completes (or errors)
- Records throughput after stream close (non-blocking)
This fixes: dispatch <model> --text '...' --open now opens the
frontend tab immediately, not after the turn finishes.
|
|
Bun.file() returns an empty MIME type for .js files, causing the browser
to reject module scripts with strict MIME checking. Added an explicit
MIME type map for common static file extensions (.js, .css, .html, .svg,
.woff2, etc.).
|
|
bin/build: compiles standalone binaries (dispatch-server + dispatch CLI)
via bun build --compile, builds the frontend static bundle with
VITE_HTTP_PORT=24991 + VITE_WS_PORT=24990, copies to dist/web/.
bin/install: installs binaries to /usr/bin/, frontend to
/usr/share/dispatch/web/, systemd service to /etc/systemd/system/,
config to /etc/dispatch/env, data dirs to /var/lib/dispatch/ +
/var/log/dispatch/. Enables + starts the dispatch systemd service.
Supports --uninstall and --no-build flags.
systemd/dispatch.service: Type=simple, reads /etc/dispatch/env,
restarts on failure, logs to journald.
systemd/dispatch.env: template config (ports 24991 HTTP + 24990 WS,
DISPATCH_WEB_DIR, API key, data paths).
transport-http: optional webDir static file serving — unmatched GET
requests fall through to Bun.file() serving with SPA index.html
fallback. Gated on DISPATCH_WEB_DIR env var (backward compatible).
|
|
transport-http: GET /conversations (list with ?q= prefix filter),
GET /conversations/:id/last (blocks until turn settles, returns last AI
text), POST /conversations/:id/open (emits conversationOpened hook),
PUT /conversations/:id/title (set title). emit threaded from host.emit.
extractLastAssistantText pure helper. 21 new tests (166 total).
transport-ws: subscribes to conversationOpened hook, broadcasts
ConversationOpenMessage to all connected WS clients. 2 new tests.
session-orchestrator: conversationOpened hook descriptor (exported).
|
|
Additive contract changes for the CLI milestone (roadmap items 2 + 4):
@dispatch/wire 0.8.0 → 0.9.0:
- ConversationMeta { id, createdAt, lastActivityAt, title }
@dispatch/transport-contract 0.12.0 → 0.13.0:
- ConversationListResponse, LastMessageResponse, OpenConversationResponse
- SetTitleRequest, TitleResponse
- WS conversation.open broadcast (additive to WsServerMessage)
ConversationStore interface:
- listConversations(), getConversationMeta(), setConversationTitle()
- Stub implementations in real store + 11 test fakes (Wave 1 fills in)
Transport-http manifest: new routes declared
(GET /conversations, GET /conversations/:id/last,
POST /conversations/:id/open, PUT /conversations/:id/title)
|
|
A per-conversation message queue (new message-queue extension) holds user
messages enqueued while a turn generates; delivered mid-turn as steering at the
tool-result boundary (or carried to a new turn if no tool call fires).
- kernel: RunTurnInput.drainSteering callback (generic; kernel stays pure)
- wire 0.7.0->0.8.0: QueuedMessage, QueuePayload, TurnSteeringEvent (additive)
- transport-contract 0.11.0->0.12.0: POST /conversations/:id/queue + chat.queue WS op
- message-queue ext: queue state + per-conversation custom surface (rendererId message-queue)
- session-orchestrator: enqueue facade + drainSteering wiring + post-seal carry
- transport-http/ws: queue endpoint + chat.queue op (fixes WsClientMessage exhaustive switch)
- host-bin: register message-queue
1043 vitest + 199 transport bun pass; tsc/biome clean; boot smoke clean.
FE courier: frontend-message-queue-handoff.md.
|
|
threaded to providers
- conversation-store: get/setReasoningEffort (own key space, mirrors cwd)
- session-orchestrator: resolveReasoningEffort (override -> stored -> 'high'),
StartTurnInput.reasoningEffort, warm() parity (cache-safe)
- transport-http: /chat validation (400 on bad level) + GET/PUT
/conversations/:id/reasoning-effort
- transport-ws: chat.send threading + validation
- cli: --effort <low|medium|high|xhigh|max>
993 vitest + 189 bun tests green; typecheck + biome clean.
|
|
/conversations/:id
Selection sinceSeq < seq < beforeSeq; newest-limit window, ascending; positive-
integer validation (400, store never sees an invalid window); 1-based gap-free
seq codified as the contractual has-older mechanism (no earliestSeq field).
transport-contract 0.9.0->0.10.0, wire 0.6.0->0.6.1 (doc-only).
conversation-store +8 tests, transport-http +20; 935 vitest + 112 bun green.
Live-verified: 6/6 probe checks OK. FE courier: frontend-history-windowing-handoff.md
|