@loicngr/kobo 2.1.22 → 2.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/AGENTS.md +72 -1
  2. package/CHANGELOG.md +8 -0
  3. package/CONFIGURATION.md +145 -3
  4. package/dist/mcp-server/kobo-tasks-server.js +7 -6
  5. package/dist/server/index.js +21 -6
  6. package/dist/server/routes/mcp.js +13 -7
  7. package/dist/server/services/agent/orchestrator.js +1 -0
  8. package/dist/server/services/dev-server-service.js +44 -72
  9. package/dist/server/services/lifecycle-hook-service.js +21 -2
  10. package/dist/server/services/mcp-message-delivery-service.js +1 -1
  11. package/dist/server/services/mcp-message-request-service.js +11 -5
  12. package/dist/server/services/settings-service.js +51 -0
  13. package/dist/server/services/workspace-dialogue-service.js +2 -0
  14. package/dist/server/services/workspace-message-service.js +61 -5
  15. package/dist/server/utils/dev-server-instance.js +58 -0
  16. package/dist/server/utils/script-runner.js +6 -1
  17. package/dist/shared/launch-instructions.js +8 -4
  18. package/dist/shared/mcp-attachments.js +58 -0
  19. package/dist/shared/memory-tools.js +2 -2
  20. package/dist/shared/workspace-creation-tools.js +4 -28
  21. package/dist/shared/workspace-dialogue-tools.js +18 -6
  22. package/docker-compose.vps.yml +1 -1
  23. package/docs/deployment-vps.md +11 -0
  24. package/package.json +1 -1
  25. package/scripts/kobo-update +6 -2
  26. package/src/client/dist/pwa/assets/ActivityFeed-BYspOKKg.js +2 -0
  27. package/src/client/dist/pwa/assets/{ActivityFeed-DdJvMzRI.css → ActivityFeed-CmAFv-2c.css} +1 -1
  28. package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-CwnNUWCh.js → AutoLoopFinalReviewSettings-DXX3CqrY.js} +1 -1
  29. package/src/client/dist/pwa/assets/{ChangelogPage-D9ke0TYz.js → ChangelogPage-DKkI5IU-.js} +1 -1
  30. package/src/client/dist/pwa/assets/{ComparisonPanel-ytDu1L7a.js → ComparisonPanel-Cw5Dkan0.js} +1 -1
  31. package/src/client/dist/pwa/assets/{CreatePage-YQ_jGilf.js → CreatePage-B7PLFOH6.js} +1 -1
  32. package/src/client/dist/pwa/assets/{DashboardPage-C-PyArla.js → DashboardPage-Dw8nfkYR.js} +1 -1
  33. package/src/client/dist/pwa/assets/{DiffViewer-CPobwSwm.js → DiffViewer-DFlDwkmd.js} +3 -3
  34. package/src/client/dist/pwa/assets/{DocumentsPanel-BjZGNI-p.js → DocumentsPanel-CH0KzdJV.js} +1 -1
  35. package/src/client/dist/pwa/assets/{DrawerToggleButton-Dtd6fhYu.js → DrawerToggleButton-CZVpU8HH.js} +1 -1
  36. package/src/client/dist/pwa/assets/{GitPanel-BHJ3lpJZ.js → GitPanel-DjKL30gM.js} +3 -3
  37. package/src/client/dist/pwa/assets/{HealthPage-Lv_IdF1l.js → HealthPage-DnPp8GSx.js} +1 -1
  38. package/src/client/dist/pwa/assets/{MainLayout-DYumlWcP.js → MainLayout-1PaKfzdy.js} +2 -2
  39. package/src/client/dist/pwa/assets/{MemoryPanel-DS2F3x-S.js → MemoryPanel-BarliK2r.js} +1 -1
  40. package/src/client/dist/pwa/assets/{MemoryProposalList-D_KDS3uX.js → MemoryProposalList-BtVjVPp7.js} +1 -1
  41. package/src/client/dist/pwa/assets/{QBtnDropdown-XkBxmJO9.js → QBtnDropdown-Rzurb7nO.js} +1 -1
  42. package/src/client/dist/pwa/assets/{QExpansionItem-B5vbHxJ2.js → QExpansionItem-CaKEBcyx.js} +1 -1
  43. package/src/client/dist/pwa/assets/{QScrollArea-BE_fAg20.js → QScrollArea-hrsAyyJf.js} +1 -1
  44. package/src/client/dist/pwa/assets/{QSelect-BjZk4-r8.js → QSelect-Dr7rAt6u.js} +1 -1
  45. package/src/client/dist/pwa/assets/{QTooltip-Dhn6VXWR.js → QTooltip-CNMsXTmD.js} +1 -1
  46. package/src/client/dist/pwa/assets/{SchedulePanel-BDhIt9pX.js → SchedulePanel-nDw2eZ9K.js} +1 -1
  47. package/src/client/dist/pwa/assets/{SearchPage-CJrtIenZ.js → SearchPage-BXJ6iVt1.js} +1 -1
  48. package/src/client/dist/pwa/assets/SettingsPage-Dcq1Jeoo.css +1 -0
  49. package/src/client/dist/pwa/assets/SettingsPage-SHpE2q9Z.js +9 -0
  50. package/src/client/dist/pwa/assets/{SplitWorkspacePage-BV62otQw.js → SplitWorkspacePage-tfZ1kAdZ.js} +1 -1
  51. package/src/client/dist/pwa/assets/{TerminalPanel-TQUHtZ-q.js → TerminalPanel-Cw8R7lAw.js} +1 -1
  52. package/src/client/dist/pwa/assets/{ToolCallItem-DxdYefuz.js → ToolCallItem-DEKhFJEv.js} +2 -2
  53. package/src/client/dist/pwa/assets/{TouchPan-CtwfZgkX.js → TouchPan-DybGzRRK.js} +1 -1
  54. package/src/client/dist/pwa/assets/{TourReplayButton-DEC_Yt6E.js → TourReplayButton-BNdlAZUR.js} +1 -1
  55. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BehGsWhI.js → WorkspaceAttentionLabels-B0H9omcK.js} +1 -1
  56. package/src/client/dist/pwa/assets/{WorkspacePage-Bk-WWOYM.css → WorkspacePage-B-82Rw9T.css} +1 -1
  57. package/src/client/dist/pwa/assets/{WorkspacePage-CQz6QL5L.js → WorkspacePage-tWGXnmrz.js} +3 -3
  58. package/src/client/dist/pwa/assets/{browser-B3bGJv7H.js → browser-DUTZhU-w.js} +1 -1
  59. package/src/client/dist/pwa/assets/{build-path-tree-Bwwe_8vU.js → build-path-tree-ZThcsIN5.js} +1 -1
  60. package/src/client/dist/pwa/assets/{custom-sounds-1Rb1a5LA.js → custom-sounds-CquHn2gw.js} +1 -1
  61. package/src/client/dist/pwa/assets/{de-bfOiFUu1.js → de-B3H0T-ft.js} +2 -2
  62. package/src/client/dist/pwa/assets/{editor.api-D2PTZoSF.js → editor.api-pSvhgban.js} +1 -1
  63. package/src/client/dist/pwa/assets/{efforts-JkWc_mU5.js → efforts-DJaFsLgd.js} +1 -1
  64. package/src/client/dist/pwa/assets/{es-C5Sk8pec.js → es-B4lACW6k.js} +2 -2
  65. package/src/client/dist/pwa/assets/{expand-template-C-aGZz7k.js → expand-template-C5D_p5eV.js} +1 -1
  66. package/src/client/dist/pwa/assets/{format-BbXB3nKS.js → format-DRfnkeRP.js} +1 -1
  67. package/src/client/dist/pwa/assets/{formatters-Bu3lC0Jk.js → formatters-Cog2icjt.js} +1 -1
  68. package/src/client/dist/pwa/assets/{fr-BNVJp67K.js → fr-3uNi02Jz.js} +2 -2
  69. package/src/client/dist/pwa/assets/{freemarker2-qq6O820q.js → freemarker2-W-OLsqOk.js} +1 -1
  70. package/src/client/dist/pwa/assets/{handlebars-nXATCa7t.js → handlebars-Kt5RbWuO.js} +1 -1
  71. package/src/client/dist/pwa/assets/{html-ZSNyTQeY.js → html-D5VKPBbB.js} +1 -1
  72. package/src/client/dist/pwa/assets/i18n-CEzCwMir.js +1 -0
  73. package/src/client/dist/pwa/assets/{index-BZKHE14f.js → index-BZwhT2-I.js} +4 -4
  74. package/src/client/dist/pwa/assets/{it-B9g24VRZ.js → it-DtFWyLHb.js} +2 -2
  75. package/src/client/dist/pwa/assets/{javascript-B3Elzrc0.js → javascript-DBjx8Bg2.js} +1 -1
  76. package/src/client/dist/pwa/assets/{liquid-CrMGh9mA.js → liquid-DYT4a7D7.js} +1 -1
  77. package/src/client/dist/pwa/assets/{mdx-C6nxSGQ5.js → mdx-9L1kysEY.js} +1 -1
  78. package/src/client/dist/pwa/assets/{monaco-contributions-WtuTHJJ8.js → monaco-contributions-DAme5TZS.js} +1 -1
  79. package/src/client/dist/pwa/assets/{monaco.contribution--KeSH1BU.js → monaco.contribution-C58k7TPw.js} +2 -2
  80. package/src/client/dist/pwa/assets/{network-auth-DknPaiQS.js → network-auth-BDUlgV-U.js} +1 -1
  81. package/src/client/dist/pwa/assets/{notify-theme-DTaB0RsK.js → notify-theme-DrQB-mrb.js} +1 -1
  82. package/src/client/dist/pwa/assets/{project-color-bm_jpHka.js → project-color-BlV5PUUW.js} +1 -1
  83. package/src/client/dist/pwa/assets/{python-CQI_FwfK.js → python-DJIlKr-Y.js} +1 -1
  84. package/src/client/dist/pwa/assets/{razor-BT2wh4_7.js → razor-CC3uqdaH.js} +1 -1
  85. package/src/client/dist/pwa/assets/{typescript-D3ibItMf.js → typescript-BnnkT9oE.js} +1 -1
  86. package/src/client/dist/pwa/assets/{use-search-index-status-BnFR-_2l.js → use-search-index-status-CExdHIar.js} +1 -1
  87. package/src/client/dist/pwa/assets/{use-tours-CsPhvrxf.js → use-tours-DOUjqO2f.js} +2 -2
  88. package/src/client/dist/pwa/assets/{use-worktree-restore-B5s0BuHk.js → use-worktree-restore-zIOYNIcb.js} +1 -1
  89. package/src/client/dist/pwa/assets/{workspace-templates-GLO1mBlj.js → workspace-templates-DXJG6flv.js} +1 -1
  90. package/src/client/dist/pwa/assets/{xml-EIEQlO3Y.js → xml-DJtVmsJT.js} +1 -1
  91. package/src/client/dist/pwa/assets/{yaml-CJdHLr6G.js → yaml-Dwuw8FcP.js} +1 -1
  92. package/src/client/dist/pwa/index.html +1 -1
  93. package/src/client/dist/pwa/sw.js +1 -1
  94. package/src/mcp-server/README.md +48 -4
  95. package/src/mcp-server/kobo-tasks-server.ts +8 -10
  96. package/src/client/dist/pwa/assets/ActivityFeed-2wu6qXx1.js +0 -2
  97. package/src/client/dist/pwa/assets/SettingsPage-BSwN45zt.js +0 -9
  98. package/src/client/dist/pwa/assets/SettingsPage-CNIpxG-U.css +0 -1
  99. package/src/client/dist/pwa/assets/i18n-B_b02gtS.js +0 -1
