@loicngr/kobo 2.1.13 → 2.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/AGENTS.md +38 -6
  2. package/CHANGELOG.md +12 -0
  3. package/CONFIGURATION.md +41 -5
  4. package/README.md +4 -2
  5. package/dist/mcp-server/kobo-tasks-server.js +62 -105
  6. package/dist/mcp-server/workspace-dialogue-client.js +15 -3
  7. package/dist/server/db/auto-loop-final-review-schema.js +18 -0
  8. package/dist/server/db/migrations.js +12 -1
  9. package/dist/server/db/review-return-schema.js +18 -1
  10. package/dist/server/db/schema.js +4 -0
  11. package/dist/server/db/workspace-message-batch-schema.js +9 -0
  12. package/dist/server/index.js +8 -1
  13. package/dist/server/routes/mcp.js +69 -3
  14. package/dist/server/routes/workspace-messages.js +22 -0
  15. package/dist/server/routes/workspaces.js +119 -4
  16. package/dist/server/services/agent/engines/claude-code/engine.js +37 -1
  17. package/dist/server/services/agent/orchestrator.js +159 -40
  18. package/dist/server/services/agent/session-controller.js +4 -1
  19. package/dist/server/services/auto-loop-final-review-service.js +329 -0
  20. package/dist/server/services/auto-loop-service.js +53 -10
  21. package/dist/server/services/pr-checkout-rollback-service.js +51 -0
  22. package/dist/server/services/pr-checkout-service.js +21 -1
  23. package/dist/server/services/review-launch-runtime.js +17 -0
  24. package/dist/server/services/review-return-service.js +58 -10
  25. package/dist/server/services/review-service.js +59 -11
  26. package/dist/server/services/workspace-creation-mcp-service.js +198 -0
  27. package/dist/server/services/workspace-group-message-mcp-service.js +58 -0
  28. package/dist/server/services/workspace-group-message-service.js +182 -0
  29. package/dist/server/services/workspace-lifecycle-mcp-service.js +72 -0
  30. package/dist/server/services/workspace-message-service.js +5 -0
  31. package/dist/server/services/workspace-service.js +11 -0
  32. package/dist/server/services/workspace-template-service.js +10 -0
  33. package/dist/shared/auto-loop-review.js +1 -0
  34. package/dist/shared/pr-checkout-steps.js +86 -0
  35. package/dist/shared/review-return.js +1 -0
  36. package/dist/shared/workspace-creation-tools.js +182 -0
  37. package/dist/shared/workspace-group-message-tools.js +134 -0
  38. package/dist/shared/workspace-group-messages.js +27 -0
  39. package/dist/shared/workspace-lifecycle-tools.js +112 -0
  40. package/package.json +1 -1
  41. package/src/client/dist/pwa/assets/ActivityFeed-tJmJhmtl.js +2 -0
  42. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Bbrq02Xt.js +1 -0
  43. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +1 -0
  44. package/src/client/dist/pwa/assets/{ChangelogPage-DD90Nl6c.js → ChangelogPage-BJfkAVYp.js} +1 -1
  45. package/src/client/dist/pwa/assets/{ComparisonPanel-D_BQFWiT.js → ComparisonPanel-BfP8APKY.js} +1 -1
  46. package/src/client/dist/pwa/assets/CreatePage-BbtNfo0x.js +2 -0
  47. package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +1 -0
  48. package/src/client/dist/pwa/assets/{DashboardPage-DyvTdmBg.js → DashboardPage-DZYsAh5y.js} +1 -1
  49. package/src/client/dist/pwa/assets/DiffViewer-4Q-GJgyA.js +8 -0
  50. package/src/client/dist/pwa/assets/{DocumentsPanel-bm639sIu.js → DocumentsPanel-BfPlfiwU.js} +1 -1
  51. package/src/client/dist/pwa/assets/{DrawerToggleButton-KfwSzjeY.js → DrawerToggleButton-DG1Qjqk1.js} +1 -1
  52. package/src/client/dist/pwa/assets/GitPanel-ECnbb_rx.js +3 -0
  53. package/src/client/dist/pwa/assets/{HealthPage-37IHOIW-.js → HealthPage-_VXG1Zeo.js} +1 -1
  54. package/src/client/dist/pwa/assets/MainLayout-BH6grTuW.js +2 -0
  55. package/src/client/dist/pwa/assets/MainLayout-DnmUxNk7.css +1 -0
  56. package/src/client/dist/pwa/assets/{MemoryPanel-CucZMhjy.js → MemoryPanel-CVlvGZXZ.js} +1 -1
  57. package/src/client/dist/pwa/assets/{MemoryProposalList-CUQybzVR.js → MemoryProposalList-SJsnzUAM.js} +1 -1
  58. package/src/client/dist/pwa/assets/{QBtnDropdown-x6qPyl_7.js → QBtnDropdown-DBgGsN-D.js} +1 -1
  59. package/src/client/dist/pwa/assets/{QExpansionItem-D33aoRoG.js → QExpansionItem-DKgaMJ_l.js} +1 -1
  60. package/src/client/dist/pwa/assets/{QScrollArea-Di-D6t2u.js → QScrollArea-D3OCsq3g.js} +1 -1
  61. package/src/client/dist/pwa/assets/QSelect-Cb_s_nox.js +1 -0
  62. package/src/client/dist/pwa/assets/{QTooltip-BhwG7lp2.js → QTooltip-X5ps8Opw.js} +1 -1
  63. package/src/client/dist/pwa/assets/{SchedulePanel-qFi9Wxzh.js → SchedulePanel-Ww_FVlW5.js} +1 -1
  64. package/src/client/dist/pwa/assets/{SearchPage-DA9nRmsA.js → SearchPage-B87WEHf3.js} +1 -1
  65. package/src/client/dist/pwa/assets/SettingsPage-BAZLGUyg.js +9 -0
  66. package/src/client/dist/pwa/assets/{SplitWorkspacePage-Brw2XsYJ.js → SplitWorkspacePage-B99AJUbG.js} +1 -1
  67. package/src/client/dist/pwa/assets/{TerminalPanel-DVP18-nk.js → TerminalPanel-DnA1MXwu.js} +9 -9
  68. package/src/client/dist/pwa/assets/{ToolCallItem-cSHh3p_F.js → ToolCallItem-2vFggaur.js} +3 -3
  69. package/src/client/dist/pwa/assets/{TouchPan-C6LZZ3rw.js → TouchPan-BSU3z0aO.js} +1 -1
  70. package/src/client/dist/pwa/assets/{TourReplayButton-Czlqo18H.js → TourReplayButton-DQwS2xdD.js} +1 -1
  71. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BDYGq0pC.js → WorkspaceAttentionLabels-CM1tGDje.js} +1 -1
  72. package/src/client/dist/pwa/assets/WorkspacePage-B248nKIU.css +1 -0
  73. package/src/client/dist/pwa/assets/WorkspacePage-CQN6EKIn.js +4 -0
  74. package/src/client/dist/pwa/assets/{browser-ifCwUGpE.js → browser-DPsupI9Q.js} +1 -1
  75. package/src/client/dist/pwa/assets/{build-path-tree-BBkGq3lN.js → build-path-tree-CbIdt-p9.js} +1 -1
  76. package/src/client/dist/pwa/assets/custom-sounds-DR68eyF8.js +1 -0
  77. package/src/client/dist/pwa/assets/de-D3Ycvf_F.js +17 -0
  78. package/src/client/dist/pwa/assets/{editor.api-CV_seGJR.js → editor.api-DvgAtnOj.js} +1 -1
  79. package/src/client/dist/pwa/assets/{efforts-ttw0bL63.js → efforts-nmxfTVQD.js} +1 -1
  80. package/src/client/dist/pwa/assets/es-TgeS5898.js +17 -0
  81. package/src/client/dist/pwa/assets/{expand-template-C8biMRXo.js → expand-template-Brn5O4a1.js} +1 -1
  82. package/src/client/dist/pwa/assets/{format-Cu95dbxt.js → format-DfJ3Fv1i.js} +1 -1
  83. package/src/client/dist/pwa/assets/{formatters-C25D1RSd.js → formatters-B30bNFhq.js} +1 -1
  84. package/src/client/dist/pwa/assets/fr-CJafryNb.js +17 -0
  85. package/src/client/dist/pwa/assets/{freemarker2-DJEFr74q.js → freemarker2-DnZe74tc.js} +1 -1
  86. package/src/client/dist/pwa/assets/{handlebars-C_lzsMjX.js → handlebars-CmuMfxzO.js} +1 -1
  87. package/src/client/dist/pwa/assets/{html-CWmLFAmY.js → html-BD7OJFGX.js} +1 -1
  88. package/src/client/dist/pwa/assets/i18n-BY4JeBjk.js +1 -0
  89. package/src/client/dist/pwa/assets/index-C5fAw9ll.js +20 -0
  90. package/src/client/dist/pwa/assets/it-C5mN8Y61.js +17 -0
  91. package/src/client/dist/pwa/assets/{javascript-tsuiEUe-.js → javascript-Y-OrgcmW.js} +1 -1
  92. package/src/client/dist/pwa/assets/{liquid-BBr9BvUk.js → liquid-BqOkKePH.js} +1 -1
  93. package/src/client/dist/pwa/assets/{mdx-gjBH1cXG.js → mdx-D-58x2h0.js} +1 -1
  94. package/src/client/dist/pwa/assets/{monaco-contributions-CvGcBIsb.js → monaco-contributions-BW4ZMKO6.js} +1 -1
  95. package/src/client/dist/pwa/assets/{monaco.contribution-Uw-Lfb8Z.js → monaco.contribution-DJWqzNG-.js} +2 -2
  96. package/src/client/dist/pwa/assets/network-auth-BGPRhUa6.js +1 -0
  97. package/src/client/dist/pwa/assets/{notify-theme-PMSxqP6t.js → notify-theme-DZDzfxph.js} +1 -1
  98. package/src/client/dist/pwa/assets/{project-color-BDQa2TgU.js → project-color-gB5yeZPN.js} +1 -1
  99. package/src/client/dist/pwa/assets/{python-DlIgD1n6.js → python-BkBj1wDC.js} +1 -1
  100. package/src/client/dist/pwa/assets/{razor-AraFPlpM.js → razor-DOOJ_0b8.js} +1 -1
  101. package/src/client/dist/pwa/assets/{typescript-CB8UpXHM.js → typescript-Bheo43G5.js} +1 -1
  102. package/src/client/dist/pwa/assets/{use-search-index-status-CSREHQM4.js → use-search-index-status-CBpnyV-R.js} +1 -1
  103. package/src/client/dist/pwa/assets/use-tours-BHNasCvg.js +2 -0
  104. package/src/client/dist/pwa/assets/{use-worktree-restore-BM3Giig1.js → use-worktree-restore-CgABsWno.js} +1 -1
  105. package/src/client/dist/pwa/assets/{workspace-templates-BPMFvTnQ.js → workspace-templates-BnFkigYg.js} +1 -1
  106. package/src/client/dist/pwa/assets/{xml-uRjL1P43.js → xml-vHt2BD26.js} +1 -1
  107. package/src/client/dist/pwa/assets/{yaml-C9CFR4qV.js → yaml-BvhLPzP9.js} +1 -1
  108. package/src/client/dist/pwa/index.html +1 -1
  109. package/src/client/dist/pwa/sw.js +1 -1
  110. package/src/mcp-server/README.md +156 -12
  111. package/src/mcp-server/kobo-tasks-server.ts +73 -106
  112. package/src/mcp-server/workspace-dialogue-client.ts +18 -3
  113. package/src/client/dist/pwa/assets/ActivityFeed-BTRzc60o.js +0 -2
  114. package/src/client/dist/pwa/assets/CreatePage-BZAoCukb.js +0 -2
  115. package/src/client/dist/pwa/assets/CreatePage-CdCs8PRS.css +0 -1
  116. package/src/client/dist/pwa/assets/DiffViewer-D0K2PlZD.js +0 -8
  117. package/src/client/dist/pwa/assets/GitPanel-C-Ejlc9_.js +0 -3
  118. package/src/client/dist/pwa/assets/MainLayout-BQOOup9o.css +0 -1
  119. package/src/client/dist/pwa/assets/MainLayout-DXQ1ouYl.js +0 -2
  120. package/src/client/dist/pwa/assets/QSelect-MdPXvwm4.js +0 -1
  121. package/src/client/dist/pwa/assets/SettingsPage-DcV6apqE.js +0 -9
  122. package/src/client/dist/pwa/assets/WorkspacePage-BwUkijpS.js +0 -4
  123. package/src/client/dist/pwa/assets/WorkspacePage-Bwfv559o.css +0 -1
  124. package/src/client/dist/pwa/assets/custom-sounds-CeSgjIY7.js +0 -1
  125. package/src/client/dist/pwa/assets/de-CHY2BAfL.js +0 -17
  126. package/src/client/dist/pwa/assets/es-DbcSKp38.js +0 -17
  127. package/src/client/dist/pwa/assets/fr-CyyJ8S2c.js +0 -17
  128. package/src/client/dist/pwa/assets/i18n-CobVQiTM.js +0 -1
  129. package/src/client/dist/pwa/assets/index-ImUH7Bbt.js +0 -20
  130. package/src/client/dist/pwa/assets/it-XQJW2Xkr.js +0 -17
  131. package/src/client/dist/pwa/assets/network-auth-D0Vm6a09.js +0 -1
  132. package/src/client/dist/pwa/assets/use-tours-6yxNVmjH.js +0 -2
