@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.
- package/AGENTS.md +59 -10
- package/CHANGELOG.md +16 -1
- package/CONFIGURATION.md +114 -14
- package/CONTRIBUTING.md +1 -1
- package/DESIGN.md +1 -1
- package/README.md +12 -9
- package/SECURITY.md +7 -2
- package/dist/server/db/memory-schema.js +5 -5
- package/dist/server/db/migrations.js +2 -0
- package/dist/server/db/mistral-memory-migration.js +40 -0
- package/dist/server/routes/engines.js +14 -0
- package/dist/server/routes/environment.js +1 -1
- package/dist/server/routes/health.js +14 -3
- package/dist/server/routes/settings.js +7 -1
- package/dist/server/routes/workspaces.js +116 -9
- package/dist/server/services/agent/engines/claude-code/capabilities.js +2 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +3 -0
- package/dist/server/services/agent/engines/codex/capabilities.js +2 -0
- package/dist/server/services/agent/engines/codex/engine.js +3 -0
- package/dist/server/services/agent/engines/configuration.js +14 -0
- package/dist/server/services/agent/engines/mistral-vibe/capabilities.js +13 -0
- package/dist/server/services/agent/engines/mistral-vibe/client.js +64 -0
- package/dist/server/services/agent/engines/mistral-vibe/configuration.js +76 -0
- package/dist/server/services/agent/engines/mistral-vibe/engine.js +361 -0
- package/dist/server/services/agent/engines/mistral-vibe/event-mapper.js +297 -0
- package/dist/server/services/agent/engines/mistral-vibe/options-builder.js +35 -0
- package/dist/server/services/agent/engines/mistral-vibe/server-requests.js +278 -0
- package/dist/server/services/agent/engines/mistral-vibe/spawn.js +176 -0
- package/dist/server/services/agent/engines/registry.js +2 -0
- package/dist/server/services/agent/engines/types.js +6 -0
- package/dist/server/services/agent/orchestrator.js +88 -25
- package/dist/server/services/agent/session-controller.js +5 -0
- package/dist/server/services/auto-loop-final-review-service.js +10 -3
- package/dist/server/services/environment-check-service.js +6 -1
- package/dist/server/services/memory-service.js +1 -1
- package/dist/server/services/mistral-options-service.js +145 -0
- package/dist/server/services/pr-watcher-service.js +40 -11
- package/dist/server/services/review-service.js +33 -9
- package/dist/server/services/session-handoff-service.js +28 -2
- package/dist/server/services/settings-defaults.js +1 -0
- package/dist/server/services/settings-service.js +19 -1
- package/dist/server/services/workspace-creation-mcp-service.js +50 -8
- package/dist/server/services/workspace-dialogue-service.js +2 -0
- package/dist/server/services/workspace-message-service.js +19 -1
- package/dist/server/services/workspace-permission-policy-service.js +24 -0
- package/dist/server/utils/bounded-process.js +1 -1
- package/dist/shared/launch-instructions.js +18 -7
- package/dist/shared/memory.js +1 -1
- package/dist/shared/mistral-models.js +9 -0
- package/dist/shared/mistral-options.js +1 -0
- package/dist/shared/question-selections.js +16 -0
- package/dist/shared/workspace-creation-tools.js +8 -2
- package/dist/shared/workspace-dialogue-tools.js +15 -4
- package/docs/deployment-vps.md +10 -3
- package/docs/getting-started.md +3 -2
- package/docs/troubleshooting.md +1 -1
- package/package.json +7 -1
- package/src/client/dist/pwa/assets/ActivityFeed-C5stmHXP.js +2 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CIDSX32d.css +1 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-DG32W_0G.js +1 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-C5KjAOMG.js → ChangelogPage-_vItY8vA.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-XXyzPEzW.js → ComparisonPanel-B3BMzQH0.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-FlGrfmjX.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-oh8ihVC4.css +1 -0
- package/src/client/dist/pwa/assets/{DashboardPage-DyhUu26C.js → DashboardPage-lJGwWQYQ.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-DIaQH3lA.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-ItndGxeB.js → DocumentsPanel-Ct-nm4fX.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-DfzS_CD8.js → DrawerToggleButton-M5qv4Io-.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BaHCgb3p.js +3 -0
- package/src/client/dist/pwa/assets/GitPanel-C8vw2qvW.css +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DR3wa6iD.js +1 -0
- package/src/client/dist/pwa/assets/HealthPage-Dbn4J_DT.css +1 -0
- package/src/client/dist/pwa/assets/{MainLayout-DoAEgANU.css → MainLayout-CHi8qP1D.css} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-WJabICsA.js +2 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-CZzI7_nY.js → MemoryPanel-CRpWdfc0.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-DqNTycY1.js → MemoryProposalList-DGz3lDbL.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-D7ml7zHo.js → QBtnDropdown-tt8rUNNR.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-BVb067oc.js → QExpansionItem-BkrI3UPL.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-CUDWkEHh.js → QScrollArea-BvgnY-oA.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-BtZB1wrB.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-B5QJIU3p.js → QTooltip-CDH6p745.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-IJOcrHgk.js → SchedulePanel-C2rWlh7l.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-BpTRWtne.js → SearchPage-BFd4NQwJ.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-B4rmd2WX.css → SettingsPage-Bm0TcarK.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-DDQCgnjL.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-BZ8wN4ii.js → SplitWorkspacePage-DKCtOZO4.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-pY-RGBb6.js → TerminalPanel-BE_MSZF0.js} +4 -4
- package/src/client/dist/pwa/assets/{ToolCallItem-C9ksVseJ.js → ToolCallItem-BEYaOtgh.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-DRynDIve.js → TouchPan-DeYoOjX5.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-D4h1QR6C.js → TourReplayButton-D5lMwIKt.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BHEAsIoo.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-v7ein__H.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-5_wsJPaJ.js +4 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CtUXz0Cq.css +1 -0
- package/src/client/dist/pwa/assets/{browser-BcOi2ES2.js → browser-ogy_9LVv.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-mDco3pi7.js → build-path-tree-CRXxaYkA.js} +1 -1
- package/src/client/dist/pwa/assets/custom-sounds-iLTkCd_i.js +1 -0
- package/src/client/dist/pwa/assets/de-BqDU7iZH.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-CRrihXg2.js → editor.api-Dx6TbfZG.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-C-p9DzkL.js → efforts-DnJh2fW_.js} +2 -2
- package/src/client/dist/pwa/assets/es-D4VMXQRk.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-gIwrPbeS.js → expand-template-PUEVWny3.js} +1 -1
- package/src/client/dist/pwa/assets/{format-DeBnwmgW.js → format-yf9XM3dV.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-CxZZ_WTb.js → formatters-D0sXu9Oe.js} +1 -1
- package/src/client/dist/pwa/assets/fr-DIyaCFB3.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-h7OLlqTn.js → freemarker2-l_xIHxRf.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-BCWmi1-q.js → handlebars-CYgq99yQ.js} +1 -1
- package/src/client/dist/pwa/assets/{html-MUMpfUBd.js → html-C28GatN_.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-2AmHIUy0.js +1 -0
- package/src/client/dist/pwa/assets/index-MMYMztJ_.js +20 -0
- package/src/client/dist/pwa/assets/it-CF6_4OS0.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-CHDwijEy.js → javascript-YCSueyyk.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-dFrnLevt.js → liquid-CUBn90Gs.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-BHS7JVD4.js → mdx-C5WOHShJ.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-DCWZe7nX.js → monaco-contributions-DYwqbh7m.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-Dx0gcAn4.js → monaco.contribution-CoFzrPQl.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BpJ6Noax.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-NPq4-Tup.js +1 -0
- package/src/client/dist/pwa/assets/permissionModes-Bbl901Hx.js +36 -0
- package/src/client/dist/pwa/assets/{project-color-DihsE4yN.js → project-color-gNyhKJlV.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BtjZnK9y.js → python-lhKUZdPj.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-DBcOGz0a.js → razor-Chi44rEL.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-B320c7wU.js → typescript-CU2TXVm6.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-B95GPaJP.js → use-search-index-status-CQ9S05G7.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-C_5R1Y5D.js → use-tours-BmrZbHjz.js} +2 -2
- package/src/client/dist/pwa/assets/use-worktree-restore-DcucRlLh.js +1 -0
- package/src/client/dist/pwa/assets/{workspace-templates-B9UETKER.js → workspace-templates-DaG42EEt.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-uK8LK4OB.js → xml-CPO5LI6F.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-B2D1l_tz.js → yaml-CoYs82jI.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +18 -6
- package/src/client/dist/pwa/assets/ActivityFeed-CiTe2VUQ.js +0 -2
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +0 -1
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-v3jXoH_Q.js +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-MYCexRJu.js +0 -2
- package/src/client/dist/pwa/assets/DiffViewer-CvsmC9d5.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-C1ybi9bZ.js +0 -3
- package/src/client/dist/pwa/assets/GitPanel-yHvU38dp.css +0 -1
- package/src/client/dist/pwa/assets/HealthPage-C1u35ANL.js +0 -1
- package/src/client/dist/pwa/assets/HealthPage-Cnlhrn--.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-BEAGPqei.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-DOnv_ry0.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-CFaRN9Pk.js +0 -9
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B9LE8HtU.css +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-Bkj2DhOn.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BTJnap9a.js +0 -4
- package/src/client/dist/pwa/assets/custom-sounds-T5HKzrWh.js +0 -1
- package/src/client/dist/pwa/assets/de-suqWUSXa.js +0 -17
- package/src/client/dist/pwa/assets/es-Dcptbz5U.js +0 -17
- package/src/client/dist/pwa/assets/fr-Db70bqVq.js +0 -17
- package/src/client/dist/pwa/assets/i18n-DLprfNLs.js +0 -1
- package/src/client/dist/pwa/assets/index-DLoUeovT.js +0 -20
- package/src/client/dist/pwa/assets/it-Cloo58Nn.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-DDMAzwYH.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-JuVT96rq.js +0 -1
- package/src/client/dist/pwa/assets/permissionModes-D-Nexnoo.js +0 -36
- package/src/client/dist/pwa/assets/use-worktree-restore-Cj8eGE9s.js +0 -1
|
@@ -9,6 +9,7 @@ import { listEngines } from './agent/engines/registry.js';
|
|
|
9
9
|
import * as agentManager from './agent/orchestrator.js';
|
|
10
10
|
import { bindReviewSession, getFinalReviewSourceSession } from './auto-loop-final-review-service.js';
|
|
11
11
|
import { canStartAutomatically } from './auto-loop-service.js';
|
|
12
|
+
import { preflightMistralConfiguration } from './mistral-options-service.js';
|
|
12
13
|
import { beginReviewLaunch, finishReviewLaunch } from './review-launch-runtime.js';
|
|
13
14
|
import { getReviewReturn, registerReviewReturn, restoreReviewConfiguration } from './review-return-service.js';
|
|
14
15
|
import { getActiveReviewTemplate, renderReviewTemplate } from './review-template-service.js';
|
|
@@ -47,20 +48,28 @@ function resolveConfiguration(workspace, body, allowCustomModel = false) {
|
|
|
47
48
|
const engine = listEngines().find((e) => e.id === engineId);
|
|
48
49
|
if (!engine)
|
|
49
50
|
throw new ReviewRequestError(`Unknown engine '${engineId}'`, 400);
|
|
51
|
+
if (!engine.capabilities.supportsReadOnly && engineId !== 'mistral-vibe')
|
|
52
|
+
throw new ReviewRequestError(`Engine '${engineId}' does not support read-only reviews`, 400);
|
|
50
53
|
const changedEngine = engineId !== workspace.engine;
|
|
51
54
|
const config = {
|
|
52
55
|
engine: engineId,
|
|
53
56
|
model: body.model ?? (changedEngine ? 'auto' : workspace.model),
|
|
54
57
|
reasoningEffort: body.reasoningEffort ?? (changedEngine ? 'auto' : workspace.reasoningEffort),
|
|
55
|
-
//
|
|
56
|
-
agentPermissionMode:
|
|
58
|
+
// Native reviews have independent permissions; other returning reviewers use Plan.
|
|
59
|
+
agentPermissionMode: engineId === 'mistral-vibe'
|
|
60
|
+
? (body.agentPermissionMode ?? 'interactive')
|
|
61
|
+
: body.returnToSession
|
|
62
|
+
? 'plan'
|
|
63
|
+
: (body.agentPermissionMode ?? workspace.agentPermissionMode),
|
|
57
64
|
};
|
|
58
65
|
// Preserve an existing custom model when unchanged; reject incompatible selections.
|
|
59
|
-
if (
|
|
66
|
+
if (engineId !== 'mistral-vibe' &&
|
|
67
|
+
!allowCustomModel &&
|
|
60
68
|
(changedEngine || config.model !== workspace.model) &&
|
|
61
69
|
!engine.capabilities.models.some((m) => m.id === config.model))
|
|
62
70
|
throw new ReviewRequestError(`Model '${config.model}' is not supported by '${engineId}'`, 400);
|
|
63
|
-
if (
|
|
71
|
+
if (engineId !== 'mistral-vibe' &&
|
|
72
|
+
(changedEngine || config.reasoningEffort !== workspace.reasoningEffort) &&
|
|
64
73
|
!engine.capabilities.effortLevels?.some((e) => e.id === config.reasoningEffort))
|
|
65
74
|
throw new ReviewRequestError(`Reasoning effort '${config.reasoningEffort}' is not supported by '${engineId}'`, 400);
|
|
66
75
|
if (!engine.capabilities.permissionModes.includes(config.agentPermissionMode))
|
|
@@ -81,6 +90,15 @@ export async function startWorkspaceReview(id, input, internal) {
|
|
|
81
90
|
throw new ReviewRequestError(`Workspace '${id}' not found`, 404);
|
|
82
91
|
const body = parseReviewRequest(input);
|
|
83
92
|
const configuration = resolveConfiguration(workspace, body, internal?.autoLoopFinalReview);
|
|
93
|
+
const nativeReview = configuration.engine === 'mistral-vibe';
|
|
94
|
+
if (nativeReview && internal?.autoLoopFinalReview)
|
|
95
|
+
configuration.agentPermissionMode = 'interactive';
|
|
96
|
+
try {
|
|
97
|
+
await preflightMistralConfiguration(configuration.engine, { workspaceId: id }, configuration);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new ReviewRequestError(error instanceof Error ? error.message : 'Invalid native review configuration', 400);
|
|
101
|
+
}
|
|
84
102
|
assertWorkspaceLifecycleAvailable(id);
|
|
85
103
|
if (workspace.archivedAt || workspace.worktreePurgedAt)
|
|
86
104
|
throw new ReviewRequestError('Restore the workspace before starting a review', 409);
|
|
@@ -94,7 +112,7 @@ export async function startWorkspaceReview(id, input, internal) {
|
|
|
94
112
|
const differsFromSession = !!originalSession &&
|
|
95
113
|
((!!originalSession.engine && originalSession.engine !== configuration.engine) ||
|
|
96
114
|
(configuration.model !== 'auto' && !!originalSession.model && originalSession.model !== configuration.model));
|
|
97
|
-
const newSession = body.newSession === true || changed || differsFromSession || body.returnToSession === true;
|
|
115
|
+
const newSession = nativeReview || body.newSession === true || changed || differsFromSession || body.returnToSession === true;
|
|
98
116
|
if (body.returnToSession &&
|
|
99
117
|
(!originalSession?.engineSessionId || (originalSession.engine && originalSession.engine !== workspace.engine)))
|
|
100
118
|
throw new ReviewRequestError('The current session cannot be resumed; start it before requesting an automatic return', 400);
|
|
@@ -134,7 +152,10 @@ export async function startWorkspaceReview(id, input, internal) {
|
|
|
134
152
|
});
|
|
135
153
|
// The return fires only when the review session ends by itself: a reviewer
|
|
136
154
|
// that starts fixing, or waits on a question, never hands back.
|
|
137
|
-
if (
|
|
155
|
+
if (nativeReview)
|
|
156
|
+
rendered +=
|
|
157
|
+
'\n\nThis is a report-only review under native Vibe permissions, without guaranteed filesystem read-only enforcement. These rules override any template or skill suggesting fixes: do not modify, create or delete files, commit, push, or mutate tasks, workspace state or memory. Report findings and recommended fixes only. Respect native approval requests; never bypass or auto-approve them. If approval is required, wait for the user. Finish with a standalone report including severity, file/line references and remaining uncertainty.';
|
|
158
|
+
else if (body.returnToSession)
|
|
138
159
|
rendered +=
|
|
139
160
|
'\n\nThis is a read-only review session; these rules override any template instruction or skill that suggests fixing issues. Do not modify, create or delete files, and do not commit or push: the original agent session applies the fixes. Running read-only checks such as tests or linters is fine. Do not ask the user questions or wait for input: note any decision that needs the user in your summary instead.' +
|
|
140
161
|
'\n\nFinish with a standalone summary of your review findings, including severity, file/line references, recommended fixes and any remaining uncertainty. This final message will be handed back to the original agent session. Do not rely on earlier messages to explain your findings.';
|
|
@@ -177,7 +198,10 @@ export async function startWorkspaceReview(id, input, internal) {
|
|
|
177
198
|
original: {
|
|
178
199
|
engine: workspace.engine,
|
|
179
200
|
model: workspace.model,
|
|
180
|
-
...(
|
|
201
|
+
...((workspace.engine !== 'mistral-vibe' || workspace.model !== 'auto') && originalSession.model
|
|
202
|
+
? { sessionModel: originalSession.model }
|
|
203
|
+
: {}),
|
|
204
|
+
// Restore iteration intent separately; native review returns retain saved session thinking.
|
|
181
205
|
reasoningEffort: workspace.reasoningEffort,
|
|
182
206
|
agentPermissionMode: workspace.agentPermissionMode,
|
|
183
207
|
},
|
|
@@ -204,9 +228,9 @@ export async function startWorkspaceReview(id, input, internal) {
|
|
|
204
228
|
pendingSessionId,
|
|
205
229
|
configuration.reasoningEffort,
|
|
206
230
|
];
|
|
207
|
-
if (reviewLaunch) {
|
|
231
|
+
if (nativeReview || reviewLaunch) {
|
|
208
232
|
startArgs[8] = undefined;
|
|
209
|
-
startArgs[9] = { mcpEnv: reviewLaunch
|
|
233
|
+
startArgs[9] = { mcpEnv: { ...reviewLaunch?.mcpEnv, ...(nativeReview ? { KOBO_REVIEW_ONLY: '1' } : {}) } };
|
|
210
234
|
}
|
|
211
235
|
const agent = agentManager.startAgent(...startArgs);
|
|
212
236
|
workspaceService.updateWorkspaceStatus(id, 'executing');
|
|
@@ -6,9 +6,11 @@ import { getDb } from '../db/index.js';
|
|
|
6
6
|
import { assertAgentStopped } from '../utils/agent-stop-result.js';
|
|
7
7
|
import { ensureDirectoryInside } from '../utils/safe-path.js';
|
|
8
8
|
import { reserveWorkspaceLifecycle } from '../utils/workspace-lifecycle-guard.js';
|
|
9
|
+
import { assertEngineConfigurationSupported } from './agent/engines/configuration.js';
|
|
9
10
|
import { listEngines } from './agent/engines/registry.js';
|
|
10
11
|
import * as agents from './agent/orchestrator.js';
|
|
11
12
|
import { buildEngineHandoff } from './engine-handoff-service.js';
|
|
13
|
+
import { preflightMistralConfiguration } from './mistral-options-service.js';
|
|
12
14
|
import { getReviewReturn } from './review-return-service.js';
|
|
13
15
|
import { activateSession, deactivateSession } from './session-activity-service.js';
|
|
14
16
|
import { registerHandoffStopHandler } from './session-handoff-runtime.js';
|
|
@@ -180,6 +182,15 @@ function parseRequest(input) {
|
|
|
180
182
|
},
|
|
181
183
|
};
|
|
182
184
|
}
|
|
185
|
+
export async function preflightSessionHandoff(workspaceId, input) {
|
|
186
|
+
const body = parseRequest(input);
|
|
187
|
+
// Existing requests are replayed by createSessionHandoff with fingerprint validation.
|
|
188
|
+
if (getDb()
|
|
189
|
+
.prepare('SELECT 1 FROM session_handoffs WHERE workspace_id = ? AND request_id = ?')
|
|
190
|
+
.get(workspaceId, body.requestId))
|
|
191
|
+
return;
|
|
192
|
+
await preflightMistralConfiguration(body.target.engine, { workspaceId }, body.target);
|
|
193
|
+
}
|
|
183
194
|
export function createSessionHandoff(workspaceId, input, suppliedReport) {
|
|
184
195
|
if (agents.isShuttingDown())
|
|
185
196
|
throw new SessionHandoffError('Kōbō is shutting down');
|
|
@@ -207,11 +218,22 @@ export function createSessionHandoff(workspaceId, input, suppliedReport) {
|
|
|
207
218
|
throw new SessionHandoffError('The current session changed. Reopen the transfer dialog.');
|
|
208
219
|
const engine = listEngines().find((item) => item.id === body.target.engine);
|
|
209
220
|
if (!engine?.capabilities.permissionModes.includes(body.target.agentPermissionMode) ||
|
|
210
|
-
(
|
|
221
|
+
(engine.id !== 'mistral-vibe' &&
|
|
222
|
+
!engine.capabilities.models.some((model) => model.id === body.target.model) &&
|
|
211
223
|
!(workspace.engine === body.target.engine && workspace.model === body.target.model)) ||
|
|
212
|
-
(
|
|
224
|
+
(engine.id !== 'mistral-vibe' &&
|
|
225
|
+
body.target.reasoningEffort !== 'auto' &&
|
|
213
226
|
!engine.capabilities.effortLevels?.some((effort) => effort.id === body.target.reasoningEffort)))
|
|
214
227
|
throw new SessionHandoffError('The target engine configuration is not supported', 400);
|
|
228
|
+
try {
|
|
229
|
+
assertEngineConfigurationSupported(engine, body.target);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
throw new SessionHandoffError(error instanceof Error ? error.message : 'Invalid engine configuration', 400);
|
|
233
|
+
}
|
|
234
|
+
if (body.generateSummary &&
|
|
235
|
+
!listEngines().find((item) => item.id === workspace.engine)?.capabilities.supportsReadOnly)
|
|
236
|
+
throw new SessionHandoffError('The source engine does not support read-only handoff summaries', 400);
|
|
215
237
|
const id = nanoid();
|
|
216
238
|
const runtime = reserve(workspaceId, id);
|
|
217
239
|
try {
|
|
@@ -289,6 +311,10 @@ async function run(runtime) {
|
|
|
289
311
|
runtime.running = true;
|
|
290
312
|
const row = read(runtime.id);
|
|
291
313
|
try {
|
|
314
|
+
const target = map(row).target;
|
|
315
|
+
await preflightMistralConfiguration(target.engine, { workspaceId: row.workspace_id }, target);
|
|
316
|
+
if (runtime.cancelled)
|
|
317
|
+
return;
|
|
292
318
|
state(row.id, 'stopping');
|
|
293
319
|
assertAgentStopped(await agents.stopAgentAndWait(row.workspace_id, undefined, 'handoff'));
|
|
294
320
|
if (runtime.cancelled)
|
|
@@ -1019,6 +1019,24 @@ const settingsMigrations = [
|
|
|
1019
1019
|
global.forceImmediateMessages = false;
|
|
1020
1020
|
},
|
|
1021
1021
|
},
|
|
1022
|
+
{
|
|
1023
|
+
version: 68,
|
|
1024
|
+
name: 'mistral-native-defaults',
|
|
1025
|
+
migrate: ({ global }) => {
|
|
1026
|
+
for (const [key, value] of [
|
|
1027
|
+
['defaultModelByEngine', 'auto'],
|
|
1028
|
+
['defaultPermissionModeByEngine', 'interactive'],
|
|
1029
|
+
]) {
|
|
1030
|
+
const map = global[key];
|
|
1031
|
+
// Missing maps inherit the full fresh-install defaults when settings are read.
|
|
1032
|
+
if (map && typeof map === 'object' && !Array.isArray(map)) {
|
|
1033
|
+
const defaults = map;
|
|
1034
|
+
if (typeof defaults['mistral-vibe'] !== 'string')
|
|
1035
|
+
defaults['mistral-vibe'] = value;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
},
|
|
1022
1040
|
];
|
|
1023
1041
|
/** Current settings schema version — always equals the highest migration version. */
|
|
1024
1042
|
export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
|
|
@@ -1127,7 +1145,7 @@ function defaultSettings() {
|
|
|
1127
1145
|
notionInProgressStatus: '',
|
|
1128
1146
|
notionAssigneeProperty: '',
|
|
1129
1147
|
notionUserId: '',
|
|
1130
|
-
defaultPermissionModeByEngine: { 'claude-code': 'plan', codex: 'plan' },
|
|
1148
|
+
defaultPermissionModeByEngine: { 'claude-code': 'plan', codex: 'plan', 'mistral-vibe': 'interactive' },
|
|
1131
1149
|
notionMcpKey: '',
|
|
1132
1150
|
sentryMcpKey: '',
|
|
1133
1151
|
notionEnabled: false,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { checkoutDecisionError, deriveSteps } from '../../shared/pr-checkout-steps.js';
|
|
2
2
|
import { validateWorkspaceCreationArguments } from '../../shared/workspace-creation-tools.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { assertEngineConfigurationSupported } from './agent/engines/configuration.js';
|
|
4
|
+
import { resolveEngine } from './agent/engines/registry.js';
|
|
5
|
+
import { parseFinalReviewConfiguration } from './auto-loop-final-review-service.js';
|
|
6
|
+
import { preflightMistralConfiguration } from './mistral-options-service.js';
|
|
5
7
|
import { rollbackCreatedPrCheckout } from './pr-checkout-rollback-service.js';
|
|
8
|
+
import { getGlobalSettings } from './settings-service.js';
|
|
6
9
|
import { beginWorkspaceCreation } from './update-maintenance-service.js';
|
|
7
10
|
export class WorkspaceCreationMcpError extends Error {
|
|
8
11
|
status;
|
|
@@ -102,13 +105,52 @@ async function executeCreation(name, raw, dispatch) {
|
|
|
102
105
|
catch (error) {
|
|
103
106
|
throw new WorkspaceCreationMcpError(error instanceof Error ? error.message : 'Invalid arguments', 400, 'validate');
|
|
104
107
|
}
|
|
105
|
-
const config = input.auto_loop_final_review;
|
|
106
|
-
if (config) {
|
|
107
|
-
const capabilities = config.engine === 'codex' ? CODEX_CAPABILITIES : CLAUDE_CODE_CAPABILITIES;
|
|
108
|
-
if (!capabilities.effortLevels?.some((effort) => effort.id === config.reasoning_effort))
|
|
109
|
-
throw new WorkspaceCreationMcpError('Unsupported review reasoning effort', 400, 'validate');
|
|
110
|
-
}
|
|
111
108
|
const body = mapInput(input);
|
|
109
|
+
try {
|
|
110
|
+
if (name === 'create_workspace') {
|
|
111
|
+
const engine = resolveEngine(input.engine ?? 'claude-code');
|
|
112
|
+
const savedMode = engine.id === 'mistral-vibe' ? getGlobalSettings().defaultPermissionModeByEngine?.[engine.id] : undefined;
|
|
113
|
+
const defaultMode = savedMode === 'plan' || engine.capabilities.permissionModes.some((mode) => mode === savedMode)
|
|
114
|
+
? savedMode
|
|
115
|
+
: undefined;
|
|
116
|
+
if (engine.id === 'mistral-vibe')
|
|
117
|
+
body.agentPermissionMode = input.agent_permission_mode ?? defaultMode ?? 'interactive';
|
|
118
|
+
assertEngineConfigurationSupported(engine, {
|
|
119
|
+
model: input.model,
|
|
120
|
+
reasoningEffort: input.reasoning_effort,
|
|
121
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
122
|
+
});
|
|
123
|
+
await preflightMistralConfiguration(engine.id, { projectPath: input.project_path }, {
|
|
124
|
+
model: input.model,
|
|
125
|
+
reasoningEffort: input.reasoning_effort,
|
|
126
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
127
|
+
});
|
|
128
|
+
assertEngineConfigurationSupported(engine, {
|
|
129
|
+
model: input.brainstorm_model,
|
|
130
|
+
reasoningEffort: input.brainstorm_reasoning_effort,
|
|
131
|
+
});
|
|
132
|
+
await preflightMistralConfiguration(engine.id, { projectPath: input.project_path }, {
|
|
133
|
+
model: input.brainstorm_model ?? input.model,
|
|
134
|
+
reasoningEffort: input.brainstorm_reasoning_effort ?? input.reasoning_effort,
|
|
135
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
throw new WorkspaceCreationMcpError(error instanceof Error ? error.message : 'Invalid engine configuration', 400, 'validate');
|
|
141
|
+
}
|
|
142
|
+
if (body.autoLoopFinalReview) {
|
|
143
|
+
try {
|
|
144
|
+
const config = parseFinalReviewConfiguration(body.autoLoopFinalReview);
|
|
145
|
+
await preflightMistralConfiguration(config.engine, { projectPath: input.project_path }, {
|
|
146
|
+
...config,
|
|
147
|
+
agentPermissionMode: 'interactive',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
throw new WorkspaceCreationMcpError(error instanceof Error ? error.message : 'Invalid final review configuration', 400, 'validate');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
112
154
|
let resolvedCheckout;
|
|
113
155
|
if (input.pr_url) {
|
|
114
156
|
const supplied = prIdentity(input.pr_url);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { parseQuestionSelections } from '../../shared/question-selections.js';
|
|
2
3
|
import { validateDialogueArguments } from '../../shared/workspace-dialogue-tools.js';
|
|
3
4
|
import { parseMessageSource } from '../../shared/workspace-message-types.js';
|
|
4
5
|
import { getDb } from '../db/index.js';
|
|
@@ -79,6 +80,7 @@ export async function executeWorkspaceDialogueTool(name, input, source = createM
|
|
|
79
80
|
throw new Error('The requested question is not the current pending question');
|
|
80
81
|
await answerPendingQuestion(workspaceId, args.answers, args.tool_call_id, {
|
|
81
82
|
source,
|
|
83
|
+
...(args.selections !== undefined ? { selections: parseQuestionSelections(args.selections) } : {}),
|
|
82
84
|
});
|
|
83
85
|
return { accepted: true };
|
|
84
86
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { validateMcpAttachments } from '../../shared/mcp-attachments.js';
|
|
2
2
|
import { assertWorkspaceLifecycleAvailable, withWorkspaceLifecycleGuard } from '../utils/workspace-lifecycle-guard.js';
|
|
3
|
-
import {
|
|
3
|
+
import { assertEngineConfigurationSupported } from './agent/engines/configuration.js';
|
|
4
|
+
import { resolveEngine } from './agent/engines/registry.js';
|
|
5
|
+
import { assertImmediateMessageSupported, isAgentUnavailableError, isShuttingDown, sendMessage, startAgent, } from './agent/orchestrator.js';
|
|
4
6
|
import { attachmentReference, removeAttachments, saveAttachments } from './attachment-service.js';
|
|
5
7
|
import { hasLoopMessage } from './auto-loop-message-service.js';
|
|
6
8
|
import * as autoLoop from './auto-loop-service.js';
|
|
7
9
|
import { createChatReceipt } from './chat-receipt-service.js';
|
|
10
|
+
import { preflightMistralConfiguration } from './mistral-options-service.js';
|
|
8
11
|
import { emitEphemeral } from './websocket-service.js';
|
|
9
12
|
import { getActiveSession, getWorkspace, updateWorkspaceStatus } from './workspace-service.js';
|
|
10
13
|
/** Shared delivery for WebSocket chat and external MCP clients. */
|
|
@@ -67,6 +70,21 @@ export async function deliverWorkspaceMessage(workspaceId, message, hooks) {
|
|
|
67
70
|
reason = 'awaiting-user';
|
|
68
71
|
throw new Error('Answer the pending question or permission request before sending a chat message');
|
|
69
72
|
}
|
|
73
|
+
if (workspace.engine === 'mistral-vibe') {
|
|
74
|
+
assertEngineConfigurationSupported(resolveEngine(workspace.engine), {
|
|
75
|
+
model: workspace.model,
|
|
76
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
77
|
+
agentPermissionMode: message.agentPermissionModeOverride ?? workspace.agentPermissionMode,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
assertImmediateMessageSupported(workspaceId);
|
|
81
|
+
if (workspace.engine === 'mistral-vibe') {
|
|
82
|
+
await preflightMistralConfiguration(workspace.engine, { workspaceId }, {
|
|
83
|
+
model: workspace.model,
|
|
84
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
85
|
+
agentPermissionMode: message.agentPermissionModeOverride ?? workspace.agentPermissionMode,
|
|
86
|
+
}, true);
|
|
87
|
+
}
|
|
70
88
|
if (attachments.length) {
|
|
71
89
|
attachmentWorktree = workspace.worktreePath;
|
|
72
90
|
await withWorkspaceLifecycleGuard(workspaceId, async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isAbsolute } from 'node:path';
|
|
2
3
|
import { nanoid } from 'nanoid';
|
|
3
4
|
import { getDb } from '../db/index.js';
|
|
4
5
|
const turnRules = new Map();
|
|
@@ -29,6 +30,26 @@ export function permissionFingerprint(payload) {
|
|
|
29
30
|
}
|
|
30
31
|
const CODEX_EDIT_FINGERPRINT_PREFIX = 'codex-edit-v2:';
|
|
31
32
|
function operationFingerprint(request) {
|
|
33
|
+
if (request.engine === 'mistral-vibe') {
|
|
34
|
+
const payload = request.payload;
|
|
35
|
+
const input = payload?.rawInput;
|
|
36
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || payload?.operationApprovalAvailable === false)
|
|
37
|
+
return null;
|
|
38
|
+
const shell = request.toolName === 'bash' &&
|
|
39
|
+
payload?.effectKind === 'shell' &&
|
|
40
|
+
payload.kind === 'execute' &&
|
|
41
|
+
typeof input.command === 'string' &&
|
|
42
|
+
input.command.trim().length > 0;
|
|
43
|
+
const write = request.toolName === 'write_file' &&
|
|
44
|
+
payload?.effectKind === 'file_write' &&
|
|
45
|
+
payload.kind === 'edit' &&
|
|
46
|
+
typeof input.filePath === 'string' &&
|
|
47
|
+
isAbsolute(input.filePath) &&
|
|
48
|
+
typeof input.content === 'string';
|
|
49
|
+
if (!shell && !write)
|
|
50
|
+
return null;
|
|
51
|
+
return `mistral-vibe-operation-v1:${permissionFingerprint({ rawInput: input, effectKind: payload?.effectKind, kind: payload?.kind })}`;
|
|
52
|
+
}
|
|
32
53
|
if (request.engine !== 'codex' || request.toolName !== 'Edit')
|
|
33
54
|
return permissionFingerprint(request.payload);
|
|
34
55
|
const payload = request.payload;
|
|
@@ -48,6 +69,9 @@ function operationFingerprint(request) {
|
|
|
48
69
|
grantRoot: payload.grantRoot ?? null,
|
|
49
70
|
}));
|
|
50
71
|
}
|
|
72
|
+
export function isOperationApprovalAvailable(request) {
|
|
73
|
+
return operationFingerprint(request) !== null;
|
|
74
|
+
}
|
|
51
75
|
function toRule(row) {
|
|
52
76
|
return {
|
|
53
77
|
active: !(row.scope === 'operation' &&
|
|
@@ -2,7 +2,7 @@ import { execFile, spawn } from 'node:child_process';
|
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
const execFileAsync = promisify(execFile);
|
|
4
4
|
/** A zombie cannot mutate a checkout, even before its parent reaps it. */
|
|
5
|
-
async function groupIsLive(pid) {
|
|
5
|
+
export async function groupIsLive(pid) {
|
|
6
6
|
try {
|
|
7
7
|
process.kill(-pid, 0);
|
|
8
8
|
}
|
|
@@ -5,20 +5,29 @@ export function getKoboPromptCapabilities(env) {
|
|
|
5
5
|
handoff: Boolean(env?.KOBO_HANDOFF_ID && env.KOBO_HANDOFF_TOKEN),
|
|
6
6
|
finalReview: Boolean(env?.KOBO_FINAL_REVIEW_SESSION_ID && env.KOBO_FINAL_REVIEW_TOKEN),
|
|
7
7
|
progressReporting: Boolean(env?.KOBO_PROGRESS_SESSION_TOKEN),
|
|
8
|
+
reviewOnly: env?.KOBO_REVIEW_ONLY === '1',
|
|
8
9
|
};
|
|
9
10
|
}
|
|
10
11
|
export function buildKoboLaunchInstructions(context) {
|
|
11
|
-
const restricted = context.readOnly || context.handoff || context.finalReview;
|
|
12
|
+
const restricted = context.readOnly || context.handoff || context.finalReview || context.reviewOnly;
|
|
12
13
|
const canWrite = context.permissionMode !== 'plan' && !restricted;
|
|
13
14
|
const lines = [
|
|
14
|
-
'[Kōbō MCP] Start by consulting the descriptions and schemas of the available tools from the managed `kobo-tasks` server. Prefer relevant Kōbō tools for operations managed by Kōbō (tasks, development services, shared memory, workspace messages and state) over reimplementing them through shell commands or HTTP. Check each tool result before treating an operation as completed. Discover tools as needed; do not call every tool or poll for activity.
|
|
15
|
+
'[Kōbō MCP] Start by consulting the descriptions and schemas of the available tools from the managed `kobo-tasks` server. Prefer relevant Kōbō tools for operations managed by Kōbō (tasks, development services, shared memory, workspace messages and state) over reimplementing them through shell commands or HTTP. Check each tool result before treating an operation as completed. Discover tools as needed; do not call every tool or poll for activity. ' +
|
|
16
|
+
(context.engine === 'mistral-vibe'
|
|
17
|
+
? 'Discover schemas with `search_tool_functions`, then call `tools.mcp_kobo_tasks.<tool>` through `run_typescript`; short names below refer to that server. '
|
|
18
|
+
: 'Exposed names use `mcp__kobo-tasks__<tool>`; short names below refer to that server. ') +
|
|
19
|
+
'Availability does not grant permission.',
|
|
15
20
|
'## Current permissions',
|
|
16
21
|
canWrite
|
|
17
22
|
? `Permission mode: ${context.permissionMode}. Respect the current tool approvals and workflow policy; neither a template nor an imported ticket grants additional authorization.`
|
|
18
|
-
:
|
|
23
|
+
: context.engine === 'mistral-vibe' && restricted
|
|
24
|
+
? `Report-only review under native ${context.permissionMode} permissions, without guaranteed filesystem read-only enforcement. Do not modify, create or delete files, commit, push, or mutate tasks, workspace state, memory or other MCP data. Kōbō tools permit only reads and explicitly scoped reports. Review instructions forbid all modifications even when native permissions allow them. Provide findings and recommended fixes only.`
|
|
25
|
+
: 'Read-only launch. Do not write files or mutate tasks, workspace state, memory, or other MCP data. Approving a plan does not change these permissions; continue only after Kōbō explicitly changes the launch permissions. Provide analysis and plans in the conversation.',
|
|
19
26
|
];
|
|
20
27
|
if (restricted) {
|
|
21
|
-
lines.push(
|
|
28
|
+
lines.push(context.engine === 'mistral-vibe'
|
|
29
|
+
? 'Respect native approval requests and wait for user approval when required; never bypass or auto-approve them. Report missing information and blockers in your final response, then finish.'
|
|
30
|
+
: 'Do not ask the user or wait for input. Report missing information and blockers in your final response, then finish so Kōbō can return to the source session.');
|
|
22
31
|
if (context.handoff)
|
|
23
32
|
lines.push('Submit the handoff report with the available `submit_session_handoff` tool according to its schema, then finish.');
|
|
24
33
|
else if (context.finalReview)
|
|
@@ -36,7 +45,7 @@ export function buildKoboLaunchInstructions(context) {
|
|
|
36
45
|
}
|
|
37
46
|
lines.push('## Development services', 'Kōbō manages the workspace dev server and its Docker services. Before tests or app checks that need those services (backend, frontend, integration or E2E), use `get_dev_server_status` and reuse the existing instance. Missing host dependencies are not a reason to launch another stack. Use the returned URL and container names, not guessed ports or names.', 'Read the project Makefile, scripts and relevant documentation to select the intended test command and runtime. Distinguish the Kōbō agent environment from the project service containers: if tests belong in a project container, run them there under current permissions. A PHP extension or other dependency missing in the agent environment alone is not evidence that the project is misconfigured, nor a reason to install global packages.', canWrite
|
|
38
47
|
? 'When an authorized task needs these services and their status is stopped, use `start_dev_server`. For an explicitly requested stop or a necessary authorized restart, use `stop_dev_server`. Do not run lifecycle commands such as `make dev-docker`, `sh/dev-docker.sh`, `docker compose up/down` or `npm run dev` directly; Kōbō runs the configured commands and tracks their processes.'
|
|
39
|
-
: 'This
|
|
48
|
+
: 'This restricted launch must not start, stop or restart services. Inspect available status/logs and report any unavailable test environment in the final response.', 'Use `get_dev_server_logs` to diagnose startup/runtime failures. If the tools are unavailable, the server is not_configured, or startup fails, report the blocker; do not bypass Kōbō with a shell launch. Test commands inside an existing container remain subject to the current permissions.', 'A running process or container is not proof that the service is ready or that tests passed. Before dependent tests, use the documented health check or a non-mutating request to the appropriate service URL. Bound readiness checks with a timeout and limited retries; if readiness remains unconfirmed, inspect logs and report the blocker rather than restarting repeatedly. Missing application readiness does not invalidate independent unit tests.');
|
|
40
49
|
lines.push('## Delegation and reporting', 'Only one agent edits the worktree at a time. Parallel read-only analysis is allowed; wait for an editing subagent to finish before editing or running tests yourself. Give each delegate its objective, exact worktree path, relevant user constraints, current permissions, intended test runtime and applicable Kōbō tools and reporting rules. Do not assume it inherits this context. A delegate without the required managed tools must ask its parent to coordinate the operation within the same permissions, not bypass Kōbō.');
|
|
41
50
|
if (context.progressReporting)
|
|
42
51
|
lines.push(PROGRESS_BAR_PROMPT);
|
|
@@ -53,9 +62,11 @@ export function buildKoboLaunchInstructions(context) {
|
|
|
53
62
|
else {
|
|
54
63
|
continuation.push('This launch cannot schedule follow-up work. Use available completion notifications for tracked work; report unfinished work or blockers honestly and finish the requested analysis/report.');
|
|
55
64
|
}
|
|
65
|
+
if (context.engine === 'mistral-vibe')
|
|
66
|
+
continuation.push('Use native `tools.process` tracked jobs for long-running commands; do not use raw redirected Bash backgrounding to detach work from native ownership.');
|
|
56
67
|
const continuationText = continuation.join('\n');
|
|
57
|
-
// Claude supports a durable application append;
|
|
58
|
-
if (context.engine
|
|
68
|
+
// Claude supports a durable application append; other engines receive these rules in their input.
|
|
69
|
+
if (context.engine !== 'claude-code')
|
|
59
70
|
lines.push(continuationText);
|
|
60
71
|
return { promptPrefix: lines.join('\n\n'), systemAppend: context.engine === 'claude-code' ? continuationText : '' };
|
|
61
72
|
}
|
package/dist/shared/memory.js
CHANGED
|
@@ -31,7 +31,7 @@ export function deriveMemoryActor(source, _callerActor) {
|
|
|
31
31
|
if (source.kind === 'internal-agent') {
|
|
32
32
|
if (!isNonEmptyString(source.workspaceId) ||
|
|
33
33
|
!isNonEmptyString(source.sessionId) ||
|
|
34
|
-
(source.engine !== 'claude-code' && source.engine !== 'codex')) {
|
|
34
|
+
(source.engine !== 'claude-code' && source.engine !== 'codex' && source.engine !== 'mistral-vibe')) {
|
|
35
35
|
throw new TypeError('Invalid internal memory actor source');
|
|
36
36
|
}
|
|
37
37
|
return Object.freeze({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Native multi-select values travel separately from legacy human-readable answers. */
|
|
2
|
+
export function parseQuestionSelections(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
6
|
+
throw new Error('selections must be an object of string arrays');
|
|
7
|
+
const entries = Object.entries(value);
|
|
8
|
+
if (entries.length > 20 ||
|
|
9
|
+
entries.some(([key, values]) => !key ||
|
|
10
|
+
key.length > 200 ||
|
|
11
|
+
!Array.isArray(values) ||
|
|
12
|
+
values.length > 32 ||
|
|
13
|
+
values.some((item) => typeof item !== 'string' || item.length > 2000)))
|
|
14
|
+
throw new Error('selections exceed their field, choice or string limits');
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
@@ -9,8 +9,8 @@ const object = (properties, required = []) => ({
|
|
|
9
9
|
required,
|
|
10
10
|
additionalProperties: false,
|
|
11
11
|
});
|
|
12
|
-
const effort = choice('auto', 'none', 'low', 'medium', 'high', 'xhigh', 'max');
|
|
13
|
-
const engine = choice('claude-code', 'codex');
|
|
12
|
+
const effort = choice('auto', 'off', 'none', 'low', 'medium', 'high', 'xhigh', 'max');
|
|
13
|
+
const engine = choice('claude-code', 'codex', 'mistral-vibe');
|
|
14
14
|
const path = string(4096);
|
|
15
15
|
const url = string(4096);
|
|
16
16
|
const titles = { type: 'array', items: string(20_000), maxItems: 500 };
|
|
@@ -144,6 +144,12 @@ export function validateWorkspaceCreationArguments(name, input) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
if (name === 'create_workspace') {
|
|
147
|
+
if (value.engine !== 'mistral-vibe' &&
|
|
148
|
+
(value.reasoning_effort === 'off' || value.brainstorm_reasoning_effort === 'off'))
|
|
149
|
+
throw new Error('off is a native Mistral Vibe thinking value');
|
|
150
|
+
const review = value.auto_loop_final_review;
|
|
151
|
+
if (review && review.engine !== 'mistral-vibe' && review.reasoning_effort === 'off')
|
|
152
|
+
throw new Error('off is a native Mistral Vibe thinking value');
|
|
147
153
|
const { attachments: _attachments, ...metadataFields } = value;
|
|
148
154
|
// Keep room for multipart file headers within the downstream 1 MiB allowance.
|
|
149
155
|
if (new TextEncoder().encode(JSON.stringify(metadataFields)).byteLength > 960 * 1024)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MCP_ATTACHMENTS_SCHEMA, validateMcpAttachments } from './mcp-attachments.js';
|
|
2
|
+
import { parseQuestionSelections } from './question-selections.js';
|
|
2
3
|
const workspace = { type: 'string', minLength: 1, maxLength: 200, description: 'Workspace ID from list_workspaces.' };
|
|
3
4
|
const session = {
|
|
4
5
|
type: 'string',
|
|
@@ -41,10 +42,16 @@ export const WORKSPACE_DIALOGUE_TOOLS = [
|
|
|
41
42
|
},
|
|
42
43
|
}, ['workspace_id'], false),
|
|
43
44
|
tool('get_workspace_questions', 'Read pending questions and whether a human tool approval is blocking the workspace.', { workspace_id: workspace }, ['workspace_id']),
|
|
44
|
-
tool('answer_workspace_question', 'Answer the current pending question using its exact tool_call_id. Answer keys are question IDs for Codex, question text for Claude. Tool permission requests require human approval in Kōbō.', {
|
|
45
|
+
tool('answer_workspace_question', 'Answer the current pending question using its exact tool_call_id. Answer keys are question IDs for Codex/Mistral Vibe, question text for Claude. Native multiple-choice arrays use selections; never split comma-joined display answers. Tool permission requests require human approval in Kōbō.', {
|
|
45
46
|
workspace_id: workspace,
|
|
46
47
|
tool_call_id: { type: 'string', minLength: 1, maxLength: 200 },
|
|
47
|
-
answers: { type: 'object',
|
|
48
|
+
answers: { type: 'object', additionalProperties: { type: 'string' } },
|
|
49
|
+
selections: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
maxProperties: 20,
|
|
52
|
+
propertyNames: { minLength: 1, maxLength: 200 },
|
|
53
|
+
additionalProperties: { type: 'array', maxItems: 32, items: { type: 'string', maxLength: 2000 } },
|
|
54
|
+
},
|
|
48
55
|
}, ['workspace_id', 'tool_call_id', 'answers'], false),
|
|
49
56
|
];
|
|
50
57
|
export const EXTERNAL_MCP_TOOLS = [WORKSPACE_DISCOVERY_TOOL, ...WORKSPACE_DIALOGUE_TOOLS];
|
|
@@ -60,6 +67,8 @@ export function validateDialogueArguments(name, value) {
|
|
|
60
67
|
for (const key of definition.inputSchema.required ?? [])
|
|
61
68
|
if (!Object.hasOwn(args, key))
|
|
62
69
|
throw new Error(`${key} is required`);
|
|
70
|
+
if (name === 'answer_workspace_question')
|
|
71
|
+
parseQuestionSelections(args.selections);
|
|
63
72
|
for (const [key, input] of Object.entries(args)) {
|
|
64
73
|
const property = properties[key];
|
|
65
74
|
if (!Object.hasOwn(properties, key))
|
|
@@ -67,6 +76,9 @@ export function validateDialogueArguments(name, value) {
|
|
|
67
76
|
if (key === 'attachments') {
|
|
68
77
|
validateMcpAttachments(input);
|
|
69
78
|
}
|
|
79
|
+
else if (key === 'selections') {
|
|
80
|
+
// Validated above; native forms may omit all optional fields.
|
|
81
|
+
}
|
|
70
82
|
else if (property.type === 'string') {
|
|
71
83
|
if (typeof input !== 'string' ||
|
|
72
84
|
(property.minLength !== 0 && !input.trim()) ||
|
|
@@ -87,9 +99,8 @@ export function validateDialogueArguments(name, value) {
|
|
|
87
99
|
else if (!input ||
|
|
88
100
|
typeof input !== 'object' ||
|
|
89
101
|
Array.isArray(input) ||
|
|
90
|
-
Object.keys(input).length === 0 ||
|
|
91
102
|
Object.values(input).some((answer) => typeof answer !== 'string' || answer.length > 100_000)) {
|
|
92
|
-
throw new Error(`${key} must be
|
|
103
|
+
throw new Error(`${key} must be an object of string answers`);
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
if (name === 'send_workspace_message') {
|
package/docs/deployment-vps.md
CHANGED
|
@@ -97,7 +97,7 @@ these checks.
|
|
|
97
97
|
|
|
98
98
|
After installation, continue with [opening Kōbō](#2-start-and-open-kōbō) and
|
|
99
99
|
[authenticating agent engines](#3-authenticate-and-choose-a-project). The installer
|
|
100
|
-
does not log you into Claude or
|
|
100
|
+
does not log you into Claude Code, Codex or Mistral Vibe. Obtain the Kōbō browser token through the
|
|
101
101
|
existing startup-log command below. For installer-managed deployments, invoke the
|
|
102
102
|
updater as `/opt/kobo/bin/kobo-update --directory /opt/kobo`; the `./kobo-update`
|
|
103
103
|
examples later in this guide describe the manual installation layout.
|
|
@@ -312,6 +312,11 @@ and login requirements can vary; `claude auth login --help` and `codex login --h
|
|
|
312
312
|
show the supported options. You can also configure your own provider API key.
|
|
313
313
|
No paid agent turn is needed for these setup commands.
|
|
314
314
|
|
|
315
|
+
For **Mistral Vibe**, install Vibe separately in the image/container and authenticate
|
|
316
|
+
as the same agent user. The server must be able to run `vibe-acp`. Follow
|
|
317
|
+
[Use Mistral Vibe inside the container](./vps-projects-and-skills.md#use-mistral-vibe-inside-the-container);
|
|
318
|
+
an installation or login on the host is insufficient.
|
|
319
|
+
|
|
315
320
|
For a personal GitHub account, complete the CLI's browser login flow from your PC,
|
|
316
321
|
then connect Git HTTPS credentials to it:
|
|
317
322
|
|
|
@@ -337,7 +342,9 @@ In the first-run form, **Local project directory** means
|
|
|
337
342
|
nor a folder on the PC running the browser. Then follow [your first mission](./getting-started.md#create-a-mission).
|
|
338
343
|
|
|
339
344
|
The named `kobo-home` volume covers `/root`: it preserves Kōbō's database/settings,
|
|
340
|
-
Claude/Codex state and Git/forge configuration
|
|
345
|
+
Claude/Codex state, Vibe state stored under that home, and Git/forge configuration
|
|
346
|
+
across container replacement. A custom `VIBE_HOME` outside the volume needs its own
|
|
347
|
+
persistence.
|
|
341
348
|
Do not run `docker compose down -v`. Authentication performed only on the host is
|
|
342
349
|
not automatically available inside a container. See [Docker volume persistence](https://docs.docker.com/reference/compose-file/volumes/).
|
|
343
350
|
|
|
@@ -616,7 +623,7 @@ the reservation deliberately has no expiry that could reopen the race. Follow
|
|
|
616
623
|
container and confirm its exit before restarting it. Restart clears the in-memory
|
|
617
624
|
reservation. Never release it while a Docker stop could still be in progress.
|
|
618
625
|
|
|
619
|
-
Claude/Codex configuration and credentials, skills/plugins, caches, browser
|
|
626
|
+
Claude Code/Codex/Mistral Vibe configuration and credentials, skills/plugins, caches, browser
|
|
620
627
|
downloads, repositories/worktrees, bind-mounted App keys/scripts and project
|
|
621
628
|
database volumes are **not included** in `kobo.tar.gz`. Files outside the Kōbō
|
|
622
629
|
directory remain in the existing volume; the updater neither archives nor deletes
|