@straiffi/archon 1.7.1 → 1.7.3
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/dist/client/assets/ActivityView-DTEenQas.js +1 -0
- package/dist/client/assets/GitHubActionsDialog-BKWHonUS.js +1 -0
- package/dist/client/assets/TestsDialog-cBaoffCs.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CJgwCv5O.js → architectureDiagram-3BPJPVTR-BzzGotac.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM--xjes8FA.js → blockDiagram-GPEHLZMM-DIcqLHKg.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DHnp82NG.js → c4Diagram-AAUBKEIU-Hc1Sqmhs.js} +1 -1
- package/dist/client/assets/channel-Cym17waa.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DAOyJCKy.js → chunk-2J33WTMH-CWSwjsgk.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-C9YI_tk4.js → chunk-3OPIFGDE-BnX3DljN.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BW9khkin.js → chunk-5ZQYHXKU-EyyJlI-d.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DL4t5Hpi.js → chunk-727SXJPM-Ci6CfxWj.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-C4VMKa_O.js → chunk-AQP2D5EJ-RKZNsJTm.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DHP7zrZK.js → chunk-CSCIHK7Q-DBf0Euoj.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-CqZWek3t.js → chunk-KSCS5N6A-BmLQI_2S.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-D3q55pCI.js → chunk-L5ZTLDWV-DKwVAb8G.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-UDY_Lo6v.js → chunk-LZXEDZCA-axotKkbz.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-ZCiOxkYV.js → chunk-ND2GUHAM-BBlJ4nXC.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-CuryoBtd.js → chunk-NZK2D7GU-BKJGoZZt.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-PnPsz2TB.js → chunk-O5CBEL6O-C-Ghigo1.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-BzOopX-U.js → chunk-WU5MYG2G-CCzVWYcd.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BcigvvIC.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-3AdSuE0N.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-5LBwQ83I.js → dagre-BM42HDAG-rwIdPcjI.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-_4pckiGR.js → diagram-2AECGRRQ-DJWxOQsg.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CqElv5rJ.js → diagram-5GNKFQAL-C-mQeFtd.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DKv4zS4F.js → diagram-KO2AKTUF-BAu6u4sR.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-iLmTH4UP.js → diagram-LMA3HP47-U6CbvBn0.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DpFYIa0o.js → diagram-OG6HWLK6-DNTwFcs1.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-DnfbvbKs.js → erDiagram-TEJ5UH35-DaHX6Xw5.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-C2MN70Qv.js → flowDiagram-I6XJVG4X-B9hKqL2l.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-yonpliOJ.js → ganttDiagram-6RSMTGT7-BQ1HPHVO.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CaKDWx7y.js → gitGraphDiagram-PVQCEYII-BBRcsmMK.js} +1 -1
- package/dist/client/assets/index-CEMaJCPS.js +273 -0
- package/dist/client/assets/index-lWftq65U.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-82-8njnx.js → infoDiagram-5YYISTIA-DUoNiSGC.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BMiyLWwK.js → ishikawaDiagram-YF4QCWOH-DJN1y2w8.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-C1qBnR37.js → journeyDiagram-JHISSGLW-BxLLMT70.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-zByEuCzn.js → kanban-definition-UN3LZRKU-BdCPistJ.js} +1 -1
- package/dist/client/assets/{line-EpLqyzw7.js → line-4mIgXsZd.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-o124REPN.js → mermaid-parser.core-Pu1QGWJ-.js} +1 -1
- package/dist/client/assets/{mermaid.core-DgiAn2-k.js → mermaid.core-DnH9DrgZ.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-UK6V0Bxq.js → mindmap-definition-RKZ34NQL-DqE4ba3C.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-B2s9oGFl.js → pieDiagram-4H26LBE5-Crjy9ejf.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-d5H7-zcc.js → quadrantDiagram-W4KKPZXB-BUSZ2VB7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DlwN9Vcv.js → requirementDiagram-4Y6WPE33-CBhFKEoV.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-9dGyAxu5.js → sankeyDiagram-5OEKKPKP-CgdbY41v.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DKwRjEym.js → sequenceDiagram-3UESZ5HK-hkp_H7AE.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-D_6cuqsX.js → stateDiagram-AJRCARHV-C6x45RWp.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DVzPjRPN.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-ChqD4j8I.js → timeline-definition-PNZ67QCA-CjnZWttQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CSAZk0IW.js → vennDiagram-CIIHVFJN-DU5ZyYju.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BUE6IF4q.js → wardleyDiagram-YWT4CUSO-CaCEnCB0.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-MOkC53m_.js → xychartDiagram-2RQKCTM6-Ce3xa37U.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +471 -39
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +1267 -213
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +281 -76
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentRuns.js +205 -38
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationProgression.js +464 -0
- package/dist/server/lib/automationProgression.js.map +1 -0
- package/dist/server/lib/automationReconciliation.js +498 -0
- package/dist/server/lib/automationReconciliation.js.map +1 -0
- package/dist/server/lib/automationStageDetails.js +232 -0
- package/dist/server/lib/automationStageDetails.js.map +1 -0
- package/dist/server/lib/buildOpenQuestionOperations.js +223 -0
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -0
- package/dist/server/lib/buildOpenQuestions.js +52 -0
- package/dist/server/lib/buildOpenQuestions.js.map +1 -1
- package/dist/server/lib/buildSequences.js +130 -46
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/bundleReviewOperations.js +82 -9
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/chatOperations.js +31 -0
- package/dist/server/lib/chatOperations.js.map +1 -1
- package/dist/server/lib/executionReconciliation.js +518 -0
- package/dist/server/lib/executionReconciliation.js.map +1 -0
- package/dist/server/lib/executionRunRetention.js +141 -0
- package/dist/server/lib/executionRunRetention.js.map +1 -0
- package/dist/server/lib/executionRuns.js +1264 -0
- package/dist/server/lib/executionRuns.js.map +1 -0
- package/dist/server/lib/git.js +385 -6
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/integrations/github.js +43 -0
- package/dist/server/lib/integrations/github.js.map +1 -1
- package/dist/server/lib/integrations/githubActions.js +1 -0
- package/dist/server/lib/integrations/githubActions.js.map +1 -1
- package/dist/server/lib/integrations/githubActionsSessions.js +337 -114
- package/dist/server/lib/integrations/githubActionsSessions.js.map +1 -1
- package/dist/server/lib/projectMemorySelections.js +7 -3
- package/dist/server/lib/projectMemorySelections.js.map +1 -1
- package/dist/server/lib/shutdown.js +2 -2
- package/dist/server/lib/shutdown.js.map +1 -1
- package/dist/server/lib/specArtifact.js +3 -0
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/specFormatting.js +1 -1
- package/dist/server/lib/specFormatting.js.map +1 -1
- package/dist/server/lib/specOperations.js +31 -0
- package/dist/server/lib/specOperations.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +44 -14
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/specReviewDraft.js +3 -2
- package/dist/server/lib/specReviewDraft.js.map +1 -1
- package/dist/server/lib/specReviewOperations.js +44 -7
- package/dist/server/lib/specReviewOperations.js.map +1 -1
- package/dist/server/lib/specReviewValidation.js +217 -0
- package/dist/server/lib/specReviewValidation.js.map +1 -0
- package/dist/server/lib/specReviews.js +30 -2
- package/dist/server/lib/specReviews.js.map +1 -1
- package/dist/server/lib/specs.js +18 -1
- package/dist/server/lib/specs.js.map +1 -1
- package/dist/server/lib/stageSnapshots.js +197 -0
- package/dist/server/lib/stageSnapshots.js.map +1 -0
- package/dist/server/lib/testSessions.js +164 -19
- package/dist/server/lib/testSessions.js.map +1 -1
- package/dist/server/lib/ticketAutomation.js +80 -4
- package/dist/server/lib/ticketAutomation.js.map +1 -1
- package/dist/server/lib/ticketAutomationCommit.js +466 -91
- package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +214 -20
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/ticketOpenQuestions.js +30 -14
- package/dist/server/lib/ticketOpenQuestions.js.map +1 -1
- package/dist/server/lib/ticketRunOperations.js +75 -7
- package/dist/server/lib/ticketRunOperations.js.map +1 -1
- package/dist/server/lib/ticketUndo.js +77 -168
- package/dist/server/lib/ticketUndo.js.map +1 -1
- package/dist/server/lib/ticketWorkflowOperations.js +132 -28
- package/dist/server/lib/ticketWorkflowOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +46 -29
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/transcriptRuns.js +3 -3
- package/dist/server/lib/transcriptRuns.js.map +1 -1
- package/dist/server/lib/turnExecutionErrors.js +12 -0
- package/dist/server/lib/turnExecutionErrors.js.map +1 -0
- package/dist/server/lib/types.js +57 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +596 -184
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +261 -130
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +720 -195
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +527 -188
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +231 -69
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +796 -239
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +211 -45
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +425 -252
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-BrUq-zO1.js +0 -1
- package/dist/client/assets/TestsDialog-D9N4GD77.js +0 -4
- package/dist/client/assets/TicketChangesModal-bYHJSO6d.js +0 -1
- package/dist/client/assets/channel-B4kCvLCt.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C-eRRNNj.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B3vxoc73.js +0 -1
- package/dist/client/assets/index-9q1GXkUw.js +0 -270
- package/dist/client/assets/index-BxTc5qQT.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BkMKz90T.js +0 -1
package/dist/server/lib/agent.js
CHANGED
|
@@ -11,7 +11,7 @@ import { appendClaudeHeadlessPlanPrompt, buildClaudeHeadlessPlanArgs } from './p
|
|
|
11
11
|
import { formatGenericToolCall } from './genericToolCall.js';
|
|
12
12
|
import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
|
|
13
13
|
import { updateSpecReviewRunStatus, updateSpecReviewRunStreaming, updateSpecReviewRunUsage } from './specReviews.js';
|
|
14
|
-
import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
14
|
+
import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForExecutionRun, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
15
15
|
import { resolveOpencodeDbPath } from './runtimePaths.js';
|
|
16
16
|
import { markTicketDescriptionVisibleInBundle } from './ticketMessages.js';
|
|
17
17
|
import { getTicket } from './tickets.js';
|
|
@@ -313,6 +313,7 @@ const hasMatchingUsageSnapshot = (current, next) => {
|
|
|
313
313
|
&& current.usage_snapshot_reasoning_tokens === next.usage_snapshot_reasoning_tokens;
|
|
314
314
|
};
|
|
315
315
|
const activeBuildProcesses = new Map();
|
|
316
|
+
const activeReviewProcesses = new Map();
|
|
316
317
|
const activeChatProcesses = new Map();
|
|
317
318
|
const activeSpecProcesses = new Map();
|
|
318
319
|
const activeSpecReviewProcesses = new Map();
|
|
@@ -1319,12 +1320,19 @@ const appendOpencodeTranscriptLine = ({ parsed, logLines, responseLines, recover
|
|
|
1319
1320
|
}
|
|
1320
1321
|
return shouldResetResponse;
|
|
1321
1322
|
};
|
|
1322
|
-
export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, }) => {
|
|
1323
|
+
export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, executionContext, deferMainFinalization = false, }) => {
|
|
1323
1324
|
const trackedRuns = [];
|
|
1324
|
-
let nextRunIndex = Math.max(getNextAgentRunIndex(parentType, parentId), mainRun ? mainRun.runIndex + 1 : 1);
|
|
1325
|
+
let nextRunIndex = Math.max(executionContext ? getNextAgentRunIndex(executionContext.runId) : getNextAgentRunIndex(parentType, parentId), mainRun ? mainRun.runIndex + 1 : 1);
|
|
1325
1326
|
const emitUpdate = () => {
|
|
1326
1327
|
emitAgentRunsUpdated(io, parentType, parentId);
|
|
1327
1328
|
};
|
|
1329
|
+
const updateMain = (log, response) => {
|
|
1330
|
+
if (!mainRun) {
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
updateAgentRunStatus(mainRun.id, 'running', log, response);
|
|
1334
|
+
emitUpdate();
|
|
1335
|
+
};
|
|
1328
1336
|
const findRun = (callId, sessionId) => {
|
|
1329
1337
|
if (callId) {
|
|
1330
1338
|
const callRun = trackedRuns.find(run => run.callId === callId);
|
|
@@ -1367,11 +1375,12 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1367
1375
|
emitUpdate();
|
|
1368
1376
|
continue;
|
|
1369
1377
|
}
|
|
1370
|
-
const runIndex = Math.max(getNextAgentRunIndex(parentType, parentId), nextRunIndex);
|
|
1378
|
+
const runIndex = Math.max(executionContext ? getNextAgentRunIndex(executionContext.runId) : getNextAgentRunIndex(parentType, parentId), nextRunIndex);
|
|
1371
1379
|
const id = createAgentRun({
|
|
1380
|
+
...(executionContext ? { executionRunId: executionContext.runId } : {}),
|
|
1372
1381
|
parentType,
|
|
1373
1382
|
parentId,
|
|
1374
|
-
runIndex,
|
|
1383
|
+
...(!executionContext ? { runIndex } : {}),
|
|
1375
1384
|
label: event.label?.trim() || 'Subagent',
|
|
1376
1385
|
role: 'subagent',
|
|
1377
1386
|
});
|
|
@@ -1467,18 +1476,26 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1467
1476
|
run.status = status;
|
|
1468
1477
|
finalizeAgentRun(run.id, status, run.response || null);
|
|
1469
1478
|
}
|
|
1470
|
-
|
|
1479
|
+
if (mainRun) {
|
|
1480
|
+
updateMain(parentLog, parentResponse);
|
|
1481
|
+
if (!deferMainFinalization) {
|
|
1482
|
+
finalizeAgentRun(mainRun.id, status, parentResponse);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (!mainRun) {
|
|
1486
|
+
emitUpdate();
|
|
1487
|
+
}
|
|
1471
1488
|
const trackedIds = new Set(trackedRuns.map(run => run.id));
|
|
1472
|
-
const persistedRuns =
|
|
1489
|
+
const persistedRuns = (executionContext
|
|
1490
|
+
? listAgentRunsForExecutionRun(executionContext.runId)
|
|
1491
|
+
: listAgentRunsForParent(parentType, parentId))
|
|
1473
1492
|
.filter(run => trackedIds.has(run.id) || run.id === mainRun?.id);
|
|
1474
|
-
const persistedIds = new Set(persistedRuns.map(run => run.id));
|
|
1475
1493
|
const timestamp = new Date().toISOString();
|
|
1476
1494
|
for (const run of trackedRuns) {
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
}
|
|
1480
|
-
persistedRuns.push({
|
|
1495
|
+
const persistedRun = persistedRuns.find(candidate => candidate.id === run.id);
|
|
1496
|
+
const snapshotRun = {
|
|
1481
1497
|
id: run.id,
|
|
1498
|
+
execution_run_id: persistedRun?.execution_run_id ?? executionContext?.runId ?? null,
|
|
1482
1499
|
parent_type: parentType,
|
|
1483
1500
|
parent_id: parentId,
|
|
1484
1501
|
run_index: run.runIndex,
|
|
@@ -1488,15 +1505,25 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1488
1505
|
agent_log: run.log || null,
|
|
1489
1506
|
streaming_response: null,
|
|
1490
1507
|
response_text: run.response || null,
|
|
1508
|
+
failure_message: persistedRun?.failure_message ?? null,
|
|
1491
1509
|
session_id: run.sessionId,
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1510
|
+
completed_at: persistedRun?.completed_at ?? timestamp,
|
|
1511
|
+
created_at: persistedRun?.created_at ?? run.createdAt,
|
|
1512
|
+
updated_at: persistedRun?.updated_at ?? timestamp,
|
|
1513
|
+
};
|
|
1514
|
+
const persistedRunIndex = persistedRuns.findIndex(candidate => candidate.id === run.id);
|
|
1515
|
+
if (persistedRunIndex >= 0) {
|
|
1516
|
+
persistedRuns[persistedRunIndex] = snapshotRun;
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
persistedRuns.push(snapshotRun);
|
|
1520
|
+
}
|
|
1495
1521
|
}
|
|
1496
1522
|
if (mainRun) {
|
|
1497
1523
|
const persistedMainRun = persistedRuns.find(run => run.id === mainRun.id);
|
|
1498
1524
|
const mainSnapshotRun = {
|
|
1499
1525
|
id: mainRun.id,
|
|
1526
|
+
execution_run_id: persistedMainRun?.execution_run_id ?? null,
|
|
1500
1527
|
parent_type: parentType,
|
|
1501
1528
|
parent_id: parentId,
|
|
1502
1529
|
run_index: mainRun.runIndex,
|
|
@@ -1506,7 +1533,9 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1506
1533
|
agent_log: parentLog,
|
|
1507
1534
|
streaming_response: null,
|
|
1508
1535
|
response_text: parentResponse,
|
|
1536
|
+
failure_message: persistedMainRun?.failure_message ?? null,
|
|
1509
1537
|
session_id: persistedMainRun?.session_id ?? null,
|
|
1538
|
+
completed_at: persistedMainRun?.completed_at ?? timestamp,
|
|
1510
1539
|
created_at: persistedMainRun?.created_at ?? timestamp,
|
|
1511
1540
|
updated_at: persistedMainRun?.updated_at ?? timestamp,
|
|
1512
1541
|
};
|
|
@@ -1521,6 +1550,7 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1521
1550
|
else {
|
|
1522
1551
|
persistedRuns.push({
|
|
1523
1552
|
id: `${parentType}:${parentId}:main`,
|
|
1553
|
+
execution_run_id: null,
|
|
1524
1554
|
parent_type: parentType,
|
|
1525
1555
|
parent_id: parentId,
|
|
1526
1556
|
run_index: 0,
|
|
@@ -1530,14 +1560,22 @@ export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, })
|
|
|
1530
1560
|
agent_log: parentLog,
|
|
1531
1561
|
streaming_response: null,
|
|
1532
1562
|
response_text: parentResponse,
|
|
1563
|
+
failure_message: null,
|
|
1533
1564
|
session_id: null,
|
|
1565
|
+
completed_at: timestamp,
|
|
1534
1566
|
created_at: timestamp,
|
|
1535
1567
|
updated_at: timestamp,
|
|
1536
1568
|
});
|
|
1537
1569
|
}
|
|
1538
1570
|
return createTranscriptRunSnapshots(persistedRuns.sort((left, right) => left.run_index - right.run_index));
|
|
1539
1571
|
};
|
|
1540
|
-
|
|
1572
|
+
const setMainSessionId = (sessionId) => {
|
|
1573
|
+
const normalized = sessionId?.trim();
|
|
1574
|
+
if (mainRun && normalized) {
|
|
1575
|
+
updateAgentRunSessionId(mainRun.id, normalized);
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
return { applySnapshot, handle, finalize, setMainSessionId, updateMain };
|
|
1541
1579
|
};
|
|
1542
1580
|
export const reconstructOpencodeSubagentTranscript = (parts, cwd) => {
|
|
1543
1581
|
let log = '';
|
|
@@ -1728,6 +1766,22 @@ const untrackChatProcess = (chatSessionId, state) => {
|
|
|
1728
1766
|
state.metadataRefresher?.stop();
|
|
1729
1767
|
state.metadataRefresher = null;
|
|
1730
1768
|
};
|
|
1769
|
+
export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
1770
|
+
const state = {
|
|
1771
|
+
child: null,
|
|
1772
|
+
researchChildren: new Set(),
|
|
1773
|
+
stopRequested: false,
|
|
1774
|
+
metadataRefresher: null,
|
|
1775
|
+
};
|
|
1776
|
+
activeChatProcesses.set(chatSessionId, state);
|
|
1777
|
+
return state;
|
|
1778
|
+
};
|
|
1779
|
+
export const untrackChatProcessById = (chatSessionId) => {
|
|
1780
|
+
const state = activeChatProcesses.get(chatSessionId);
|
|
1781
|
+
if (state) {
|
|
1782
|
+
untrackChatProcess(chatSessionId, state);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1731
1785
|
const untrackSpecProcess = (specSessionId, state) => {
|
|
1732
1786
|
if (activeSpecProcesses.get(specSessionId) === state) {
|
|
1733
1787
|
activeSpecProcesses.delete(specSessionId);
|
|
@@ -1743,6 +1797,7 @@ const untrackSpecProcess = (specSessionId, state) => {
|
|
|
1743
1797
|
export const registerSpecProcessPlaceholder = (specSessionId) => {
|
|
1744
1798
|
const state = {
|
|
1745
1799
|
child: null,
|
|
1800
|
+
researchChildren: new Set(),
|
|
1746
1801
|
stopRequested: false,
|
|
1747
1802
|
metadataRefresher: null,
|
|
1748
1803
|
};
|
|
@@ -1763,6 +1818,24 @@ const untrackSpecReviewProcess = (specReviewRunId, state) => {
|
|
|
1763
1818
|
state.metadataRefresher = null;
|
|
1764
1819
|
};
|
|
1765
1820
|
export const isBuildAgentRunning = (ticketId) => activeBuildProcesses.has(ticketId);
|
|
1821
|
+
export const registerReviewProcess = (ticketId) => {
|
|
1822
|
+
const state = { children: new Set(), stopRequested: false };
|
|
1823
|
+
activeReviewProcesses.set(ticketId, state);
|
|
1824
|
+
return state;
|
|
1825
|
+
};
|
|
1826
|
+
export const untrackReviewProcess = (ticketId, state) => {
|
|
1827
|
+
if (activeReviewProcesses.get(ticketId) === state) {
|
|
1828
|
+
activeReviewProcesses.delete(ticketId);
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
export const isReviewAgentRunning = (ticketId) => activeReviewProcesses.has(ticketId);
|
|
1832
|
+
export const isReviewStopRequested = (ticketId) => {
|
|
1833
|
+
return activeReviewProcesses.get(ticketId)?.stopRequested === true;
|
|
1834
|
+
};
|
|
1835
|
+
export const stopAllReviewAgents = async () => {
|
|
1836
|
+
const ticketIds = [...activeReviewProcesses.keys()];
|
|
1837
|
+
await Promise.all(ticketIds.map(stopReviewAgent));
|
|
1838
|
+
};
|
|
1766
1839
|
export const stopAllBuildAgents = async () => {
|
|
1767
1840
|
const ticketIds = [...activeBuildProcesses.keys()];
|
|
1768
1841
|
await Promise.all(ticketIds.map(stopBuildAgent));
|
|
@@ -1781,13 +1854,34 @@ export const stopBuildAgent = async (ticketId) => {
|
|
|
1781
1854
|
}
|
|
1782
1855
|
return true;
|
|
1783
1856
|
};
|
|
1857
|
+
export const stopReviewAgent = async (ticketId) => {
|
|
1858
|
+
const state = activeReviewProcesses.get(ticketId);
|
|
1859
|
+
if (!state) {
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
state.stopRequested = true;
|
|
1863
|
+
const children = [...state.children];
|
|
1864
|
+
for (const child of children) {
|
|
1865
|
+
killProcess(child, 'SIGTERM');
|
|
1866
|
+
}
|
|
1867
|
+
await Promise.all(children.map(child => waitForProcessExit(child, 5000)));
|
|
1868
|
+
const aliveChildren = children.filter(isProcessAlive);
|
|
1869
|
+
for (const child of aliveChildren) {
|
|
1870
|
+
killProcess(child, 'SIGKILL');
|
|
1871
|
+
}
|
|
1872
|
+
await Promise.all(aliveChildren.map(child => waitForProcessExit(child, 1000)));
|
|
1873
|
+
return true;
|
|
1874
|
+
};
|
|
1784
1875
|
export const stopChatAgent = async (chatSessionId) => {
|
|
1785
1876
|
const state = activeChatProcesses.get(chatSessionId);
|
|
1786
|
-
if (!state
|
|
1877
|
+
if (!state) {
|
|
1787
1878
|
return false;
|
|
1788
1879
|
}
|
|
1789
1880
|
state.stopRequested = true;
|
|
1790
1881
|
state.metadataRefresher?.stop();
|
|
1882
|
+
if (!state.child) {
|
|
1883
|
+
return true;
|
|
1884
|
+
}
|
|
1791
1885
|
killProcess(state.child, 'SIGTERM');
|
|
1792
1886
|
await waitForProcessExit(state.child, 5000);
|
|
1793
1887
|
if (isProcessAlive(state.child)) {
|
|
@@ -1803,20 +1897,22 @@ export const stopSpecAgent = async (specSessionId) => {
|
|
|
1803
1897
|
}
|
|
1804
1898
|
state.stopRequested = true;
|
|
1805
1899
|
state.metadataRefresher?.stop();
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
if (!state.child) {
|
|
1900
|
+
const children = [...state.researchChildren];
|
|
1901
|
+
if (state.child) {
|
|
1902
|
+
children.push(state.child);
|
|
1903
|
+
}
|
|
1904
|
+
if (children.length === 0) {
|
|
1812
1905
|
return true;
|
|
1813
1906
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1907
|
+
for (const child of children) {
|
|
1908
|
+
killProcess(child, 'SIGTERM');
|
|
1909
|
+
}
|
|
1910
|
+
await Promise.all(children.map(child => waitForProcessExit(child, 5000)));
|
|
1911
|
+
const aliveChildren = children.filter(isProcessAlive);
|
|
1912
|
+
for (const child of aliveChildren) {
|
|
1913
|
+
killProcess(child, 'SIGKILL');
|
|
1819
1914
|
}
|
|
1915
|
+
await Promise.all(aliveChildren.map(child => waitForProcessExit(child, 1000)));
|
|
1820
1916
|
return true;
|
|
1821
1917
|
};
|
|
1822
1918
|
export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
@@ -1834,7 +1930,7 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
|
1834
1930
|
}
|
|
1835
1931
|
return true;
|
|
1836
1932
|
};
|
|
1837
|
-
export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, emitSuccessUpdate = true, opencodeAgentMode, runStream, }) => {
|
|
1933
|
+
export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, reviewProcessState, emitSuccessUpdate = true, opencodeAgentMode, runStream, executionContext, persistSessionIdToTicket = false, }) => {
|
|
1838
1934
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
1839
1935
|
const subagentTracker = createSubagentRunTracker({
|
|
1840
1936
|
parentType: 'ticket',
|
|
@@ -1850,6 +1946,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1850
1946
|
},
|
|
1851
1947
|
}
|
|
1852
1948
|
: {}),
|
|
1949
|
+
executionContext,
|
|
1853
1950
|
});
|
|
1854
1951
|
const writeRunStatus = (status, log, streamingResponse = null, options = {}) => {
|
|
1855
1952
|
if (runStream) {
|
|
@@ -1863,6 +1960,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1863
1960
|
if (runStream) {
|
|
1864
1961
|
updateAgentRunSessionId(runStream.runId, sessionId);
|
|
1865
1962
|
emitAgentRunsUpdated(io, 'ticket', ticketId);
|
|
1963
|
+
if (persistSessionIdToTicket) {
|
|
1964
|
+
updateSessionId(ticketId, sessionId, sessionField, io);
|
|
1965
|
+
}
|
|
1866
1966
|
return;
|
|
1867
1967
|
}
|
|
1868
1968
|
updateSessionId(ticketId, sessionId, sessionField, io);
|
|
@@ -1885,6 +1985,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1885
1985
|
let settled = false;
|
|
1886
1986
|
let startTime = null;
|
|
1887
1987
|
let firstFinalAnswerTime = null;
|
|
1988
|
+
let reviewChild = null;
|
|
1888
1989
|
const buildState = trackBuildProcess
|
|
1889
1990
|
? { child: null, stopRequested: false, sessionLookupTimer: null }
|
|
1890
1991
|
: null;
|
|
@@ -1901,6 +2002,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1901
2002
|
if (trackBuildProcess && buildState) {
|
|
1902
2003
|
untrackBuildProcess(ticketId, buildState);
|
|
1903
2004
|
}
|
|
2005
|
+
if (reviewChild) {
|
|
2006
|
+
reviewProcessState?.children.delete(reviewChild);
|
|
2007
|
+
}
|
|
1904
2008
|
handler(value);
|
|
1905
2009
|
};
|
|
1906
2010
|
const resolveOnce = settle(resolve);
|
|
@@ -1937,12 +2041,22 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1937
2041
|
if (tool === 'opencode') {
|
|
1938
2042
|
spawnTimestamp = Date.now();
|
|
1939
2043
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2044
|
+
let child;
|
|
2045
|
+
try {
|
|
2046
|
+
child = spawn(cmd, args, {
|
|
2047
|
+
cwd,
|
|
2048
|
+
stdio: tool === 'claude' ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
|
2049
|
+
env: { ...process.env, PWD: cwd },
|
|
2050
|
+
detached: process.platform !== 'win32',
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
catch (error) {
|
|
2054
|
+
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
2055
|
+
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2056
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
reviewChild = child;
|
|
1946
2060
|
if (tool === 'claude') {
|
|
1947
2061
|
const claudePrompt = opencodeAgentMode === 'plan'
|
|
1948
2062
|
? appendClaudeHeadlessPlanPrompt(prompt)
|
|
@@ -1953,6 +2067,12 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1953
2067
|
buildState.child = child;
|
|
1954
2068
|
activeBuildProcesses.set(ticketId, buildState);
|
|
1955
2069
|
}
|
|
2070
|
+
if (reviewProcessState) {
|
|
2071
|
+
reviewProcessState.children.add(child);
|
|
2072
|
+
if (reviewProcessState.stopRequested) {
|
|
2073
|
+
killProcess(child, 'SIGTERM');
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
1956
2076
|
startTime = Date.now();
|
|
1957
2077
|
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
1958
2078
|
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
@@ -2018,7 +2138,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2018
2138
|
const finalResponse = responseLines.join('\n\n') || errOutput;
|
|
2019
2139
|
const modelError = isModelResolutionError(finalLog);
|
|
2020
2140
|
const timing = startTime ? { totalMs: Date.now() - startTime } : null;
|
|
2021
|
-
if (buildState?.stopRequested) {
|
|
2141
|
+
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2022
2142
|
const transcriptRuns = finalizeRun('stopped', finalLog, finalResponse);
|
|
2023
2143
|
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing, usageSnapshot }, transcriptRuns));
|
|
2024
2144
|
return;
|
|
@@ -2033,7 +2153,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2033
2153
|
});
|
|
2034
2154
|
child.on('error', err => {
|
|
2035
2155
|
progressUpdater.cancel();
|
|
2036
|
-
if (buildState?.stopRequested) {
|
|
2156
|
+
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2037
2157
|
const transcriptRuns = finalizeRun('stopped', null, null);
|
|
2038
2158
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null, usageSnapshot }, transcriptRuns));
|
|
2039
2159
|
return;
|
|
@@ -2117,8 +2237,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2117
2237
|
? recoveredResponse
|
|
2118
2238
|
: responseLines.join('\n\n') || stderrOutput;
|
|
2119
2239
|
const modelError = isModelResolutionError(log);
|
|
2120
|
-
const timing = buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: Boolean(buildState?.stopRequested) });
|
|
2121
|
-
if (buildState?.stopRequested) {
|
|
2240
|
+
const timing = buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: Boolean(buildState?.stopRequested || reviewProcessState?.stopRequested) });
|
|
2241
|
+
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2122
2242
|
const transcriptRuns = finalizeRun('stopped', log, finalResponse);
|
|
2123
2243
|
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing }, transcriptRuns));
|
|
2124
2244
|
return;
|
|
@@ -2137,7 +2257,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2137
2257
|
clearTimeout(sessionLookupTimer);
|
|
2138
2258
|
sessionLookupTimer = null;
|
|
2139
2259
|
}
|
|
2140
|
-
if (buildState?.stopRequested) {
|
|
2260
|
+
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2141
2261
|
const transcriptRuns = finalizeRun('stopped', null, null);
|
|
2142
2262
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null }, transcriptRuns));
|
|
2143
2263
|
return;
|
|
@@ -2147,12 +2267,15 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2147
2267
|
});
|
|
2148
2268
|
});
|
|
2149
2269
|
};
|
|
2150
|
-
export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, opencodeAgentMode = 'build', }) => {
|
|
2270
|
+
export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, opencodeAgentMode = 'build', executionContext, mainRun, }) => {
|
|
2151
2271
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2152
2272
|
const subagentTracker = createSubagentRunTracker({
|
|
2153
2273
|
parentType: 'chat',
|
|
2154
2274
|
parentId: chatSessionId,
|
|
2155
2275
|
io,
|
|
2276
|
+
executionContext,
|
|
2277
|
+
mainRun,
|
|
2278
|
+
deferMainFinalization: true,
|
|
2156
2279
|
});
|
|
2157
2280
|
return new Promise((resolve, reject) => {
|
|
2158
2281
|
let sessionId = null;
|
|
@@ -2163,11 +2286,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2163
2286
|
let startTime = null;
|
|
2164
2287
|
let firstFinalAnswerTime = null;
|
|
2165
2288
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
2166
|
-
const chatState =
|
|
2167
|
-
child: null,
|
|
2168
|
-
stopRequested: false,
|
|
2169
|
-
metadataRefresher: null,
|
|
2170
|
-
};
|
|
2289
|
+
const chatState = activeChatProcesses.get(chatSessionId) ?? registerChatProcessPlaceholder(chatSessionId);
|
|
2171
2290
|
const settle = (handler) => (value) => {
|
|
2172
2291
|
if (settled) {
|
|
2173
2292
|
return;
|
|
@@ -2180,6 +2299,16 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2180
2299
|
};
|
|
2181
2300
|
const resolveOnce = settle(resolve);
|
|
2182
2301
|
const rejectOnce = settle(reject);
|
|
2302
|
+
if (chatState.stopRequested) {
|
|
2303
|
+
const transcriptRuns = subagentTracker.finalize('stopped', null, null);
|
|
2304
|
+
resolveOnce(withTranscriptRuns({
|
|
2305
|
+
completionStatus: 'stopped',
|
|
2306
|
+
finalResponse: null,
|
|
2307
|
+
usageSnapshot,
|
|
2308
|
+
responseTiming: null,
|
|
2309
|
+
}, transcriptRuns));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2183
2312
|
const [cmd, args] = tool === 'claude'
|
|
2184
2313
|
? (() => {
|
|
2185
2314
|
sessionId = resumeSessionId || randomUUID();
|
|
@@ -2190,8 +2319,10 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2190
2319
|
})()
|
|
2191
2320
|
: ['opencode', ['run', ...(opencodeAgentMode === 'plan' ? ['--agent', 'plan'] : []), '--format', 'json', '--thinking', ...(resumeSessionId ? ['--session', resumeSessionId] : []), ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
|
|
2192
2321
|
updateChatSessionStatus(chatSessionId, 'running', null, io);
|
|
2322
|
+
subagentTracker.updateMain(null, null);
|
|
2193
2323
|
if (tool === 'claude' && sessionId) {
|
|
2194
2324
|
updateChatSessionId(chatSessionId, tool, sessionId, io);
|
|
2325
|
+
subagentTracker.setMainSessionId(sessionId);
|
|
2195
2326
|
}
|
|
2196
2327
|
if (tool === 'opencode') {
|
|
2197
2328
|
spawnTimestamp = Date.now();
|
|
@@ -2209,12 +2340,21 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2209
2340
|
}
|
|
2210
2341
|
}
|
|
2211
2342
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2343
|
+
let child;
|
|
2344
|
+
try {
|
|
2345
|
+
child = spawn(cmd, args, {
|
|
2346
|
+
cwd,
|
|
2347
|
+
stdio: tool === 'claude' ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
|
2348
|
+
env: { ...process.env, PWD: cwd },
|
|
2349
|
+
detached: process.platform !== 'win32',
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
catch (error) {
|
|
2353
|
+
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
2354
|
+
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2355
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2218
2358
|
chatState.child = child;
|
|
2219
2359
|
activeChatProcesses.set(chatSessionId, chatState);
|
|
2220
2360
|
if (tool === 'claude') {
|
|
@@ -2233,6 +2373,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2233
2373
|
discoverParentSession: false,
|
|
2234
2374
|
onParentSessionId: discoveredSessionId => {
|
|
2235
2375
|
updateChatSessionId(chatSessionId, 'opencode', discoveredSessionId, io);
|
|
2376
|
+
subagentTracker.setMainSessionId(discoveredSessionId);
|
|
2236
2377
|
},
|
|
2237
2378
|
});
|
|
2238
2379
|
opencodeSubagentMonitor.start();
|
|
@@ -2243,7 +2384,10 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2243
2384
|
let remainder = '';
|
|
2244
2385
|
const stderrChunks = [];
|
|
2245
2386
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
2246
|
-
|
|
2387
|
+
const log = logLines.join('\n\n');
|
|
2388
|
+
const response = responseLines.join('\n\n');
|
|
2389
|
+
updateChatSessionStatus(chatSessionId, 'running', log, io, response);
|
|
2390
|
+
subagentTracker.updateMain(log, response);
|
|
2247
2391
|
});
|
|
2248
2392
|
child.stdout?.on('data', (chunk) => {
|
|
2249
2393
|
const text = remainder + chunk.toString();
|
|
@@ -2350,7 +2494,10 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2350
2494
|
let remainder = '';
|
|
2351
2495
|
const stderrChunks = [];
|
|
2352
2496
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
2353
|
-
|
|
2497
|
+
const log = logLines.join('\n\n');
|
|
2498
|
+
const response = responseLines.join('\n\n');
|
|
2499
|
+
updateChatSessionStatus(chatSessionId, 'running', log, io, response);
|
|
2500
|
+
subagentTracker.updateMain(log, response);
|
|
2354
2501
|
});
|
|
2355
2502
|
child.stdout?.on('data', (chunk) => {
|
|
2356
2503
|
const text = remainder + chunk.toString();
|
|
@@ -2492,12 +2639,15 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2492
2639
|
});
|
|
2493
2640
|
});
|
|
2494
2641
|
};
|
|
2495
|
-
export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, }) => {
|
|
2642
|
+
export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, executionContext, mainRun, }) => {
|
|
2496
2643
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2497
2644
|
const subagentTracker = createSubagentRunTracker({
|
|
2498
2645
|
parentType: 'spec',
|
|
2499
2646
|
parentId: specSessionId,
|
|
2500
2647
|
io,
|
|
2648
|
+
executionContext,
|
|
2649
|
+
mainRun,
|
|
2650
|
+
deferMainFinalization: true,
|
|
2501
2651
|
});
|
|
2502
2652
|
return new Promise((resolve, reject) => {
|
|
2503
2653
|
let sessionId = null;
|
|
@@ -2508,11 +2658,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2508
2658
|
let startTime = null;
|
|
2509
2659
|
let firstFinalAnswerTime = null;
|
|
2510
2660
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
2511
|
-
const specState =
|
|
2512
|
-
child: null,
|
|
2513
|
-
stopRequested: false,
|
|
2514
|
-
metadataRefresher: null,
|
|
2515
|
-
};
|
|
2661
|
+
const specState = activeSpecProcesses.get(specSessionId) ?? registerSpecProcessPlaceholder(specSessionId);
|
|
2516
2662
|
const settle = (handler) => (value) => {
|
|
2517
2663
|
if (settled) {
|
|
2518
2664
|
return;
|
|
@@ -2525,6 +2671,16 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2525
2671
|
};
|
|
2526
2672
|
const resolveOnce = settle(resolve);
|
|
2527
2673
|
const rejectOnce = settle(reject);
|
|
2674
|
+
if (specState.stopRequested) {
|
|
2675
|
+
const transcriptRuns = subagentTracker.finalize('stopped', null, null);
|
|
2676
|
+
resolveOnce(withTranscriptRuns({
|
|
2677
|
+
completionStatus: 'stopped',
|
|
2678
|
+
finalResponse: null,
|
|
2679
|
+
usageSnapshot,
|
|
2680
|
+
responseTiming: null,
|
|
2681
|
+
}, transcriptRuns));
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2528
2684
|
const [cmd, args] = tool === 'claude'
|
|
2529
2685
|
? (() => {
|
|
2530
2686
|
sessionId = resumeSessionId || randomUUID();
|
|
@@ -2535,8 +2691,10 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2535
2691
|
})()
|
|
2536
2692
|
: ['opencode', ['run', '--agent', 'plan', '--format', 'json', '--thinking', ...(resumeSessionId ? ['--session', resumeSessionId] : []), ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
|
|
2537
2693
|
updateSpecSessionStatusLocal(specSessionId, 'running', null, io);
|
|
2694
|
+
subagentTracker.updateMain(null, null);
|
|
2538
2695
|
if (tool === 'claude' && sessionId) {
|
|
2539
2696
|
updateSpecSessionIdLocal(specSessionId, tool, sessionId, io);
|
|
2697
|
+
subagentTracker.setMainSessionId(sessionId);
|
|
2540
2698
|
}
|
|
2541
2699
|
if (tool === 'opencode') {
|
|
2542
2700
|
spawnTimestamp = Date.now();
|
|
@@ -2554,12 +2712,21 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2554
2712
|
}
|
|
2555
2713
|
}
|
|
2556
2714
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2715
|
+
let child;
|
|
2716
|
+
try {
|
|
2717
|
+
child = spawn(cmd, args, {
|
|
2718
|
+
cwd,
|
|
2719
|
+
stdio: tool === 'claude' ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
|
2720
|
+
env: { ...process.env, PWD: cwd },
|
|
2721
|
+
detached: process.platform !== 'win32',
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
catch (error) {
|
|
2725
|
+
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
2726
|
+
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2727
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2563
2730
|
specState.child = child;
|
|
2564
2731
|
activeSpecProcesses.set(specSessionId, specState);
|
|
2565
2732
|
if (tool === 'claude') {
|
|
@@ -2575,6 +2742,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2575
2742
|
discoverParentSession: false,
|
|
2576
2743
|
onParentSessionId: discoveredSessionId => {
|
|
2577
2744
|
updateSpecSessionIdLocal(specSessionId, 'opencode', discoveredSessionId, io);
|
|
2745
|
+
subagentTracker.setMainSessionId(discoveredSessionId);
|
|
2578
2746
|
},
|
|
2579
2747
|
});
|
|
2580
2748
|
opencodeSubagentMonitor.start();
|
|
@@ -2585,7 +2753,10 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2585
2753
|
let remainder = '';
|
|
2586
2754
|
const stderrChunks = [];
|
|
2587
2755
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
2588
|
-
|
|
2756
|
+
const log = logLines.join('\n\n');
|
|
2757
|
+
const response = responseLines.join('\n\n');
|
|
2758
|
+
updateSpecSessionStatusLocal(specSessionId, 'running', log, io, response);
|
|
2759
|
+
subagentTracker.updateMain(log, response);
|
|
2589
2760
|
});
|
|
2590
2761
|
child.stdout?.on('data', (chunk) => {
|
|
2591
2762
|
const text = remainder + chunk.toString();
|
|
@@ -2692,7 +2863,10 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2692
2863
|
let remainder = '';
|
|
2693
2864
|
const stderrChunks = [];
|
|
2694
2865
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
2695
|
-
|
|
2866
|
+
const log = logLines.join('\n\n');
|
|
2867
|
+
const response = responseLines.join('\n\n');
|
|
2868
|
+
updateSpecSessionStatusLocal(specSessionId, 'running', log, io, response);
|
|
2869
|
+
subagentTracker.updateMain(log, response);
|
|
2696
2870
|
});
|
|
2697
2871
|
child.stdout?.on('data', (chunk) => {
|
|
2698
2872
|
const text = remainder + chunk.toString();
|
|
@@ -2845,15 +3019,24 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2845
3019
|
* updateSpecReviewRunStatus / updateSpecReviewRunStreaming / updateSpecReviewRunUsage —
|
|
2846
3020
|
* the spec_sessions row is never touched by this path.
|
|
2847
3021
|
*/
|
|
2848
|
-
export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [], cwd, io, model, variant = null, }) => {
|
|
3022
|
+
export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, executionContext, }) => {
|
|
2849
3023
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
3024
|
+
const subagentTracker = createSubagentRunTracker({
|
|
3025
|
+
parentType: 'spec',
|
|
3026
|
+
parentId: specSessionId,
|
|
3027
|
+
io,
|
|
3028
|
+
executionContext,
|
|
3029
|
+
});
|
|
2850
3030
|
return new Promise((resolve, reject) => {
|
|
3031
|
+
let spawnTimestamp = null;
|
|
3032
|
+
let opencodeSubagentMonitor = null;
|
|
2851
3033
|
let settled = false;
|
|
2852
3034
|
let startTime = null;
|
|
2853
3035
|
let firstFinalAnswerTime = null;
|
|
2854
3036
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
2855
3037
|
const reviewState = {
|
|
2856
3038
|
child: null,
|
|
3039
|
+
researchChildren: new Set(),
|
|
2857
3040
|
stopRequested: false,
|
|
2858
3041
|
metadataRefresher: null,
|
|
2859
3042
|
};
|
|
@@ -2862,14 +3045,12 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2862
3045
|
return;
|
|
2863
3046
|
}
|
|
2864
3047
|
settled = true;
|
|
3048
|
+
opencodeSubagentMonitor?.stop();
|
|
2865
3049
|
untrackSpecReviewProcess(specReviewRunId, reviewState);
|
|
2866
3050
|
handler(value);
|
|
2867
3051
|
};
|
|
2868
3052
|
const resolveOnce = settle(resolve);
|
|
2869
3053
|
const rejectOnce = settle(reject);
|
|
2870
|
-
const emitReviewUpdated = () => {
|
|
2871
|
-
io.emit('spec-review:updated', { spec_review_run_id: specReviewRunId });
|
|
2872
|
-
};
|
|
2873
3054
|
const [cmd, args] = tool === 'claude'
|
|
2874
3055
|
? [
|
|
2875
3056
|
'claude',
|
|
@@ -2877,7 +3058,6 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2877
3058
|
]
|
|
2878
3059
|
: ['opencode', ['run', '--agent', 'plan', '--format', 'json', '--thinking', ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
|
|
2879
3060
|
updateSpecReviewRunStreaming(specReviewRunId, null);
|
|
2880
|
-
emitReviewUpdated();
|
|
2881
3061
|
const child = spawn(cmd, args, {
|
|
2882
3062
|
cwd,
|
|
2883
3063
|
stdio: tool === 'claude' ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
|
@@ -2890,6 +3070,16 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2890
3070
|
writeClaudeStdin(child, buildClaudeStdinMessage(appendClaudeHeadlessPlanPrompt(prompt), files, cwd));
|
|
2891
3071
|
}
|
|
2892
3072
|
startTime = Date.now();
|
|
3073
|
+
if (tool === 'opencode') {
|
|
3074
|
+
spawnTimestamp = startTime;
|
|
3075
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
3076
|
+
initialParentSessionId: null,
|
|
3077
|
+
spawnTimestamp,
|
|
3078
|
+
cwd,
|
|
3079
|
+
tracker: subagentTracker,
|
|
3080
|
+
});
|
|
3081
|
+
opencodeSubagentMonitor.start();
|
|
3082
|
+
}
|
|
2893
3083
|
if (tool === 'claude') {
|
|
2894
3084
|
const logLines = [];
|
|
2895
3085
|
const responseLines = [];
|
|
@@ -2904,7 +3094,8 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2904
3094
|
remainder = lines.pop() ?? '';
|
|
2905
3095
|
for (const line of lines) {
|
|
2906
3096
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
|
|
2907
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(line);
|
|
3097
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(line);
|
|
3098
|
+
subagentTracker.handle(subagentEvents);
|
|
2908
3099
|
if (logFragment !== null) {
|
|
2909
3100
|
logLines.push(logFragment);
|
|
2910
3101
|
}
|
|
@@ -2921,7 +3112,8 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2921
3112
|
progressUpdater.cancel();
|
|
2922
3113
|
if (remainder.trim()) {
|
|
2923
3114
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
2924
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(remainder);
|
|
3115
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(remainder);
|
|
3116
|
+
subagentTracker.handle(subagentEvents);
|
|
2925
3117
|
if (logFragment !== null) {
|
|
2926
3118
|
logLines.push(logFragment);
|
|
2927
3119
|
}
|
|
@@ -2934,6 +3126,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2934
3126
|
const finalResponse = responseLines.join('\n\n') || errOutput;
|
|
2935
3127
|
if (reviewState.stopRequested) {
|
|
2936
3128
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3129
|
+
subagentTracker.finalize('stopped', finalLog || null, finalResponse || null);
|
|
2937
3130
|
resolveOnce({
|
|
2938
3131
|
completionStatus: 'stopped',
|
|
2939
3132
|
finalResponse,
|
|
@@ -2945,6 +3138,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2945
3138
|
const modelError = isModelResolutionError(finalLog);
|
|
2946
3139
|
if (code === 0 && !modelError) {
|
|
2947
3140
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3141
|
+
subagentTracker.finalize('done', finalLog || null, finalResponse || null);
|
|
2948
3142
|
resolveOnce({
|
|
2949
3143
|
completionStatus: 'done',
|
|
2950
3144
|
finalResponse,
|
|
@@ -2953,10 +3147,12 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2953
3147
|
});
|
|
2954
3148
|
return;
|
|
2955
3149
|
}
|
|
3150
|
+
subagentTracker.finalize('error', finalLog || null, finalResponse || null);
|
|
2956
3151
|
rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}${errOutput !== finalLog && errOutput ? '\n\n' + errOutput : ''}`));
|
|
2957
3152
|
});
|
|
2958
3153
|
child.on('error', err => {
|
|
2959
3154
|
progressUpdater.cancel();
|
|
3155
|
+
subagentTracker.finalize(reviewState.stopRequested ? 'stopped' : 'error', getErrorMessage(err), null);
|
|
2960
3156
|
rejectOnce(err instanceof Error ? err : new Error(getErrorMessage(err)));
|
|
2961
3157
|
});
|
|
2962
3158
|
return;
|
|
@@ -2991,7 +3187,9 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2991
3187
|
}
|
|
2992
3188
|
}
|
|
2993
3189
|
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
2994
|
-
const { logFragment, responseFragment } = parsed;
|
|
3190
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
3191
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
3192
|
+
subagentTracker.handle(subagentEvents);
|
|
2995
3193
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2996
3194
|
firstFinalAnswerTime = null;
|
|
2997
3195
|
}
|
|
@@ -3003,9 +3201,12 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
3003
3201
|
child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
|
|
3004
3202
|
child.on('close', code => {
|
|
3005
3203
|
progressUpdater.cancel();
|
|
3204
|
+
opencodeSubagentMonitor?.refresh();
|
|
3006
3205
|
if (remainder.trim()) {
|
|
3007
3206
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
3008
3207
|
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
3208
|
+
opencodeSubagentMonitor?.setParentSessionId(parsed.providerSessionId);
|
|
3209
|
+
subagentTracker.handle(parsed.subagentEvents);
|
|
3009
3210
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3010
3211
|
firstFinalAnswerTime = null;
|
|
3011
3212
|
}
|
|
@@ -3018,6 +3219,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
3018
3219
|
: responseLines.join('\n\n') || stderrOutput;
|
|
3019
3220
|
if (reviewState.stopRequested) {
|
|
3020
3221
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3222
|
+
subagentTracker.finalize('stopped', log || null, finalResponse || null);
|
|
3021
3223
|
resolveOnce({
|
|
3022
3224
|
completionStatus: 'stopped',
|
|
3023
3225
|
finalResponse,
|
|
@@ -3029,6 +3231,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
3029
3231
|
const modelError = isModelResolutionError(log);
|
|
3030
3232
|
if (isSuccessfulOpencodeExit(code, modelError, recoveryTracker)) {
|
|
3031
3233
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3234
|
+
subagentTracker.finalize('done', log || null, finalResponse || null);
|
|
3032
3235
|
resolveOnce({
|
|
3033
3236
|
completionStatus: 'done',
|
|
3034
3237
|
finalResponse,
|
|
@@ -3037,10 +3240,12 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
3037
3240
|
});
|
|
3038
3241
|
return;
|
|
3039
3242
|
}
|
|
3243
|
+
subagentTracker.finalize('error', log || null, finalResponse || null);
|
|
3040
3244
|
rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`));
|
|
3041
3245
|
});
|
|
3042
3246
|
child.on('error', err => {
|
|
3043
3247
|
progressUpdater.cancel();
|
|
3248
|
+
subagentTracker.finalize(reviewState.stopRequested ? 'stopped' : 'error', getErrorMessage(err), null);
|
|
3044
3249
|
rejectOnce(err instanceof Error ? err : new Error(getErrorMessage(err)));
|
|
3045
3250
|
});
|
|
3046
3251
|
});
|