package/AGENTS.md CHANGED
@@ -116,13 +116,15 @@ src/
116
116
  | `tasks` | workspace sub-items: title, status, `is_acceptance_criterion`, sort_order; CASCADE DELETE on workspace |
117
117
  | `agent_sessions` | agent-engine sessions: pid where applicable, engine session id, status, timestamps, model, and name |
118
118
  | `ws_events` | persisted WebSocket events for replay on reconnect: type, payload, session_id, created_at |
119
- | `pending_review_returns` | one-shot return from a review to its original session and LLM configuration (migration v44) |
119
+ | `pending_review_returns` | durable review-to-source return, original LLM configuration and dispatch phase (v44, extended v52) |
120
+ | `auto_loop_final_reviews` | configured final reviewer, review/correction cycle, scoped verdict and progress tracking (v52) |
120
121
  | `pending_wakeups` | one-row-per-workspace scheduler for the `schedule_wakeup` MCP tool: target_at (ISO UTC), prompt, reason; CASCADE DELETE on workspace |
121
122
  | `pending_quota_backoffs` | persisted quota retries and their attempt counts |
122
123
  | `pending_crons` | recurring workspace schedules and their next run |
123
124
  | `session_event_metrics` / `usage_snapshots` | session event counters and provider usage snapshots |
