@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,297 @@
1
+ import { createStreamingBatcher } from '../../streaming-batcher.js';
2
+ function object(value) {
3
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
4
+ ? value
5
+ : undefined;
6
+ }
7
+ function validTokens(inputTokens, outputTokens) {
8
+ return typeof inputTokens === 'number' &&
9
+ Number.isSafeInteger(inputTokens) &&
10
+ inputTokens >= 0 &&
11
+ typeof outputTokens === 'number' &&
12
+ Number.isSafeInteger(outputTokens) &&
13
+ outputTokens >= 0
14
+ ? { inputTokens, outputTokens }
15
+ : undefined;
16
+ }
17
+ function validCost(value) {
18
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
19
+ }
20
+ function toolName(tool) {
21
+ const name = tool._meta?.tool_name;
22
+ return typeof name === 'string' && name.trim() ? name : tool.name?.trim() || undefined;
23
+ }
24
+ const BRAINSTORM_COMPLETE = '[BRAINSTORM_COMPLETE]';
25
+ /** One owned session and prompt per launch; the engine filters native session IDs before dispatch. */
26
+ export function createMistralEventMapper(launchId, emit) {
27
+ const scoped = (kind, id) => `vibe:${encodeURIComponent(launchId)}:${kind}:${encodeURIComponent(id)}`;
28
+ const canonicalToolCallId = (nativeId) => scoped('tool', nativeId);
29
+ const batcher = createStreamingBatcher(emit);
30
+ const tools = new Map();
31
+ const compactions = new Map();
32
+ let live = false;
33
+ let finished = false;
34
+ let messageId;
35
+ let messageKind;
36
+ let messageSequence = 0;
37
+ let planSequence = 0;
38
+ let brainstormTail = '';
39
+ let brainstormEmitted = false;
40
+ let baseline;
41
+ let baselineCost;
42
+ let latest;
43
+ let latestCost;
44
+ let sawLiveTokens = false;
45
+ let sawLiveCost = false;
46
+ let tokensReset = false;
47
+ let costReset = false;
48
+ function captureTokens(input, output) {
49
+ const tokens = validTokens(input, output);
50
+ if (!tokens) {
51
+ if (live)
52
+ tokensReset = true;
53
+ else
54
+ latest = undefined;
55
+ return;
56
+ }
57
+ if (live) {
58
+ sawLiveTokens = true;
59
+ if (latest && (tokens.inputTokens < latest.inputTokens || tokens.outputTokens < latest.outputTokens))
60
+ tokensReset = true;
61
+ }
62
+ latest = tokens;
63
+ }
64
+ function captureUsage(update) {
65
+ const meta = update._meta;
66
+ if (meta && ('promptTokens' in meta || 'completionTokens' in meta))
67
+ captureTokens(meta.promptTokens, meta.completionTokens);
68
+ if (update.cost) {
69
+ if (update.cost.currency !== 'USD' || !validCost(update.cost.amount)) {
70
+ if (live)
71
+ costReset = true;
72
+ else
73
+ latestCost = undefined;
74
+ }
75
+ else {
76
+ if (live) {
77
+ sawLiveCost = true;
78
+ if (latestCost !== undefined && update.cost.amount < latestCost)
79
+ costReset = true;
80
+ }
81
+ latestCost = update.cost.amount;
82
+ }
83
+ }
84
+ }
85
+ function closeMessage() {
86
+ if (messageId)
87
+ batcher.push({ kind: 'message:end', messageId });
88
+ messageId = undefined;
89
+ messageKind = undefined;
90
+ brainstormTail = '';
91
+ }
92
+ function emitTool(tracked, force = false) {
93
+ if ((!tracked.callReady && !force) || tracked.resultEmitted)
94
+ return;
95
+ const { tool } = tracked;
96
+ const toolCallId = canonicalToolCallId(tool.toolCallId);
97
+ if (!tracked.callEmitted) {
98
+ tracked.callEmitted = true;
99
+ batcher.push({
100
+ kind: 'tool:call',
101
+ messageId: '',
102
+ toolCallId,
103
+ name: toolName(tool) ?? (tool.title || 'Tool'),
104
+ input: tool.rawInput,
105
+ });
106
+ }
107
+ if (tool.status === 'completed' || tool.status === 'failed') {
108
+ tracked.resultEmitted = true;
109
+ const output = tool.content?.length || tool.locations?.length
110
+ ? {
111
+ ...(tool.rawOutput !== undefined ? { rawOutput: tool.rawOutput } : {}),
112
+ ...(tool.content?.length ? { content: tool.content } : {}),
113
+ ...(tool.locations?.length ? { locations: tool.locations } : {}),
114
+ }
115
+ : (tool.rawOutput ?? null);
116
+ batcher.push({ kind: 'tool:result', toolCallId, output, isError: tool.status === 'failed' });
117
+ }
118
+ }
119
+ function mergeTool(update) {
120
+ let tracked = tools.get(update.toolCallId);
121
+ if (!tracked) {
122
+ tracked = {
123
+ tool: { toolCallId: update.toolCallId, title: '' },
124
+ hasMetadata: false,
125
+ callReady: false,
126
+ callEmitted: false,
127
+ resultEmitted: false,
128
+ };
129
+ tools.set(update.toolCallId, tracked);
130
+ }
131
+ if (tracked.resultEmitted)
132
+ return;
133
+ const tool = tracked.tool;
134
+ const terminal = tool.status === 'completed' || tool.status === 'failed';
135
+ if (update.title != null)
136
+ tool.title = update.title;
137
+ if (update.name != null)
138
+ tool.name = update.name;
139
+ if (update.kind != null)
140
+ tool.kind = update.kind;
141
+ if (update.status != null && !terminal)
142
+ tool.status = update.status;
143
+ if (update.rawInput !== undefined)
144
+ tool.rawInput = update.rawInput;
145
+ if (update.rawOutput !== undefined && (!terminal || tool.rawOutput === undefined))
146
+ tool.rawOutput = update.rawOutput;
147
+ if (update.content !== undefined)
148
+ tool.content = update.content ?? undefined;
149
+ if (update.locations !== undefined)
150
+ tool.locations = update.locations ?? undefined;
151
+ if (update._meta !== undefined)
152
+ tool._meta = update._meta === null ? undefined : { ...tool._meta, ...update._meta };
153
+ tracked.hasMetadata ||= update.title != null || toolName(tool) !== undefined;
154
+ tracked.callReady ||=
155
+ update.sessionUpdate === 'tool_call' || (toolName(tool) !== undefined && tool.rawInput !== undefined);
156
+ emitTool(tracked);
157
+ }
158
+ function flush() {
159
+ if (!live)
160
+ return;
161
+ closeMessage();
162
+ for (const tracked of tools.values())
163
+ emitTool(tracked, true);
164
+ batcher.close();
165
+ }
166
+ return {
167
+ canonicalToolCallId,
168
+ getTool(nativeId) {
169
+ const tracked = tools.get(nativeId);
170
+ return tracked?.hasMetadata ? tracked.tool : undefined;
171
+ },
172
+ beginLive(explicit) {
173
+ if (live || finished)
174
+ return;
175
+ baseline = explicit ? validTokens(explicit.inputTokens, explicit.outputTokens) : latest;
176
+ baselineCost = explicit ? (validCost(explicit.costUsd) ? explicit.costUsd : undefined) : latestCost;
177
+ latest = baseline;
178
+ latestCost = baselineCost;
179
+ live = true;
180
+ },
181
+ onUpdate({ update }) {
182
+ if (finished)
183
+ return;
184
+ if (update.sessionUpdate === 'usage_update') {
185
+ captureUsage(update);
186
+ return;
187
+ }
188
+ if (!live)
189
+ return;
190
+ switch (update.sessionUpdate) {
191
+ case 'agent_message_chunk':
192
+ case 'agent_thought_chunk': {
193
+ if (update.content.type !== 'text')
194
+ return;
195
+ if (messageKind && messageKind !== update.sessionUpdate)
196
+ closeMessage();
197
+ const nextId = update.messageId
198
+ ? scoped('message', update.messageId)
199
+ : (messageId ?? scoped('anonymous-message', String(++messageSequence)));
200
+ if (messageId && messageId !== nextId)
201
+ closeMessage();
202
+ messageId = nextId;
203
+ messageKind = update.sessionUpdate;
204
+ if (update.sessionUpdate === 'agent_thought_chunk') {
205
+ batcher.close();
206
+ batcher.push({ kind: 'message:thinking', messageId, text: update.content.text, streaming: true });
207
+ }
208
+ else {
209
+ batcher.push({ kind: 'message:text', messageId, text: update.content.text, streaming: true });
210
+ if (!brainstormEmitted) {
211
+ const text = brainstormTail + update.content.text;
212
+ if (text.includes(BRAINSTORM_COMPLETE)) {
213
+ brainstormEmitted = true;
214
+ batcher.push({ kind: 'session:brainstorm-complete' });
215
+ }
216
+ brainstormTail = text.slice(-(BRAINSTORM_COMPLETE.length - 1));
217
+ }
218
+ }
219
+ return;
220
+ }
221
+ case 'tool_call':
222
+ case 'tool_call_update':
223
+ closeMessage();
224
+ mergeTool(update);
225
+ return;
226
+ case 'plan': {
227
+ closeMessage();
228
+ const toolCallId = scoped('plan', String(++planSequence));
229
+ batcher.push({
230
+ kind: 'tool:call',
231
+ messageId: '',
232
+ toolCallId,
233
+ name: 'TodoWrite',
234
+ input: { todos: update.entries.map(({ content, status }) => ({ content, status })) },
235
+ });
236
+ batcher.push({ kind: 'tool:result', toolCallId, output: null, isError: false });
237
+ return;
238
+ }
239
+ case 'notice':
240
+ if (update.severity === 'error')
241
+ batcher.push({ kind: 'error', category: 'other', message: update.description || update.title });
242
+ return;
243
+ case 'compaction_update': {
244
+ const previous = compactions.get(update.compactionId);
245
+ if (previous === update.status || (previous && previous !== 'in_progress'))
246
+ return;
247
+ const compactionId = scoped('compaction', update.compactionId);
248
+ if (update.status === 'in_progress') {
249
+ compactions.set(update.compactionId, update.status);
250
+ batcher.push({ kind: 'session:compacting', active: true, compactionId });
251
+ }
252
+ else if (['completed', 'failed', 'cancelled'].includes(update.status)) {
253
+ compactions.set(update.compactionId, update.status);
254
+ batcher.push({ kind: 'session:compacting', active: false, compactionId });
255
+ if (update.status === 'completed')
256
+ batcher.push({ kind: 'session:compacted', compactionId });
257
+ }
258
+ return;
259
+ }
260
+ }
261
+ },
262
+ finish(result) {
263
+ if (!live || finished)
264
+ return;
265
+ // Stable SDK responses are unchecked; native cumulative usage is not in PromptResponse's type.
266
+ const response = object(result);
267
+ const nativeUsage = object(response?.usage);
268
+ if (nativeUsage && ('inputTokens' in nativeUsage || 'outputTokens' in nativeUsage))
269
+ captureTokens(nativeUsage.inputTokens, nativeUsage.outputTokens);
270
+ flush();
271
+ finished = true;
272
+ if (baseline && latest && sawLiveTokens && !tokensReset) {
273
+ batcher.push({
274
+ kind: 'usage',
275
+ inputTokens: latest.inputTokens - baseline.inputTokens,
276
+ outputTokens: latest.outputTokens - baseline.outputTokens,
277
+ ...(baselineCost !== undefined && latestCost !== undefined && sawLiveCost && !costReset
278
+ ? { costUsd: latestCost - baselineCost }
279
+ : {}),
280
+ });
281
+ }
282
+ const stopReason = response?.stopReason;
283
+ if (stopReason === 'max_tokens' || stopReason === 'max_turn_requests' || stopReason === 'refusal') {
284
+ batcher.push({
285
+ kind: 'error',
286
+ category: 'other',
287
+ message: `Vibe prompt stopped: ${stopReason}`,
288
+ code: `acp_${stopReason}`,
289
+ });
290
+ }
291
+ else if (stopReason !== 'end_turn' && stopReason !== 'cancelled') {
292
+ batcher.push({ kind: 'error', category: 'parse_error', message: 'Invalid Vibe ACP prompt stop reason' });
293
+ }
294
+ },
295
+ flush,
296
+ };
297
+ }
@@ -0,0 +1,35 @@
1
+ import { resolve } from 'node:path';
2
+ import { buildKoboLaunchInstructions, getKoboPromptCapabilities, } from '../../../../../shared/launch-instructions.js';
3
+ import { assertMistralSelection, MISTRAL_MODES } from './configuration.js';
4
+ export function buildMistralOptions(options) {
5
+ if (options.resumeFromEngineSessionId !== undefined && !options.resumeFromEngineSessionId.trim())
6
+ throw new Error('Mistral Vibe requires a nonempty saved session identity');
7
+ const permissionMode = options.agentPermissionMode ?? 'interactive';
8
+ const capabilities = getKoboPromptCapabilities(options.mcpServers?.find((server) => server.name === 'kobo-tasks')?.env);
9
+ if (permissionMode === 'plan' || options.readOnly || capabilities.handoff)
10
+ throw new Error('Mistral Vibe does not support Plan or read-only launches');
11
+ assertMistralSelection(options.model, 'model');
12
+ assertMistralSelection(options.effort, 'thinking');
13
+ if (!Object.hasOwn(MISTRAL_MODES, permissionMode))
14
+ throw new Error('Unsupported Mistral Vibe permission mode');
15
+ const modeId = MISTRAL_MODES[permissionMode];
16
+ const koboInstructions = buildKoboLaunchInstructions({
17
+ engine: 'mistral-vibe',
18
+ permissionMode,
19
+ autoLoop: options.autoLoop,
20
+ ...capabilities,
21
+ });
22
+ return {
23
+ cwd: resolve(options.workingDir),
24
+ modeId,
25
+ koboInstructions,
26
+ mcpServers: (options.mcpServers ?? []).map((server) => ({
27
+ name: server.name,
28
+ command: server.command,
29
+ args: [...server.args],
30
+ env: Object.entries(server.env).map(([name, value]) => ({ name, value })),
31
+ })),
32
+ prompt: [{ type: 'text', text: `${koboInstructions.promptPrefix}\n\n${options.prompt}` }],
33
+ env: { ...process.env, ...options.env },
34
+ };
35
+ }
@@ -0,0 +1,278 @@
1
+ import { isOperationApprovalAvailable } from '../../../workspace-permission-policy-service.js';
2
+ import { PendingUserInputValidationError } from '../types.js';
3
+ function record(value) {
4
+ return !!value && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+ function onlyKeys(value, keys) {
7
+ return Object.keys(value).every((key) => keys.includes(key));
8
+ }
9
+ function text(value, max = 4000) {
10
+ return typeof value === 'string' && value.length <= max;
11
+ }
12
+ function bound(value, fallback, ceiling) {
13
+ if (value == null)
14
+ return fallback;
15
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= ceiling ? value : undefined;
16
+ }
17
+ function choices(schema, titledKey) {
18
+ if (schema.enum != null && schema[titledKey] != null)
19
+ return undefined;
20
+ let options;
21
+ if (Array.isArray(schema.enum) && schema.enum.every((value) => text(value, 2000))) {
22
+ options = schema.enum.map((label) => ({ label }));
23
+ }
24
+ else if (Array.isArray(schema[titledKey])) {
25
+ const values = schema[titledKey];
26
+ if (!values.every((v) => record(v) &&
27
+ onlyKeys(v, ['const', 'title', 'description', '_meta']) &&
28
+ text(v.const, 2000) &&
29
+ text(v.title, 2000) &&
30
+ (v.description == null || text(v.description))))
31
+ return undefined;
32
+ options = values.map((v) => ({ label: v.const, description: (v.description ?? v.title) }));
33
+ }
34
+ else
35
+ return undefined;
36
+ if (!options.length || options.length > 32 || new Set(options.map((o) => o.label)).size !== options.length)
37
+ return undefined;
38
+ return options;
39
+ }
40
+ function formFields(schema) {
41
+ if (!record(schema) ||
42
+ !onlyKeys(schema, ['type', 'title', 'description', 'properties', 'required', '_meta']) ||
43
+ (schema.type != null && schema.type !== 'object') ||
44
+ !record(schema.properties))
45
+ return undefined;
46
+ const entries = Object.entries(schema.properties);
47
+ if (!entries.length || entries.length > 20)
48
+ return undefined;
49
+ const required = schema.required ?? [];
50
+ if (!Array.isArray(required) ||
51
+ required.some((key) => typeof key !== 'string' || !Object.hasOwn(schema.properties, key)) ||
52
+ new Set(required).size !== required.length)
53
+ return undefined;
54
+ const fields = [];
55
+ for (const [id, property] of entries) {
56
+ if (!id ||
57
+ !text(id, 200) ||
58
+ !record(property) ||
59
+ (property.title != null && !text(property.title)) ||
60
+ (property.description != null && !text(property.description)))
61
+ return undefined;
62
+ const multiSelect = property.type === 'array';
63
+ if (property.type !== 'string' && !multiSelect)
64
+ return undefined;
65
+ if (!onlyKeys(property, multiSelect
66
+ ? ['type', 'title', 'description', 'items', 'minItems', 'maxItems', 'default', '_meta']
67
+ : [
68
+ 'type',
69
+ 'title',
70
+ 'description',
71
+ 'minLength',
72
+ 'maxLength',
73
+ 'enum',
74
+ 'oneOf',
75
+ 'default',
76
+ 'pattern',
77
+ 'format',
78
+ '_meta',
79
+ ]))
80
+ return undefined;
81
+ // Pattern/format constraints need native validation support before they can be rendered safely.
82
+ if (property.pattern != null || property.format != null)
83
+ return undefined;
84
+ let options = [];
85
+ if (multiSelect) {
86
+ if (!record(property.items) ||
87
+ !onlyKeys(property.items, ['type', 'enum', 'anyOf', '_meta']) ||
88
+ (property.items.type != null && property.items.type !== 'string'))
89
+ return undefined;
90
+ const parsed = choices(property.items, 'anyOf');
91
+ if (!parsed)
92
+ return undefined;
93
+ options = parsed;
94
+ }
95
+ else if (property.enum != null || property.oneOf != null) {
96
+ const parsed = choices(property, 'oneOf');
97
+ if (!parsed)
98
+ return undefined;
99
+ options = parsed;
100
+ }
101
+ const min = bound(multiSelect ? property.minItems : property.minLength, 0, multiSelect ? options.length : 10000);
102
+ const max = bound(multiSelect ? property.maxItems : property.maxLength, multiSelect ? options.length : 10000, multiSelect ? options.length : 10000);
103
+ if (min === undefined || max === undefined || min > max)
104
+ return undefined;
105
+ fields.push({
106
+ id,
107
+ question: (property.description ?? property.title ?? id),
108
+ ...(property.title ? { header: property.title } : {}),
109
+ options,
110
+ required: required.includes(id),
111
+ freeText: !multiSelect && !options.length,
112
+ multiSelect,
113
+ isOther: false,
114
+ min,
115
+ max,
116
+ });
117
+ }
118
+ return fields;
119
+ }
120
+ function formAnswer(fields, response) {
121
+ if (!record(response.answers) || (response.selections !== undefined && !record(response.selections)))
122
+ return undefined;
123
+ if (Object.keys(response.answers).some((id) => !fields.some((field) => field.id === id)) ||
124
+ Object.keys(response.selections ?? {}).some((id) => !fields.some((field) => field.id === id && field.multiSelect)))
125
+ return undefined;
126
+ const content = [];
127
+ for (const field of fields) {
128
+ const source = field.multiSelect ? response.selections : response.answers;
129
+ if (!source || !Object.hasOwn(source, field.id)) {
130
+ if (field.required || Object.hasOwn(response.answers, field.id))
131
+ return undefined;
132
+ continue;
133
+ }
134
+ const value = source[field.id];
135
+ if (field.multiSelect) {
136
+ if (!Array.isArray(value) ||
137
+ value.length < field.min ||
138
+ value.length > field.max ||
139
+ new Set(value).size !== value.length ||
140
+ value.some((v) => !text(v, 2000) || !field.options.some((o) => o.label === v)))
141
+ return undefined;
142
+ content.push([field.id, [...value]]);
143
+ }
144
+ else {
145
+ if (!text(value, field.max) ||
146
+ value.length < field.min ||
147
+ (!field.freeText && !field.options.some((o) => o.label === value)))
148
+ return undefined;
149
+ content.push([field.id, value]);
150
+ }
151
+ }
152
+ return { action: 'accept', content: Object.fromEntries(content) };
153
+ }
154
+ export function createMistralServerRequests(args) {
155
+ const pending = new Map();
156
+ const seen = new Set();
157
+ let closed = false;
158
+ function wait(key, signal, cancelled, answer, event) {
159
+ return new Promise((resolve) => {
160
+ function settle(result) {
161
+ if (!pending.delete(key))
162
+ return;
163
+ signal.removeEventListener('abort', cancel);
164
+ resolve(result);
165
+ args.onPendingChange?.(pending.size);
166
+ }
167
+ const cancel = () => settle(cancelled);
168
+ pending.set(key, {
169
+ cancel,
170
+ answer: (response) => {
171
+ const result = answer(response);
172
+ if (result === undefined)
173
+ throw new PendingUserInputValidationError();
174
+ settle(result);
175
+ return true;
176
+ },
177
+ });
178
+ seen.add(key);
179
+ signal.addEventListener('abort', cancel, { once: true });
180
+ args.onPendingChange?.(pending.size);
181
+ if (pending.has(key))
182
+ args.emit(event);
183
+ });
184
+ }
185
+ function unavailable(key, signal) {
186
+ return closed || signal.aborted || seen.has(key) || pending.size >= 32 || seen.size >= 4096;
187
+ }
188
+ return {
189
+ requestPermission({ params, signal }) {
190
+ const cancelled = { outcome: { outcome: 'cancelled' } };
191
+ if (closed || signal.aborted || args.readOnly || params.sessionId !== args.sessionId)
192
+ return cancelled;
193
+ const tool = args.getTool(params.toolCall.toolCallId);
194
+ const toolName = tool?._meta?.tool_name;
195
+ if (!tool || typeof toolName !== 'string' || !toolName.trim() || toolName.length > 200)
196
+ return cancelled;
197
+ const key = args.canonicalToolCallId(params.toolCall.toolCallId);
198
+ if (unavailable(key, signal))
199
+ return cancelled;
200
+ const offered = params.options;
201
+ if (!offered.length ||
202
+ offered.some((option) => !option.optionId) ||
203
+ new Set(offered.map((option) => option.optionId)).size !== offered.length)
204
+ return cancelled;
205
+ const allow = offered.find((option) => option.kind === 'allow_once');
206
+ const deny = offered.find((option) => option.kind === 'reject_once');
207
+ if (!allow || !deny)
208
+ return cancelled;
209
+ const payload = {
210
+ rawInput: tool.rawInput ?? null,
211
+ effectKind: tool._meta?.effect_kind ?? null,
212
+ kind: tool.kind ?? null,
213
+ operationApprovalAvailable: false,
214
+ };
215
+ payload.operationApprovalAvailable = isOperationApprovalAvailable({
216
+ engine: 'mistral-vibe',
217
+ toolName,
218
+ payload: { ...payload, operationApprovalAvailable: true },
219
+ });
220
+ const selected = (optionId) => ({ outcome: { outcome: 'selected', optionId } });
221
+ const approved = args.autoApprove?.(toolName, payload);
222
+ if (closed || signal.aborted)
223
+ return cancelled;
224
+ if (approved) {
225
+ seen.add(key);
226
+ return selected(allow.optionId);
227
+ }
228
+ return wait(key, signal, cancelled, (response) => response.kind === 'permission-allow'
229
+ ? selected(allow.optionId)
230
+ : response.kind === 'permission-deny'
231
+ ? selected(deny.optionId)
232
+ : undefined, { kind: 'session:user-input-requested', requestKind: 'permission', toolCallId: key, toolName, payload });
233
+ },
234
+ createElicitation({ params, signal, requestId }) {
235
+ if (closed || signal.aborted || args.readOnly)
236
+ return { action: 'cancel' };
237
+ if (params.mode !== 'form' ||
238
+ !('sessionId' in params) ||
239
+ params.sessionId !== args.sessionId ||
240
+ !text(params.message))
241
+ return { action: 'decline' };
242
+ const fields = formFields(params.requestedSchema);
243
+ if (!fields)
244
+ return { action: 'decline' };
245
+ const nativeId = typeof params.toolCallId === 'string' ? params.toolCallId : '';
246
+ const key = `${args.canonicalToolCallId(nativeId)}:elicitation:${typeof requestId}:${encodeURIComponent(String(requestId))}`;
247
+ if (unavailable(key, signal))
248
+ return { action: 'decline' };
249
+ return wait(key, signal, { action: 'cancel' }, (response) => response.kind === 'question-cancel'
250
+ ? { action: 'cancel' }
251
+ : response.kind === 'question'
252
+ ? formAnswer(fields, response)
253
+ : undefined, {
254
+ kind: 'session:user-input-requested',
255
+ requestKind: 'question',
256
+ toolCallId: key,
257
+ toolName: 'AskUserQuestion',
258
+ payload: {
259
+ questions: fields.map(({ min, max, ...field }) => ({
260
+ ...field,
261
+ ...(field.multiSelect ? { minItems: min, maxItems: max } : { minLength: min, maxLength: max }),
262
+ })),
263
+ },
264
+ });
265
+ },
266
+ hasPendingUserInput(toolCallId) {
267
+ return pending.has(toolCallId);
268
+ },
269
+ resolvePendingUserInput(toolCallId, response) {
270
+ return pending.get(toolCallId)?.answer(response) ?? false;
271
+ },
272
+ cancelAll() {
273
+ closed = true;
274
+ for (const entry of pending.values())
275
+ entry.cancel();
276
+ },
277
+ };
278
+ }