@straiffi/archon 1.8.13 → 1.8.14
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-DD1Fom-k.js → ActivityView-DYDYyqIN.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-aDf_6y6m.js → GitHubActionsDialog-jhi4GOyV.js} +1 -1
- package/dist/client/assets/{RepositoryGraphDialog-B4Ij2xmA.js → RepositoryGraphDialog-BZV0vHu6.js} +1 -1
- package/dist/client/assets/{VerificationDialog-BBCRKTdr.js → VerificationDialog-Sc6e8AKH.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DW3kiEe6.js → architectureDiagram-3BPJPVTR-DFbki8mi.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BVWrspTj.js → blockDiagram-GPEHLZMM-BZFehG8P.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CDqXjYR1.js → c4Diagram-AAUBKEIU-FVDXRu1t.js} +1 -1
- package/dist/client/assets/channel-D1kOpezG.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Db4bg8nj.js → chunk-2J33WTMH-4tuVZaT0.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DqziKs5m.js → chunk-3OPIFGDE-DSn14S35.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-wdN7gQNo.js → chunk-5ZQYHXKU-Cwx1KNjg.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Dj3aKc06.js → chunk-727SXJPM-Odza4hes.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CAlE_C_J.js → chunk-AQP2D5EJ-BmVoQT7M.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-KstW8JRv.js → chunk-CSCIHK7Q-Bg7Ktx0B.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-DrY-9bi9.js → chunk-KSCS5N6A-2tnhar2x.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-C6fw_Dab.js → chunk-L5ZTLDWV-dAyg8qMJ.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-DaZP5hZq.js → chunk-LZXEDZCA-0gElxrlR.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-ChYXM7G3.js → chunk-ND2GUHAM-Bu-8Lz91.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-JajrDJgw.js → chunk-NZK2D7GU-CInZFdzd.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-C4HoCf_q.js → chunk-O5CBEL6O-rAzpvdNR.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-BdjAiDOI.js → chunk-WU5MYG2G-DOOw2vq7.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C253nwny.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DFgDA23g.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-CpXupRCy.js → dagre-BM42HDAG-Dgua1iuh.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DyopuXBv.js → diagram-2AECGRRQ-odacwlwu.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-C8lS8Vro.js → diagram-5GNKFQAL-BiKjFmJ1.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CsyC_xgK.js → diagram-KO2AKTUF-CmYcvFri.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DWsQdA71.js → diagram-LMA3HP47-ncx0RBuX.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-YkNa_Tt6.js → diagram-OG6HWLK6-DbchKIzw.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-VmMz5G_L.js → erDiagram-TEJ5UH35-CMnpvRyQ.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DZAu6IJl.js → flowDiagram-I6XJVG4X-Bi02_PK5.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CHJRz_2M.js → ganttDiagram-6RSMTGT7-BiS992US.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BfA1qYTR.js → gitGraphDiagram-PVQCEYII-Cc1hrTsT.js} +1 -1
- package/dist/client/assets/{index-B4x8ZZ8N.js → index-B1gdJKwK.js} +90 -90
- package/dist/client/assets/{infoDiagram-5YYISTIA-CyqqEYvx.js → infoDiagram-5YYISTIA-BKVfqJCe.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DnXMvxPm.js → ishikawaDiagram-YF4QCWOH-B-IbFOty.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DBumlehf.js → journeyDiagram-JHISSGLW-DAZBgAQW.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-D-QvfQhh.js → kanban-definition-UN3LZRKU-Bkr_fXev.js} +1 -1
- package/dist/client/assets/{line-BgwSCyaP.js → line-CROuuXDO.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-qDA1puIK.js → mermaid-parser.core-DrBw_Rsc.js} +1 -1
- package/dist/client/assets/{mermaid.core-CFM-fuJi.js → mermaid.core-pPd6OYYj.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B93RAITk.js → mindmap-definition-RKZ34NQL-BXSd3TZU.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-C9arDpCE.js → pieDiagram-4H26LBE5-DoT7PIZC.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Tma_RsUu.js → quadrantDiagram-W4KKPZXB-ycZkLowm.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-C6GCxpqT.js → requirementDiagram-4Y6WPE33-BPkO1yLu.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CY8ukFyG.js → sankeyDiagram-5OEKKPKP-D7AvhhCw.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B6MbhJm7.js → sequenceDiagram-3UESZ5HK-C982swnH.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Bjy1dBVk.js → stateDiagram-AJRCARHV-xvJ3u8lc.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BKQG8NCn.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-KrbaHcGf.js → timeline-definition-PNZ67QCA-D89qjzb5.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-DQ-5gbga.js → vennDiagram-CIIHVFJN-CoKeC__u.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Cwpu-hvf.js → wardleyDiagram-YWT4CUSO-DKhjzvSE.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DoP4HxQE.js → xychartDiagram-2RQKCTM6-DFn9ass1.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +26 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +171 -170
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +64 -0
- package/dist/server/lib/agentProvider.js.map +1 -0
- package/dist/server/lib/agentRuns.js +1 -0
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/automationProgression.js +6 -2
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +4 -3
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/claudeAgentProvider.js +48 -0
- package/dist/server/lib/claudeAgentProvider.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js +20 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -0
- package/dist/server/lib/hostExecutionEnvironment.js +77 -0
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/lightweightPrompt.js +17 -18
- package/dist/server/lib/lightweightPrompt.js.map +1 -1
- package/dist/server/lib/opencodeAgentProvider.js +139 -0
- package/dist/server/lib/opencodeAgentProvider.js.map +1 -0
- package/dist/server/lib/opencodeSessionOperations.js +348 -0
- package/dist/server/lib/opencodeSessionOperations.js.map +1 -0
- package/dist/server/lib/opencodeTaskSessions.js +13 -225
- package/dist/server/lib/opencodeTaskSessions.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +22 -19
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +15 -0
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/followUp.js +10 -6
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +5 -21
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +33 -14
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-C14KP1Bm.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CR20Gg9e.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B-N-i15d.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-asf2fsPC.js +0 -1
package/dist/server/lib/agent.js
CHANGED
|
@@ -2,73 +2,29 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { extname, isAbsolute, join, relative, resolve, sep } from 'path';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
5
|
import db from '../db.js';
|
|
7
6
|
import { getChatSession, updateChatSessionUsageSnapshot } from './chats.js';
|
|
8
7
|
import { normalizeClaudeModelId, normalizeModelId } from './models.js';
|
|
9
8
|
import { createOpencodeTaskSessionReader, parseOpencodeTaskEnvelope } from './opencodeTaskSessions.js';
|
|
9
|
+
import { hostOpencodeSessionOperations } from './opencodeSessionOperations.js';
|
|
10
|
+
import { hostExecutionEnvironment } from './hostExecutionEnvironment.js';
|
|
11
|
+
import { claudeAgentProvider } from './claudeAgentProvider.js';
|
|
12
|
+
import { buildOpencodeSpecReviewInvocation, createOpencodeExitError, createOpencodeRecoveryTracker, interpretOpencodeRecoveryEvent, isSuccessfulOpencodeExit, opencodeAgentProvider, } from './opencodeAgentProvider.js';
|
|
10
13
|
import { appendClaudeHeadlessPlanPrompt, buildClaudeHeadlessPlanArgs } from './planModeQuestions.js';
|
|
11
14
|
import { formatGenericToolCall } from './genericToolCall.js';
|
|
12
15
|
import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
|
|
13
16
|
import { updateSpecReviewRunStatus, updateSpecReviewRunStreaming, updateSpecReviewRunUsage } from './specReviews.js';
|
|
14
17
|
import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForExecutionRun, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
15
|
-
import { resolveOpencodeDbPath } from './runtimePaths.js';
|
|
16
18
|
import { markTicketDescriptionVisibleInBundle } from './ticketMessages.js';
|
|
17
19
|
import { getTicket } from './tickets.js';
|
|
18
20
|
import { createTranscriptRunSnapshots } from './transcriptRuns.js';
|
|
19
21
|
import { createWorkspaceProcessEnvironment } from './workspaceProcessEnvironment.js';
|
|
20
|
-
import { OpencodeModelResolutionError,
|
|
22
|
+
import { OpencodeModelResolutionError, parseOpencodeStderrModelResolutionError, } from './opencodeErrors.js';
|
|
21
23
|
import { extractLeadingOpencodeJsonObjects } from './opencodePromptOutput.js';
|
|
22
24
|
import { assertAgentSpawnPermit, AgentSpawnPermitRevokedError } from './agentSpawnPermits.js';
|
|
23
25
|
const withTranscriptRuns = (value, transcriptRuns) => {
|
|
24
26
|
return transcriptRuns ? { ...value, transcriptRuns } : value;
|
|
25
27
|
};
|
|
26
|
-
const createOpencodeRecoveryTracker = () => {
|
|
27
|
-
let sawError = false;
|
|
28
|
-
let latestEvent = null;
|
|
29
|
-
let recoveredFinalAnswerFragments = [];
|
|
30
|
-
let latestError = null;
|
|
31
|
-
let modelResolutionError = null;
|
|
32
|
-
return {
|
|
33
|
-
consume: events => {
|
|
34
|
-
let shouldResetResponse = false;
|
|
35
|
-
for (const event of events ?? []) {
|
|
36
|
-
latestEvent = event.type;
|
|
37
|
-
if (event.type === 'error') {
|
|
38
|
-
sawError = true;
|
|
39
|
-
latestError = event.error;
|
|
40
|
-
if (event.error?.isModelResolutionError) {
|
|
41
|
-
modelResolutionError = event.error;
|
|
42
|
-
}
|
|
43
|
-
recoveredFinalAnswerFragments = [];
|
|
44
|
-
shouldResetResponse = true;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (sawError) {
|
|
48
|
-
recoveredFinalAnswerFragments.push(event.text);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return shouldResetResponse;
|
|
52
|
-
},
|
|
53
|
-
hasRecoveredFinalAnswer: () => (sawError
|
|
54
|
-
&& latestEvent === 'final_answer'
|
|
55
|
-
&& recoveredFinalAnswerFragments.length > 0),
|
|
56
|
-
hasUnrecoveredError: () => latestEvent === 'error',
|
|
57
|
-
getModelResolutionError: () => modelResolutionError,
|
|
58
|
-
getUnrecoveredError: () => latestEvent === 'error' ? latestError : null,
|
|
59
|
-
getRecoveredResponse: () => recoveredFinalAnswerFragments.join('\n\n'),
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
const isSuccessfulOpencodeExit = (code, stderrModelError, recoveryTracker) => (!stderrModelError
|
|
63
|
-
&& !recoveryTracker.getModelResolutionError()
|
|
64
|
-
&& !recoveryTracker.hasUnrecoveredError()
|
|
65
|
-
&& (code === 0 || recoveryTracker.hasRecoveredFinalAnswer()));
|
|
66
|
-
const createOpencodeExitError = (message, stderrModelError, recoveryTracker) => {
|
|
67
|
-
const structuredError = recoveryTracker.getModelResolutionError() ?? recoveryTracker.getUnrecoveredError();
|
|
68
|
-
return structuredError?.isModelResolutionError || stderrModelError
|
|
69
|
-
? new OpencodeModelResolutionError(message)
|
|
70
|
-
: new Error(message);
|
|
71
|
-
};
|
|
72
28
|
export class AgentContinuationRejectedError extends Error {
|
|
73
29
|
constructor(message) {
|
|
74
30
|
super(message);
|
|
@@ -91,7 +47,6 @@ export const getAgentErrorTranscriptRuns = (error) => {
|
|
|
91
47
|
const transcriptRuns = error.transcriptRuns;
|
|
92
48
|
return Array.isArray(transcriptRuns) && transcriptRuns.length > 0 ? transcriptRuns : null;
|
|
93
49
|
};
|
|
94
|
-
const OPENCODE_PLACEHOLDER_TITLE_PATTERN = /^New session\b/i;
|
|
95
50
|
const OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS = 1500;
|
|
96
51
|
const AGENT_PROGRESS_UPDATE_INTERVAL_MS = 100;
|
|
97
52
|
const resolveAgentModel = (tool, model) => {
|
|
@@ -157,13 +112,6 @@ const buildClaudeArgs = (mode, model, resumeSessionId, sessionId) => {
|
|
|
157
112
|
sessionId,
|
|
158
113
|
];
|
|
159
114
|
};
|
|
160
|
-
const OPENCODE_SESSION_BY_ID_QUERY = 'SELECT id, title FROM session WHERE id = ? LIMIT 1';
|
|
161
|
-
const OPENCODE_SESSION_BY_CREATED_AT_QUERY = 'SELECT id, title FROM session WHERE time_created > ? ORDER BY time_created DESC LIMIT 1';
|
|
162
|
-
const OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_QUERY = "SELECT data FROM message WHERE session_id = ? AND json_extract(data, '$.role') = 'assistant' AND json_extract(data, '$.time.completed') IS NOT NULL ORDER BY time_created DESC LIMIT 1";
|
|
163
|
-
const OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_SINCE_QUERY = "SELECT data FROM message WHERE session_id = ? AND time_created >= ? AND json_extract(data, '$.role') = 'assistant' AND json_extract(data, '$.time.completed') IS NOT NULL ORDER BY time_created DESC LIMIT 1";
|
|
164
|
-
const OPENCODE_STEP_FINISH_USAGE_QUERY = "SELECT data FROM part WHERE session_id = ? AND json_extract(data, '$.type') = 'step-finish' ORDER BY time_created DESC LIMIT 1";
|
|
165
|
-
const OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY = "SELECT data FROM part WHERE session_id = ? AND time_created >= ? AND json_extract(data, '$.type') = 'step-finish' ORDER BY time_created DESC LIMIT 1";
|
|
166
|
-
const OPENCODE_SESSION_COST_QUERY = 'SELECT cost FROM session WHERE id = ?';
|
|
167
115
|
const GET_PERSISTED_CHAT_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM chat_sessions WHERE id = ?';
|
|
168
116
|
const GET_PERSISTED_SPEC_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM spec_sessions WHERE id = ?';
|
|
169
117
|
export const EMPTY_CHAT_USAGE_SNAPSHOT = {
|
|
@@ -327,9 +275,6 @@ export const mergeUsageFromJsonLine = (current, line) => {
|
|
|
327
275
|
}
|
|
328
276
|
return next;
|
|
329
277
|
};
|
|
330
|
-
const hasUsageSnapshot = (value) => {
|
|
331
|
-
return Object.values(value).some(candidate => candidate !== null && candidate !== undefined);
|
|
332
|
-
};
|
|
333
278
|
const hasMatchingUsageSnapshot = (current, next) => {
|
|
334
279
|
return current.usage_snapshot_context_window === next.usage_snapshot_context_window
|
|
335
280
|
&& current.usage_snapshot_max_output_tokens === next.usage_snapshot_max_output_tokens
|
|
@@ -500,67 +445,12 @@ const createThrottledProgressUpdater = (flushUpdate) => {
|
|
|
500
445
|
},
|
|
501
446
|
};
|
|
502
447
|
};
|
|
503
|
-
const openOpencodeDatabase = () => new Database(resolveOpencodeDbPath(), { readonly: true });
|
|
504
|
-
const readOpencodeSession = (statement, value) => {
|
|
505
|
-
const opencodeDb = openOpencodeDatabase();
|
|
506
|
-
try {
|
|
507
|
-
const row = opencodeDb.prepare(statement).get(value);
|
|
508
|
-
if (!row?.id) {
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
const normalizedTitle = typeof row.title === 'string'
|
|
512
|
-
? row.title.trim() || null
|
|
513
|
-
: null;
|
|
514
|
-
const title = normalizedTitle && !OPENCODE_PLACEHOLDER_TITLE_PATTERN.test(normalizedTitle)
|
|
515
|
-
? normalizedTitle
|
|
516
|
-
: null;
|
|
517
|
-
return {
|
|
518
|
-
id: row.id,
|
|
519
|
-
title,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
finally {
|
|
523
|
-
opencodeDb.close();
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
448
|
export const readOpencodeUsageSnapshot = ({ sessionId, spawnTimestamp = null, }) => {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const sessionCost = typeof costRow?.cost === 'number' ? { usage_snapshot_cost: costRow.cost } : {};
|
|
531
|
-
const completedAssistantMessage = (spawnTimestamp === null
|
|
532
|
-
? opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_QUERY).get(sessionId)
|
|
533
|
-
: opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
534
|
-
if (typeof completedAssistantMessage?.data === 'string') {
|
|
535
|
-
try {
|
|
536
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(completedAssistantMessage.data));
|
|
537
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
538
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
// Fall through to the step-finish lookup below.
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
const latestStepFinish = (spawnTimestamp === null
|
|
546
|
-
? opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_QUERY).get(sessionId)
|
|
547
|
-
: opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
548
|
-
if (typeof latestStepFinish?.data === 'string') {
|
|
549
|
-
try {
|
|
550
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(latestStepFinish.data));
|
|
551
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
552
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch {
|
|
556
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
opencodeDb.close();
|
|
449
|
+
const snapshot = hostOpencodeSessionOperations.readUsageSnapshot(sessionId, spawnTimestamp);
|
|
450
|
+
if (!snapshot) {
|
|
451
|
+
throw new Error('OpenCode usage snapshot read failed');
|
|
563
452
|
}
|
|
453
|
+
return snapshot;
|
|
564
454
|
};
|
|
565
455
|
const resolveOpencodeUsageSnapshot = ({ current, sessionId, spawnTimestamp = null, }) => {
|
|
566
456
|
try {
|
|
@@ -655,12 +545,12 @@ const createOpencodeMetadataRefresher = ({ chatSessionId, io, spawnTimestamp, in
|
|
|
655
545
|
};
|
|
656
546
|
const readCurrentSession = () => {
|
|
657
547
|
if (knownSessionId) {
|
|
658
|
-
return
|
|
548
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
659
549
|
}
|
|
660
550
|
if (spawnTimestamp === null) {
|
|
661
551
|
return null;
|
|
662
552
|
}
|
|
663
|
-
return
|
|
553
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
664
554
|
};
|
|
665
555
|
const refresh = () => {
|
|
666
556
|
if (stopped) {
|
|
@@ -723,12 +613,12 @@ const createSpecOpencodeMetadataRefresher = ({ specSessionId, io, spawnTimestamp
|
|
|
723
613
|
};
|
|
724
614
|
const readCurrentSession = () => {
|
|
725
615
|
if (knownSessionId) {
|
|
726
|
-
return
|
|
616
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
727
617
|
}
|
|
728
618
|
if (spawnTimestamp === null) {
|
|
729
619
|
return null;
|
|
730
620
|
}
|
|
731
|
-
return
|
|
621
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
732
622
|
};
|
|
733
623
|
const refresh = () => {
|
|
734
624
|
if (stopped) {
|
|
@@ -1094,15 +984,14 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1094
984
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
1095
985
|
const text = event.part.text.trim();
|
|
1096
986
|
const phase = event.part.metadata?.openai?.phase;
|
|
987
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, text, phase);
|
|
1097
988
|
return {
|
|
1098
989
|
logFragment: text || null,
|
|
1099
990
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
1100
991
|
subagentEvents: [],
|
|
1101
992
|
providerSessionId,
|
|
1102
993
|
modelResolutionError: false,
|
|
1103
|
-
recoveryEvents:
|
|
1104
|
-
? [{ type: 'final_answer', text }]
|
|
1105
|
-
: [],
|
|
994
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1106
995
|
};
|
|
1107
996
|
}
|
|
1108
997
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -1186,14 +1075,15 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1186
1075
|
};
|
|
1187
1076
|
}
|
|
1188
1077
|
if (event.type === 'error') {
|
|
1189
|
-
const
|
|
1078
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, event.part?.text, undefined);
|
|
1079
|
+
const parsedError = recoveryEvent?.type === 'error' ? recoveryEvent.error : null;
|
|
1190
1080
|
const text = parsedError?.message ?? '';
|
|
1191
1081
|
return {
|
|
1192
1082
|
logFragment: text ? `**Error:** ${text}` : null,
|
|
1193
1083
|
responseFragment: null,
|
|
1194
1084
|
subagentEvents: [],
|
|
1195
1085
|
providerSessionId,
|
|
1196
|
-
recoveryEvents:
|
|
1086
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1197
1087
|
};
|
|
1198
1088
|
}
|
|
1199
1089
|
return emptyParsedStreamLine();
|
|
@@ -1724,6 +1614,16 @@ const killProcess = (proc, signal) => {
|
|
|
1724
1614
|
catch {
|
|
1725
1615
|
}
|
|
1726
1616
|
};
|
|
1617
|
+
const interruptProcess = (state, signal) => {
|
|
1618
|
+
if (!isProcessAlive(state.child)) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
if (state.interrupt) {
|
|
1622
|
+
state.interrupt(signal);
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
killProcess(state.child, signal);
|
|
1626
|
+
};
|
|
1727
1627
|
const waitForProcessExit = async (proc, timeoutMs) => {
|
|
1728
1628
|
const deadline = Date.now() + timeoutMs;
|
|
1729
1629
|
await new Promise(resolve => {
|
|
@@ -1757,6 +1657,7 @@ const untrackChatProcess = (chatSessionId, state) => {
|
|
|
1757
1657
|
export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
1758
1658
|
const state = {
|
|
1759
1659
|
child: null,
|
|
1660
|
+
interrupt: null,
|
|
1760
1661
|
researchChildren: new Set(),
|
|
1761
1662
|
stopRequested: false,
|
|
1762
1663
|
metadataRefresher: null,
|
|
@@ -1765,7 +1666,7 @@ export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
|
1765
1666
|
return state;
|
|
1766
1667
|
};
|
|
1767
1668
|
export const registerBuildProcessPlaceholder = (ticketId) => {
|
|
1768
|
-
const state = { child: null, stopRequested: false, sessionLookupTimer: null };
|
|
1669
|
+
const state = { child: null, interrupt: null, stopRequested: false, sessionLookupTimer: null };
|
|
1769
1670
|
activeBuildProcesses.set(ticketId, state);
|
|
1770
1671
|
return state;
|
|
1771
1672
|
};
|
|
@@ -1796,6 +1697,7 @@ const untrackSpecProcess = (specSessionId, state) => {
|
|
|
1796
1697
|
export const registerSpecProcessPlaceholder = (specSessionId) => {
|
|
1797
1698
|
const state = {
|
|
1798
1699
|
child: null,
|
|
1700
|
+
interrupt: null,
|
|
1799
1701
|
researchChildren: new Set(),
|
|
1800
1702
|
stopRequested: false,
|
|
1801
1703
|
metadataRefresher: null,
|
|
@@ -1819,6 +1721,7 @@ const untrackSpecReviewProcess = (specReviewRunId, state) => {
|
|
|
1819
1721
|
export const registerSpecReviewProcessPlaceholder = (specReviewRunId) => {
|
|
1820
1722
|
const state = {
|
|
1821
1723
|
child: null,
|
|
1724
|
+
interrupt: null,
|
|
1822
1725
|
researchChildren: new Set(),
|
|
1823
1726
|
stopRequested: false,
|
|
1824
1727
|
metadataRefresher: null,
|
|
@@ -1864,10 +1767,20 @@ export const stopBuildAgent = async (ticketId) => {
|
|
|
1864
1767
|
if (!state.child) {
|
|
1865
1768
|
return true;
|
|
1866
1769
|
}
|
|
1867
|
-
|
|
1770
|
+
if (state.interrupt) {
|
|
1771
|
+
state.interrupt('SIGTERM');
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
killProcess(state.child, 'SIGTERM');
|
|
1775
|
+
}
|
|
1868
1776
|
await waitForProcessExit(state.child, 5000);
|
|
1869
1777
|
if (isProcessAlive(state.child)) {
|
|
1870
|
-
|
|
1778
|
+
if (state.interrupt) {
|
|
1779
|
+
state.interrupt('SIGKILL');
|
|
1780
|
+
}
|
|
1781
|
+
else {
|
|
1782
|
+
killProcess(state.child, 'SIGKILL');
|
|
1783
|
+
}
|
|
1871
1784
|
await waitForProcessExit(state.child, 1000);
|
|
1872
1785
|
}
|
|
1873
1786
|
return true;
|
|
@@ -1943,15 +1856,15 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
|
1943
1856
|
if (!state.child) {
|
|
1944
1857
|
return true;
|
|
1945
1858
|
}
|
|
1946
|
-
|
|
1859
|
+
interruptProcess(state, 'SIGTERM');
|
|
1947
1860
|
await waitForProcessExit(state.child, 5000);
|
|
1948
1861
|
if (isProcessAlive(state.child)) {
|
|
1949
|
-
|
|
1862
|
+
interruptProcess(state, 'SIGKILL');
|
|
1950
1863
|
await waitForProcessExit(state.child, 1000);
|
|
1951
1864
|
}
|
|
1952
1865
|
return true;
|
|
1953
1866
|
};
|
|
1954
|
-
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, spawnPermitId, }) => {
|
|
1867
|
+
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, spawnPermitId, executionProvider = hostExecutionEnvironment, }) => {
|
|
1955
1868
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
1956
1869
|
const subagentTracker = createSubagentRunTracker({
|
|
1957
1870
|
parentType: 'ticket',
|
|
@@ -2049,7 +1962,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2049
1962
|
return hasResolvedOpencodeSessionId;
|
|
2050
1963
|
}
|
|
2051
1964
|
try {
|
|
2052
|
-
const session =
|
|
1965
|
+
const session = hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
2053
1966
|
if (session?.id) {
|
|
2054
1967
|
bindResolvedOpencodeSession(session.id);
|
|
2055
1968
|
}
|
|
@@ -2059,15 +1972,19 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2059
1972
|
}
|
|
2060
1973
|
return hasResolvedOpencodeSessionId;
|
|
2061
1974
|
};
|
|
2062
|
-
const
|
|
2063
|
-
?
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
:
|
|
1975
|
+
const agentProvider = tool === 'claude'
|
|
1976
|
+
? claudeAgentProvider
|
|
1977
|
+
: opencodeAgentProvider;
|
|
1978
|
+
const invocation = agentProvider.buildStreamingInvocation({
|
|
1979
|
+
cwd,
|
|
1980
|
+
prompt,
|
|
1981
|
+
model: resolvedModel,
|
|
1982
|
+
variant,
|
|
1983
|
+
mode: opencodeAgentMode,
|
|
1984
|
+
resumeSessionId,
|
|
1985
|
+
sessionId: randomUUID(),
|
|
1986
|
+
});
|
|
1987
|
+
sessionId = invocation.sessionId;
|
|
2071
1988
|
writeRunStatus('running', null);
|
|
2072
1989
|
if (tool === 'claude' && sessionId) {
|
|
2073
1990
|
writeRunSessionId(sessionId);
|
|
@@ -2076,6 +1993,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2076
1993
|
spawnTimestamp = Date.now();
|
|
2077
1994
|
}
|
|
2078
1995
|
let child;
|
|
1996
|
+
let interrupt;
|
|
1997
|
+
let placementFailure;
|
|
2079
1998
|
try {
|
|
2080
1999
|
if (buildState?.stopRequested) {
|
|
2081
2000
|
throw new AgentSpawnPermitRevokedError();
|
|
@@ -2083,28 +2002,37 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2083
2002
|
if (spawnPermitId && executionContext) {
|
|
2084
2003
|
assertAgentSpawnPermit(spawnPermitId, executionContext.runId);
|
|
2085
2004
|
}
|
|
2086
|
-
|
|
2005
|
+
const handle = executionProvider.execute({
|
|
2006
|
+
target: {
|
|
2007
|
+
kind: 'worktree',
|
|
2008
|
+
id: ticketId,
|
|
2009
|
+
workspacePath: cwd,
|
|
2010
|
+
revision: 'working-tree',
|
|
2011
|
+
},
|
|
2012
|
+
command: invocation.command,
|
|
2013
|
+
args: invocation.args,
|
|
2087
2014
|
cwd,
|
|
2088
|
-
stdio:
|
|
2015
|
+
stdio: invocation.stdio,
|
|
2089
2016
|
env: createWorkspaceProcessEnvironment(cwd, { PWD: cwd }),
|
|
2090
2017
|
detached: process.platform !== 'win32',
|
|
2091
2018
|
});
|
|
2019
|
+
child = handle.child;
|
|
2020
|
+
interrupt = handle.interrupt;
|
|
2021
|
+
placementFailure = handle.placementFailure;
|
|
2092
2022
|
}
|
|
2093
2023
|
catch (error) {
|
|
2094
|
-
const resolvedError =
|
|
2024
|
+
const resolvedError = agentProvider.classifyExecutionFailure(error);
|
|
2095
2025
|
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2096
2026
|
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2097
2027
|
return;
|
|
2098
2028
|
}
|
|
2099
2029
|
reviewChild = child;
|
|
2100
|
-
if (
|
|
2101
|
-
|
|
2102
|
-
? appendClaudeHeadlessPlanPrompt(prompt)
|
|
2103
|
-
: prompt;
|
|
2104
|
-
writeClaudeStdin(child, buildClaudeStdinMessage(claudePrompt, [], cwd));
|
|
2030
|
+
if (invocation.stdin !== null) {
|
|
2031
|
+
writeClaudeStdin(child, invocation.stdin);
|
|
2105
2032
|
}
|
|
2106
2033
|
if (trackBuildProcess && buildState) {
|
|
2107
2034
|
buildState.child = child;
|
|
2035
|
+
buildState.interrupt = interrupt;
|
|
2108
2036
|
activeBuildProcesses.set(ticketId, buildState);
|
|
2109
2037
|
}
|
|
2110
2038
|
if (reviewProcessState) {
|
|
@@ -2113,6 +2041,14 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2113
2041
|
killProcess(child, 'SIGTERM');
|
|
2114
2042
|
}
|
|
2115
2043
|
}
|
|
2044
|
+
void placementFailure.then(failure => {
|
|
2045
|
+
if (!failure || settled) {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const resolvedError = agentProvider.classifyExecutionFailure(failure);
|
|
2049
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2050
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2051
|
+
});
|
|
2116
2052
|
startTime = Date.now();
|
|
2117
2053
|
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
2118
2054
|
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
@@ -2207,8 +2143,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2207
2143
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null, usageSnapshot }, transcriptRuns));
|
|
2208
2144
|
return;
|
|
2209
2145
|
}
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2146
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2147
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2148
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2212
2149
|
});
|
|
2213
2150
|
return;
|
|
2214
2151
|
}
|
|
@@ -2321,8 +2258,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2321
2258
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null }, transcriptRuns));
|
|
2322
2259
|
return;
|
|
2323
2260
|
}
|
|
2324
|
-
const
|
|
2325
|
-
|
|
2261
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2262
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2263
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2326
2264
|
});
|
|
2327
2265
|
});
|
|
2328
2266
|
};
|
|
@@ -3127,6 +3065,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3127
3065
|
});
|
|
3128
3066
|
return new Promise((resolve, reject) => {
|
|
3129
3067
|
let spawnTimestamp = null;
|
|
3068
|
+
let opencodeSessionId = null;
|
|
3130
3069
|
let opencodeSubagentMonitor = null;
|
|
3131
3070
|
let settled = false;
|
|
3132
3071
|
let startTime = null;
|
|
@@ -3134,6 +3073,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3134
3073
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
3135
3074
|
const reviewState = registeredState ?? {
|
|
3136
3075
|
child: null,
|
|
3076
|
+
interrupt: null,
|
|
3137
3077
|
researchChildren: new Set(),
|
|
3138
3078
|
stopRequested: false,
|
|
3139
3079
|
metadataRefresher: null,
|
|
@@ -3149,21 +3089,56 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3149
3089
|
};
|
|
3150
3090
|
const resolveOnce = settle(resolve);
|
|
3151
3091
|
const rejectOnce = settle(reject);
|
|
3152
|
-
const [cmd, args] =
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
]
|
|
3157
|
-
: ['opencode', ['run', '--agent', 'plan', '--format', 'json', '--thinking', ...(resolvedModel ? ['--model', resolvedModel] : []), ...(variant ? ['--variant', variant] : []), ...files.flatMap(filePath => ['--file', filePath]), ...(files.length > 0 ? ['--'] : []), prompt]];
|
|
3092
|
+
const [cmd, args] = [
|
|
3093
|
+
'claude',
|
|
3094
|
+
buildClaudeArgs('plan', resolvedModel, null, randomUUID()),
|
|
3095
|
+
];
|
|
3158
3096
|
updateSpecReviewRunStreaming(specReviewRunId, null);
|
|
3159
|
-
const
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3097
|
+
const env = createWorkspaceProcessEnvironment(cwd, { PWD: cwd });
|
|
3098
|
+
let child;
|
|
3099
|
+
if (tool === 'opencode') {
|
|
3100
|
+
const invocation = buildOpencodeSpecReviewInvocation({ prompt, files, model: resolvedModel, variant });
|
|
3101
|
+
try {
|
|
3102
|
+
const handle = hostExecutionEnvironment.execute({
|
|
3103
|
+
target: {
|
|
3104
|
+
kind: 'repository',
|
|
3105
|
+
id: cwd,
|
|
3106
|
+
workspacePath: cwd,
|
|
3107
|
+
revision: 'working-tree',
|
|
3108
|
+
},
|
|
3109
|
+
command: invocation.command,
|
|
3110
|
+
args: invocation.args,
|
|
3111
|
+
cwd,
|
|
3112
|
+
stdio: invocation.stdio,
|
|
3113
|
+
env,
|
|
3114
|
+
detached: process.platform !== 'win32',
|
|
3115
|
+
});
|
|
3116
|
+
child = handle.child;
|
|
3117
|
+
reviewState.interrupt = handle.interrupt;
|
|
3118
|
+
void handle.placementFailure.then(failure => {
|
|
3119
|
+
if (failure) {
|
|
3120
|
+
rejectOnce(opencodeAgentProvider.classifyExecutionFailure(failure));
|
|
3121
|
+
}
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
catch (error) {
|
|
3125
|
+
rejectOnce(opencodeAgentProvider.classifyExecutionFailure(error));
|
|
3126
|
+
return;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
else {
|
|
3130
|
+
child = spawn(cmd, args, {
|
|
3131
|
+
cwd,
|
|
3132
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3133
|
+
env,
|
|
3134
|
+
detached: process.platform !== 'win32',
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3165
3137
|
reviewState.child = child;
|
|
3166
3138
|
activeSpecReviewProcesses.set(specReviewRunId, reviewState);
|
|
3139
|
+
if (reviewState.stopRequested) {
|
|
3140
|
+
interruptProcess(reviewState, 'SIGTERM');
|
|
3141
|
+
}
|
|
3167
3142
|
if (tool === 'claude') {
|
|
3168
3143
|
writeClaudeStdin(child, buildClaudeStdinMessage(appendClaudeHeadlessPlanPrompt(prompt), files, cwd));
|
|
3169
3144
|
}
|
|
@@ -3287,6 +3262,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3287
3262
|
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
3288
3263
|
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
3289
3264
|
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
3265
|
+
opencodeSessionId = providerSessionId ?? opencodeSessionId;
|
|
3290
3266
|
subagentTracker.handle(subagentEvents);
|
|
3291
3267
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3292
3268
|
firstFinalAnswerTime = null;
|
|
@@ -3304,6 +3280,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3304
3280
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
3305
3281
|
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
3306
3282
|
opencodeSubagentMonitor?.setParentSessionId(parsed.providerSessionId);
|
|
3283
|
+
opencodeSessionId = parsed.providerSessionId ?? opencodeSessionId;
|
|
3307
3284
|
subagentTracker.handle(parsed.subagentEvents);
|
|
3308
3285
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3309
3286
|
firstFinalAnswerTime = null;
|
|
@@ -3315,6 +3292,13 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3315
3292
|
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3316
3293
|
? recoveredResponse
|
|
3317
3294
|
: responseLines.join('\n\n') || stderrOutput;
|
|
3295
|
+
if (opencodeSessionId && spawnTimestamp !== null) {
|
|
3296
|
+
usageSnapshot = resolveOpencodeUsageSnapshot({
|
|
3297
|
+
current: usageSnapshot,
|
|
3298
|
+
sessionId: opencodeSessionId,
|
|
3299
|
+
spawnTimestamp,
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3318
3302
|
if (reviewState.stopRequested) {
|
|
3319
3303
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3320
3304
|
subagentTracker.finalize('stopped', log || null, finalResponse || null);
|
|
@@ -3344,8 +3328,25 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3344
3328
|
});
|
|
3345
3329
|
child.on('error', err => {
|
|
3346
3330
|
progressUpdater.cancel();
|
|
3347
|
-
|
|
3348
|
-
|
|
3331
|
+
if (reviewState.stopRequested) {
|
|
3332
|
+
const processError = getErrorMessage(err);
|
|
3333
|
+
const finalLog = logLines.join('\n\n') || processError;
|
|
3334
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3335
|
+
? recoveryTracker.getRecoveredResponse()
|
|
3336
|
+
: responseLines.join('\n\n') || null;
|
|
3337
|
+
updateSpecReviewRunStreaming(specReviewRunId, finalResponse);
|
|
3338
|
+
subagentTracker.finalize('stopped', finalLog || null, finalResponse);
|
|
3339
|
+
resolveOnce({
|
|
3340
|
+
completionStatus: 'stopped',
|
|
3341
|
+
finalResponse,
|
|
3342
|
+
usageSnapshot,
|
|
3343
|
+
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
3344
|
+
});
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
const failure = opencodeAgentProvider.classifyExecutionFailure(err);
|
|
3348
|
+
subagentTracker.finalize('error', failure.message, null);
|
|
3349
|
+
rejectOnce(failure);
|
|
3349
3350
|
});
|
|
3350
3351
|
});
|
|
3351
3352
|
};
|