@loicngr/kobo 2.1.29 → 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 (160) hide show
  1. package/AGENTS.md +59 -10
  2. package/CHANGELOG.md +16 -1
  3. package/CONFIGURATION.md +114 -14
  4. package/CONTRIBUTING.md +1 -1
  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/codex/capabilities.js +2 -0
  19. package/dist/server/services/agent/engines/codex/engine.js +3 -0
  20. package/dist/server/services/agent/engines/configuration.js +14 -0
  21. package/dist/server/services/agent/engines/mistral-vibe/capabilities.js +13 -0
  22. package/dist/server/services/agent/engines/mistral-vibe/client.js +64 -0
  23. package/dist/server/services/agent/engines/mistral-vibe/configuration.js +76 -0
  24. package/dist/server/services/agent/engines/mistral-vibe/engine.js +361 -0
  25. package/dist/server/services/agent/engines/mistral-vibe/event-mapper.js +297 -0
  26. package/dist/server/services/agent/engines/mistral-vibe/options-builder.js +35 -0
  27. package/dist/server/services/agent/engines/mistral-vibe/server-requests.js +278 -0
  28. package/dist/server/services/agent/engines/mistral-vibe/spawn.js +176 -0
  29. package/dist/server/services/agent/engines/registry.js +2 -0
  30. package/dist/server/services/agent/engines/types.js +6 -0
  31. package/dist/server/services/agent/orchestrator.js +88 -25
  32. package/dist/server/services/agent/session-controller.js +5 -0
  33. package/dist/server/services/auto-loop-final-review-service.js +10 -3
  34. package/dist/server/services/environment-check-service.js +6 -1
  35. package/dist/server/services/memory-service.js +1 -1
  36. package/dist/server/services/mistral-options-service.js +145 -0
  37. package/dist/server/services/pr-watcher-service.js +40 -11
  38. package/dist/server/services/review-service.js +33 -9
  39. package/dist/server/services/session-handoff-service.js +28 -2
  40. package/dist/server/services/settings-defaults.js +1 -0
  41. package/dist/server/services/settings-service.js +19 -1
  42. package/dist/server/services/workspace-creation-mcp-service.js +50 -8
  43. package/dist/server/services/workspace-dialogue-service.js +2 -0
  44. package/dist/server/services/workspace-message-service.js +19 -1
  45. package/dist/server/services/workspace-permission-policy-service.js +24 -0
  46. package/dist/server/utils/bounded-process.js +1 -1
  47. package/dist/shared/launch-instructions.js +18 -7
  48. package/dist/shared/memory.js +1 -1
  49. package/dist/shared/mistral-models.js +9 -0
  50. package/dist/shared/mistral-options.js +1 -0
  51. package/dist/shared/question-selections.js +16 -0
  52. package/dist/shared/workspace-creation-tools.js +8 -2
  53. package/dist/shared/workspace-dialogue-tools.js +15 -4
  54. package/docs/deployment-vps.md +10 -3
  55. package/docs/getting-started.md +3 -2
  56. package/docs/troubleshooting.md +1 -1
  57. package/package.json +7 -1
  58. package/src/client/dist/pwa/assets/ActivityFeed-C5stmHXP.js +2 -0
  59. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CIDSX32d.css +1 -0
  60. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-DG32W_0G.js +1 -0
  61. package/src/client/dist/pwa/assets/{ChangelogPage-C5KjAOMG.js → ChangelogPage-_vItY8vA.js} +1 -1
  62. package/src/client/dist/pwa/assets/{ComparisonPanel-XXyzPEzW.js → ComparisonPanel-B3BMzQH0.js} +1 -1
  63. package/src/client/dist/pwa/assets/CreatePage-FlGrfmjX.js +2 -0
  64. package/src/client/dist/pwa/assets/CreatePage-oh8ihVC4.css +1 -0
  65. package/src/client/dist/pwa/assets/{DashboardPage-DyhUu26C.js → DashboardPage-lJGwWQYQ.js} +1 -1
  66. package/src/client/dist/pwa/assets/DiffViewer-DIaQH3lA.js +8 -0
  67. package/src/client/dist/pwa/assets/{DocumentsPanel-ItndGxeB.js → DocumentsPanel-Ct-nm4fX.js} +1 -1
  68. package/src/client/dist/pwa/assets/{DrawerToggleButton-DfzS_CD8.js → DrawerToggleButton-M5qv4Io-.js} +1 -1
  69. package/src/client/dist/pwa/assets/GitPanel-BaHCgb3p.js +3 -0
  70. package/src/client/dist/pwa/assets/GitPanel-C8vw2qvW.css +1 -0
  71. package/src/client/dist/pwa/assets/HealthPage-DR3wa6iD.js +1 -0
  72. package/src/client/dist/pwa/assets/HealthPage-Dbn4J_DT.css +1 -0
  73. package/src/client/dist/pwa/assets/{MainLayout-DoAEgANU.css → MainLayout-CHi8qP1D.css} +1 -1
  74. package/src/client/dist/pwa/assets/MainLayout-WJabICsA.js +2 -0
  75. package/src/client/dist/pwa/assets/{MemoryPanel-CZzI7_nY.js → MemoryPanel-CRpWdfc0.js} +1 -1
  76. package/src/client/dist/pwa/assets/{MemoryProposalList-DqNTycY1.js → MemoryProposalList-DGz3lDbL.js} +1 -1
  77. package/src/client/dist/pwa/assets/{QBtnDropdown-D7ml7zHo.js → QBtnDropdown-tt8rUNNR.js} +1 -1
  78. package/src/client/dist/pwa/assets/{QExpansionItem-BVb067oc.js → QExpansionItem-BkrI3UPL.js} +1 -1
  79. package/src/client/dist/pwa/assets/{QScrollArea-CUDWkEHh.js → QScrollArea-BvgnY-oA.js} +1 -1
  80. package/src/client/dist/pwa/assets/QSelect-BtZB1wrB.js +1 -0
  81. package/src/client/dist/pwa/assets/{QTooltip-B5QJIU3p.js → QTooltip-CDH6p745.js} +1 -1
  82. package/src/client/dist/pwa/assets/{SchedulePanel-IJOcrHgk.js → SchedulePanel-C2rWlh7l.js} +1 -1
  83. package/src/client/dist/pwa/assets/{SearchPage-BpTRWtne.js → SearchPage-BFd4NQwJ.js} +1 -1
  84. package/src/client/dist/pwa/assets/{SettingsPage-B4rmd2WX.css → SettingsPage-Bm0TcarK.css} +1 -1
  85. package/src/client/dist/pwa/assets/SettingsPage-DDQCgnjL.js +9 -0
  86. package/src/client/dist/pwa/assets/{SplitWorkspacePage-BZ8wN4ii.js → SplitWorkspacePage-DKCtOZO4.js} +1 -1
  87. package/src/client/dist/pwa/assets/{TerminalPanel-pY-RGBb6.js → TerminalPanel-BE_MSZF0.js} +4 -4
  88. package/src/client/dist/pwa/assets/{ToolCallItem-C9ksVseJ.js → ToolCallItem-BEYaOtgh.js} +3 -3
  89. package/src/client/dist/pwa/assets/{TouchPan-DRynDIve.js → TouchPan-DeYoOjX5.js} +1 -1
  90. package/src/client/dist/pwa/assets/{TourReplayButton-D4h1QR6C.js → TourReplayButton-D5lMwIKt.js} +1 -1
  91. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BHEAsIoo.js +1 -0
  92. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-v7ein__H.css +1 -0
  93. package/src/client/dist/pwa/assets/WorkspacePage-5_wsJPaJ.js +4 -0
  94. package/src/client/dist/pwa/assets/WorkspacePage-CtUXz0Cq.css +1 -0
  95. package/src/client/dist/pwa/assets/{browser-BcOi2ES2.js → browser-ogy_9LVv.js} +1 -1
  96. package/src/client/dist/pwa/assets/{build-path-tree-mDco3pi7.js → build-path-tree-CRXxaYkA.js} +1 -1
  97. package/src/client/dist/pwa/assets/custom-sounds-iLTkCd_i.js +1 -0
  98. package/src/client/dist/pwa/assets/de-BqDU7iZH.js +17 -0
  99. package/src/client/dist/pwa/assets/{editor.api-CRrihXg2.js → editor.api-Dx6TbfZG.js} +1 -1
  100. package/src/client/dist/pwa/assets/{efforts-C-p9DzkL.js → efforts-DnJh2fW_.js} +2 -2
  101. package/src/client/dist/pwa/assets/es-D4VMXQRk.js +17 -0
  102. package/src/client/dist/pwa/assets/{expand-template-gIwrPbeS.js → expand-template-PUEVWny3.js} +1 -1
  103. package/src/client/dist/pwa/assets/{format-DeBnwmgW.js → format-yf9XM3dV.js} +1 -1
  104. package/src/client/dist/pwa/assets/{formatters-CxZZ_WTb.js → formatters-D0sXu9Oe.js} +1 -1
  105. package/src/client/dist/pwa/assets/fr-DIyaCFB3.js +17 -0
  106. package/src/client/dist/pwa/assets/{freemarker2-h7OLlqTn.js → freemarker2-l_xIHxRf.js} +1 -1
  107. package/src/client/dist/pwa/assets/{handlebars-BCWmi1-q.js → handlebars-CYgq99yQ.js} +1 -1
  108. package/src/client/dist/pwa/assets/{html-MUMpfUBd.js → html-C28GatN_.js} +1 -1
  109. package/src/client/dist/pwa/assets/i18n-2AmHIUy0.js +1 -0
  110. package/src/client/dist/pwa/assets/index-MMYMztJ_.js +20 -0
  111. package/src/client/dist/pwa/assets/it-CF6_4OS0.js +17 -0
  112. package/src/client/dist/pwa/assets/{javascript-CHDwijEy.js → javascript-YCSueyyk.js} +1 -1
  113. package/src/client/dist/pwa/assets/{liquid-dFrnLevt.js → liquid-CUBn90Gs.js} +1 -1
  114. package/src/client/dist/pwa/assets/{mdx-BHS7JVD4.js → mdx-C5WOHShJ.js} +1 -1
  115. package/src/client/dist/pwa/assets/{monaco-contributions-DCWZe7nX.js → monaco-contributions-DYwqbh7m.js} +1 -1
  116. package/src/client/dist/pwa/assets/{monaco.contribution-Dx0gcAn4.js → monaco.contribution-CoFzrPQl.js} +2 -2
  117. package/src/client/dist/pwa/assets/network-auth-BpJ6Noax.js +1 -0
  118. package/src/client/dist/pwa/assets/notify-theme-NPq4-Tup.js +1 -0
  119. package/src/client/dist/pwa/assets/permissionModes-Bbl901Hx.js +36 -0
  120. package/src/client/dist/pwa/assets/{project-color-DihsE4yN.js → project-color-gNyhKJlV.js} +1 -1
  121. package/src/client/dist/pwa/assets/{python-BtjZnK9y.js → python-lhKUZdPj.js} +1 -1
  122. package/src/client/dist/pwa/assets/{razor-DBcOGz0a.js → razor-Chi44rEL.js} +1 -1
  123. package/src/client/dist/pwa/assets/{typescript-B320c7wU.js → typescript-CU2TXVm6.js} +1 -1
  124. package/src/client/dist/pwa/assets/{use-search-index-status-B95GPaJP.js → use-search-index-status-CQ9S05G7.js} +1 -1
  125. package/src/client/dist/pwa/assets/{use-tours-C_5R1Y5D.js → use-tours-BmrZbHjz.js} +2 -2
  126. package/src/client/dist/pwa/assets/use-worktree-restore-DcucRlLh.js +1 -0
  127. package/src/client/dist/pwa/assets/{workspace-templates-B9UETKER.js → workspace-templates-DaG42EEt.js} +1 -1
  128. package/src/client/dist/pwa/assets/{xml-uK8LK4OB.js → xml-CPO5LI6F.js} +1 -1
  129. package/src/client/dist/pwa/assets/{yaml-B2D1l_tz.js → yaml-CoYs82jI.js} +1 -1
  130. package/src/client/dist/pwa/index.html +1 -1
  131. package/src/client/dist/pwa/sw.js +1 -1
  132. package/src/mcp-server/README.md +18 -6
  133. package/src/client/dist/pwa/assets/ActivityFeed-CiTe2VUQ.js +0 -2
  134. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +0 -1
  135. package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-v3jXoH_Q.js +0 -1
  136. package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +0 -1
  137. package/src/client/dist/pwa/assets/CreatePage-MYCexRJu.js +0 -2
  138. package/src/client/dist/pwa/assets/DiffViewer-CvsmC9d5.js +0 -8
  139. package/src/client/dist/pwa/assets/GitPanel-C1ybi9bZ.js +0 -3
  140. package/src/client/dist/pwa/assets/GitPanel-yHvU38dp.css +0 -1
  141. package/src/client/dist/pwa/assets/HealthPage-C1u35ANL.js +0 -1
  142. package/src/client/dist/pwa/assets/HealthPage-Cnlhrn--.css +0 -1
  143. package/src/client/dist/pwa/assets/MainLayout-BEAGPqei.js +0 -2
  144. package/src/client/dist/pwa/assets/QSelect-DOnv_ry0.js +0 -1
  145. package/src/client/dist/pwa/assets/SettingsPage-CFaRN9Pk.js +0 -9
  146. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B9LE8HtU.css +0 -1
  147. package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-Bkj2DhOn.js +0 -1
  148. package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +0 -1
  149. package/src/client/dist/pwa/assets/WorkspacePage-BTJnap9a.js +0 -4
  150. package/src/client/dist/pwa/assets/custom-sounds-T5HKzrWh.js +0 -1
  151. package/src/client/dist/pwa/assets/de-suqWUSXa.js +0 -17
  152. package/src/client/dist/pwa/assets/es-Dcptbz5U.js +0 -17
  153. package/src/client/dist/pwa/assets/fr-Db70bqVq.js +0 -17
  154. package/src/client/dist/pwa/assets/i18n-DLprfNLs.js +0 -1
  155. package/src/client/dist/pwa/assets/index-DLoUeovT.js +0 -20
  156. package/src/client/dist/pwa/assets/it-Cloo58Nn.js +0 -17
  157. package/src/client/dist/pwa/assets/network-auth-DDMAzwYH.js +0 -1
  158. package/src/client/dist/pwa/assets/notify-theme-JuVT96rq.js +0 -1
  159. package/src/client/dist/pwa/assets/permissionModes-D-Nexnoo.js +0 -36
  160. package/src/client/dist/pwa/assets/use-worktree-restore-Cj8eGE9s.js +0 -1
@@ -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
+ }
@@ -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
+ }