124
125
  | `workspace_activity` | significant lifecycle/PR events for the absence digest |
125
126
  | `search_*` | derived logical-message search index and indexing progress; see `search-schema.ts` |
127
+ | `workspace_message_batches` | immutable recipient snapshots, request fingerprint and per-recipient delivery outcomes (v53); no stored message body |
126
128
  | `mcp_message_requests` | durable external-message idempotency ledger; independent of event retention |
127
129
  | `schema_migrations` | applied migration versions, names and timestamps |
128
130
  | `workspace_chat_history` | chat-input history per workspace: message text + `created_at`, ordered by autoincrement id, capped at 200 entries by the service; CASCADE DELETE on workspace |
@@ -210,6 +212,8 @@ Claude SDK `task_*` messages cover agents **and** background jobs. Preserve `tas
210
212
 
211
213
  ### External MCP dialogue and update checks
212
214
 
215
+ Workspace-bound stdio, global stdio and HTTP expose shared `create_workspace` / `diagnose_workspace_pr` definitions in `src/shared/workspace-creation-tools.ts`. Stdio forwards them to the backend. `workspace-creation-mcp-service.ts` validates inputs and dispatches through the existing workspace and PR routes in process; preserve UI validation, checkout fingerprints, lifecycle guards and rollback. Inputs use snake_case except the existing camelCase PR checkout decisions. Attachments are inline base64, bounded to the shared 10-file/50 MiB decoded limit before checkout mutations. Keep SDK HTTP/stdio limits aligned with the shared creation envelope budget. Only creation envelopes get the larger base64 HTTP body budget; ordinary MCP requests remain 1 MiB. After a known PR creation failure, rollback only a checkout proven newly created, unchanged, clean (including ignored files) and unowned, under the Git repository lock; retain other checkouts and expose recovery details. Do not retry uncertain creation automatically or treat `creation_id` as an idempotency key. Restricted review/handoff launches cannot create workspaces or invoke lifecycle mutations. Shared lifecycle tools (`workspace-lifecycle-tools.ts` / `workspace-lifecycle-mcp-service.ts`) also expose archive, purge, delete, unarchive and restore through the same transports. Reject reserved/bulk/path-like IDs, require explicit purge/delete flags, and preserve warnings. Single-workspace DELETE accepts optional `confirmationBranch`, rechecked under its lifecycle guard before stopping processes; MCP always supplies it. Branch rename/resync share this guard so the confirmed branch and checkout path cannot change during teardown. Recovery uses `restore-worktree` before unarchive and never starts an agent.
216
+
213
217
  `POST /api/mcp` exposes stateless Streamable HTTP discovery/dialogue tools, behind the existing Host/Origin and network-token gates. The global stdio server forwards dialogue calls through this backend so agent runtime ownership remains centralized. `workspace-message-service.ts` shares delivery with WebSocket chat.
214
218
 
215
219
  Optional `send_workspace_message.idempotency_key` uses `mcp_message_requests` (migration v43). Reserve before dispatch, preserve fingerprints and terminal outcomes, and never automatically resend an unfinished request after restart. The user event and accepted receipt commit in one SQLite transaction; broadcast follows commit. Dispatch may precede persistence, so interrupted requests are explicitly `delivery_unknown`, not an exactly-once execution promise. The backend alone calls `reconcileMessageRequests` on startup. History retention does not delete the ledger; workspace deletion cascades it.
@@ -367,11 +371,33 @@ Resume selection is scoped to the target engine; never pass a reviewer's native
367
371
  conversation id to the original engine, even after cancellation or restart.
368
372
  The report reassembles text deltas/final snapshots, is bounded to 24,000 characters,
369
373
  and links back to the complete review session through the conversation tool.
370
- The receiving agent is asked to summarize and await instructions before fixes.
371
- The intent is consumed before delivery, preventing repeated ends from sending twice.
372
- Errors restore settings without automatically resuming; explicit stop cancels the
373
- return. Startup restores interrupted-review settings without replaying a possibly
374
- already-delivered handoff. Preserve these guards and the auto-loop interception.
374
+ For a manual review, the receiving agent summarizes and awaits instructions before fixes.
375
+ The durable phase tracks `reviewing`, `ready`, `dispatching`, `unknown` and `blocked`.
376
+ Client disconnect and server shutdown preserve the original session, configurations,
377
+ review prompt and completed return prompt. Startup resumes an interrupted reviewer
378
+ or a known-unsent return after admission and confirmed runtime closure. Claim the
379
+ return before dispatch; confirm it only once the engine accepts the initial input.
380
+ Claude's session initialization is insufficient: wait for a foreground acknowledgement
381
+ correlated with the initial message UUID (`EngineProcess.initialPromptAccepted`).
382
+ Codex's readiness follows the accepted `turn.start` response.
383
+ An interrupted dispatch becomes `unknown`, with explicit retry/cancel controls;
384
+ never automatically resend a possibly delivered summary. Explicit user stop cancels
385
+ the continuation. Errors retain recoverable intent and restore original settings.
386
+
387
+ `auto_loop_final_reviews` (migration v52) adds an optional final gate configured
388
+ before execution. Each pass starts a separate Plan review session with its own
389
+ engine/model/effort/instructions. Only the active reviewer capability may submit
390
+ the structured `kobo__submit_final_review` verdict. Findings become work tasks,
391
+ invalidate finalization, and return to the exact original working session for
392
+ automatic corrections under its original permissions. Repeat verification and a
393
+ fresh review until zero findings, then summarize in the source session before
394
+ completion. The source must come from the actual loop controller/ending writer, including
395
+ adoption of an already-running session; never infer it from UI selection. A
396
+ reviewer resume failure invalidates only its own native conversation, preserving
397
+ other source sessions. Confirmed capacity release wakes manual durable returns
398
+ as well as auto-loops. Preserve the loop's admission, quota, stop and stagnation safeguards;
399
+ prose alone cannot clear the gate. Persist this gate and manual review returns
400
+ across restart without allowing a second writer.
375
401
  `workspace:configuration` is ephemeral and updates all four LLM settings across tabs.
376
402
 
377
403
  ### Delete confirmation
@@ -504,6 +530,12 @@ unread events or deleting history; re-enabling resumes from the saved checkpoint
504
530
 
505
531
  `src/client/src/utils/workspace-attention.ts` derives a small set of badges (CI failure, changes-requested) from the PR snapshot + git stats stored on each workspace. `WorkspaceAttentionLabels.vue` renders them inline on the workspace cards in the left drawer. The derivation is a pure function, easy to unit-test and free of IO. Drawer cards therefore stay reactive to whatever the pr-watcher / bulk-info refresh writes back into the store.
506
532
 
533
+ ### Group workspace messages
534
+
535
+ `workspace-group-message-service.ts` owns durable group receipts and a server-wide queue capped at three simultaneous delivery attempts. `/api/workspace-messages` creates a batch (202); `/:id` reads its receipt. Requests contain explicit unique workspace IDs, one content body and a stable request ID. A repeated ID is observation only; different content/recipients/source conflicts. Tags/status filters are preview helpers (OR within each group, AND across groups); archived/purged workspaces cannot receive messages. The optional `devServerRunning` preview filter (`dev_server_running` in MCP) requires exact dev-server status `running` and intersects tags/statuses. The dialog prefers the live dev-server store status over the workspace snapshot. Both UI tag filters union the global settings catalogue with assigned tags via `collectTags`; unused catalogue tags have count zero. Wait for settings and archived workspaces to load before pruning a remembered sidebar selection.
536
+
537
+ Snapshot each target and its delivery mode before scheduling: auto-loop uses `next_iteration`, other workspaces normal chat. Preserve common lifecycle, human-input and compaction guards; a mode change rejects instead of silently changing behavior. For an existing controller, run `beforeDispatch` after its startup promise settles and immediately before engine delivery, so a mode change during startup is still rejected. Receipt completion means accepted/queued, not an agent reply. Persist chat event and accepted outcome transactionally; failed notification cannot undo acceptance. Startup marks pending targets `not_sent` and in-flight ones `unknown`, never replays them. Shutdown stops scheduling before draining active attempts and closing SQLite. Migration v53 and fresh schema share `initWorkspaceMessageBatchSchema`. The dialog stores its immutable request in per-tab sessionStorage for reload recovery and never POSTs automatically on reload. Keep all MCP transports on the same backend owner, launch read-only restrictions and own-workspace exclusion.
538
+
507
539
  ### Bulk workspace info refresh
508
540
 
509
541
  `GET /api/workspaces/info` returns `{ workspaces, prSnapshots, gitStats }` in one shot. The client polls this endpoint every 15 s so every non-archived workspace stays ≤ 15 s fresh without a per-card stats request. The poll is skipped while the browser tab is hidden and fires once immediately when the tab becomes visible again. The server-side pr-watcher feeds the same caches (`lastKnownGitStats` map, PR snapshots) so the work is shared between the polling client and the watchdog loop.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 2.1.16
8
+
9
+ - feat(workspaces): add group messaging and expand mcp lifecycle tools
10
+
11
+ ## 2.1.15
12
+
13
+ - feat(auto-loop): add durable final reviews and correction cycles
14
+
15
+ ## 2.1.14
16
+
17
+ - fix(auto-loop): correct banner visibility and allow dismissal
18
+
7
19
  ## 2.1.13
8
20
 
9
21
  - fix(claude): count streaming generation as watchdog activity
package/CONFIGURATION.md CHANGED
@@ -502,6 +502,18 @@ Two consequences worth knowing:
502
502
  - However far behind the loop falls (a laptop waking from sleep, say), it sends
503
503
  **one** reminder per pass, not one per missed interval.
504
504
 
505
+ ## Group workspace messages
506
+
507
+ Use **Group message** in the workspace drawer to send the same instruction to several workspaces. Combine tags and workspace statuses, review the named recipient list, and deselect individual workspaces before sending. Selected tags match any tag; selected statuses match any status; both filter groups must match. Enable **Only workspaces with a running development server** to further restrict the list to server status `running`; starting, stopping, stopped, unknown and failed servers are excluded. This filter updates with received server status changes. Empty filters include all non-archived, non-purged workspaces. Both the group-message tag selector and the sidebar tag filter include the saved global tag catalogue plus tags already assigned to workspaces. Unused catalogue tags remain visible (with a zero count in the sidebar); assign a tag through a workspace menu before using it to target that workspace.
508
+
509
+ Auto-loop recipients receive a durable instruction for their next iteration, without stopping the loop. Other recipients use normal chat delivery, which can resume an inactive agent. Normal chat restrictions still apply: pending human questions, compaction and lifecycle operations are not bypassed. The delivery mode is fixed when submitted; a subsequent auto-loop mode change rejects that recipient rather than silently switching modes.
510
+
511
+ Each batch accepts up to 200 recipients and 100,000 characters of plain text. Delivery runs on the server with at most three attempts in flight. Results distinguish sent, queued for auto-loop, rejected, not sent and uncertain delivery. **Queued** means saved for an upcoming iteration, not already processed by the agent. Failures do not stop delivery to the other recipients.
512
+
513
+ Closing the dialog or disconnecting the browser does not cancel an accepted batch. The browser tab retains the request ID and original payload across reloads to recover its receipt, independently of other tabs. **Check this delivery** reuses that request and cannot duplicate accepted deliveries. After a server restart, unstarted recipients become **Not sent**, and interrupted attempts become **Delivery uncertain**; they are never resent automatically. Inspect the affected workspace's chat before creating a new message for an uncertain result.
514
+
515
+ The same feature is available through internal/global stdio and external HTTP MCP; see the [MCP guide](src/mcp-server/README.md). Batch receipts retain recipient names, IDs, modes and outcomes independently of workspace deletion. The batch table stores a content fingerprint, not the message body; accepted messages retain normal workspace chat history.
516
+
505
517
  ## Attachments in the creation description
506
518
 
507
519
  On the create page, paste or drop files into the mission description, or use
@@ -615,9 +627,33 @@ receiving agent summarizes the findings and waits for your instructions before
615
627
  applying fixes. The complete review remains in its own session history.
616
628
 
617
629
  With the option off, subsequent conversation continues with the review LLM.
618
- When automatic return is enabled, stopping the review cancels it. If the review
619
- fails or Kobo restarts during it, the original settings are restored without launching the
620
- original agent automatically; review output already recorded remains available.
630
+ Closing the browser or shutting down Kōbō preserves the automatic return. After a
631
+ server restart, Kōbō resumes the interrupted review and returns its summary to the
632
+ original conversation when capacity and quotas permit. A completed review waiting
633
+ to return keeps its summary. Explicitly stopping the agent cancels the return.
634
+
635
+ If the server stops during summary delivery and cannot confirm whether it arrived,
636
+ the chat displays a pending return with **Retry** and **Cancel**. Inspect the original
637
+ conversation before retrying: it may already contain that summary. A failed review
638
+ also keeps a recoverable return instead of silently losing it.
639
+
640
+ ### Final review for auto-loop
641
+
642
+ Enable **Final review** in the creation form or the workspace's Auto-loop panel.
643
+ Configure the reviewer engine, model, reasoning effort and additional instructions
644
+ in advance. Workspace templates and remembered creation preferences retain these
645
+ settings. Each review uses a separate session in Plan mode and returns to the exact
646
+ working session with its original LLM settings and permissions.
647
+
648
+ After all work and final checks pass, Kōbō launches the reviewer. Its structured
649
+ findings become tasks for the original agent to correct automatically. The agent
650
+ verifies the corrections and completes final checks, then Kōbō launches a fresh
651
+ review. This cycle continues until a review reports zero findings; the original
652
+ session receives the result before auto-loop completes. Repeated unchanged findings
653
+ trigger a diagnostic and bounded further attempts, then block for attention.
654
+ Closing the client or restarting the server preserves this cycle as well as the
655
+ return. A manual review outside auto-loop still summarizes and waits for your
656
+ instructions before applying fixes.
621
657
 
622
658
  ## Workspace templates and duplication
623
659
 
@@ -803,7 +839,7 @@ The setting lives in `settings.json` under `global` and has no UI control yet:
803
839
 
804
840
  ## External LLM access through MCP
805
841
 
806
- An external MCP client can connect to `http://127.0.0.1:3000/api/mcp` using Streamable HTTP (use your backend's configured port). It can discover workspaces, read their conversations, send messages to their agents and answer pending questions. The existing global stdio MCP server exposes these conversation tools too.
842
+ An external MCP client can connect to `http://127.0.0.1:3000/api/mcp` using Streamable HTTP (use your backend's configured port). It can create workspaces with the same options as the form, diagnose and check out existing PRs, discover workspaces, read their conversations, send messages and answer pending questions. Workspace-bound stdio, global stdio and HTTP share the creation contract, including brainstorm settings, final auto-loop review, workflow policy, imports, existing worktrees, comparison grouping and inline attachments. All three transports also expose archive, disk-space purge, permanent deletion with explicit branch confirmation, unarchive and recovery of purged worktrees. See the [creation and PR guide](src/mcp-server/README.md#create_workspace) for fields, explicit checkout decisions and timeout/retry behavior.
807
843
 
808
844
  Remote HTTP clients follow the [network access](#network-access) settings and supply the Kōbō token as `Authorization: Bearer <token>` or `X-Kobo-Token`. The endpoint uses the same Host/Origin and reverse-proxy policy as the rest of the backend. No network setting is changed automatically.
809
845
 
@@ -1984,7 +2020,7 @@ and across Unicode normalization boundaries.
1984
2020
 
1985
2021
  ## Auto-loop completion and recovery
1986
2022
 
1987
- Auto-loop remains enabled while work is unfinished. Its panel distinguishes grooming, execution and final verification from waiting, blocked, completed and stopped states. All todos and acceptance criteria must be complete, followed by a successful final verification. The final check runs again after task changes or new instructions. Empty custom finalization settings use the built-in final check.
2023
+ Auto-loop remains enabled while work is unfinished. Its panel distinguishes grooming, execution and final verification from waiting, blocked, completed and stopped states. All todos and acceptance criteria must be complete, followed by a successful final verification. The final check runs again after task changes or new instructions. Empty custom finalization settings use the built-in final check. With [Final review](#final-review-for-auto-loop) configured, completion also requires an independent review with zero findings and the summary returned to the original session. Review findings reopen work and final verification before another review.
1988
2024
 
1989
2025
  Completing an auto-loop task through MCP or HTTP requires structured verification: a non-empty `method`, `summary`, and `checks` array with named checks whose `status` is `passed`. A required test that could not run stays open (`not_run`); writing test source alone is insufficient. These are results reported by the agent, not independently executed by Kōbō.
1990
2026
 
package/README.md CHANGED
@@ -19,9 +19,11 @@ Kōbō (工房, "workshop") turns *one agent, one terminal* into a real workflow
19
19
  |---|---|
20
20
  | **🗂️ Isolated worktrees, two engines** | Every workspace is its own git worktree and branch — parallel sessions never collide. Pick Claude Code or OpenAI Codex per workspace, and [continue in a fresh session](#fresh-sessions-and-llm-handoffs) with the same LLM or a different model/engine, preserving the worktree and mission history. |
21
21
  | **💬 Live chat, real diff review** | Streaming responses, inline Edit/Write diffs, a reasoning panel, and a Monaco diff viewer with **inline file editing**, conflict resolution, and one-click `Sync` / `Push` / `Open PR` / `Merge`. |
22
- | **🔁 Auto-loop, cron, wakeups** | Turn an agent loose on the task list: it works through tasks, retries on rate limits, and stops itself when there's nothing left, progress stalls, or it needs you. Cron schedules and one-shot wakeups keep workspaces moving on their own timeline. |
22
+ | **🔁 Auto-loop, cron, wakeups** | Turn an agent loose on the task list: it works through tasks, retries on rate limits, and stops itself when there's nothing left, progress stalls, or it needs you. An optional [independent final review](CONFIGURATION.md#final-review-for-auto-loop) returns findings to the original session for automatic correction and repeats until none remain, with durable recovery after restart. Cron schedules and one-shot wakeups keep workspaces moving on their own timeline. |
23
23
  | **🔀 Create from a PR/MR, or from a ticket** | Pick an open pull/merge request from GitHub, GitLab, or Bitbucket and Kōbō resolves every local conflict for you before spinning up the workspace. Or start straight from a Notion page or a Sentry issue URL. |
24
24
 
25
+ Use [group messages](CONFIGURATION.md#group-workspace-messages) to send one instruction to selected workspaces, filtered by tags and status, from the interface or MCP. Auto-loops receive it on their next iteration; each recipient has a durable delivery result.
26
+
25
27
  ## Quick start
26
28
 
27
29
  Requires Node.js ≥ 24.15, Git, Bash and your own Claude Code **or** Codex authentication. No skill plugin or ticket integration is required. Kōbō is a local, single-user tool; each user installs their own instance.
@@ -51,7 +53,7 @@ Kōbō's production build is an installable PWA — use your browser's **Install
51
53
  - **Attachments from the first prompt**: paste, drop or attach screenshots, Markdown, text files or PDFs to the creation description or workspace chat. Kōbō copies them into the worktree for the agent. See [creation attachments](./CONFIGURATION.md#attachments-in-the-creation-description) and [chat attachments](./CONFIGURATION.md#attachments-in-workspace-chat).
52
54
 
53
55
  - **Command palette & search**: `/` autocompletes skills and commands, `@` fuzzy-completes worktree file paths, `Ctrl+F` searches readable messages, `Ctrl+K` opens a command palette. The global search page deep-links to the same spot.
54
- - **MCP toolset (`kobo-tasks`)**: task/acceptance-criteria CRUD, dev server control, a unified `get_ticket` (Notion or Sentry), cross-workspace conversation search, per-session usage, and a `.ai/thoughts` decision log — see the [MCP guide](./src/mcp-server/README.md#tools). External MCP clients can also discover workspaces, read conversations, send messages, and answer questions through HTTP or global stdio.
56
+ - **MCP toolset (`kobo-tasks`)**: task/acceptance-criteria CRUD, dev server control, a unified `get_ticket` (Notion or Sentry), cross-workspace conversation search, per-session usage, and a `.ai/thoughts` decision log — see the [MCP guide](./src/mcp-server/README.md#tools). Internal and external MCP clients can create fully configured workspaces, including final auto-loop review, attachments and checkout of an existing PR. HTTP and stdio clients can also archive, purge, delete or restore workspaces, read conversations, send messages, and answer questions.
55
57
 
56
58
  ![Sub-agents panel showing parallel tool calls](docs/assets/images/sub-agents-panel.png)
57
59
  - **Multi-forge Git panel**: GitHub (`gh`), GitLab (`glab`), or Bitbucket Community (`bkt`), auto-detected from the remote — `Open PR`, `Merge ready PR`, `Change PR base`, `Change source branch`, all from the UI.
@@ -4,10 +4,15 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { getDb } from '../server/db/index.js';
6
6
  import { EXTERNAL_MEMORY_TOOL_DEFINITIONS, isMemoryToolName, MEMORY_TOOL_DEFINITIONS } from '../shared/memory-tools.js';
7
+ import { isWorkspaceCreationTool, MAX_MCP_CREATION_REQUEST_BYTES, WORKSPACE_CREATION_TOOLS, } from '../shared/workspace-creation-tools.js';
7
8
  import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js';
9
+ import { isWorkspaceGroupMessageTool, WORKSPACE_GROUP_MESSAGE_TOOLS } from '../shared/workspace-group-message-tools.js';
10
+ import { isWorkspaceLifecycleTool, WORKSPACE_LIFECYCLE_TOOLS } from '../shared/workspace-lifecycle-tools.js';
8
11
  import { createTaskHandler, cronListHandler, deleteTaskHandler, getDevServerStatusHandler, getSessionUsageHandler, getSettingsHandler, getTicketSourcesHandler, getWorkspaceInfoHandler, listDocumentsHandler, listTasksHandler, listWorkspaceImagesHandler, listWorkspacesHandler, logThoughtHandler, markAutoLoopReadyHandler, markTaskDoneHandler, readDocumentHandler, readWorkspaceEventsCsvHandler, setWorkspaceAgentDescriptionHandler, updateTaskHandler, } from './kobo-tasks-handlers.js';
9
12
  import { callMemoryTool, isMemoryOutputSuppressed } from './memory-client.js';
10
13
  import { callWorkspaceDialogueTool } from './workspace-dialogue-client.js';
14
+ const finalReviewToken = process.env.KOBO_FINAL_REVIEW_TOKEN;
15
+ const finalReviewSessionId = process.env.KOBO_FINAL_REVIEW_SESSION_ID;
11
16
  const handoffId = process.env.KOBO_HANDOFF_ID;
12
17
  const handoffToken = process.env.KOBO_HANDOFF_TOKEN;
13
18
  const workspaceId = process.env.KOBO_WORKSPACE_ID;
@@ -150,6 +155,33 @@ const TASK_ORDER_PROPERTIES = {
150
155
  },
151
156
  };
152
157
  const WORKSPACE_SCOPED_TOOLS = [
158
+ {
159
+ name: 'submit_final_review',
160
+ description: 'Submit the final structured verdict for this backend-owned auto-loop review, then end your turn. Include all actionable findings; use an empty findings array only when the entire mission is clear.',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ summary: { type: 'string', minLength: 1, maxLength: 12000 },
165
+ findings: {
166
+ type: 'array',
167
+ maxItems: 100,
168
+ items: {
169
+ type: 'object',
170
+ properties: {
171
+ severity: { type: 'string', enum: ['critical', 'important', 'minor'] },
172
+ file: { type: 'string', minLength: 1, maxLength: 1000 },
173
+ line: { type: 'integer', minimum: 1 },
174
+ description: { type: 'string', minLength: 1, maxLength: 4000 },
175
+ recommendation: { type: 'string', minLength: 1, maxLength: 4000 },
176
+ },
177
+ required: ['severity', 'file', 'description', 'recommendation'],
178
+ },
179
+ },
180
+ },
181
+ required: ['summary', 'findings'],
182
+ },
183
+ annotations: { destructiveHint: false, openWorldHint: false },
184
+ },
153
185
  {
154
186
  name: 'submit_session_handoff',
155
187
  description: 'Submit the final Markdown handoff requested by Kōbō for this generation turn, then end your turn. Only available during a backend-owned session transfer; does not launch or stop sessions.',
@@ -537,6 +569,9 @@ const WORKSPACE_SCOPED_TOOLS = [
537
569
  ];
538
570
  const GLOBAL_TOOLS = [
539
571
  ...WORKSPACE_DIALOGUE_TOOLS,
572
+ ...WORKSPACE_CREATION_TOOLS,
573
+ ...WORKSPACE_LIFECYCLE_TOOLS,
574
+ ...WORKSPACE_GROUP_MESSAGE_TOOLS,
540
575
  ...EXTERNAL_MEMORY_TOOL_DEFINITIONS,
541
576
  {
542
577
  name: 'list_workspaces',
@@ -558,63 +593,6 @@ const GLOBAL_TOOLS = [
558
593
  },
559
594
  annotations: { readOnlyHint: true, openWorldHint: false },
560
595
  },
561
- {
562
- name: 'create_workspace',
563
- description: 'Create a new Kōbō workspace (git worktree + agent session), like the "Créer" button on the Create page. Requires the Kōbō backend server to be running and reachable at KOBO_BACKEND_URL. name/project_path/source_branch/working_branch are mandatory — unlike the UI, this API does not auto-derive a branch name from the workspace name.',
564
- inputSchema: {
565
- type: 'object',
566
- properties: {
567
- name: { type: 'string', description: 'Workspace display name.' },
568
- project_path: { type: 'string', description: 'Absolute path to the project on disk.' },
569
- source_branch: { type: 'string', description: 'Branch to base the new worktree on (e.g. "develop").' },
570
- working_branch: {
571
- type: 'string',
572
- description: 'New branch name for the worktree (e.g. "feature/my-thing").',
573
- },
574
- model: { type: 'string', description: 'Model id override (optional, defaults to project/global setting).' },
575
- reasoning_effort: { type: 'string', description: 'Reasoning effort override (optional).' },
576
- engine: { type: 'string', description: 'Agent engine id, e.g. "claude-code" or "codex" (optional).' },
577
- description: { type: 'string', description: 'Task description / initial brainstorming prompt (optional).' },
578
- tags: {
579
- type: 'array',
580
- items: { type: 'string' },
581
- description: 'Workspace tags to assign at creation (optional).',
582
- },
583
- tasks: { type: 'array', items: { type: 'string' }, description: 'Initial manual task titles (optional).' },
584
- acceptance_criteria: {
585
- type: 'array',
586
- items: { type: 'string' },
587
- description: 'Initial manual acceptance criteria (optional).',
588
- },
589
- agent_permission_mode: {
590
- type: 'string',
591
- enum: ['plan', 'bypass', 'strict', 'interactive'],
592
- description: 'Permission mode for the new session (optional).',
593
- },
594
- auto_loop: { type: 'boolean', description: 'Start the workspace in auto-loop mode (optional).' },
595
- auto_loop_session_mode: {
596
- type: 'string',
597
- enum: ['per_task', 'continuous'],
598
- description: "Auto-loop session mode when auto_loop is true (optional, default 'per_task').",
599
- },
600
- skip_setup_script: { type: 'boolean', description: "Skip the project's setup script (optional)." },
601
- },
602
- required: ['name', 'project_path', 'source_branch', 'working_branch'],
603
- },
604
- annotations: { destructiveHint: false, openWorldHint: false },
605
- },
606
- {
607
- name: 'archive_workspace',
608
- description: 'Archive a workspace by id, like the "Archiver" action in the workspace context menu. Requires the Kōbō backend server to be running.',
609
- inputSchema: {
610
- type: 'object',
611
- properties: {
612
- workspace_id: { type: 'string', description: 'Workspace id, from list_workspaces.' },
613
- },
614
- required: ['workspace_id'],
615
- },
616
- annotations: { destructiveHint: false, openWorldHint: false },
617
- },
618
596
  {
619
597
  name: 'stop_workspace',
620
598
  description: 'Force-stop the currently running agent session on a workspace, like the red "Arrêter" button in the chat header. Requires the Kōbō backend server to be running. Safe to call when nothing is running.',
@@ -643,7 +621,9 @@ function availableTools() {
643
621
  : GLOBAL_TOOLS;
644
622
  return tools.filter((tool) => handoffId && handoffToken
645
623
  ? tool.name === 'submit_session_handoff' || tool.annotations?.readOnlyHint === true
646
- : tool.name !== 'submit_session_handoff');
624
+ : finalReviewToken && finalReviewSessionId
625
+ ? tool.name === 'submit_final_review' || tool.annotations?.readOnlyHint === true
626
+ : tool.name !== 'submit_session_handoff' && tool.name !== 'submit_final_review');
647
627
  }
648
628
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: availableTools() }));
649
629
  /** Wrap a successful result as an MCP tool response with JSON text content. */
@@ -675,6 +655,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
675
655
  return fail(`Tool '${name}' requires a workspace-bound MCP session (KOBO_WORKSPACE_ID not set).`);
676
656
  }
677
657
  try {
658
+ if (finalReviewToken && finalReviewSessionId && !availableTools().some((tool) => tool.name === name))
659
+ return fail('This is a read-only final review. Only read tools and submit_final_review are available.');
660
+ if (name === 'submit_final_review') {
661
+ if (!workspaceId || !finalReviewToken || !finalReviewSessionId)
662
+ return fail('No final review is active for this MCP invocation.');
663
+ return ok(await backendRequest('POST', `/api/workspaces/${workspaceId}/auto-loop/final-review/report`, {
664
+ token: finalReviewToken,
665
+ sessionId: finalReviewSessionId,
666
+ report: a,
667
+ }));
668
+ }
678
669
  if (handoffId && handoffToken && !availableTools().some((tool) => tool.name === name))
679
670
  return fail('This turn is reserved for a handoff report. Only read tools and submit_session_handoff are available.');
680
671
  if (name === 'submit_session_handoff') {
@@ -709,8 +700,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
709
700
  : {}),
710
701
  };
711
702
  }
712
- if (WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
713
- if (workspaceId && a.workspace_id === workspaceId && name === 'send_workspace_message')
703
+ if (isWorkspaceCreationTool(name) ||
704
+ isWorkspaceLifecycleTool(name) ||
705
+ isWorkspaceGroupMessageTool(name) ||
706
+ WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
707
+ if (workspaceId &&
708
+ ((a.workspace_id === workspaceId && name === 'send_workspace_message') ||
709
+ (name === 'send_workspace_group_message' &&
710
+ Array.isArray(a.workspace_ids) &&
711
+ a.workspace_ids.includes(workspaceId))))
714
712
  return fail('Use the normal conversation to communicate in your own workspace.');
715
713
  return await callWorkspaceDialogueTool(backendUrl, name, a, process.env.KOBO_NETWORK_TOKEN, process.env.KOBO_MCP_CLIENT_NAME ?? server.getClientVersion()?.name);
716
714
  }
@@ -983,50 +981,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
983
981
  const tag = typeof a.tag === 'string' ? a.tag : undefined;
984
982
  return ok(listWorkspacesHandler(db, { includeArchived, tag }));
985
983
  }
986
- if (name === 'create_workspace') {
987
- const workspaceName = a.name;
988
- const projectPath = a.project_path;
989
- const sourceBranch = a.source_branch;
990
- const workingBranch = a.working_branch;
991
- if (!workspaceName || !projectPath || !sourceBranch || !workingBranch) {
992
- return fail('name, project_path, source_branch, and working_branch parameters are required');
993
- }
994
- try {
995
- const created = await backendRequest('POST', '/api/workspaces', {
996
- name: workspaceName,
997
- projectPath,
998
- sourceBranch,
999
- workingBranch,
1000
- model: a.model,
1001
- reasoningEffort: a.reasoning_effort,
1002
- engine: a.engine,
1003
- description: a.description,
1004
- tags: a.tags,
1005
- tasks: a.tasks,
1006
- acceptanceCriteria: a.acceptance_criteria,
1007
- agentPermissionMode: a.agent_permission_mode,
1008
- autoLoop: a.auto_loop,
1009
- autoLoopSessionMode: a.auto_loop_session_mode,
1010
- skipSetupScript: a.skip_setup_script,
1011
- });
1012
- return ok(created);
1013
- }
1014
- catch (err) {
1015
- return fail(backendErrorMessage(err));
1016
- }
1017
- }
1018
- if (name === 'archive_workspace') {
1019
- const targetId = a.workspace_id;
1020
- if (!targetId)
1021
- return fail('workspace_id parameter is required');
1022
- try {
1023
- const result = await backendRequest('POST', `/api/workspaces/${targetId}/archive`);
1024
- return ok(result);
1025
- }
1026
- catch (err) {
1027
- return fail(backendErrorMessage(err));
1028
- }
1029
- }
1030
984
  if (name === 'stop_workspace') {
1031
985
  const targetId = a.workspace_id;
1032
986
  if (!targetId)
@@ -1045,7 +999,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1045
999
  return fail(err instanceof Error ? err.message : String(err));
1046
1000
  }
1047
1001
  });
1048
- const transport = new StdioServerTransport();
1002
+ const transport = new StdioServerTransport(process.stdin, process.stdout, {
1003
+ // Include the JSON-lines delimiter in addition to the HTTP envelope budget.
1004
+ maxBufferSize: MAX_MCP_CREATION_REQUEST_BYTES + 1,
1005
+ });
1049
1006
  server.connect(transport).catch((err) => {
1050
1007
  console.error('[kobo-tasks-server] Fatal:', err);
1051
1008
  process.exit(1);
@@ -1,15 +1,27 @@
1
1
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
3
  import { isMemoryToolName, validateMemoryToolArguments } from '../shared/memory-tools.js';
4
+ import { isWorkspaceCreationTool, validateWorkspaceCreationArguments } from '../shared/workspace-creation-tools.js';
4
5
  import { validateDialogueArguments } from '../shared/workspace-dialogue-tools.js';
6
+ import { isWorkspaceGroupMessageTool, validateWorkspaceGroupMessageArguments, } from '../shared/workspace-group-message-tools.js';
7
+ import { isWorkspaceLifecycleTool, validateWorkspaceLifecycleArguments } from '../shared/workspace-lifecycle-tools.js';
5
8
  import { normalizeClientName } from '../shared/workspace-message-types.js';
6
9
  /** Stateless HTTP bridge: external stdio clients never own agent runtime state. */
7
10
  export async function callWorkspaceDialogueTool(backendUrl, name, args, token, clientName) {
8
11
  const arguments_ = isMemoryToolName(name)
9
12
  ? validateMemoryToolArguments(name, args, true)
10
- : validateDialogueArguments(name, args);
13
+ : isWorkspaceCreationTool(name)
14
+ ? validateWorkspaceCreationArguments(name, args)
15
+ : isWorkspaceLifecycleTool(name)
16
+ ? validateWorkspaceLifecycleArguments(name, args)
17
+ : isWorkspaceGroupMessageTool(name)
18
+ ? validateWorkspaceGroupMessageArguments(name, args)
19
+ : validateDialogueArguments(name, args);
11
20
  const client = new Client({ name: 'kobo-stdio-bridge', version: '1.0.0' });
12
- const signal = AbortSignal.timeout(30_000);
21
+ // Creation can include forge reads, worktree setup and user setup scripts.
22
+ // A timed-out creation is ambiguous and must never be retried automatically.
23
+ const timeout = isWorkspaceCreationTool(name) || isWorkspaceLifecycleTool(name) ? 15 * 60_000 : 30_000;
24
+ const signal = AbortSignal.timeout(timeout);
13
25
  const transport = new StreamableHTTPClientTransport(new URL(`${backendUrl.replace(/\/$/, '')}/api/mcp`), {
14
26
  requestInit: {
15
27
  signal,
@@ -23,7 +35,7 @@ export async function callWorkspaceDialogueTool(backendUrl, name, args, token, c
23
35
  });
24
36
  try {
25
37
  await client.connect(transport, { signal });
26
- return await client.callTool({ name, arguments: arguments_ }, undefined, { signal });
38
+ return await client.callTool({ name, arguments: arguments_ }, undefined, { signal, timeout });
27
39
  }
28
40
  finally {
29
41
  await client.close();
@@ -0,0 +1,18 @@
1
+ export function initAutoLoopFinalReviewSchema(db) {
2
+ db.exec(`CREATE TABLE IF NOT EXISTS auto_loop_final_reviews (
3
+ workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
4
+ configuration TEXT NOT NULL,
5
+ state TEXT NOT NULL DEFAULT 'pending',
6
+ cycle INTEGER NOT NULL DEFAULT 0,
7
+ findings_count INTEGER,
8
+ reason TEXT,
9
+ review_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
10
+ original_session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
11
+ token TEXT,
12
+ verdict TEXT,
13
+ previous_findings TEXT,
14
+ stagnant_cycles INTEGER NOT NULL DEFAULT 0,
15
+ return_prompt TEXT,
16
+ updated_at TEXT NOT NULL
17
+ )`);
18
+ }
@@ -1,10 +1,12 @@
1
+ import { initAutoLoopFinalReviewSchema } from './auto-loop-final-review-schema.js';
1
2
  import { initAutoLoopSchema } from './auto-loop-schema.js';
2
3
  import { initMcpMessageSchema } from './mcp-message-schema.js';
3
4
  import { migrateMemorySchema } from './memory-schema.js';
4
- import { initReviewReturnSchema } from './review-return-schema.js';
5
+ import { initReviewReturnSchema, migrateDurableReviewReturns } from './review-return-schema.js';
5
6
  import { initSchema } from './schema.js';
6
7
  import { initSearchSchema } from './search-schema.js';
7
8
  import { initSessionHandoffSchema } from './session-handoff-schema.js';
9
+ import { initWorkspaceMessageBatchSchema } from './workspace-message-batch-schema.js';
8
10
  /** Ordered registry of all schema migrations. Append new entries at the end. */
9
11
  export const migrations = [
10
12
  {
@@ -799,6 +801,15 @@ export const migrations = [
799
801
  name: 'persistent-memory-schema',
800
802
  migrate: migrateMemorySchema,
801
803
  },
804
+ {
805
+ version: 52,
806
+ name: 'durable-final-reviews',
807
+ migrate(db) {
808
+ migrateDurableReviewReturns(db);
809
+ initAutoLoopFinalReviewSchema(db);
810
+ },
811
+ },
812
+ { version: 53, name: 'workspace-group-message-receipts', migrate: initWorkspaceMessageBatchSchema },
802
813
  ];
803
814
  /** Current schema version — always equals the highest migration version. */
804
815
  export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
@@ -5,6 +5,23 @@ export function initReviewReturnSchema(db) {
5
5
  original_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
6
6
  original_configuration TEXT NOT NULL,
7
7
  review_configuration TEXT NOT NULL,
8
- created_at TEXT NOT NULL
8
+ created_at TEXT NOT NULL,
9
+ phase TEXT NOT NULL DEFAULT 'reviewing',
10
+ review_prompt TEXT,
11
+ return_prompt TEXT,
12
+ last_error TEXT
9
13
  )`);
10
14
  }
15
+ /** v52: retain the return intent and its dispatch boundary across server restarts. */
16
+ export function migrateDurableReviewReturns(db) {
17
+ const columns = new Set(db.prepare('PRAGMA table_info(pending_review_returns)').all().map((row) => row.name));
18
+ for (const [name, declaration] of Object.entries({
19
+ phase: "TEXT NOT NULL DEFAULT 'reviewing'",
20
+ review_prompt: 'TEXT',
21
+ return_prompt: 'TEXT',
22
+ last_error: 'TEXT',
23
+ })) {
24
+ if (!columns.has(name))
25
+ db.exec(`ALTER TABLE pending_review_returns ADD COLUMN ${name} ${declaration}`);
26
+ }
27
+ }
@@ -1,9 +1,11 @@
1
+ import { initAutoLoopFinalReviewSchema } from './auto-loop-final-review-schema.js';
1
2
  import { initAutoLoopSchema } from './auto-loop-schema.js';
2
3
  import { initMcpMessageSchema } from './mcp-message-schema.js';
3
4
  import { initMemorySchema } from './memory-schema.js';
4
5
  import { initReviewReturnSchema } from './review-return-schema.js';
5
6
  import { initSearchSchema } from './search-schema.js';
6
7
  import { initSessionHandoffSchema } from './session-handoff-schema.js';
8
+ import { initWorkspaceMessageBatchSchema } from './workspace-message-batch-schema.js';
7
9
  /** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
8
10
  export function initSchema(db) {
9
11
  db.exec(`
@@ -236,6 +238,8 @@ export function initSchema(db) {
236
238
  initMcpMessageSchema(db);
237
239
  initReviewReturnSchema(db);
238
240
  initAutoLoopSchema(db);
241
+ initAutoLoopFinalReviewSchema(db);
239
242
  initSessionHandoffSchema(db);
240
243
  initMemorySchema(db);
244
+ initWorkspaceMessageBatchSchema(db);
241
245
  }
@@ -0,0 +1,9 @@
1
+ /** Receipts survive workspace deletion; message bodies live only in normal chat/loop storage. */
2
+ export function initWorkspaceMessageBatchSchema(db) {
3
+ db.exec(`CREATE TABLE IF NOT EXISTS workspace_message_batches (
4
+ id TEXT PRIMARY KEY,
5
+ fingerprint TEXT NOT NULL,
6
+ receipt_json TEXT NOT NULL,
7
+ created_at TEXT NOT NULL
8
+ )`);
9
+ }