Releases: devinoldenburg/codeplane
v29.0.15
Summary
Hotfix for "my queued message disappears" — when a user queued a follow-up, the message appeared in both the timeline (as an optimistic user message) AND the dock (from the server's queue). If promptAsync hit a transient network error, the optimistic timeline message was removed by sendFollowupDraft's error handler — to the user this looked like the message vanishing for no reason. v29.0.15 makes the dock the single source of truth for queued submissions until the server's worker actually runs them.
Fixed
sendFollowupDraftgets anoptimisticMessage: booleanoption (defaulttrue, preserves send/steer behavior). Whenfalse, the function skips adding the optimistic user message to the timeline AND skips the corresponding remove on failure.queueFollowupinpackages/app/src/pages/session.tsxnow passesoptimisticMessage: falseso queued items only appear in the dock — no duplicate representation, no "disappearing" on transient errors.
Verification
bun run typecheckclean inpackages/app.- No server-side changes; existing queue tests still cover the wire contract.
v29.0.15
Summary
Hotfix for "my queued message disappears" — when a user queued a follow-up, the message appeared in both the timeline (as an optimistic user message) AND the dock (from the server's queue). If promptAsync hit a transient network error, the optimistic timeline message was removed by sendFollowupDraft's error handler — to the user this looked like the message vanishing for no reason. v29.0.15 makes the dock the single source of truth for queued submissions until the server's worker actually runs them.
Fixed
sendFollowupDraftgets anoptimisticMessage: booleanoption (defaulttrue, preserves send/steer behavior). Whenfalse, the function skips adding the optimistic user message to the timeline AND skips the corresponding remove on failure.queueFollowupinpackages/app/src/pages/session.tsxnow passesoptimisticMessage: falseso queued items only appear in the dock — no duplicate representation, no "disappearing" on transient errors.
Verification
bun run typecheckclean inpackages/app.- No server-side changes; existing queue tests still cover the wire contract.
v29.0.15
Summary
Hotfix for "my queued message disappears" — when a user queued a follow-up, the message appeared in both the timeline (as an optimistic user message) AND the dock (from the server's queue). If promptAsync hit a transient network error, the optimistic timeline message was removed by sendFollowupDraft's error handler — to the user this looked like the message vanishing for no reason. v29.0.15 makes the dock the single source of truth for queued submissions until the server's worker actually runs them.
Fixed
sendFollowupDraftgets anoptimisticMessage: booleanoption (defaulttrue, preserves send/steer behavior). Whenfalse, the function skips adding the optimistic user message to the timeline AND skips the corresponding remove on failure.queueFollowupinpackages/app/src/pages/session.tsxnow passesoptimisticMessage: falseso queued items only appear in the dock — no duplicate representation, no "disappearing" on transient errors.
Verification
bun run typecheckclean inpackages/app.- No server-side changes; existing queue tests still cover the wire contract.
v29.0.14
Summary
Hotfix for v29.0.13 where the prompt-queue dock would stay empty even after submitting a follow-up — the dock relied solely on session.queue.* SSE events, so any dropped event left the client out of sync with the server's actual queue. v29.0.14 makes the dock self-healing: every control command (enqueue, cancel, reorder, send-now) now refetches the queue snapshot from the server, so the UI converges to the server's truth regardless of SSE delivery hiccups. Also widens cross-directory event routing to include queue events.
Fixed
- Dock now shows queued items reliably. Web app's
queueFollowup/editFollowup/deleteFollowup/reorderFollowupall call a newrefreshQueue(sessionID, directory)helper after the API succeeds. TUI'ssubmit()queue path,sendQueuedFollowup,deleteQueuedFollowup, andmoveQueuedFollowupdo the same. Refetch is one HTTP round-trip; the event-reducer dedupes by id so the bus-event and refresh paths cleanly converge. routeBySessionIDinpackages/app/src/context/global-sync/event-targets.tsnow includessession.queue.created,session.queue.updated, andsession.queue.removed. Without this, queue events only routed to the store whose directory exactly matched the event source — worktree-based sessions or scenarios with multiple open project stores would silently miss queue updates in their non-source stores.- Reorder 409 conflict path now triggers a refresh so the dock matches what the server actually has rather than waiting for the next event.
Verification
bun run typecheckclean inpackages/codeplaneandpackages/app.- Existing queue unit + integration tests (
test/session/prompt-queue.test.ts,test/server/session-queue.test.ts) cover the server-side reducer paths these new refetches feed; the client refetch is a pure idempotent HTTP call so server contract is unchanged.
v29.0.14
Summary
Hotfix for v29.0.13 where the prompt-queue dock would stay empty even after submitting a follow-up — the dock relied solely on session.queue.* SSE events, so any dropped event left the client out of sync with the server's actual queue. v29.0.14 makes the dock self-healing: every control command (enqueue, cancel, reorder, send-now) now refetches the queue snapshot from the server, so the UI converges to the server's truth regardless of SSE delivery hiccups. Also widens cross-directory event routing to include queue events.
Fixed
- Dock now shows queued items reliably. Web app's
queueFollowup/editFollowup/deleteFollowup/reorderFollowupall call a newrefreshQueue(sessionID, directory)helper after the API succeeds. TUI'ssubmit()queue path,sendQueuedFollowup,deleteQueuedFollowup, andmoveQueuedFollowupdo the same. Refetch is one HTTP round-trip; the event-reducer dedupes by id so the bus-event and refresh paths cleanly converge. routeBySessionIDinpackages/app/src/context/global-sync/event-targets.tsnow includessession.queue.created,session.queue.updated, andsession.queue.removed. Without this, queue events only routed to the store whose directory exactly matched the event source — worktree-based sessions or scenarios with multiple open project stores would silently miss queue updates in their non-source stores.- Reorder 409 conflict path now triggers a refresh so the dock matches what the server actually has rather than waiting for the next event.
Verification
bun run typecheckclean inpackages/codeplaneandpackages/app.- Existing queue unit + integration tests (
test/session/prompt-queue.test.ts,test/server/session-queue.test.ts) cover the server-side reducer paths these new refetches feed; the client refetch is a pure idempotent HTTP call so server contract is unchanged.
v29.0.14
Summary
Hotfix for v29.0.13 where the prompt-queue dock would stay empty even after submitting a follow-up — the dock relied solely on session.queue.* SSE events, so any dropped event left the client out of sync with the server's actual queue. v29.0.14 makes the dock self-healing: every control command (enqueue, cancel, reorder, send-now) now refetches the queue snapshot from the server, so the UI converges to the server's truth regardless of SSE delivery hiccups. Also widens cross-directory event routing to include queue events.
Fixed
- Dock now shows queued items reliably. Web app's
queueFollowup/editFollowup/deleteFollowup/reorderFollowupall call a newrefreshQueue(sessionID, directory)helper after the API succeeds. TUI'ssubmit()queue path,sendQueuedFollowup,deleteQueuedFollowup, andmoveQueuedFollowupdo the same. Refetch is one HTTP round-trip; the event-reducer dedupes by id so the bus-event and refresh paths cleanly converge. routeBySessionIDinpackages/app/src/context/global-sync/event-targets.tsnow includessession.queue.created,session.queue.updated, andsession.queue.removed. Without this, queue events only routed to the store whose directory exactly matched the event source — worktree-based sessions or scenarios with multiple open project stores would silently miss queue updates in their non-source stores.- Reorder 409 conflict path now triggers a refresh so the dock matches what the server actually has rather than waiting for the next event.
Verification
bun run typecheckclean inpackages/codeplaneandpackages/app.- Existing queue unit + integration tests (
test/session/prompt-queue.test.ts,test/server/session-queue.test.ts) cover the server-side reducer paths these new refetches feed; the client refetch is a pure idempotent HTTP call so server contract is unchanged.
v29.0.13
Summary
Codeplane v29.0.13 moves the prompt queue (follow-up queue) to be server-authoritative. The queue, FIFO ordering, retries, and execution all live on the server in a SQLite-backed prompt_job table drained by a background worker. Web, TUI, desktop, and mobile clients now only display state and send control commands — submissions survive client disconnects and server restarts, and the queue is consistent across every surface. v29.0.12's tag exists but its npm publish failed on the version-verify step (packages/plugin was not bumped); v29.0.13 carries the same payload plus that fix.
Added
- Server-authoritative
PromptQueuekeyed by sessionID with per-session FIFO enforced by SQL; the in-processPromptQueueWorkerclaims rows on a 1s tick, runs each in a forked fiber onAppRuntime, and records terminal status. - HTTP routes
GET /session/:id/queue(snapshot, optional?all=1to include terminal rows),DELETE /session/:id/queue/:jobID(cancel, idempotent), andPOST /session/:id/queue/reorder(rewrite pending order; returns the affected jobs in their new order). - Bus events
session.queue.created,session.queue.updated, andsession.queue.removedpublished on every PromptQueue mutation so subscribed clients render the queue live without polling. sort_orderinteger column onprompt_jobplus a migration;PromptQueue.claimandPromptQueue.listorder bysort_order ASC NULLS LAST, id ASCso explicit reorders sort before never-reordered rows while newly enqueued items still default to FIFO.PromptQueue.reordervalidates that every supplied id references a pending row in the session, fails with aPromptQueueConflict(HTTP 409) when a row's status has changed, and rewritessort_orderatomically.- In-process wake hook:
PromptQueue.setWakeNotifier, registered by the worker onstartand dropped onstop, firessafeTickafter every successfulenqueueso idle sends incur essentially no latency vs. the previous up-to-1s tick delay. - 409
PromptQueueConflictstatus mapped in the error middleware and registered in the OpenAPI responses set. - Web app + TUI now keep a
prompt_queue: { [sessionID]: PromptQueueJob[] }map in sync state, populated by asession.queue.listsnapshot on session change and kept fresh by the three new bus events; the reducer handles snapshot/event races idempotently (drop duplicate Created, treat unknown Updated as create) and prunes completed/cancelled while keepingfailedrows visible so users can see the error. - TUI follow-up dock derives entirely from the server queue, previews payload text via a payload parser, and routes Send-now, Edit, Delete, and Move via
queue.reorder/queue.cancel. - Web composer wired to the same model: queueing immediately calls
promptAsync(server enqueues); reordering and cancelling go through the new routes; edit cancels the row and pre-fills the composer with the text portion of the payload. - Session-working detection refinements: new
hasUnansweredUserMessagehelper, idle/working resync polling effect insession.tsxthat backfills sync state when the working signal lags, andsession-turn-workingcomponent refinements. - New
server-errorsutility for normalizing SDK error envelopes into user-facing strings. - Desktop
ui-hostupdates and TUI feature plugin / route updates (session-v2, system plugins). - New integration test
test/server/session-queue.test.tscovers list (empty, populated,?all=1), cancel (idempotent), reorder (success, 409 conflict, 404 not-found). - New unit tests in
test/session/prompt-queue.test.tscover Created/Updated bus events on enqueue and claim, per-row Updated events oncancelSession, reorder ordering semantics, reorder rejecting non-pending and unknown ids, and newly-enqueued jobs sorting after reordered ones.
Changed
- TUI submission unified: every regular prompt — queue, send, and steer disposition — now flows through
session.promptAsync. The synchronousclient.session.prompt(...)codepath is gone from the TUI; the server's worker is the only execution path. Shell and custom commands keep their dedicated endpoints (they have different semantics). PromptQueue.cancelSessionreads affected rows before the bulk update so per-rowUpdatedevents can be published; the existing transactional cancellation behavior is preserved.PromptQueue.recovernow publishes anUpdatedevent for each recovered row so a freshly reconnected UI sees recovery transitions without re-fetching.- The web app's session-page snapshot effect refetches the queue on
sdk.directory/params.idchange; concurrent SSE events use the same reducer path. - TUI double-interrupt no longer calls a local
clearFollowupQueue—session.abortalready callsPromptQueue.cancelSessionserver-side, so the queue is wiped on the server. - Composer abort handler now maps to
session.abort(which cancels both the in-flight turn and all pending queue rows), preserving the user's "stop everything" intent. PromptQueue.listordering now matchesclaimso reorders are reflected in the next snapshot.truthy,falsy, andnumberhelpers are now exported fromsrc/flag/flag.tssoflag.test.tscan test parsing semantics directly instead of doingdelete require.cache + await import, which previously forked theFlagmodule across consumers and silently broke any later-loaded test that mutatedFlag.X = truewhile source modules still held the original reference.
Fixed
- Server-authoritative queue eliminates the freeze / fail-silently / double-fire / does-not-continue failure modes of the previous client-side dispatch loop: there is no longer a client-side effect that watches
session_statusand tries to dispatch the next job — the server worker owns that, with bounded retries and crash recovery on boot. - Worker latency: idle-send no longer waits up to a full
TICK_MS(1s) for the worker's interval to fire. The wake hook tripssafeTicksynchronously afterenqueuecommits. - Best-effort bus publishes inside
PromptQueue.enqueue/claim/recordResult/cancelSession/recoverswallow publish failures so a transient SSE outage cannot roll back a committed DB mutation; the row is authoritative and clients can always refetch the snapshot. - Test isolation:
plugin/workspace-adaptor.test.tsnowresetDatabase()inafterEachandafterAllso the EventTable rows it writes underCODEPLANE_EXPERIMENTAL_WORKSPACES=trueno longer leak into later test files (test/sync,test/workspace) and break their length-based assertions. sync/index.test.tsandworkspace/workspace-restore.test.tsnowresetDatabase()inbeforeEachas a defense-in-depth against any future polluting test.- v29.0.12 release was rejected by the npm-release workflow because
packages/plugin/package.jsonwas left at 29.0.11 while the rest of the monorepo bumped to 29.0.12. v29.0.13 syncspackages/plugin,packages/script, andpackages/sharedso the entire workspace ships at one version.
Removed
packages/app/src/pages/session/followup-queue.tsand its test — thenextRunnableFollowupclient-side dispatch logic is obsolete now that the server worker runs head-of-queue automatically.- Web app:
followupMutation(TanStack mutation that calledsendFollowupDraft), thenextRunnableFollowupcreateEffectthat watched local state and triggered dispatch, theitems/failed/pausedslots of the persistedfollowupstore, and the local-state-only versions ofsendFollowup/deleteFollowup/reorderFollowup. - TUI: module-level
followupQueuestore, theenqueueFollowup/dequeueFollowup/takeFollowup/moveFollowup/clearFollowupQueuehelpers, the busy→idle draincreateEffect, theQueuedSubmissiontype'sdispatchclosure field, and the redundant queue-onlypromptAsyncbranch insubmit()(collapsed into the unified dispatch closure).
Verification
bun run typecheckclean in all 8 packages (codeplane, app, sdk/js, ui, desktop, mobile, shared, plugin).bun testinpackages/codeplane: queue-related suites pass (test/session/prompt-queue.test.ts,test/session/prompt-queue-worker.test.ts,test/server/session-queue.test.ts,test/queue.comprehensive.test.ts).bun test --preload ./happydom.ts ./srcinpackages/app: 614 pass.bun testinpackages/ui: 349 pass.bun run buildinpackages/codeplaneproduces multi-platform binaries;dist/codeplane-darwin-arm64/bin/codeplane --versionreturns29.0.13.- End-to-end smoke against the built binary:
serveboots,POST /sessioncreates a session,GET /session/:id/queuereturns[]for an empty queue.
v29.0.13
Summary
Codeplane v29.0.13 moves the prompt queue (follow-up queue) to be server-authoritative. The queue, FIFO ordering, retries, and execution all live on the server in a SQLite-backed prompt_job table drained by a background worker. Web, TUI, desktop, and mobile clients now only display state and send control commands — submissions survive client disconnects and server restarts, and the queue is consistent across every surface. v29.0.12's tag exists but its npm publish failed on the version-verify step (packages/plugin was not bumped); v29.0.13 carries the same payload plus that fix.
Added
- Server-authoritative
PromptQueuekeyed by sessionID with per-session FIFO enforced by SQL; the in-processPromptQueueWorkerclaims rows on a 1s tick, runs each in a forked fiber onAppRuntime, and records terminal status. - HTTP routes
GET /session/:id/queue(snapshot, optional?all=1to include terminal rows),DELETE /session/:id/queue/:jobID(cancel, idempotent), andPOST /session/:id/queue/reorder(rewrite pending order; returns the affected jobs in their new order). - Bus events
session.queue.created,session.queue.updated, andsession.queue.removedpublished on every PromptQueue mutation so subscribed clients render the queue live without polling. sort_orderinteger column onprompt_jobplus a migration;PromptQueue.claimandPromptQueue.listorder bysort_order ASC NULLS LAST, id ASCso explicit reorders sort before never-reordered rows while newly enqueued items still default to FIFO.PromptQueue.reordervalidates that every supplied id references a pending row in the session, fails with aPromptQueueConflict(HTTP 409) when a row's status has changed, and rewritessort_orderatomically.- In-process wake hook:
PromptQueue.setWakeNotifier, registered by the worker onstartand dropped onstop, firessafeTickafter every successfulenqueueso idle sends incur essentially no latency vs. the previous up-to-1s tick delay. - 409
PromptQueueConflictstatus mapped in the error middleware and registered in the OpenAPI responses set. - Web app + TUI now keep a
prompt_queue: { [sessionID]: PromptQueueJob[] }map in sync state, populated by asession.queue.listsnapshot on session change and kept fresh by the three new bus events; the reducer handles snapshot/event races idempotently (drop duplicate Created, treat unknown Updated as create) and prunes completed/cancelled while keepingfailedrows visible so users can see the error. - TUI follow-up dock derives entirely from the server queue, previews payload text via a payload parser, and routes Send-now, Edit, Delete, and Move via
queue.reorder/queue.cancel. - Web composer wired to the same model: queueing immediately calls
promptAsync(server enqueues); reordering and cancelling go through the new routes; edit cancels the row and pre-fills the composer with the text portion of the payload. - Session-working detection refinements: new
hasUnansweredUserMessagehelper, idle/working resync polling effect insession.tsxthat backfills sync state when the working signal lags, andsession-turn-workingcomponent refinements. - New
server-errorsutility for normalizing SDK error envelopes into user-facing strings. - Desktop
ui-hostupdates and TUI feature plugin / route updates (session-v2, system plugins). - New integration test
test/server/session-queue.test.tscovers list (empty, populated,?all=1), cancel (idempotent), reorder (success, 409 conflict, 404 not-found). - New unit tests in
test/session/prompt-queue.test.tscover Created/Updated bus events on enqueue and claim, per-row Updated events oncancelSession, reorder ordering semantics, reorder rejecting non-pending and unknown ids, and newly-enqueued jobs sorting after reordered ones.
Changed
- TUI submission unified: every regular prompt — queue, send, and steer disposition — now flows through
session.promptAsync. The synchronousclient.session.prompt(...)codepath is gone from the TUI; the server's worker is the only execution path. Shell and custom commands keep their dedicated endpoints (they have different semantics). PromptQueue.cancelSessionreads affected rows before the bulk update so per-rowUpdatedevents can be published; the existing transactional cancellation behavior is preserved.PromptQueue.recovernow publishes anUpdatedevent for each recovered row so a freshly reconnected UI sees recovery transitions without re-fetching.- The web app's session-page snapshot effect refetches the queue on
sdk.directory/params.idchange; concurrent SSE events use the same reducer path. - TUI double-interrupt no longer calls a local
clearFollowupQueue—session.abortalready callsPromptQueue.cancelSessionserver-side, so the queue is wiped on the server. - Composer abort handler now maps to
session.abort(which cancels both the in-flight turn and all pending queue rows), preserving the user's "stop everything" intent. PromptQueue.listordering now matchesclaimso reorders are reflected in the next snapshot.truthy,falsy, andnumberhelpers are now exported fromsrc/flag/flag.tssoflag.test.tscan test parsing semantics directly instead of doingdelete require.cache + await import, which previously forked theFlagmodule across consumers and silently broke any later-loaded test that mutatedFlag.X = truewhile source modules still held the original reference.
Fixed
- Server-authoritative queue eliminates the freeze / fail-silently / double-fire / does-not-continue failure modes of the previous client-side dispatch loop: there is no longer a client-side effect that watches
session_statusand tries to dispatch the next job — the server worker owns that, with bounded retries and crash recovery on boot. - Worker latency: idle-send no longer waits up to a full
TICK_MS(1s) for the worker's interval to fire. The wake hook tripssafeTicksynchronously afterenqueuecommits. - Best-effort bus publishes inside
PromptQueue.enqueue/claim/recordResult/cancelSession/recoverswallow publish failures so a transient SSE outage cannot roll back a committed DB mutation; the row is authoritative and clients can always refetch the snapshot. - Test isolation:
plugin/workspace-adaptor.test.tsnowresetDatabase()inafterEachandafterAllso the EventTable rows it writes underCODEPLANE_EXPERIMENTAL_WORKSPACES=trueno longer leak into later test files (test/sync,test/workspace) and break their length-based assertions. sync/index.test.tsandworkspace/workspace-restore.test.tsnowresetDatabase()inbeforeEachas a defense-in-depth against any future polluting test.- v29.0.12 release was rejected by the npm-release workflow because
packages/plugin/package.jsonwas left at 29.0.11 while the rest of the monorepo bumped to 29.0.12. v29.0.13 syncspackages/plugin,packages/script, andpackages/sharedso the entire workspace ships at one version.
Removed
packages/app/src/pages/session/followup-queue.tsand its test — thenextRunnableFollowupclient-side dispatch logic is obsolete now that the server worker runs head-of-queue automatically.- Web app:
followupMutation(TanStack mutation that calledsendFollowupDraft), thenextRunnableFollowupcreateEffectthat watched local state and triggered dispatch, theitems/failed/pausedslots of the persistedfollowupstore, and the local-state-only versions ofsendFollowup/deleteFollowup/reorderFollowup. - TUI: module-level
followupQueuestore, theenqueueFollowup/dequeueFollowup/takeFollowup/moveFollowup/clearFollowupQueuehelpers, the busy→idle draincreateEffect, theQueuedSubmissiontype'sdispatchclosure field, and the redundant queue-onlypromptAsyncbranch insubmit()(collapsed into the unified dispatch closure).
Verification
bun run typecheckclean in all 8 packages (codeplane, app, sdk/js, ui, desktop, mobile, shared, plugin).bun testinpackages/codeplane: queue-related suites pass (test/session/prompt-queue.test.ts,test/session/prompt-queue-worker.test.ts,test/server/session-queue.test.ts,test/queue.comprehensive.test.ts).bun test --preload ./happydom.ts ./srcinpackages/app: 614 pass.bun testinpackages/ui: 349 pass.bun run buildinpackages/codeplaneproduces multi-platform binaries;dist/codeplane-darwin-arm64/bin/codeplane --versionreturns29.0.13.- End-to-end smoke against the built binary:
serveboots,POST /sessioncreates a session,GET /session/:id/queuereturns[]for an empty queue.
v29.0.13
Summary
Codeplane v29.0.13 moves the prompt queue (follow-up queue) to be server-authoritative. The queue, FIFO ordering, retries, and execution all live on the server in a SQLite-backed prompt_job table drained by a background worker. Web, TUI, desktop, and mobile clients now only display state and send control commands — submissions survive client disconnects and server restarts, and the queue is consistent across every surface. v29.0.12's tag exists but its npm publish failed on the version-verify step (packages/plugin was not bumped); v29.0.13 carries the same payload plus that fix.
Added
- Server-authoritative
PromptQueuekeyed by sessionID with per-session FIFO enforced by SQL; the in-processPromptQueueWorkerclaims rows on a 1s tick, runs each in a forked fiber onAppRuntime, and records terminal status. - HTTP routes
GET /session/:id/queue(snapshot, optional?all=1to include terminal rows),DELETE /session/:id/queue/:jobID(cancel, idempotent), andPOST /session/:id/queue/reorder(rewrite pending order; returns the affected jobs in their new order). - Bus events
session.queue.created,session.queue.updated, andsession.queue.removedpublished on every PromptQueue mutation so subscribed clients render the queue live without polling. sort_orderinteger column onprompt_jobplus a migration;PromptQueue.claimandPromptQueue.listorder bysort_order ASC NULLS LAST, id ASCso explicit reorders sort before never-reordered rows while newly enqueued items still default to FIFO.PromptQueue.reordervalidates that every supplied id references a pending row in the session, fails with aPromptQueueConflict(HTTP 409) when a row's status has changed, and rewritessort_orderatomically.- In-process wake hook:
PromptQueue.setWakeNotifier, registered by the worker onstartand dropped onstop, firessafeTickafter every successfulenqueueso idle sends incur essentially no latency vs. the previous up-to-1s tick delay. - 409
PromptQueueConflictstatus mapped in the error middleware and registered in the OpenAPI responses set. - Web app + TUI now keep a
prompt_queue: { [sessionID]: PromptQueueJob[] }map in sync state, populated by asession.queue.listsnapshot on session change and kept fresh by the three new bus events; the reducer handles snapshot/event races idempotently (drop duplicate Created, treat unknown Updated as create) and prunes completed/cancelled while keepingfailedrows visible so users can see the error. - TUI follow-up dock derives entirely from the server queue, previews payload text via a payload parser, and routes Send-now, Edit, Delete, and Move via
queue.reorder/queue.cancel. - Web composer wired to the same model: queueing immediately calls
promptAsync(server enqueues); reordering and cancelling go through the new routes; edit cancels the row and pre-fills the composer with the text portion of the payload. - Session-working detection refinements: new
hasUnansweredUserMessagehelper, idle/working resync polling effect insession.tsxthat backfills sync state when the working signal lags, andsession-turn-workingcomponent refinements. - New
server-errorsutility for normalizing SDK error envelopes into user-facing strings. - Desktop
ui-hostupdates and TUI feature plugin / route updates (session-v2, system plugins). - New integration test
test/server/session-queue.test.tscovers list (empty, populated,?all=1), cancel (idempotent), reorder (success, 409 conflict, 404 not-found). - New unit tests in
test/session/prompt-queue.test.tscover Created/Updated bus events on enqueue and claim, per-row Updated events oncancelSession, reorder ordering semantics, reorder rejecting non-pending and unknown ids, and newly-enqueued jobs sorting after reordered ones.
Changed
- TUI submission unified: every regular prompt — queue, send, and steer disposition — now flows through
session.promptAsync. The synchronousclient.session.prompt(...)codepath is gone from the TUI; the server's worker is the only execution path. Shell and custom commands keep their dedicated endpoints (they have different semantics). PromptQueue.cancelSessionreads affected rows before the bulk update so per-rowUpdatedevents can be published; the existing transactional cancellation behavior is preserved.PromptQueue.recovernow publishes anUpdatedevent for each recovered row so a freshly reconnected UI sees recovery transitions without re-fetching.- The web app's session-page snapshot effect refetches the queue on
sdk.directory/params.idchange; concurrent SSE events use the same reducer path. - TUI double-interrupt no longer calls a local
clearFollowupQueue—session.abortalready callsPromptQueue.cancelSessionserver-side, so the queue is wiped on the server. - Composer abort handler now maps to
session.abort(which cancels both the in-flight turn and all pending queue rows), preserving the user's "stop everything" intent. PromptQueue.listordering now matchesclaimso reorders are reflected in the next snapshot.truthy,falsy, andnumberhelpers are now exported fromsrc/flag/flag.tssoflag.test.tscan test parsing semantics directly instead of doingdelete require.cache + await import, which previously forked theFlagmodule across consumers and silently broke any later-loaded test that mutatedFlag.X = truewhile source modules still held the original reference.
Fixed
- Server-authoritative queue eliminates the freeze / fail-silently / double-fire / does-not-continue failure modes of the previous client-side dispatch loop: there is no longer a client-side effect that watches
session_statusand tries to dispatch the next job — the server worker owns that, with bounded retries and crash recovery on boot. - Worker latency: idle-send no longer waits up to a full
TICK_MS(1s) for the worker's interval to fire. The wake hook tripssafeTicksynchronously afterenqueuecommits. - Best-effort bus publishes inside
PromptQueue.enqueue/claim/recordResult/cancelSession/recoverswallow publish failures so a transient SSE outage cannot roll back a committed DB mutation; the row is authoritative and clients can always refetch the snapshot. - Test isolation:
plugin/workspace-adaptor.test.tsnowresetDatabase()inafterEachandafterAllso the EventTable rows it writes underCODEPLANE_EXPERIMENTAL_WORKSPACES=trueno longer leak into later test files (test/sync,test/workspace) and break their length-based assertions. sync/index.test.tsandworkspace/workspace-restore.test.tsnowresetDatabase()inbeforeEachas a defense-in-depth against any future polluting test.- v29.0.12 release was rejected by the npm-release workflow because
packages/plugin/package.jsonwas left at 29.0.11 while the rest of the monorepo bumped to 29.0.12. v29.0.13 syncspackages/plugin,packages/script, andpackages/sharedso the entire workspace ships at one version.
Removed
packages/app/src/pages/session/followup-queue.tsand its test — thenextRunnableFollowupclient-side dispatch logic is obsolete now that the server worker runs head-of-queue automatically.- Web app:
followupMutation(TanStack mutation that calledsendFollowupDraft), thenextRunnableFollowupcreateEffectthat watched local state and triggered dispatch, theitems/failed/pausedslots of the persistedfollowupstore, and the local-state-only versions ofsendFollowup/deleteFollowup/reorderFollowup. - TUI: module-level
followupQueuestore, theenqueueFollowup/dequeueFollowup/takeFollowup/moveFollowup/clearFollowupQueuehelpers, the busy→idle draincreateEffect, theQueuedSubmissiontype'sdispatchclosure field, and the redundant queue-onlypromptAsyncbranch insubmit()(collapsed into the unified dispatch closure).
Verification
bun run typecheckclean in all 8 packages (codeplane, app, sdk/js, ui, desktop, mobile, shared, plugin).bun testinpackages/codeplane: queue-related suites pass (test/session/prompt-queue.test.ts,test/session/prompt-queue-worker.test.ts,test/server/session-queue.test.ts,test/queue.comprehensive.test.ts).bun test --preload ./happydom.ts ./srcinpackages/app: 614 pass.bun testinpackages/ui: 349 pass.bun run buildinpackages/codeplaneproduces multi-platform binaries;dist/codeplane-darwin-arm64/bin/codeplane --versionreturns29.0.13.- End-to-end smoke against the built binary:
serveboots,POST /sessioncreates a session,GET /session/:id/queuereturns[]for an empty queue.
v29.0.11
Summary
Codeplane v29.0.11 ships a broad desktop reliability, streaming, TUI follow-up, Markdown, config-secret, and session-scoping release. Desktop now reconnects to upgraded servers without stale cached UI or buffered live streams, while web and TUI surfaces get safer session routing, cleaner Markdown, and more precise per-turn diffs.
Added
- Desktop UI cache metadata now stores an entry hash for
index.html, so same-version rebuilt client bundles can invalidate stale local caches. - Desktop Electron proxy now has a native SSE path for
/global/eventand/event, forwarding cookies and instance headers while avoiding Electronsession.fetchbuffering. - TUI prompt now includes an explicit follow-up queue dock with send, edit, delete, move up, and move down actions plus command-palette entries for queued follow-ups.
- TUI Markdown rendering now uses
marked/GFM tokens for headings, lists, task lists, blockquotes, code, links, images, strikethrough, and terminal-friendly table rows. - Global sync now has an event-target routing helper that fans session events to relevant open stores by source directory, known session id, and project id.
- Session turn diff filtering now matches summary diffs against files reported by completed mutating tools.
- Desktop logger now exposes
flush()so tests and diagnostics can wait for queued disk writes deterministically.
Changed
- Desktop always probes the target server before selecting a cached UI bundle instead of trusting a fresh origin cache entry for 30 days.
- Desktop clears renderer HTTP cache before loading prepared UI after normal opens, auth bootstrap, and server-upgrade reconnects.
- Desktop setup, local install/cache, open, and server-upgrade reconnect progress now render as opaque fullscreen surfaces instead of translucent or inline progress fragments.
- Desktop-managed local instances now delegate same-id version changes to the shared local-instance manager, which restarts the server when
binaryVersionchanges. - Web composer controls now live in the dock tray with a single grouped composer chrome, and the submit/stop button explicitly submits or aborts without focus and overlay edge cases.
- Session directory listing now filters by path containment plus project id, so child directories are included only when they belong to the same project scope.
- Secret substitution can distinguish general missing tokens from missing secrets; missing config secrets resolve to empty so the instance can still open.
- Secret set/delete routes now invalidate config; deleting a managed secret also removes matching global-config placeholders.
- Server error formatting now unwraps SDK error envelopes and message-like objects before presenting user-facing errors.
- TUI remote instance forms now mask passwords fully, redact sensitive headers in display, and validate URLs with
URL. - TUI layout components now clamp dialog, toast, scroll, and content sizes for very small terminal dimensions.
- ChatGPT/Codex OAuth provider filtering now drops unsupported
gpt-5.5-profrom the provider model list. - Bash tool guidance now permits user-requested visual artifacts/files instead of forcing every chart-like output through a chart widget.
Fixed
- Fixed Desktop continuing to load old cached client UI after a server upgrade.
- Fixed Desktop live stream endings lagging or hanging when SSE responses were buffered by Electron session fetch.
- Fixed local Desktop instances staying on an old running binary after the selected server version changed.
- Fixed nested project session events leaking into ancestor project stores while preserving live stream updates for stores that already know the session.
- Fixed per-message summary diffs showing ambient or stale snapshot files from unrelated tools such as browser screenshots.
- Fixed missing secret placeholders bricking config loading.
- Fixed secret deletion leaving stale
{secret:name}references in global config. - Fixed TUI link clicks opening non-http protocols such as
file:orjavascript:. - Fixed terminal remote form displays leaking the last typed password character or sensitive header values.
- Fixed logger tests racing temporary directory cleanup and producing false append failures.
Removed
- Removed the old custom rich Markdown block renderer, hydration, CSS, tests, and story fixtures for chart, stock, KPI, tabs, choice, callout, preview, badge, progress, timeline, quote, file-tree, comparison, and plot blocks.
- Removed clipboard backend
console.lognoise from the TUI. - Removed Desktop macOS transparent/vibrancy window backgrounds for connect, reconnect, and setup flows.
Verification
bun run version:bump patchbun turbo typecheckbun lintgit diff --checkbun testinpackages/app(612 pass)bun testinpackages/shared(10708 pass)bun testinpackages/ui(347 pass)bun testinpackages/desktop(1889 pass)bun test test/config/config.test.ts test/plugin/codex.test.ts test/session/summary.test.ts test/session/session.test.ts test/server/global-session-list.test.ts test/tool/parameters.test.ts test/tool/tools.test.ts test/tool/forge.test.ts test/tool/git.test.ts test/agent/agent.test.ts test/tui-suite/tests/rich-block-markdown.test.tsx test/tui-suite/tests/rich-block.test.tsx test/tui-suite/tests/remote-form.test.tsx test/tui/link.test.tsinpackages/codeplane(257 pass)bun test test/session/llm.test.tsinpackages/codeplane(15 pass)bun --cwd site typecheckbun --cwd site run buildbun --cwd packages/desktop run test:e2e(10 pass)bun --cwd packages/codeplane script/build.ts --skip-install --single(--versionand embedded root UI serve smoke passed)