package/AGENTS.md CHANGED
@@ -243,7 +243,23 @@ Claude SDK `task_*` messages cover agents **and** background jobs. Preserve `tas
243
243
 
244
244
  ### External MCP dialogue and update checks
245
245
 
246
- 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.
246
+ 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. Creation and send_workspace_message envelopes with attachments 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.
247
+
248
+ MCP creation and single-workspace messages share `shared/mcp-attachments.ts`
249
+ validation and inline `{name, mime_type, data_base64}` schemas. Validate before
250
+ writing, reserve keyed-message identity before uploading, and fingerprint the
251
+ original content/session plus attachment names, types and bytes (preserving old
252
+ text-only fingerprints). Delivery saves files under the target worktree using
253
+ the existing attachment service, then passes references to the engine and chat.
254
+ Clean up definitively rejected files; retain them if dispatch is uncertain.
255
+ Never expose raw server filesystem paths as attachment inputs. Group messages
256
+ remain text-only.
257
+
258
+ The global JSON setting `forceImmediateMessages` (v67, default false) bypasses
259
+ client chat queueing for new messages using confirmed immediate delivery. It
260
+ also bypasses the auto-loop next-iteration UI path; normal backend immediate
261
+ message delivery pauses a ready loop. Do not flush existing queues on toggle,
262
+ remove compaction/permission/handoff guards, or queue rejected forced sends.
247
263
 
248
264
  `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.
