@loicngr/kobo 2.1.28 → 2.1.30
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 +60 -11
- package/CHANGELOG.md +20 -1
- package/CONFIGURATION.md +140 -35
- package/CONTRIBUTING.md +2 -2
- package/DESIGN.md +1 -1
- package/README.md +12 -9
- package/SECURITY.md +7 -2
- package/dist/server/db/memory-schema.js +5 -5
- package/dist/server/db/migrations.js +2 -0
- package/dist/server/db/mistral-memory-migration.js +40 -0
- package/dist/server/routes/engines.js +14 -0
- package/dist/server/routes/environment.js +1 -1
- package/dist/server/routes/health.js +14 -3
- package/dist/server/routes/settings.js +7 -1
- package/dist/server/routes/workspaces.js +116 -9
- package/dist/server/services/agent/engines/claude-code/capabilities.js +2 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +3 -0
- package/dist/server/services/agent/engines/claude-code/resolve-binary.js +1 -1
- package/dist/server/services/agent/engines/codex/capabilities.js +2 -0
- package/dist/server/services/agent/engines/codex/engine.js +3 -0
- package/dist/server/services/agent/engines/configuration.js +14 -0
- package/dist/server/services/agent/engines/mistral-vibe/capabilities.js +13 -0
- package/dist/server/services/agent/engines/mistral-vibe/client.js +64 -0
- package/dist/server/services/agent/engines/mistral-vibe/configuration.js +76 -0
- package/dist/server/services/agent/engines/mistral-vibe/engine.js +361 -0
- package/dist/server/services/agent/engines/mistral-vibe/event-mapper.js +297 -0
- package/dist/server/services/agent/engines/mistral-vibe/options-builder.js +35 -0
- package/dist/server/services/agent/engines/mistral-vibe/server-requests.js +278 -0
- package/dist/server/services/agent/engines/mistral-vibe/spawn.js +176 -0
- package/dist/server/services/agent/engines/registry.js +2 -0
- package/dist/server/services/agent/engines/types.js +6 -0
- package/dist/server/services/agent/orchestrator.js +88 -25
- package/dist/server/services/agent/session-controller.js +5 -0
- package/dist/server/services/auto-loop-final-review-service.js +10 -3
- package/dist/server/services/dev-server-service.js +68 -4
- package/dist/server/services/environment-check-service.js +9 -3
- package/dist/server/services/memory-service.js +1 -1
- package/dist/server/services/mistral-options-service.js +145 -0
- package/dist/server/services/pr-watcher-service.js +40 -11
- package/dist/server/services/review-service.js +33 -9
- package/dist/server/services/session-handoff-service.js +28 -2
- package/dist/server/services/settings-defaults.js +1 -0
- package/dist/server/services/settings-service.js +19 -1
- package/dist/server/services/workspace-creation-mcp-service.js +50 -8
- package/dist/server/services/workspace-dialogue-service.js +2 -0
- package/dist/server/services/workspace-message-service.js +19 -1
- package/dist/server/services/workspace-permission-policy-service.js +24 -0
- package/dist/server/utils/bounded-process.js +1 -1
- package/dist/shared/launch-instructions.js +18 -7
- package/dist/shared/memory.js +1 -1
- package/dist/shared/mistral-models.js +9 -0
- package/dist/shared/mistral-options.js +1 -0
- package/dist/shared/question-selections.js +16 -0
- package/dist/shared/workspace-creation-tools.js +8 -2
- package/dist/shared/workspace-dialogue-tools.js +15 -4
- package/docs/deployment-vps.md +10 -3
- package/docs/getting-started.md +4 -3
- package/docs/troubleshooting.md +2 -2
- package/package.json +20 -14
- package/src/client/dist/pwa/assets/ActivityFeed-C5stmHXP.js +2 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CIDSX32d.css +1 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-DG32W_0G.js +1 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-CAAUEn5X.js → ChangelogPage-_vItY8vA.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-CRV2hxHS.js → ClosePopup-rU8Bm-mC.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-BCb7vB5E.js → ComparisonPanel-B3BMzQH0.js} +2 -2
- package/src/client/dist/pwa/assets/CreatePage-FlGrfmjX.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-oh8ihVC4.css +1 -0
- package/src/client/dist/pwa/assets/{DashboardPage-XE52EABP.js → DashboardPage-lJGwWQYQ.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-DIaQH3lA.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-L4pbQfz1.js → DocumentsPanel-Ct-nm4fX.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-DiOJm5he.js → DrawerToggleButton-M5qv4Io-.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BaHCgb3p.js +3 -0
- package/src/client/dist/pwa/assets/GitPanel-C8vw2qvW.css +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DR3wa6iD.js +1 -0
- package/src/client/dist/pwa/assets/HealthPage-Dbn4J_DT.css +1 -0
- package/src/client/dist/pwa/assets/{MainLayout-DoAEgANU.css → MainLayout-CHi8qP1D.css} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-WJabICsA.js +2 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-DtEvh_b7.js → MemoryPanel-CRpWdfc0.js} +1 -1
- package/src/client/dist/pwa/assets/MemoryProposalList-DGz3lDbL.js +1 -0
- package/src/client/dist/pwa/assets/{QBadge-CW3Lw-kL.js → QBadge-DiwbRpte.js} +1 -1
- package/src/client/dist/pwa/assets/{QBanner-BT2ZemYu.js → QBanner-Cw89HSjk.js} +1 -1
- package/src/client/dist/pwa/assets/QBtn-kfhHFy7Q.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-CWPIugs8.js → QBtnDropdown-tt8rUNNR.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnGroup-CnM4vSJA.js → QBtnGroup-Bioxjp9q.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnToggle-DccOOtbh.js → QBtnToggle-Bod-x9im.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-Be16M4Ig.js → QChip-DvhtPv_L.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-D-EGL2b3.js → QExpansionItem-BkrI3UPL.js} +1 -1
- package/src/client/dist/pwa/assets/{QItemSection-MdJ1_ZK7.js → QItemSection-ahqRK4JF.js} +1 -1
- package/src/client/dist/pwa/assets/{QList-Cx1UfaUm.js → QList-CAUl0v66.js} +1 -1
- package/src/client/dist/pwa/assets/{QMarkupTable-UhP7fVUp.js → QMarkupTable-B4LLAly-.js} +1 -1
- package/src/client/dist/pwa/assets/{QPage-CqkGHokx.js → QPage-DIy_mRoT.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-BMrqK-xd.js → QScrollArea-BvgnY-oA.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-BtZB1wrB.js +1 -0
- package/src/client/dist/pwa/assets/{QSpinnerDots-xdWyaE1i.js → QSpinnerDots-Bea6hAuJ.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-Bk8dLUtU.js → QTooltip-CDH6p745.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-CuxmxCKi.js → SchedulePanel-C2rWlh7l.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-DKnQPUqY.js → SearchPage-BFd4NQwJ.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-B4rmd2WX.css → SettingsPage-Bm0TcarK.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-DDQCgnjL.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-WhQyX8fn.js → SplitWorkspacePage-DKCtOZO4.js} +1 -1
- package/src/client/dist/pwa/assets/TerminalPanel-BE_MSZF0.js +16 -0
- package/src/client/dist/pwa/assets/{ToolCallItem-DYubi-J0.js → ToolCallItem-BEYaOtgh.js} +4 -4
- package/src/client/dist/pwa/assets/{TouchPan-DDotyYLN.js → TouchPan-DeYoOjX5.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-kAnEMcy2.js → TourReplayButton-D5lMwIKt.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BHEAsIoo.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-v7ein__H.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-5_wsJPaJ.js +4 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CtUXz0Cq.css +1 -0
- package/src/client/dist/pwa/assets/{browser-DVYLH8vl.js → browser-ogy_9LVv.js} +2 -2
- package/src/client/dist/pwa/assets/build-path-tree-CRXxaYkA.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-iLTkCd_i.js +1 -0
- package/src/client/dist/pwa/assets/de-BqDU7iZH.js +17 -0
- package/src/client/dist/pwa/assets/detached-fullscreen-CHqMxB-8.js +1 -0
- package/src/client/dist/pwa/assets/dom-CRPVbmiy.js +2 -0
- package/src/client/dist/pwa/assets/ecl-94qehhXe.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-BC38eXAS.js → editor.api-Dx6TbfZG.js} +9 -9
- package/src/client/dist/pwa/assets/editor.worker-xrHNhPSq.js +33 -0
- package/src/client/dist/pwa/assets/efforts-DnJh2fW_.js +9 -0
- package/src/client/dist/pwa/assets/es-D4VMXQRk.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-PUEVWny3.js +1 -0
- package/src/client/dist/pwa/assets/{format-OwxXBnDJ.js → format-yf9XM3dV.js} +48 -48
- package/src/client/dist/pwa/assets/{formatters-YgaCZcwJ.js → formatters-D0sXu9Oe.js} +1 -1
- package/src/client/dist/pwa/assets/fr-DIyaCFB3.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-l7R42fI2.js → freemarker2-l_xIHxRf.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DFmPrgjH.js → handlebars-CYgq99yQ.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CERIAlPM.js → html-C28GatN_.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-2AmHIUy0.js +1 -0
- package/src/client/dist/pwa/assets/index-C0MApoXN.css +1 -0
- package/src/client/dist/pwa/assets/index-MMYMztJ_.js +20 -0
- package/src/client/dist/pwa/assets/it-CF6_4OS0.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BBYncplF.js → javascript-YCSueyyk.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-C1HqYyYU.js → liquid-CUBn90Gs.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-E74dsP22.js → mdx-C5WOHShJ.js} +1 -1
- package/src/client/dist/pwa/assets/monaco-contributions-DYwqbh7m.js +53 -0
- package/src/client/dist/pwa/assets/{monaco.contribution-DYdn9CwR.js → monaco.contribution-CoFzrPQl.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BpJ6Noax.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-NPq4-Tup.js +1 -0
- package/src/client/dist/pwa/assets/permissionModes-Bbl901Hx.js +36 -0
- package/src/client/dist/pwa/assets/{private.use-form-DY1BkePL.js → private.use-form-BcspuzM9.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-_snXWYUp.js → project-color-gNyhKJlV.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DNAAGBcX.js → python-lhKUZdPj.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-CB3g5p_T.js → razor-Chi44rEL.js} +1 -1
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-Dplx1ILy.js +1 -0
- package/src/client/dist/pwa/assets/render-chat-markdown-C9ihAYBt.js +67 -0
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-BAUgNzMK.js +1 -0
- package/src/client/dist/pwa/assets/{typescript-0kpz8bqP.js → typescript-CU2TXVm6.js} +1 -1
- package/src/client/dist/pwa/assets/{use-checkbox-v4aAbE6K.js → use-checkbox-DUxgXR4e.js} +1 -1
- package/src/client/dist/pwa/assets/{use-id-BrdYXce0.js → use-id-B7ieAx2m.js} +1 -1
- package/src/client/dist/pwa/assets/use-is-mobile-CHmaewI1.js +1 -0
- package/src/client/dist/pwa/assets/{use-search-index-status-D38yFOD_.js → use-search-index-status-CQ9S05G7.js} +1 -1
- package/src/client/dist/pwa/assets/use-slide-transition-BJh9xGMa.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-BmrZbHjz.js +2 -0
- package/src/client/dist/pwa/assets/use-tours-CU5oA7cz.css +1 -0
- package/src/client/dist/pwa/assets/use-worktree-restore-DcucRlLh.js +1 -0
- package/src/client/dist/pwa/assets/{workspace-templates-D-lPJQ4G.js → workspace-templates-DaG42EEt.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-BbOuqdUe.js → xml-CPO5LI6F.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-gasFQtfk.js → yaml-CoYs82jI.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 +18 -6
- package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +0 -2
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CK3r_fVB.js +0 -1
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +0 -3
- package/src/client/dist/pwa/assets/GitPanel-yHvU38dp.css +0 -1
- package/src/client/dist/pwa/assets/HealthPage-Cnlhrn--.css +0 -1
- package/src/client/dist/pwa/assets/HealthPage-hJY5MurK.js +0 -1
- package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +0 -2
- package/src/client/dist/pwa/assets/MemoryProposalList-kgFCm9zz.js +0 -1
- package/src/client/dist/pwa/assets/QBtn-BUSdPaf4.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +0 -9
- package/src/client/dist/pwa/assets/TerminalPanel-BY0lPq6k.js +0 -16
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B9LE8HtU.css +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DiYt3rP3.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-DEuOdIIk.js +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +0 -1
- package/src/client/dist/pwa/assets/de-Bk3zQrin.js +0 -17
- package/src/client/dist/pwa/assets/detached-fullscreen-Du3TcYdX.js +0 -1
- package/src/client/dist/pwa/assets/dom-BtYTvLeN.js +0 -2
- package/src/client/dist/pwa/assets/ecl-D05T4iGw.js +0 -1
- package/src/client/dist/pwa/assets/editor.worker-BLJF_oQx.js +0 -33
- package/src/client/dist/pwa/assets/efforts-TGacUPcJ.js +0 -9
- package/src/client/dist/pwa/assets/es-f3xuAWA9.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-DM8gq3Mo.js +0 -1
- package/src/client/dist/pwa/assets/fr-B6c21SG3.js +0 -17
- package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +0 -1
- package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +0 -1
- package/src/client/dist/pwa/assets/index-CpWBGKdM.js +0 -20
- package/src/client/dist/pwa/assets/it-DUstDpC2.js +0 -17
- package/src/client/dist/pwa/assets/monaco-contributions-DHvm-kp5.js +0 -53
- package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-FuTL7HuS.js +0 -1
- package/src/client/dist/pwa/assets/permissionModes-D-Nexnoo.js +0 -36
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-DY1JkAEA.js +0 -1
- package/src/client/dist/pwa/assets/render-chat-markdown-CafzCcmu.js +0 -69
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-Bqg71Vll.js +0 -1
- package/src/client/dist/pwa/assets/use-is-mobile-CDB_rzM9.js +0 -1
- package/src/client/dist/pwa/assets/use-slide-transition-B_Nu6u-t.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-BWkHp34X.js +0 -2
- package/src/client/dist/pwa/assets/use-tours-CTZuBZOi.css +0 -1
- package/src/client/dist/pwa/assets/use-worktree-restore-DUjZr44N.js +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Kōbō
|
|
2
2
|
|
|
3
|
-
**Run a fleet of Claude Code and
|
|
3
|
+
**Run a fleet of Claude Code, Codex and Mistral Vibe agents in parallel — each in its own git worktree, all from one dashboard.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@loicngr/kobo)
|
|
6
6
|
[](./LICENSE)
|
|
@@ -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,
|
|
20
|
+
| **🗂️ Isolated worktrees, three engines** | Every workspace is its own git worktree and branch — parallel sessions never collide. Pick Claude Code, OpenAI Codex or Mistral Vibe 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. An optional [independent final review](CONFIGURATION.md#final-review-for-auto-loop) returns findings to the original session for automatic correction and repeats until none remain, with durable recovery after restart. Cron schedules and one-shot wakeups keep workspaces moving on their own timeline. |
|
|
23
23
|
| **🔀 Create from a PR/MR, or from a ticket** | Pick an open pull/merge request from GitHub, GitLab, or Bitbucket and Kōbō resolves every local conflict for you before spinning up the workspace. Or start straight from a Notion page or a Sentry issue URL. |
|
|
@@ -28,13 +28,13 @@ Use [group messages](CONFIGURATION.md#group-workspace-messages) to send one inst
|
|
|
28
28
|
|
|
29
29
|
## Quick start
|
|
30
30
|
|
|
31
|
-
Requires Node.js ≥
|
|
31
|
+
Requires Node.js ≥ 26.12.1, Git, Bash and your own authentication for the chosen engine (Claude Code, Codex or Mistral Vibe). No skill plugin or ticket integration is required. Kōbō is a local, single-user tool; each user installs their own instance.
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
npx @loicngr/kobo@latest
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Open <http://localhost:3000> and follow the first-run check. Use **Choose a folder** beside the project field to select a local Git repository; completing setup opens the mission form without launching an agent. New installs use the plugin-free **Standard** suite, **Plan** permissions, manual commit/push/publication preferences, and disabled optional integrations/audio. Existing preferences are preserved on upgrade.
|
|
37
|
+
Open <http://localhost:3000> and follow the first-run check. Use **Choose a folder** beside the project field to select a local Git repository; completing setup opens the mission form without launching an agent. New installs use the plugin-free **Standard** suite, **Plan** permissions for Claude Code/Codex (**Interactive** for Mistral Vibe), manual commit/push/publication preferences, and disabled optional integrations/audio. Existing preferences are preserved on upgrade.
|
|
38
38
|
|
|
39
39
|
Follow [your first mission](./docs/getting-started.md), [troubleshooting](./docs/troubleshooting.md), or the [security and privacy model](./SECURITY.md).
|
|
40
40
|
|
|
@@ -69,11 +69,11 @@ Kōbō's production build is an installable PWA — use your browser's **Install
|
|
|
69
69
|
- **Disk-space purge**: reclaim a merged workspace's `node_modules`/`vendor` weight without losing its chat history — see [`CONFIGURATION.md`](./CONFIGURATION.md#auto-purge-worktree-on-pr-merged).
|
|
70
70
|
- **Lifecycle scripts**: shell scripts run on setup, cleanup, archive, session end, PR merge, or auto-loop stop, with output streamed into the chat.
|
|
71
71
|
- **Observability**: a per-session timeline (duration, tools, tokens, errors) and a downloadable redacted diagnostic JSON.
|
|
72
|
-
- **Optional integrations**: Notion (import missions) and Sentry (fix from issue URL), usable by
|
|
72
|
+
- **Optional integrations**: Notion (import missions) and Sentry (fix from issue URL), usable by all three agent engines through saved connections, each independently toggled with a **Test connection** action; local voice transcription via `whisper.cpp`.
|
|
73
73
|
|
|
74
74
|
### Persistent memory across engines
|
|
75
75
|
|
|
76
|
-
Kōbō offers **Manual**, **Hybrid** (the default), and **Automatic** memory behavior in **Settings → Memory**. Global, project, and workspace scopes are shared by Claude Code and
|
|
76
|
+
Kōbō offers **Manual**, **Hybrid** (the default), and **Automatic** memory behavior in **Settings → Memory**. Global, project, and workspace scopes are shared by Claude Code, Codex and Mistral Vibe but remain independently manageable. Hybrid applies workspace notes while asking for human approval before shared project/global changes. The workspace's right-hand **Memory** tab shows the current conversation's delivered context, pending proposals, and a provenance-aware operation journal. Memory has conservative per-response and per-conversation budgets; it cannot retract facts already transmitted into an agent or provider conversation. See the [persistent memory guide](./CONFIGURATION.md#persistent-memory) and [MCP memory tools](./src/mcp-server/README.md#persistent-memory-tools).
|
|
77
77
|
|
|
78
78
|
## Fresh sessions and LLM handoffs
|
|
79
79
|
|
|
@@ -83,6 +83,8 @@ Long conversation getting crowded? Use **Continue in a fresh session** at the bo
|
|
|
83
83
|
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.
|
|
84
84
|
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.
|
|
85
85
|
|
|
86
|
+
Mistral Vibe can be a destination with a supported execution mode. When Vibe is the source, disable **Generate a handoff with the current agent**: its restricted summary launch is unsupported. Existing Plan selections are never silently promoted.
|
|
87
|
+
|
|
86
88
|
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.
|
|
87
89
|
|
|
88
90
|
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.
|
|
@@ -112,7 +114,7 @@ agents, while engine permissions remain a separate control.
|
|
|
112
114
|
Notion and Sentry are optional. Save a **Direct connection** in the integration tab,
|
|
113
115
|
enable it, save Settings, then use **Test connection**. Saved direct connections take
|
|
114
116
|
priority over the Claude MCP selector, which is disabled while one exists. Enabled
|
|
115
|
-
connections also reach Claude Code and
|
|
117
|
+
connections also reach Claude Code, Codex and Mistral Vibe agents through `kobo-notion-*` and
|
|
116
118
|
`kobo-sentry-*` MCP servers. Stop and resume a running agent to apply a connection
|
|
117
119
|
change. See [managed integration MCPs](./CONFIGURATION.md#managed-integration-mcps-in-agent-sessions)
|
|
118
120
|
for credential priority and native-configuration scope.
|
|
@@ -131,8 +133,9 @@ Kōbō binds to `127.0.0.1` only by default. Enabling **Settings → General →
|
|
|
131
133
|
|
|
132
134
|
- **Claude Code**: authenticate once with `claude /login`. Kōbō calls the embedded SDK directly — no separately installed Claude CLI is required at runtime; the SDK supplies its runtime.
|
|
133
135
|
- **OpenAI Codex**: run `codex login` or export `OPENAI_API_KEY`. Kōbō spawns a long-lived `codex app-server` subprocess per workspace.
|
|
136
|
+
- **Mistral Vibe** (experimental): install and authenticate native Vibe as the server user, with `vibe-acp` on that user's PATH. Kōbō uses ACP v1 and native model/thinking selection (Auto or discovered native values). [Setup, limits and compatibility evidence](./CONFIGURATION.md#mistral-vibe).
|
|
134
137
|
|
|
135
|
-
|
|
138
|
+
All three engines share task tracking, memory and managed MCP. Permissions and available controls depend on the engine; Mistral Vibe offers fresh reviews under native permissions (Interactive by default; no guaranteed filesystem read-only access) and excludes Plan, live steering, account/quota controls, managed skills and sub-agent cards. The mapping of Kōbō's four permission modes (`plan` / `bypass` / `strict` / `interactive`) to each engine's native permission semantics is in [`CONFIGURATION.md`](./CONFIGURATION.md#permission-modes).
|
|
136
139
|
|
|
137
140
|
## Skill suites
|
|
138
141
|
|
|
@@ -147,7 +150,7 @@ Hono backend, Vue 3 + Quasar PWA, SQLite (WAL) for persistence, WebSocket for li
|
|
|
147
150
|
```
|
|
148
151
|
src/
|
|
149
152
|
├── server/ # Hono backend (routes, services, db, agent orchestrator)
|
|
150
|
-
│ ├── services/agent/engines/ # claude-code/ + codex/ engines
|
|
153
|
+
│ ├── services/agent/engines/ # claude-code/ + codex/ + mistral-vibe/ engines
|
|
151
154
|
│ └── ...
|
|
152
155
|
├── client/ # Vue 3 + Quasar PWA
|
|
153
156
|
├── mcp-server/ # kobo-tasks MCP server, spawned per workspace
|
package/SECURITY.md
CHANGED
|
@@ -50,11 +50,16 @@ capabilities. This tool boundary does not sandbox arbitrary code executed by an
|
|
|
50
50
|
agent or other integrations running as the same OS account.
|
|
51
51
|
|
|
52
52
|
Git workflow preferences express which automatic actions are wanted. They are not
|
|
53
|
-
a sandbox for arbitrary shell commands or external MCP tools. Claude
|
|
54
|
-
permission modes have different enforcement mechanisms; unrestricted access can
|
|
53
|
+
a sandbox for arbitrary shell commands or external MCP tools. Claude Code, Codex
|
|
54
|
+
and Mistral Vibe permission modes have different enforcement mechanisms; unrestricted access can
|
|
55
55
|
modify files outside a worktree. Provider authentication is separate from Kōbō's
|
|
56
56
|
network token.
|
|
57
57
|
|
|
58
|
+
Mistral Vibe uses native ACP execution modes and has no Plan/read-only mode.
|
|
59
|
+
Review prompts forbid modifications, but native permissions do not guarantee
|
|
60
|
+
filesystem read-only access. Kōbō-managed review tools and memory remain restricted
|
|
61
|
+
to reads, scoped reports and progress. See [Mistral Vibe](./CONFIGURATION.md#mistral-vibe).
|
|
62
|
+
|
|
58
63
|
## Data and credentials
|
|
59
64
|
|
|
60
65
|
KOBO_HOME stores conversations, workspaces, backups and settings. Direct integration
|
|
@@ -44,7 +44,7 @@ export function initMemorySchema(db) {
|
|
|
44
44
|
UNIQUE (scope_id, memory_key),
|
|
45
45
|
CHECK (
|
|
46
46
|
(actor_kind = 'human' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NULL AND transport IS NULL) OR
|
|
47
|
-
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex') AND client_name IS NULL AND transport IS NULL) OR
|
|
47
|
+
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex', 'mistral-vibe') AND client_name IS NULL AND transport IS NULL) OR
|
|
48
48
|
(actor_kind = 'external-mcp' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NOT NULL AND transport IN ('http', 'stdio'))
|
|
49
49
|
)
|
|
50
50
|
);
|
|
@@ -73,7 +73,7 @@ export function initMemorySchema(db) {
|
|
|
73
73
|
CHECK ((target_entry_id IS NULL AND base_revision IS NULL) OR (target_entry_id IS NOT NULL AND base_revision IS NOT NULL)),
|
|
74
74
|
CHECK (
|
|
75
75
|
(actor_kind = 'human' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NULL AND transport IS NULL) OR
|
|
76
|
-
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex') AND client_name IS NULL AND transport IS NULL) OR
|
|
76
|
+
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex', 'mistral-vibe') AND client_name IS NULL AND transport IS NULL) OR
|
|
77
77
|
(actor_kind = 'external-mcp' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NOT NULL AND transport IN ('http', 'stdio'))
|
|
78
78
|
)
|
|
79
79
|
);
|
|
@@ -106,7 +106,7 @@ export function initMemorySchema(db) {
|
|
|
106
106
|
CHECK (affected_proposals IS NULL OR affected_proposals >= 0),
|
|
107
107
|
CHECK (
|
|
108
108
|
(actor_kind = 'human' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NULL AND transport IS NULL) OR
|
|
109
|
-
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex') AND client_name IS NULL AND transport IS NULL) OR
|
|
109
|
+
(actor_kind = 'internal-agent' AND source_engine IN ('claude-code', 'codex', 'mistral-vibe') AND client_name IS NULL AND transport IS NULL) OR
|
|
110
110
|
(actor_kind = 'external-mcp' AND source_workspace_id IS NULL AND source_session_id IS NULL AND source_engine IS NULL AND client_name IS NOT NULL AND transport IN ('http', 'stdio'))
|
|
111
111
|
)
|
|
112
112
|
);
|
|
@@ -122,7 +122,7 @@ export function initMemorySchema(db) {
|
|
|
122
122
|
dispatch_id TEXT NOT NULL UNIQUE,
|
|
123
123
|
budget_context_id TEXT REFERENCES memory_budget_contexts(id) ON DELETE SET NULL,
|
|
124
124
|
budget_epoch INTEGER NOT NULL DEFAULT 0 CHECK (budget_epoch >= 0),
|
|
125
|
-
engine TEXT NOT NULL CHECK (engine IN ('claude-code', 'codex')),
|
|
125
|
+
engine TEXT NOT NULL CHECK (engine IN ('claude-code', 'codex', 'mistral-vibe')),
|
|
126
126
|
state TEXT NOT NULL CHECK (state IN ('prepared', 'submitted', 'initialized', 'failed', 'unknown')),
|
|
127
127
|
entry_revisions_json TEXT NOT NULL,
|
|
128
128
|
scope_generations_json TEXT NOT NULL,
|
|
@@ -144,7 +144,7 @@ export function initMemorySchema(db) {
|
|
|
144
144
|
id TEXT PRIMARY KEY,
|
|
145
145
|
kind TEXT NOT NULL CHECK (kind IN ('internal', 'external')),
|
|
146
146
|
session_id TEXT REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
147
|
-
engine TEXT CHECK (engine IS NULL OR engine IN ('claude-code', 'codex')),
|
|
147
|
+
engine TEXT CHECK (engine IS NULL OR engine IN ('claude-code', 'codex', 'mistral-vibe')),
|
|
148
148
|
conversation_key TEXT CHECK (conversation_key IS NULL OR length(trim(conversation_key)) > 0),
|
|
149
149
|
native_conversation_id TEXT,
|
|
150
150
|
external_context_id TEXT,
|
|
@@ -2,6 +2,7 @@ import { initAutoLoopFinalReviewSchema } from './auto-loop-final-review-schema.j
|
|
|
2
2
|
import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
3
3
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
4
4
|
import { migrateMemorySchema } from './memory-schema.js';
|
|
5
|
+
import { migrateMistralMemoryEngine } from './mistral-memory-migration.js';
|
|
5
6
|
import { initProgressBarSchema } from './progress-bar-schema.js';
|
|
6
7
|
import { initReviewReturnSchema, migrateDurableReviewReturns } from './review-return-schema.js';
|
|
7
8
|
import { initSchema } from './schema.js';
|
|
@@ -812,6 +813,7 @@ export const migrations = [
|
|
|
812
813
|
},
|
|
813
814
|
{ version: 53, name: 'workspace-group-message-receipts', migrate: initWorkspaceMessageBatchSchema },
|
|
814
815
|
{ version: 54, name: 'session-progress-bars', migrate: initProgressBarSchema },
|
|
816
|
+
{ version: 55, name: 'mistral-vibe-memory-engine', migrate: migrateMistralMemoryEngine },
|
|
815
817
|
];
|
|
816
818
|
/** Current schema version — always equals the highest migration version. */
|
|
817
819
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Rebuild only engine CHECK constraints; the migration runner owns the transaction. */
|
|
2
|
+
export function migrateMistralMemoryEngine(db) {
|
|
3
|
+
// Parents precede children so replacement FKs never point at tables being dropped.
|
|
4
|
+
const tables = [
|
|
5
|
+
'memory_entries',
|
|
6
|
+
'memory_budget_contexts',
|
|
7
|
+
'memory_proposals',
|
|
8
|
+
'memory_operations',
|
|
9
|
+
'memory_contexts',
|
|
10
|
+
];
|
|
11
|
+
const definitions = tables.map((name) => ({
|
|
12
|
+
name,
|
|
13
|
+
sql: db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(name)
|
|
14
|
+
.sql,
|
|
15
|
+
}));
|
|
16
|
+
if (definitions.every(({ sql }) => sql.includes("'mistral-vibe'")))
|
|
17
|
+
return;
|
|
18
|
+
const indexes = tables.flatMap((name) => db
|
|
19
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL")
|
|
20
|
+
.all(name));
|
|
21
|
+
const sequence = db
|
|
22
|
+
.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'memory_operations'")
|
|
23
|
+
.safeIntegers()
|
|
24
|
+
.get();
|
|
25
|
+
for (const { name, sql } of definitions) {
|
|
26
|
+
let replacement = sql.replaceAll("'claude-code', 'codex'", "'claude-code', 'codex', 'mistral-vibe'");
|
|
27
|
+
for (const table of tables)
|
|
28
|
+
replacement = replacement.replaceAll(new RegExp(`\\b${table}\\b`, 'g'), `${table}_mistral`);
|
|
29
|
+
db.exec(replacement);
|
|
30
|
+
db.exec(`INSERT INTO ${name}_mistral SELECT * FROM ${name}`);
|
|
31
|
+
}
|
|
32
|
+
for (const name of [...tables].reverse())
|
|
33
|
+
db.exec(`DROP TABLE ${name}`);
|
|
34
|
+
for (const name of tables)
|
|
35
|
+
db.exec(`ALTER TABLE ${name}_mistral RENAME TO ${name}`);
|
|
36
|
+
for (const { sql } of indexes)
|
|
37
|
+
db.exec(sql);
|
|
38
|
+
if (sequence)
|
|
39
|
+
db.prepare("UPDATE sqlite_sequence SET seq = ? WHERE name = 'memory_operations'").run(sequence.seq);
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { listEngines } from '../services/agent/engines/registry.js';
|
|
3
|
+
import { getMistralOptions } from '../services/mistral-options-service.js';
|
|
3
4
|
/** Hono sub-router exposing `GET /` — the list of registered agent engines with capabilities. */
|
|
4
5
|
export const enginesRouter = new Hono();
|
|
5
6
|
enginesRouter.get('/', (c) => c.json(listEngines().map((e) => ({
|
|
@@ -7,3 +8,16 @@ enginesRouter.get('/', (c) => c.json(listEngines().map((e) => ({
|
|
|
7
8
|
displayName: e.displayName,
|
|
8
9
|
capabilities: e.capabilities,
|
|
9
10
|
}))));
|
|
11
|
+
enginesRouter.get('/mistral-vibe/options', async (c) => {
|
|
12
|
+
try {
|
|
13
|
+
return c.json(await getMistralOptions({
|
|
14
|
+
workspaceId: c.req.query('workspaceId'),
|
|
15
|
+
projectPath: c.req.query('projectPath'),
|
|
16
|
+
model: c.req.query('model'),
|
|
17
|
+
agentPermissionMode: c.req.query('agentPermissionMode'),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return c.json({ error: error instanceof Error ? error.message : 'Native Vibe options unavailable' }, 400);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -4,7 +4,7 @@ const app = new Hono();
|
|
|
4
4
|
app.get('/', async (c) => {
|
|
5
5
|
const engine = c.req.query('engine') ?? 'claude-code';
|
|
6
6
|
const projectPath = c.req.query('projectPath')?.trim();
|
|
7
|
-
if (!['claude-code', 'codex'].includes(engine) ||
|
|
7
|
+
if (!['claude-code', 'codex', 'mistral-vibe'].includes(engine) ||
|
|
8
8
|
(projectPath && (projectPath.length > 4096 || projectPath.includes('\0')))) {
|
|
9
9
|
return c.json({ error: 'Invalid environment check request' }, 400);
|
|
10
10
|
}
|
|
@@ -23,7 +23,11 @@ function integrationConfigured(integration, key) {
|
|
|
23
23
|
}
|
|
24
24
|
async function checkCli(binary) {
|
|
25
25
|
try {
|
|
26
|
-
const { stdout } = await execFileAsync(binary, ['--version'], {
|
|
26
|
+
const { stdout } = await execFileAsync(binary, ['--version'], {
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
timeout: 2_000,
|
|
29
|
+
maxBuffer: 64 * 1024,
|
|
30
|
+
});
|
|
27
31
|
return { available: true, version: stdout.trim() || null };
|
|
28
32
|
}
|
|
29
33
|
catch {
|
|
@@ -78,7 +82,13 @@ app.get('/report', async (c) => {
|
|
|
78
82
|
const db = getDb();
|
|
79
83
|
const dbPath = getDbPath();
|
|
80
84
|
const home = getKoboHome();
|
|
81
|
-
const cliChecks = Promise.all([
|
|
85
|
+
const cliChecks = Promise.all([
|
|
86
|
+
checkClaudeCli(),
|
|
87
|
+
checkCodexCli(),
|
|
88
|
+
checkCli('vibe-acp'),
|
|
89
|
+
checkCli('gh'),
|
|
90
|
+
checkCli('glab'),
|
|
91
|
+
]);
|
|
82
92
|
// DB schema version
|
|
83
93
|
const row = db.prepare('SELECT MAX(version) as v FROM schema_migrations').get();
|
|
84
94
|
const dbSchemaVersion = row?.v ?? 0;
|
|
@@ -170,7 +180,7 @@ app.get('/report', async (c) => {
|
|
|
170
180
|
.all();
|
|
171
181
|
const settingsRow = db.prepare('SELECT COUNT(*) as n FROM workspaces').get();
|
|
172
182
|
const archivedRow = db.prepare('SELECT COUNT(*) as n FROM workspaces WHERE archived_at IS NOT NULL').get();
|
|
173
|
-
const [claudeCli, codexCli, ghCli, glabCli] = await cliChecks;
|
|
183
|
+
const [claudeCli, codexCli, mistralCli, ghCli, glabCli] = await cliChecks;
|
|
174
184
|
const report = {
|
|
175
185
|
version: getPackageVersion(),
|
|
176
186
|
koboHome: home,
|
|
@@ -183,6 +193,7 @@ app.get('/report', async (c) => {
|
|
|
183
193
|
settings: { schemaVersion: SETTINGS_SCHEMA_VERSION },
|
|
184
194
|
claudeCli,
|
|
185
195
|
codexCli,
|
|
196
|
+
mistralCli,
|
|
186
197
|
forgeCli: { gh: ghCli, glab: glabCli },
|
|
187
198
|
workspaces: {
|
|
188
199
|
total: settingsRow.n,
|
|
@@ -3,7 +3,7 @@ import { isMemoryMode } from '../../shared/memory.js';
|
|
|
3
3
|
import { isValidSkillSuite } from '../../shared/skill-suite-prompts.js';
|
|
4
4
|
import { isWorkflowPolicy } from '../../shared/workflow-policy.js';
|
|
5
5
|
import { getDb } from '../db/index.js';
|
|
6
|
-
import { getBackendPort } from '../services/agent/orchestrator.js';
|
|
6
|
+
import { getBackendPort, notifyCapacityAvailable } from '../services/agent/orchestrator.js';
|
|
7
7
|
import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT, } from '../services/initial-prompt-template-service.js';
|
|
8
8
|
import { getMcpConnectionInfo } from '../services/mcp-connection-info-service.js';
|
|
9
9
|
import { generateToken, getLanUrls } from '../services/network-access-service.js';
|
|
@@ -167,7 +167,13 @@ app.put('/global', async (c) => {
|
|
|
167
167
|
return c.json({ error: 'Invalid workflowPolicy' }, 400);
|
|
168
168
|
if (body.memoryMode !== undefined && !isMemoryMode(body.memoryMode))
|
|
169
169
|
return c.json({ error: 'Invalid memoryMode' }, 400);
|
|
170
|
+
if ('maxConcurrentAgents' in body &&
|
|
171
|
+
(!Number.isSafeInteger(body.maxConcurrentAgents) || body.maxConcurrentAgents < 0))
|
|
172
|
+
return c.json({ error: 'maxConcurrentAgents must be a non-negative safe integer' }, 400);
|
|
173
|
+
const previousLimit = 'maxConcurrentAgents' in body ? settingsService.getGlobalSettings().maxConcurrentAgents : undefined;
|
|
170
174
|
const updated = settingsService.updateGlobalSettings(body);
|
|
175
|
+
if (previousLimit && (updated.maxConcurrentAgents === 0 || updated.maxConcurrentAgents > previousLimit))
|
|
176
|
+
notifyCapacityAvailable();
|
|
171
177
|
// The client assigns this response straight into its store, so echoing the
|
|
172
178
|
// real credentials back would undo the masking on GET the first time
|
|
173
179
|
// anyone saves anything.
|
|
@@ -4,9 +4,10 @@ import { nanoid } from 'nanoid';
|
|
|
4
4
|
import { isMergeMethod } from '../../shared/forge-merge.js';
|
|
5
5
|
import { isWorkflowPolicy, resolveWorkflowPolicy, WORKFLOW_ACTIONS, } from '../../shared/workflow-policy.js';
|
|
6
6
|
import * as finalReviewService from '../services/auto-loop-final-review-service.js';
|
|
7
|
+
import { preflightMistralConfiguration } from '../services/mistral-options-service.js';
|
|
7
8
|
import { getReviewReturnStatus } from '../services/review-return-service.js';
|
|
8
9
|
import { getSearchIndexStatus, searchEvents } from '../services/search-service.js';
|
|
9
|
-
import { createSessionHandoff, decideSessionHandoff, getCurrentSessionHandoff, SessionHandoffError, submitSessionHandoff, waitForSessionHandoff, } from '../services/session-handoff-service.js';
|
|
10
|
+
import { createSessionHandoff, decideSessionHandoff, getCurrentSessionHandoff, preflightSessionHandoff, SessionHandoffError, submitSessionHandoff, waitForSessionHandoff, } from '../services/session-handoff-service.js';
|
|
10
11
|
import { AgentStopError, assertAgentStopped } from '../utils/agent-stop-result.js';
|
|
11
12
|
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
12
13
|
import { assertWorkspaceLifecycleAvailable, isWorkspaceLifecycleBusy, workspaceLifecycleReason, } from '../utils/workspace-lifecycle-guard.js';
|
|
@@ -18,9 +19,11 @@ import { Hono } from 'hono';
|
|
|
18
19
|
import { bodyLimit } from 'hono/body-limit';
|
|
19
20
|
import { MAX_ATTACHMENT_REQUEST_BYTES } from '../../shared/attachments.js';
|
|
20
21
|
import { AUTO_LOOP_HARD_RULES, buildAutoLoopGroomingSteps, buildGroomingIntro } from '../../shared/auto-loop-prompts.js';
|
|
22
|
+
import { parseQuestionSelections } from '../../shared/question-selections.js';
|
|
21
23
|
import { getDb } from '../db/index.js';
|
|
22
24
|
import { migrationGuard } from '../middleware/migration-guard.js';
|
|
23
25
|
import { workspaceCreationMaintenance } from '../middleware/workspace-creation-maintenance.js';
|
|
26
|
+
import { assertEngineConfigurationSupported } from '../services/agent/engines/configuration.js';
|
|
24
27
|
import { listEngines } from '../services/agent/engines/registry.js';
|
|
25
28
|
import * as agentManager from '../services/agent/orchestrator.js';
|
|
26
29
|
import * as archiveScriptService from '../services/archive-script-service.js';
|
|
@@ -316,7 +319,7 @@ function resolveCreateAgentPermissionMode(bodyValue, _projectPath, globalSetting
|
|
|
316
319
|
const global = globalSettings.defaultPermissionModeByEngine?.[engineId];
|
|
317
320
|
if (isAgentPermissionMode(global))
|
|
318
321
|
return global;
|
|
319
|
-
return 'bypass';
|
|
322
|
+
return engineId === 'mistral-vibe' ? 'interactive' : 'bypass';
|
|
320
323
|
}
|
|
321
324
|
/**
|
|
322
325
|
* Extract the numeric PR/MR number from a forge URL.
|
|
@@ -357,7 +360,9 @@ function handoffError(c, error) {
|
|
|
357
360
|
}
|
|
358
361
|
app.post('/:id/session-handoffs', migrationGuard, async (c) => {
|
|
359
362
|
try {
|
|
360
|
-
|
|
363
|
+
const input = await c.req.json();
|
|
364
|
+
await preflightSessionHandoff(c.req.param('id'), input);
|
|
365
|
+
return c.json({ handoff: createSessionHandoff(c.req.param('id'), input) }, 202);
|
|
361
366
|
}
|
|
362
367
|
catch (error) {
|
|
363
368
|
return handoffError(c, error);
|
|
@@ -418,7 +423,8 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
418
423
|
const engine = listEngines().find((item) => item.id === body.engine);
|
|
419
424
|
if (!engine)
|
|
420
425
|
return c.json({ error: `Unknown engine '${body.engine}'` }, 400);
|
|
421
|
-
const model = typeof body.model === 'string' &&
|
|
426
|
+
const model = typeof body.model === 'string' &&
|
|
427
|
+
(engine.id === 'mistral-vibe' || engine.capabilities.models.some((item) => item.id === body.model))
|
|
422
428
|
? body.model
|
|
423
429
|
: engine.capabilities.models[0]?.id;
|
|
424
430
|
if (!model)
|
|
@@ -428,6 +434,21 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
428
434
|
return c.json({ error: `Engine '${body.engine}' does not support the selected permission mode` }, 400);
|
|
429
435
|
}
|
|
430
436
|
const effort = typeof body.reasoningEffort === 'string' ? body.reasoningEffort : 'auto';
|
|
437
|
+
try {
|
|
438
|
+
assertEngineConfigurationSupported(engine, {
|
|
439
|
+
model: body.model,
|
|
440
|
+
reasoningEffort: body.reasoningEffort,
|
|
441
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
442
|
+
});
|
|
443
|
+
await preflightMistralConfiguration(engine.id, { workspaceId: workspace.id }, {
|
|
444
|
+
model: body.model,
|
|
445
|
+
reasoningEffort: body.reasoningEffort,
|
|
446
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
451
|
+
}
|
|
431
452
|
const handoff = typeof body.handoff === 'string' && body.handoff.trim()
|
|
432
453
|
? body.handoff.trim().slice(0, 30_000)
|
|
433
454
|
: await buildEngineHandoff(workspace, workspace.engine, body.engine);
|
|
@@ -544,6 +565,48 @@ app.post('/', workspaceCreationMaintenance, migrationGuard, creationBodyLimit, a
|
|
|
544
565
|
}
|
|
545
566
|
}
|
|
546
567
|
}
|
|
568
|
+
if (body.engine === 'mistral-vibe' &&
|
|
569
|
+
body.agentPermissionMode !== undefined &&
|
|
570
|
+
!isAgentPermissionMode(body.agentPermissionMode))
|
|
571
|
+
return c.json({ error: 'Invalid agentPermissionMode', step: 'validate' }, 400);
|
|
572
|
+
const selectedEngine = listEngines().find((engine) => engine.id === (body.engine ?? 'claude-code'));
|
|
573
|
+
if (selectedEngine) {
|
|
574
|
+
try {
|
|
575
|
+
const mode = resolveCreateAgentPermissionMode(body.agentPermissionMode, body.projectPath, settingsService.getGlobalSettings(), selectedEngine.id);
|
|
576
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
577
|
+
model: body.model,
|
|
578
|
+
reasoningEffort: body.reasoningEffort,
|
|
579
|
+
agentPermissionMode: mode,
|
|
580
|
+
});
|
|
581
|
+
await preflightMistralConfiguration(selectedEngine.id, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
582
|
+
model: body.model,
|
|
583
|
+
reasoningEffort: body.reasoningEffort,
|
|
584
|
+
agentPermissionMode: mode,
|
|
585
|
+
});
|
|
586
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
587
|
+
model: body.brainstormModel,
|
|
588
|
+
reasoningEffort: body.brainstormReasoningEffort,
|
|
589
|
+
});
|
|
590
|
+
await preflightMistralConfiguration(selectedEngine.id, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
591
|
+
model: body.brainstormModel ?? body.model,
|
|
592
|
+
reasoningEffort: body.brainstormReasoningEffort ?? body.reasoningEffort,
|
|
593
|
+
agentPermissionMode: mode,
|
|
594
|
+
});
|
|
595
|
+
if (body.autoLoopFinalReview) {
|
|
596
|
+
const review = finalReviewService.parseFinalReviewConfiguration(body.autoLoopFinalReview);
|
|
597
|
+
await preflightMistralConfiguration(review.engine, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
598
|
+
...review,
|
|
599
|
+
agentPermissionMode: 'interactive',
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
body.agentPermissionMode = mode;
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
const message = error instanceof Error ? error.message : 'Invalid engine configuration';
|
|
606
|
+
emitCreateFailed(creationId, 'validate', message);
|
|
607
|
+
return c.json({ error: message, step: 'validate' }, 400);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
547
610
|
// Fetch the source branch from origin first. On failure this is non-fatal:
|
|
548
611
|
// fall back to the local source branch if it exists (offline / no remote),
|
|
549
612
|
// and only hard-block (422) when neither origin nor a local branch is usable.
|
|
@@ -1745,7 +1808,10 @@ app.get('/:id/auto-loop/final-review', (c) => {
|
|
|
1745
1808
|
app.patch('/:id/auto-loop/final-review', migrationGuard, async (c) => {
|
|
1746
1809
|
try {
|
|
1747
1810
|
const body = await c.req.json();
|
|
1748
|
-
|
|
1811
|
+
const config = finalReviewService.parseFinalReviewConfiguration(body?.configuration);
|
|
1812
|
+
if (config)
|
|
1813
|
+
await preflightMistralConfiguration(config.engine, { workspaceId: c.req.param('id') }, { ...config, agentPermissionMode: 'interactive' });
|
|
1814
|
+
return c.json(finalReviewService.configureFinalReview(c.req.param('id'), config));
|
|
1749
1815
|
}
|
|
1750
1816
|
catch (error) {
|
|
1751
1817
|
return c.json({ error: error instanceof Error ? error.message : 'Invalid request' }, error instanceof ReviewRequestError ? error.status : 400);
|
|
@@ -2080,6 +2146,7 @@ app.post('/:id/deferred-tool-use/answer', async (c) => {
|
|
|
2080
2146
|
await agentManager.answerPendingQuestion(id, body.answers, body.toolCallId, {
|
|
2081
2147
|
awaitingFreeForm: body.awaitingFreeForm === true,
|
|
2082
2148
|
...(typeof body.response === 'string' ? { response: body.response } : {}),
|
|
2149
|
+
...(body.selections !== undefined ? { selections: parseQuestionSelections(body.selections) } : {}),
|
|
2083
2150
|
});
|
|
2084
2151
|
return c.json({ ok: true });
|
|
2085
2152
|
}
|
|
@@ -2871,6 +2938,25 @@ app.patch('/:id', migrationGuard, async (c) => {
|
|
|
2871
2938
|
}, 400);
|
|
2872
2939
|
}
|
|
2873
2940
|
}
|
|
2941
|
+
const selectedEngine = listEngines().find((engine) => engine.id === workspace.engine);
|
|
2942
|
+
if (selectedEngine &&
|
|
2943
|
+
(body.model !== undefined || body.reasoningEffort !== undefined || body.agentPermissionMode !== undefined)) {
|
|
2944
|
+
try {
|
|
2945
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
2946
|
+
model: body.model ?? workspace.model,
|
|
2947
|
+
reasoningEffort: body.reasoningEffort ?? workspace.reasoningEffort,
|
|
2948
|
+
agentPermissionMode: body.agentPermissionMode ?? workspace.agentPermissionMode,
|
|
2949
|
+
});
|
|
2950
|
+
await preflightMistralConfiguration(selectedEngine.id, { workspaceId: workspace.id }, {
|
|
2951
|
+
model: body.model ?? workspace.model,
|
|
2952
|
+
reasoningEffort: body.reasoningEffort ?? workspace.reasoningEffort,
|
|
2953
|
+
agentPermissionMode: body.agentPermissionMode ?? workspace.agentPermissionMode,
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
catch (error) {
|
|
2957
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2874
2960
|
if (body.status !== undefined) {
|
|
2875
2961
|
if (!isWorkspaceStatus(body.status)) {
|
|
2876
2962
|
return c.json({ error: `Invalid status. Must be one of: ${VALID_WORKSPACE_STATUSES.join(', ')}` }, 400);
|
|
@@ -3415,6 +3501,24 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3415
3501
|
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
3416
3502
|
}, 409);
|
|
3417
3503
|
}
|
|
3504
|
+
const selectedEngine = listEngines().find((engine) => engine.id === workspaceEngine);
|
|
3505
|
+
if (selectedEngine) {
|
|
3506
|
+
try {
|
|
3507
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
3508
|
+
model: workspace.model,
|
|
3509
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
3510
|
+
agentPermissionMode: workspace.agentPermissionMode,
|
|
3511
|
+
});
|
|
3512
|
+
await preflightMistralConfiguration(selectedEngine.id, { workspaceId: workspace.id }, {
|
|
3513
|
+
model: workspace.model,
|
|
3514
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
3515
|
+
agentPermissionMode: workspace.agentPermissionMode,
|
|
3516
|
+
}, resume);
|
|
3517
|
+
}
|
|
3518
|
+
catch (error) {
|
|
3519
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3418
3522
|
// A manual start supersedes any scheduled retry (quota or transient
|
|
3419
3523
|
// watchdog recovery). Leaving its timer armed could spawn a second agent
|
|
3420
3524
|
// after the user has already resumed the workspace.
|
|
@@ -4258,7 +4362,8 @@ app.post('/:id/merge-pr', async (c) => {
|
|
|
4258
4362
|
code: `forge_${availability.reason ?? 'unavailable'}`,
|
|
4259
4363
|
}, 409);
|
|
4260
4364
|
}
|
|
4261
|
-
|
|
4365
|
+
// Record OPEN before merging, even if the watcher has not run since startup.
|
|
4366
|
+
const snapshot = await refreshPrSnapshot(id);
|
|
4262
4367
|
if (snapshot?.state !== 'OPEN') {
|
|
4263
4368
|
return c.json({ error: 'No open PR found for this workspace', code: 'pr_not_open' }, 409);
|
|
4264
4369
|
}
|
|
@@ -4302,13 +4407,15 @@ app.post('/:id/delete-remote-branch', async (c) => {
|
|
|
4302
4407
|
if (!provider.capabilities.canDeleteRemoteBranch) {
|
|
4303
4408
|
return c.json({ error: 'This project cannot delete remote branches', code: 'forge_unsupported' }, 409);
|
|
4304
4409
|
}
|
|
4305
|
-
|
|
4410
|
+
// The merge watcher may have already archived and purged this worktree.
|
|
4411
|
+
const repoPath = workspace.projectPath;
|
|
4412
|
+
const availability = await provider.isAvailable(repoPath);
|
|
4306
4413
|
if (!availability.available)
|
|
4307
4414
|
return c.json({ error: 'Forge CLI unavailable', code: 'forge_unavailable' }, 409);
|
|
4308
|
-
const snapshot = await provider.getPrStatus(
|
|
4415
|
+
const snapshot = await provider.getPrStatus(repoPath, workspace.workingBranch);
|
|
4309
4416
|
if (snapshot?.state !== 'MERGED')
|
|
4310
4417
|
return c.json({ error: 'The PR is not merged yet', code: 'pr_not_merged' }, 409);
|
|
4311
|
-
await provider.deleteRemoteBranch(
|
|
4418
|
+
await provider.deleteRemoteBranch(repoPath, workspace.workingBranch);
|
|
4312
4419
|
return c.json({ success: true });
|
|
4313
4420
|
}
|
|
4314
4421
|
catch (err) {
|
|
@@ -18,6 +18,8 @@ export const CLAUDE_CODE_CAPABILITIES = {
|
|
|
18
18
|
permissionModes: ['plan', 'bypass', 'strict', 'interactive'],
|
|
19
19
|
supportsResume: true,
|
|
20
20
|
supportsMcp: true,
|
|
21
|
+
supportsLiveSteering: true,
|
|
22
|
+
supportsReadOnly: true,
|
|
21
23
|
supportsSkills: true,
|
|
22
24
|
supportsSubagents: true,
|
|
23
25
|
supportsQuotaStatus: true,
|
|
@@ -1049,6 +1049,9 @@ export function createClaudeCodeEngine() {
|
|
|
1049
1049
|
// swallow — best effort
|
|
1050
1050
|
}
|
|
1051
1051
|
},
|
|
1052
|
+
hasPendingUserInput(toolCallId) {
|
|
1053
|
+
return pendingResolvers.has(toolCallId);
|
|
1054
|
+
},
|
|
1052
1055
|
resolvePendingUserInput(toolCallId, response) {
|
|
1053
1056
|
const resolver = pendingResolvers.get(toolCallId);
|
|
1054
1057
|
if (!resolver)
|
|
@@ -3,7 +3,7 @@ const localRequire = createRequire(import.meta.url);
|
|
|
3
3
|
/**
|
|
4
4
|
* Detect the live host's platform/arch/libc from `process`. `glibcVersionRuntime`
|
|
5
5
|
* lives in the diagnostic report header on glibc systems and is absent on musl.
|
|
6
|
-
* Available since Node 12; Kōbō requires Node ≥
|
|
6
|
+
* Available since Node 12; Kōbō requires Node ≥ 26.12.1 (see AGENTS.md).
|
|
7
7
|
*/
|
|
8
8
|
export function detectPlatform() {
|
|
9
9
|
const report = process.report;
|
|
@@ -13,6 +13,8 @@ export const CODEX_CAPABILITIES = {
|
|
|
13
13
|
permissionModes: ['plan', 'bypass', 'strict', 'interactive'],
|
|
14
14
|
supportsResume: true,
|
|
15
15
|
supportsMcp: true,
|
|
16
|
+
supportsLiveSteering: true,
|
|
17
|
+
supportsReadOnly: true,
|
|
16
18
|
supportsSkills: false,
|
|
17
19
|
supportsSubagents: true,
|
|
18
20
|
supportsQuotaStatus: true,
|
|
@@ -692,6 +692,9 @@ export function createCodexEngine() {
|
|
|
692
692
|
}
|
|
693
693
|
await terminateChild();
|
|
694
694
|
},
|
|
695
|
+
hasPendingUserInput(callId) {
|
|
696
|
+
return pendingByCallId.has(callId);
|
|
697
|
+
},
|
|
695
698
|
resolvePendingUserInput(callId, response) {
|
|
696
699
|
const pending = pendingByCallId.get(callId);
|
|
697
700
|
if (!pending)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { assertMistralSelection } from './mistral-vibe/configuration.js';
|
|
2
|
+
/** Validate native selections before creating or replacing launch state. */
|
|
3
|
+
export function assertEngineConfigurationSupported(engine, selection) {
|
|
4
|
+
if (selection.readOnly && !engine.capabilities.supportsReadOnly)
|
|
5
|
+
throw new Error(`Engine '${engine.id}' does not support read-only launches`);
|
|
6
|
+
// Native membership is checked contextually and again on the actual ACP session.
|
|
7
|
+
if (engine.id !== 'mistral-vibe')
|
|
8
|
+
return;
|
|
9
|
+
const mode = selection.agentPermissionMode ?? 'interactive';
|
|
10
|
+
if (!engine.capabilities.permissionModes.some((item) => item === mode))
|
|
11
|
+
throw new Error(`Engine '${engine.id}' does not support agentPermissionMode '${String(mode)}'`);
|
|
12
|
+
assertMistralSelection(selection.model, 'model');
|
|
13
|
+
assertMistralSelection(selection.reasoningEffort, 'thinking');
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MISTRAL_MODELS } from '../../../../../shared/mistral-models.js';
|
|
2
|
+
export const MISTRAL_CAPABILITIES = {
|
|
3
|
+
models: MISTRAL_MODELS.map((model) => ({ id: model.id, label: model.label })),
|
|
4
|
+
effortLevels: [{ id: 'auto', label: 'Auto' }],
|
|
5
|
+
permissionModes: ['interactive', 'strict', 'bypass'],
|
|
6
|
+
supportsResume: true,
|
|
7
|
+
supportsMcp: true,
|
|
8
|
+
supportsLiveSteering: false,
|
|
9
|
+
supportsReadOnly: false,
|
|
10
|
+
supportsSkills: false,
|
|
11
|
+
supportsSubagents: false,
|
|
12
|
+
supportsQuotaStatus: false,
|
|
13
|
+
};
|