@loicngr/kobo 1.11.43 → 1.11.45
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 +74 -1
- package/CHANGELOG.md +11 -0
- package/README.md +13 -1
- package/dist/mcp-server/kobo-tasks-handlers.js +33 -75
- package/dist/mcp-server/kobo-tasks-server.js +82 -9
- package/dist/server/db/auto-loop-schema.js +44 -0
- package/dist/server/db/migrations.js +18 -0
- package/dist/server/db/review-return-schema.js +10 -0
- package/dist/server/db/schema.js +8 -1
- package/dist/server/db/session-handoff-schema.js +24 -0
- package/dist/server/index.js +12 -9
- package/dist/server/routes/auto-loop-messages.js +51 -0
- package/dist/server/routes/documents.js +1 -1
- package/dist/server/routes/workspaces.js +119 -145
- package/dist/server/services/agent/engines/claude-code/engine.js +16 -0
- package/dist/server/services/agent/engines/codex/engine.js +92 -31
- package/dist/server/services/agent/orchestrator.js +298 -96
- package/dist/server/services/agent/session-controller.js +42 -2
- package/dist/server/services/auto-loop-message-service.js +79 -0
- package/dist/server/services/auto-loop-service.js +225 -86
- package/dist/server/services/auto-loop-state-service.js +30 -0
- package/dist/server/services/cron-service.js +30 -16
- package/dist/server/services/engine-handoff-service.js +5 -5
- package/dist/server/services/quota-backoff-service.js +42 -10
- package/dist/server/services/review-return-service.js +64 -0
- package/dist/server/services/review-service.js +187 -0
- package/dist/server/services/session-activity-service.js +17 -0
- package/dist/server/services/session-handoff-runtime.js +26 -0
- package/dist/server/services/session-handoff-service.js +479 -0
- package/dist/server/services/settings-service.js +3 -3
- package/dist/server/services/task-mutations.js +186 -0
- package/dist/server/services/wakeup-service.js +24 -1
- package/dist/server/services/workspace-message-service.js +16 -2
- package/dist/server/services/workspace-service.js +28 -36
- package/dist/server/utils/workspace-lifecycle-guard.js +35 -20
- package/dist/shared/auto-loop-prompts.js +22 -11
- package/dist/shared/auto-loop-types.js +1 -0
- package/dist/shared/document-roots.js +2 -0
- package/dist/shared/review.js +6 -0
- package/dist/shared/session-handoff.js +4 -0
- package/dist/shared/task-verification.js +11 -0
- package/package.json +12 -12
- package/src/client/dist/pwa/assets/ActivityFeed-CLXjdqS8.js +8 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-DX1WGFea.js → ChangelogPage-CnXTnFhe.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-BqW5dy-O.js → ClosePopup-CWjIRHxu.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-B4ne55Xj.js → ComparisonPanel-CKylN45q.js} +2 -2
- package/src/client/dist/pwa/assets/CreatePage-SoCcvxuD.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-Cv6HJEcO.js → DashboardPage-CZQLfoV1.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-COwGxPt6.js → DiffViewer-Csf3WmLc.js} +4 -4
- package/src/client/dist/pwa/assets/{DocumentsPanel-DCXpLLb7.js → DocumentsPanel-BeCttB-x.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-DRL3RCkM.js → DrawerToggleButton-BJBBYeOc.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-B-ZHgghQ.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-XrOuMoDJ.js → HealthPage-l88TEk-k.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-BAAQLq95.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-Cf4Q9x1u.js +2 -0
- package/src/client/dist/pwa/assets/{QBadge-DbkxGtht.js → QBadge-FWC1zDCq.js} +1 -1
- package/src/client/dist/pwa/assets/{QBanner-DhqH95GW.js → QBanner-CcRI_QIy.js} +1 -1
- package/src/client/dist/pwa/assets/QBtn-DEdx_Y1V.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-163oM6T_.js → QBtnDropdown-44Mj-y_1.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnGroup-DELKYo3u.js → QBtnGroup-CAoD6G0Q.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnToggle-BI3k3Ch2.js → QBtnToggle-C_hQ2UCQ.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-owciRuZu.js → QChip-DCPUhqXX.js} +1 -1
- package/src/client/dist/pwa/assets/QDrawer-B4qHw8wt.js +1 -0
- package/src/client/dist/pwa/assets/QExpansionItem-Bok1m8Wi.js +1 -0
- package/src/client/dist/pwa/assets/{QItemSection-D_5_0UrX.js → QItemSection-BhqphaH6.js} +1 -1
- package/src/client/dist/pwa/assets/{QList-qupzuEqO.js → QList-CHm4JNP0.js} +1 -1
- package/src/client/dist/pwa/assets/{QMarkupTable-B9QuKtK_.js → QMarkupTable-CNpiA9oq.js} +1 -1
- package/src/client/dist/pwa/assets/{QPage-DaX9sMQC.js → QPage-BXBw2Alx.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-BbtD5Iks.js +1 -0
- package/src/client/dist/pwa/assets/{QSpinnerDots-DnCV3VzI.js → QSpinnerDots-BkFiCsH3.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-BQWY7aeM.js → QTooltip-BDihn2aY.js} +1 -1
- package/src/client/dist/pwa/assets/SchedulePanel-DiWCDNZ8.js +1 -0
- package/src/client/dist/pwa/assets/{SearchPage-DOHkP5lj.js → SearchPage-BwZ2TX1G.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BUh0WfNf.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-DL4kp9Ah.js → SplitWorkspacePage-Bg6nKBcA.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-DHDnyiYh.js → TerminalPanel-Cq-FR7A7.js} +9 -9
- package/src/client/dist/pwa/assets/{TouchPan-BnHXDR6F.js → TouchPan-BQ1cvzRC.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-ClVcB6mO.js → TourReplayButton-Dz1yEz_z.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-Bxe6oIJI.js → WorkspaceAttentionLabels-BCVOP8vp.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BbUvR1Vf.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-Cl8gap4b.js +4 -0
- package/src/client/dist/pwa/assets/{browser-Bkx4fbxl.js → browser-BmWgBx9M.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-6EjBmYYY.js +1 -0
- package/src/client/dist/pwa/assets/de-COObokpu.js +17 -0
- package/src/client/dist/pwa/assets/documents-BZ8bwcjr.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-DreEG0If.js → editor.api-CK7--ckY.js} +1 -1
- package/src/client/dist/pwa/assets/editor.worker-iO5iN0DB.js +33 -0
- package/src/client/dist/pwa/assets/efforts-Bw0o2Bud.js +9 -0
- package/src/client/dist/pwa/assets/engineFeatures-B-9jHocY.css +1 -0
- package/src/client/dist/pwa/assets/engineFeatures-DQO9HHXI.js +1 -0
- package/src/client/dist/pwa/assets/es-By2Q9Tca.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-C8VyjASo.js +1 -0
- package/src/client/dist/pwa/assets/{format-B7z3F-Mw.js → format-LtTLLitJ.js} +3 -3
- package/src/client/dist/pwa/assets/{formatters-BTBiU0v3.js → formatters-Bl9zA7Ai.js} +1 -1
- package/src/client/dist/pwa/assets/fr-D-sd0ddn.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-BZNTVQ3K.js → freemarker2-DxezMaje.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-k8tN0dI3.js → handlebars-DK_Bj6Rx.js} +1 -1
- package/src/client/dist/pwa/assets/{html-iZL3bbXq.js → html-DFfsfStD.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-aDWy4KHt.js +1 -0
- package/src/client/dist/pwa/assets/index-0wtBh1Xt.css +1 -0
- package/src/client/dist/pwa/assets/index-BLd8DAzg.js +20 -0
- package/src/client/dist/pwa/assets/it-CeK5lyIA.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-TV6gg0MO.js → javascript-DuNbcaL7.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-Cifa5ujF.js → liquid-CM3JKyhv.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-Bnj_8yGE.js → mdx-BG3zYwNF.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-pEh0n9Wk.js → monaco-contributions-DLscZGH-.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-DnhZHwXT.js +2 -0
- package/src/client/dist/pwa/assets/{network-auth-3_5p4fdc.js → network-auth-BJiPbO5l.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-D_nf9AeF.js → notify-theme-D4p3qPkp.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-CUnxC1xw.js → pending-diff-open-dYIFdS4r.js} +1 -1
- package/src/client/dist/pwa/assets/permissionModes-B7fuUegU.js +36 -0
- package/src/client/dist/pwa/assets/{pinia-BKqmoVru.js → pinia-CTJ3OfP3.js} +1 -1
- package/src/client/dist/pwa/assets/{private.use-form-C7umbGO-.js → private.use-form-Bk8ti264.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CihwpDAA.js → project-color-R_1je4vW.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DOoNBSNL.js → python-COs6pHk2.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-Bvq5vVpJ.js → razor-Jl1VqMsk.js} +1 -1
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-Bo9anJjg.js +1 -0
- package/src/client/dist/pwa/assets/render-chat-markdown-AaOo0Tgq.js +67 -0
- package/src/client/dist/pwa/assets/{runtime-core.esm-bundler-CDnVUfeZ.js → runtime-core.esm-bundler-CNFKm4ij.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-VVzxoeyM.js → typescript-BU_TUuAM.js} +1 -1
- package/src/client/dist/pwa/assets/{use-checkbox-FnauAgTp.js → use-checkbox-CnvHBhSK.js} +1 -1
- package/src/client/dist/pwa/assets/{use-id-BIuaWKkB.js → use-id-wXd4c8FA.js} +1 -1
- package/src/client/dist/pwa/assets/use-is-mobile-DpKI3XMy.js +1 -0
- package/src/client/dist/pwa/assets/{use-search-index-status-DtLzygcy.js → use-search-index-status-Bluf9L99.js} +1 -1
- package/src/client/dist/pwa/assets/use-slide-transition-CiaDm-tR.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-CVn874kD.js +2 -0
- package/src/client/dist/pwa/assets/{workspace-templates-1ui80Kqo.js → workspace-templates-BCKR8TXr.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-CzGFIxQa.js → xml-gFFsobxE.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-Bl7nDJeC.js → yaml-DaXaqaGD.js} +1 -1
- package/src/client/dist/pwa/index.html +10 -10
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +8 -1
- package/src/mcp-server/kobo-tasks-handlers.ts +72 -105
- package/src/mcp-server/kobo-tasks-server.ts +94 -9
- package/src/client/dist/pwa/assets/ActivityFeed-D1e-zV3O.js +0 -8
- package/src/client/dist/pwa/assets/CreatePage-Bx_GpUni.js +0 -2
- package/src/client/dist/pwa/assets/GitPanel-C9mz4GXF.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-BwV89R-l.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CwWavQjV.js +0 -2
- package/src/client/dist/pwa/assets/QBtn-CZaBclNw.js +0 -1
- package/src/client/dist/pwa/assets/QDrawer-DNEj8wLZ.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-BUlOQe5k.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-Bos94OyR.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-DtGsd0XH.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-Clc4Fl3B.js +0 -4
- package/src/client/dist/pwa/assets/WorkspacePage-DCg9zSxz.css +0 -1
- package/src/client/dist/pwa/assets/build-path-tree-BpSY7N5O.js +0 -1
- package/src/client/dist/pwa/assets/de-BU0dILBs.js +0 -17
- package/src/client/dist/pwa/assets/documents-BIlWh3o-.js +0 -1
- package/src/client/dist/pwa/assets/editor.worker-CwQAd_xr.js +0 -30
- package/src/client/dist/pwa/assets/efforts-D_yTzymv.js +0 -9
- package/src/client/dist/pwa/assets/engineFeatures-C33LTVkK.js +0 -1
- package/src/client/dist/pwa/assets/es-CNp0s9na.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-wRhAXT-1.js +0 -1
- package/src/client/dist/pwa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- package/src/client/dist/pwa/assets/fr-RqJyWSdm.js +0 -17
- package/src/client/dist/pwa/assets/i18n--PCet00a.js +0 -1
- package/src/client/dist/pwa/assets/index-CPfrY38K.js +0 -20
- package/src/client/dist/pwa/assets/index-CUoUy6tH.css +0 -1
- package/src/client/dist/pwa/assets/it-DSxNuCwL.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-D7NmJUJH.js +0 -2
- package/src/client/dist/pwa/assets/permissionModes-DC51Pz84.js +0 -36
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-DEJj9cFv.js +0 -1
- package/src/client/dist/pwa/assets/render-chat-markdown-BQ_saiy5.js +0 -67
- package/src/client/dist/pwa/assets/use-is-mobile-CWjqe8gC.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-BKeYrJaQ.js +0 -2
package/AGENTS.md
CHANGED
|
@@ -116,6 +116,7 @@ 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
120
|
| `pending_wakeups` | one-row-per-workspace scheduler for the `schedule_wakeup` MCP tool: target_at (ISO UTC), prompt, reason; CASCADE DELETE on workspace |
|
|
120
121
|
| `pending_quota_backoffs` | persisted quota retries and their attempt counts |
|
|
121
122
|
| `pending_crons` | recurring workspace schedules and their next run |
|
|
@@ -135,7 +136,11 @@ src/
|
|
|
135
136
|
|
|
136
137
|
`worktree_purged_at` + `worktree_purge_restore_data` drive the disk-space purge feature (see [Worktree purge](#worktree-purge) below): when set, the workspace's worktree folder has been removed from disk but the chat history is preserved. `worktree_purge_restore_data` is a JSON blob (`{ prNumber, prUrl, forge, mergeCommitSha, originalWorktreePath, originalSourceBranch, originalWorkingBranch }`) captured at purge time for future "Restore" UX. Both fields are cleared automatically by the pr-watcher when the worktree folder reappears on disk.
|
|
137
138
|
|
|
138
|
-
`auto_loop`
|
|
139
|
+
`auto_loop` preserves the user's execution intent. `auto_loop_runs` (migration v45) stores phase (`grooming | execution | finalization`), operational state (`active | waiting | blocked | completed | stopped`), iteration count and diagnostic recovery. `auto_loop_progress` records monotone task milestones; metadata changes are not progress. After three stagnant iterations, run one diagnostic and two further attempts, then block explicitly without clearing intent. Quotas wait for their actual reset, including multi-day windows; only transient failures use the retry cap. Archived/deleted workspaces disable the loop.
|
|
140
|
+
|
|
141
|
+
Tasks have `role` (`work | finalization`) and structured `verification`. MCP and HTTP share transactional task mutations. An auto-loop task cannot become done without passing reported checks. Finalization runs after all other tasks/criteria, is invalidated by scope changes, and is mandatory before completion. The proof is agent-reported evidence, not server-side execution of arbitrary commands.
|
|
142
|
+
|
|
143
|
+
`auto_loop_messages` durably queues next-iteration instructions, independently of engine session ids. A dispatch interrupted before confirmed completion becomes `unknown`, requiring explicit acknowledgement or retry. Unknown or in-flight instructions prevent completion. Actual engine closure (`EngineProcess.closed`), not a logical end event alone, releases controller ownership and permits the next writer.
|
|
139
144
|
|
|
140
145
|
`brainstorm_model` (nullable, set only at creation) lets a workspace created with `auto_loop=1` run its initial brainstorming session on a different model than the auto-loop iterations that follow, while staying on the same engine. `POST /api/workspaces` uses it for the very first `startAgent` call only; every subsequent auto-loop iteration (`auto-loop-service.ts`'s `spawnNextIteration`) always reads `model`, unchanged. The switch to `model` takes effect once that first live session ends naturally — not at the `[BRAINSTORM_COMPLETE]` marker, since the same session keeps running under `brainstorm_model` until it stops on its own.
|
|
141
146
|
|
|
@@ -259,6 +264,74 @@ Background: the engine was migrated from `@openai/codex-sdk` (one-shot `codex ex
|
|
|
259
264
|
|
|
260
265
|
## Workspace operations
|
|
261
266
|
|
|
267
|
+
### Fresh sessions and LLM handoffs
|
|
268
|
+
|
|
269
|
+
`session-handoff-service.ts` owns manual transfers within a workspace, including
|
|
270
|
+
same-engine model changes. `session_handoffs` (migration v46) preserves the source,
|
|
271
|
+
target, request idempotency, report and operation state. Lifecycle reservations
|
|
272
|
+
permit only launches carrying their owner token; ordinary chat, reviews and
|
|
273
|
+
automatic writers remain excluded until completion or explicit cancellation.
|
|
274
|
+
|
|
275
|
+
Stop the source immediately and confirm closure before resuming its exact native
|
|
276
|
+
conversation for the optional report-only turn. The final destination always uses
|
|
277
|
+
a fresh conversation. Generation uses a scoped `submit_session_handoff` MCP tool;
|
|
278
|
+
the backend persists the report, publishes `.ai/handoffs/` documents and owns the
|
|
279
|
+
transition. Never treat this generation as auto-loop progress or settle uncertain
|
|
280
|
+
instructions through it. Pending instruction contents stay with the loop dispatcher.
|
|
281
|
+
|
|
282
|
+
`EngineProcess.ready`, when provided, confirms initial native session/turn
|
|
283
|
+
acceptance. A returned process handle alone is insufficient evidence that a
|
|
284
|
+
transfer started successfully. Startup reconciliation retains interrupted transfers
|
|
285
|
+
for an explicit retry/skip/cancel decision and never replays them automatically.
|
|
286
|
+
Stop cancels pending transfers, including while no engine is running. Preserve
|
|
287
|
+
source selection by last use after a review return; loading a historical completed
|
|
288
|
+
transfer must not change the user's currently selected conversation.
|
|
289
|
+
Failed, interrupted and cancelled transfers restore the source conversation as well
|
|
290
|
+
as its configuration once the engine is stopped. `agent_sessions.activation_order`
|
|
291
|
+
(migration v47) records starts, resumes and source restoration independently of real
|
|
292
|
+
start/end timestamps. Backend current-session and implicit-resume selection, and the
|
|
293
|
+
client's current-session helper, use this order with the legacy timestamp fallback.
|
|
294
|
+
Keep failed target sessions in history and do not force a historical handoff's
|
|
295
|
+
selection over a later conversation after reload.
|
|
296
|
+
Rolled-back targets have `activation_order = -1`: exclude them from default
|
|
297
|
+
selection and implicit resume, but retain their native id and execution history.
|
|
298
|
+
An explicit start/resume activates them again. Without a source conversation,
|
|
299
|
+
rollback clears the client selection, including stale persisted preferences,
|
|
300
|
+
so the next ordinary message starts a fresh conversation on the restored engine.
|
|
301
|
+
Pending wakeups pinned to the source move to the destination only after confirmed
|
|
302
|
+
startup, in the same transaction as handoff completion. Preserve their prompt and
|
|
303
|
+
deadline, and never rebind a wakeup belonging to another conversation.
|
|
304
|
+
|
|
305
|
+
### Review LLM and return to the original session
|
|
306
|
+
|
|
307
|
+
The current conversation is the running session, or the last activated session,
|
|
308
|
+
falling back to `max(startedAt, endedAt)` for legacy rows. Keep the session list in creation order,
|
|
309
|
+
but do not use its first entry to identify the current conversation: a temporary
|
|
310
|
+
review can return to an older session. The stale-session banner and the next
|
|
311
|
+
review's return target must recognize that original session, including on reload.
|
|
312
|
+
|
|
313
|
+
`StartReviewDialog.vue` uses the shared model/effort/permission catalogues. Changed
|
|
314
|
+
settings force a new session on both client and server. `review-service.ts`
|
|
315
|
+
validates before side effects, waits for confirmed shutdown, then applies the
|
|
316
|
+
selected configuration and launches the review. Without a return request, these
|
|
317
|
+
settings remain the workspace defaults. Legacy requests retain their behavior.
|
|
318
|
+
|
|
319
|
+
The optional `returnToSession` flag captures the exact resumable original session.
|
|
320
|
+
`pending_review_returns` persists its configuration and the review session id
|
|
321
|
+
before switching engines. The orchestrator intercepts a successful review end
|
|
322
|
+
(including watchdog closure without a nonzero exit), restores configuration and
|
|
323
|
+
resumes that exact native conversation with the reviewer's final text report.
|
|
324
|
+
Resume selection is scoped to the target engine; never pass a reviewer's native
|
|
325
|
+
conversation id to the original engine, even after cancellation or restart.
|
|
326
|
+
The report reassembles text deltas/final snapshots, is bounded to 24,000 characters,
|
|
327
|
+
and links back to the complete review session through the conversation tool.
|
|
328
|
+
The receiving agent is asked to summarize and await instructions before fixes.
|
|
329
|
+
The intent is consumed before delivery, preventing repeated ends from sending twice.
|
|
330
|
+
Errors restore settings without automatically resuming; explicit stop cancels the
|
|
331
|
+
return. Startup restores interrupted-review settings without replaying a possibly
|
|
332
|
+
already-delivered handoff. Preserve these guards and the auto-loop interception.
|
|
333
|
+
`workspace:configuration` is ephemeral and updates all four LLM settings across tabs.
|
|
334
|
+
|
|
262
335
|
### Delete confirmation
|
|
263
336
|
|
|
264
337
|
The workspace deletion dialog requires the exact `workingBranch`, not the editable
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ 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
|
+
## 1.11.45
|
|
8
|
+
|
|
9
|
+
- fix(sessions): harden handoff recovery and highlight transfer progress
|
|
10
|
+
- feat(sessions): add fresh sessions with optional handoffs
|
|
11
|
+
|
|
12
|
+
## 1.11.44
|
|
13
|
+
|
|
14
|
+
- chore(deps): update packages and node to 24.21.0
|
|
15
|
+
- fix(auto-loop): preserve mission intent until verified completion
|
|
16
|
+
- feat(review): configure review LLM and return to original session
|
|
17
|
+
|
|
7
18
|
## 1.11.43
|
|
8
19
|
|
|
9
20
|
- fix(workspaces): refine watchdog stops and deletion confirmation
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Kōbō (工房, "workshop") turns *one agent, one terminal* into a real workflow
|
|
|
17
17
|
|
|
18
18
|
| | |
|
|
19
19
|
|---|---|
|
|
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
|
|
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
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. |
|
|
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. |
|
|
@@ -62,6 +62,18 @@ Kōbō's production build is an installable PWA — use your browser's **Install
|
|
|
62
62
|
- **Observability**: a per-session timeline (duration, tools, tokens, errors) and a downloadable redacted diagnostic JSON.
|
|
63
63
|
- **Optional integrations**: Notion (import missions) and Sentry (fix from issue URL), each independently toggled with a **Test connection** action; local voice transcription via `whisper.cpp`.
|
|
64
64
|
|
|
65
|
+
## Fresh sessions and LLM handoffs
|
|
66
|
+
|
|
67
|
+
Long conversation getting crowded? Use **Continue in a fresh session** at the bottom of the right-hand Tools panel. **Change LLM** uses the same transfer flow and also lets you select another model within the current engine.
|
|
68
|
+
|
|
69
|
+
1. Choose the destination model, reasoning effort and permission mode. A fresh session starts with the workspace's current configuration selected.
|
|
70
|
+
2. Leave **Generate a handoff with the current agent** enabled to have the current conversation summarize the objective, constraints, decisions, verified progress, failed approaches and next action. Disable it when the old provider has no quota left: Kōbō builds context from the mission, tasks, Git state and source history without calling that LLM.
|
|
71
|
+
3. Start the transfer. Kōbō interrupts the current work immediately, waits for confirmed shutdown, prepares the handoff and opens a fresh native conversation that continues the mission automatically.
|
|
72
|
+
|
|
73
|
+
The workspace, worktree, task list and conversation history remain available. The transfer links to its source session and a Markdown report in **Documents** (`.ai/handoffs/`, ignored by Git). Auto-loop keeps its intent and diagnostic budget; queued instructions retain their delivery state. Pending wakeups from the source conversation follow the fresh session once its startup is confirmed.
|
|
74
|
+
|
|
75
|
+
If generation fails, choose **Retry**, **Continue without an LLM summary**, or **Cancel transfer**. Cancel and Stop leave the work stopped and restore the source conversation and LLM configuration, including when the destination failed to start. Failed attempts remain in history without being resumed automatically. If there was no source conversation, the next message starts a fresh one. A server restart preserves the transfer and report for an explicit recovery decision; it never automatically replays a potentially delivered prompt. Transfers are manual—conversation size does not trigger them automatically.
|
|
76
|
+
|
|
65
77
|
## Configuration
|
|
66
78
|
|
|
67
79
|
The most common knobs:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { DOCUMENT_DIRS } from '../shared/document-roots.js';
|
|
2
|
+
export { DOCUMENT_DIRS } from '../shared/document-roots.js';
|
|
1
3
|
import fs from 'node:fs';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { nanoid } from 'nanoid';
|
|
4
6
|
import * as cronService from '../server/services/cron-service.js';
|
|
5
7
|
import * as settingsService from '../server/services/settings-service.js';
|
|
8
|
+
import { createTaskRecord, deleteTaskRecord, updateTaskRecord, } from '../server/services/task-mutations.js';
|
|
6
9
|
import { slugifyProjectName } from '../server/utils/project-slug.js';
|
|
7
10
|
import { ensureDirectoryInside, resolveExistingPathInside } from '../server/utils/safe-path.js';
|
|
8
11
|
import { resolveWorkspaceWorktreePath } from '../server/utils/worktree-paths.js';
|
|
9
12
|
import { MASKED_SECRET, SECRET_GLOBAL_KEYS } from '../shared/consts.js';
|
|
13
|
+
import { parseTaskVerification } from '../shared/task-verification.js';
|
|
10
14
|
/** Allowed task status values. */
|
|
11
15
|
export const VALID_TASK_STATUSES = ['pending', 'in_progress', 'done'];
|
|
12
16
|
function rowToDto(row) {
|
|
@@ -15,12 +19,15 @@ function rowToDto(row) {
|
|
|
15
19
|
title: row.title,
|
|
16
20
|
status: row.status,
|
|
17
21
|
is_acceptance_criterion: row.is_acceptance_criterion === 1,
|
|
22
|
+
sort_order: row.sort_order,
|
|
23
|
+
role: row.role,
|
|
24
|
+
verification: parseTaskVerification(row.verification),
|
|
18
25
|
};
|
|
19
26
|
}
|
|
20
27
|
/** Return all tasks for a workspace, ordered by sort_order. */
|
|
21
28
|
export function listTasksHandler(db, workspaceId) {
|
|
22
29
|
const rows = db
|
|
23
|
-
.prepare('SELECT
|
|
30
|
+
.prepare('SELECT * FROM tasks WHERE workspace_id = ? ORDER BY sort_order ASC, rowid ASC')
|
|
24
31
|
.all(workspaceId);
|
|
25
32
|
return rows.map(rowToDto);
|
|
26
33
|
}
|
|
@@ -81,86 +88,38 @@ export function setWorkspaceAgentDescriptionHandler(db, workspaceId, args) {
|
|
|
81
88
|
}
|
|
82
89
|
return { ok: true, description: stored };
|
|
83
90
|
}
|
|
84
|
-
/** Set a task's status to
|
|
85
|
-
export function markTaskDoneHandler(db, workspaceId, taskId) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (result.changes === 0) {
|
|
91
|
-
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`);
|
|
92
|
-
}
|
|
93
|
-
const row = db
|
|
94
|
-
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?')
|
|
95
|
-
.get(taskId);
|
|
91
|
+
/** Set a task's status to done; auto-loop tasks require successful evidence. */
|
|
92
|
+
export function markTaskDoneHandler(db, workspaceId, taskId, verification) {
|
|
93
|
+
const row = updateTaskRecord(db, workspaceId, taskId, {
|
|
94
|
+
status: 'done',
|
|
95
|
+
...(verification !== undefined ? { verification } : {}),
|
|
96
|
+
});
|
|
96
97
|
return { success: true, task: rowToDto(row) };
|
|
97
98
|
}
|
|
98
|
-
/** Create a
|
|
99
|
+
/** Create a task at an explicit position or append it to the list. */
|
|
99
100
|
export function createTaskHandler(db, workspaceId, data) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
const id = nanoid();
|
|
109
|
-
const now = new Date().toISOString();
|
|
110
|
-
const isAC = data.is_acceptance_criterion ? 1 : 0;
|
|
111
|
-
// Append at the end: max(sort_order) + 1
|
|
112
|
-
const maxRow = db
|
|
113
|
-
.prepare('SELECT COALESCE(MAX(sort_order), -1) AS max FROM tasks WHERE workspace_id = ?')
|
|
114
|
-
.get(workspaceId);
|
|
115
|
-
const sortOrder = maxRow.max + 1;
|
|
116
|
-
db.prepare('INSERT INTO tasks (id, workspace_id, title, status, is_acceptance_criterion, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(id, workspaceId, data.title.trim(), 'pending', isAC, sortOrder, now, now);
|
|
117
|
-
const row = db.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?').get(id);
|
|
118
|
-
return rowToDto(row);
|
|
101
|
+
return rowToDto(createTaskRecord(db, workspaceId, {
|
|
102
|
+
title: data.title,
|
|
103
|
+
isAcceptanceCriterion: data.is_acceptance_criterion,
|
|
104
|
+
sortOrder: data.sort_order,
|
|
105
|
+
afterTaskId: data.after_task_id,
|
|
106
|
+
role: data.role,
|
|
107
|
+
}));
|
|
119
108
|
}
|
|
120
|
-
/** Update
|
|
109
|
+
/** Update task fields atomically, including insertion order and verification. */
|
|
121
110
|
export function updateTaskHandler(db, workspaceId, taskId, data) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!data.title.trim())
|
|
131
|
-
throw new Error('title cannot be empty');
|
|
132
|
-
sets.push('title = ?');
|
|
133
|
-
values.push(data.title.trim());
|
|
134
|
-
}
|
|
135
|
-
if (data.status !== undefined) {
|
|
136
|
-
if (!VALID_TASK_STATUSES.includes(data.status)) {
|
|
137
|
-
throw new Error(`Invalid status '${data.status}'. Must be one of: ${VALID_TASK_STATUSES.join(', ')}`);
|
|
138
|
-
}
|
|
139
|
-
sets.push('status = ?');
|
|
140
|
-
values.push(data.status);
|
|
141
|
-
}
|
|
142
|
-
if (data.is_acceptance_criterion !== undefined) {
|
|
143
|
-
sets.push('is_acceptance_criterion = ?');
|
|
144
|
-
values.push(data.is_acceptance_criterion ? 1 : 0);
|
|
145
|
-
}
|
|
146
|
-
if (sets.length === 0) {
|
|
147
|
-
throw new Error('No fields to update (provide title, status, or is_acceptance_criterion)');
|
|
148
|
-
}
|
|
149
|
-
sets.push('updated_at = ?');
|
|
150
|
-
values.push(new Date().toISOString());
|
|
151
|
-
values.push(taskId);
|
|
152
|
-
db.prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...values);
|
|
153
|
-
const row = db
|
|
154
|
-
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?')
|
|
155
|
-
.get(taskId);
|
|
156
|
-
return rowToDto(row);
|
|
111
|
+
return rowToDto(updateTaskRecord(db, workspaceId, taskId, {
|
|
112
|
+
title: data.title,
|
|
113
|
+
status: data.status,
|
|
114
|
+
isAcceptanceCriterion: data.is_acceptance_criterion,
|
|
115
|
+
sortOrder: data.sort_order,
|
|
116
|
+
afterTaskId: data.after_task_id,
|
|
117
|
+
verification: data.verification,
|
|
118
|
+
}));
|
|
157
119
|
}
|
|
158
|
-
/** Permanently delete a task from a workspace. */
|
|
120
|
+
/** Permanently delete a task from a workspace and invalidate its final review. */
|
|
159
121
|
export function deleteTaskHandler(db, workspaceId, taskId) {
|
|
160
|
-
|
|
161
|
-
if (result.changes === 0) {
|
|
162
|
-
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`);
|
|
163
|
-
}
|
|
122
|
+
deleteTaskRecord(db, workspaceId, taskId);
|
|
164
123
|
return { success: true, task_id: taskId };
|
|
165
124
|
}
|
|
166
125
|
/** Read the dev-server status for a workspace directly from the database. */
|
|
@@ -310,7 +269,6 @@ export function getTicketSourcesHandler(worktreePath) {
|
|
|
310
269
|
}
|
|
311
270
|
// ── Documents ────────────────────────────────────────────────────────────────
|
|
312
271
|
/** Directories (relative to the worktree root) scanned for AI-generated docs. */
|
|
313
|
-
export const DOCUMENT_DIRS = ['docs/plans', 'docs/superpowers', '.ai/thoughts'];
|
|
314
272
|
/** Depth cap to keep recursion bounded even on pathological symlink loops. */
|
|
315
273
|
const DOC_MAX_DEPTH = 8;
|
|
316
274
|
const DOC_MAX_FILE_BYTES = 1024 * 1024;
|
|
@@ -6,6 +6,8 @@ import { getDb } from '../server/db/index.js';
|
|
|
6
6
|
import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js';
|
|
7
7
|
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';
|
|
8
8
|
import { callWorkspaceDialogueTool } from './workspace-dialogue-client.js';
|
|
9
|
+
const handoffId = process.env.KOBO_HANDOFF_ID;
|
|
10
|
+
const handoffToken = process.env.KOBO_HANDOFF_TOKEN;
|
|
9
11
|
const workspaceId = process.env.KOBO_WORKSPACE_ID;
|
|
10
12
|
const dbPath = process.env.KOBO_DB_PATH;
|
|
11
13
|
const settingsPath = process.env.KOBO_SETTINGS_PATH;
|
|
@@ -113,20 +115,63 @@ async function backendRequest(method, pathname, body) {
|
|
|
113
115
|
return text ? JSON.parse(text) : null;
|
|
114
116
|
}
|
|
115
117
|
const server = new Server({ name: 'kobo-tasks', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
118
|
+
const TASK_VERIFICATION_SCHEMA = {
|
|
119
|
+
type: 'object',
|
|
120
|
+
description: 'Required to complete an auto-loop task. Describe checks actually performed; every check must be passed for done. Record failed/not_run checks only on an unfinished task.',
|
|
121
|
+
properties: {
|
|
122
|
+
method: { type: 'string', minLength: 1 },
|
|
123
|
+
summary: { type: 'string', minLength: 1 },
|
|
124
|
+
checks: {
|
|
125
|
+
type: 'array',
|
|
126
|
+
minItems: 1,
|
|
127
|
+
items: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
name: { type: 'string', minLength: 1 },
|
|
131
|
+
status: { type: 'string', enum: ['passed', 'failed', 'not_run'] },
|
|
132
|
+
},
|
|
133
|
+
required: ['name', 'status'],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ['method', 'summary', 'checks'],
|
|
138
|
+
};
|
|
139
|
+
const TASK_ORDER_PROPERTIES = {
|
|
140
|
+
sort_order: {
|
|
141
|
+
type: 'integer',
|
|
142
|
+
minimum: 0,
|
|
143
|
+
description: 'Insert/move at this order, shifting siblings. Omit to append on creation. Mutually exclusive with after_task_id.',
|
|
144
|
+
},
|
|
145
|
+
after_task_id: {
|
|
146
|
+
type: 'string',
|
|
147
|
+
description: 'Insert/move immediately after this task in the same workspace, preserving the remaining order.',
|
|
148
|
+
},
|
|
149
|
+
};
|
|
116
150
|
const WORKSPACE_SCOPED_TOOLS = [
|
|
151
|
+
{
|
|
152
|
+
name: 'submit_session_handoff',
|
|
153
|
+
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.',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: { report: { type: 'string', minLength: 1, maxLength: 24000 } },
|
|
157
|
+
required: ['report'],
|
|
158
|
+
},
|
|
159
|
+
annotations: { destructiveHint: false, openWorldHint: false },
|
|
160
|
+
},
|
|
117
161
|
{
|
|
118
162
|
name: 'list_tasks',
|
|
119
|
-
description: 'CALL FIRST on any non-trivial turn to know what the user wants done and what is already completed. Returns every task and acceptance criterion for the current workspace with its id and
|
|
163
|
+
description: 'CALL FIRST on any non-trivial turn to know what the user wants done and what is already completed. Returns every task and acceptance criterion for the current workspace with its id, status, sort_order, role, and verification evidence. Re-call periodically (before marking something done, or after the user asks for a status) to stay in sync with user-added or external updates.',
|
|
120
164
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
121
165
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
122
166
|
},
|
|
123
167
|
{
|
|
124
168
|
name: 'mark_task_done',
|
|
125
|
-
description: 'CALL AS SOON AS a task or acceptance criterion is finished AND verified
|
|
169
|
+
description: 'CALL AS SOON AS a task or acceptance criterion is finished AND verified by checks actually performed. Auto-loop requires structured verification evidence with all checks passed. Never mark done if required checks failed or were not run. Finalization also requires all work tasks and criteria done. Do not wait for the end of the turn — the user watches progress live and marking each item as it completes is the primary signal Kōbō uses to track you.',
|
|
126
170
|
inputSchema: {
|
|
127
171
|
type: 'object',
|
|
128
172
|
properties: {
|
|
129
173
|
task_id: { type: 'string', description: 'Task id from list_tasks.' },
|
|
174
|
+
verification: TASK_VERIFICATION_SCHEMA,
|
|
130
175
|
},
|
|
131
176
|
required: ['task_id'],
|
|
132
177
|
},
|
|
@@ -140,10 +185,16 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
140
185
|
},
|
|
141
186
|
{
|
|
142
187
|
name: 'create_task',
|
|
143
|
-
description: 'CALL WHEN you discover follow-up work that was not in the original list and needs to stick around (e.g. "refactor this helper later", "add a test for edge case"). Appends
|
|
188
|
+
description: 'CALL WHEN you discover follow-up work that was not in the original list and needs to stick around (e.g. "refactor this helper later", "add a test for edge case"). Appends by default; use after_task_id for a dependent check immediately after its parent. Use role finalization for the final verification gate. Do not use it for ephemeral internal notes — prefer log_thought for those.',
|
|
144
189
|
inputSchema: {
|
|
145
190
|
type: 'object',
|
|
146
191
|
properties: {
|
|
192
|
+
...TASK_ORDER_PROPERTIES,
|
|
193
|
+
role: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
enum: ['work', 'finalization'],
|
|
196
|
+
description: 'Default work. Finalization is the final verification gate and cannot finish while work/criteria remain open.',
|
|
197
|
+
},
|
|
147
198
|
title: { type: 'string', description: 'Short, imperative title (e.g. "Add retry to fetchUser").' },
|
|
148
199
|
is_acceptance_criterion: {
|
|
149
200
|
type: 'boolean',
|
|
@@ -161,6 +212,8 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
161
212
|
type: 'object',
|
|
162
213
|
properties: {
|
|
163
214
|
task_id: { type: 'string', description: 'Task id from list_tasks.' },
|
|
215
|
+
...TASK_ORDER_PROPERTIES,
|
|
216
|
+
verification: TASK_VERIFICATION_SCHEMA,
|
|
164
217
|
title: { type: 'string', description: 'New title (optional).' },
|
|
165
218
|
status: {
|
|
166
219
|
type: 'string',
|
|
@@ -371,13 +424,13 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
371
424
|
// ── Knowledge / context tools ─────────────────────────────────────────────
|
|
372
425
|
{
|
|
373
426
|
name: 'list_documents',
|
|
374
|
-
description: 'CALL EARLY on a new session to discover plans, specs, and thoughts previously written for this workspace. Recursively lists every .md under docs/plans/, docs/superpowers/, and .ai/
|
|
427
|
+
description: 'CALL EARLY on a new session to discover plans, specs, and thoughts previously written for this workspace. Recursively lists every .md under docs/plans/, docs/superpowers/, .ai/thoughts/, and .ai/handoffs/. Before writing a new plan, check if one already exists.',
|
|
375
428
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
376
429
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
377
430
|
},
|
|
378
431
|
{
|
|
379
432
|
name: 'read_document',
|
|
380
|
-
description: 'CALL AFTER list_documents when a file title looks relevant to the current task. Returns the full markdown content. Scoped to docs/plans/, docs/superpowers/, .ai/thoughts/ — reject anything else.',
|
|
433
|
+
description: 'CALL AFTER list_documents when a file title looks relevant to the current task. Returns the full markdown content. Scoped to docs/plans/, docs/superpowers/, .ai/thoughts/, .ai/handoffs/ — reject anything else.',
|
|
381
434
|
inputSchema: {
|
|
382
435
|
type: 'object',
|
|
383
436
|
properties: {
|
|
@@ -568,9 +621,13 @@ const GLOBAL_TOOLS = [
|
|
|
568
621
|
* one of the ~35 workspace-scoped branches.
|
|
569
622
|
*/
|
|
570
623
|
const GLOBAL_TOOL_NAMES = new Set(GLOBAL_TOOLS.map((t) => t.name));
|
|
571
|
-
|
|
572
|
-
tools
|
|
573
|
-
|
|
624
|
+
function availableTools() {
|
|
625
|
+
const tools = workspaceId ? [...WORKSPACE_SCOPED_TOOLS, ...GLOBAL_TOOLS] : GLOBAL_TOOLS;
|
|
626
|
+
return tools.filter((tool) => handoffId && handoffToken
|
|
627
|
+
? tool.name === 'submit_session_handoff' || tool.annotations?.readOnlyHint === true
|
|
628
|
+
: tool.name !== 'submit_session_handoff');
|
|
629
|
+
}
|
|
630
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: availableTools() }));
|
|
574
631
|
/** Wrap a successful result as an MCP tool response with JSON text content. */
|
|
575
632
|
function ok(data) {
|
|
576
633
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
@@ -600,6 +657,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
600
657
|
return fail(`Tool '${name}' requires a workspace-bound MCP session (KOBO_WORKSPACE_ID not set).`);
|
|
601
658
|
}
|
|
602
659
|
try {
|
|
660
|
+
if (handoffId && handoffToken && !availableTools().some((tool) => tool.name === name))
|
|
661
|
+
return fail('This turn is reserved for a handoff report. Only read tools and submit_session_handoff are available.');
|
|
662
|
+
if (name === 'submit_session_handoff') {
|
|
663
|
+
if (!workspaceId || !handoffId || !handoffToken)
|
|
664
|
+
return fail('No handoff generation is active for this MCP invocation.');
|
|
665
|
+
return ok(await backendRequest('POST', `/api/workspaces/${workspaceId}/session-handoffs/${handoffId}/report`, {
|
|
666
|
+
token: handoffToken,
|
|
667
|
+
report: a.report,
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
603
670
|
if (WORKSPACE_DIALOGUE_TOOLS.some((tool) => tool.name === name)) {
|
|
604
671
|
if (workspaceId && a.workspace_id === workspaceId && name === 'send_workspace_message')
|
|
605
672
|
return fail('Use the normal conversation to communicate in your own workspace.');
|
|
@@ -612,7 +679,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
612
679
|
const taskId = a.task_id;
|
|
613
680
|
if (!taskId)
|
|
614
681
|
return fail('task_id parameter is required');
|
|
615
|
-
const result = markTaskDoneHandler(db, workspaceId, taskId);
|
|
682
|
+
const result = markTaskDoneHandler(db, workspaceId, taskId, a.verification);
|
|
616
683
|
void notifyBackend(taskId);
|
|
617
684
|
return ok(result);
|
|
618
685
|
}
|
|
@@ -627,7 +694,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
627
694
|
return fail('title parameter is required');
|
|
628
695
|
const task = createTaskHandler(db, workspaceId, {
|
|
629
696
|
title,
|
|
697
|
+
role: a.role,
|
|
630
698
|
is_acceptance_criterion: a.is_acceptance_criterion,
|
|
699
|
+
sort_order: a.sort_order,
|
|
700
|
+
after_task_id: a.after_task_id,
|
|
631
701
|
});
|
|
632
702
|
void notifyTasksUpdated();
|
|
633
703
|
return ok(task);
|
|
@@ -639,7 +709,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
639
709
|
const task = updateTaskHandler(db, workspaceId, taskId, {
|
|
640
710
|
title: a.title,
|
|
641
711
|
status: a.status,
|
|
712
|
+
verification: a.verification,
|
|
642
713
|
is_acceptance_criterion: a.is_acceptance_criterion,
|
|
714
|
+
sort_order: a.sort_order,
|
|
715
|
+
after_task_id: a.after_task_id,
|
|
643
716
|
});
|
|
644
717
|
void notifyTasksUpdated();
|
|
645
718
|
return ok(task);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Shared by fresh installs and the append-only upgrade. */
|
|
2
|
+
export function initAutoLoopSchema(db) {
|
|
3
|
+
const columns = db.prepare('PRAGMA table_info(tasks)').all();
|
|
4
|
+
if (columns.length) {
|
|
5
|
+
if (!columns.some((c) => c.name === 'role'))
|
|
6
|
+
db.exec("ALTER TABLE tasks ADD COLUMN role TEXT NOT NULL DEFAULT 'work'");
|
|
7
|
+
if (!columns.some((c) => c.name === 'verification'))
|
|
8
|
+
db.exec('ALTER TABLE tasks ADD COLUMN verification TEXT');
|
|
9
|
+
if (columns.some((c) => c.name === 'title')) {
|
|
10
|
+
db.exec("UPDATE tasks SET role = 'finalization' WHERE title LIKE '[FINAL] %' AND role = 'work'");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS auto_loop_runs (
|
|
15
|
+
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
16
|
+
phase TEXT NOT NULL DEFAULT 'grooming',
|
|
17
|
+
state TEXT NOT NULL DEFAULT 'waiting',
|
|
18
|
+
reason TEXT,
|
|
19
|
+
iteration INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
diagnostic_attempts INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
current_task_id TEXT,
|
|
22
|
+
current_session_id TEXT,
|
|
23
|
+
updated_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
CREATE TABLE IF NOT EXISTS auto_loop_progress (
|
|
26
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
27
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
28
|
+
milestone INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
PRIMARY KEY (workspace_id, task_id)
|
|
30
|
+
);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS auto_loop_messages (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
34
|
+
client_message_id TEXT NOT NULL,
|
|
35
|
+
content TEXT NOT NULL,
|
|
36
|
+
source TEXT,
|
|
37
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
38
|
+
session_id TEXT,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
UNIQUE (workspace_id, client_message_id)
|
|
41
|
+
);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_auto_loop_messages_pending ON auto_loop_messages(workspace_id, state, id);
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
1
2
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
3
|
+
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
2
4
|
import { initSchema } from './schema.js';
|
|
3
5
|
import { initSearchSchema } from './search-schema.js';
|
|
6
|
+
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
4
7
|
/** Ordered registry of all schema migrations. Append new entries at the end. */
|
|
5
8
|
export const migrations = [
|
|
6
9
|
{
|
|
@@ -744,6 +747,21 @@ export const migrations = [
|
|
|
744
747
|
},
|
|
745
748
|
{ version: 42, name: 'index-logical-search-messages', migrate: initSearchSchema },
|
|
746
749
|
{ version: 43, name: 'durable-mcp-message-requests', migrate: initMcpMessageSchema },
|
|
750
|
+
{ version: 44, name: 'review-session-returns', migrate: initReviewReturnSchema },
|
|
751
|
+
{ version: 45, name: 'durable-auto-loop', migrate: initAutoLoopSchema },
|
|
752
|
+
{ version: 46, name: 'session-handoffs', migrate: initSessionHandoffSchema },
|
|
753
|
+
{
|
|
754
|
+
version: 47,
|
|
755
|
+
name: 'session-activation-order',
|
|
756
|
+
migrate(db) {
|
|
757
|
+
const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'agent_sessions'").get();
|
|
758
|
+
if (!table)
|
|
759
|
+
return;
|
|
760
|
+
const columns = db.prepare('PRAGMA table_info(agent_sessions)').all();
|
|
761
|
+
if (!columns.some((column) => column.name === 'activation_order'))
|
|
762
|
+
db.exec('ALTER TABLE agent_sessions ADD COLUMN activation_order INTEGER NOT NULL DEFAULT 0');
|
|
763
|
+
},
|
|
764
|
+
},
|
|
747
765
|
];
|
|
748
766
|
/** Current schema version — always equals the highest migration version. */
|
|
749
767
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function initReviewReturnSchema(db) {
|
|
2
|
+
db.exec(`CREATE TABLE IF NOT EXISTS pending_review_returns (
|
|
3
|
+
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
4
|
+
review_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
5
|
+
original_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
6
|
+
original_configuration TEXT NOT NULL,
|
|
7
|
+
review_configuration TEXT NOT NULL,
|
|
8
|
+
created_at TEXT NOT NULL
|
|
9
|
+
)`);
|
|
10
|
+
}
|
package/dist/server/db/schema.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
1
2
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
3
|
+
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
2
4
|
import { initSearchSchema } from './search-schema.js';
|
|
5
|
+
import { initSessionHandoffSchema } from './session-handoff-schema.js';
|
|
3
6
|
/** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
|
|
4
7
|
export function initSchema(db) {
|
|
5
8
|
db.exec(`
|
|
@@ -68,7 +71,8 @@ export function initSchema(db) {
|
|
|
68
71
|
ended_at TEXT,
|
|
69
72
|
name TEXT,
|
|
70
73
|
task_progress_baseline TEXT,
|
|
71
|
-
end_reason TEXT
|
|
74
|
+
end_reason TEXT,
|
|
75
|
+
activation_order INTEGER NOT NULL DEFAULT 0
|
|
72
76
|
);
|
|
73
77
|
|
|
74
78
|
CREATE TABLE IF NOT EXISTS ws_events (
|
|
@@ -225,4 +229,7 @@ export function initSchema(db) {
|
|
|
225
229
|
`);
|
|
226
230
|
initSearchSchema(db);
|
|
227
231
|
initMcpMessageSchema(db);
|
|
232
|
+
initReviewReturnSchema(db);
|
|
233
|
+
initAutoLoopSchema(db);
|
|
234
|
+
initSessionHandoffSchema(db);
|
|
228
235
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function initSessionHandoffSchema(db) {
|
|
2
|
+
db.exec(`CREATE TABLE IF NOT EXISTS session_handoffs (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
5
|
+
request_id TEXT NOT NULL,
|
|
6
|
+
request_fingerprint TEXT NOT NULL,
|
|
7
|
+
source_session_id TEXT,
|
|
8
|
+
target_session_id TEXT,
|
|
9
|
+
source_configuration TEXT NOT NULL,
|
|
10
|
+
target_configuration TEXT NOT NULL,
|
|
11
|
+
source_model TEXT,
|
|
12
|
+
generate_summary INTEGER NOT NULL,
|
|
13
|
+
state TEXT NOT NULL,
|
|
14
|
+
report TEXT,
|
|
15
|
+
report_path TEXT,
|
|
16
|
+
generation_token TEXT,
|
|
17
|
+
error TEXT,
|
|
18
|
+
created_at TEXT NOT NULL,
|
|
19
|
+
updated_at TEXT NOT NULL,
|
|
20
|
+
UNIQUE(workspace_id, request_id)
|
|
21
|
+
);
|
|
22
|
+
CREATE UNIQUE INDEX IF NOT EXISTS session_handoffs_pending
|
|
23
|
+
ON session_handoffs(workspace_id) WHERE state NOT IN ('completed', 'cancelled');`);
|
|
24
|
+
}
|