@loicngr/kobo 1.11.36 → 1.11.38
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 +18 -1
- package/CHANGELOG.md +8 -0
- package/dist/server/index.js +20 -24
- package/dist/server/routes/dev-server.js +2 -1
- package/dist/server/routes/pull-requests.js +5 -4
- package/dist/server/routes/workspaces.js +161 -109
- package/dist/server/services/agent/engines/claude-code/engine.js +11 -0
- package/dist/server/services/agent/engines/codex/client.js +1 -0
- package/dist/server/services/agent/engines/codex/engine.js +47 -10
- package/dist/server/services/agent/engines/codex/event-mapper.js +8 -2
- package/dist/server/services/agent/engines/codex/jsonrpc/peer.js +21 -8
- package/dist/server/services/agent/engines/codex/jsonrpc/transport.js +30 -3
- package/dist/server/services/agent/event-router.js +1 -1
- package/dist/server/services/agent/orchestrator.js +219 -78
- package/dist/server/services/agent/session-controller.js +12 -2
- package/dist/server/services/auto-loop-service.js +60 -6
- package/dist/server/services/change-source-branch-service.js +36 -4
- package/dist/server/services/cron-service.js +19 -0
- package/dist/server/services/db-backup-service.js +30 -0
- package/dist/server/services/dev-server-service.js +167 -38
- package/dist/server/services/image-service.js +25 -28
- package/dist/server/services/pr-checkout-service.js +34 -5
- package/dist/server/services/pr-watcher-service.js +5 -3
- package/dist/server/services/quota-backoff-service.js +25 -8
- package/dist/server/services/wakeup-service.js +23 -3
- package/dist/server/services/websocket-service.js +10 -2
- package/dist/server/services/workspace-service.js +8 -4
- package/dist/server/services/worktree-purge-service.js +3 -7
- package/dist/server/utils/agent-stop-result.js +12 -0
- package/dist/server/utils/git-ops.js +8 -1
- package/dist/server/utils/git-worktree-fingerprint.js +86 -0
- package/dist/server/utils/safe-path.js +21 -0
- package/dist/server/utils/script-runner.js +32 -9
- package/dist/server/utils/workspace-lifecycle-guard.js +27 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BzzvcOmq.js +8 -0
- package/src/client/dist/pwa/assets/{ActivityFeed-Bakgvay6.css → ActivityFeed-Chsw1Mh7.css} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BFGYQ6eY.js → ChangelogPage-D8563008.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-Bd0X08sg.js +2 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-A3pVradx.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-Bon8qIka.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-D8g7_r4S.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-DYxdxEul.js +1 -0
- package/src/client/dist/pwa/assets/{DiffViewer-DSlSAIiW.js → DiffViewer-BpsLNxnj.js} +3 -3
- package/src/client/dist/pwa/assets/{DocumentsPanel-C1eXTw_Q.js → DocumentsPanel-BJsJ0fNs.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BxYpUb4y.js → DrawerToggleButton-BVLO5K5d.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-DnBWSdVp.js → GitPanel-Cal_wzC0.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-rX1TiaP9.js → HealthPage-RFZS7Lao.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-Ceuh5uJj.js → MainLayout-vvyiRcql.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnDropdown-K7syiyyH.js → QBtnDropdown-X8oNU5Mb.js} +1 -1
- package/src/client/dist/pwa/assets/{QDrawer-DapzE8DH.js → QDrawer-Cij17skX.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-Bodml89k.js → QSelect-xd-AlGo_.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-K-9OdDfq.js → QTooltip-CwcCGSWq.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-CfumTzAa.js → SchedulePanel-CXRUsz6G.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-AhZYS-dS.js → SearchPage-DopbgLxh.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-BoHXWvKn.js → SettingsPage-Jx6TnOzR.js} +1 -1
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-3wK11mcu.js → SplitWorkspacePage-D7lGFRKs.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CKg0BeNK.js → TerminalPanel-DI8DHb9e.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-D6ctyPYf.js → TouchPan-B3nrkCY8.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Dn4Dy_kH.js → TourReplayButton-DU1UShMY.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-DnoabsfK.js → WorkspaceAttentionLabels-C-yN5IX8.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-DYQrfeYw.css → WorkspacePage-BAC6u9OP.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-DKRpdpPg.js +4 -0
- package/src/client/dist/pwa/assets/{browser-DHpSUK8-.js → browser-f9MHQjfi.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-DtEjsZlh.js → build-path-tree-BWBzP8os.js} +1 -1
- package/src/client/dist/pwa/assets/{de-Dt_m-H83.js → de-QkDLrcGq.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-B2dg1wRf.js → documents-C2cLnEBQ.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-Cd1D35oj.js → editor.api-sw146MWN.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-IB1vPjJQ.js → efforts-C5ZZgCH4.js} +1 -1
- package/src/client/dist/pwa/assets/{engineFeatures-DYSyDEJe.js → engineFeatures-CAVMDawq.js} +1 -1
- package/src/client/dist/pwa/assets/{es-DFg25DCS.js → es-BgQFKlo8.js} +1 -1
- package/src/client/dist/pwa/assets/{expand-template-CNAHiesL.js → expand-template-B6xAlAaM.js} +1 -1
- package/src/client/dist/pwa/assets/{format-DidwgbbX.js → format-BwWP6Aay.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BjzyUHlu.js → formatters-DyWtO-nR.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-CQ1slpol.js → fr-DAdLWjNM.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-jNGWawVw.js → freemarker2-DZ5Hm8UH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DfSqClrW.js → handlebars-BRUGpTA7.js} +1 -1
- package/src/client/dist/pwa/assets/{html-ByLX-n8T.js → html-DNOayNNs.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-CBez_vip.js +1 -0
- package/src/client/dist/pwa/assets/index-VL3fI9a3.js +20 -0
- package/src/client/dist/pwa/assets/{it-w09rfaP7.js → it-Bumaewp1.js} +1 -1
- package/src/client/dist/pwa/assets/{javascript-Dja1fHfv.js → javascript-C0tDQU3z.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-D315hW6C.js → liquid-CN46O6OF.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-B5yUwpzv.js → mdx-DM-lrr8Q.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-NfxrU0r3.js → monaco-contributions-CkN7y-8c.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-D1hXPkDZ.js → monaco.contribution-2BcAOLjc.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-CARVtbNe.js → network-auth-KYS4snQH.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-CKC3-EAZ.js → notify-theme-a91Tcr6o.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-BlLrQaW7.js → pending-diff-open-DOtELZfB.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-DMzFEQhr.js → permissionModes-C-9Crj-c.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CFjdIj6W.js → project-color-BRe4oyNx.js} +1 -1
- package/src/client/dist/pwa/assets/{python-Dc6mnEip.js → python-BRpJG6sZ.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-DArVFBGO.js → razor-CehCmvHl.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BTPAjU2g.js → render-chat-markdown-DlbQLZ02.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-COE1vIyB.js → typescript-Dt_8mVuj.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-C8x2V1w5.js → use-tours-CSVM_54K.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-DHyA_GEW.js → workspace-templates-GCObcjO6.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-eYjr4cGR.js → xml-_gOwdzB4.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-UooOcssF.js → yaml-BxeBqpGf.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BPx-xhFY.js +0 -8
- package/src/client/dist/pwa/assets/ComparisonPanel-C6DfjpZZ.js +0 -2
- package/src/client/dist/pwa/assets/ComparisonPanel-x1iuICgd.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DxOyY65u.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-ypEzkAKv.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DsVRjzzy.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-DsWn5cWl.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BI7CW2yT.js +0 -4
- package/src/client/dist/pwa/assets/i18n-CnjXQjxy.js +0 -1
- package/src/client/dist/pwa/assets/index-DcwcFDrZ.js +0 -20
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { execFile as execFileCb, execFileSync, spawn } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
+
import { AgentStopError, assertAgentStopped } from '../utils/agent-stop-result.js';
|
|
4
|
+
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
3
5
|
const execFileAsync = promisify(execFileCb);
|
|
4
6
|
import fs from 'node:fs';
|
|
5
7
|
import path from 'node:path';
|
|
@@ -50,8 +52,46 @@ import * as safePath from '../utils/safe-path.js';
|
|
|
50
52
|
import { WorkspaceLifecycleBusyError, withWorkspaceLifecycleGuard } from '../utils/workspace-lifecycle-guard.js';
|
|
51
53
|
import { resolveExtractedName } from '../utils/workspace-name.js';
|
|
52
54
|
import { resolveSiblingWorkspaceWorktreePath } from '../utils/worktree-paths.js';
|
|
53
|
-
/**
|
|
55
|
+
/** Lifecycle conflicts can be retried once the current owner has stopped. */
|
|
56
|
+
function workspaceErrorStatus(err) {
|
|
57
|
+
return err instanceof AgentStopError || err instanceof WorkspaceLifecycleBusyError ? 409 : 500;
|
|
58
|
+
}
|
|
59
|
+
/** Hono sub-router for workspace CRUD, tasks, agents, Git and PR creation. */
|
|
54
60
|
const app = new Hono();
|
|
61
|
+
// These handlers own teardown across awaits; purge/delete/restore own their guard in the service.
|
|
62
|
+
for (const route of ['/:id/archive', '/:id/run-setup-script', '/:id/cancel-source-change']) {
|
|
63
|
+
app.use(route, async (c, next) => {
|
|
64
|
+
try {
|
|
65
|
+
await withWorkspaceLifecycleGuard(c.req.param('id'), next);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (err instanceof WorkspaceLifecycleBusyError)
|
|
69
|
+
return c.json({ error: err.message, code: err.code }, 409);
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// User prompt actions must reject before cancelling timers, stopping engines,
|
|
75
|
+
// creating a PR or persisting input. The orchestrator also checks at delivery.
|
|
76
|
+
for (const route of [
|
|
77
|
+
'/:id/start',
|
|
78
|
+
'/:id/switch-engine',
|
|
79
|
+
'/:id/git/commit-with-agent',
|
|
80
|
+
'/:id/git/resolve-with-agent',
|
|
81
|
+
'/:id/open-pr',
|
|
82
|
+
'/:id/start-review',
|
|
83
|
+
'/:id/start-ci-fix',
|
|
84
|
+
]) {
|
|
85
|
+
app.use(route, async (c, next) => {
|
|
86
|
+
if (c.req.method === 'POST' && workspaceService.getWorkspace(c.req.param('id'))?.status === 'compacting') {
|
|
87
|
+
return c.json({
|
|
88
|
+
code: 'compacting',
|
|
89
|
+
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
90
|
+
}, 409);
|
|
91
|
+
}
|
|
92
|
+
await next();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
55
95
|
/** Tracks workspaces currently running a setup script to prevent concurrent executions. */
|
|
56
96
|
const setupScriptRunning = new Set();
|
|
57
97
|
/**
|
|
@@ -243,7 +283,7 @@ app.get('/', (c) => {
|
|
|
243
283
|
}
|
|
244
284
|
catch (err) {
|
|
245
285
|
const message = err instanceof Error ? err.message : String(err);
|
|
246
|
-
return c.json({ error: message },
|
|
286
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
247
287
|
}
|
|
248
288
|
});
|
|
249
289
|
// POST /api/workspaces/:id/engine-handoff-preview — build an editable, deterministic handoff.
|
|
@@ -291,10 +331,16 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
291
331
|
const handoff = typeof body.handoff === 'string' && body.handoff.trim()
|
|
292
332
|
? body.handoff.trim().slice(0, 30_000)
|
|
293
333
|
: buildEngineHandoff(workspace, workspace.engine, body.engine);
|
|
334
|
+
if (workspaceService.getWorkspace(workspace.id)?.status === 'compacting') {
|
|
335
|
+
return c.json({
|
|
336
|
+
code: 'compacting',
|
|
337
|
+
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
338
|
+
}, 409);
|
|
339
|
+
}
|
|
294
340
|
// A new engine is started three lines below: the previous one must be dead
|
|
295
341
|
// first, or two agents share the worktree.
|
|
296
342
|
if (agentManager.hasController(id))
|
|
297
|
-
await agentManager.stopAgentAndWait(id);
|
|
343
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'replacement'));
|
|
298
344
|
const updated = workspaceService.updateWorkspaceEngineConfiguration(id, body.engine, model, effort, body.agentPermissionMode);
|
|
299
345
|
const session = agentManager.startAgent(id, updated.worktreePath, handoff, updated.model, false, updated.agentPermissionMode, undefined, updated.reasoningEffort);
|
|
300
346
|
workspaceService.updateWorkspaceStatus(id, 'executing');
|
|
@@ -1366,7 +1412,7 @@ app.post('/:id/sessions', migrationGuard, (c) => {
|
|
|
1366
1412
|
}
|
|
1367
1413
|
catch (err) {
|
|
1368
1414
|
const message = err instanceof Error ? err.message : String(err);
|
|
1369
|
-
return c.json({ error: message },
|
|
1415
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1370
1416
|
}
|
|
1371
1417
|
});
|
|
1372
1418
|
// GET /api/workspaces/:id/sessions — list sessions for a workspace
|
|
@@ -1381,7 +1427,7 @@ app.get('/:id/sessions', (c) => {
|
|
|
1381
1427
|
}
|
|
1382
1428
|
catch (err) {
|
|
1383
1429
|
const message = err instanceof Error ? err.message : String(err);
|
|
1384
|
-
return c.json({ error: message },
|
|
1430
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1385
1431
|
}
|
|
1386
1432
|
});
|
|
1387
1433
|
// GET /api/workspaces/pr-states — batch snapshot of PR states known to the
|
|
@@ -1395,7 +1441,7 @@ app.get('/pr-states', (c) => {
|
|
|
1395
1441
|
}
|
|
1396
1442
|
catch (err) {
|
|
1397
1443
|
const message = err instanceof Error ? err.message : String(err);
|
|
1398
|
-
return c.json({ error: message },
|
|
1444
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1399
1445
|
}
|
|
1400
1446
|
});
|
|
1401
1447
|
// GET /api/workspaces/info — bulk snapshot for the 30s client refresh:
|
|
@@ -1415,7 +1461,7 @@ app.get('/info', (c) => {
|
|
|
1415
1461
|
}
|
|
1416
1462
|
catch (err) {
|
|
1417
1463
|
const message = err instanceof Error ? err.message : String(err);
|
|
1418
|
-
return c.json({ error: message },
|
|
1464
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1419
1465
|
}
|
|
1420
1466
|
});
|
|
1421
1467
|
// GET /api/workspaces/:id/comparison — the workspaces running the same task on
|
|
@@ -1449,7 +1495,7 @@ app.get('/:id/comparison', (c) => {
|
|
|
1449
1495
|
}
|
|
1450
1496
|
catch (err) {
|
|
1451
1497
|
const message = err instanceof Error ? err.message : String(err);
|
|
1452
|
-
return c.json({ error: message },
|
|
1498
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1453
1499
|
}
|
|
1454
1500
|
});
|
|
1455
1501
|
// GET /api/workspaces/:id/preset — the create-form preset this workspace would
|
|
@@ -1464,7 +1510,7 @@ app.get('/:id/preset', (c) => {
|
|
|
1464
1510
|
}
|
|
1465
1511
|
catch (err) {
|
|
1466
1512
|
const message = err instanceof Error ? err.message : String(err);
|
|
1467
|
-
return c.json({ error: message },
|
|
1513
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1468
1514
|
}
|
|
1469
1515
|
});
|
|
1470
1516
|
// POST /api/workspaces/pr-snapshot/refresh/:id — on-demand refresh of a single
|
|
@@ -1483,7 +1529,7 @@ app.post('/pr-snapshot/refresh/:id', async (c) => {
|
|
|
1483
1529
|
const message = err instanceof Error ? err.message : String(err);
|
|
1484
1530
|
if (/not found/i.test(message))
|
|
1485
1531
|
return c.json({ error: message }, 404);
|
|
1486
|
-
return c.json({ error: message },
|
|
1532
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1487
1533
|
}
|
|
1488
1534
|
});
|
|
1489
1535
|
// GET /api/workspaces/auto-loop-states — batch snapshot keyed by workspace id.
|
|
@@ -1518,7 +1564,7 @@ app.get('/auto-loop-states', (c) => {
|
|
|
1518
1564
|
}
|
|
1519
1565
|
catch (err) {
|
|
1520
1566
|
const message = err instanceof Error ? err.message : String(err);
|
|
1521
|
-
return c.json({ error: message },
|
|
1567
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1522
1568
|
}
|
|
1523
1569
|
});
|
|
1524
1570
|
// GET /api/workspaces/:id/auto-loop — current auto-loop status for one workspace.
|
|
@@ -1534,7 +1580,7 @@ app.get('/:id/auto-loop', (c) => {
|
|
|
1534
1580
|
}
|
|
1535
1581
|
catch (err) {
|
|
1536
1582
|
const message = err instanceof Error ? err.message : String(err);
|
|
1537
|
-
return c.json({ error: message },
|
|
1583
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1538
1584
|
}
|
|
1539
1585
|
});
|
|
1540
1586
|
// POST /api/workspaces/:id/auto-loop — enable the loop (user toggle ON).
|
|
@@ -1556,7 +1602,7 @@ app.delete('/:id/auto-loop', (c) => {
|
|
|
1556
1602
|
}
|
|
1557
1603
|
catch (err) {
|
|
1558
1604
|
const message = err instanceof Error ? err.message : String(err);
|
|
1559
|
-
return c.json({ error: message },
|
|
1605
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1560
1606
|
}
|
|
1561
1607
|
});
|
|
1562
1608
|
// POST /api/workspaces/:id/auto-loop-ready — force auto_loop_ready=true.
|
|
@@ -1576,7 +1622,7 @@ app.post('/:id/auto-loop-ready', (c) => {
|
|
|
1576
1622
|
}
|
|
1577
1623
|
catch (err) {
|
|
1578
1624
|
const message = err instanceof Error ? err.message : String(err);
|
|
1579
|
-
return c.json({ error: message },
|
|
1625
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1580
1626
|
}
|
|
1581
1627
|
});
|
|
1582
1628
|
// GET /api/workspaces/:id/crons — list pending crons for a workspace.
|
|
@@ -1590,7 +1636,7 @@ app.get('/:id/crons', (c) => {
|
|
|
1590
1636
|
}
|
|
1591
1637
|
catch (err) {
|
|
1592
1638
|
const message = err instanceof Error ? err.message : String(err);
|
|
1593
|
-
return c.json({ error: message },
|
|
1639
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1594
1640
|
}
|
|
1595
1641
|
});
|
|
1596
1642
|
// POST /api/workspaces/:id/crons — arm a new cron. Validates the expression
|
|
@@ -1638,7 +1684,7 @@ app.post('/:id/crons', async (c) => {
|
|
|
1638
1684
|
}
|
|
1639
1685
|
catch (err) {
|
|
1640
1686
|
const message = err instanceof Error ? err.message : String(err);
|
|
1641
|
-
return c.json({ error: message },
|
|
1687
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1642
1688
|
}
|
|
1643
1689
|
});
|
|
1644
1690
|
// DELETE /api/workspaces/:id/crons/:cronId — cancel a single cron. Idempotent:
|
|
@@ -1655,7 +1701,7 @@ app.delete('/:id/crons/:cronId', (c) => {
|
|
|
1655
1701
|
}
|
|
1656
1702
|
catch (err) {
|
|
1657
1703
|
const message = err instanceof Error ? err.message : String(err);
|
|
1658
|
-
return c.json({ error: message },
|
|
1704
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1659
1705
|
}
|
|
1660
1706
|
});
|
|
1661
1707
|
// GET /api/workspaces/:id/pending-wakeup — returns the pending wakeup or null.
|
|
@@ -1667,7 +1713,7 @@ app.get('/:id/pending-wakeup', (c) => {
|
|
|
1667
1713
|
}
|
|
1668
1714
|
catch (err) {
|
|
1669
1715
|
const message = err instanceof Error ? err.message : String(err);
|
|
1670
|
-
return c.json({ error: message },
|
|
1716
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1671
1717
|
}
|
|
1672
1718
|
});
|
|
1673
1719
|
// DELETE /api/workspaces/:id/pending-wakeup — user-initiated cancel ("×" button)
|
|
@@ -1680,7 +1726,7 @@ app.delete('/:id/pending-wakeup', (c) => {
|
|
|
1680
1726
|
}
|
|
1681
1727
|
catch (err) {
|
|
1682
1728
|
const message = err instanceof Error ? err.message : String(err);
|
|
1683
|
-
return c.json({ error: message },
|
|
1729
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1684
1730
|
}
|
|
1685
1731
|
});
|
|
1686
1732
|
// GET /api/workspaces/:id/quota-backoff — returns the pending quota backoff or null.
|
|
@@ -1696,7 +1742,7 @@ app.get('/:id/quota-backoff', (c) => {
|
|
|
1696
1742
|
}
|
|
1697
1743
|
catch (err) {
|
|
1698
1744
|
const message = err instanceof Error ? err.message : String(err);
|
|
1699
|
-
return c.json({ error: message },
|
|
1745
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1700
1746
|
}
|
|
1701
1747
|
});
|
|
1702
1748
|
// DELETE /api/workspaces/:id/quota-backoff — user-initiated cancel ("×" button).
|
|
@@ -1711,7 +1757,7 @@ app.delete('/:id/quota-backoff', (c) => {
|
|
|
1711
1757
|
}
|
|
1712
1758
|
catch (err) {
|
|
1713
1759
|
const message = err instanceof Error ? err.message : String(err);
|
|
1714
|
-
return c.json({ error: message },
|
|
1760
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1715
1761
|
}
|
|
1716
1762
|
});
|
|
1717
1763
|
// POST /api/workspaces/:id/pending-wakeup — schedule a wakeup, either from
|
|
@@ -1748,7 +1794,7 @@ app.post('/:id/pending-wakeup', async (c) => {
|
|
|
1748
1794
|
}
|
|
1749
1795
|
catch (err) {
|
|
1750
1796
|
const message = err instanceof Error ? err.message : String(err);
|
|
1751
|
-
return c.json({ error: message },
|
|
1797
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1752
1798
|
}
|
|
1753
1799
|
});
|
|
1754
1800
|
// PATCH /api/workspaces/:id/sessions/:sessionId — rename a session
|
|
@@ -1772,7 +1818,7 @@ app.patch('/:id/sessions/:sessionId', async (c) => {
|
|
|
1772
1818
|
}
|
|
1773
1819
|
catch (err) {
|
|
1774
1820
|
const message = err instanceof Error ? err.message : String(err);
|
|
1775
|
-
return c.json({ error: message },
|
|
1821
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1776
1822
|
}
|
|
1777
1823
|
});
|
|
1778
1824
|
// DELETE /api/workspaces/:id/sessions/:sessionId — remove a stopped session and its history.
|
|
@@ -1909,7 +1955,7 @@ app.delete('/:id/events/:eventId', (c) => {
|
|
|
1909
1955
|
}
|
|
1910
1956
|
catch (err) {
|
|
1911
1957
|
const message = err instanceof Error ? err.message : 'unknown';
|
|
1912
|
-
return c.json({ error: message },
|
|
1958
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1913
1959
|
}
|
|
1914
1960
|
});
|
|
1915
1961
|
// POST /api/workspaces/:id/tasks — create a new task
|
|
@@ -1937,7 +1983,7 @@ app.post('/:id/tasks', async (c) => {
|
|
|
1937
1983
|
}
|
|
1938
1984
|
catch (err) {
|
|
1939
1985
|
const message = err instanceof Error ? err.message : String(err);
|
|
1940
|
-
return c.json({ error: message },
|
|
1986
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1941
1987
|
}
|
|
1942
1988
|
});
|
|
1943
1989
|
// PATCH /api/workspaces/:id/tasks/:taskId — update task status and/or title
|
|
@@ -1976,7 +2022,7 @@ app.patch('/:id/tasks/:taskId', async (c) => {
|
|
|
1976
2022
|
}
|
|
1977
2023
|
catch (err) {
|
|
1978
2024
|
const message = err instanceof Error ? err.message : String(err);
|
|
1979
|
-
return c.json({ error: message },
|
|
2025
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1980
2026
|
}
|
|
1981
2027
|
});
|
|
1982
2028
|
// DELETE /api/workspaces/:id/tasks/:taskId — delete a task
|
|
@@ -1997,7 +2043,7 @@ app.delete('/:id/tasks/:taskId', (c) => {
|
|
|
1997
2043
|
}
|
|
1998
2044
|
catch (err) {
|
|
1999
2045
|
const message = err instanceof Error ? err.message : String(err);
|
|
2000
|
-
return c.json({ error: message },
|
|
2046
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2001
2047
|
}
|
|
2002
2048
|
});
|
|
2003
2049
|
// POST /api/workspaces/:id/tasks/notify-updated — broadcast generic task list change
|
|
@@ -2015,7 +2061,7 @@ app.post('/:id/tasks/notify-updated', (c) => {
|
|
|
2015
2061
|
}
|
|
2016
2062
|
catch (err) {
|
|
2017
2063
|
const message = err instanceof Error ? err.message : String(err);
|
|
2018
|
-
return c.json({ error: message },
|
|
2064
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2019
2065
|
}
|
|
2020
2066
|
});
|
|
2021
2067
|
// POST /api/workspaces/:id/agent-description/notify-updated — broadcast
|
|
@@ -2037,7 +2083,7 @@ app.post('/:id/agent-description/notify-updated', (c) => {
|
|
|
2037
2083
|
}
|
|
2038
2084
|
catch (err) {
|
|
2039
2085
|
const message = err instanceof Error ? err.message : String(err);
|
|
2040
|
-
return c.json({ error: message },
|
|
2086
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2041
2087
|
}
|
|
2042
2088
|
});
|
|
2043
2089
|
// POST /api/workspaces/:id/crons/notify-updated — broadcast cron list changed
|
|
@@ -2053,7 +2099,7 @@ app.post('/:id/crons/notify-updated', (c) => {
|
|
|
2053
2099
|
}
|
|
2054
2100
|
catch (err) {
|
|
2055
2101
|
const message = err instanceof Error ? err.message : String(err);
|
|
2056
|
-
return c.json({ error: message },
|
|
2102
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2057
2103
|
}
|
|
2058
2104
|
});
|
|
2059
2105
|
// POST /api/workspaces/:id/tasks/:taskId/notify-done — broadcast task:updated event
|
|
@@ -2070,7 +2116,7 @@ app.post('/:id/tasks/:taskId/notify-done', (c) => {
|
|
|
2070
2116
|
}
|
|
2071
2117
|
catch (err) {
|
|
2072
2118
|
const message = err instanceof Error ? err.message : String(err);
|
|
2073
|
-
return c.json({ error: message },
|
|
2119
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2074
2120
|
}
|
|
2075
2121
|
});
|
|
2076
2122
|
// Cap how many recent rows one search may scan: the LIKE below cannot use an
|
|
@@ -2201,7 +2247,7 @@ app.get('/:id/sessions/:sessionId/summary', (c) => {
|
|
|
2201
2247
|
}
|
|
2202
2248
|
catch (err) {
|
|
2203
2249
|
const message = err instanceof Error ? err.message : String(err);
|
|
2204
|
-
return c.json({ error: message },
|
|
2250
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2205
2251
|
}
|
|
2206
2252
|
});
|
|
2207
2253
|
// GET /api/workspaces/:id/session-metrics — compact observability data for the timeline.
|
|
@@ -2378,7 +2424,7 @@ app.get('/:id/events', (c) => {
|
|
|
2378
2424
|
}
|
|
2379
2425
|
catch (err) {
|
|
2380
2426
|
const message = err instanceof Error ? err.message : String(err);
|
|
2381
|
-
return c.json({ error: message },
|
|
2427
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2382
2428
|
}
|
|
2383
2429
|
});
|
|
2384
2430
|
// GET /api/workspaces/archived — list archived workspaces (must be before GET /:id)
|
|
@@ -2388,7 +2434,7 @@ app.get('/archived', (c) => {
|
|
|
2388
2434
|
}
|
|
2389
2435
|
catch (err) {
|
|
2390
2436
|
const message = err instanceof Error ? err.message : String(err);
|
|
2391
|
-
return c.json({ error: message },
|
|
2437
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2392
2438
|
}
|
|
2393
2439
|
});
|
|
2394
2440
|
// GET /:id/prep-autoloop-prompt — compose the project-aware grooming
|
|
@@ -2415,7 +2461,7 @@ ${AUTO_LOOP_HARD_RULES}`;
|
|
|
2415
2461
|
}
|
|
2416
2462
|
catch (err) {
|
|
2417
2463
|
const message = err instanceof Error ? err.message : String(err);
|
|
2418
|
-
return c.json({ error: message },
|
|
2464
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2419
2465
|
}
|
|
2420
2466
|
});
|
|
2421
2467
|
// GET /:id/chat-history — list up to 200 most recent chat-input messages
|
|
@@ -2431,7 +2477,7 @@ app.get('/:id/chat-history', (c) => {
|
|
|
2431
2477
|
}
|
|
2432
2478
|
catch (err) {
|
|
2433
2479
|
const message = err instanceof Error ? err.message : String(err);
|
|
2434
|
-
return c.json({ error: message },
|
|
2480
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2435
2481
|
}
|
|
2436
2482
|
});
|
|
2437
2483
|
// POST /:id/chat-history — append a message to this workspace's history.
|
|
@@ -2454,7 +2500,7 @@ app.post('/:id/chat-history', async (c) => {
|
|
|
2454
2500
|
}
|
|
2455
2501
|
catch (err) {
|
|
2456
2502
|
const message = err instanceof Error ? err.message : String(err);
|
|
2457
|
-
return c.json({ error: message },
|
|
2503
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2458
2504
|
}
|
|
2459
2505
|
});
|
|
2460
2506
|
// POST /:id/save-file — persist edited file content from the diff viewer.
|
|
@@ -2490,7 +2536,7 @@ app.post('/:id/save-file', async (c) => {
|
|
|
2490
2536
|
}
|
|
2491
2537
|
catch (err) {
|
|
2492
2538
|
const message = err instanceof Error ? err.message : String(err);
|
|
2493
|
-
return c.json({ error: message },
|
|
2539
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2494
2540
|
}
|
|
2495
2541
|
});
|
|
2496
2542
|
// GET /api/workspaces/:id — get workspace details with tasks
|
|
@@ -2505,7 +2551,7 @@ app.get('/:id', (c) => {
|
|
|
2505
2551
|
}
|
|
2506
2552
|
catch (err) {
|
|
2507
2553
|
const message = err instanceof Error ? err.message : String(err);
|
|
2508
|
-
return c.json({ error: message },
|
|
2554
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2509
2555
|
}
|
|
2510
2556
|
});
|
|
2511
2557
|
// POST /api/workspaces/:id/favorite — mark workspace as favorite
|
|
@@ -2660,7 +2706,7 @@ app.patch('/:id', migrationGuard, async (c) => {
|
|
|
2660
2706
|
message.includes('Description must be')) {
|
|
2661
2707
|
return c.json({ error: message }, 400);
|
|
2662
2708
|
}
|
|
2663
|
-
return c.json({ error: message },
|
|
2709
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2664
2710
|
}
|
|
2665
2711
|
});
|
|
2666
2712
|
/** Open the workspace worktree in the user's configured editor. */
|
|
@@ -2696,7 +2742,7 @@ app.post('/:id/open-editor', (c) => {
|
|
|
2696
2742
|
}
|
|
2697
2743
|
catch (err) {
|
|
2698
2744
|
const message = err instanceof Error ? err.message : String(err);
|
|
2699
|
-
return c.json({ error: message },
|
|
2745
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2700
2746
|
}
|
|
2701
2747
|
});
|
|
2702
2748
|
/** Open the workspace worktree in the user's configured terminal emulator. */
|
|
@@ -2736,7 +2782,7 @@ app.post('/:id/open-terminal', async (c) => {
|
|
|
2736
2782
|
}
|
|
2737
2783
|
catch (err) {
|
|
2738
2784
|
const message = err instanceof Error ? err.message : String(err);
|
|
2739
|
-
return c.json({ error: message },
|
|
2785
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2740
2786
|
}
|
|
2741
2787
|
});
|
|
2742
2788
|
/** Open the workspace worktree in the user's configured file manager. */
|
|
@@ -2769,7 +2815,7 @@ app.post('/:id/open-file-manager', async (c) => {
|
|
|
2769
2815
|
}
|
|
2770
2816
|
catch (err) {
|
|
2771
2817
|
const message = err instanceof Error ? err.message : String(err);
|
|
2772
|
-
return c.json({ error: message },
|
|
2818
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2773
2819
|
}
|
|
2774
2820
|
});
|
|
2775
2821
|
/** Spawn a detached process and resolve as soon as it has started (or reject
|
|
@@ -2814,11 +2860,12 @@ app.post('/:id/run-setup-script', async (c) => {
|
|
|
2814
2860
|
// rewrites the worktree the agent may still be writing to.
|
|
2815
2861
|
try {
|
|
2816
2862
|
if (agentManager.getAgentStatus(id)) {
|
|
2817
|
-
await agentManager.stopAgentAndWait(id);
|
|
2863
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'setup'));
|
|
2818
2864
|
}
|
|
2819
2865
|
}
|
|
2820
2866
|
catch (err) {
|
|
2821
2867
|
console.error(`[workspaces] stopAgentAndWait before setup script failed for '${id}':`, err);
|
|
2868
|
+
throw err;
|
|
2822
2869
|
}
|
|
2823
2870
|
const effectiveSettings = settingsService.getEffectiveSettings(workspace.projectPath);
|
|
2824
2871
|
if (!effectiveSettings.setupScript) {
|
|
@@ -2845,7 +2892,7 @@ app.post('/:id/run-setup-script', async (c) => {
|
|
|
2845
2892
|
}
|
|
2846
2893
|
catch (err) {
|
|
2847
2894
|
const message = err instanceof Error ? err.message : String(err);
|
|
2848
|
-
return c.json({ error: message },
|
|
2895
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2849
2896
|
}
|
|
2850
2897
|
});
|
|
2851
2898
|
// POST /api/workspaces/:id/archive — mark workspace as archived (soft-delete)
|
|
@@ -2860,10 +2907,11 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2860
2907
|
return c.json({ error: 'Already archived' }, 400);
|
|
2861
2908
|
}
|
|
2862
2909
|
try {
|
|
2863
|
-
await agentManager.stopAgentAndWait(id);
|
|
2910
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'archive'));
|
|
2864
2911
|
}
|
|
2865
2912
|
catch (err) {
|
|
2866
2913
|
console.error(`[workspaces] stopAgentAndWait during archive failed for '${id}':`, err);
|
|
2914
|
+
throw err;
|
|
2867
2915
|
}
|
|
2868
2916
|
try {
|
|
2869
2917
|
await devServerService.stopDevServer(id);
|
|
@@ -2871,6 +2919,7 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2871
2919
|
catch (err) {
|
|
2872
2920
|
const message = err instanceof Error ? err.message : String(err);
|
|
2873
2921
|
console.error(`[workspaces] stopDevServer during archive failed: ${message}`);
|
|
2922
|
+
throw err;
|
|
2874
2923
|
}
|
|
2875
2924
|
try {
|
|
2876
2925
|
terminalService.destroyTerminal(id);
|
|
@@ -2886,7 +2935,7 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2886
2935
|
}
|
|
2887
2936
|
catch (err) {
|
|
2888
2937
|
const message = err instanceof Error ? err.message : String(err);
|
|
2889
|
-
return c.json({ error: message },
|
|
2938
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2890
2939
|
}
|
|
2891
2940
|
});
|
|
2892
2941
|
// POST /api/workspaces/:id/purge-worktree
|
|
@@ -2909,7 +2958,7 @@ app.post('/:id/purge-worktree', migrationGuard, async (c) => {
|
|
|
2909
2958
|
if (err instanceof WorkspaceLifecycleBusyError)
|
|
2910
2959
|
return c.json({ code: err.code, error: err.message }, 409);
|
|
2911
2960
|
const message = err instanceof Error ? err.message : String(err);
|
|
2912
|
-
return c.json({ error: message },
|
|
2961
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2913
2962
|
}
|
|
2914
2963
|
});
|
|
2915
2964
|
// Recreate the checkout before unarchiving; never starts agents or setup scripts.
|
|
@@ -2953,7 +3002,7 @@ app.post('/:id/unarchive', migrationGuard, (c) => {
|
|
|
2953
3002
|
}
|
|
2954
3003
|
catch (err) {
|
|
2955
3004
|
const message = err instanceof Error ? err.message : String(err);
|
|
2956
|
-
return c.json({ error: message },
|
|
3005
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2957
3006
|
}
|
|
2958
3007
|
});
|
|
2959
3008
|
// Shared teardown for a single workspace: stops the agent, destroys the
|
|
@@ -2979,10 +3028,11 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
2979
3028
|
// a few lines below: firing and forgetting here pulled the directory from
|
|
2980
3029
|
// under an agent still writing to it, destroying uncommitted work.
|
|
2981
3030
|
try {
|
|
2982
|
-
await agentManager.stopAgentAndWait(workspace.id, undefined, 'delete');
|
|
3031
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(workspace.id, undefined, 'delete'));
|
|
2983
3032
|
}
|
|
2984
3033
|
catch (err) {
|
|
2985
3034
|
console.error(`[workspaces] stopAgentAndWait during delete failed for '${workspace.name}':`, err);
|
|
3035
|
+
throw err;
|
|
2986
3036
|
}
|
|
2987
3037
|
// Stop dev server if it was running. The processSpawn would otherwise
|
|
2988
3038
|
// outlive the workspace (and keep its port + docker containers alive).
|
|
@@ -2991,6 +3041,7 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
2991
3041
|
}
|
|
2992
3042
|
catch (err) {
|
|
2993
3043
|
console.error(`[workspaces] stopDevServer during delete failed for '${workspace.name}':`, err);
|
|
3044
|
+
throw err;
|
|
2994
3045
|
}
|
|
2995
3046
|
try {
|
|
2996
3047
|
terminalService.destroyTerminal(workspace.id);
|
|
@@ -3089,7 +3140,7 @@ app.delete('/archived', migrationGuard, async (c) => {
|
|
|
3089
3140
|
}
|
|
3090
3141
|
catch (err) {
|
|
3091
3142
|
const message = err instanceof Error ? err.message : String(err);
|
|
3092
|
-
return c.json({ error: message },
|
|
3143
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3093
3144
|
}
|
|
3094
3145
|
});
|
|
3095
3146
|
// DELETE /api/workspaces/:id — delete workspace
|
|
@@ -3117,7 +3168,7 @@ app.delete('/:id', migrationGuard, async (c) => {
|
|
|
3117
3168
|
if (err instanceof WorkspaceLifecycleBusyError)
|
|
3118
3169
|
return c.json({ code: err.code, error: err.message }, 409);
|
|
3119
3170
|
const message = err instanceof Error ? err.message : String(err);
|
|
3120
|
-
return c.json({ error: message },
|
|
3171
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3121
3172
|
}
|
|
3122
3173
|
});
|
|
3123
3174
|
// POST /api/workspaces/:id/start — start/restart agent
|
|
@@ -3156,16 +3207,23 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3156
3207
|
const prompt = body.prompt ?? pendingInitialPrompt ?? 'Continue the previous task where you left off.';
|
|
3157
3208
|
const agentSessionId = body.agentSessionId;
|
|
3158
3209
|
const resume = body.resume === true;
|
|
3210
|
+
if (workspaceService.getWorkspace(id)?.status === 'compacting') {
|
|
3211
|
+
return c.json({
|
|
3212
|
+
code: 'compacting',
|
|
3213
|
+
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
3214
|
+
}, 409);
|
|
3215
|
+
}
|
|
3159
3216
|
// A manual start supersedes any scheduled retry (quota or transient
|
|
3160
3217
|
// watchdog recovery). Leaving its timer armed could spawn a second agent
|
|
3161
3218
|
// after the user has already resumed the workspace.
|
|
3162
3219
|
quotaBackoffService.cancel(id, 'user');
|
|
3163
3220
|
// Stop the existing agent and wait: a fresh one starts right below.
|
|
3164
3221
|
try {
|
|
3165
|
-
await agentManager.stopAgentAndWait(id);
|
|
3222
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'replacement'));
|
|
3166
3223
|
}
|
|
3167
3224
|
catch (err) {
|
|
3168
3225
|
console.error(`[workspaces] stopAgentAndWait before start failed for '${id}':`, err);
|
|
3226
|
+
throw err;
|
|
3169
3227
|
}
|
|
3170
3228
|
const worktreePath = workspace.worktreePath;
|
|
3171
3229
|
const agent = agentManager.startAgent(id, worktreePath, prompt, workspace.model, resume, workspace.agentPermissionMode, agentSessionId, workspace.reasoningEffort);
|
|
@@ -3195,7 +3253,7 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3195
3253
|
}
|
|
3196
3254
|
catch (err) {
|
|
3197
3255
|
const message = err instanceof Error ? err.message : String(err);
|
|
3198
|
-
return c.json({ error: message },
|
|
3256
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3199
3257
|
}
|
|
3200
3258
|
});
|
|
3201
3259
|
// GET /api/workspaces/:id/git-stats — commit count and diff stats for the branch
|
|
@@ -3221,7 +3279,7 @@ app.get('/:id/git-stats', async (c) => {
|
|
|
3221
3279
|
}
|
|
3222
3280
|
catch (err) {
|
|
3223
3281
|
const message = err instanceof Error ? err.message : String(err);
|
|
3224
|
-
return c.json({ error: message },
|
|
3282
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3225
3283
|
}
|
|
3226
3284
|
});
|
|
3227
3285
|
// GET /api/workspaces/:id/diff?mode=branch|unpushed — list changed files
|
|
@@ -3283,7 +3341,7 @@ app.get('/:id/diff', async (c) => {
|
|
|
3283
3341
|
}
|
|
3284
3342
|
catch (err) {
|
|
3285
3343
|
const message = err instanceof Error ? err.message : String(err);
|
|
3286
|
-
return c.json({ error: message },
|
|
3344
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3287
3345
|
}
|
|
3288
3346
|
});
|
|
3289
3347
|
// GET /api/workspaces/:id/diff-file?path=...&mode=branch|unpushed
|
|
@@ -3349,7 +3407,7 @@ app.get('/:id/diff-file', (c) => {
|
|
|
3349
3407
|
}
|
|
3350
3408
|
catch (err) {
|
|
3351
3409
|
const message = err instanceof Error ? err.message : String(err);
|
|
3352
|
-
return c.json({ error: message },
|
|
3410
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3353
3411
|
}
|
|
3354
3412
|
});
|
|
3355
3413
|
// POST /api/workspaces/:id/rollback-file { path }
|
|
@@ -3388,7 +3446,7 @@ app.post('/:id/rollback-file', async (c) => {
|
|
|
3388
3446
|
}
|
|
3389
3447
|
catch (err) {
|
|
3390
3448
|
const message = err instanceof Error ? err.message : String(err);
|
|
3391
|
-
return c.json({ error: message },
|
|
3449
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3392
3450
|
}
|
|
3393
3451
|
});
|
|
3394
3452
|
// GET /api/workspaces/:id/branch-divergence?limit=50
|
|
@@ -3420,7 +3478,7 @@ app.get('/:id/branch-divergence', async (c) => {
|
|
|
3420
3478
|
}
|
|
3421
3479
|
catch (err) {
|
|
3422
3480
|
const message = err instanceof Error ? err.message : String(err);
|
|
3423
|
-
return c.json({ error: message },
|
|
3481
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3424
3482
|
}
|
|
3425
3483
|
});
|
|
3426
3484
|
// GET /api/workspaces/:id/commits?limit=50 — list commits between
|
|
@@ -3444,7 +3502,7 @@ app.get('/:id/commits', async (c) => {
|
|
|
3444
3502
|
}
|
|
3445
3503
|
catch (err) {
|
|
3446
3504
|
const message = err instanceof Error ? err.message : String(err);
|
|
3447
|
-
return c.json({ error: message },
|
|
3505
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3448
3506
|
}
|
|
3449
3507
|
});
|
|
3450
3508
|
// GET /:id/working-tree-files — list uncommitted working-tree files (read-only)
|
|
@@ -3459,7 +3517,7 @@ app.get('/:id/working-tree-files', (c) => {
|
|
|
3459
3517
|
}
|
|
3460
3518
|
catch (err) {
|
|
3461
3519
|
const message = err instanceof Error ? err.message : String(err);
|
|
3462
|
-
return c.json({ error: message },
|
|
3520
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3463
3521
|
}
|
|
3464
3522
|
});
|
|
3465
3523
|
// POST /api/workspaces/:id/rename-branch { newName }
|
|
@@ -3531,7 +3589,7 @@ app.post('/:id/rename-branch', async (c) => {
|
|
|
3531
3589
|
}
|
|
3532
3590
|
catch (err) {
|
|
3533
3591
|
const message = err instanceof Error ? err.message : String(err);
|
|
3534
|
-
return c.json({ error: message },
|
|
3592
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3535
3593
|
}
|
|
3536
3594
|
});
|
|
3537
3595
|
// POST /api/workspaces/:id/resync-branch
|
|
@@ -3587,7 +3645,7 @@ app.post('/:id/resync-branch', (c) => {
|
|
|
3587
3645
|
}
|
|
3588
3646
|
catch (err) {
|
|
3589
3647
|
const message = err instanceof Error ? err.message : String(err);
|
|
3590
|
-
return c.json({ error: message },
|
|
3648
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3591
3649
|
}
|
|
3592
3650
|
});
|
|
3593
3651
|
// POST /api/workspaces/:id/push — push working branch to origin
|
|
@@ -3624,7 +3682,7 @@ app.post('/:id/push', async (c) => {
|
|
|
3624
3682
|
}
|
|
3625
3683
|
catch (err) {
|
|
3626
3684
|
const message = err instanceof Error ? err.message : String(err);
|
|
3627
|
-
return c.json({ error: message },
|
|
3685
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3628
3686
|
}
|
|
3629
3687
|
// Emit a trace into the chat feed so the user sees the action
|
|
3630
3688
|
const session = workspaceService.getActiveSession(id);
|
|
@@ -3638,7 +3696,7 @@ app.post('/:id/push', async (c) => {
|
|
|
3638
3696
|
}
|
|
3639
3697
|
catch (err) {
|
|
3640
3698
|
const message = err instanceof Error ? err.message : String(err);
|
|
3641
|
-
return c.json({ error: message },
|
|
3699
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3642
3700
|
}
|
|
3643
3701
|
});
|
|
3644
3702
|
// POST /api/workspaces/:id/fetch — git fetch the workspace repo (all branches of origin).
|
|
@@ -3655,7 +3713,7 @@ app.post('/:id/fetch', async (c) => {
|
|
|
3655
3713
|
}
|
|
3656
3714
|
catch (err) {
|
|
3657
3715
|
const message = err instanceof Error ? err.message : String(err);
|
|
3658
|
-
return c.json({ error: message },
|
|
3716
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3659
3717
|
}
|
|
3660
3718
|
});
|
|
3661
3719
|
// POST /api/workspaces/:id/pull — pull working branch from origin (fast-forward only)
|
|
@@ -3676,7 +3734,7 @@ app.post('/:id/pull', async (c) => {
|
|
|
3676
3734
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3677
3735
|
}
|
|
3678
3736
|
const message = err instanceof Error ? err.message : String(err);
|
|
3679
|
-
return c.json({ error: message },
|
|
3737
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3680
3738
|
}
|
|
3681
3739
|
// Emit a trace into the chat feed so the user sees the action
|
|
3682
3740
|
const session = workspaceService.getActiveSession(id);
|
|
@@ -3685,7 +3743,7 @@ app.post('/:id/pull', async (c) => {
|
|
|
3685
3743
|
}
|
|
3686
3744
|
catch (err) {
|
|
3687
3745
|
const message = err instanceof Error ? err.message : String(err);
|
|
3688
|
-
return c.json({ error: message },
|
|
3746
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3689
3747
|
}
|
|
3690
3748
|
});
|
|
3691
3749
|
/** Rebase the workspace branch onto its source branch. */
|
|
@@ -3708,7 +3766,7 @@ app.post('/:id/rebase', async (c) => {
|
|
|
3708
3766
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3709
3767
|
}
|
|
3710
3768
|
const message = err instanceof Error ? err.message : String(err);
|
|
3711
|
-
return c.json({ error: message },
|
|
3769
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3712
3770
|
}
|
|
3713
3771
|
});
|
|
3714
3772
|
/** Merge the source branch into the workspace branch (non-fast-forward). */
|
|
@@ -3731,7 +3789,7 @@ app.post('/:id/merge', async (c) => {
|
|
|
3731
3789
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3732
3790
|
}
|
|
3733
3791
|
const message = err instanceof Error ? err.message : String(err);
|
|
3734
|
-
return c.json({ error: message },
|
|
3792
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3735
3793
|
}
|
|
3736
3794
|
});
|
|
3737
3795
|
/** Abort any in-progress merge or rebase in the worktree. */
|
|
@@ -3747,7 +3805,7 @@ app.post('/:id/git/abort', (c) => {
|
|
|
3747
3805
|
}
|
|
3748
3806
|
catch (err) {
|
|
3749
3807
|
const message = err instanceof Error ? err.message : String(err);
|
|
3750
|
-
return c.json({ error: message },
|
|
3808
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3751
3809
|
}
|
|
3752
3810
|
});
|
|
3753
3811
|
/** Continue an in-progress merge, rebase or cherry-pick after resolution. */
|
|
@@ -3764,7 +3822,7 @@ app.post('/:id/git/continue', (c) => {
|
|
|
3764
3822
|
}
|
|
3765
3823
|
catch (err) {
|
|
3766
3824
|
const message = err instanceof Error ? err.message : String(err);
|
|
3767
|
-
return c.json({ error: message },
|
|
3825
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3768
3826
|
}
|
|
3769
3827
|
});
|
|
3770
3828
|
/** Stage + commit all working-tree changes (recovery action for a dirty rebase/merge). */
|
|
@@ -3783,7 +3841,7 @@ app.post('/:id/git/commit-all', async (c) => {
|
|
|
3783
3841
|
}
|
|
3784
3842
|
catch (err) {
|
|
3785
3843
|
const message = err instanceof Error ? err.message : String(err);
|
|
3786
|
-
return c.json({ error: message },
|
|
3844
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3787
3845
|
}
|
|
3788
3846
|
});
|
|
3789
3847
|
/** Ask the workspace agent to inspect and commit the current working tree. */
|
|
@@ -3806,7 +3864,6 @@ app.post('/:id/git/commit-with-agent', migrationGuard, async (c) => {
|
|
|
3806
3864
|
5. Do NOT push the branch.
|
|
3807
3865
|
|
|
3808
3866
|
When finished, report the commit SHA, its message, the files included, and the checks you ran.`;
|
|
3809
|
-
wakeupService.cancel(workspace.id, 'user-message');
|
|
3810
3867
|
let messageSent = false;
|
|
3811
3868
|
try {
|
|
3812
3869
|
const { agentSessionId } = await deliverAgentPrompt(workspace, workspace.worktreePath, prompt);
|
|
@@ -3835,7 +3892,7 @@ app.post('/:id/git/discard', (c) => {
|
|
|
3835
3892
|
}
|
|
3836
3893
|
catch (err) {
|
|
3837
3894
|
const message = err instanceof Error ? err.message : String(err);
|
|
3838
|
-
return c.json({ error: message },
|
|
3895
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3839
3896
|
}
|
|
3840
3897
|
});
|
|
3841
3898
|
/** Hand off merge/rebase conflicts to the workspace agent with an intelligent-resolution prompt. */
|
|
@@ -3889,9 +3946,6 @@ ${fileList}
|
|
|
3889
3946
|
6. Report the summary: which files you touched, the key decisions you made, and the final test result.${cherryPickNote}
|
|
3890
3947
|
|
|
3891
3948
|
Start now.`;
|
|
3892
|
-
// Cancel any pending wakeup: the user is driving this turn, the
|
|
3893
|
-
// scheduler should not also wake the agent a few minutes later.
|
|
3894
|
-
wakeupService.cancel(workspace.id, 'user-message');
|
|
3895
3949
|
let messageSent = false;
|
|
3896
3950
|
try {
|
|
3897
3951
|
const { agentSessionId } = await deliverAgentPrompt(workspace, worktreePath, prompt);
|
|
@@ -3906,7 +3960,7 @@ Start now.`;
|
|
|
3906
3960
|
}
|
|
3907
3961
|
catch (err) {
|
|
3908
3962
|
const message = err instanceof Error ? err.message : String(err);
|
|
3909
|
-
return c.json({ error: message },
|
|
3963
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3910
3964
|
}
|
|
3911
3965
|
});
|
|
3912
3966
|
/** Change the base branch of an existing PR via the resolved forge provider. */
|
|
@@ -3943,7 +3997,7 @@ app.post('/:id/change-pr-base', async (c) => {
|
|
|
3943
3997
|
return c.json({ error: err.message, code: 'forge_unavailable' }, 409);
|
|
3944
3998
|
}
|
|
3945
3999
|
const message = err instanceof Error ? err.message : String(err);
|
|
3946
|
-
return c.json({ error: message },
|
|
4000
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3947
4001
|
}
|
|
3948
4002
|
});
|
|
3949
4003
|
/** Merge a ready, open PR/MR through its resolved forge provider. */
|
|
@@ -3980,7 +4034,7 @@ app.post('/:id/merge-pr', async (c) => {
|
|
|
3980
4034
|
return c.json({ error: err.message, code: 'forge_unavailable' }, 409);
|
|
3981
4035
|
}
|
|
3982
4036
|
const message = err instanceof Error ? err.message : String(err);
|
|
3983
|
-
return c.json({ error: message },
|
|
4037
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3984
4038
|
}
|
|
3985
4039
|
});
|
|
3986
4040
|
/** Delete the workspace branch from the remote after its PR/MR was merged. */
|
|
@@ -4005,7 +4059,7 @@ app.post('/:id/delete-remote-branch', async (c) => {
|
|
|
4005
4059
|
}
|
|
4006
4060
|
catch (err) {
|
|
4007
4061
|
const message = err instanceof Error ? err.message : String(err);
|
|
4008
|
-
return c.json({ error: message },
|
|
4062
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4009
4063
|
}
|
|
4010
4064
|
});
|
|
4011
4065
|
/** Re-target a workspace onto a new source branch (metadata + worktree + PR base). */
|
|
@@ -4077,13 +4131,17 @@ app.post('/:id/cancel-source-change', async (c) => {
|
|
|
4077
4131
|
if (backups.length === 0) {
|
|
4078
4132
|
return c.json({ error: 'No backup branch found — cannot auto-restore', code: 'no_backup' }, 409);
|
|
4079
4133
|
}
|
|
4080
|
-
|
|
4134
|
+
if (agentManager.hasController(id))
|
|
4135
|
+
return c.json({ error: 'Stop the agent before restoring the branch' }, 409);
|
|
4136
|
+
await withGitRepoLock(workspace.worktreePath, async () => {
|
|
4137
|
+
gitOps.restoreBranchFromBackup(workspace.worktreePath, workspace.workingBranch, backups[0]);
|
|
4138
|
+
});
|
|
4081
4139
|
workspaceService.updateWorkspaceSourceBranch(id, body.previousBase);
|
|
4082
4140
|
return c.json({ success: true, restoredFrom: backups[0] });
|
|
4083
4141
|
}
|
|
4084
4142
|
catch (err) {
|
|
4085
4143
|
const message = err instanceof Error ? err.message : String(err);
|
|
4086
|
-
return c.json({ error: message },
|
|
4144
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4087
4145
|
}
|
|
4088
4146
|
});
|
|
4089
4147
|
/** Force-push the working branch with --force-with-lease (after a history rewrite). */
|
|
@@ -4119,7 +4177,7 @@ app.post('/:id/force-push', async (c) => {
|
|
|
4119
4177
|
}
|
|
4120
4178
|
catch (err) {
|
|
4121
4179
|
const message = err instanceof Error ? err.message : String(err);
|
|
4122
|
-
return c.json({ error: message },
|
|
4180
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4123
4181
|
}
|
|
4124
4182
|
});
|
|
4125
4183
|
// POST /api/workspaces/:id/open-pr — create a GitHub PR and send a templated prompt to the agent
|
|
@@ -4222,8 +4280,6 @@ app.post('/:id/open-pr', async (c) => {
|
|
|
4222
4280
|
diffStats,
|
|
4223
4281
|
tasks,
|
|
4224
4282
|
});
|
|
4225
|
-
// Cancel any pending wakeup: the user is driving this turn.
|
|
4226
|
-
wakeupService.cancel(workspace.id, 'user-message');
|
|
4227
4283
|
// Send to the running agent, or resume the agent with the PR prompt
|
|
4228
4284
|
let messageSent = false;
|
|
4229
4285
|
try {
|
|
@@ -4239,7 +4295,7 @@ app.post('/:id/open-pr', async (c) => {
|
|
|
4239
4295
|
}
|
|
4240
4296
|
catch (err) {
|
|
4241
4297
|
const message = err instanceof Error ? err.message : String(err);
|
|
4242
|
-
return c.json({ error: message },
|
|
4298
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4243
4299
|
}
|
|
4244
4300
|
});
|
|
4245
4301
|
// POST /api/workspaces/:id/start-review — ask the agent to review committed + uncommitted changes
|
|
@@ -4291,22 +4347,23 @@ app.post('/:id/start-review', async (c) => {
|
|
|
4291
4347
|
baseCommit,
|
|
4292
4348
|
additionalInstructions,
|
|
4293
4349
|
});
|
|
4294
|
-
try {
|
|
4295
|
-
wakeupService.cancel(workspace.id, 'user-message');
|
|
4296
|
-
}
|
|
4297
|
-
catch {
|
|
4298
|
-
/* swallow */
|
|
4299
|
-
}
|
|
4300
4350
|
let messageSent = false;
|
|
4301
4351
|
let emitSessionId;
|
|
4352
|
+
if (workspaceService.getWorkspace(workspace.id)?.status === 'compacting') {
|
|
4353
|
+
return c.json({
|
|
4354
|
+
code: 'compacting',
|
|
4355
|
+
error: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
4356
|
+
}, 409);
|
|
4357
|
+
}
|
|
4302
4358
|
if (newSession) {
|
|
4303
4359
|
// Stop current agent, wait for it, then start fresh.
|
|
4304
4360
|
try {
|
|
4305
|
-
await agentManager.stopAgentAndWait(workspace.id);
|
|
4361
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(workspace.id, undefined, 'replacement'));
|
|
4306
4362
|
}
|
|
4307
4363
|
catch (err) {
|
|
4308
4364
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4309
4365
|
console.error(`[start-review] stopAgentAndWait failed (continuing): ${msg}`);
|
|
4366
|
+
throw err;
|
|
4310
4367
|
}
|
|
4311
4368
|
try {
|
|
4312
4369
|
const agent = agentManager.startAgent(workspace.id, worktreePath, rendered, workspace.model, false /* resume */, workspace.agentPermissionMode, undefined, workspace.reasoningEffort);
|
|
@@ -4339,7 +4396,7 @@ app.post('/:id/start-review', async (c) => {
|
|
|
4339
4396
|
}
|
|
4340
4397
|
catch (err) {
|
|
4341
4398
|
const message = err instanceof Error ? err.message : String(err);
|
|
4342
|
-
return c.json({ error: message },
|
|
4399
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4343
4400
|
}
|
|
4344
4401
|
});
|
|
4345
4402
|
// POST /api/workspaces/:id/start-ci-fix — dispatch the configured CI-fix
|
|
@@ -4384,12 +4441,6 @@ app.post('/:id/start-ci-fix', migrationGuard, async (c) => {
|
|
|
4384
4441
|
failedChecks,
|
|
4385
4442
|
ciRunUrl,
|
|
4386
4443
|
});
|
|
4387
|
-
try {
|
|
4388
|
-
wakeupService.cancel(workspace.id, 'user-message');
|
|
4389
|
-
}
|
|
4390
|
-
catch {
|
|
4391
|
-
/* swallow */
|
|
4392
|
-
}
|
|
4393
4444
|
const session = workspaceService.getActiveSession(workspace.id);
|
|
4394
4445
|
let emitSessionId = session?.id;
|
|
4395
4446
|
try {
|
|
@@ -4405,7 +4456,7 @@ app.post('/:id/start-ci-fix', migrationGuard, async (c) => {
|
|
|
4405
4456
|
}
|
|
4406
4457
|
catch (err) {
|
|
4407
4458
|
const message = err instanceof Error ? err.message : String(err);
|
|
4408
|
-
return c.json({ error: message },
|
|
4459
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4409
4460
|
}
|
|
4410
4461
|
});
|
|
4411
4462
|
/**
|
|
@@ -4435,7 +4486,7 @@ app.post('/:id/dismiss-pr-attention', async (c) => {
|
|
|
4435
4486
|
}
|
|
4436
4487
|
catch (err) {
|
|
4437
4488
|
const message = err instanceof Error ? err.message : String(err);
|
|
4438
|
-
return c.json({ error: message },
|
|
4489
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4439
4490
|
}
|
|
4440
4491
|
});
|
|
4441
4492
|
/**
|
|
@@ -4461,7 +4512,7 @@ app.post('/:id/restore-pr-attention', async (c) => {
|
|
|
4461
4512
|
}
|
|
4462
4513
|
catch (err) {
|
|
4463
4514
|
const message = err instanceof Error ? err.message : String(err);
|
|
4464
|
-
return c.json({ error: message },
|
|
4515
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4465
4516
|
}
|
|
4466
4517
|
});
|
|
4467
4518
|
/** POST /api/workspaces/:id/mark-read — mark workspace as read (clear unread indicator). */
|
|
@@ -4478,7 +4529,7 @@ app.post('/:id/mark-read', (c) => {
|
|
|
4478
4529
|
}
|
|
4479
4530
|
catch (err) {
|
|
4480
4531
|
const message = err instanceof Error ? err.message : String(err);
|
|
4481
|
-
return c.json({ error: message },
|
|
4532
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4482
4533
|
}
|
|
4483
4534
|
});
|
|
4484
4535
|
// POST /api/workspaces/:id/stop — stop agent
|
|
@@ -4491,11 +4542,12 @@ app.post('/:id/stop', migrationGuard, async (c) => {
|
|
|
4491
4542
|
}
|
|
4492
4543
|
try {
|
|
4493
4544
|
// The response says "stopped": make that true before returning.
|
|
4494
|
-
await agentManager.stopAgentAndWait(id);
|
|
4545
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id));
|
|
4495
4546
|
}
|
|
4496
4547
|
catch (err) {
|
|
4497
4548
|
// Agent may not be tracked (e.g. server restarted) — just update status
|
|
4498
4549
|
console.error(`[workspaces] stopAgentAndWait on manual stop failed for '${id}':`, err);
|
|
4550
|
+
throw err;
|
|
4499
4551
|
}
|
|
4500
4552
|
// Always transition to idle so the UI reflects the stopped state
|
|
4501
4553
|
try {
|
|
@@ -4508,7 +4560,7 @@ app.post('/:id/stop', migrationGuard, async (c) => {
|
|
|
4508
4560
|
}
|
|
4509
4561
|
catch (err) {
|
|
4510
4562
|
const message = err instanceof Error ? err.message : String(err);
|
|
4511
|
-
return c.json({ error: message },
|
|
4563
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4512
4564
|
}
|
|
4513
4565
|
});
|
|
4514
4566
|
// POST /api/workspaces/:id/interrupt — soft-interrupt agent (SIGINT, like Escape in Claude Code)
|
|
@@ -4555,7 +4607,7 @@ app.post('/:id/interrupt', migrationGuard, async (c) => {
|
|
|
4555
4607
|
const status = err.code === 'interrupt_failed' ? 500 : 409;
|
|
4556
4608
|
return c.json({ error: message, code: err.code }, status);
|
|
4557
4609
|
}
|
|
4558
|
-
return c.json({ error: message },
|
|
4610
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4559
4611
|
}
|
|
4560
4612
|
});
|
|
4561
4613
|
export default app;
|