@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.
Files changed (207) hide show
  1. package/AGENTS.md +60 -11
  2. package/CHANGELOG.md +20 -1
  3. package/CONFIGURATION.md +140 -35
  4. package/CONTRIBUTING.md +2 -2
  5. package/DESIGN.md +1 -1
  6. package/README.md +12 -9
  7. package/SECURITY.md +7 -2
  8. package/dist/server/db/memory-schema.js +5 -5
  9. package/dist/server/db/migrations.js +2 -0
  10. package/dist/server/db/mistral-memory-migration.js +40 -0
  11. package/dist/server/routes/engines.js +14 -0
  12. package/dist/server/routes/environment.js +1 -1
  13. package/dist/server/routes/health.js +14 -3
  14. package/dist/server/routes/settings.js +7 -1
  15. package/dist/server/routes/workspaces.js +116 -9
  16. package/dist/server/services/agent/engines/claude-code/capabilities.js +2 -0
  17. package/dist/server/services/agent/engines/claude-code/engine.js +3 -0
  18. package/dist/server/services/agent/engines/claude-code/resolve-binary.js +1 -1
  19. package/dist/server/services/agent/engines/codex/capabilities.js +2 -0
  20. package/dist/server/services/agent/engines/codex/engine.js +3 -0
  21. package/dist/server/services/agent/engines/configuration.js +14 -0
  22. package/dist/server/services/agent/engines/mistral-vibe/capabilities.js +13 -0
  23. package/dist/server/services/agent/engines/mistral-vibe/client.js +64 -0
  24. package/dist/server/services/agent/engines/mistral-vibe/configuration.js +76 -0
  25. package/dist/server/services/agent/engines/mistral-vibe/engine.js +361 -0
  26. package/dist/server/services/agent/engines/mistral-vibe/event-mapper.js +297 -0
  27. package/dist/server/services/agent/engines/mistral-vibe/options-builder.js +35 -0
  28. package/dist/server/services/agent/engines/mistral-vibe/server-requests.js +278 -0
  29. package/dist/server/services/agent/engines/mistral-vibe/spawn.js +176 -0
  30. package/dist/server/services/agent/engines/registry.js +2 -0
  31. package/dist/server/services/agent/engines/types.js +6 -0
  32. package/dist/server/services/agent/orchestrator.js +88 -25
  33. package/dist/server/services/agent/session-controller.js +5 -0
  34. package/dist/server/services/auto-loop-final-review-service.js +10 -3
  35. package/dist/server/services/dev-server-service.js +68 -4
  36. package/dist/server/services/environment-check-service.js +9 -3
  37. package/dist/server/services/memory-service.js +1 -1
  38. package/dist/server/services/mistral-options-service.js +145 -0
  39. package/dist/server/services/pr-watcher-service.js +40 -11
  40. package/dist/server/services/review-service.js +33 -9
  41. package/dist/server/services/session-handoff-service.js +28 -2
  42. package/dist/server/services/settings-defaults.js +1 -0
  43. package/dist/server/services/settings-service.js +19 -1
  44. package/dist/server/services/workspace-creation-mcp-service.js +50 -8
  45. package/dist/server/services/workspace-dialogue-service.js +2 -0
  46. package/dist/server/services/workspace-message-service.js +19 -1
  47. package/dist/server/services/workspace-permission-policy-service.js +24 -0
  48. package/dist/server/utils/bounded-process.js +1 -1
  49. package/dist/shared/launch-instructions.js +18 -7
  50. package/dist/shared/memory.js +1 -1
  51. package/dist/shared/mistral-models.js +9 -0
  52. package/dist/shared/mistral-options.js +1 -0
  53. package/dist/shared/question-selections.js +16 -0
  54. package/dist/shared/workspace-creation-tools.js +8 -2
  55. package/dist/shared/workspace-dialogue-tools.js +15 -4
  56. package/docs/deployment-vps.md +10 -3
  57. package/docs/getting-started.md +4 -3
  58. package/docs/troubleshooting.md +2 -2
  59. package/package.json +20 -14
  60. package/src/client/dist/pwa/assets/ActivityFeed-C5stmHXP.js +2 -0
  61. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CIDSX32d.css +1 -0
  62. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-DG32W_0G.js +1 -0
  63. package/src/client/dist/pwa/assets/{ChangelogPage-CAAUEn5X.js → ChangelogPage-_vItY8vA.js} +1 -1
  64. package/src/client/dist/pwa/assets/{ClosePopup-CRV2hxHS.js → ClosePopup-rU8Bm-mC.js} +1 -1
  65. package/src/client/dist/pwa/assets/{ComparisonPanel-BCb7vB5E.js → ComparisonPanel-B3BMzQH0.js} +2 -2
  66. package/src/client/dist/pwa/assets/CreatePage-FlGrfmjX.js +2 -0
  67. package/src/client/dist/pwa/assets/CreatePage-oh8ihVC4.css +1 -0
  68. package/src/client/dist/pwa/assets/{DashboardPage-XE52EABP.js → DashboardPage-lJGwWQYQ.js} +1 -1
  69. package/src/client/dist/pwa/assets/DiffViewer-DIaQH3lA.js +8 -0
  70. package/src/client/dist/pwa/assets/{DocumentsPanel-L4pbQfz1.js → DocumentsPanel-Ct-nm4fX.js} +1 -1
  71. package/src/client/dist/pwa/assets/{DrawerToggleButton-DiOJm5he.js → DrawerToggleButton-M5qv4Io-.js} +1 -1
  72. package/src/client/dist/pwa/assets/GitPanel-BaHCgb3p.js +3 -0
  73. package/src/client/dist/pwa/assets/GitPanel-C8vw2qvW.css +1 -0
  74. package/src/client/dist/pwa/assets/HealthPage-DR3wa6iD.js +1 -0
  75. package/src/client/dist/pwa/assets/HealthPage-Dbn4J_DT.css +1 -0
  76. package/src/client/dist/pwa/assets/{MainLayout-DoAEgANU.css → MainLayout-CHi8qP1D.css} +1 -1
  77. package/src/client/dist/pwa/assets/MainLayout-WJabICsA.js +2 -0
  78. package/src/client/dist/pwa/assets/{MemoryPanel-DtEvh_b7.js → MemoryPanel-CRpWdfc0.js} +1 -1
  79. package/src/client/dist/pwa/assets/MemoryProposalList-DGz3lDbL.js +1 -0
  80. package/src/client/dist/pwa/assets/{QBadge-CW3Lw-kL.js → QBadge-DiwbRpte.js} +1 -1
  81. package/src/client/dist/pwa/assets/{QBanner-BT2ZemYu.js → QBanner-Cw89HSjk.js} +1 -1
  82. package/src/client/dist/pwa/assets/QBtn-kfhHFy7Q.js +1 -0
  83. package/src/client/dist/pwa/assets/{QBtnDropdown-CWPIugs8.js → QBtnDropdown-tt8rUNNR.js} +1 -1
  84. package/src/client/dist/pwa/assets/{QBtnGroup-CnM4vSJA.js → QBtnGroup-Bioxjp9q.js} +1 -1
  85. package/src/client/dist/pwa/assets/{QBtnToggle-DccOOtbh.js → QBtnToggle-Bod-x9im.js} +1 -1
  86. package/src/client/dist/pwa/assets/{QChip-Be16M4Ig.js → QChip-DvhtPv_L.js} +1 -1
  87. package/src/client/dist/pwa/assets/{QExpansionItem-D-EGL2b3.js → QExpansionItem-BkrI3UPL.js} +1 -1
  88. package/src/client/dist/pwa/assets/{QItemSection-MdJ1_ZK7.js → QItemSection-ahqRK4JF.js} +1 -1
  89. package/src/client/dist/pwa/assets/{QList-Cx1UfaUm.js → QList-CAUl0v66.js} +1 -1
  90. package/src/client/dist/pwa/assets/{QMarkupTable-UhP7fVUp.js → QMarkupTable-B4LLAly-.js} +1 -1
  91. package/src/client/dist/pwa/assets/{QPage-CqkGHokx.js → QPage-DIy_mRoT.js} +1 -1
  92. package/src/client/dist/pwa/assets/{QScrollArea-BMrqK-xd.js → QScrollArea-BvgnY-oA.js} +1 -1
  93. package/src/client/dist/pwa/assets/QSelect-BtZB1wrB.js +1 -0
  94. package/src/client/dist/pwa/assets/{QSpinnerDots-xdWyaE1i.js → QSpinnerDots-Bea6hAuJ.js} +1 -1
  95. package/src/client/dist/pwa/assets/{QTooltip-Bk8dLUtU.js → QTooltip-CDH6p745.js} +1 -1
  96. package/src/client/dist/pwa/assets/{SchedulePanel-CuxmxCKi.js → SchedulePanel-C2rWlh7l.js} +1 -1
  97. package/src/client/dist/pwa/assets/{SearchPage-DKnQPUqY.js → SearchPage-BFd4NQwJ.js} +1 -1
  98. package/src/client/dist/pwa/assets/{SettingsPage-B4rmd2WX.css → SettingsPage-Bm0TcarK.css} +1 -1
  99. package/src/client/dist/pwa/assets/SettingsPage-DDQCgnjL.js +9 -0
  100. package/src/client/dist/pwa/assets/{SplitWorkspacePage-WhQyX8fn.js → SplitWorkspacePage-DKCtOZO4.js} +1 -1
  101. package/src/client/dist/pwa/assets/TerminalPanel-BE_MSZF0.js +16 -0
  102. package/src/client/dist/pwa/assets/{ToolCallItem-DYubi-J0.js → ToolCallItem-BEYaOtgh.js} +4 -4
  103. package/src/client/dist/pwa/assets/{TouchPan-DDotyYLN.js → TouchPan-DeYoOjX5.js} +1 -1
  104. package/src/client/dist/pwa/assets/{TourReplayButton-kAnEMcy2.js → TourReplayButton-D5lMwIKt.js} +1 -1
  105. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BHEAsIoo.js +1 -0
  106. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-v7ein__H.css +1 -0
  107. package/src/client/dist/pwa/assets/WorkspacePage-5_wsJPaJ.js +4 -0
  108. package/src/client/dist/pwa/assets/WorkspacePage-CtUXz0Cq.css +1 -0
  109. package/src/client/dist/pwa/assets/{browser-DVYLH8vl.js → browser-ogy_9LVv.js} +2 -2
  110. package/src/client/dist/pwa/assets/build-path-tree-CRXxaYkA.js +1 -0
  111. package/src/client/dist/pwa/assets/custom-sounds-iLTkCd_i.js +1 -0
  112. package/src/client/dist/pwa/assets/de-BqDU7iZH.js +17 -0
  113. package/src/client/dist/pwa/assets/detached-fullscreen-CHqMxB-8.js +1 -0
  114. package/src/client/dist/pwa/assets/dom-CRPVbmiy.js +2 -0
  115. package/src/client/dist/pwa/assets/ecl-94qehhXe.js +1 -0
  116. package/src/client/dist/pwa/assets/{editor.api-BC38eXAS.js → editor.api-Dx6TbfZG.js} +9 -9
  117. package/src/client/dist/pwa/assets/editor.worker-xrHNhPSq.js +33 -0
  118. package/src/client/dist/pwa/assets/efforts-DnJh2fW_.js +9 -0
  119. package/src/client/dist/pwa/assets/es-D4VMXQRk.js +17 -0
  120. package/src/client/dist/pwa/assets/expand-template-PUEVWny3.js +1 -0
  121. package/src/client/dist/pwa/assets/{format-OwxXBnDJ.js → format-yf9XM3dV.js} +48 -48
  122. package/src/client/dist/pwa/assets/{formatters-YgaCZcwJ.js → formatters-D0sXu9Oe.js} +1 -1
  123. package/src/client/dist/pwa/assets/fr-DIyaCFB3.js +17 -0
  124. package/src/client/dist/pwa/assets/{freemarker2-l7R42fI2.js → freemarker2-l_xIHxRf.js} +1 -1
  125. package/src/client/dist/pwa/assets/{handlebars-DFmPrgjH.js → handlebars-CYgq99yQ.js} +1 -1
  126. package/src/client/dist/pwa/assets/{html-CERIAlPM.js → html-C28GatN_.js} +1 -1
  127. package/src/client/dist/pwa/assets/i18n-2AmHIUy0.js +1 -0
  128. package/src/client/dist/pwa/assets/index-C0MApoXN.css +1 -0
  129. package/src/client/dist/pwa/assets/index-MMYMztJ_.js +20 -0
  130. package/src/client/dist/pwa/assets/it-CF6_4OS0.js +17 -0
  131. package/src/client/dist/pwa/assets/{javascript-BBYncplF.js → javascript-YCSueyyk.js} +1 -1
  132. package/src/client/dist/pwa/assets/{liquid-C1HqYyYU.js → liquid-CUBn90Gs.js} +1 -1
  133. package/src/client/dist/pwa/assets/{mdx-E74dsP22.js → mdx-C5WOHShJ.js} +1 -1
  134. package/src/client/dist/pwa/assets/monaco-contributions-DYwqbh7m.js +53 -0
  135. package/src/client/dist/pwa/assets/{monaco.contribution-DYdn9CwR.js → monaco.contribution-CoFzrPQl.js} +2 -2
  136. package/src/client/dist/pwa/assets/network-auth-BpJ6Noax.js +1 -0
  137. package/src/client/dist/pwa/assets/notify-theme-NPq4-Tup.js +1 -0
  138. package/src/client/dist/pwa/assets/permissionModes-Bbl901Hx.js +36 -0
  139. package/src/client/dist/pwa/assets/{private.use-form-DY1BkePL.js → private.use-form-BcspuzM9.js} +1 -1
  140. package/src/client/dist/pwa/assets/{project-color-_snXWYUp.js → project-color-gNyhKJlV.js} +1 -1
  141. package/src/client/dist/pwa/assets/{python-DNAAGBcX.js → python-lhKUZdPj.js} +1 -1
  142. package/src/client/dist/pwa/assets/{razor-CB3g5p_T.js → razor-Chi44rEL.js} +1 -1
  143. package/src/client/dist/pwa/assets/reactivity.esm-bundler-Dplx1ILy.js +1 -0
  144. package/src/client/dist/pwa/assets/render-chat-markdown-C9ihAYBt.js +67 -0
  145. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-BAUgNzMK.js +1 -0
  146. package/src/client/dist/pwa/assets/{typescript-0kpz8bqP.js → typescript-CU2TXVm6.js} +1 -1
  147. package/src/client/dist/pwa/assets/{use-checkbox-v4aAbE6K.js → use-checkbox-DUxgXR4e.js} +1 -1
  148. package/src/client/dist/pwa/assets/{use-id-BrdYXce0.js → use-id-B7ieAx2m.js} +1 -1
  149. package/src/client/dist/pwa/assets/use-is-mobile-CHmaewI1.js +1 -0
  150. package/src/client/dist/pwa/assets/{use-search-index-status-D38yFOD_.js → use-search-index-status-CQ9S05G7.js} +1 -1
  151. package/src/client/dist/pwa/assets/use-slide-transition-BJh9xGMa.js +1 -0
  152. package/src/client/dist/pwa/assets/use-tours-BmrZbHjz.js +2 -0
  153. package/src/client/dist/pwa/assets/use-tours-CU5oA7cz.css +1 -0
  154. package/src/client/dist/pwa/assets/use-worktree-restore-DcucRlLh.js +1 -0
  155. package/src/client/dist/pwa/assets/{workspace-templates-D-lPJQ4G.js → workspace-templates-DaG42EEt.js} +1 -1
  156. package/src/client/dist/pwa/assets/{xml-BbOuqdUe.js → xml-CPO5LI6F.js} +1 -1
  157. package/src/client/dist/pwa/assets/{yaml-gasFQtfk.js → yaml-CoYs82jI.js} +1 -1
  158. package/src/client/dist/pwa/index.html +10 -10
  159. package/src/client/dist/pwa/sw.js +1 -1
  160. package/src/mcp-server/README.md +18 -6
  161. package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +0 -2
  162. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CK3r_fVB.js +0 -1
  163. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +0 -1
  164. package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +0 -2
  165. package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +0 -1
  166. package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +0 -8
  167. package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +0 -3
  168. package/src/client/dist/pwa/assets/GitPanel-yHvU38dp.css +0 -1
  169. package/src/client/dist/pwa/assets/HealthPage-Cnlhrn--.css +0 -1
  170. package/src/client/dist/pwa/assets/HealthPage-hJY5MurK.js +0 -1
  171. package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +0 -2
  172. package/src/client/dist/pwa/assets/MemoryProposalList-kgFCm9zz.js +0 -1
  173. package/src/client/dist/pwa/assets/QBtn-BUSdPaf4.js +0 -1
  174. package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +0 -1
  175. package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +0 -9
  176. package/src/client/dist/pwa/assets/TerminalPanel-BY0lPq6k.js +0 -16
  177. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B9LE8HtU.css +0 -1
  178. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DiYt3rP3.js +0 -1
  179. package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +0 -1
  180. package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +0 -4
  181. package/src/client/dist/pwa/assets/build-path-tree-DEuOdIIk.js +0 -1
  182. package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +0 -1
  183. package/src/client/dist/pwa/assets/de-Bk3zQrin.js +0 -17
  184. package/src/client/dist/pwa/assets/detached-fullscreen-Du3TcYdX.js +0 -1
  185. package/src/client/dist/pwa/assets/dom-BtYTvLeN.js +0 -2
  186. package/src/client/dist/pwa/assets/ecl-D05T4iGw.js +0 -1
  187. package/src/client/dist/pwa/assets/editor.worker-BLJF_oQx.js +0 -33
  188. package/src/client/dist/pwa/assets/efforts-TGacUPcJ.js +0 -9
  189. package/src/client/dist/pwa/assets/es-f3xuAWA9.js +0 -17
  190. package/src/client/dist/pwa/assets/expand-template-DM8gq3Mo.js +0 -1
  191. package/src/client/dist/pwa/assets/fr-B6c21SG3.js +0 -17
  192. package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +0 -1
  193. package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +0 -1
  194. package/src/client/dist/pwa/assets/index-CpWBGKdM.js +0 -20
  195. package/src/client/dist/pwa/assets/it-DUstDpC2.js +0 -17
  196. package/src/client/dist/pwa/assets/monaco-contributions-DHvm-kp5.js +0 -53
  197. package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +0 -1
  198. package/src/client/dist/pwa/assets/notify-theme-FuTL7HuS.js +0 -1
  199. package/src/client/dist/pwa/assets/permissionModes-D-Nexnoo.js +0 -36
  200. package/src/client/dist/pwa/assets/reactivity.esm-bundler-DY1JkAEA.js +0 -1
  201. package/src/client/dist/pwa/assets/render-chat-markdown-CafzCcmu.js +0 -69
  202. package/src/client/dist/pwa/assets/runtime-core.esm-bundler-Bqg71Vll.js +0 -1
  203. package/src/client/dist/pwa/assets/use-is-mobile-CDB_rzM9.js +0 -1
  204. package/src/client/dist/pwa/assets/use-slide-transition-B_Nu6u-t.js +0 -1
  205. package/src/client/dist/pwa/assets/use-tours-BWkHp34X.js +0 -2
  206. package/src/client/dist/pwa/assets/use-tours-CTZuBZOi.css +0 -1
  207. package/src/client/dist/pwa/assets/use-worktree-restore-DUjZr44N.js +0 -1