249
265
 
@@ -279,6 +295,26 @@ Two emit flavors in `websocket-service.ts`:
279
295
 
280
296
  ## External integrations
281
297
 
298
+ ### Graceful shutdown and development services
299
+
300
+ `stopDevServersOnShutdown` is a global boolean, default `true` (JSON settings
301
+ migration v66). Shutdown first rejects new dev-server starts and stops tracked
302
+ local processes/agents. Disabling the setting skips configured dev stop commands
303
+ and Compose teardown, preserving independent Docker services; manual Stop and
304
+ workspace deletion/purge remain unchanged. Status recovery uses the existing
305
+ instance-file and exact Compose-label inspection, never a duplicate startup.
306
+
307
+ `shutdownScript` follows the global/project script cascade: empty inherits,
308
+ whitespace disables. The `kobo-shutdown` lifecycle hook runs once per persisted
309
+ workspace with an existing, non-purged worktree (including archived workspaces),
310
+ after local writers stop and before optional Docker teardown. Skip unconfirmed
311
+ agent stops. Each hook has a 1-minute timeout, and the outer shutdown deadline
312
+ is 150 seconds (supervisors need at least 180 seconds). Inject `PROJECT_NAME` from
313
+ the workspace instance config, empty if unavailable, and `INSTANCE`; never guess
314
+ a Compose project. User hooks may explicitly remove Docker resources even when
315
+ automatic shutdown is disabled. Preserve hook outputs and keep SQLite open until
316
+ hooks settle. Do not run scripts on abrupt exits or claim they survive SIGKILL.
317
+
282
318
  ### VPS deployment tooling
283
319
 
284
320
  `docker-compose.vps.yml` is the standalone fresh-instance example for an existing
@@ -381,6 +417,33 @@ expand tool permissions. Initial writable auto-loop launches seed tasks and end;
381
417
  manual launches implement under the workflow policy. Preserve custom templates
382
418
  and frozen migration text when changing generated guidance.
383
419
 
420
+ Prefer dedicated managed MCP operations and require checking their results.
421
+ Writable launches synchronize task progress and blockers during work using only
422
+ supported statuses; restricted launches must not receive task mutation guidance.
423
+ Delegation passes the objective, worktree path, constraints, permissions, test
424
+ runtime and applicable MCP/reporting rules explicitly. Keep final reporting
425
+ evidence-based and phase-aware: distinguish actual checks, delegated results,
426
+ blocked/unverified work and jobs still running. Reuse the shared progress lifecycle
427
+ instead of duplicating it or finishing ongoing work at the end of a turn.
428
+
429
+ Dev-server lifecycle guidance must cover backend/integration dependencies as well
430
+ as frontend/E2E checks. Agents and delegates inspect/reuse the managed instance,
431
+ start/stop only through its MCP tools under writable launch permissions, and
432
+ report unavailable tools/configuration or startup failures instead of bypassing
433
+ Kōbō with direct shell startup commands. Keep tool descriptions in sync.
434
+ Distinguish the agent runtime from project containers, running status from
435
+ application readiness, and interrupted reporting from terminated work. Keep
436
+ readiness probes bounded and do not suggest bypassing failed MCP authentication
437
+ or replaying an ambiguous mutation without checking its outcome.
438
+
439
+ Launch guidance explicitly directs agents to consult injected Kōbō
440
+ memory before targeted retrieval, and to save confirmed reusable facts through
441
+ `remember` only under the current mode/permissions. Preserve retrieval budgets,
442
+ scope selection and Hybrid proposal semantics; native provider memory is not a
443
+ replacement for this shared store.
444
+ Keep detailed workflow guidance in the application launch instructions: the
445
+ separate mode-specific memory bootstrap has a strict total retrieval budget.
446
+
384
447
  The engine's `onPromptPrepared` callback supplies only its textual Kōbō prefix and
385
448
  system append. Initial creation and pending initial retries opt into a single
386
449
  session-scoped `user:message` with `sender: system-prompt`, displayed as Kōbō
@@ -607,6 +670,14 @@ reuse outside PR import still forces setup off. Worktree ownership is unchanged.
607
670
 
