@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.
- package/AGENTS.md +38 -6
- package/CHANGELOG.md +12 -0
- package/CONFIGURATION.md +41 -5
- package/README.md +4 -2
- package/dist/mcp-server/kobo-tasks-server.js +62 -105
- package/dist/mcp-server/workspace-dialogue-client.js +15 -3
- package/dist/server/db/auto-loop-final-review-schema.js +18 -0
- package/dist/server/db/migrations.js +12 -1
- package/dist/server/db/review-return-schema.js +18 -1
- package/dist/server/db/schema.js +4 -0
- package/dist/server/db/workspace-message-batch-schema.js +9 -0
- package/dist/server/index.js +8 -1
- package/dist/server/routes/mcp.js +69 -3
- package/dist/server/routes/workspace-messages.js +22 -0
- package/dist/server/routes/workspaces.js +119 -4
- package/dist/server/services/agent/engines/claude-code/engine.js +37 -1
- package/dist/server/services/agent/orchestrator.js +159 -40
- package/dist/server/services/agent/session-controller.js +4 -1
- package/dist/server/services/auto-loop-final-review-service.js +329 -0
- package/dist/server/services/auto-loop-service.js +53 -10
- package/dist/server/services/pr-checkout-rollback-service.js +51 -0
- package/dist/server/services/pr-checkout-service.js +21 -1
- package/dist/server/services/review-launch-runtime.js +17 -0
- package/dist/server/services/review-return-service.js +58 -10
- package/dist/server/services/review-service.js +59 -11
- package/dist/server/services/workspace-creation-mcp-service.js +198 -0
- package/dist/server/services/workspace-group-message-mcp-service.js +58 -0
- package/dist/server/services/workspace-group-message-service.js +182 -0
- package/dist/server/services/workspace-lifecycle-mcp-service.js +72 -0
- package/dist/server/services/workspace-message-service.js +5 -0
- package/dist/server/services/workspace-service.js +11 -0
- package/dist/server/services/workspace-template-service.js +10 -0
- package/dist/shared/auto-loop-review.js +1 -0
- package/dist/shared/pr-checkout-steps.js +86 -0
- package/dist/shared/review-return.js +1 -0
- package/dist/shared/workspace-creation-tools.js +182 -0
- package/dist/shared/workspace-group-message-tools.js +134 -0
- package/dist/shared/workspace-group-messages.js +27 -0
- package/dist/shared/workspace-lifecycle-tools.js +112 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-tJmJhmtl.js +2 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Bbrq02Xt.js +1 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +1 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-DD90Nl6c.js → ChangelogPage-BJfkAVYp.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-D_BQFWiT.js → ComparisonPanel-BfP8APKY.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-BbtNfo0x.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +1 -0
- package/src/client/dist/pwa/assets/{DashboardPage-DyvTdmBg.js → DashboardPage-DZYsAh5y.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-4Q-GJgyA.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-bm639sIu.js → DocumentsPanel-BfPlfiwU.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-KfwSzjeY.js → DrawerToggleButton-DG1Qjqk1.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-ECnbb_rx.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-37IHOIW-.js → HealthPage-_VXG1Zeo.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-BH6grTuW.js +2 -0
- package/src/client/dist/pwa/assets/MainLayout-DnmUxNk7.css +1 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-CucZMhjy.js → MemoryPanel-CVlvGZXZ.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-CUQybzVR.js → MemoryProposalList-SJsnzUAM.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-x6qPyl_7.js → QBtnDropdown-DBgGsN-D.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-D33aoRoG.js → QExpansionItem-DKgaMJ_l.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-Di-D6t2u.js → QScrollArea-D3OCsq3g.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-Cb_s_nox.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-BhwG7lp2.js → QTooltip-X5ps8Opw.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-qFi9Wxzh.js → SchedulePanel-Ww_FVlW5.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-DA9nRmsA.js → SearchPage-B87WEHf3.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BAZLGUyg.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Brw2XsYJ.js → SplitWorkspacePage-B99AJUbG.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-DVP18-nk.js → TerminalPanel-DnA1MXwu.js} +9 -9
- package/src/client/dist/pwa/assets/{ToolCallItem-cSHh3p_F.js → ToolCallItem-2vFggaur.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-C6LZZ3rw.js → TouchPan-BSU3z0aO.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Czlqo18H.js → TourReplayButton-DQwS2xdD.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BDYGq0pC.js → WorkspaceAttentionLabels-CM1tGDje.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-B248nKIU.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CQN6EKIn.js +4 -0
- package/src/client/dist/pwa/assets/{browser-ifCwUGpE.js → browser-DPsupI9Q.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-BBkGq3lN.js → build-path-tree-CbIdt-p9.js} +1 -1
- package/src/client/dist/pwa/assets/custom-sounds-DR68eyF8.js +1 -0
- package/src/client/dist/pwa/assets/de-D3Ycvf_F.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-CV_seGJR.js → editor.api-DvgAtnOj.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-ttw0bL63.js → efforts-nmxfTVQD.js} +1 -1
- package/src/client/dist/pwa/assets/es-TgeS5898.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-C8biMRXo.js → expand-template-Brn5O4a1.js} +1 -1
- package/src/client/dist/pwa/assets/{format-Cu95dbxt.js → format-DfJ3Fv1i.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-C25D1RSd.js → formatters-B30bNFhq.js} +1 -1
- package/src/client/dist/pwa/assets/fr-CJafryNb.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-DJEFr74q.js → freemarker2-DnZe74tc.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-C_lzsMjX.js → handlebars-CmuMfxzO.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CWmLFAmY.js → html-BD7OJFGX.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BY4JeBjk.js +1 -0
- package/src/client/dist/pwa/assets/index-C5fAw9ll.js +20 -0
- package/src/client/dist/pwa/assets/it-C5mN8Y61.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-tsuiEUe-.js → javascript-Y-OrgcmW.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-BBr9BvUk.js → liquid-BqOkKePH.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-gjBH1cXG.js → mdx-D-58x2h0.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-CvGcBIsb.js → monaco-contributions-BW4ZMKO6.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-Uw-Lfb8Z.js → monaco.contribution-DJWqzNG-.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BGPRhUa6.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-PMSxqP6t.js → notify-theme-DZDzfxph.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-BDQa2TgU.js → project-color-gB5yeZPN.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DlIgD1n6.js → python-BkBj1wDC.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-AraFPlpM.js → razor-DOOJ_0b8.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-CB8UpXHM.js → typescript-Bheo43G5.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CSREHQM4.js → use-search-index-status-CBpnyV-R.js} +1 -1
- package/src/client/dist/pwa/assets/use-tours-BHNasCvg.js +2 -0
- package/src/client/dist/pwa/assets/{use-worktree-restore-BM3Giig1.js → use-worktree-restore-CgABsWno.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-BPMFvTnQ.js → workspace-templates-BnFkigYg.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-uRjL1P43.js → xml-vHt2BD26.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-C9CFR4qV.js → yaml-BvhLPzP9.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +156 -12
- package/src/mcp-server/kobo-tasks-server.ts +73 -106
- package/src/mcp-server/workspace-dialogue-client.ts +18 -3
- package/src/client/dist/pwa/assets/ActivityFeed-BTRzc60o.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-BZAoCukb.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-CdCs8PRS.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-D0K2PlZD.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-C-Ejlc9_.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-BQOOup9o.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-DXQ1ouYl.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-MdPXvwm4.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-DcV6apqE.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-BwUkijpS.js +0 -4
- package/src/client/dist/pwa/assets/WorkspacePage-Bwfv559o.css +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-CeSgjIY7.js +0 -1
- package/src/client/dist/pwa/assets/de-CHY2BAfL.js +0 -17
- package/src/client/dist/pwa/assets/es-DbcSKp38.js +0 -17
- package/src/client/dist/pwa/assets/fr-CyyJ8S2c.js +0 -17
- package/src/client/dist/pwa/assets/i18n-CobVQiTM.js +0 -1
- package/src/client/dist/pwa/assets/index-ImUH7Bbt.js +0 -20
- package/src/client/dist/pwa/assets/it-XQJW2Xkr.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-D0Vm6a09.js +0 -1
- 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` |
|
|
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
|
-
|
|
371
|
-
The
|
|
372
|
-
|
|
373
|
-
return. Startup
|
|
374
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
original
|
|
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
|
|
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).
|
|
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
|

|
|
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
|
-
:
|
|
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 (
|
|
713
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/server/db/schema.js
CHANGED
|
@@ -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
|
+
}
|