@@ -0,0 +1,64 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+ import * as acp from '@agentclientprotocol/sdk';
3
+ /** The SDK owns framing and incoming validation. Stderr never enters ACP. */
4
+ export function connectVibeAcp(child, handlers) {
5
+ if (!child.stdin || !child.stdout)
6
+ throw new Error('Vibe ACP requires piped stdin and stdout');
7
+ const app = acp.client({ name: 'kobo' });
8
+ if (handlers.sessionUpdate)
9
+ app.onNotification(acp.methods.client.session.update, handlers.sessionUpdate);
10
+ if (handlers.requestPermission)
11
+ app.onRequest(acp.methods.client.session.requestPermission, handlers.requestPermission);
12
+ if (handlers.createElicitation)
13
+ app.onRequest(acp.methods.client.elicitation.create, handlers.createElicitation);
14
+ // Node and DOM disagree on BYOB generics; both adapters are standard Web Streams at runtime.
15
+ const readable = Readable.toWeb(child.stdout);
16
+ return app.connect(acp.ndJsonStream(Writable.toWeb(child.stdin), readable));
17
+ }
18
+ /** Fluent SDK responses are unchecked; validate the fields this transport consumes. */
19
+ function validateResponse(method, response) {
20
+ const value = response;
21
+ if (method === acp.methods.agent.initialize && value?.protocolVersion !== acp.PROTOCOL_VERSION) {
22
+ throw new Error('Invalid Vibe ACP protocol version; ACP v1 is required');
23
+ }
24
+ if (method === acp.methods.agent.session.new &&
25
+ (typeof value?.sessionId !== 'string' || value.sessionId.trim().length === 0)) {
26
+ throw new Error('Invalid Vibe ACP session identity');
27
+ }
28
+ if (method === acp.methods.agent.session.prompt &&
29
+ (typeof value?.stopReason !== 'string' ||
30
+ !['end_turn', 'max_tokens', 'max_turn_requests', 'refusal', 'cancelled'].includes(value.stopReason))) {
31
+ throw new Error('Invalid Vibe ACP prompt stop reason');
32
+ }
33
+ }
34
+ /** Use for all requests: initialization/setup are bounded, prompts have no absolute deadline. */
35
+ export async function requestVibeAcp(connection, method, params) {
36
+ const timeout = method === acp.methods.agent.initialize ? 20000 : 30000;
37
+ let timer;
38
+ const pending = connection.agent.request(method, params);
39
+ try {
40
+ const response = await (method === acp.methods.agent.session.prompt
41
+ ? pending
42
+ : Promise.race([
43
+ pending,
44
+ new Promise((_resolve, reject) => {
45
+ timer = setTimeout(() => {
46
+ const error = new Error(`Vibe ACP ${method} timed out after ${timeout}ms`);
47
+ reject(error);
48
+ }, timeout);
49
+ }),
50
+ ]));
51
+ validateResponse(method, response);
52
+ return response;
53
+ }
54
+ finally {
55
+ clearTimeout(timer);
56
+ }
57
+ }
58
+ /** Pass as runtime.stop's callback; the runtime still ends stdin and confirms process exit. */
59
+ export async function closeVibeAcpSession(connection, session) {
60
+ if (session.active)
61
+ await connection.agent.notify(acp.methods.agent.session.cancel, { sessionId: session.sessionId });
62
+ if (session.canClose)
63
+ await requestVibeAcp(connection, acp.methods.agent.session.close, { sessionId: session.sessionId });
64
+ }
@@ -0,0 +1,76 @@
1
+ export const MISTRAL_MODES = { interactive: 'ask', strict: 'accept-edits', bypass: 'auto-approve' };
2
+ export function assertMistralSelection(value, label) {
3
+ if (value !== undefined && !text(value))
4
+ throw new Error(`Invalid Mistral Vibe ${label}; expected a nonempty native value of at most 256 characters`);
5
+ }
6
+ function record(value) {
7
+ return !!value && typeof value === 'object' && !Array.isArray(value);
8
+ }
9
+ function text(value, max = 256) {
10
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Native identities and labels must exclude control characters.
11
+ return typeof value === 'string' && !!value.trim() && value.length <= max && !/[\x00-\x1f\x7f]/.test(value);
12
+ }
13
+ function invalid() {
14
+ throw new Error('Invalid Vibe ACP configuration options');
15
+ }
16
+ /** SDK responses are unchecked. Keep only bounded, validated native display fields. */
17
+ export function readMistralSelect(response, id, required = true) {
18
+ if (!record(response) || !Array.isArray(response.configOptions) || response.configOptions.length > 64)
19
+ return invalid();
20
+ const ids = new Set();
21
+ for (const item of response.configOptions) {
22
+ if (!record(item) || !text(item.id) || ids.has(item.id))
23
+ return invalid();
24
+ ids.add(item.id);
25
+ }
26
+ const selected = response.configOptions.find((item) => item.id === id);
27
+ if (!selected && !required)
28
+ return undefined;
29
+ if (selected?.type !== 'select' ||
30
+ !text(selected.currentValue) ||
31
+ !Array.isArray(selected.options) ||
32
+ selected.options.length > 256)
33
+ return invalid();
34
+ const options = [];
35
+ const values = new Set();
36
+ const groups = new Set();
37
+ const append = (item, group) => {
38
+ if (!record(item) ||
39
+ !text(item.value) ||
40
+ !text(item.name) ||
41
+ values.has(item.value) ||
42
+ (item.description != null && !text(item.description, 2048)) ||
43
+ options.length >= 256)
44
+ return invalid();
45
+ values.add(item.value);
46
+ options.push({
47
+ value: item.value,
48
+ name: item.name,
49
+ ...(typeof item.description === 'string' ? { description: item.description } : {}),
50
+ ...(group ? { group } : {}),
51
+ });
52
+ };
53
+ for (const item of selected.options) {
54
+ if (record(item) && 'options' in item) {
55
+ if (!text(item.group) ||
56
+ !text(item.name) ||
57
+ groups.has(item.group) ||
58
+ !Array.isArray(item.options) ||
59
+ item.options.length > 256)
60
+ return invalid();
61
+ groups.add(item.group);
62
+ for (const option of item.options)
63
+ append(option, item.name);
64
+ }
65
+ else
66
+ append(item);
67
+ }
68
+ if (!values.has(selected.currentValue))
69
+ return invalid();
70
+ return { currentValue: selected.currentValue, options };
71
+ }
72
+ export function requireMistralOption(response, id, value, confirmed = false) {
73
+ const option = readMistralSelect(response, id);
74
+ if (!option.options.some((item) => item.value === value) || (confirmed && option.currentValue !== value))
75
+ throw new Error(`Mistral Vibe ${id} selection is unavailable or overridden by the native profile; refresh native options`);
76
+ }
@@ -0,0 +1,361 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import * as acp from '@agentclientprotocol/sdk';
3
+ import { isWorkspacePermissionAllowed } from '../../../workspace-permission-policy-service.js';
4
+ import { createTurnLiveness } from '../../turn-liveness.js';
5
+ import { AGENT_NO_LONGER_RUNNING_TEXT } from '../types.js';
6
+ import { MISTRAL_CAPABILITIES } from './capabilities.js';
7
+ import { closeVibeAcpSession, connectVibeAcp, requestVibeAcp } from './client.js';
8
+ import { readMistralSelect, requireMistralOption } from './configuration.js';
9
+ import { createMistralEventMapper } from './event-mapper.js';
10
+ import { buildMistralOptions } from './options-builder.js';
11
+ import { createMistralServerRequests } from './server-requests.js';
12
+ import { spawnVibeAcp } from './spawn.js';
13
+ function receipt() {
14
+ let resolve;
15
+ let reject;
16
+ const promise = new Promise((yes, no) => {
17
+ resolve = yes;
18
+ reject = no;
19
+ });
20
+ void promise.catch(() => { });
21
+ return { promise, resolve, reject };
22
+ }
23
+ function record(value) {
24
+ return !!value && typeof value === 'object' && !Array.isArray(value);
25
+ }
26
+ function invalidResponse() {
27
+ throw new Error('Invalid Vibe ACP setup response');
28
+ }
29
+ function diagnostic(error, stage) {
30
+ const message = error instanceof Error ? error.message : '';
31
+ if (/missing.*api.?key|auth|credential|\b401\b|\b403\b/i.test(message))
32
+ return {
33
+ kind: 'error',
34
+ category: 'other',
35
+ code: 'acp_auth_required',
36
+ message: 'Mistral Vibe authentication is required; check the native Vibe authentication configuration for the server user.',
37
+ };
38
+ if (/quota|rate.?limit|\b429\b/i.test(message))
39
+ return {
40
+ kind: 'error',
41
+ category: 'quota',
42
+ code: 'acp_quota',
43
+ message: 'Mistral Vibe reported a quota or rate limit; check the provider limits before retrying.',
44
+ };
45
+ if (/^Mistral Vibe .*selection is unavailable/.test(message))
46
+ return { kind: 'error', category: 'other', code: 'acp_configuration_unavailable', message };
47
+ if (/Invalid Vibe ACP/i.test(message))
48
+ return {
49
+ kind: 'error',
50
+ category: 'parse_error',
51
+ code: 'acp_invalid_response',
52
+ message: 'Mistral Vibe returned an incompatible ACP response; ACP v1 and supported native modes are required.',
53
+ };
54
+ // resume_failed clears native identity and permits a fresh auto-loop conversation.
55
+ if (stage === 'load')
56
+ return {
57
+ kind: 'error',
58
+ category: 'other',
59
+ code: 'acp_load_failed',
60
+ message: 'Mistral Vibe could not load the saved session. No new conversation was started.',
61
+ };
62
+ return {
63
+ kind: 'error',
64
+ category: stage === 'connect' ? 'spawn_failed' : 'other',
65
+ code: `acp_${stage}_failed`,
66
+ message: `Mistral Vibe ${stage} failed. Check the native executable, configuration and provider connection before retrying.`,
67
+ };
68
+ }
69
+ export function createMistralVibeEngine() {
70
+ return {
71
+ id: 'mistral-vibe',
72
+ displayName: 'Mistral Vibe',
73
+ capabilities: MISTRAL_CAPABILITIES,
74
+ async start(options, onEvent) {
75
+ // All unsupported launch choices fail before runtime ownership exists.
76
+ const built = buildMistralOptions(options);
77
+ const runtime = spawnVibeAcp({ cwd: built.cwd, env: built.env });
78
+ const ready = receipt();
79
+ const accepted = receipt();
80
+ let connection;
81
+ let sessionId = options.resumeFromEngineSessionId;
82
+ let canClose = false;
83
+ let active = false;
84
+ let live = false;
85
+ let ended = false;
86
+ let physicallyClosed = false;
87
+ let mappedError = false;
88
+ let shutdownPromise;
89
+ const tools = new Set();
90
+ const liveness = createTurnLiveness({
91
+ timeoutMs: 120_000,
92
+ enabled: options.watchdogEnabled === true,
93
+ onTimeout: () => end('watchdog', {
94
+ kind: 'error',
95
+ category: 'other',
96
+ code: 'acp_watchdog',
97
+ message: 'Mistral Vibe stopped reporting foreground activity for this turn.',
98
+ }),
99
+ });
100
+ const emit = (event) => {
101
+ if (ended)
102
+ return;
103
+ if (event.kind === 'message:text' ||
104
+ event.kind === 'message:thinking' ||
105
+ event.kind === 'tool:call' ||
106
+ event.kind === 'tool:result') {
107
+ if (event.kind === 'tool:call')
108
+ tools.add(event.toolCallId);
109
+ if (event.kind === 'tool:result')
110
+ tools.delete(event.toolCallId);
111
+ liveness.setTimeoutMs(tools.size ? 30 * 60_000 : 120_000);
112
+ liveness.activity();
113
+ }
114
+ if (event.kind === 'error') {
115
+ mappedError = true;
116
+ // Typed native notices may include provider details; expose fixed actionable diagnostics.
117
+ if (!event.code && event.category !== 'parse_error')
118
+ event = diagnostic(new Error(event.message), 'prompt');
119
+ }
120
+ onEvent(event);
121
+ };
122
+ const mapper = createMistralEventMapper(randomUUID(), emit);
123
+ let requests;
124
+ const shutdown = () => {
125
+ if (!shutdownPromise) {
126
+ shutdownPromise = runtime.stop(connection && sessionId
127
+ ? () => closeVibeAcpSession(connection, { sessionId: sessionId, active, canClose })
128
+ : undefined);
129
+ void shutdownPromise.catch(() => { });
130
+ }
131
+ return shutdownPromise;
132
+ };
133
+ function end(reason, error) {
134
+ if (ended)
135
+ return;
136
+ liveness.stop();
137
+ requests?.cancelAll();
138
+ mapper.flush();
139
+ if (error)
140
+ emit(error);
141
+ ended = true;
142
+ const failure = new Error(error?.message ?? 'Mistral Vibe ended before initial prompt consumption was confirmed');
143
+ ready.reject(failure);
144
+ accepted.reject(failure);
145
+ if (reason === 'completed')
146
+ onEvent({ kind: 'turn:completed' });
147
+ onEvent({ kind: 'session:ended', reason, exitCode: reason === 'completed' ? 0 : null });
148
+ void shutdown();
149
+ }
150
+ const onClose = () => {
151
+ if (!ended)
152
+ end('error', diagnostic(new Error('Native transport closed'), active ? 'prompt' : 'setup'));
153
+ };
154
+ runtime.child.once('close', onClose);
155
+ void runtime.closed
156
+ .then(() => {
157
+ physicallyClosed = true;
158
+ onClose();
159
+ runtime.child.removeListener('close', onClose);
160
+ // SDK close cancels its receive reader; only do this after native EOF cleanup.
161
+ connection?.close();
162
+ })
163
+ .catch(() => { });
164
+ const run = async () => {
165
+ let stage = 'connect';
166
+ try {
167
+ connection = connectVibeAcp(runtime.child, {
168
+ sessionUpdate: ({ params }) => {
169
+ if (ended || !sessionId || params.sessionId !== sessionId)
170
+ return;
171
+ // Plans synthesize tool events; only native foreground signals prove prompt consumption.
172
+ const update = params.update;
173
+ if (live &&
174
+ (update.sessionUpdate === 'tool_call' ||
175
+ update.sessionUpdate === 'tool_call_update' ||
176
+ ((update.sessionUpdate === 'agent_message_chunk' || update.sessionUpdate === 'agent_thought_chunk') &&
177
+ update.content.type === 'text' &&
178
+ update.content.text.length > 0)))
179
+ accepted.resolve();
180
+ mapper.onUpdate(params);
181
+ if (live &&
182
+ [
183
+ 'agent_message_chunk',
184
+ 'agent_thought_chunk',
185
+ 'tool_call',
186
+ 'tool_call_update',
187
+ 'plan',
188
+ 'compaction_update',
189
+ ].includes(params.update.sessionUpdate))
190
+ liveness.activity();
191
+ },
192
+ requestPermission: (context) => live && !ended && requests ? requests.requestPermission(context) : { outcome: { outcome: 'cancelled' } },
193
+ createElicitation: (context) => live && !ended && requests ? requests.createElicitation(context) : { action: 'cancel' },
194
+ });
195
+ void connection.closed.then(onClose, onClose);
196
+ stage = 'initialize';
197
+ const initialized = await requestVibeAcp(connection, acp.methods.agent.initialize, {
198
+ protocolVersion: acp.PROTOCOL_VERSION,
199
+ clientCapabilities: { elicitation: { form: {} } },
200
+ });
201
+ if (ended)
202
+ return;
203
+ if (!record(initialized) ||
204
+ initialized.protocolVersion !== acp.PROTOCOL_VERSION ||
205
+ !record(initialized.agentCapabilities))
206
+ invalidResponse();
207
+ const capabilities = initialized.agentCapabilities;
208
+ if (capabilities.loadSession != null && typeof capabilities.loadSession !== 'boolean')
209
+ invalidResponse();
210
+ if (capabilities.sessionCapabilities != null && !record(capabilities.sessionCapabilities))
211
+ invalidResponse();
212
+ const close = capabilities.sessionCapabilities?.close;
213
+ if (close != null && !record(close))
214
+ invalidResponse();
215
+ canClose = close != null;
216
+ if (options.resumeFromEngineSessionId && capabilities.loadSession !== true)
217
+ invalidResponse();
218
+ stage = options.resumeFromEngineSessionId ? 'load' : 'setup';
219
+ const params = { cwd: built.cwd, mcpServers: built.mcpServers };
220
+ const session = options.resumeFromEngineSessionId
221
+ ? await requestVibeAcp(connection, acp.methods.agent.session.load, {
222
+ ...params,
223
+ sessionId: options.resumeFromEngineSessionId,
224
+ })
225
+ : await requestVibeAcp(connection, acp.methods.agent.session.new, params);
226
+ if (ended)
227
+ return;
228
+ if (!record(session))
229
+ invalidResponse();
230
+ if (!options.resumeFromEngineSessionId) {
231
+ if (!('sessionId' in session) || typeof session.sessionId !== 'string' || !session.sessionId.trim())
232
+ invalidResponse();
233
+ sessionId = session.sessionId;
234
+ }
235
+ if (!sessionId ||
236
+ !record(session.modes) ||
237
+ typeof session.modes.currentModeId !== 'string' ||
238
+ !Array.isArray(session.modes.availableModes) ||
239
+ !session.modes.availableModes.every((mode) => record(mode) && typeof mode.id === 'string') ||
240
+ !session.modes.availableModes.some((mode) => mode.id === built.modeId))
241
+ invalidResponse();
242
+ requireMistralOption(session, 'mode', built.modeId);
243
+ readMistralSelect(session, 'model', false);
244
+ requests = createMistralServerRequests({
245
+ sessionId,
246
+ readOnly: false,
247
+ emit,
248
+ getTool: mapper.getTool,
249
+ canonicalToolCallId: mapper.canonicalToolCallId,
250
+ autoApprove: (toolName, payload) => isWorkspacePermissionAllowed(options.workspaceId, { engine: 'mistral-vibe', toolName, payload }),
251
+ onPendingChange: (count) => {
252
+ if (count > 0)
253
+ liveness.pause();
254
+ else if (!ended)
255
+ liveness.resume();
256
+ },
257
+ });
258
+ stage = 'configuration';
259
+ let configured = await requestVibeAcp(connection, acp.methods.agent.session.setConfigOption, {
260
+ sessionId,
261
+ configId: 'mode',
262
+ value: built.modeId,
263
+ });
264
+ if (ended)
265
+ return;
266
+ requireMistralOption(configured, 'mode', built.modeId, true);
267
+ for (const [configId, value] of [
268
+ ['model', options.model],
269
+ ['thinking', options.effort],
270
+ ]) {
271
+ if (!value || value === 'auto')
272
+ continue;
273
+ requireMistralOption(configured, configId, value);
274
+ configured = await requestVibeAcp(connection, acp.methods.agent.session.setConfigOption, {
275
+ sessionId,
276
+ configId,
277
+ value,
278
+ });
279
+ if (ended)
280
+ return;
281
+ requireMistralOption(configured, configId, value, true);
282
+ requireMistralOption(configured, 'mode', built.modeId, true);
283
+ }
284
+ if (options.model && options.model !== 'auto')
285
+ requireMistralOption(configured, 'model', options.model, true);
286
+ const model = readMistralSelect(configured, 'model', false)?.currentValue;
287
+ try {
288
+ options.onPromptPrepared?.(built.koboInstructions);
289
+ }
290
+ catch {
291
+ console.warn('[mistral-vibe] Could not persist prepared launch instructions');
292
+ }
293
+ if (ended)
294
+ return;
295
+ emit({ kind: 'session:started', engineSessionId: sessionId, ...(model ? { model } : {}) });
296
+ if (ended)
297
+ return;
298
+ stage = 'prompt';
299
+ active = true;
300
+ live = true;
301
+ mapper.beginLive();
302
+ liveness.start();
303
+ const pending = requestVibeAcp(connection, acp.methods.agent.session.prompt, {
304
+ sessionId,
305
+ prompt: built.prompt,
306
+ });
307
+ ready.resolve();
308
+ const result = await pending;
309
+ if (ended)
310
+ return;
311
+ active = false;
312
+ mapper.finish(result);
313
+ if (result.stopReason === 'end_turn' && !mappedError) {
314
+ accepted.resolve();
315
+ end('completed');
316
+ }
317
+ else if (result.stopReason === 'cancelled') {
318
+ end('error', {
319
+ kind: 'error',
320
+ category: 'other',
321
+ code: 'acp_cancelled',
322
+ message: 'Mistral Vibe cancelled the prompt without a local stop request.',
323
+ });
324
+ }
325
+ else
326
+ end('error');
327
+ }
328
+ catch (error) {
329
+ if (!ended)
330
+ end('error', diagnostic(error, stage));
331
+ }
332
+ };
333
+ // start() must return ownership even when asynchronous setup/cleanup fails.
334
+ void run();
335
+ return {
336
+ ready: ready.promise,
337
+ initialPromptAccepted: accepted.promise,
338
+ closed: runtime.closed,
339
+ pid: runtime.child.pid,
340
+ get engineSessionId() {
341
+ return sessionId;
342
+ },
343
+ isAlive: () => !physicallyClosed,
344
+ sendMessage() {
345
+ if (physicallyClosed)
346
+ throw new Error(`Mistral Vibe ${AGENT_NO_LONGER_RUNNING_TEXT}`);
347
+ throw new Error('Mistral Vibe live message delivery is unsupported; wait for native closure and resume the conversation');
348
+ },
349
+ interrupt() {
350
+ end('killed');
351
+ },
352
+ stop() {
353
+ end('killed');
354
+ return shutdown();
355
+ },
356
+ hasPendingUserInput: (id) => requests?.hasPendingUserInput(id) ?? false,
357
+ resolvePendingUserInput: (id, response) => requests?.resolvePendingUserInput(id, response) ?? false,
358
+ };
359
+ },
360
+ };
361
+ }