@loicngr/kobo 2.1.4 → 2.1.6
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/CHANGELOG.md +8 -0
- package/CONFIGURATION.md +11 -3
- package/dist/server/index.js +24 -91
- package/dist/server/routes/git.js +6 -7
- package/dist/server/routes/settings.js +21 -11
- package/dist/server/routes/workspaces.js +84 -74
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +33 -0
- package/dist/server/services/agent/engines/codex/engine.js +10 -1
- package/dist/server/services/agent/engines/codex/event-mapper.js +2 -0
- package/dist/server/services/agent/integration-mcp.js +4 -3
- package/dist/server/services/agent/orchestrator.js +1 -1
- package/dist/server/services/agent/streaming-batcher.js +14 -3
- package/dist/server/services/engine-handoff-service.js +120 -24
- package/dist/server/services/pr-checkout-service.js +90 -53
- package/dist/server/services/pr-watcher-service.js +13 -7
- package/dist/server/services/retention/worker.js +40 -0
- package/dist/server/services/review-service.js +8 -4
- package/dist/server/services/session-event-metrics.js +43 -0
- package/dist/server/services/session-handoff-service.js +6 -4
- package/dist/server/services/templates-service.js +67 -49
- package/dist/server/services/terminal-service.js +45 -14
- package/dist/server/services/terminal-websocket-service.js +143 -0
- package/dist/server/services/wakeup-service.js +23 -21
- package/dist/server/services/workspace-service.js +3 -44
- package/dist/server/services/workspace-template-service.js +16 -18
- package/dist/server/services/worktree-purge-service.js +1 -4
- package/dist/server/services/ws-events-retention-service.js +197 -104
- package/dist/server/services/ws-events-retention-worker-service.js +112 -0
- package/dist/server/utils/atomic-json-file.js +23 -0
- package/dist/server/utils/git-ops.js +262 -60
- package/dist/shared/codex-models.js +20 -3
- package/dist/shared/models.js +12 -0
- package/package.json +6 -6
- package/src/client/dist/pwa/assets/ActivityFeed-BO1mKMJ0.js +1 -0
- package/src/client/dist/pwa/assets/ActivityFeed-ZTxXVmNM.css +1 -0
- package/src/client/dist/pwa/assets/ChangelogPage-BXNz7PGb.js +1 -0
- package/src/client/dist/pwa/assets/ChangelogPage-DVyx9ZjV.css +1 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-ByVbqYCs.css +1 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-CfmJHeH_.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-6_AP5zso.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-CMyl2j8Z.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-BKCrV6u0.js +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-CtaHaVdf.css +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-BMzrb9fB.js +8 -0
- package/src/client/dist/pwa/assets/DocumentsPanel-CqGg38Oi.js +1 -0
- package/src/client/dist/pwa/assets/{DrawerToggleButton-Bh4FXdtJ.js → DrawerToggleButton-DKytIBlk.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-C4wOcZV2.css → GitPanel-C3blk_Pd.css} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-DRzhrs_V.js +3 -0
- package/src/client/dist/pwa/assets/HealthPage-DT8FGrKA.css +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DWotmfkh.js +1 -0
- package/src/client/dist/pwa/assets/MainLayout-DkSavoiV.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-fRVZ01LN.js +2 -0
- package/src/client/dist/pwa/assets/QBanner-mzrnn82R.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-DVbxGTNb.js → QBtnDropdown-BGGvQIvH.js} +1 -1
- package/src/client/dist/pwa/assets/QChip-Chu7r6RZ.js +1 -0
- package/src/client/dist/pwa/assets/QExpansionItem-C2XXHOHk.js +1 -0
- package/src/client/dist/pwa/assets/QItemSection-CR3Kfibl.js +1 -0
- package/src/client/dist/pwa/assets/QList-DnezW6gW.js +1 -0
- package/src/client/dist/pwa/assets/QMarkupTable-wuDf50Oh.js +1 -0
- package/src/client/dist/pwa/assets/QScrollArea-BnmQ9US7.js +1 -0
- package/src/client/dist/pwa/assets/QSelect-BskTzVCM.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-BLhkNT4s.js → QTooltip-CO84H0cq.js} +1 -1
- package/src/client/dist/pwa/assets/SchedulePanel-CBM_GpS3.css +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-CPirTYw5.js +1 -0
- package/src/client/dist/pwa/assets/SearchPage-CnT0-Hpe.js +1 -0
- package/src/client/dist/pwa/assets/{SettingsPage-BYdLXJEJ.css → SettingsPage-CpykRKq2.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-DCMZL7GO.js +9 -0
- package/src/client/dist/pwa/assets/SplitWorkspacePage-DwWsD7f5.js +1 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-D2j5AA8S.js → TerminalPanel-C4dYabx6.js} +2 -2
- package/src/client/dist/pwa/assets/{TerminalPanel-DbVZk0vD.css → TerminalPanel-Dz9EE-Ph.css} +1 -1
- package/src/client/dist/pwa/assets/ToolCallItem-CjXWiKJc.js +8 -0
- package/src/client/dist/pwa/assets/ToolCallItem-DhjdG8vp.css +1 -0
- package/src/client/dist/pwa/assets/{TouchPan-C0EqFcrr.js → TouchPan-6fI8xFPq.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-DlMvPblb.js → TourReplayButton-DhbNI6O5.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CZxW7QSq.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DSw8Upcp.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-B0wCQdev.js +4 -0
- package/src/client/dist/pwa/assets/{WorkspacePage--5HsnMQ1.css → WorkspacePage-BnW-gp3u.css} +1 -1
- package/src/client/dist/pwa/assets/{browser-DNEbLF-4.js → browser-CTkGHrN5.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-Bd50EZMW.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-DoPDmcY5.js +1 -0
- package/src/client/dist/pwa/assets/de-BPtzBQu0.js +17 -0
- package/src/client/dist/pwa/assets/defineProperty-BbfpZ9Tg.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-PENN0io6.js → editor.api-CKcTKXcM.js} +1 -1
- package/src/client/dist/pwa/assets/efforts-CDsBbk8o.js +9 -0
- package/src/client/dist/pwa/assets/es-BoNOfG0w.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-D6zFPzzB.js +1 -0
- package/src/client/dist/pwa/assets/{format-CVI7mr7G.js → format-MtzcnR3z.js} +7 -7
- package/src/client/dist/pwa/assets/formatters-DCjBZ47F.js +1 -0
- package/src/client/dist/pwa/assets/fr-Bu92fK5U.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-BxN_Aq6C.js → freemarker2-JGUTjYvR.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-C9UHXtO1.js → handlebars-BWStl2Hc.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CTH9wSJr.js → html-CjGCxUEe.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-DKj-umtn.js +19 -0
- package/src/client/dist/pwa/assets/i18n-DtmbeJu8.js +1 -0
- package/src/client/dist/pwa/assets/index-BPmUmc6D.js +2 -0
- package/src/client/dist/pwa/assets/it-CrA4Q7oc.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-WT4BS8p4.js → javascript-C_jd0j2i.js} +1 -1
- package/src/client/dist/pwa/assets/latest-request-CBPb8UWQ.js +1 -0
- package/src/client/dist/pwa/assets/{liquid-D6dfe-gU.js → liquid-DMyQVGkp.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-B8pNXNeC.js → mdx-DeW5qzOU.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-COLfslyi.js → monaco-contributions-Be_o6s3m.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-VUGNbVME.js +2 -0
- package/src/client/dist/pwa/assets/network-auth-CSx0jS5X.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-7MsG-MyN.js → notify-theme-uhSG2r09.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-B-ciuUbz.js → permissionModes-Cc5VaOdC.js} +1 -1
- package/src/client/dist/pwa/assets/preload-helper-DjzSQ1t3.js +1 -0
- package/src/client/dist/pwa/assets/{project-color-Bo6iEcqN.js → project-color-BQbOnYBN.js} +1 -1
- package/src/client/dist/pwa/assets/{python-D2gb6Jjz.js → python-C1v3VovI.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-dksrCQF2.js → razor-DMEmKSdO.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-px_6WDIX.js → render-chat-markdown-CafzCcmu.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-DTPyRTrW.js → typescript-rKyiSQID.js} +1 -1
- package/src/client/dist/pwa/assets/use-checkbox-BqDMQ6x7.js +1 -0
- package/src/client/dist/pwa/assets/use-search-index-status-BL3cb04x.js +1 -0
- package/src/client/dist/pwa/assets/{use-tours-b00RruX_.js → use-tours-Dum-bOyQ.js} +2 -2
- package/src/client/dist/pwa/assets/use-worktree-restore-BQyoz3Qa.js +1 -0
- package/src/client/dist/pwa/assets/workspace-templates-YRwFjkUh.js +1 -0
- package/src/client/dist/pwa/assets/{xml-mxUZCM4r.js → xml-U_pDcywM.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-BuN8sUWC.js → yaml-CRCd_ET9.js} +1 -1
- package/src/client/dist/pwa/index.html +6 -3
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DCmiJP-z.css +0 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DdGARu8o.js +0 -8
- package/src/client/dist/pwa/assets/ChangelogPage-CGenjiI5.js +0 -1
- package/src/client/dist/pwa/assets/ChangelogPage-DKJkFiU_.css +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-aXgjEulK.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-BaB_30xJ.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DsbwmO2W.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-XiCj24i2.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer--dUMf7b3.js +0 -8
- package/src/client/dist/pwa/assets/DocumentsPanel-DTjEUYUd.js +0 -1
- package/src/client/dist/pwa/assets/GitPanel-PUPLj0M8.js +0 -3
- package/src/client/dist/pwa/assets/HealthPage-BHgucifz.js +0 -1
- package/src/client/dist/pwa/assets/HealthPage-CFZ-tdKK.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CNmwCnDD.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CQVLnTX3.js +0 -2
- package/src/client/dist/pwa/assets/QBanner-XE0EMe9L.js +0 -1
- package/src/client/dist/pwa/assets/QChip-Bt_JvnHb.js +0 -1
- package/src/client/dist/pwa/assets/QExpansionItem-CxEBY3jY.js +0 -1
- package/src/client/dist/pwa/assets/QItemSection-Cf6OZEH4.js +0 -1
- package/src/client/dist/pwa/assets/QList-Z90BspT4.js +0 -1
- package/src/client/dist/pwa/assets/QMarkupTable-Bwjknbmw.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-CMzJDhi-.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-DTVwe7sX.js +0 -1
- package/src/client/dist/pwa/assets/SearchPage-CtrR7dNr.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Bl_RNTGi.js +0 -9
- package/src/client/dist/pwa/assets/SplitWorkspacePage-Bac_Pzmm.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BAk5l4Ik.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CrLP_I5S.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-FScR5YAD.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-C0b61nxI.js +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-DE4JLdGG.js +0 -1
- package/src/client/dist/pwa/assets/de-DgZ9jKq_.js +0 -17
- package/src/client/dist/pwa/assets/documents-CALwmao-.js +0 -1
- package/src/client/dist/pwa/assets/efforts-Detdm-FH.js +0 -9
- package/src/client/dist/pwa/assets/es-zvlgzw9H.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-ZIVLPdW2.js +0 -1
- package/src/client/dist/pwa/assets/formatters-C_zcfJBt.js +0 -1
- package/src/client/dist/pwa/assets/fr-BICzq59I.js +0 -17
- package/src/client/dist/pwa/assets/i18n-DG_vChZ3.js +0 -1
- package/src/client/dist/pwa/assets/index-fuLr-ZVa.js +0 -20
- package/src/client/dist/pwa/assets/it-BIGArQrS.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-CDhuQbzi.js +0 -2
- package/src/client/dist/pwa/assets/network-auth-QouPCx6A.js +0 -1
- package/src/client/dist/pwa/assets/pending-diff-open-CX24u9gW.js +0 -1
- package/src/client/dist/pwa/assets/pinia-BdFjQEqa.js +0 -1
- package/src/client/dist/pwa/assets/use-checkbox-Bh9EQhkV.js +0 -1
- package/src/client/dist/pwa/assets/use-search-index-status-_nCyACc7.js +0 -1
- package/src/client/dist/pwa/assets/use-worktree-restore-BDOZc9fk.js +0 -1
- package/src/client/dist/pwa/assets/workspace-templates-DV9Njz0T.js +0 -1
|
@@ -6,7 +6,7 @@ import { getSearchIndexStatus, searchEvents } from '../services/search-service.j
|
|
|
6
6
|
import { createSessionHandoff, decideSessionHandoff, getCurrentSessionHandoff, SessionHandoffError, submitSessionHandoff, waitForSessionHandoff, } from '../services/session-handoff-service.js';
|
|
7
7
|
import { AgentStopError, assertAgentStopped } from '../utils/agent-stop-result.js';
|
|
8
8
|
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
9
|
-
import { isWorkspaceLifecycleBusy, workspaceLifecycleReason } from '../utils/workspace-lifecycle-guard.js';
|
|
9
|
+
import { assertWorkspaceLifecycleAvailable, isWorkspaceLifecycleBusy, workspaceLifecycleReason, } from '../utils/workspace-lifecycle-guard.js';
|
|
10
10
|
import autoLoopMessagesRoutes from './auto-loop-messages.js';
|
|
11
11
|
const execFileAsync = promisify(execFileCb);
|
|
12
12
|
import fs from 'node:fs';
|
|
@@ -73,7 +73,11 @@ function realPathOrResolved(p) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
function workspaceErrorStatus(err) {
|
|
76
|
-
return err instanceof AgentStopError ||
|
|
76
|
+
return err instanceof AgentStopError ||
|
|
77
|
+
err instanceof WorkspaceLifecycleBusyError ||
|
|
78
|
+
err instanceof terminalService.TerminalStopError
|
|
79
|
+
? 409
|
|
80
|
+
: 500;
|
|
77
81
|
}
|
|
78
82
|
/** Hono sub-router for workspace CRUD, tasks, agents, Git and PR creation. */
|
|
79
83
|
const app = new Hono();
|
|
@@ -381,7 +385,7 @@ app.post('/:id/engine-handoff-preview', migrationGuard, async (c) => {
|
|
|
381
385
|
const engine = listEngines().find((item) => item.id === body.engine);
|
|
382
386
|
if (!engine)
|
|
383
387
|
return c.json({ error: `Unknown engine '${body.engine ?? ''}'` }, 400);
|
|
384
|
-
return c.json({ handoff: buildEngineHandoff(workspace, workspace.engine, engine.id) });
|
|
388
|
+
return c.json({ handoff: await buildEngineHandoff(workspace, workspace.engine, engine.id) });
|
|
385
389
|
}
|
|
386
390
|
catch (err) {
|
|
387
391
|
return c.json({ error: err instanceof Error ? err.message : 'Failed to build engine handoff' }, 500);
|
|
@@ -414,7 +418,7 @@ app.post('/:id/switch-engine', migrationGuard, async (c) => {
|
|
|
414
418
|
const effort = typeof body.reasoningEffort === 'string' ? body.reasoningEffort : 'auto';
|
|
415
419
|
const handoff = typeof body.handoff === 'string' && body.handoff.trim()
|
|
416
420
|
? body.handoff.trim().slice(0, 30_000)
|
|
417
|
-
: buildEngineHandoff(workspace, workspace.engine, body.engine);
|
|
421
|
+
: await buildEngineHandoff(workspace, workspace.engine, body.engine);
|
|
418
422
|
if (workspaceService.getWorkspace(workspace.id)?.status === 'compacting') {
|
|
419
423
|
return c.json({
|
|
420
424
|
code: 'compacting',
|
|
@@ -1914,13 +1918,19 @@ app.post('/:id/pending-wakeup', async (c) => {
|
|
|
1914
1918
|
if (rawMode !== 'fresh' && rawMode !== 'resume') {
|
|
1915
1919
|
return c.json({ error: "mode must be 'fresh' or 'resume'" }, 400);
|
|
1916
1920
|
}
|
|
1921
|
+
const workspace = workspaceService.getWorkspace(id);
|
|
1922
|
+
if (!workspace)
|
|
1923
|
+
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1924
|
+
if (workspace.archivedAt || workspace.worktreePurgedAt) {
|
|
1925
|
+
return c.json({ error: 'Restore the workspace before scheduling a wakeup' }, 409);
|
|
1926
|
+
}
|
|
1927
|
+
assertWorkspaceLifecycleAvailable(id);
|
|
1917
1928
|
// 'resume' pins the active session so the wakeup resumes that conversation;
|
|
1918
1929
|
// 'fresh' (default) — or 'resume' with no active session — leaves it unpinned
|
|
1919
1930
|
// so the wakeup fires a brand-new session running `prompt`. This is what makes
|
|
1920
1931
|
// manual scheduling on an idle workspace possible (no more hard 409).
|
|
1921
1932
|
const agentSessionId = rawMode === 'resume' ? (agentManager.getActiveSessionId(id) ?? undefined) : undefined;
|
|
1922
|
-
wakeupService.schedule(id, delaySeconds, prompt, reason, agentSessionId);
|
|
1923
|
-
const pending = wakeupService.getPending(id);
|
|
1933
|
+
const pending = wakeupService.schedule(id, delaySeconds, prompt, reason, agentSessionId);
|
|
1924
1934
|
return c.json({ ok: true, pending });
|
|
1925
1935
|
}
|
|
1926
1936
|
catch (err) {
|
|
@@ -2408,8 +2418,34 @@ app.get('/:id/events', (c) => {
|
|
|
2408
2418
|
// optional: scope to a session view. Session views also include
|
|
2409
2419
|
// workspace-level rows where session_id IS NULL (legacy/pre-session items).
|
|
2410
2420
|
const session = c.req.query('session');
|
|
2421
|
+
// Optional, narrow history to one child. Nested tool calls keep their
|
|
2422
|
+
// parent origin, so match both that origin and their own toolCallId.
|
|
2423
|
+
const subagentToolCallId = c.req.query('subagentToolCallId');
|
|
2424
|
+
const subagentThreadIds = (c.req.query('subagentThreadIds') ?? '')
|
|
2425
|
+
.split(',')
|
|
2426
|
+
.map((threadId) => threadId.trim())
|
|
2427
|
+
.filter(Boolean)
|
|
2428
|
+
.slice(0, 50);
|
|
2411
2429
|
const parsedLimit = parseInt(c.req.query('limit') ?? '100', 10);
|
|
2412
2430
|
const limit = Math.max(1, Math.min(Number.isFinite(parsedLimit) ? parsedLimit : 100, 500));
|
|
2431
|
+
const sessionClause = session ? ' AND (session_id = ? OR session_id IS NULL)' : '';
|
|
2432
|
+
const sessionParams = session ? [session] : [];
|
|
2433
|
+
const originPayload = "CASE WHEN json_valid(payload) THEN payload ELSE '{}' END";
|
|
2434
|
+
const originClauses = [];
|
|
2435
|
+
const originParams = [];
|
|
2436
|
+
if (subagentToolCallId) {
|
|
2437
|
+
originClauses.push(`(json_extract(${originPayload}, '$.origin.toolCallId') = ? OR json_extract(${originPayload}, '$.toolCallId') = ?)`);
|
|
2438
|
+
originParams.push(subagentToolCallId, subagentToolCallId);
|
|
2439
|
+
}
|
|
2440
|
+
if (subagentThreadIds.length > 0) {
|
|
2441
|
+
originClauses.push(`json_extract(${originPayload}, '$.origin.threadId') IN (${subagentThreadIds.map(() => '?').join(', ')})`);
|
|
2442
|
+
originParams.push(...subagentThreadIds);
|
|
2443
|
+
}
|
|
2444
|
+
const originClause = originClauses.length
|
|
2445
|
+
? ` AND type = 'agent:event' AND json_extract(${originPayload}, '$.origin.kind') = 'subagent' AND (${originClauses.join(' OR ')})`
|
|
2446
|
+
: '';
|
|
2447
|
+
const scopedClause = `${sessionClause}${originClause}`;
|
|
2448
|
+
const scopedParams = [...sessionParams, ...originParams];
|
|
2413
2449
|
const db = getDb();
|
|
2414
2450
|
let rows;
|
|
2415
2451
|
if (around) {
|
|
@@ -2417,13 +2453,9 @@ app.get('/:id/events', (c) => {
|
|
|
2417
2453
|
if (!targetRow)
|
|
2418
2454
|
return c.json({ events: [], hasMore: false });
|
|
2419
2455
|
const halfWindow = Math.floor(limit / 2);
|
|
2420
|
-
rows =
|
|
2421
|
-
?
|
|
2422
|
-
|
|
2423
|
-
.all(id, session, targetRow.rowid - halfWindow, targetRow.rowid + halfWindow)
|
|
2424
|
-
: db
|
|
2425
|
-
.prepare('SELECT * FROM ws_events WHERE workspace_id = ? AND rowid BETWEEN ? AND ? ORDER BY rowid ASC')
|
|
2426
|
-
.all(id, targetRow.rowid - halfWindow, targetRow.rowid + halfWindow);
|
|
2456
|
+
rows = db
|
|
2457
|
+
.prepare(`SELECT * FROM ws_events WHERE workspace_id = ?${scopedClause} AND rowid BETWEEN ? AND ? ORDER BY rowid ASC`)
|
|
2458
|
+
.all(id, ...scopedParams, targetRow.rowid - halfWindow, targetRow.rowid + halfWindow);
|
|
2427
2459
|
}
|
|
2428
2460
|
else if (before) {
|
|
2429
2461
|
// Get the rowid of the cursor event
|
|
@@ -2431,25 +2463,17 @@ app.get('/:id/events', (c) => {
|
|
|
2431
2463
|
if (!cursorRow) {
|
|
2432
2464
|
return c.json({ events: [], hasMore: false });
|
|
2433
2465
|
}
|
|
2434
|
-
rows =
|
|
2435
|
-
?
|
|
2436
|
-
|
|
2437
|
-
.all(id, session, cursorRow.rowid, limit)
|
|
2438
|
-
: db
|
|
2439
|
-
.prepare('SELECT * FROM ws_events WHERE workspace_id = ? AND rowid < ? ORDER BY rowid DESC LIMIT ?')
|
|
2440
|
-
.all(id, cursorRow.rowid, limit);
|
|
2466
|
+
rows = db
|
|
2467
|
+
.prepare(`SELECT * FROM ws_events WHERE workspace_id = ?${scopedClause} AND rowid < ? ORDER BY rowid DESC LIMIT ?`)
|
|
2468
|
+
.all(id, ...scopedParams, cursorRow.rowid, limit);
|
|
2441
2469
|
}
|
|
2442
2470
|
else {
|
|
2443
2471
|
// No cursor — return events. When filtering by session, we want the
|
|
2444
2472
|
// MOST RECENT events of that session first (so the feed renders from
|
|
2445
2473
|
// the end), reversed to chronological order below.
|
|
2446
|
-
rows =
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
.all(id, session, limit)
|
|
2450
|
-
: db
|
|
2451
|
-
.prepare('SELECT * FROM ws_events WHERE workspace_id = ? ORDER BY rowid DESC LIMIT ?')
|
|
2452
|
-
.all(id, limit);
|
|
2474
|
+
rows = db
|
|
2475
|
+
.prepare(`SELECT * FROM ws_events WHERE workspace_id = ?${scopedClause} ORDER BY rowid DESC LIMIT ?`)
|
|
2476
|
+
.all(id, ...scopedParams, limit);
|
|
2453
2477
|
}
|
|
2454
2478
|
// Reverse to chronological order (we queried DESC for "before" pagination,
|
|
2455
2479
|
// or for the "session + no cursor" case where we fetched the newest first).
|
|
@@ -2478,13 +2502,9 @@ app.get('/:id/events', (c) => {
|
|
|
2478
2502
|
if (before || around) {
|
|
2479
2503
|
const firstRow = db.prepare('SELECT rowid FROM ws_events WHERE id = ?').get(rows[0].id);
|
|
2480
2504
|
if (firstRow) {
|
|
2481
|
-
const older =
|
|
2482
|
-
?
|
|
2483
|
-
|
|
2484
|
-
.get(id, session, firstRow.rowid)
|
|
2485
|
-
: db
|
|
2486
|
-
.prepare('SELECT 1 as c FROM ws_events WHERE workspace_id = ? AND rowid < ? LIMIT 1')
|
|
2487
|
-
.get(id, firstRow.rowid);
|
|
2505
|
+
const older = db
|
|
2506
|
+
.prepare(`SELECT 1 as c FROM ws_events WHERE workspace_id = ?${scopedClause} AND rowid < ? LIMIT 1`)
|
|
2507
|
+
.get(id, ...scopedParams, firstRow.rowid);
|
|
2488
2508
|
hasMore = older !== undefined;
|
|
2489
2509
|
}
|
|
2490
2510
|
}
|
|
@@ -2492,11 +2512,9 @@ app.get('/:id/events', (c) => {
|
|
|
2492
2512
|
// `LIMIT 1 OFFSET <page size>` answers "is there anything past this
|
|
2493
2513
|
// page?" by reading one row, where COUNT(*) walked every event the
|
|
2494
2514
|
// workspace ever produced — on each page, synchronously.
|
|
2495
|
-
const beyond =
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
.get(id, session, rows.length)
|
|
2499
|
-
: db.prepare('SELECT 1 as c FROM ws_events WHERE workspace_id = ? LIMIT 1 OFFSET ?').get(id, rows.length);
|
|
2515
|
+
const beyond = db
|
|
2516
|
+
.prepare(`SELECT 1 as c FROM ws_events WHERE workspace_id = ?${scopedClause} LIMIT 1 OFFSET ?`)
|
|
2517
|
+
.get(id, ...scopedParams, rows.length);
|
|
2500
2518
|
hasMore = beyond !== undefined;
|
|
2501
2519
|
}
|
|
2502
2520
|
}
|
|
@@ -3018,12 +3036,7 @@ app.post('/:id/archive', migrationGuard, async (c) => {
|
|
|
3018
3036
|
console.error(`[workspaces] stopDevServer during archive failed: ${message}`);
|
|
3019
3037
|
throw err;
|
|
3020
3038
|
}
|
|
3021
|
-
|
|
3022
|
-
terminalService.destroyTerminal(id);
|
|
3023
|
-
}
|
|
3024
|
-
catch {
|
|
3025
|
-
// Terminal may not exist — ignore
|
|
3026
|
-
}
|
|
3039
|
+
await terminalService.destroyTerminal(id);
|
|
3027
3040
|
const updated = workspaceService.archiveWorkspace(id);
|
|
3028
3041
|
wsService.emitEphemeral(id, 'workspace:archived', { workspace: updated });
|
|
3029
3042
|
// Run the project's archive script (best-effort — never blocks the archive).
|
|
@@ -3140,12 +3153,7 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
3140
3153
|
console.error(`[workspaces] stopDevServer during delete failed for '${workspace.name}':`, err);
|
|
3141
3154
|
throw err;
|
|
3142
3155
|
}
|
|
3143
|
-
|
|
3144
|
-
terminalService.destroyTerminal(workspace.id);
|
|
3145
|
-
}
|
|
3146
|
-
catch {
|
|
3147
|
-
// Terminal may not exist — ignore
|
|
3148
|
-
}
|
|
3156
|
+
await terminalService.destroyTerminal(workspace.id);
|
|
3149
3157
|
// Collected best-effort warnings: the DB deletion always proceeds, but
|
|
3150
3158
|
// side-effects (worktree, local/remote branches) can fail independently.
|
|
3151
3159
|
// We surface a user-friendly message per failure so the UI can show a
|
|
@@ -3178,7 +3186,7 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
3178
3186
|
// Delete local branch if requested
|
|
3179
3187
|
if (opts.deleteLocalBranch) {
|
|
3180
3188
|
try {
|
|
3181
|
-
gitOps.
|
|
3189
|
+
await gitOps.deleteLocalBranchAsync(workspace.projectPath, workspace.workingBranch);
|
|
3182
3190
|
}
|
|
3183
3191
|
catch (err) {
|
|
3184
3192
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3191,7 +3199,7 @@ async function deleteWorkspaceWithSideEffectsUnlocked(workspace, opts) {
|
|
|
3191
3199
|
// Delete remote branch if requested
|
|
3192
3200
|
if (opts.deleteRemoteBranch) {
|
|
3193
3201
|
try {
|
|
3194
|
-
gitOps.
|
|
3202
|
+
await gitOps.deleteRemoteBranchAsync(workspace.projectPath, workspace.workingBranch);
|
|
3195
3203
|
}
|
|
3196
3204
|
catch (err) {
|
|
3197
3205
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3405,7 +3413,7 @@ app.get('/:id/diff', async (c) => {
|
|
|
3405
3413
|
if (!from || !to) {
|
|
3406
3414
|
return c.json({ error: 'mode=commits requires from and to query params' }, 400);
|
|
3407
3415
|
}
|
|
3408
|
-
if (!gitOps.
|
|
3416
|
+
if (!(await gitOps.commitExistsAsync(worktreePath, to))) {
|
|
3409
3417
|
return c.json({ error: `Invalid commit ref '${to}'` }, 400);
|
|
3410
3418
|
}
|
|
3411
3419
|
// `to` is strict (400 above). `from` is intentionally lenient: any ref it
|
|
@@ -3413,8 +3421,8 @@ app.get('/:id/diff', async (c) => {
|
|
|
3413
3421
|
// only unresolved `from` the UI ever sends is a root commit's `<sha>^`
|
|
3414
3422
|
// (single-commit diff of the first commit); the compare dialog otherwise
|
|
3415
3423
|
// only offers refs that resolve.
|
|
3416
|
-
const fromRef = gitOps.
|
|
3417
|
-
const files = gitOps.
|
|
3424
|
+
const fromRef = (await gitOps.commitExistsAsync(worktreePath, from)) ? from : gitOps.EMPTY_TREE_SHA;
|
|
3425
|
+
const files = await gitOps.getChangedFilesBetweenAsync(worktreePath, fromRef, to);
|
|
3418
3426
|
c.header('Cache-Control', 'no-store');
|
|
3419
3427
|
return c.json({ files, mode: 'commits', from: fromRef, to });
|
|
3420
3428
|
}
|
|
@@ -3426,8 +3434,8 @@ app.get('/:id/diff', async (c) => {
|
|
|
3426
3434
|
await gitOps.fetchSourceBranchAsync(worktreePath, workspace.sourceBranch);
|
|
3427
3435
|
}
|
|
3428
3436
|
const files = mode === 'unpushed'
|
|
3429
|
-
? gitOps.
|
|
3430
|
-
: gitOps.
|
|
3437
|
+
? await gitOps.getUnpushedChangedFilesAsync(worktreePath, workspace.workingBranch)
|
|
3438
|
+
: await gitOps.getChangedFilesAsync(worktreePath, workspace.sourceBranch, includeUntracked);
|
|
3431
3439
|
c.header('Cache-Control', 'no-store');
|
|
3432
3440
|
return c.json({
|
|
3433
3441
|
files,
|
|
@@ -3446,7 +3454,7 @@ app.get('/:id/diff', async (c) => {
|
|
|
3446
3454
|
// - `branch` → sourceBranch
|
|
3447
3455
|
// - `unpushed` → origin/<workingBranch>
|
|
3448
3456
|
// `modified` is always the current worktree content.
|
|
3449
|
-
app.get('/:id/diff-file', (c) => {
|
|
3457
|
+
app.get('/:id/diff-file', async (c) => {
|
|
3450
3458
|
try {
|
|
3451
3459
|
const id = c.req.param('id');
|
|
3452
3460
|
const filePath = c.req.query('path');
|
|
@@ -3472,14 +3480,16 @@ app.get('/:id/diff-file', (c) => {
|
|
|
3472
3480
|
if (!from || !to) {
|
|
3473
3481
|
return c.json({ error: 'mode=commits requires from and to query params' }, 400);
|
|
3474
3482
|
}
|
|
3475
|
-
if (!gitOps.
|
|
3483
|
+
if (!(await gitOps.commitExistsAsync(worktreePath, to))) {
|
|
3476
3484
|
return c.json({ error: `Invalid commit ref '${to}'` }, 400);
|
|
3477
3485
|
}
|
|
3478
3486
|
// `from` is lenient (empty-tree fallback) for a root commit's `<sha>^` —
|
|
3479
3487
|
// see the matching note in the `/diff` commits branch above.
|
|
3480
|
-
const fromRef = gitOps.
|
|
3481
|
-
const original =
|
|
3482
|
-
|
|
3488
|
+
const fromRef = (await gitOps.commitExistsAsync(worktreePath, from)) ? from : gitOps.EMPTY_TREE_SHA;
|
|
3489
|
+
const [original, modified] = await Promise.all([
|
|
3490
|
+
gitOps.getFileAtRefAsync(worktreePath, fromRef, filePath),
|
|
3491
|
+
gitOps.getFileAtRefAsync(worktreePath, to, filePath),
|
|
3492
|
+
]);
|
|
3483
3493
|
c.header('Cache-Control', 'no-store');
|
|
3484
3494
|
return c.json({
|
|
3485
3495
|
original: original ?? '',
|
|
@@ -3491,7 +3501,7 @@ app.get('/:id/diff-file', (c) => {
|
|
|
3491
3501
|
});
|
|
3492
3502
|
}
|
|
3493
3503
|
const baseRef = mode === 'unpushed' ? `origin/${workspace.workingBranch}` : workspace.sourceBranch;
|
|
3494
|
-
const original = gitOps.
|
|
3504
|
+
const original = await gitOps.getFileAtRefAsync(worktreePath, baseRef, filePath);
|
|
3495
3505
|
const modified = gitOps.getFileContent(worktreePath, filePath);
|
|
3496
3506
|
c.header('Cache-Control', 'no-store');
|
|
3497
3507
|
return c.json({
|
|
@@ -3555,7 +3565,7 @@ app.post('/:id/rollback-file', async (c) => {
|
|
|
3555
3565
|
// GET /api/workspaces/:id/branch-divergence?limit=50
|
|
3556
3566
|
// Returns commits on the working branch ahead of `origin/<sourceBranch>`
|
|
3557
3567
|
// (`ahead`) and commits on `origin/<sourceBranch>` not yet on the working
|
|
3558
|
-
// branch (`behind`).
|
|
3568
|
+
// branch (`behind`). Awaits refreshing `origin/<sourceBranch>` so the
|
|
3559
3569
|
// counts and lists are never computed against a stale local source ref.
|
|
3560
3570
|
app.get('/:id/branch-divergence', async (c) => {
|
|
3561
3571
|
try {
|
|
@@ -3569,8 +3579,8 @@ app.get('/:id/branch-divergence', async (c) => {
|
|
|
3569
3579
|
const limit = Math.min(Math.max(1, Number.isNaN(parsed) ? 50 : parsed), 200);
|
|
3570
3580
|
const worktreePath = workspace.worktreePath;
|
|
3571
3581
|
await gitOps.fetchSourceBranchAsync(worktreePath, workspace.sourceBranch);
|
|
3572
|
-
const ahead = gitOps.
|
|
3573
|
-
const behind = gitOps.
|
|
3582
|
+
const ahead = await gitOps.listBranchCommitsAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch, limit);
|
|
3583
|
+
const behind = await gitOps.listCommitsBehindAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch, limit);
|
|
3574
3584
|
c.header('Cache-Control', 'no-store');
|
|
3575
3585
|
return c.json({
|
|
3576
3586
|
ahead,
|
|
@@ -3586,7 +3596,7 @@ app.get('/:id/branch-divergence', async (c) => {
|
|
|
3586
3596
|
});
|
|
3587
3597
|
// GET /api/workspaces/:id/commits?limit=50 — list commits between
|
|
3588
3598
|
// `origin/<sourceBranch>` and HEAD, each tagged with whether it's already
|
|
3589
|
-
// pushed to origin/<branch>.
|
|
3599
|
+
// pushed to origin/<branch>. Awaits refreshing `origin/<sourceBranch>`
|
|
3590
3600
|
// so the list is not computed against a stale local source ref.
|
|
3591
3601
|
app.get('/:id/commits', async (c) => {
|
|
3592
3602
|
try {
|
|
@@ -3599,7 +3609,7 @@ app.get('/:id/commits', async (c) => {
|
|
|
3599
3609
|
const limit = Math.min(Math.max(1, parseInt(limitRaw ?? '50', 10) || 50), 200);
|
|
3600
3610
|
const worktreePath = workspace.worktreePath;
|
|
3601
3611
|
await gitOps.fetchSourceBranchAsync(worktreePath, workspace.sourceBranch);
|
|
3602
|
-
const commits = gitOps.
|
|
3612
|
+
const commits = await gitOps.listBranchCommitsAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch, limit);
|
|
3603
3613
|
c.header('Cache-Control', 'no-store');
|
|
3604
3614
|
return c.json({ commits, sourceBranch: workspace.sourceBranch, workingBranch: workspace.workingBranch });
|
|
3605
3615
|
}
|
|
@@ -3609,13 +3619,13 @@ app.get('/:id/commits', async (c) => {
|
|
|
3609
3619
|
}
|
|
3610
3620
|
});
|
|
3611
3621
|
// GET /:id/working-tree-files — list uncommitted working-tree files (read-only)
|
|
3612
|
-
app.get('/:id/working-tree-files', (c) => {
|
|
3622
|
+
app.get('/:id/working-tree-files', async (c) => {
|
|
3613
3623
|
try {
|
|
3614
3624
|
const id = c.req.param('id');
|
|
3615
3625
|
const workspace = workspaceService.getWorkspace(id);
|
|
3616
3626
|
if (!workspace)
|
|
3617
3627
|
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
3618
|
-
const files = gitOps.
|
|
3628
|
+
const files = await gitOps.getWorkingTreeFilesAsync(workspace.worktreePath);
|
|
3619
3629
|
return c.json({ files });
|
|
3620
3630
|
}
|
|
3621
3631
|
catch (err) {
|
|
@@ -3954,7 +3964,7 @@ app.post('/:id/git/commit-with-agent', migrationGuard, async (c) => {
|
|
|
3954
3964
|
const workspace = workspaceService.getWorkspace(id);
|
|
3955
3965
|
if (!workspace)
|
|
3956
3966
|
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
3957
|
-
const workingTree = gitOps.
|
|
3967
|
+
const workingTree = await gitOps.getWorkingTreeStatusAsync(workspace.worktreePath);
|
|
3958
3968
|
if (workingTree.staged + workingTree.modified + workingTree.untracked === 0) {
|
|
3959
3969
|
return c.json({ error: 'No uncommitted changes to commit' }, 400);
|
|
3960
3970
|
}
|
|
@@ -4372,8 +4382,8 @@ app.post('/:id/open-pr', async (c) => {
|
|
|
4372
4382
|
return c.json({ ok: true, prNumber, prUrl, messageSent: false });
|
|
4373
4383
|
}
|
|
4374
4384
|
// Build context and render the PR prompt template
|
|
4375
|
-
const commits = gitOps.
|
|
4376
|
-
const diffStats = gitOps.
|
|
4385
|
+
const commits = await gitOps.getCommitsBetweenAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch);
|
|
4386
|
+
const diffStats = await gitOps.getDiffStatsBetweenAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch);
|
|
4377
4387
|
const tasks = workspaceService.listTasks(workspace.id);
|
|
4378
4388
|
const rendered = renderPrTemplate(effective.prPromptTemplate, {
|
|
4379
4389
|
workspace,
|
|
@@ -94,6 +94,8 @@ const TASK_UPDATED_TERMINAL_STATUS = {
|
|
|
94
94
|
};
|
|
95
95
|
export function createMapperState() {
|
|
96
96
|
return {
|
|
97
|
+
childMessageOrigins: new Map(),
|
|
98
|
+
childToolOrigins: new Map(),
|
|
97
99
|
sessionStartedEmitted: false,
|
|
98
100
|
openMessages: new Map(),
|
|
99
101
|
sawErrorResult: false,
|
|
@@ -147,6 +149,37 @@ function tryEmitQuotaError(state, events, message) {
|
|
|
147
149
|
* `state` as needed.
|
|
148
150
|
*/
|
|
149
151
|
export function mapSdkMessage(msg, state) {
|
|
152
|
+
const parsed = msg;
|
|
153
|
+
const parentId = typeof parsed.parent_tool_use_id === 'string' ? parsed.parent_tool_use_id : undefined;
|
|
154
|
+
const origin = parentId ? { kind: 'subagent', toolCallId: parentId } : undefined;
|
|
155
|
+
const streamed = parsed.event;
|
|
156
|
+
if (origin && streamed?.type === 'message_start' && streamed.message?.id) {
|
|
157
|
+
state.childMessageOrigins.set(streamed.message.id, origin);
|
|
158
|
+
}
|
|
159
|
+
const events = mapSdkMessagePayload(msg, state);
|
|
160
|
+
return events.map((event) => {
|
|
161
|
+
if (event.kind === 'message:text' || event.kind === 'message:thinking' || event.kind === 'message:end') {
|
|
162
|
+
const eventOrigin = origin ?? state.childMessageOrigins.get(event.messageId);
|
|
163
|
+
if (origin)
|
|
164
|
+
state.childMessageOrigins.set(event.messageId, origin);
|
|
165
|
+
if (event.kind === 'message:end')
|
|
166
|
+
state.childMessageOrigins.delete(event.messageId);
|
|
167
|
+
return eventOrigin ? { ...event, origin: eventOrigin } : event;
|
|
168
|
+
}
|
|
169
|
+
if (event.kind === 'tool:call') {
|
|
170
|
+
if (origin)
|
|
171
|
+
state.childToolOrigins.set(event.toolCallId, origin);
|
|
172
|
+
return origin ? { ...event, origin } : event;
|
|
173
|
+
}
|
|
174
|
+
if (event.kind === 'tool:result') {
|
|
175
|
+
const eventOrigin = origin ?? state.childToolOrigins.get(event.toolCallId);
|
|
176
|
+
state.childToolOrigins.delete(event.toolCallId);
|
|
177
|
+
return eventOrigin ? { ...event, origin: eventOrigin } : event;
|
|
178
|
+
}
|
|
179
|
+
return event;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function mapSdkMessagePayload(msg, state) {
|
|
150
183
|
// Treat as a generic record — the SDK discriminated union is too broad to
|
|
151
184
|
// narrow per branch here.
|
|
152
185
|
const parsed = msg;
|
|
@@ -276,7 +276,16 @@ export function createCodexEngine() {
|
|
|
276
276
|
}
|
|
277
277
|
// Child output remains visible, but its failures/compaction/markers
|
|
278
278
|
// cannot control the parent session or its auto-loop lifecycle.
|
|
279
|
-
return map(state)
|
|
279
|
+
return map(state)
|
|
280
|
+
.filter((event) => event.kind !== 'error' && !event.kind.startsWith('session:'))
|
|
281
|
+
.map((event) => {
|
|
282
|
+
const origin = { kind: 'subagent', threadId: notification.threadId };
|
|
283
|
+
if (event.kind === 'message:text' || event.kind === 'message:thinking' || event.kind === 'message:end')
|
|
284
|
+
return { ...event, origin };
|
|
285
|
+
if (event.kind === 'tool:call' || event.kind === 'tool:result')
|
|
286
|
+
return { ...event, origin };
|
|
287
|
+
return event;
|
|
288
|
+
});
|
|
280
289
|
};
|
|
281
290
|
const itemKey = (threadId, turnId, itemId) => JSON.stringify([threadId, turnId, itemId]);
|
|
282
291
|
const client = createAppServerClient({
|
|
@@ -134,6 +134,7 @@ export function handleItemStarted(item, state) {
|
|
|
134
134
|
events.push({
|
|
135
135
|
kind: 'subagent:progress',
|
|
136
136
|
toolCallId,
|
|
137
|
+
threadIds: item.receiverThreadIds,
|
|
137
138
|
status: 'running',
|
|
138
139
|
description: item.prompt ?? item.tool,
|
|
139
140
|
taskType: item.tool,
|
|
@@ -271,6 +272,7 @@ export function handleItemCompleted(item, state) {
|
|
|
271
272
|
events.push({
|
|
272
273
|
kind: 'subagent:progress',
|
|
273
274
|
toolCallId,
|
|
275
|
+
threadIds: item.receiverThreadIds,
|
|
274
276
|
status: hasActiveAgent ? 'running' : 'done',
|
|
275
277
|
description: item.prompt ?? item.tool,
|
|
276
278
|
taskType: item.tool,
|
|
@@ -14,10 +14,11 @@ export function hasIntegrationMcpConfig(integration, selectedKey) {
|
|
|
14
14
|
return Boolean(readClaudeMcpEntry((name) => key ? name === key : integration === 'notion' ? name === 'notion' : /sentry/i.test(name)));
|
|
15
15
|
}
|
|
16
16
|
/** Resolve at every launch/resume; never write credentials into a project file. */
|
|
17
|
-
export function buildIntegrationMcpServers(settings, onUnavailable) {
|
|
17
|
+
export function buildIntegrationMcpServers(settings, engineId, onUnavailable) {
|
|
18
18
|
const servers = [];
|
|
19
|
-
//
|
|
20
|
-
|
|
19
|
+
// Codex needs fresh names to avoid recursively merging stale native config.
|
|
20
|
+
// Claude needs stable tool names to preserve its prompt cache across resumes.
|
|
21
|
+
const launch = engineId === 'codex' ? nanoid(12) : 'managed';
|
|
21
22
|
for (const integration of ['notion', 'sentry']) {
|
|
22
23
|
if (!settings[`${integration}Enabled`])
|
|
23
24
|
continue;
|
|
@@ -1385,7 +1385,7 @@ export function startAgent(workspaceId, workingDir, prompt, model, resume = fals
|
|
|
1385
1385
|
// enforce read-only plan mode here, not only through its prompt.
|
|
1386
1386
|
const readOnly = getReviewReturn(workspaceId, agentSessionId) !== null;
|
|
1387
1387
|
const unavailableIntegrations = [];
|
|
1388
|
-
const integrationServers = buildIntegrationMcpServers(getGlobalSettings(), (name) => unavailableIntegrations.push(name));
|
|
1388
|
+
const integrationServers = buildIntegrationMcpServers(getGlobalSettings(), engine.id, (name) => unavailableIntegrations.push(name));
|
|
1389
1389
|
const options = {
|
|
1390
1390
|
workspaceId,
|
|
1391
1391
|
workingDir,
|
|
@@ -8,19 +8,30 @@ export function createStreamingBatcher(emit, options = {}) {
|
|
|
8
8
|
return;
|
|
9
9
|
clearTimeout(entry.timer);
|
|
10
10
|
pending.delete(messageId);
|
|
11
|
-
emit({
|
|
11
|
+
emit({
|
|
12
|
+
kind: 'message:text',
|
|
13
|
+
messageId,
|
|
14
|
+
text: entry.text,
|
|
15
|
+
streaming: true,
|
|
16
|
+
...(entry.origin ? { origin: entry.origin } : {}),
|
|
17
|
+
});
|
|
12
18
|
};
|
|
13
19
|
return {
|
|
14
20
|
push(event) {
|
|
15
21
|
if (event.kind === 'message:text' && event.streaming) {
|
|
16
|
-
|
|
22
|
+
let entry = pending.get(event.messageId);
|
|
23
|
+
if (entry &&
|
|
24
|
+
(entry.origin?.toolCallId !== event.origin?.toolCallId || entry.origin?.threadId !== event.origin?.threadId)) {
|
|
25
|
+
flush(event.messageId);
|
|
26
|
+
entry = undefined;
|
|
27
|
+
}
|
|
17
28
|
if (entry) {
|
|
18
29
|
entry.text += event.text;
|
|
19
30
|
return;
|
|
20
31
|
}
|
|
21
32
|
const timer = setTimeout(() => flush(event.messageId), windowMs);
|
|
22
33
|
timer.unref?.();
|
|
23
|
-
pending.set(event.messageId, { text: event.text, timer });
|
|
34
|
+
pending.set(event.messageId, { text: event.text, origin: event.origin, timer });
|
|
24
35
|
return;
|
|
25
36
|
}
|
|
26
37
|
if (event.kind === 'message:end')
|