608
671
  Each event maps to one effective-settings key (`sessionEndedScript` / `prMergedScript` / `autoLoopDisabledScript`, settings migration v55), cascading project-over-global with empty meaning inherit. Execution reuses `runScript` with `eventPrefix: 'hook:<event>'`; `runScript` gained an `extraEnv` option, merged **under** the identity variables so a hook payload can never claim a different `WORKSPACE_ID`. `runLifecycleHook` never rejects and returns `null` when there is nothing to run (no script, unknown workspace, worktree gone). The pr-watcher awaits the `pr-merged` hook before auto-purge so a deploy script does not lose its worktree mid-run. See [CONFIGURATION.md → Lifecycle hooks](CONFIGURATION.md#lifecycle-hooks).
609
672
 
673
+ The shared `utils/script-runner.ts` supplies `INSTANCE` and `PROJECT_NAME` to
674
+ setup, cleanup, archive and every lifecycle hook, including shutdown. Resolve
675
+ these at execution time through `utils/dev-server-instance.ts`, shared with the
676
+ dev-server service. Never inherit ambient Docker identity or invent a Compose
677
+ project name: `PROJECT_NAME` is empty when no matching instance metadata exists.
678
+ `ScriptVariablesHelp.vue` documents common and event-specific variables in both
679
+ global and project settings; keep this help and CONFIGURATION.md in sync.
680
+
610
681
  ### Engine comparison
611
682
 
612
683
  Successful paired creation opens the existing `split` route with `left` and `right` workspace ids in creation order. A single successful creation keeps the normal workspace route, including when the second creation fails.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ 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.24
8
+
9
+ - feat: add MCP attachments and immediate chat delivery
10
+
11
+ ## 2.1.23
12
+
13
+ - feat: configure shutdown hooks and improve agent guidance
14
+
7
15
  ## 2.1.22
8
16
 
9
17
  - feat(settings): make agent watchdogs opt-in and progress bars configurable
package/CONFIGURATION.md CHANGED
@@ -234,6 +234,8 @@ Projects override a subset of global settings. Anything you set here takes prece
234
234
  | `taskPromptTemplate` | `string` | Prompt used to prefill the mission description when this project is selected. Empty leaves the description without a project-provided prompt. |
235
235
  | `forge` | `'auto' \| 'github' \| 'gitlab' \| 'bitbucket-community' \| 'none'` | Which forge provides PR/MR features. `auto` detects from the git remote URL. See [Forge integration](#forge-integration). |
236
236
  | `devServer.startCommand` / `stopCommand` | `string` | Per-workspace dev server commands. Docker, npm, or any shell-startable process. See [Dev server](#dev-server) for the status/URL contract. |
237
+ | `stopDevServersOnShutdown` | `boolean` | Global, default `true`. Disable to leave Docker services running when Kōbō exits or updates. Tracked local processes always stop. Explicit Stop and workspace teardown still stop services. |
238
+ | `shutdownScript` | `string` | Optional global/per-project shutdown hook, run in each existing workspace worktree before automatic Docker teardown. Empty project value inherits; whitespace disables. See [Kōbō shutdown](#kōbō-shutdown). |
237
239
  | `e2e.framework` | `'cypress' \| 'playwright' \| 'jest' \| 'vitest' \| 'other' \| ''` | E2E framework auto-loop grooming should target. |
238
240
  | `e2e.skill` | `string` | Optional skill name injected into the E2E grooming prompt. |
239
241
  | `e2e.prompt` | `string` | Free-form prompt appended to every `[E2E] ` sub-task. |
@@ -395,14 +397,43 @@ silently.
395
397
 
396
398
  ### Hook environment variables
397
399
 
398
- Every hook gets the standard set: `WORKSPACE_ID`, `WORKSPACE_NAME`,
399
- `BRANCH_NAME`, `SOURCE_BRANCH`, `PROJECT_PATH`, plus `KOBO_HOOK_EVENT` naming
400
- the event. On top of that:
400
+ Settings → Scripts and each project's script fields include expandable
401
+ **Available variables** help. Setup, cleanup, archive and lifecycle scripts all
402
+ run in the workspace worktree (`PWD`) and receive:
403
+
404
+ | Variable | Value |
405
+ |---|---|
406
+ | `WORKSPACE_ID`, `WORKSPACE_NAME` | Workspace identifier and display name |
407
+ | `BRANCH_NAME`, `SOURCE_BRANCH` | Working branch and source branch |
408
+ | `PROJECT_PATH` | Main repository path, not the worktree |
409
+ | `INSTANCE` | Matching Docker instance name, or the lowercased branch with `/` and `_` replaced by `-` |
410
+ | `PROJECT_NAME` | Exact Compose project name from the matching `PROJECT_PATH/.container/instances/*.env` file; empty without a known matching instance |
411
+
412
+ Docker identity is resolved at each script execution using the same instance
413
+ metadata as the dev-server panel. It never inherits a Docker project from the
414
+ server's environment or guesses one from a folder name. In particular, setup
415
+ before the first dev-server launch usually has an empty `PROJECT_NAME`.
416
+ Keep the metadata file if later archive/PR hooks need that identity. These
417
+ variables are specific to the shared workspace script runner; dev start/stop
418
+ and change-source-branch commands have their own contracts documented below.
419
+
420
+ Quote variables and guard destructive Docker commands against a missing project:
421
+
422
+ ```bash
423
+ # Archive or PR merged — stop this stack, preserving volumes and images.
424
+ docker compose -p "${PROJECT_NAME:?Missing Docker project}" down --remove-orphans
425
+ ```
426
+
427
+ Add `--volumes --rmi local` only to also remove its Compose-managed volumes and
428
+ images without custom tags. This does not remove every image on the Docker host.
429
+
430
+ Lifecycle hooks also receive `KOBO_HOOK_EVENT` naming the event. On top of that:
401
431
 
402
432
  | Hook | Extra variables |
403
433
  |---|---|
404
434
  | Session ended | `KOBO_SESSION_ID`, `KOBO_SESSION_END_REASON` (`completed` / `error` / `killed` / `watchdog`), `KOBO_SESSION_EXIT_CODE` (empty when the engine reports none), `KOBO_SESSION_STOP_CAUSE` (`user`, `replacement`, `setup`, `archive`, or `shutdown`; empty for a natural end; superseded sessions and delete/purge stops skip the hook), `KOBO_AUTOLOOP_ACTIVE` (`1` when auto-loop is enabled, so a test run may overlap with the next agent's edits) |
405
435
  | PR merged | `KOBO_PR_NUMBER`, `KOBO_PR_URL` |
436
+ | Kōbō shutdown | `KOBO_SHUTDOWN_SIGNAL` |
406
437
  | Auto-loop stopped | `KOBO_AUTOLOOP_REASON` (`completed` / `user-action`; blocked missions retain their intent and do not fire this hook), `KOBO_TASKS_PENDING` |
407
438
 
408
439
  Only `KOBO_`-prefixed variables are passed through from the event; a hook
@@ -849,6 +880,13 @@ The setting lives in `settings.json` under `global` and has no UI control yet:
849
880
 
850
881
  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.
851
882
 
883
+ Both `create_workspace` and `send_workspace_message` accept inline `attachments`
884
+ with `{name, mime_type, data_base64}`: 10 files, 50 MiB decoded total, same formats
885
+ as the interface. Messages may contain only attachments. Kōbō saves them in the
886
+ destination worktree and sends file/image references to the agent and chat.
887
+ Keyed retries include attachment identity and never upload or send twice. See
888
+ [Sending files and images](src/mcp-server/README.md#sending-files-and-images).
889
+
852
890
  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.
853
891
 
854
892
  See [Connect an external LLM](src/mcp-server/README.md#connect-an-external-llm) for connection examples, tool arguments, cursor polling and authentication details.
@@ -1423,6 +1461,23 @@ Kōbō talks to the embedded [`@anthropic-ai/claude-agent-sdk`](https://github.c
1423
1461
 
1424
1462
  The model selector is populated from [`src/shared/models.ts`](./src/shared/models.ts), also used by backend validation. This checkout includes Fable 5.1, Mythos 5, Opus 5, Opus 4.8, Sonnet 5 and older variants. Pick the default in **Settings → Agents**. Catalogue entries describe what Kōbō offers; provider access still depends on your account. `auto` leaves model selection to the runtime.
1425
1463
 
1464
+ #### Immediate chat messages
1465
+
1466
+ **Settings → Agents → Force immediate message delivery** controls
1467
+ `forceImmediateMessages`, **false by default**, including existing installations.
1468
+ When enabled, new chat messages reach a busy Claude/Codex agent directly, like
1469
+ **Ctrl+Enter**, instead of waiting in Kōbō's message queue. Messages sent during
1470
+ an auto-loop go through immediate delivery and pause the active, ready loop.
1471
+ Previously queued instructions stay queued; changing the setting never sends
1472
+ old drafts automatically. The setting affects subsequent sends without restarting
1473
+ the server or agent.
1474
+
1475
+ Compaction, pending questions/permissions, archive and session handoff guards
1476
+ still apply. Failed or rejected sends restore the draft; they do not silently
1477
+ queue it. MCP messages already use direct delivery and do not depend on this
1478
+ interface setting. Explicit next-iteration scheduling remains available through
1479
+ its dedicated auto-loop API.
1480
+
1426
1481
  #### Session watchdogs
1427
1482
 
1428
1483
  **Settings → Agents → Enable agent inactivity watchdogs**, at the top of the tab,
@@ -1597,6 +1652,48 @@ logs contract does not provide general process health monitoring.
1597
1652
  **No instance file at all**: Start/Stop run your commands; the panel uses the
1598
1653
  saved status when available, otherwise `unknown`, with no URL or Docker logs.
1599
1654
 
1655
+ ### Kōbō shutdown
1656
+
1657
+ In **Settings → Scripts**, **Stop development servers when Kōbō exits** defaults
1658
+ to enabled. Disable it to preserve independently running Docker containers during
1659
+ an update/restart. Kōbō still terminates its tracked local processes, including
1660
+ startup commands. Use detached Docker services (`docker compose up -d`); a
1661
+ foreground Compose process or a custom signal trap can itself stop containers.
1662
+ After restart, the existing instance-file/Compose-label contract lets the panel
1663
+ inspect those containers and recover their URL and logs without starting them.
1664
+ The setting does not change manual Stop, workspace deletion/purge, or user hooks.
1665
+
1666
+ The optional **Hook: Kōbō shutdown** uses the global script by default. A project
1667
+ script replaces it; an empty project field inherits and whitespace disables it.
1668
+ It runs **once per workspace, in that workspace's worktree**, including archived
1669
+ workspaces with an existing checkout. Purged/missing worktrees and workspaces
1670
+ whose agent stop is unconfirmed are skipped. Scripts run after tracked agent/local
1671
+ process shutdown and before automatic Docker teardown, even when automatic Docker
1672
+ shutdown is disabled. Each script has a **1-minute timeout**; failures are logged
1673
+ and do not cancel shutdown. Output is recorded under `hook:kobo-shutdown`.
1674
+
1675
+ Available variables include `WORKSPACE_ID`, `WORKSPACE_NAME`, `BRANCH_NAME`,
1676
+ `SOURCE_BRANCH`, `PROJECT_PATH`, `KOBO_HOOK_EVENT=kobo-shutdown`, and
1677
+ `KOBO_SHUTDOWN_SIGNAL` (`SIGTERM`, `SIGINT`, etc.). `INSTANCE` and `PROJECT_NAME`
1678
+ come from that workspace's dev-server instance configuration. `PROJECT_NAME` is
1679
+ empty when no matching Docker instance is known; never guess a shared stack name.
1680
+
1681
+ For explicit destructive cleanup of the workspace's Compose resources:
1682
+
1683
+ ```bash
1684
+ docker compose -p "${PROJECT_NAME:?Missing Docker project}" down --remove-orphans --volumes --rmi local
1685
+ ```
1686
+
1687
+ This removes Compose-managed volumes and images without custom tags; external
1688
+ volumes and tagged images remain. It can stop containers **even with automatic
1689
+ Docker shutdown disabled**, because the hook is an explicit user command. Supply
1690
+ any `-f` or `--env-file` arguments your project's Compose setup requires.
1691
+
1692
+ These hooks require a graceful Kōbō shutdown; they cannot run after `SIGKILL`,
1693
+ a host power loss or a crash that bypasses teardown. Kōbō's total shutdown deadline
1694
+ is 150 seconds. Allow at least 180 seconds in a container/process supervisor
1695
+ (`stop_grace_period: 180s` in the Compose examples).
1696
+
1600
1697
  ### Example: the full convention end-to-end
1601
1698
 
1602
1699
  The reference implementation is the `sekur` project. Its Kōbō `startCommand` is effectively `make dev-docker`, where the `Makefile` calls `./sh/dev-docker.sh $(INSTANCE)` and `$(INSTANCE)` reads the `INSTANCE` env var Kōbō injects. The script:
@@ -2184,6 +2281,51 @@ Notion token environment overrides are applied consistently to both token variab
2184
2281
 
2185
2282
  New workspaces receive the mission, repository context, current permissions, current phase and expected result. Agents first consult the available managed MCP tool descriptions and schemas, then prefer relevant Kōbō tools for workspace operations. This is not a request to call every tool or poll. Long tasks and delegated work use labeled progress bars; dismissing a bar hides only that activity and does not cancel its work.
2186
2283
 
2284
+ For tasks, development services, shared memory, workspace messages and state,
2285
+ agents prefer the dedicated managed MCP tool and check its result. Writable
2286
+ launches keep existing tasks current during work: `in_progress` when starting,
2287
+ blockers promptly reported in the conversation with the affected task, and done
2288
+ only after required checks pass. Task requirements and evidence are preserved.
2289
+ Blocked tasks stay unfinished; no unsupported field or status is invented.
2290
+ Delegates receive the objective, exact worktree path, user constraints, launch
2291
+ permissions, test runtime and applicable MCP/reporting rules. When a delegate
2292
+ lacks the tools, its parent coordinates the operation under the same permissions.
2293
+ The final report identifies changes/findings, checks actually executed and their
2294
+ results, and remaining blockers or ongoing work. Delegated results are attributed;
2295
+ progress bars finish only for activities with a known outcome, and completing a
2296
+ phase or review does not imply the whole mission is done.
2297
+
2298
+ Agents explicitly use Kōbō internal memory for shared knowledge across sessions,
2299
+ workspaces and engines. At task start/resume, they consult injected facts before
2300
+ targeted retrieval of missing decisions, conventions and known pitfalls. When
2301
+ permitted, they save confirmed reusable facts through `remember` in the narrowest
2302
+ scope, updating existing entries rather than duplicating them. Native provider
2303
+ memory does not replace this shared store. Retrieval budgets, Manual/read-only
2304
+ restrictions and Hybrid approval requirements still apply; secrets, raw logs and
2305
+ routine progress are not memory entries.
2306
+
2307
+ Development services have an explicit managed lifecycle rule: before backend,
2308
+ frontend, integration or E2E checks that need workspace services, inspect
2309
+ `get_dev_server_status` and reuse the existing instance. Authorized writable
2310
+ launches start/stop it through the Kōbō MCP tools, never by invoking the startup
2311
+ script, `make dev-docker` or Docker Compose directly. Diagnose failures with
2312
+ `get_dev_server_logs`; unavailable tools or configuration are reported as blockers.
2313
+ Delegates follow the same rule. Read-only launches cannot start/stop services;
2314
+ test execution inside an existing container still follows current permissions.
2315
+
2316
+ Agents choose test commands and runtimes from the project Makefile, scripts and
2317
+ documentation. Missing dependencies in the agent environment do not establish a
2318
+ project configuration defect when tests belong in a service container. A running
2319
+ dev server is not proof of application readiness: dependent checks require a
2320
+ documented health check or non-mutating URL probe with a timeout and bounded
2321
+ retries. Independent unit tests need not wait for application readiness.
2322
+
2323
+ After interruption/resume, agents verify relevant task/Git state and ongoing
2324
+ jobs, tests and containers before repeating work. MCP failures must be reported
2325
+ as failures, with safe diagnostics; authentication/permission errors never justify
2326
+ modifying credentials or Kōbō storage to bypass the tools. Ambiguous mutation
2327
+ outcomes require checking available status/receipts before retrying.
2328
+
2187
2329
  Plan launches remain conversation-only. Writable auto-loop launches prepare the plan and task list before returning control to the loop; writable manual launches implement after plan approval, under the configured Git policy. Custom brainstorming, Notion and Sentry templates are preserved; they cannot expand launch permissions or authorize commits automatically. Review returns and handoff reports remain read-only, with only their scoped report and operational progress tools allowed. Plan approval does not change permissions; explicitly select an execution mode in Kōbō and start a new launch to implement.
2188
2330
 
2189
2331
  The expandable **Kōbō instructions** chat entry records the application-owned text prepared by the engine builder for the initial launch or a pending initial-prompt retry, including progress guidance, continuation rules, workflow policy and managed integration notices. The pending initial mission is retained until this entry is durably recorded, so a failure during asynchronous launch preparation remains retryable. A later replacement mission is never cleared by an earlier launch. Preparation is not proof of provider delivery. The provider's own system preset is not included. Retrieved memory bodies and capability tokens are not copied to chat; memory usage remains visible separately in the Memory panel. Existing historical prompt entries retain their original content. Ordinary resumes receive current instructions without creating another initial-prompt entry.
@@ -3,9 +3,10 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  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
+ import { MAX_MCP_ATTACHMENT_REQUEST_BYTES } from '../shared/mcp-attachments.js';
6
7
  import { EXTERNAL_MEMORY_TOOL_DEFINITIONS, isMemoryToolName, MEMORY_TOOL_DEFINITIONS } from '../shared/memory-tools.js';
7
8
  import { INTERNAL_PROGRESS_BAR_TOOLS, isProgressBarTool, PROGRESS_BAR_TOOLS } from '../shared/progress-bar-tools.js';
8
- import { isWorkspaceCreationTool, MAX_MCP_CREATION_REQUEST_BYTES, WORKSPACE_CREATION_TOOLS, } from '../shared/workspace-creation-tools.js';
9
+ import { isWorkspaceCreationTool, WORKSPACE_CREATION_TOOLS } from '../shared/workspace-creation-tools.js';
9
10
  import { WORKSPACE_DIALOGUE_TOOLS } from '../shared/workspace-dialogue-tools.js';
10
11
  import { isWorkspaceGroupMessageTool, WORKSPACE_GROUP_MESSAGE_TOOLS } from '../shared/workspace-group-message-tools.js';
11
12
  import { isWorkspaceLifecycleTool, WORKSPACE_LIFECYCLE_TOOLS } from '../shared/workspace-lifecycle-tools.js';
@@ -408,25 +409,25 @@ const WORKSPACE_SCOPED_TOOLS = [
408
409
  },
409
410
  {
410
411
  name: 'get_dev_server_status',
411
- description: 'CALL BEFORE asking the user whether the app is running, or when your change is dev-server-sensitive. Returns `not_configured` when this project has no dev server: do not start one manually in that case. Otherwise returns running/stopped/starting/error + URL, port, container names.',
412
+ description: 'CALL BEFORE backend, frontend, integration or E2E tests that need workspace services, or before asking the user whether the app is running. Reuse the existing instance and its returned URL/container names. Returns `not_configured` when this project has no dev server: report the blocker, do not launch one manually. Otherwise returns running/stopped/starting/error + URL, port, container names. Running is lifecycle status, not application readiness: use the documented health check with bounded retries before dependent tests.',
412
413
  inputSchema: { type: 'object', properties: {}, required: [] },
413
414
  annotations: { readOnlyHint: true, openWorldHint: false },
414
415
  },
415
416
  {
416
417
  name: 'start_dev_server',
417
- description: 'CALL WHEN the user asks you to test the running app and the dev server is stopped.',
418
+ description: 'CALL WHEN an authorized task needs workspace services for app checks or backend/frontend/integration/E2E tests and get_dev_server_status reports stopped. Kōbō runs the configured startup command and tracks the instance. Use this tool instead of shell lifecycle commands such as make dev-docker, sh/dev-docker.sh, docker compose up or npm run dev. Respect launch permissions; read-only sessions cannot start services. On failure, inspect get_dev_server_logs and report the blocker, without a manual shell fallback.',
418
419
  inputSchema: { type: 'object', properties: {}, required: [] },
419
420
  annotations: { destructiveHint: false, openWorldHint: false },
420
421
  },
421
422
  {
422
423
  name: 'stop_dev_server',
423
- description: 'CALL WHEN the user explicitly asks to stop the dev server, or before destructive operations that require a clean boot.',
424
+ description: 'CALL WHEN the user explicitly asks to stop the dev server, or when an authorized operation requires a restart. Use this tool instead of shell shutdown commands such as docker compose down or killing the server process directly. Respect launch permissions; read-only sessions cannot stop services. Do not stop a shared workspace instance merely because your tests finished.',
424
425
  inputSchema: { type: 'object', properties: {}, required: [] },
425
426
  annotations: { destructiveHint: false, openWorldHint: false },
426
427
  },
427
428
  {
428
429
  name: 'get_dev_server_logs',
429
- description: 'CALL WHEN debugging a runtime issue the user describes as happening in the running app. Returns the last N lines of logs (default 200). Cheaper than asking the user to paste them.',
430
+ description: 'CALL WHEN diagnosing a dev-server startup failure or a runtime issue during app checks or tests. Returns the last N lines of logs (default 200). Use these logs with get_dev_server_status before reporting a blocker; do not bypass a failed managed startup with a shell launch.',
430
431
  inputSchema: {
431
432
  type: 'object',
432
433
  properties: {
@@ -1023,7 +1024,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1023
1024
  });
1024
1025
  const transport = new StdioServerTransport(process.stdin, process.stdout, {
1025
1026
  // Include the JSON-lines delimiter in addition to the HTTP envelope budget.
1026
- maxBufferSize: MAX_MCP_CREATION_REQUEST_BYTES + 1,
1027
+ maxBufferSize: MAX_MCP_ATTACHMENT_REQUEST_BYTES + 1,
1027
1028
  });
1028
1029
  server.connect(transport).catch((err) => {
1029
1030
  console.error('[kobo-tasks-server] Fatal:', err);
@@ -37,13 +37,14 @@ import voiceRouter from './routes/voice.js';
37
37
  import workspaceMessagesRouter from './routes/workspace-messages.js';
38
38
  import workspaceTemplatesRouter from './routes/workspace-templates.js';
39
39
  import workspacesRouter from './routes/workspaces.js';
40
- import { getAvailableSkills, reconcileOrphanSessions, restoreRetryCountsFromDb, resumePendingReviewReturns, setBackendPort, startAgent, startWatchdog, stopAgentAndWait, stopAllAgents, stopWatchdog, } from './services/agent/orchestrator.js';
40
+ import { getActiveSessionId, getAvailableSkills, reconcileOrphanSessions, restoreRetryCountsFromDb, resumePendingReviewReturns, setBackendPort, startAgent, startWatchdog, stopAgentAndWait, stopAllAgents, stopWatchdog, } from './services/agent/orchestrator.js';
41
41
  import * as autoLoopService from './services/auto-loop-service.js';
42
42
  import { startAwaitingUserReminder, stopAwaitingUserReminder } from './services/awaiting-user-reminder-service.js';
43
43
  import { runContentMigrationIfNeeded } from './services/content-migration-service.js';
44
44
  import * as cronService from './services/cron-service.js';
45
45
  import { createDailyDbBackupIfNeeded, createPreMigrationBackup, startDailyDbBackupScheduler, } from './services/db-backup-service.js';
46
- import { startDevServer, stopAllDevServers, stopDevServer } from './services/dev-server-service.js';
46
+ import { beginDevServerShutdown, startDevServer, stopAllDevServers, stopDevServer, } from './services/dev-server-service.js';
47
+ import { onKoboShutdown } from './services/lifecycle-hook-service.js';
47
48
  import { reconcileMessageRequests } from './services/mcp-message-request-service.js';
48
49
  import { reconcileMemoryContextsOnStartup } from './services/memory-context-service.js';
49
50
  import { authorizeWsUpgrade, generateToken, getLanHostnames, getLanUrls, isAllowedOrigin, isAllowedRequestHost, resolveBindHost, resolveDevClientOrigin, resolveNetworkAccessEnvOverrides, resolveProxyHostname, } from './services/network-access-service.js';
@@ -422,13 +423,15 @@ async function gracefulShutdown(signal, exitCode = 0) {
422
423
  if (isShuttingDown)
423
424
  return;
424
425
  isShuttingDown = true;
426
+ beginDevServerShutdown();
427
+ const stopDevServersOnShutdown = getGlobalSettings().stopDevServersOnShutdown !== false;
425
428
  stopUpdateChecker();
426
429
  const groupMessagesStopped = stopGroupMessageBatches();
427
430
  console.log(`\n[kobo] Received ${signal}, shutting down gracefully…`);
428
431
  const forceExitTimer = setTimeout(() => {
429
432
  console.error('[kobo] Graceful shutdown timed out; forcing exit');
430
433
  process.exit(1);
431
- }, 5_000);
434
+ }, 150_000);
432
435
  forceExitTimer.unref();
433
436
  // Ask WebSocket clients to leave so their upgraded HTTP connections do not
434
437
  // keep server.close() waiting indefinitely.
@@ -469,14 +472,26 @@ async function gracefulShutdown(signal, exitCode = 0) {
469
472
  // Best-effort
470
473
  }
471
474
  clearInterval(retentionTimer);
472
- // Stop managed agents and both direct/Docker dev servers before closing DB.
475
+ // Stop local writers before user hooks, leaving Docker available to those hooks.
473
476
  try {
474
- await Promise.all([stopAllAgents(), stopAllDevServers()]);
475
- console.log('[kobo] Agents and dev servers stopped');
477
+ await Promise.all([stopAllAgents(), stopAllDevServers({ preserveDocker: true })]);
478
+ await onKoboShutdown(signal, (workspaceId) => getActiveSessionId(workspaceId) !== undefined);
476
479
  }
477
480
  catch {
478
481
  // Best-effort
479
482
  }
483
+ try {
484
+ if (stopDevServersOnShutdown) {
485
+ await stopAllDevServers();
486
+ console.log('[kobo] Dev servers stopped');
487
+ }
488
+ else {
489
+ console.log('[kobo] Local dev processes stopped; Docker services left to the configured shutdown hooks');
490
+ }
491
+ }
492
+ catch (err) {
493
+ console.error('[kobo] Dev-server shutdown failed:', err);
494
+ }
480
495
  // Stop accepting new HTTP/WS work, then let in-flight handlers finish before
481
496
  // closing SQLite. Closing the DB earlier makes those handlers fail midway.
482
497
  try {
@@ -3,9 +3,10 @@ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { Hono } from 'hono';
5
5
  import { bodyLimit } from 'hono/body-limit';
6
+ import { MAX_MCP_ATTACHMENT_REQUEST_BYTES } from '../../shared/mcp-attachments.js';
6
7
  import { EXTERNAL_MEMORY_TOOL_DEFINITIONS, isMemoryToolName } from '../../shared/memory-tools.js';
7
8
  import { isProgressBarTool, PROGRESS_BAR_TOOLS } from '../../shared/progress-bar-tools.js';
8
- import { isWorkspaceCreationTool, MAX_MCP_CREATION_REQUEST_BYTES, WORKSPACE_CREATION_TOOLS, } from '../../shared/workspace-creation-tools.js';
9
+ import { isWorkspaceCreationTool, WORKSPACE_CREATION_TOOLS } from '../../shared/workspace-creation-tools.js';
9
10
  import { EXTERNAL_MCP_TOOLS } from '../../shared/workspace-dialogue-tools.js';
10
11
  import { isWorkspaceGroupMessageTool, WORKSPACE_GROUP_MESSAGE_TOOLS, } from '../../shared/workspace-group-message-tools.js';
11
12
  import { isWorkspaceLifecycleTool, WORKSPACE_LIFECYCLE_TOOLS } from '../../shared/workspace-lifecycle-tools.js';
@@ -19,22 +20,27 @@ import { executeWorkspaceGroupMessageTool, WorkspaceGroupMessageMcpError, } from
19
20
  import { executeWorkspaceLifecycleTool, WorkspaceLifecycleMcpError, } from '../services/workspace-lifecycle-mcp-service.js';
20
21
  import { prepareMemoryToolEnvelope } from '../utils/memory-token-budget.js';
21
22
  const app = new Hono();
22
- // Only creation envelopes may carry the larger, base64 attachment payload.
23
+ // Only creation and message envelopes may carry the larger, base64 attachment payload.
23
24
  // Other tools retain their existing 1 MiB limit, including malformed envelopes.
24
- app.use('*', bodyLimit({ maxSize: MAX_MCP_CREATION_REQUEST_BYTES }));
25
+ app.use('*', bodyLimit({ maxSize: MAX_MCP_ATTACHMENT_REQUEST_BYTES }));
25
26
  app.use('*', async (c, next) => {
26
27
  if (c.req.raw.body) {
27
28
  const body = await c.req.raw.clone().arrayBuffer();
28
29
  if (body.byteLength > 1024 * 1024) {
29
- let creation = false;
30
+ let attachmentTool = false;
30
31
  try {
31
32
  const envelope = JSON.parse(new TextDecoder().decode(body));
32
- creation = envelope?.method === 'tools/call' && envelope?.params?.name === 'create_workspace';
33
+ attachmentTool =
34
+ envelope?.method === 'tools/call' &&
35
+ (envelope?.params?.name === 'create_workspace' ||
36
+ (envelope?.params?.name === 'send_workspace_message' &&
37
+ Array.isArray(envelope?.params?.arguments?.attachments) &&
38
+ envelope.params.arguments.attachments.length > 0));
33
39
  }
34
40
  catch {
35
41
  /* Preserve the normal body-size rejection for malformed input. */
36
42
  }
37
- if (!creation)
43
+ if (!attachmentTool)
38
44
  return c.json({ error: 'MCP request is too large' }, 413);
39
45
  }
40
46
  }
@@ -64,7 +70,7 @@ app.all('/', async (c) => {
64
70
  const transport = new WebStandardStreamableHTTPServerTransport({
65
71
  sessionIdGenerator: undefined,
66
72
  enableJsonResponse: true,
67
- maxRequestBodySize: MAX_MCP_CREATION_REQUEST_BYTES,
73
+ maxRequestBodySize: MAX_MCP_ATTACHMENT_REQUEST_BYTES,
68
74
  });
69
75
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
70
76
  tools: [
@@ -486,6 +486,7 @@ function readEffectiveSettingsSafe(projectPath) {
486
486
  archiveScript: '',
487
487
  changeSourceBranchScript: '',
488
488
  sessionEndedScript: '',
489
+ shutdownScript: '',
489
490
  prMergedScript: '',
490
491
  autoLoopDisabledScript: '',
491
492
  notionStatusProperty: '',