@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Rebuild only engine CHECK constraints; the migration runner owns the transaction. */
|
|
2
|
+
export function migrateMistralMemoryEngine(db) {
|
|
3
|
+
// Parents precede children so replacement FKs never point at tables being dropped.
|
|
4
|
+
const tables = [
|
|
5
|
+
'memory_entries',
|
|
6
|
+
'memory_budget_contexts',
|
|
7
|
+
'memory_proposals',
|
|
8
|
+
'memory_operations',
|
|
9
|
+
'memory_contexts',
|
|
10
|
+
];
|
|
11
|
+
const definitions = tables.map((name) => ({
|
|
12
|
+
name,
|
|
13
|
+
sql: db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?").get(name)
|
|
14
|
+
.sql,
|
|
15
|
+
}));
|
|
16
|
+
if (definitions.every(({ sql }) => sql.includes("'mistral-vibe'")))
|
|
17
|
+
return;
|
|
18
|
+
const indexes = tables.flatMap((name) => db
|
|
19
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL")
|
|
20
|
+
.all(name));
|
|
21
|
+
const sequence = db
|
|
22
|
+
.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'memory_operations'")
|
|
23
|
+
.safeIntegers()
|
|
24
|
+
.get();
|
|
25
|
+
for (const { name, sql } of definitions) {
|
|
26
|
+
let replacement = sql.replaceAll("'claude-code', 'codex'", "'claude-code', 'codex', 'mistral-vibe'");
|
|
27
|
+
for (const table of tables)
|
|
28
|
+
replacement = replacement.replaceAll(new RegExp(`\\b${table}\\b`, 'g'), `${table}_mistral`);
|
|
29
|
+
db.exec(replacement);
|
|
30
|
+
db.exec(`INSERT INTO ${name}_mistral SELECT * FROM ${name}`);
|
|
31
|
+
}
|
|
32
|
+
for (const name of [...tables].reverse())
|
|
33
|
+
db.exec(`DROP TABLE ${name}`);
|
|
34
|
+
for (const name of tables)
|
|
35
|
+
db.exec(`ALTER TABLE ${name}_mistral RENAME TO ${name}`);
|
|
36
|
+
for (const { sql } of indexes)
|
|
37
|
+
db.exec(sql);
|
|
38
|
+
if (sequence)
|
|
39
|
+
db.prepare("UPDATE sqlite_sequence SET seq = ? WHERE name = 'memory_operations'").run(sequence.seq);
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { listEngines } from '../services/agent/engines/registry.js';
|
|
3
|
+
import { getMistralOptions } from '../services/mistral-options-service.js';
|
|
3
4
|
/** Hono sub-router exposing `GET /` — the list of registered agent engines with capabilities. */
|
|
4
5
|
export const enginesRouter = new Hono();
|
|
5
6
|
enginesRouter.get('/', (c) => c.json(listEngines().map((e) => ({
|
|
@@ -7,3 +8,16 @@ enginesRouter.get('/', (c) => c.json(listEngines().map((e) => ({
|
|
|
7
8
|
displayName: e.displayName,
|
|
8
9
|
capabilities: e.capabilities,
|
|
9
10
|
}))));
|
|
11
|
+
enginesRouter.get('/mistral-vibe/options', async (c) => {
|
|
12
|
+
try {
|
|
13
|
+
return c.json(await getMistralOptions({
|
|
14
|
+
workspaceId: c.req.query('workspaceId'),
|
|
15
|
+
projectPath: c.req.query('projectPath'),
|
|
16
|
+
model: c.req.query('model'),
|
|
17
|
+
agentPermissionMode: c.req.query('agentPermissionMode'),
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return c.json({ error: error instanceof Error ? error.message : 'Native Vibe options unavailable' }, 400);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -4,7 +4,7 @@ const app = new Hono();
|
|
|
4
4
|
app.get('/', async (c) => {
|
|
5
5
|
const engine = c.req.query('engine') ?? 'claude-code';
|
|
6
6
|
const projectPath = c.req.query('projectPath')?.trim();
|
|
7
|
-
if (!['claude-code', 'codex'].includes(engine) ||
|
|
7
|
+
if (!['claude-code', 'codex', 'mistral-vibe'].includes(engine) ||
|
|
8
8
|
(projectPath && (projectPath.length > 4096 || projectPath.includes('\0')))) {
|
|
9
9
|
return c.json({ error: 'Invalid environment check request' }, 400);
|
|
10
10
|
}
|
|
@@ -23,7 +23,11 @@ function integrationConfigured(integration, key) {
|
|
|
23
23
|
}
|
|
24
24
|
async function checkCli(binary) {
|
|
25
25
|
try {
|
|
26
|
-
const { stdout } = await execFileAsync(binary, ['--version'], {
|
|
26
|
+
const { stdout } = await execFileAsync(binary, ['--version'], {
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
timeout: 2_000,
|
|
29
|
+
maxBuffer: 64 * 1024,
|
|
30
|
+
});
|
|
27
31
|
return { available: true, version: stdout.trim() || null };
|
|
28
32
|
}
|
|
29
33
|
catch {
|
|
@@ -78,7 +82,13 @@ app.get('/report', async (c) => {
|
|
|
78
82
|
const db = getDb();
|
|
79
83
|
const dbPath = getDbPath();
|
|
80
84
|
const home = getKoboHome();
|
|
81
|
-
const cliChecks = Promise.all([
|
|
85
|
+
const cliChecks = Promise.all([
|
|
86
|
+
checkClaudeCli(),
|
|
87
|
+
checkCodexCli(),
|
|
88
|
+
checkCli('vibe-acp'),
|
|
89
|
+
checkCli('gh'),
|
|
90
|
+
checkCli('glab'),
|
|
91
|
+
]);
|
|
82
92
|
// DB schema version
|
|
83
93
|
const row = db.prepare('SELECT MAX(version) as v FROM schema_migrations').get();
|
|
84
94
|
const dbSchemaVersion = row?.v ?? 0;
|
|
@@ -170,7 +180,7 @@ app.get('/report', async (c) => {
|
|
|
170
180
|
.all();
|
|
171
181
|
const settingsRow = db.prepare('SELECT COUNT(*) as n FROM workspaces').get();
|
|
172
182
|
const archivedRow = db.prepare('SELECT COUNT(*) as n FROM workspaces WHERE archived_at IS NOT NULL').get();
|
|
173
|
-
const [claudeCli, codexCli, ghCli, glabCli] = await cliChecks;
|
|
183
|
+
const [claudeCli, codexCli, mistralCli, ghCli, glabCli] = await cliChecks;
|
|
174
184
|
const report = {
|
|
175
185
|
version: getPackageVersion(),
|
|
176
186
|
koboHome: home,
|
|
@@ -183,6 +193,7 @@ app.get('/report', async (c) => {
|
|
|
183
193
|
settings: { schemaVersion: SETTINGS_SCHEMA_VERSION },
|
|
184
194
|
claudeCli,
|
|
185
195
|
codexCli,
|
|
196
|
+
mistralCli,
|
|
186
197
|
forgeCli: { gh: ghCli, glab: glabCli },
|
|
187
198
|
workspaces: {
|
|
188
199
|
total: settingsRow.n,
|
|
@@ -3,7 +3,7 @@ import { isMemoryMode } from '../../shared/memory.js';
|
|
|
3
3
|
import { isValidSkillSuite } from '../../shared/skill-suite-prompts.js';
|
|
4
4
|
import { isWorkflowPolicy } from '../../shared/workflow-policy.js';
|
|
5
5
|
import { getDb } from '../db/index.js';
|
|
6
|
-
import { getBackendPort } from '../services/agent/orchestrator.js';
|
|
6
|
+
import { getBackendPort, notifyCapacityAvailable } from '../services/agent/orchestrator.js';
|
|
7
7
|
import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT, } from '../services/initial-prompt-template-service.js';
|
|
8
8
|
import { getMcpConnectionInfo } from '../services/mcp-connection-info-service.js';
|
|
9
9
|
import { generateToken, getLanUrls } from '../services/network-access-service.js';
|
|
@@ -167,7 +167,13 @@ app.put('/global', async (c) => {
|
|
|
167
167
|
return c.json({ error: 'Invalid workflowPolicy' }, 400);
|
|
168
168
|
if (body.memoryMode !== undefined && !isMemoryMode(body.memoryMode))
|
|
169
169
|
return c.json({ error: 'Invalid memoryMode' }, 400);
|
|
170
|
+
if ('maxConcurrentAgents' in body &&
|
|
171
|
+
(!Number.isSafeInteger(body.maxConcurrentAgents) || body.maxConcurrentAgents < 0))
|
|
172
|
+
return c.json({ error: 'maxConcurrentAgents must be a non-negative safe integer' }, 400);
|
|
173
|
+
const previousLimit = 'maxConcurrentAgents' in body ? settingsService.getGlobalSettings().maxConcurrentAgents : undefined;
|
|
170
174
|
const updated = settingsService.updateGlobalSettings(body);
|
|
175
|
+
if (previousLimit && (updated.maxConcurrentAgents === 0 || updated.maxConcurrentAgents > previousLimit))
|
|
176
|
+
notifyCapacityAvailable();
|
|
171
177
|
// The client assigns this response straight into its store, so echoing the
|
|
172
178
|
// real credentials back would undo the masking on GET the first time
|
|
173
179
|
// anyone saves anything.
|
|
@@ -4,9 +4,10 @@ import { nanoid } from 'nanoid';
|
|
|
4
4
|
import { isMergeMethod } from '../../shared/forge-merge.js';
|
|
5
5
|
import { isWorkflowPolicy, resolveWorkflowPolicy, WORKFLOW_ACTIONS, } from '../../shared/workflow-policy.js';
|
|
6
6
|
import * as finalReviewService from '../services/auto-loop-final-review-service.js';
|
|
7
|
+
import { preflightMistralConfiguration } from '../services/mistral-options-service.js';
|
|
7
8
|
import { getReviewReturnStatus } from '../services/review-return-service.js';
|
|
8
9
|
import { getSearchIndexStatus, searchEvents } from '../services/search-service.js';
|
|
9
|
-
import { createSessionHandoff, decideSessionHandoff, getCurrentSessionHandoff, SessionHandoffError, submitSessionHandoff, waitForSessionHandoff, } from '../services/session-handoff-service.js';
|
|
10
|
+
import { createSessionHandoff, decideSessionHandoff, getCurrentSessionHandoff, preflightSessionHandoff, SessionHandoffError, submitSessionHandoff, waitForSessionHandoff, } from '../services/session-handoff-service.js';
|
|
10
11
|
import { AgentStopError, assertAgentStopped } from '../utils/agent-stop-result.js';
|
|
11
12
|
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
12
13
|
import { assertWorkspaceLifecycleAvailable, isWorkspaceLifecycleBusy, workspaceLifecycleReason, } from '../utils/workspace-lifecycle-guard.js';
|
|
@@ -18,9 +19,11 @@ import { Hono } from 'hono';
|
|
|
18
19
|
import { bodyLimit } from 'hono/body-limit';
|
|
19
20
|
import { MAX_ATTACHMENT_REQUEST_BYTES } from '../../shared/attachments.js';
|
|
20
21
|
import { AUTO_LOOP_HARD_RULES, buildAutoLoopGroomingSteps, buildGroomingIntro } from '../../shared/auto-loop-prompts.js';
|
|
22
|
+
import { parseQuestionSelections } from '../../shared/question-selections.js';
|
|
21
23
|
import { getDb } from '../db/index.js';
|
|
22
24
|
import { migrationGuard } from '../middleware/migration-guard.js';
|
|
23
25
|
import { workspaceCreationMaintenance } from '../middleware/workspace-creation-maintenance.js';
|
|
26
|
+
import { assertEngineConfigurationSupported } from '../services/agent/engines/configuration.js';
|
|
24
27
|
import { listEngines } from '../services/agent/engines/registry.js';
|
|
25
28
|
import * as agentManager from '../services/agent/orchestrator.js';
|
|
26
29
|
import * as archiveScriptService from '../services/archive-script-service.js';
|
|
@@ -316,7 +319,7 @@ function resolveCreateAgentPermissionMode(bodyValue, _projectPath, globalSetting
|
|
|
316
319
|
const global = globalSettings.defaultPermissionModeByEngine?.[engineId];
|
|
317
320
|
if (isAgentPermissionMode(global))
|
|
318
321
|
return global;
|
|
319
|
-
return 'bypass';
|
|
322
|
+
return engineId === 'mistral-vibe' ? 'interactive' : 'bypass';
|
|
320
323
|
}
|
|
321
324
|
/**
|
|
322
325
|
* Extract the numeric PR/MR number from a forge URL.
|
|
@@ -357,7 +360,9 @@ function handoffError(c, error) {
|
|
|
357
360
|
}
|
|
358
361
|
app.post('/:id/session-handoffs', migrationGuard, async (c) => {
|
|
359
362
|
try {
|
|
360
|
-
|
|
363
|
+
const input = await c.req.json();
|
|
364
|
+
await preflightSessionHandoff(c.req.param('id'), input);
|
|
365
|
+
return c.json({ handoff: createSessionHandoff(c.req.param('id'), input) }, 202);
|
|
361
366
|
}
|
|
362
367
|
catch (error) {
|
|
363
368
|
return handoffError(c, error);
|
|
@@ -418,7 +423,8 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
418
423
|
const engine = listEngines().find((item) => item.id === body.engine);
|
|
419
424
|
if (!engine)
|
|
420
425
|
return c.json({ error: `Unknown engine '${body.engine}'` }, 400);
|
|
421
|
-
const model = typeof body.model === 'string' &&
|
|
426
|
+
const model = typeof body.model === 'string' &&
|
|
427
|
+
(engine.id === 'mistral-vibe' || engine.capabilities.models.some((item) => item.id === body.model))
|
|
422
428
|
? body.model
|
|
423
429
|
: engine.capabilities.models[0]?.id;
|
|
424
430
|
if (!model)
|
|
@@ -428,6 +434,21 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
428
434
|
return c.json({ error: `Engine '${body.engine}' does not support the selected permission mode` }, 400);
|
|
429
435
|
}
|
|
430
436
|
const effort = typeof body.reasoningEffort === 'string' ? body.reasoningEffort : 'auto';
|
|
437
|
+
try {
|
|
438
|
+
assertEngineConfigurationSupported(engine, {
|
|
439
|
+
model: body.model,
|
|
440
|
+
reasoningEffort: body.reasoningEffort,
|
|
441
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
442
|
+
});
|
|
443
|
+
await preflightMistralConfiguration(engine.id, { workspaceId: workspace.id }, {
|
|
444
|
+
model: body.model,
|
|
445
|
+
reasoningEffort: body.reasoningEffort,
|
|
446
|
+
agentPermissionMode: body.agentPermissionMode,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
451
|
+
}
|
|
431
452
|
const handoff = typeof body.handoff === 'string' && body.handoff.trim()
|
|
432
453
|
? body.handoff.trim().slice(0, 30_000)
|
|
433
454
|
: await buildEngineHandoff(workspace, workspace.engine, body.engine);
|
|
@@ -544,6 +565,48 @@ app.post('/', workspaceCreationMaintenance, migrationGuard, creationBodyLimit, a
|
|
|
544
565
|
}
|
|
545
566
|
}
|
|
546
567
|
}
|
|
568
|
+
if (body.engine === 'mistral-vibe' &&
|
|
569
|
+
body.agentPermissionMode !== undefined &&
|
|
570
|
+
!isAgentPermissionMode(body.agentPermissionMode))
|
|
571
|
+
return c.json({ error: 'Invalid agentPermissionMode', step: 'validate' }, 400);
|
|
572
|
+
const selectedEngine = listEngines().find((engine) => engine.id === (body.engine ?? 'claude-code'));
|
|
573
|
+
if (selectedEngine) {
|
|
574
|
+
try {
|
|
575
|
+
const mode = resolveCreateAgentPermissionMode(body.agentPermissionMode, body.projectPath, settingsService.getGlobalSettings(), selectedEngine.id);
|
|
576
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
577
|
+
model: body.model,
|
|
578
|
+
reasoningEffort: body.reasoningEffort,
|
|
579
|
+
agentPermissionMode: mode,
|
|
580
|
+
});
|
|
581
|
+
await preflightMistralConfiguration(selectedEngine.id, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
582
|
+
model: body.model,
|
|
583
|
+
reasoningEffort: body.reasoningEffort,
|
|
584
|
+
agentPermissionMode: mode,
|
|
585
|
+
});
|
|
586
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
587
|
+
model: body.brainstormModel,
|
|
588
|
+
reasoningEffort: body.brainstormReasoningEffort,
|
|
589
|
+
});
|
|
590
|
+
await preflightMistralConfiguration(selectedEngine.id, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
591
|
+
model: body.brainstormModel ?? body.model,
|
|
592
|
+
reasoningEffort: body.brainstormReasoningEffort ?? body.reasoningEffort,
|
|
593
|
+
agentPermissionMode: mode,
|
|
594
|
+
});
|
|
595
|
+
if (body.autoLoopFinalReview) {
|
|
596
|
+
const review = finalReviewService.parseFinalReviewConfiguration(body.autoLoopFinalReview);
|
|
597
|
+
await preflightMistralConfiguration(review.engine, { projectPath: body.worktreePath ?? body.projectPath }, {
|
|
598
|
+
...review,
|
|
599
|
+
agentPermissionMode: 'interactive',
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
body.agentPermissionMode = mode;
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
const message = error instanceof Error ? error.message : 'Invalid engine configuration';
|
|
606
|
+
emitCreateFailed(creationId, 'validate', message);
|
|
607
|
+
return c.json({ error: message, step: 'validate' }, 400);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
547
610
|
// Fetch the source branch from origin first. On failure this is non-fatal:
|
|
548
611
|
// fall back to the local source branch if it exists (offline / no remote),
|
|
549
612
|
// and only hard-block (422) when neither origin nor a local branch is usable.
|
|
@@ -1745,7 +1808,10 @@ app.get('/:id/auto-loop/final-review', (c) => {
|
|
|
1745
1808
|
app.patch('/:id/auto-loop/final-review', migrationGuard, async (c) => {
|
|
1746
1809
|
try {
|
|
1747
1810
|
const body = await c.req.json();
|
|
1748
|
-
|
|
1811
|
+
const config = finalReviewService.parseFinalReviewConfiguration(body?.configuration);
|
|
1812
|
+
if (config)
|
|
1813
|
+
await preflightMistralConfiguration(config.engine, { workspaceId: c.req.param('id') }, { ...config, agentPermissionMode: 'interactive' });
|
|
1814
|
+
return c.json(finalReviewService.configureFinalReview(c.req.param('id'), config));
|
|
1749
1815
|
}
|
|
1750
1816
|
catch (error) {
|
|
1751
1817
|
return c.json({ error: error instanceof Error ? error.message : 'Invalid request' }, error instanceof ReviewRequestError ? error.status : 400);
|
|
@@ -2080,6 +2146,7 @@ app.post('/:id/deferred-tool-use/answer', async (c) => {
|
|
|
2080
2146
|
await agentManager.answerPendingQuestion(id, body.answers, body.toolCallId, {
|
|
2081
2147
|
awaitingFreeForm: body.awaitingFreeForm === true,
|
|
2082
2148
|
...(typeof body.response === 'string' ? { response: body.response } : {}),
|
|
2149
|
+
...(body.selections !== undefined ? { selections: parseQuestionSelections(body.selections) } : {}),
|
|
2083
2150
|
});
|
|
2084
2151
|
return c.json({ ok: true });
|
|
2085
2152
|
}
|
|
@@ -2871,6 +2938,25 @@ app.patch('/:id', migrationGuard, async (c) => {
|
|
|
2871
2938
|
}, 400);
|
|
2872
2939
|
}
|
|
2873
2940
|
}
|
|
2941
|
+
const selectedEngine = listEngines().find((engine) => engine.id === workspace.engine);
|
|
2942
|
+
if (selectedEngine &&
|
|
2943
|
+
(body.model !== undefined || body.reasoningEffort !== undefined || body.agentPermissionMode !== undefined)) {
|
|
2944
|
+
try {
|
|
2945
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
2946
|
+
model: body.model ?? workspace.model,
|
|
2947
|
+
reasoningEffort: body.reasoningEffort ?? workspace.reasoningEffort,
|
|
2948
|
+
agentPermissionMode: body.agentPermissionMode ?? workspace.agentPermissionMode,
|
|
2949
|
+
});
|
|
2950
|
+
await preflightMistralConfiguration(selectedEngine.id, { workspaceId: workspace.id }, {
|
|
2951
|
+
model: body.model ?? workspace.model,
|
|
2952
|
+
reasoningEffort: body.reasoningEffort ?? workspace.reasoningEffort,
|
|
2953
|
+
agentPermissionMode: body.agentPermissionMode ?? workspace.agentPermissionMode,
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
catch (error) {
|
|
2957
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2874
2960
|
if (body.status !== undefined) {
|
|
2875
2961
|
if (!isWorkspaceStatus(body.status)) {
|
|
2876
2962
|
return c.json({ error: `Invalid status. Must be one of: ${VALID_WORKSPACE_STATUSES.join(', ')}` }, 400);
|
|
@@ -3415,6 +3501,24 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3415
3501
|
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
3416
3502
|
}, 409);
|
|
3417
3503
|
}
|
|
3504
|
+
const selectedEngine = listEngines().find((engine) => engine.id === workspaceEngine);
|
|
3505
|
+
if (selectedEngine) {
|
|
3506
|
+
try {
|
|
3507
|
+
assertEngineConfigurationSupported(selectedEngine, {
|
|
3508
|
+
model: workspace.model,
|
|
3509
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
3510
|
+
agentPermissionMode: workspace.agentPermissionMode,
|
|
3511
|
+
});
|
|
3512
|
+
await preflightMistralConfiguration(selectedEngine.id, { workspaceId: workspace.id }, {
|
|
3513
|
+
model: workspace.model,
|
|
3514
|
+
reasoningEffort: workspace.reasoningEffort,
|
|
3515
|
+
agentPermissionMode: workspace.agentPermissionMode,
|
|
3516
|
+
}, resume);
|
|
3517
|
+
}
|
|
3518
|
+
catch (error) {
|
|
3519
|
+
return c.json({ error: error instanceof Error ? error.message : 'Invalid engine configuration' }, 400);
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3418
3522
|
// A manual start supersedes any scheduled retry (quota or transient
|
|
3419
3523
|
// watchdog recovery). Leaving its timer armed could spawn a second agent
|
|
3420
3524
|
// after the user has already resumed the workspace.
|
|
@@ -4258,7 +4362,8 @@ app.post('/:id/merge-pr', async (c) => {
|
|
|
4258
4362
|
code: `forge_${availability.reason ?? 'unavailable'}`,
|
|
4259
4363
|
}, 409);
|
|
4260
4364
|
}
|
|
4261
|
-
|
|
4365
|
+
// Record OPEN before merging, even if the watcher has not run since startup.
|
|
4366
|
+
const snapshot = await refreshPrSnapshot(id);
|
|
4262
4367
|
if (snapshot?.state !== 'OPEN') {
|
|
4263
4368
|
return c.json({ error: 'No open PR found for this workspace', code: 'pr_not_open' }, 409);
|
|
4264
4369
|
}
|
|
@@ -4302,13 +4407,15 @@ app.post('/:id/delete-remote-branch', async (c) => {
|
|
|
4302
4407
|
if (!provider.capabilities.canDeleteRemoteBranch) {
|
|
4303
4408
|
return c.json({ error: 'This project cannot delete remote branches', code: 'forge_unsupported' }, 409);
|
|
4304
4409
|
}
|
|
4305
|
-
|
|
4410
|
+
// The merge watcher may have already archived and purged this worktree.
|
|
4411
|
+
const repoPath = workspace.projectPath;
|
|
4412
|
+
const availability = await provider.isAvailable(repoPath);
|
|
4306
4413
|
if (!availability.available)
|
|
4307
4414
|
return c.json({ error: 'Forge CLI unavailable', code: 'forge_unavailable' }, 409);
|
|
4308
|
-
const snapshot = await provider.getPrStatus(
|
|
4415
|
+
const snapshot = await provider.getPrStatus(repoPath, workspace.workingBranch);
|
|
4309
4416
|
if (snapshot?.state !== 'MERGED')
|
|
4310
4417
|
return c.json({ error: 'The PR is not merged yet', code: 'pr_not_merged' }, 409);
|
|
4311
|
-
await provider.deleteRemoteBranch(
|
|
4418
|
+
await provider.deleteRemoteBranch(repoPath, workspace.workingBranch);
|
|
4312
4419
|
return c.json({ success: true });
|
|
4313
4420
|
}
|
|
4314
4421
|
catch (err) {
|
|
@@ -18,6 +18,8 @@ export const CLAUDE_CODE_CAPABILITIES = {
|
|
|
18
18
|
permissionModes: ['plan', 'bypass', 'strict', 'interactive'],
|
|
19
19
|
supportsResume: true,
|
|
20
20
|
supportsMcp: true,
|
|
21
|
+
supportsLiveSteering: true,
|
|
22
|
+
supportsReadOnly: true,
|
|
21
23
|
supportsSkills: true,
|
|
22
24
|
supportsSubagents: true,
|
|
23
25
|
supportsQuotaStatus: true,
|
|
@@ -1049,6 +1049,9 @@ export function createClaudeCodeEngine() {
|
|
|
1049
1049
|
// swallow — best effort
|
|
1050
1050
|
}
|
|
1051
1051
|
},
|
|
1052
|
+
hasPendingUserInput(toolCallId) {
|
|
1053
|
+
return pendingResolvers.has(toolCallId);
|
|
1054
|
+
},
|
|
1052
1055
|
resolvePendingUserInput(toolCallId, response) {
|
|
1053
1056
|
const resolver = pendingResolvers.get(toolCallId);
|
|
1054
1057
|
if (!resolver)
|
|
@@ -13,6 +13,8 @@ export const CODEX_CAPABILITIES = {
|
|
|
13
13
|
permissionModes: ['plan', 'bypass', 'strict', 'interactive'],
|
|
14
14
|
supportsResume: true,
|
|
15
15
|
supportsMcp: true,
|
|
16
|
+
supportsLiveSteering: true,
|
|
17
|
+
supportsReadOnly: true,
|
|
16
18
|
supportsSkills: false,
|
|
17
19
|
supportsSubagents: true,
|
|
18
20
|
supportsQuotaStatus: true,
|
|
@@ -692,6 +692,9 @@ export function createCodexEngine() {
|
|
|
692
692
|
}
|
|
693
693
|
await terminateChild();
|
|
694
694
|
},
|
|
695
|
+
hasPendingUserInput(callId) {
|
|
696
|
+
return pendingByCallId.has(callId);
|
|
697
|
+
},
|
|
695
698
|
resolvePendingUserInput(callId, response) {
|
|
696
699
|
const pending = pendingByCallId.get(callId);
|
|
697
700
|
if (!pending)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { assertMistralSelection } from './mistral-vibe/configuration.js';
|
|
2
|
+
/** Validate native selections before creating or replacing launch state. */
|
|
3
|
+
export function assertEngineConfigurationSupported(engine, selection) {
|
|
4
|
+
if (selection.readOnly && !engine.capabilities.supportsReadOnly)
|
|
5
|
+
throw new Error(`Engine '${engine.id}' does not support read-only launches`);
|
|
6
|
+
// Native membership is checked contextually and again on the actual ACP session.
|
|
7
|
+
if (engine.id !== 'mistral-vibe')
|
|
8
|
+
return;
|
|
9
|
+
const mode = selection.agentPermissionMode ?? 'interactive';
|
|
10
|
+
if (!engine.capabilities.permissionModes.some((item) => item === mode))
|
|
11
|
+
throw new Error(`Engine '${engine.id}' does not support agentPermissionMode '${String(mode)}'`);
|
|
12
|
+
assertMistralSelection(selection.model, 'model');
|
|
13
|
+
assertMistralSelection(selection.reasoningEffort, 'thinking');
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MISTRAL_MODELS } from '../../../../../shared/mistral-models.js';
|
|
2
|
+
export const MISTRAL_CAPABILITIES = {
|
|
3
|
+
models: MISTRAL_MODELS.map((model) => ({ id: model.id, label: model.label })),
|
|
4
|
+
effortLevels: [{ id: 'auto', label: 'Auto' }],
|
|
5
|
+
permissionModes: ['interactive', 'strict', 'bypass'],
|
|
6
|
+
supportsResume: true,
|
|
7
|
+
supportsMcp: true,
|
|
8
|
+
supportsLiveSteering: false,
|
|
9
|
+
supportsReadOnly: false,
|
|
10
|
+
supportsSkills: false,
|
|
11
|
+
supportsSubagents: false,
|
|
12
|
+
supportsQuotaStatus: false,
|
|
13
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Readable, Writable } from 'node:stream';
|
|
2
|
+
import * as acp from '@agentclientprotocol/sdk';
|
|
3
|
+
/** The SDK owns framing and incoming validation. Stderr never enters ACP. */
|
|
4
|
+
export function connectVibeAcp(child, handlers) {
|
|
5
|
+
if (!child.stdin || !child.stdout)
|
|
6
|
+
throw new Error('Vibe ACP requires piped stdin and stdout');
|
|
7
|
+
const app = acp.client({ name: 'kobo' });
|
|
8
|
+
if (handlers.sessionUpdate)
|
|
9
|
+
app.onNotification(acp.methods.client.session.update, handlers.sessionUpdate);
|
|
10
|
+
if (handlers.requestPermission)
|
|
11
|
+
app.onRequest(acp.methods.client.session.requestPermission, handlers.requestPermission);
|
|
12
|
+
if (handlers.createElicitation)
|
|
13
|
+
app.onRequest(acp.methods.client.elicitation.create, handlers.createElicitation);
|
|
14
|
+
// Node and DOM disagree on BYOB generics; both adapters are standard Web Streams at runtime.
|
|
15
|
+
const readable = Readable.toWeb(child.stdout);
|
|
16
|
+
return app.connect(acp.ndJsonStream(Writable.toWeb(child.stdin), readable));
|
|
17
|
+
}
|
|
18
|
+
/** Fluent SDK responses are unchecked; validate the fields this transport consumes. */
|
|
19
|
+
function validateResponse(method, response) {
|
|
20
|
+
const value = response;
|
|
21
|
+
if (method === acp.methods.agent.initialize && value?.protocolVersion !== acp.PROTOCOL_VERSION) {
|
|
22
|
+
throw new Error('Invalid Vibe ACP protocol version; ACP v1 is required');
|
|
23
|
+
}
|
|
24
|
+
if (method === acp.methods.agent.session.new &&
|
|
25
|
+
(typeof value?.sessionId !== 'string' || value.sessionId.trim().length === 0)) {
|
|
26
|
+
throw new Error('Invalid Vibe ACP session identity');
|
|
27
|
+
}
|
|
28
|
+
if (method === acp.methods.agent.session.prompt &&
|
|
29
|
+
(typeof value?.stopReason !== 'string' ||
|
|
30
|
+
!['end_turn', 'max_tokens', 'max_turn_requests', 'refusal', 'cancelled'].includes(value.stopReason))) {
|
|
31
|
+
throw new Error('Invalid Vibe ACP prompt stop reason');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Use for all requests: initialization/setup are bounded, prompts have no absolute deadline. */
|
|
35
|
+
export async function requestVibeAcp(connection, method, params) {
|
|
36
|
+
const timeout = method === acp.methods.agent.initialize ? 20000 : 30000;
|
|
37
|
+
let timer;
|
|
38
|
+
const pending = connection.agent.request(method, params);
|
|
39
|
+
try {
|
|
40
|
+
const response = await (method === acp.methods.agent.session.prompt
|
|
41
|
+
? pending
|
|
42
|
+
: Promise.race([
|
|
43
|
+
pending,
|
|
44
|
+
new Promise((_resolve, reject) => {
|
|
45
|
+
timer = setTimeout(() => {
|
|
46
|
+
const error = new Error(`Vibe ACP ${method} timed out after ${timeout}ms`);
|
|
47
|
+
reject(error);
|
|
48
|
+
}, timeout);
|
|
49
|
+
}),
|
|
50
|
+
]));
|
|
51
|
+
validateResponse(method, response);
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Pass as runtime.stop's callback; the runtime still ends stdin and confirms process exit. */
|
|
59
|
+
export async function closeVibeAcpSession(connection, session) {
|
|
60
|
+
if (session.active)
|
|
61
|
+
await connection.agent.notify(acp.methods.agent.session.cancel, { sessionId: session.sessionId });
|
|
62
|
+
if (session.canClose)
|
|
63
|
+
await requestVibeAcp(connection, acp.methods.agent.session.close, { sessionId: session.sessionId });
|
|
64
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const MISTRAL_MODES = { interactive: 'ask', strict: 'accept-edits', bypass: 'auto-approve' };
|
|
2
|
+
export function assertMistralSelection(value, label) {
|
|
3
|
+
if (value !== undefined && !text(value))
|
|
4
|
+
throw new Error(`Invalid Mistral Vibe ${label}; expected a nonempty native value of at most 256 characters`);
|
|
5
|
+
}
|
|
6
|
+
function record(value) {
|
|
7
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function text(value, max = 256) {
|
|
10
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Native identities and labels must exclude control characters.
|
|
11
|
+
return typeof value === 'string' && !!value.trim() && value.length <= max && !/[\x00-\x1f\x7f]/.test(value);
|
|
12
|
+
}
|
|
13
|
+
function invalid() {
|
|
14
|
+
throw new Error('Invalid Vibe ACP configuration options');
|
|
15
|
+
}
|
|
16
|
+
/** SDK responses are unchecked. Keep only bounded, validated native display fields. */
|
|
17
|
+
export function readMistralSelect(response, id, required = true) {
|
|
18
|
+
if (!record(response) || !Array.isArray(response.configOptions) || response.configOptions.length > 64)
|
|
19
|
+
return invalid();
|
|
20
|
+
const ids = new Set();
|
|
21
|
+
for (const item of response.configOptions) {
|
|
22
|
+
if (!record(item) || !text(item.id) || ids.has(item.id))
|
|
23
|
+
return invalid();
|
|
24
|
+
ids.add(item.id);
|
|
25
|
+
}
|
|
26
|
+
const selected = response.configOptions.find((item) => item.id === id);
|
|
27
|
+
if (!selected && !required)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (selected?.type !== 'select' ||
|
|
30
|
+
!text(selected.currentValue) ||
|
|
31
|
+
!Array.isArray(selected.options) ||
|
|
32
|
+
selected.options.length > 256)
|
|
33
|
+
return invalid();
|
|
34
|
+
const options = [];
|
|
35
|
+
const values = new Set();
|
|
36
|
+
const groups = new Set();
|
|
37
|
+
const append = (item, group) => {
|
|
38
|
+
if (!record(item) ||
|
|
39
|
+
!text(item.value) ||
|
|
40
|
+
!text(item.name) ||
|
|
41
|
+
values.has(item.value) ||
|
|
42
|
+
(item.description != null && !text(item.description, 2048)) ||
|
|
43
|
+
options.length >= 256)
|
|
44
|
+
return invalid();
|
|
45
|
+
values.add(item.value);
|
|
46
|
+
options.push({
|
|
47
|
+
value: item.value,
|
|
48
|
+
name: item.name,
|
|
49
|
+
...(typeof item.description === 'string' ? { description: item.description } : {}),
|
|
50
|
+
...(group ? { group } : {}),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
for (const item of selected.options) {
|
|
54
|
+
if (record(item) && 'options' in item) {
|
|
55
|
+
if (!text(item.group) ||
|
|
56
|
+
!text(item.name) ||
|
|
57
|
+
groups.has(item.group) ||
|
|
58
|
+
!Array.isArray(item.options) ||
|
|
59
|
+
item.options.length > 256)
|
|
60
|
+
return invalid();
|
|
61
|
+
groups.add(item.group);
|
|
62
|
+
for (const option of item.options)
|
|
63
|
+
append(option, item.name);
|
|
64
|
+
}
|
|
65
|
+
else
|
|
66
|
+
append(item);
|
|
67
|
+
}
|
|
68
|
+
if (!values.has(selected.currentValue))
|
|
69
|
+
return invalid();
|
|
70
|
+
return { currentValue: selected.currentValue, options };
|
|
71
|
+
}
|
|
72
|
+
export function requireMistralOption(response, id, value, confirmed = false) {
|
|
73
|
+
const option = readMistralSelect(response, id);
|
|
74
|
+
if (!option.options.some((item) => item.value === value) || (confirmed && option.currentValue !== value))
|
|
75
|
+
throw new Error(`Mistral Vibe ${id} selection is unavailable or overridden by the native profile; refresh native options`);
|
|
76
|
+
}
|