@loicngr/kobo 1.11.35 → 1.11.37
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 +15 -0
- package/CHANGELOG.md +9 -0
- package/dist/server/index.js +11 -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 +122 -91
- package/dist/server/services/agent/engines/claude-code/engine.js +26 -0
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +10 -10
- 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 +5 -1
- 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/orchestrator.js +175 -68
- package/dist/server/services/agent/session-controller.js +12 -2
- package/dist/server/services/auto-loop-service.js +71 -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/quota-backoff-service.js +25 -8
- package/dist/server/services/wakeup-service.js +37 -3
- 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-Bakgvay6.css → ActivityFeed-BDyFBoxd.css} +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-C_-hxIo8.js +8 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-DVLWWvvz.js → ChangelogPage-CBlGJ4zT.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-LZrvk6a6.js → ComparisonPanel-DZAE2X9j.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-A3pVradx.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-ArmtcF4d.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-0odIhseb.js → DashboardPage-eQTCXcdz.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-NWB2uQkz.js → DiffViewer-CaT7vuSr.js} +3 -3
- package/src/client/dist/pwa/assets/{DocumentsPanel-3yApHgbl.js → DocumentsPanel-A0isCTaI.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-C_bNtZsE.js → DrawerToggleButton-T70fh8dp.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-BMjWwiAX.js → GitPanel-CAvk0G7h.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-Vyr1YG2o.js → HealthPage-D8Mr0V6w.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-s8_9FPE2.js → MainLayout-x6Es4Da-.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnDropdown-BxuXEBp5.js → QBtnDropdown-CQqzzlFI.js} +1 -1
- package/src/client/dist/pwa/assets/{QDrawer-BdlDiU6a.js → QDrawer-Cwj2qh0r.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-BrSemS3o.js → QSelect-BSPtuXJg.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-CLCFHW2D.js → QTooltip-DC_kDKku.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-F9bdAq6K.js → SchedulePanel-9aJSoz6Y.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-CBCefYqc.js → SearchPage-BjNvtE_U.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-BPgTHmKj.js → SettingsPage-CvLnT73V.js} +1 -1
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-DDs2nuoT.js → SplitWorkspacePage-DTao0DHr.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CulE0wTU.js → TerminalPanel-BrKIW-bJ.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-DUgqs2cW.js → TouchPan-_n1AQgoo.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-CdHpGBdr.js → TourReplayButton-CJF3nVvn.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-W5SvqzXO.js → WorkspaceAttentionLabels-CxesUiIm.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-DYQrfeYw.css → WorkspacePage-DoXtwpjV.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-MxBE0mr3.js +4 -0
- package/src/client/dist/pwa/assets/{browser-CnuqDTyc.js → browser-DGWpkvN3.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-JodIO7-I.js → build-path-tree-Ba1dSLgi.js} +1 -1
- package/src/client/dist/pwa/assets/{de-Dt_m-H83.js → de-DVK21RB6.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-3ZBK1pXE.js → documents-BMHzjW_g.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-DZ9WO_J7.js → editor.api-CYTuNwqu.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-LrtJPx-j.js → efforts-BPlRSHXp.js} +1 -1
- package/src/client/dist/pwa/assets/{engineFeatures-DmnHj_uD.js → engineFeatures-DVB2rklO.js} +1 -1
- package/src/client/dist/pwa/assets/{es-DFg25DCS.js → es-C8cQwDZM.js} +1 -1
- package/src/client/dist/pwa/assets/{expand-template-Dv4JC2zU.js → expand-template-Cj82YE-O.js} +1 -1
- package/src/client/dist/pwa/assets/{format-CHjfjuhr.js → format-Dk7O3LhA.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BsgH8CoH.js → formatters-TGjvs7Ca.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-CQ1slpol.js → fr-BVOMZ2Bl.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-Dla55SIR.js → freemarker2-39lO_dWH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DtuuZy7D.js → handlebars-M_0V82cD.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CKYOCR8W.js → html-yILqYDsl.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-2UeMfd5m.js +1 -0
- package/src/client/dist/pwa/assets/{index-CIP08MpU.js → index-BJU5dnkF.js} +2 -2
- package/src/client/dist/pwa/assets/{it-w09rfaP7.js → it-CxkAoOj4.js} +1 -1
- package/src/client/dist/pwa/assets/{javascript-BxzTnsIQ.js → javascript-DrW-amDc.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-C8BfPFUy.js → liquid-D_mXmUoL.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-DZm_YXkC.js → mdx-E8O3lvR5.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-HDbRHiNR.js → monaco-contributions-C2K3sl_V.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-DRW-851R.js → monaco.contribution-DLhHQIJn.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-Ce9hsryo.js → network-auth-BUwWtnui.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-OBpMEBvP.js → notify-theme-1mOpY2vN.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-CVQxWyzP.js → pending-diff-open-BL7dKRqF.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-CVFHS2ec.js → permissionModes-BtC3_P-P.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-mcpQp2o6.js → project-color-BFBFZWeG.js} +1 -1
- package/src/client/dist/pwa/assets/{python-CO5iQN74.js → python-DoZAYim6.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-BC7D8LMf.js → razor-9bZ30g2y.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-Cb15kvox.js → render-chat-markdown-7EtWNjG0.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-BCHCSNpD.js → typescript-C1CBfL3L.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-DhLhglwr.js → use-tours-BJ81clv6.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-CoWrOEac.js → workspace-templates-r1-bipjM.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-y9M3NAIZ.js → xml-COuu5isK.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-CthlToPo.js → yaml-ChZ7WqaJ.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-YRbIRutK.js +0 -8
- package/src/client/dist/pwa/assets/CreatePage-DxOyY65u.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-gFrn4xl9.js +0 -2
- package/src/client/dist/pwa/assets/WorkspacePage-B6O7j57u.js +0 -4
- package/src/client/dist/pwa/assets/i18n-DTfKQUMf.js +0 -1
|
@@ -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,25 @@ 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
|
+
}
|
|
55
74
|
/** Tracks workspaces currently running a setup script to prevent concurrent executions. */
|
|
56
75
|
const setupScriptRunning = new Set();
|
|
57
76
|
/**
|
|
@@ -243,7 +262,7 @@ app.get('/', (c) => {
|
|
|
243
262
|
}
|
|
244
263
|
catch (err) {
|
|
245
264
|
const message = err instanceof Error ? err.message : String(err);
|
|
246
|
-
return c.json({ error: message },
|
|
265
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
247
266
|
}
|
|
248
267
|
});
|
|
249
268
|
// POST /api/workspaces/:id/engine-handoff-preview — build an editable, deterministic handoff.
|
|
@@ -294,7 +313,7 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
294
313
|
// A new engine is started three lines below: the previous one must be dead
|
|
295
314
|
// first, or two agents share the worktree.
|
|
296
315
|
if (agentManager.hasController(id))
|
|
297
|
-
await agentManager.stopAgentAndWait(id);
|
|
316
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'replacement'));
|
|
298
317
|
const updated = workspaceService.updateWorkspaceEngineConfiguration(id, body.engine, model, effort, body.agentPermissionMode);
|
|
299
318
|
const session = agentManager.startAgent(id, updated.worktreePath, handoff, updated.model, false, updated.agentPermissionMode, undefined, updated.reasoningEffort);
|
|
300
319
|
workspaceService.updateWorkspaceStatus(id, 'executing');
|
|
@@ -1366,7 +1385,7 @@ app.post('/:id/sessions', migrationGuard, (c) => {
|
|
|
1366
1385
|
}
|
|
1367
1386
|
catch (err) {
|
|
1368
1387
|
const message = err instanceof Error ? err.message : String(err);
|
|
1369
|
-
return c.json({ error: message },
|
|
1388
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1370
1389
|
}
|
|
1371
1390
|
});
|
|
1372
1391
|
// GET /api/workspaces/:id/sessions — list sessions for a workspace
|
|
@@ -1381,7 +1400,7 @@ app.get('/:id/sessions', (c) => {
|
|
|
1381
1400
|
}
|
|
1382
1401
|
catch (err) {
|
|
1383
1402
|
const message = err instanceof Error ? err.message : String(err);
|
|
1384
|
-
return c.json({ error: message },
|
|
1403
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1385
1404
|
}
|
|
1386
1405
|
});
|
|
1387
1406
|
// GET /api/workspaces/pr-states — batch snapshot of PR states known to the
|
|
@@ -1395,7 +1414,7 @@ app.get('/pr-states', (c) => {
|
|
|
1395
1414
|
}
|
|
1396
1415
|
catch (err) {
|
|
1397
1416
|
const message = err instanceof Error ? err.message : String(err);
|
|
1398
|
-
return c.json({ error: message },
|
|
1417
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1399
1418
|
}
|
|
1400
1419
|
});
|
|
1401
1420
|
// GET /api/workspaces/info — bulk snapshot for the 30s client refresh:
|
|
@@ -1415,7 +1434,7 @@ app.get('/info', (c) => {
|
|
|
1415
1434
|
}
|
|
1416
1435
|
catch (err) {
|
|
1417
1436
|
const message = err instanceof Error ? err.message : String(err);
|
|
1418
|
-
return c.json({ error: message },
|
|
1437
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1419
1438
|
}
|
|
1420
1439
|
});
|
|
1421
1440
|
// GET /api/workspaces/:id/comparison — the workspaces running the same task on
|
|
@@ -1449,7 +1468,7 @@ app.get('/:id/comparison', (c) => {
|
|
|
1449
1468
|
}
|
|
1450
1469
|
catch (err) {
|
|
1451
1470
|
const message = err instanceof Error ? err.message : String(err);
|
|
1452
|
-
return c.json({ error: message },
|
|
1471
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1453
1472
|
}
|
|
1454
1473
|
});
|
|
1455
1474
|
// GET /api/workspaces/:id/preset — the create-form preset this workspace would
|
|
@@ -1464,7 +1483,7 @@ app.get('/:id/preset', (c) => {
|
|
|
1464
1483
|
}
|
|
1465
1484
|
catch (err) {
|
|
1466
1485
|
const message = err instanceof Error ? err.message : String(err);
|
|
1467
|
-
return c.json({ error: message },
|
|
1486
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1468
1487
|
}
|
|
1469
1488
|
});
|
|
1470
1489
|
// POST /api/workspaces/pr-snapshot/refresh/:id — on-demand refresh of a single
|
|
@@ -1483,7 +1502,7 @@ app.post('/pr-snapshot/refresh/:id', async (c) => {
|
|
|
1483
1502
|
const message = err instanceof Error ? err.message : String(err);
|
|
1484
1503
|
if (/not found/i.test(message))
|
|
1485
1504
|
return c.json({ error: message }, 404);
|
|
1486
|
-
return c.json({ error: message },
|
|
1505
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1487
1506
|
}
|
|
1488
1507
|
});
|
|
1489
1508
|
// GET /api/workspaces/auto-loop-states — batch snapshot keyed by workspace id.
|
|
@@ -1518,7 +1537,7 @@ app.get('/auto-loop-states', (c) => {
|
|
|
1518
1537
|
}
|
|
1519
1538
|
catch (err) {
|
|
1520
1539
|
const message = err instanceof Error ? err.message : String(err);
|
|
1521
|
-
return c.json({ error: message },
|
|
1540
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1522
1541
|
}
|
|
1523
1542
|
});
|
|
1524
1543
|
// GET /api/workspaces/:id/auto-loop — current auto-loop status for one workspace.
|
|
@@ -1534,7 +1553,7 @@ app.get('/:id/auto-loop', (c) => {
|
|
|
1534
1553
|
}
|
|
1535
1554
|
catch (err) {
|
|
1536
1555
|
const message = err instanceof Error ? err.message : String(err);
|
|
1537
|
-
return c.json({ error: message },
|
|
1556
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1538
1557
|
}
|
|
1539
1558
|
});
|
|
1540
1559
|
// POST /api/workspaces/:id/auto-loop — enable the loop (user toggle ON).
|
|
@@ -1556,7 +1575,7 @@ app.delete('/:id/auto-loop', (c) => {
|
|
|
1556
1575
|
}
|
|
1557
1576
|
catch (err) {
|
|
1558
1577
|
const message = err instanceof Error ? err.message : String(err);
|
|
1559
|
-
return c.json({ error: message },
|
|
1578
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1560
1579
|
}
|
|
1561
1580
|
});
|
|
1562
1581
|
// POST /api/workspaces/:id/auto-loop-ready — force auto_loop_ready=true.
|
|
@@ -1576,7 +1595,7 @@ app.post('/:id/auto-loop-ready', (c) => {
|
|
|
1576
1595
|
}
|
|
1577
1596
|
catch (err) {
|
|
1578
1597
|
const message = err instanceof Error ? err.message : String(err);
|
|
1579
|
-
return c.json({ error: message },
|
|
1598
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1580
1599
|
}
|
|
1581
1600
|
});
|
|
1582
1601
|
// GET /api/workspaces/:id/crons — list pending crons for a workspace.
|
|
@@ -1590,7 +1609,7 @@ app.get('/:id/crons', (c) => {
|
|
|
1590
1609
|
}
|
|
1591
1610
|
catch (err) {
|
|
1592
1611
|
const message = err instanceof Error ? err.message : String(err);
|
|
1593
|
-
return c.json({ error: message },
|
|
1612
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1594
1613
|
}
|
|
1595
1614
|
});
|
|
1596
1615
|
// POST /api/workspaces/:id/crons — arm a new cron. Validates the expression
|
|
@@ -1638,7 +1657,7 @@ app.post('/:id/crons', async (c) => {
|
|
|
1638
1657
|
}
|
|
1639
1658
|
catch (err) {
|
|
1640
1659
|
const message = err instanceof Error ? err.message : String(err);
|
|
1641
|
-
return c.json({ error: message },
|
|
1660
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1642
1661
|
}
|
|
1643
1662
|
});
|
|
1644
1663
|
// DELETE /api/workspaces/:id/crons/:cronId — cancel a single cron. Idempotent:
|
|
@@ -1655,7 +1674,7 @@ app.delete('/:id/crons/:cronId', (c) => {
|
|
|
1655
1674
|
}
|
|
1656
1675
|
catch (err) {
|
|
1657
1676
|
const message = err instanceof Error ? err.message : String(err);
|
|
1658
|
-
return c.json({ error: message },
|
|
1677
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1659
1678
|
}
|
|
1660
1679
|
});
|
|
1661
1680
|
// GET /api/workspaces/:id/pending-wakeup — returns the pending wakeup or null.
|
|
@@ -1667,7 +1686,7 @@ app.get('/:id/pending-wakeup', (c) => {
|
|
|
1667
1686
|
}
|
|
1668
1687
|
catch (err) {
|
|
1669
1688
|
const message = err instanceof Error ? err.message : String(err);
|
|
1670
|
-
return c.json({ error: message },
|
|
1689
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1671
1690
|
}
|
|
1672
1691
|
});
|
|
1673
1692
|
// DELETE /api/workspaces/:id/pending-wakeup — user-initiated cancel ("×" button)
|
|
@@ -1680,7 +1699,7 @@ app.delete('/:id/pending-wakeup', (c) => {
|
|
|
1680
1699
|
}
|
|
1681
1700
|
catch (err) {
|
|
1682
1701
|
const message = err instanceof Error ? err.message : String(err);
|
|
1683
|
-
return c.json({ error: message },
|
|
1702
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1684
1703
|
}
|
|
1685
1704
|
});
|
|
1686
1705
|
// GET /api/workspaces/:id/quota-backoff — returns the pending quota backoff or null.
|
|
@@ -1696,7 +1715,7 @@ app.get('/:id/quota-backoff', (c) => {
|
|
|
1696
1715
|
}
|
|
1697
1716
|
catch (err) {
|
|
1698
1717
|
const message = err instanceof Error ? err.message : String(err);
|
|
1699
|
-
return c.json({ error: message },
|
|
1718
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1700
1719
|
}
|
|
1701
1720
|
});
|
|
1702
1721
|
// DELETE /api/workspaces/:id/quota-backoff — user-initiated cancel ("×" button).
|
|
@@ -1711,7 +1730,7 @@ app.delete('/:id/quota-backoff', (c) => {
|
|
|
1711
1730
|
}
|
|
1712
1731
|
catch (err) {
|
|
1713
1732
|
const message = err instanceof Error ? err.message : String(err);
|
|
1714
|
-
return c.json({ error: message },
|
|
1733
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1715
1734
|
}
|
|
1716
1735
|
});
|
|
1717
1736
|
// POST /api/workspaces/:id/pending-wakeup — schedule a wakeup, either from
|
|
@@ -1748,7 +1767,7 @@ app.post('/:id/pending-wakeup', async (c) => {
|
|
|
1748
1767
|
}
|
|
1749
1768
|
catch (err) {
|
|
1750
1769
|
const message = err instanceof Error ? err.message : String(err);
|
|
1751
|
-
return c.json({ error: message },
|
|
1770
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1752
1771
|
}
|
|
1753
1772
|
});
|
|
1754
1773
|
// PATCH /api/workspaces/:id/sessions/:sessionId — rename a session
|
|
@@ -1772,7 +1791,7 @@ app.patch('/:id/sessions/:sessionId', async (c) => {
|
|
|
1772
1791
|
}
|
|
1773
1792
|
catch (err) {
|
|
1774
1793
|
const message = err instanceof Error ? err.message : String(err);
|
|
1775
|
-
return c.json({ error: message },
|
|
1794
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1776
1795
|
}
|
|
1777
1796
|
});
|
|
1778
1797
|
// DELETE /api/workspaces/:id/sessions/:sessionId — remove a stopped session and its history.
|
|
@@ -1909,7 +1928,7 @@ app.delete('/:id/events/:eventId', (c) => {
|
|
|
1909
1928
|
}
|
|
1910
1929
|
catch (err) {
|
|
1911
1930
|
const message = err instanceof Error ? err.message : 'unknown';
|
|
1912
|
-
return c.json({ error: message },
|
|
1931
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1913
1932
|
}
|
|
1914
1933
|
});
|
|
1915
1934
|
// POST /api/workspaces/:id/tasks — create a new task
|
|
@@ -1937,7 +1956,7 @@ app.post('/:id/tasks', async (c) => {
|
|
|
1937
1956
|
}
|
|
1938
1957
|
catch (err) {
|
|
1939
1958
|
const message = err instanceof Error ? err.message : String(err);
|
|
1940
|
-
return c.json({ error: message },
|
|
1959
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1941
1960
|
}
|
|
1942
1961
|
});
|
|
1943
1962
|
// PATCH /api/workspaces/:id/tasks/:taskId — update task status and/or title
|
|
@@ -1976,7 +1995,7 @@ app.patch('/:id/tasks/:taskId', async (c) => {
|
|
|
1976
1995
|
}
|
|
1977
1996
|
catch (err) {
|
|
1978
1997
|
const message = err instanceof Error ? err.message : String(err);
|
|
1979
|
-
return c.json({ error: message },
|
|
1998
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
1980
1999
|
}
|
|
1981
2000
|
});
|
|
1982
2001
|
// DELETE /api/workspaces/:id/tasks/:taskId — delete a task
|
|
@@ -1997,7 +2016,7 @@ app.delete('/:id/tasks/:taskId', (c) => {
|
|
|
1997
2016
|
}
|
|
1998
2017
|
catch (err) {
|
|
1999
2018
|
const message = err instanceof Error ? err.message : String(err);
|
|
2000
|
-
return c.json({ error: message },
|
|
2019
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2001
2020
|
}
|
|
2002
2021
|
});
|
|
2003
2022
|
// POST /api/workspaces/:id/tasks/notify-updated — broadcast generic task list change
|
|
@@ -2015,7 +2034,7 @@ app.post('/:id/tasks/notify-updated', (c) => {
|
|
|
2015
2034
|
}
|
|
2016
2035
|
catch (err) {
|
|
2017
2036
|
const message = err instanceof Error ? err.message : String(err);
|
|
2018
|
-
return c.json({ error: message },
|
|
2037
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2019
2038
|
}
|
|
2020
2039
|
});
|
|
2021
2040
|
// POST /api/workspaces/:id/agent-description/notify-updated — broadcast
|
|
@@ -2037,7 +2056,7 @@ app.post('/:id/agent-description/notify-updated', (c) => {
|
|
|
2037
2056
|
}
|
|
2038
2057
|
catch (err) {
|
|
2039
2058
|
const message = err instanceof Error ? err.message : String(err);
|
|
2040
|
-
return c.json({ error: message },
|
|
2059
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2041
2060
|
}
|
|
2042
2061
|
});
|
|
2043
2062
|
// POST /api/workspaces/:id/crons/notify-updated — broadcast cron list changed
|
|
@@ -2053,7 +2072,7 @@ app.post('/:id/crons/notify-updated', (c) => {
|
|
|
2053
2072
|
}
|
|
2054
2073
|
catch (err) {
|
|
2055
2074
|
const message = err instanceof Error ? err.message : String(err);
|
|
2056
|
-
return c.json({ error: message },
|
|
2075
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2057
2076
|
}
|
|
2058
2077
|
});
|
|
2059
2078
|
// POST /api/workspaces/:id/tasks/:taskId/notify-done — broadcast task:updated event
|
|
@@ -2070,7 +2089,7 @@ app.post('/:id/tasks/:taskId/notify-done', (c) => {
|
|
|
2070
2089
|
}
|
|
2071
2090
|
catch (err) {
|
|
2072
2091
|
const message = err instanceof Error ? err.message : String(err);
|
|
2073
|
-
return c.json({ error: message },
|
|
2092
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2074
2093
|
}
|
|
2075
2094
|
});
|
|
2076
2095
|
// Cap how many recent rows one search may scan: the LIKE below cannot use an
|
|
@@ -2201,7 +2220,7 @@ app.get('/:id/sessions/:sessionId/summary', (c) => {
|
|
|
2201
2220
|
}
|
|
2202
2221
|
catch (err) {
|
|
2203
2222
|
const message = err instanceof Error ? err.message : String(err);
|
|
2204
|
-
return c.json({ error: message },
|
|
2223
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2205
2224
|
}
|
|
2206
2225
|
});
|
|
2207
2226
|
// GET /api/workspaces/:id/session-metrics — compact observability data for the timeline.
|
|
@@ -2378,7 +2397,7 @@ app.get('/:id/events', (c) => {
|
|
|
2378
2397
|
}
|
|
2379
2398
|
catch (err) {
|
|
2380
2399
|
const message = err instanceof Error ? err.message : String(err);
|
|
2381
|
-
return c.json({ error: message },
|
|
2400
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2382
2401
|
}
|
|
2383
2402
|
});
|
|
2384
2403
|
// GET /api/workspaces/archived — list archived workspaces (must be before GET /:id)
|
|
@@ -2388,7 +2407,7 @@ app.get('/archived', (c) => {
|
|
|
2388
2407
|
}
|
|
2389
2408
|
catch (err) {
|
|
2390
2409
|
const message = err instanceof Error ? err.message : String(err);
|
|
2391
|
-
return c.json({ error: message },
|
|
2410
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2392
2411
|
}
|
|
2393
2412
|
});
|
|
2394
2413
|
// GET /:id/prep-autoloop-prompt — compose the project-aware grooming
|
|
@@ -2415,7 +2434,7 @@ ${AUTO_LOOP_HARD_RULES}`;
|
|
|
2415
2434
|
}
|
|
2416
2435
|
catch (err) {
|
|
2417
2436
|
const message = err instanceof Error ? err.message : String(err);
|
|
2418
|
-
return c.json({ error: message },
|
|
2437
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2419
2438
|
}
|
|
2420
2439
|
});
|
|
2421
2440
|
// GET /:id/chat-history — list up to 200 most recent chat-input messages
|
|
@@ -2431,7 +2450,7 @@ app.get('/:id/chat-history', (c) => {
|
|
|
2431
2450
|
}
|
|
2432
2451
|
catch (err) {
|
|
2433
2452
|
const message = err instanceof Error ? err.message : String(err);
|
|
2434
|
-
return c.json({ error: message },
|
|
2453
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2435
2454
|
}
|
|
2436
2455
|
});
|
|
2437
2456
|
// POST /:id/chat-history — append a message to this workspace's history.
|
|
@@ -2454,7 +2473,7 @@ app.post('/:id/chat-history', async (c) => {
|
|
|
2454
2473
|
}
|
|
2455
2474
|
catch (err) {
|
|
2456
2475
|
const message = err instanceof Error ? err.message : String(err);
|
|
2457
|
-
return c.json({ error: message },
|
|
2476
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2458
2477
|
}
|
|
2459
2478
|
});
|
|
2460
2479
|
// POST /:id/save-file — persist edited file content from the diff viewer.
|
|
@@ -2490,7 +2509,7 @@ app.post('/:id/save-file', async (c) => {
|
|
|
2490
2509
|
}
|
|
2491
2510
|
catch (err) {
|
|
2492
2511
|
const message = err instanceof Error ? err.message : String(err);
|
|
2493
|
-
return c.json({ error: message },
|
|
2512
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2494
2513
|
}
|
|
2495
2514
|
});
|
|
2496
2515
|
// GET /api/workspaces/:id — get workspace details with tasks
|
|
@@ -2505,7 +2524,7 @@ app.get('/:id', (c) => {
|
|
|
2505
2524
|
}
|
|
2506
2525
|
catch (err) {
|
|
2507
2526
|
const message = err instanceof Error ? err.message : String(err);
|
|
2508
|
-
return c.json({ error: message },
|
|
2527
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2509
2528
|
}
|
|
2510
2529
|
});
|
|
2511
2530
|
// POST /api/workspaces/:id/favorite — mark workspace as favorite
|
|
@@ -2660,7 +2679,7 @@ app.patch('/:id', migrationGuard, async (c) => {
|
|
|
2660
2679
|
message.includes('Description must be')) {
|
|
2661
2680
|
return c.json({ error: message }, 400);
|
|
2662
2681
|
}
|
|
2663
|
-
return c.json({ error: message },
|
|
2682
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2664
2683
|
}
|
|
2665
2684
|
});
|
|
2666
2685
|
/** Open the workspace worktree in the user's configured editor. */
|
|
@@ -2696,7 +2715,7 @@ app.post('/:id/open-editor', (c) => {
|
|
|
2696
2715
|
}
|
|
2697
2716
|
catch (err) {
|
|
2698
2717
|
const message = err instanceof Error ? err.message : String(err);
|
|
2699
|
-
return c.json({ error: message },
|
|
2718
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2700
2719
|
}
|
|
2701
2720
|
});
|
|
2702
2721
|
/** Open the workspace worktree in the user's configured terminal emulator. */
|
|
@@ -2736,7 +2755,7 @@ app.post('/:id/open-terminal', async (c) => {
|
|
|
2736
2755
|
}
|
|
2737
2756
|
catch (err) {
|
|
2738
2757
|
const message = err instanceof Error ? err.message : String(err);
|
|
2739
|
-
return c.json({ error: message },
|
|
2758
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2740
2759
|
}
|
|
2741
2760
|
});
|
|
2742
2761
|
/** Open the workspace worktree in the user's configured file manager. */
|
|
@@ -2769,7 +2788,7 @@ app.post('/:id/open-file-manager', async (c) => {
|
|
|
2769
2788
|
}
|
|
2770
2789
|
catch (err) {
|
|
2771
2790
|
const message = err instanceof Error ? err.message : String(err);
|
|
2772
|
-
return c.json({ error: message },
|
|
2791
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2773
2792
|
}
|
|
2774
2793
|
});
|
|
2775
2794
|
/** Spawn a detached process and resolve as soon as it has started (or reject
|
|
@@ -2814,11 +2833,12 @@ app.post('/:id/run-setup-script', async (c) => {
|
|
|
2814
2833
|
// rewrites the worktree the agent may still be writing to.
|
|
2815
2834
|
try {
|
|
2816
2835
|
if (agentManager.getAgentStatus(id)) {
|
|
2817
|
-
await agentManager.stopAgentAndWait(id);
|
|
2836
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'setup'));
|
|
2818
2837
|
}
|
|
2819
2838
|
}
|
|
2820
2839
|
catch (err) {
|
|
2821
2840
|
console.error(`[workspaces] stopAgentAndWait before setup script failed for '${id}':`, err);
|
|
2841
|
+
throw err;
|
|
2822
2842
|
}
|
|
2823
2843
|
const effectiveSettings = settingsService.getEffectiveSettings(workspace.projectPath);
|
|
2824
2844
|
if (!effectiveSettings.setupScript) {
|
|
@@ -2845,7 +2865,7 @@ app.post('/:id/run-setup-script', async (c) => {
|
|
|
2845
2865
|
}
|
|
2846
2866
|
catch (err) {
|
|
2847
2867
|
const message = err instanceof Error ? err.message : String(err);
|
|
2848
|
-
return c.json({ error: message },
|
|
2868
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2849
2869
|
}
|
|
2850
2870
|
});
|
|
2851
2871
|
// POST /api/workspaces/:id/archive — mark workspace as archived (soft-delete)
|
|
@@ -2860,10 +2880,11 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2860
2880
|
return c.json({ error: 'Already archived' }, 400);
|
|
2861
2881
|
}
|
|
2862
2882
|
try {
|
|
2863
|
-
await agentManager.stopAgentAndWait(id);
|
|
2883
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'archive'));
|
|
2864
2884
|
}
|
|
2865
2885
|
catch (err) {
|
|
2866
2886
|
console.error(`[workspaces] stopAgentAndWait during archive failed for '${id}':`, err);
|
|
2887
|
+
throw err;
|
|
2867
2888
|
}
|
|
2868
2889
|
try {
|
|
2869
2890
|
await devServerService.stopDevServer(id);
|
|
@@ -2871,6 +2892,7 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2871
2892
|
catch (err) {
|
|
2872
2893
|
const message = err instanceof Error ? err.message : String(err);
|
|
2873
2894
|
console.error(`[workspaces] stopDevServer during archive failed: ${message}`);
|
|
2895
|
+
throw err;
|
|
2874
2896
|
}
|
|
2875
2897
|
try {
|
|
2876
2898
|
terminalService.destroyTerminal(id);
|
|
@@ -2886,7 +2908,7 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
2886
2908
|
}
|
|
2887
2909
|
catch (err) {
|
|
2888
2910
|
const message = err instanceof Error ? err.message : String(err);
|
|
2889
|
-
return c.json({ error: message },
|
|
2911
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2890
2912
|
}
|
|
2891
2913
|
});
|
|
2892
2914
|
// POST /api/workspaces/:id/purge-worktree
|
|
@@ -2909,7 +2931,7 @@ app.post('/:id/purge-worktree', migrationGuard, async (c) => {
|
|
|
2909
2931
|
if (err instanceof WorkspaceLifecycleBusyError)
|
|
2910
2932
|
return c.json({ code: err.code, error: err.message }, 409);
|
|
2911
2933
|
const message = err instanceof Error ? err.message : String(err);
|
|
2912
|
-
return c.json({ error: message },
|
|
2934
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2913
2935
|
}
|
|
2914
2936
|
});
|
|
2915
2937
|
// Recreate the checkout before unarchiving; never starts agents or setup scripts.
|
|
@@ -2953,7 +2975,7 @@ app.post('/:id/unarchive', migrationGuard, (c) => {
|
|
|
2953
2975
|
}
|
|
2954
2976
|
catch (err) {
|
|
2955
2977
|
const message = err instanceof Error ? err.message : String(err);
|
|
2956
|
-
return c.json({ error: message },
|
|
2978
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
2957
2979
|
}
|
|
2958
2980
|
});
|
|
2959
2981
|
// Shared teardown for a single workspace: stops the agent, destroys the
|
|
@@ -2979,10 +3001,11 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
2979
3001
|
// a few lines below: firing and forgetting here pulled the directory from
|
|
2980
3002
|
// under an agent still writing to it, destroying uncommitted work.
|
|
2981
3003
|
try {
|
|
2982
|
-
await agentManager.stopAgentAndWait(workspace.id, undefined, 'delete');
|
|
3004
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(workspace.id, undefined, 'delete'));
|
|
2983
3005
|
}
|
|
2984
3006
|
catch (err) {
|
|
2985
3007
|
console.error(`[workspaces] stopAgentAndWait during delete failed for '${workspace.name}':`, err);
|
|
3008
|
+
throw err;
|
|
2986
3009
|
}
|
|
2987
3010
|
// Stop dev server if it was running. The processSpawn would otherwise
|
|
2988
3011
|
// outlive the workspace (and keep its port + docker containers alive).
|
|
@@ -2991,6 +3014,7 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
2991
3014
|
}
|
|
2992
3015
|
catch (err) {
|
|
2993
3016
|
console.error(`[workspaces] stopDevServer during delete failed for '${workspace.name}':`, err);
|
|
3017
|
+
throw err;
|
|
2994
3018
|
}
|
|
2995
3019
|
try {
|
|
2996
3020
|
terminalService.destroyTerminal(workspace.id);
|
|
@@ -3089,7 +3113,7 @@ app.delete('/archived', migrationGuard, async (c) => {
|
|
|
3089
3113
|
}
|
|
3090
3114
|
catch (err) {
|
|
3091
3115
|
const message = err instanceof Error ? err.message : String(err);
|
|
3092
|
-
return c.json({ error: message },
|
|
3116
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3093
3117
|
}
|
|
3094
3118
|
});
|
|
3095
3119
|
// DELETE /api/workspaces/:id — delete workspace
|
|
@@ -3117,7 +3141,7 @@ app.delete('/:id', migrationGuard, async (c) => {
|
|
|
3117
3141
|
if (err instanceof WorkspaceLifecycleBusyError)
|
|
3118
3142
|
return c.json({ code: err.code, error: err.message }, 409);
|
|
3119
3143
|
const message = err instanceof Error ? err.message : String(err);
|
|
3120
|
-
return c.json({ error: message },
|
|
3144
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3121
3145
|
}
|
|
3122
3146
|
});
|
|
3123
3147
|
// POST /api/workspaces/:id/start — start/restart agent
|
|
@@ -3162,10 +3186,11 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3162
3186
|
quotaBackoffService.cancel(id, 'user');
|
|
3163
3187
|
// Stop the existing agent and wait: a fresh one starts right below.
|
|
3164
3188
|
try {
|
|
3165
|
-
await agentManager.stopAgentAndWait(id);
|
|
3189
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id, undefined, 'replacement'));
|
|
3166
3190
|
}
|
|
3167
3191
|
catch (err) {
|
|
3168
3192
|
console.error(`[workspaces] stopAgentAndWait before start failed for '${id}':`, err);
|
|
3193
|
+
throw err;
|
|
3169
3194
|
}
|
|
3170
3195
|
const worktreePath = workspace.worktreePath;
|
|
3171
3196
|
const agent = agentManager.startAgent(id, worktreePath, prompt, workspace.model, resume, workspace.agentPermissionMode, agentSessionId, workspace.reasoningEffort);
|
|
@@ -3195,7 +3220,7 @@ app.post('/:id/start', migrationGuard, async (c) => {
|
|
|
3195
3220
|
}
|
|
3196
3221
|
catch (err) {
|
|
3197
3222
|
const message = err instanceof Error ? err.message : String(err);
|
|
3198
|
-
return c.json({ error: message },
|
|
3223
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3199
3224
|
}
|
|
3200
3225
|
});
|
|
3201
3226
|
// GET /api/workspaces/:id/git-stats — commit count and diff stats for the branch
|
|
@@ -3221,7 +3246,7 @@ app.get('/:id/git-stats', async (c) => {
|
|
|
3221
3246
|
}
|
|
3222
3247
|
catch (err) {
|
|
3223
3248
|
const message = err instanceof Error ? err.message : String(err);
|
|
3224
|
-
return c.json({ error: message },
|
|
3249
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3225
3250
|
}
|
|
3226
3251
|
});
|
|
3227
3252
|
// GET /api/workspaces/:id/diff?mode=branch|unpushed — list changed files
|
|
@@ -3283,7 +3308,7 @@ app.get('/:id/diff', async (c) => {
|
|
|
3283
3308
|
}
|
|
3284
3309
|
catch (err) {
|
|
3285
3310
|
const message = err instanceof Error ? err.message : String(err);
|
|
3286
|
-
return c.json({ error: message },
|
|
3311
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3287
3312
|
}
|
|
3288
3313
|
});
|
|
3289
3314
|
// GET /api/workspaces/:id/diff-file?path=...&mode=branch|unpushed
|
|
@@ -3349,7 +3374,7 @@ app.get('/:id/diff-file', (c) => {
|
|
|
3349
3374
|
}
|
|
3350
3375
|
catch (err) {
|
|
3351
3376
|
const message = err instanceof Error ? err.message : String(err);
|
|
3352
|
-
return c.json({ error: message },
|
|
3377
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3353
3378
|
}
|
|
3354
3379
|
});
|
|
3355
3380
|
// POST /api/workspaces/:id/rollback-file { path }
|
|
@@ -3388,7 +3413,7 @@ app.post('/:id/rollback-file', async (c) => {
|
|
|
3388
3413
|
}
|
|
3389
3414
|
catch (err) {
|
|
3390
3415
|
const message = err instanceof Error ? err.message : String(err);
|
|
3391
|
-
return c.json({ error: message },
|
|
3416
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3392
3417
|
}
|
|
3393
3418
|
});
|
|
3394
3419
|
// GET /api/workspaces/:id/branch-divergence?limit=50
|
|
@@ -3420,7 +3445,7 @@ app.get('/:id/branch-divergence', async (c) => {
|
|
|
3420
3445
|
}
|
|
3421
3446
|
catch (err) {
|
|
3422
3447
|
const message = err instanceof Error ? err.message : String(err);
|
|
3423
|
-
return c.json({ error: message },
|
|
3448
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3424
3449
|
}
|
|
3425
3450
|
});
|
|
3426
3451
|
// GET /api/workspaces/:id/commits?limit=50 — list commits between
|
|
@@ -3444,7 +3469,7 @@ app.get('/:id/commits', async (c) => {
|
|
|
3444
3469
|
}
|
|
3445
3470
|
catch (err) {
|
|
3446
3471
|
const message = err instanceof Error ? err.message : String(err);
|
|
3447
|
-
return c.json({ error: message },
|
|
3472
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3448
3473
|
}
|
|
3449
3474
|
});
|
|
3450
3475
|
// GET /:id/working-tree-files — list uncommitted working-tree files (read-only)
|
|
@@ -3459,7 +3484,7 @@ app.get('/:id/working-tree-files', (c) => {
|
|
|
3459
3484
|
}
|
|
3460
3485
|
catch (err) {
|
|
3461
3486
|
const message = err instanceof Error ? err.message : String(err);
|
|
3462
|
-
return c.json({ error: message },
|
|
3487
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3463
3488
|
}
|
|
3464
3489
|
});
|
|
3465
3490
|
// POST /api/workspaces/:id/rename-branch { newName }
|
|
@@ -3531,7 +3556,7 @@ app.post('/:id/rename-branch', async (c) => {
|
|
|
3531
3556
|
}
|
|
3532
3557
|
catch (err) {
|
|
3533
3558
|
const message = err instanceof Error ? err.message : String(err);
|
|
3534
|
-
return c.json({ error: message },
|
|
3559
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3535
3560
|
}
|
|
3536
3561
|
});
|
|
3537
3562
|
// POST /api/workspaces/:id/resync-branch
|
|
@@ -3587,7 +3612,7 @@ app.post('/:id/resync-branch', (c) => {
|
|
|
3587
3612
|
}
|
|
3588
3613
|
catch (err) {
|
|
3589
3614
|
const message = err instanceof Error ? err.message : String(err);
|
|
3590
|
-
return c.json({ error: message },
|
|
3615
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3591
3616
|
}
|
|
3592
3617
|
});
|
|
3593
3618
|
// POST /api/workspaces/:id/push — push working branch to origin
|
|
@@ -3624,7 +3649,7 @@ app.post('/:id/push', async (c) => {
|
|
|
3624
3649
|
}
|
|
3625
3650
|
catch (err) {
|
|
3626
3651
|
const message = err instanceof Error ? err.message : String(err);
|
|
3627
|
-
return c.json({ error: message },
|
|
3652
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3628
3653
|
}
|
|
3629
3654
|
// Emit a trace into the chat feed so the user sees the action
|
|
3630
3655
|
const session = workspaceService.getActiveSession(id);
|
|
@@ -3638,7 +3663,7 @@ app.post('/:id/push', async (c) => {
|
|
|
3638
3663
|
}
|
|
3639
3664
|
catch (err) {
|
|
3640
3665
|
const message = err instanceof Error ? err.message : String(err);
|
|
3641
|
-
return c.json({ error: message },
|
|
3666
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3642
3667
|
}
|
|
3643
3668
|
});
|
|
3644
3669
|
// POST /api/workspaces/:id/fetch — git fetch the workspace repo (all branches of origin).
|
|
@@ -3655,7 +3680,7 @@ app.post('/:id/fetch', async (c) => {
|
|
|
3655
3680
|
}
|
|
3656
3681
|
catch (err) {
|
|
3657
3682
|
const message = err instanceof Error ? err.message : String(err);
|
|
3658
|
-
return c.json({ error: message },
|
|
3683
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3659
3684
|
}
|
|
3660
3685
|
});
|
|
3661
3686
|
// POST /api/workspaces/:id/pull — pull working branch from origin (fast-forward only)
|
|
@@ -3676,7 +3701,7 @@ app.post('/:id/pull', async (c) => {
|
|
|
3676
3701
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3677
3702
|
}
|
|
3678
3703
|
const message = err instanceof Error ? err.message : String(err);
|
|
3679
|
-
return c.json({ error: message },
|
|
3704
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3680
3705
|
}
|
|
3681
3706
|
// Emit a trace into the chat feed so the user sees the action
|
|
3682
3707
|
const session = workspaceService.getActiveSession(id);
|
|
@@ -3685,7 +3710,7 @@ app.post('/:id/pull', async (c) => {
|
|
|
3685
3710
|
}
|
|
3686
3711
|
catch (err) {
|
|
3687
3712
|
const message = err instanceof Error ? err.message : String(err);
|
|
3688
|
-
return c.json({ error: message },
|
|
3713
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3689
3714
|
}
|
|
3690
3715
|
});
|
|
3691
3716
|
/** Rebase the workspace branch onto its source branch. */
|
|
@@ -3708,7 +3733,7 @@ app.post('/:id/rebase', async (c) => {
|
|
|
3708
3733
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3709
3734
|
}
|
|
3710
3735
|
const message = err instanceof Error ? err.message : String(err);
|
|
3711
|
-
return c.json({ error: message },
|
|
3736
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3712
3737
|
}
|
|
3713
3738
|
});
|
|
3714
3739
|
/** Merge the source branch into the workspace branch (non-fast-forward). */
|
|
@@ -3731,7 +3756,7 @@ app.post('/:id/merge', async (c) => {
|
|
|
3731
3756
|
return c.json({ error: err.message, code: 'dirty_worktree', operation: err.operation, status: err.status }, 409);
|
|
3732
3757
|
}
|
|
3733
3758
|
const message = err instanceof Error ? err.message : String(err);
|
|
3734
|
-
return c.json({ error: message },
|
|
3759
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3735
3760
|
}
|
|
3736
3761
|
});
|
|
3737
3762
|
/** Abort any in-progress merge or rebase in the worktree. */
|
|
@@ -3747,7 +3772,7 @@ app.post('/:id/git/abort', (c) => {
|
|
|
3747
3772
|
}
|
|
3748
3773
|
catch (err) {
|
|
3749
3774
|
const message = err instanceof Error ? err.message : String(err);
|
|
3750
|
-
return c.json({ error: message },
|
|
3775
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3751
3776
|
}
|
|
3752
3777
|
});
|
|
3753
3778
|
/** Continue an in-progress merge, rebase or cherry-pick after resolution. */
|
|
@@ -3764,7 +3789,7 @@ app.post('/:id/git/continue', (c) => {
|
|
|
3764
3789
|
}
|
|
3765
3790
|
catch (err) {
|
|
3766
3791
|
const message = err instanceof Error ? err.message : String(err);
|
|
3767
|
-
return c.json({ error: message },
|
|
3792
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3768
3793
|
}
|
|
3769
3794
|
});
|
|
3770
3795
|
/** Stage + commit all working-tree changes (recovery action for a dirty rebase/merge). */
|
|
@@ -3783,7 +3808,7 @@ app.post('/:id/git/commit-all', async (c) => {
|
|
|
3783
3808
|
}
|
|
3784
3809
|
catch (err) {
|
|
3785
3810
|
const message = err instanceof Error ? err.message : String(err);
|
|
3786
|
-
return c.json({ error: message },
|
|
3811
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3787
3812
|
}
|
|
3788
3813
|
});
|
|
3789
3814
|
/** Ask the workspace agent to inspect and commit the current working tree. */
|
|
@@ -3835,7 +3860,7 @@ app.post('/:id/git/discard', (c) => {
|
|
|
3835
3860
|
}
|
|
3836
3861
|
catch (err) {
|
|
3837
3862
|
const message = err instanceof Error ? err.message : String(err);
|
|
3838
|
-
return c.json({ error: message },
|
|
3863
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3839
3864
|
}
|
|
3840
3865
|
});
|
|
3841
3866
|
/** Hand off merge/rebase conflicts to the workspace agent with an intelligent-resolution prompt. */
|
|
@@ -3906,7 +3931,7 @@ Start now.`;
|
|
|
3906
3931
|
}
|
|
3907
3932
|
catch (err) {
|
|
3908
3933
|
const message = err instanceof Error ? err.message : String(err);
|
|
3909
|
-
return c.json({ error: message },
|
|
3934
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3910
3935
|
}
|
|
3911
3936
|
});
|
|
3912
3937
|
/** Change the base branch of an existing PR via the resolved forge provider. */
|
|
@@ -3943,7 +3968,7 @@ app.post('/:id/change-pr-base', async (c) => {
|
|
|
3943
3968
|
return c.json({ error: err.message, code: 'forge_unavailable' }, 409);
|
|
3944
3969
|
}
|
|
3945
3970
|
const message = err instanceof Error ? err.message : String(err);
|
|
3946
|
-
return c.json({ error: message },
|
|
3971
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3947
3972
|
}
|
|
3948
3973
|
});
|
|
3949
3974
|
/** Merge a ready, open PR/MR through its resolved forge provider. */
|
|
@@ -3980,7 +4005,7 @@ app.post('/:id/merge-pr', async (c) => {
|
|
|
3980
4005
|
return c.json({ error: err.message, code: 'forge_unavailable' }, 409);
|
|
3981
4006
|
}
|
|
3982
4007
|
const message = err instanceof Error ? err.message : String(err);
|
|
3983
|
-
return c.json({ error: message },
|
|
4008
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
3984
4009
|
}
|
|
3985
4010
|
});
|
|
3986
4011
|
/** Delete the workspace branch from the remote after its PR/MR was merged. */
|
|
@@ -4005,7 +4030,7 @@ app.post('/:id/delete-remote-branch', async (c) => {
|
|
|
4005
4030
|
}
|
|
4006
4031
|
catch (err) {
|
|
4007
4032
|
const message = err instanceof Error ? err.message : String(err);
|
|
4008
|
-
return c.json({ error: message },
|
|
4033
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4009
4034
|
}
|
|
4010
4035
|
});
|
|
4011
4036
|
/** Re-target a workspace onto a new source branch (metadata + worktree + PR base). */
|
|
@@ -4077,13 +4102,17 @@ app.post('/:id/cancel-source-change', async (c) => {
|
|
|
4077
4102
|
if (backups.length === 0) {
|
|
4078
4103
|
return c.json({ error: 'No backup branch found — cannot auto-restore', code: 'no_backup' }, 409);
|
|
4079
4104
|
}
|
|
4080
|
-
|
|
4105
|
+
if (agentManager.hasController(id))
|
|
4106
|
+
return c.json({ error: 'Stop the agent before restoring the branch' }, 409);
|
|
4107
|
+
await withGitRepoLock(workspace.worktreePath, async () => {
|
|
4108
|
+
gitOps.restoreBranchFromBackup(workspace.worktreePath, workspace.workingBranch, backups[0]);
|
|
4109
|
+
});
|
|
4081
4110
|
workspaceService.updateWorkspaceSourceBranch(id, body.previousBase);
|
|
4082
4111
|
return c.json({ success: true, restoredFrom: backups[0] });
|
|
4083
4112
|
}
|
|
4084
4113
|
catch (err) {
|
|
4085
4114
|
const message = err instanceof Error ? err.message : String(err);
|
|
4086
|
-
return c.json({ error: message },
|
|
4115
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4087
4116
|
}
|
|
4088
4117
|
});
|
|
4089
4118
|
/** Force-push the working branch with --force-with-lease (after a history rewrite). */
|
|
@@ -4119,7 +4148,7 @@ app.post('/:id/force-push', async (c) => {
|
|
|
4119
4148
|
}
|
|
4120
4149
|
catch (err) {
|
|
4121
4150
|
const message = err instanceof Error ? err.message : String(err);
|
|
4122
|
-
return c.json({ error: message },
|
|
4151
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4123
4152
|
}
|
|
4124
4153
|
});
|
|
4125
4154
|
// POST /api/workspaces/:id/open-pr — create a GitHub PR and send a templated prompt to the agent
|
|
@@ -4239,7 +4268,7 @@ app.post('/:id/open-pr', async (c) => {
|
|
|
4239
4268
|
}
|
|
4240
4269
|
catch (err) {
|
|
4241
4270
|
const message = err instanceof Error ? err.message : String(err);
|
|
4242
|
-
return c.json({ error: message },
|
|
4271
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4243
4272
|
}
|
|
4244
4273
|
});
|
|
4245
4274
|
// POST /api/workspaces/:id/start-review — ask the agent to review committed + uncommitted changes
|
|
@@ -4302,11 +4331,12 @@ app.post('/:id/start-review', async (c) => {
|
|
|
4302
4331
|
if (newSession) {
|
|
4303
4332
|
// Stop current agent, wait for it, then start fresh.
|
|
4304
4333
|
try {
|
|
4305
|
-
await agentManager.stopAgentAndWait(workspace.id);
|
|
4334
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(workspace.id, undefined, 'replacement'));
|
|
4306
4335
|
}
|
|
4307
4336
|
catch (err) {
|
|
4308
4337
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4309
4338
|
console.error(`[start-review] stopAgentAndWait failed (continuing): ${msg}`);
|
|
4339
|
+
throw err;
|
|
4310
4340
|
}
|
|
4311
4341
|
try {
|
|
4312
4342
|
const agent = agentManager.startAgent(workspace.id, worktreePath, rendered, workspace.model, false /* resume */, workspace.agentPermissionMode, undefined, workspace.reasoningEffort);
|
|
@@ -4339,7 +4369,7 @@ app.post('/:id/start-review', async (c) => {
|
|
|
4339
4369
|
}
|
|
4340
4370
|
catch (err) {
|
|
4341
4371
|
const message = err instanceof Error ? err.message : String(err);
|
|
4342
|
-
return c.json({ error: message },
|
|
4372
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4343
4373
|
}
|
|
4344
4374
|
});
|
|
4345
4375
|
// POST /api/workspaces/:id/start-ci-fix — dispatch the configured CI-fix
|
|
@@ -4405,7 +4435,7 @@ app.post('/:id/start-ci-fix', migrationGuard, async (c) => {
|
|
|
4405
4435
|
}
|
|
4406
4436
|
catch (err) {
|
|
4407
4437
|
const message = err instanceof Error ? err.message : String(err);
|
|
4408
|
-
return c.json({ error: message },
|
|
4438
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4409
4439
|
}
|
|
4410
4440
|
});
|
|
4411
4441
|
/**
|
|
@@ -4435,7 +4465,7 @@ app.post('/:id/dismiss-pr-attention', async (c) => {
|
|
|
4435
4465
|
}
|
|
4436
4466
|
catch (err) {
|
|
4437
4467
|
const message = err instanceof Error ? err.message : String(err);
|
|
4438
|
-
return c.json({ error: message },
|
|
4468
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4439
4469
|
}
|
|
4440
4470
|
});
|
|
4441
4471
|
/**
|
|
@@ -4461,7 +4491,7 @@ app.post('/:id/restore-pr-attention', async (c) => {
|
|
|
4461
4491
|
}
|
|
4462
4492
|
catch (err) {
|
|
4463
4493
|
const message = err instanceof Error ? err.message : String(err);
|
|
4464
|
-
return c.json({ error: message },
|
|
4494
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4465
4495
|
}
|
|
4466
4496
|
});
|
|
4467
4497
|
/** POST /api/workspaces/:id/mark-read — mark workspace as read (clear unread indicator). */
|
|
@@ -4478,7 +4508,7 @@ app.post('/:id/mark-read', (c) => {
|
|
|
4478
4508
|
}
|
|
4479
4509
|
catch (err) {
|
|
4480
4510
|
const message = err instanceof Error ? err.message : String(err);
|
|
4481
|
-
return c.json({ error: message },
|
|
4511
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4482
4512
|
}
|
|
4483
4513
|
});
|
|
4484
4514
|
// POST /api/workspaces/:id/stop — stop agent
|
|
@@ -4491,11 +4521,12 @@ app.post('/:id/stop', migrationGuard, async (c) => {
|
|
|
4491
4521
|
}
|
|
4492
4522
|
try {
|
|
4493
4523
|
// The response says "stopped": make that true before returning.
|
|
4494
|
-
await agentManager.stopAgentAndWait(id);
|
|
4524
|
+
assertAgentStopped(await agentManager.stopAgentAndWait(id));
|
|
4495
4525
|
}
|
|
4496
4526
|
catch (err) {
|
|
4497
4527
|
// Agent may not be tracked (e.g. server restarted) — just update status
|
|
4498
4528
|
console.error(`[workspaces] stopAgentAndWait on manual stop failed for '${id}':`, err);
|
|
4529
|
+
throw err;
|
|
4499
4530
|
}
|
|
4500
4531
|
// Always transition to idle so the UI reflects the stopped state
|
|
4501
4532
|
try {
|
|
@@ -4508,7 +4539,7 @@ app.post('/:id/stop', migrationGuard, async (c) => {
|
|
|
4508
4539
|
}
|
|
4509
4540
|
catch (err) {
|
|
4510
4541
|
const message = err instanceof Error ? err.message : String(err);
|
|
4511
|
-
return c.json({ error: message },
|
|
4542
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4512
4543
|
}
|
|
4513
4544
|
});
|
|
4514
4545
|
// POST /api/workspaces/:id/interrupt — soft-interrupt agent (SIGINT, like Escape in Claude Code)
|
|
@@ -4555,7 +4586,7 @@ app.post('/:id/interrupt', migrationGuard, async (c) => {
|
|
|
4555
4586
|
const status = err.code === 'interrupt_failed' ? 500 : 409;
|
|
4556
4587
|
return c.json({ error: message, code: err.code }, status);
|
|
4557
4588
|
}
|
|
4558
|
-
return c.json({ error: message },
|
|
4589
|
+
return c.json({ error: message }, workspaceErrorStatus(err));
|
|
4559
4590
|
}
|
|
4560
4591
|
});
|
|
4561
4592
|
export default app;
|