@straiffi/archon 1.8.12 → 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-CEETnHri.js → ActivityView-DYDYyqIN.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-CjBW65OR.js → GitHubActionsDialog-jhi4GOyV.js} +1 -1
- package/dist/client/assets/{RepositoryGraphDialog-F3C0n89W.js → RepositoryGraphDialog-BZV0vHu6.js} +1 -1
- package/dist/client/assets/{VerificationDialog-DD1CLydA.js → VerificationDialog-Sc6e8AKH.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-COl_GdZ6.js → architectureDiagram-3BPJPVTR-DFbki8mi.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-qojIlH-q.js → blockDiagram-GPEHLZMM-BZFehG8P.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DFtG4fMg.js → c4Diagram-AAUBKEIU-FVDXRu1t.js} +1 -1
- package/dist/client/assets/channel-D1kOpezG.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-TJkOJXbn.js → chunk-2J33WTMH-4tuVZaT0.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-CELQ603z.js → chunk-3OPIFGDE-DSn14S35.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BgPEJ16b.js → chunk-5ZQYHXKU-Cwx1KNjg.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-Biph-aPf.js → chunk-727SXJPM-Odza4hes.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CPVToZH6.js → chunk-AQP2D5EJ-BmVoQT7M.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-BHO9oB4A.js → chunk-CSCIHK7Q-Bg7Ktx0B.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-B8vatrko.js → chunk-KSCS5N6A-2tnhar2x.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DvzBHR4-.js → chunk-L5ZTLDWV-dAyg8qMJ.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Bm8cj-JK.js → chunk-LZXEDZCA-0gElxrlR.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-BPQZvPl9.js → chunk-ND2GUHAM-Bu-8Lz91.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-DEQVA6Ya.js → chunk-NZK2D7GU-CInZFdzd.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-hPGqWilt.js → chunk-O5CBEL6O-rAzpvdNR.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-D0VvFXkQ.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-rmJlMuw_.js → dagre-BM42HDAG-Dgua1iuh.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BqejYr09.js → diagram-2AECGRRQ-odacwlwu.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DGSf1BNT.js → diagram-5GNKFQAL-BiKjFmJ1.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-e_TRdqvx.js → diagram-KO2AKTUF-CmYcvFri.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CTSnwgUq.js → diagram-LMA3HP47-ncx0RBuX.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-mUn9Y0aC.js → diagram-OG6HWLK6-DbchKIzw.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-TmdWUwfA.js → erDiagram-TEJ5UH35-CMnpvRyQ.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-CoE0KTIp.js → flowDiagram-I6XJVG4X-Bi02_PK5.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-FkX1d1D7.js → ganttDiagram-6RSMTGT7-BiS992US.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CXze3DV2.js → gitGraphDiagram-PVQCEYII-Cc1hrTsT.js} +1 -1
- package/dist/client/assets/index-B1gdJKwK.js +275 -0
- package/dist/client/assets/index-CCQwWUKF.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CO0CivOy.js → infoDiagram-5YYISTIA-BKVfqJCe.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-pEp8gScG.js → ishikawaDiagram-YF4QCWOH-B-IbFOty.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BAnBAX-u.js → journeyDiagram-JHISSGLW-DAZBgAQW.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Cqe-m5fR.js → kanban-definition-UN3LZRKU-Bkr_fXev.js} +1 -1
- package/dist/client/assets/{line-CKXoUXdX.js → line-CROuuXDO.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-7XZHbu9R.js → mermaid-parser.core-DrBw_Rsc.js} +1 -1
- package/dist/client/assets/{mermaid.core-CsF9gbdO.js → mermaid.core-pPd6OYYj.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-88fK1Uq_.js → mindmap-definition-RKZ34NQL-BXSd3TZU.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DHFlSAwL.js → pieDiagram-4H26LBE5-DoT7PIZC.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-D5WyQ5EU.js → quadrantDiagram-W4KKPZXB-ycZkLowm.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DGkMfp_N.js → requirementDiagram-4Y6WPE33-BPkO1yLu.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-loJAHDiT.js → sankeyDiagram-5OEKKPKP-D7AvhhCw.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-xgcwtjyW.js → sequenceDiagram-3UESZ5HK-C982swnH.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-BB95DmQX.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-OCHTEVfL.js → timeline-definition-PNZ67QCA-D89qjzb5.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-Bo_7YNy2.js → vennDiagram-CIIHVFJN-CoKeC__u.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DquywRz0.js → wardleyDiagram-YWT4CUSO-DKhjzvSE.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BLNrbCwd.js → xychartDiagram-2RQKCTM6-DFn9ass1.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +77 -2
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +90 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +205 -242
- 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/automationReconciliation.js +20 -0
- package/dist/server/lib/automationReconciliation.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +6 -5
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/buildCompletionContinuation.js +1 -1
- package/dist/server/lib/buildCompletionContinuation.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/executionReconciliation.js +43 -4
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +16 -9
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRetryPredicates.js +12 -3
- package/dist/server/lib/executionRetryPredicates.js.map +1 -1
- package/dist/server/lib/git.js +4 -4
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/hostExecutionEnvironment.js +77 -0
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -0
- package/dist/server/lib/lightweightPrompt.js +17 -54
- 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/opencodeErrors.js +35 -0
- package/dist/server/lib/opencodeErrors.js.map +1 -0
- package/dist/server/lib/opencodePromptOutput.js +112 -0
- package/dist/server/lib/opencodePromptOutput.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/postBuildCheckRuns.js +16 -12
- package/dist/server/lib/postBuildCheckRuns.js.map +1 -1
- package/dist/server/lib/postBuildChecks.js +45 -43
- package/dist/server/lib/postBuildChecks.js.map +1 -1
- package/dist/server/lib/previewProxy.js +36 -4
- package/dist/server/lib/previewProxy.js.map +1 -1
- package/dist/server/lib/projectAutoConfig.js +27 -97
- package/dist/server/lib/projectAutoConfig.js.map +1 -1
- package/dist/server/lib/projects.js +34 -6
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/reviewContext.js +10 -5
- package/dist/server/lib/reviewContext.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +201 -27
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/reviewerConfig.js +23 -0
- package/dist/server/lib/reviewerConfig.js.map +1 -0
- package/dist/server/lib/specReviewOperations.js +64 -9
- package/dist/server/lib/specReviewOperations.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +28 -0
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/tickets.js +15 -0
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +8 -1
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +15 -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 +176 -70
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +26 -11
- package/dist/server/workers/specReview.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-DJLjk5--.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-aHXCyXE_.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CRPsGis_.js +0 -1
- package/dist/client/assets/index-B9S66NLn.css +0 -2
- package/dist/client/assets/index-CHsj9z-M.js +0 -275
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DKhxPOLT.js +0 -1
package/dist/server/lib/agent.js
CHANGED
|
@@ -2,56 +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';
|
|
22
|
+
import { OpencodeModelResolutionError, parseOpencodeStderrModelResolutionError, } from './opencodeErrors.js';
|
|
23
|
+
import { extractLeadingOpencodeJsonObjects } from './opencodePromptOutput.js';
|
|
20
24
|
import { assertAgentSpawnPermit, AgentSpawnPermitRevokedError } from './agentSpawnPermits.js';
|
|
21
25
|
const withTranscriptRuns = (value, transcriptRuns) => {
|
|
22
26
|
return transcriptRuns ? { ...value, transcriptRuns } : value;
|
|
23
27
|
};
|
|
24
|
-
const createOpencodeRecoveryTracker = () => {
|
|
25
|
-
let sawError = false;
|
|
26
|
-
let latestEvent = null;
|
|
27
|
-
let recoveredFinalAnswerFragments = [];
|
|
28
|
-
return {
|
|
29
|
-
consume: events => {
|
|
30
|
-
let shouldResetResponse = false;
|
|
31
|
-
for (const event of events ?? []) {
|
|
32
|
-
latestEvent = event.type;
|
|
33
|
-
if (event.type === 'error') {
|
|
34
|
-
sawError = true;
|
|
35
|
-
recoveredFinalAnswerFragments = [];
|
|
36
|
-
shouldResetResponse = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (sawError) {
|
|
40
|
-
recoveredFinalAnswerFragments.push(event.text);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return shouldResetResponse;
|
|
44
|
-
},
|
|
45
|
-
hasRecoveredFinalAnswer: () => (sawError
|
|
46
|
-
&& latestEvent === 'final_answer'
|
|
47
|
-
&& recoveredFinalAnswerFragments.length > 0),
|
|
48
|
-
hasUnrecoveredError: () => latestEvent === 'error',
|
|
49
|
-
getRecoveredResponse: () => recoveredFinalAnswerFragments.join('\n\n'),
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
const isSuccessfulOpencodeExit = (code, modelError, recoveryTracker) => (!modelError
|
|
53
|
-
&& !recoveryTracker.hasUnrecoveredError()
|
|
54
|
-
&& (code === 0 || recoveryTracker.hasRecoveredFinalAnswer()));
|
|
55
28
|
export class AgentContinuationRejectedError extends Error {
|
|
56
29
|
constructor(message) {
|
|
57
30
|
super(message);
|
|
@@ -74,7 +47,6 @@ export const getAgentErrorTranscriptRuns = (error) => {
|
|
|
74
47
|
const transcriptRuns = error.transcriptRuns;
|
|
75
48
|
return Array.isArray(transcriptRuns) && transcriptRuns.length > 0 ? transcriptRuns : null;
|
|
76
49
|
};
|
|
77
|
-
const OPENCODE_PLACEHOLDER_TITLE_PATTERN = /^New session\b/i;
|
|
78
50
|
const OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS = 1500;
|
|
79
51
|
const AGENT_PROGRESS_UPDATE_INTERVAL_MS = 100;
|
|
80
52
|
const resolveAgentModel = (tool, model) => {
|
|
@@ -140,13 +112,6 @@ const buildClaudeArgs = (mode, model, resumeSessionId, sessionId) => {
|
|
|
140
112
|
sessionId,
|
|
141
113
|
];
|
|
142
114
|
};
|
|
143
|
-
const OPENCODE_SESSION_BY_ID_QUERY = 'SELECT id, title FROM session WHERE id = ? LIMIT 1';
|
|
144
|
-
const OPENCODE_SESSION_BY_CREATED_AT_QUERY = 'SELECT id, title FROM session WHERE time_created > ? ORDER BY time_created DESC LIMIT 1';
|
|
145
|
-
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";
|
|
146
|
-
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";
|
|
147
|
-
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";
|
|
148
|
-
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";
|
|
149
|
-
const OPENCODE_SESSION_COST_QUERY = 'SELECT cost FROM session WHERE id = ?';
|
|
150
115
|
const GET_PERSISTED_CHAT_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM chat_sessions WHERE id = ?';
|
|
151
116
|
const GET_PERSISTED_SPEC_STATE_QUERY = 'SELECT agent_log, streaming_response, opencode_session_id FROM spec_sessions WHERE id = ?';
|
|
152
117
|
export const EMPTY_CHAT_USAGE_SNAPSHOT = {
|
|
@@ -284,7 +249,7 @@ export const mergeUsageFromJsonLine = (current, line) => {
|
|
|
284
249
|
if (!trimmedLine.startsWith('{')) {
|
|
285
250
|
return current;
|
|
286
251
|
}
|
|
287
|
-
const { jsonObjects } =
|
|
252
|
+
const { jsonObjects } = extractLeadingOpencodeJsonObjects(trimmedLine);
|
|
288
253
|
let next = current;
|
|
289
254
|
for (const jsonObject of jsonObjects) {
|
|
290
255
|
try {
|
|
@@ -310,9 +275,6 @@ export const mergeUsageFromJsonLine = (current, line) => {
|
|
|
310
275
|
}
|
|
311
276
|
return next;
|
|
312
277
|
};
|
|
313
|
-
const hasUsageSnapshot = (value) => {
|
|
314
|
-
return Object.values(value).some(candidate => candidate !== null && candidate !== undefined);
|
|
315
|
-
};
|
|
316
278
|
const hasMatchingUsageSnapshot = (current, next) => {
|
|
317
279
|
return current.usage_snapshot_context_window === next.usage_snapshot_context_window
|
|
318
280
|
&& current.usage_snapshot_max_output_tokens === next.usage_snapshot_max_output_tokens
|
|
@@ -483,67 +445,12 @@ const createThrottledProgressUpdater = (flushUpdate) => {
|
|
|
483
445
|
},
|
|
484
446
|
};
|
|
485
447
|
};
|
|
486
|
-
const openOpencodeDatabase = () => new Database(resolveOpencodeDbPath(), { readonly: true });
|
|
487
|
-
const readOpencodeSession = (statement, value) => {
|
|
488
|
-
const opencodeDb = openOpencodeDatabase();
|
|
489
|
-
try {
|
|
490
|
-
const row = opencodeDb.prepare(statement).get(value);
|
|
491
|
-
if (!row?.id) {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
const normalizedTitle = typeof row.title === 'string'
|
|
495
|
-
? row.title.trim() || null
|
|
496
|
-
: null;
|
|
497
|
-
const title = normalizedTitle && !OPENCODE_PLACEHOLDER_TITLE_PATTERN.test(normalizedTitle)
|
|
498
|
-
? normalizedTitle
|
|
499
|
-
: null;
|
|
500
|
-
return {
|
|
501
|
-
id: row.id,
|
|
502
|
-
title,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
finally {
|
|
506
|
-
opencodeDb.close();
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
448
|
export const readOpencodeUsageSnapshot = ({ sessionId, spawnTimestamp = null, }) => {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const sessionCost = typeof costRow?.cost === 'number' ? { usage_snapshot_cost: costRow.cost } : {};
|
|
514
|
-
const completedAssistantMessage = (spawnTimestamp === null
|
|
515
|
-
? opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_QUERY).get(sessionId)
|
|
516
|
-
: opencodeDb.prepare(OPENCODE_COMPLETED_ASSISTANT_MESSAGE_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
517
|
-
if (typeof completedAssistantMessage?.data === 'string') {
|
|
518
|
-
try {
|
|
519
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(completedAssistantMessage.data));
|
|
520
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
521
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
catch {
|
|
525
|
-
// Fall through to the step-finish lookup below.
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
const latestStepFinish = (spawnTimestamp === null
|
|
529
|
-
? opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_QUERY).get(sessionId)
|
|
530
|
-
: opencodeDb.prepare(OPENCODE_STEP_FINISH_USAGE_SINCE_QUERY).get(sessionId, spawnTimestamp));
|
|
531
|
-
if (typeof latestStepFinish?.data === 'string') {
|
|
532
|
-
try {
|
|
533
|
-
const parsedUsage = extractChatUsageSnapshot(JSON.parse(latestStepFinish.data));
|
|
534
|
-
if (hasUsageSnapshot(parsedUsage)) {
|
|
535
|
-
return mergeChatUsageSnapshot(mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, parsedUsage), sessionCost);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
catch {
|
|
539
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return mergeChatUsageSnapshot(EMPTY_CHAT_USAGE_SNAPSHOT, sessionCost);
|
|
543
|
-
}
|
|
544
|
-
finally {
|
|
545
|
-
opencodeDb.close();
|
|
449
|
+
const snapshot = hostOpencodeSessionOperations.readUsageSnapshot(sessionId, spawnTimestamp);
|
|
450
|
+
if (!snapshot) {
|
|
451
|
+
throw new Error('OpenCode usage snapshot read failed');
|
|
546
452
|
}
|
|
453
|
+
return snapshot;
|
|
547
454
|
};
|
|
548
455
|
const resolveOpencodeUsageSnapshot = ({ current, sessionId, spawnTimestamp = null, }) => {
|
|
549
456
|
try {
|
|
@@ -638,12 +545,12 @@ const createOpencodeMetadataRefresher = ({ chatSessionId, io, spawnTimestamp, in
|
|
|
638
545
|
};
|
|
639
546
|
const readCurrentSession = () => {
|
|
640
547
|
if (knownSessionId) {
|
|
641
|
-
return
|
|
548
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
642
549
|
}
|
|
643
550
|
if (spawnTimestamp === null) {
|
|
644
551
|
return null;
|
|
645
552
|
}
|
|
646
|
-
return
|
|
553
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
647
554
|
};
|
|
648
555
|
const refresh = () => {
|
|
649
556
|
if (stopped) {
|
|
@@ -706,12 +613,12 @@ const createSpecOpencodeMetadataRefresher = ({ specSessionId, io, spawnTimestamp
|
|
|
706
613
|
};
|
|
707
614
|
const readCurrentSession = () => {
|
|
708
615
|
if (knownSessionId) {
|
|
709
|
-
return
|
|
616
|
+
return hostOpencodeSessionOperations.findSessionById(knownSessionId);
|
|
710
617
|
}
|
|
711
618
|
if (spawnTimestamp === null) {
|
|
712
619
|
return null;
|
|
713
620
|
}
|
|
714
|
-
return
|
|
621
|
+
return hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
715
622
|
};
|
|
716
623
|
const refresh = () => {
|
|
717
624
|
if (stopped) {
|
|
@@ -956,6 +863,7 @@ const emptyParsedStreamLine = () => ({
|
|
|
956
863
|
responseFragment: null,
|
|
957
864
|
subagentEvents: [],
|
|
958
865
|
providerSessionId: null,
|
|
866
|
+
modelResolutionError: false,
|
|
959
867
|
});
|
|
960
868
|
const isExecutionCapableParsedStreamLine = (parsed) => (parsed.logFragment !== null
|
|
961
869
|
|| parsed.responseFragment !== null
|
|
@@ -1067,62 +975,6 @@ const formatOpencodeStepTitle = (event) => {
|
|
|
1067
975
|
const title = event.part?.title?.trim();
|
|
1068
976
|
return title || null;
|
|
1069
977
|
};
|
|
1070
|
-
const extractLeadingJsonObject = (line) => {
|
|
1071
|
-
if (!line.startsWith('{')) {
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
let depth = 0;
|
|
1075
|
-
let inString = false;
|
|
1076
|
-
let escaping = false;
|
|
1077
|
-
for (let index = 0; index < line.length; index += 1) {
|
|
1078
|
-
const char = line[index];
|
|
1079
|
-
if (inString) {
|
|
1080
|
-
if (escaping) {
|
|
1081
|
-
escaping = false;
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
|
-
if (char === '\\') {
|
|
1085
|
-
escaping = true;
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
if (char === '"') {
|
|
1089
|
-
inString = false;
|
|
1090
|
-
}
|
|
1091
|
-
continue;
|
|
1092
|
-
}
|
|
1093
|
-
if (char === '"') {
|
|
1094
|
-
inString = true;
|
|
1095
|
-
continue;
|
|
1096
|
-
}
|
|
1097
|
-
if (char === '{') {
|
|
1098
|
-
depth += 1;
|
|
1099
|
-
continue;
|
|
1100
|
-
}
|
|
1101
|
-
if (char === '}') {
|
|
1102
|
-
depth -= 1;
|
|
1103
|
-
if (depth === 0) {
|
|
1104
|
-
return line.slice(0, index + 1);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
return null;
|
|
1109
|
-
};
|
|
1110
|
-
const extractLeadingJsonObjects = (line) => {
|
|
1111
|
-
const jsonObjects = [];
|
|
1112
|
-
let remaining = line.trim();
|
|
1113
|
-
while (remaining.startsWith('{')) {
|
|
1114
|
-
const jsonObject = extractLeadingJsonObject(remaining);
|
|
1115
|
-
if (!jsonObject) {
|
|
1116
|
-
break;
|
|
1117
|
-
}
|
|
1118
|
-
jsonObjects.push(jsonObject);
|
|
1119
|
-
remaining = remaining.slice(jsonObject.length).trim();
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
jsonObjects,
|
|
1123
|
-
remainder: remaining,
|
|
1124
|
-
};
|
|
1125
|
-
};
|
|
1126
978
|
const parseOpencodeJsonEvent = (event, cwd) => {
|
|
1127
979
|
const providerSessionId = event.sessionID?.trim()
|
|
1128
980
|
|| event.sessionId?.trim()
|
|
@@ -1132,14 +984,14 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1132
984
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
1133
985
|
const text = event.part.text.trim();
|
|
1134
986
|
const phase = event.part.metadata?.openai?.phase;
|
|
987
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, text, phase);
|
|
1135
988
|
return {
|
|
1136
989
|
logFragment: text || null,
|
|
1137
990
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
1138
991
|
subagentEvents: [],
|
|
1139
992
|
providerSessionId,
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
: [],
|
|
993
|
+
modelResolutionError: false,
|
|
994
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1143
995
|
};
|
|
1144
996
|
}
|
|
1145
997
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -1149,6 +1001,7 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1149
1001
|
responseFragment: null,
|
|
1150
1002
|
subagentEvents: [],
|
|
1151
1003
|
providerSessionId,
|
|
1004
|
+
modelResolutionError: false,
|
|
1152
1005
|
};
|
|
1153
1006
|
}
|
|
1154
1007
|
if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
|
|
@@ -1188,6 +1041,7 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1188
1041
|
: []),
|
|
1189
1042
|
],
|
|
1190
1043
|
providerSessionId,
|
|
1044
|
+
modelResolutionError: false,
|
|
1191
1045
|
};
|
|
1192
1046
|
}
|
|
1193
1047
|
if (!isToolInvocation) {
|
|
@@ -1199,6 +1053,7 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1199
1053
|
responseFragment: null,
|
|
1200
1054
|
subagentEvents: [],
|
|
1201
1055
|
providerSessionId,
|
|
1056
|
+
modelResolutionError: false,
|
|
1202
1057
|
};
|
|
1203
1058
|
}
|
|
1204
1059
|
if (event.type === 'step_start') {
|
|
@@ -1207,6 +1062,7 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1207
1062
|
responseFragment: null,
|
|
1208
1063
|
subagentEvents: [],
|
|
1209
1064
|
providerSessionId,
|
|
1065
|
+
modelResolutionError: false,
|
|
1210
1066
|
};
|
|
1211
1067
|
}
|
|
1212
1068
|
if (event.type === 'step_finish') {
|
|
@@ -1215,23 +1071,19 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1215
1071
|
responseFragment: null,
|
|
1216
1072
|
subagentEvents: [],
|
|
1217
1073
|
providerSessionId,
|
|
1074
|
+
modelResolutionError: false,
|
|
1218
1075
|
};
|
|
1219
1076
|
}
|
|
1220
1077
|
if (event.type === 'error') {
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
? (event.error.data?.message?.trim()
|
|
1225
|
-
|| event.error.message?.trim()
|
|
1226
|
-
|| JSON.stringify(event.error))
|
|
1227
|
-
: '';
|
|
1228
|
-
const text = event.part?.text?.trim() || errorText || '';
|
|
1078
|
+
const recoveryEvent = interpretOpencodeRecoveryEvent(event.type, event.error, event.part?.text, undefined);
|
|
1079
|
+
const parsedError = recoveryEvent?.type === 'error' ? recoveryEvent.error : null;
|
|
1080
|
+
const text = parsedError?.message ?? '';
|
|
1229
1081
|
return {
|
|
1230
1082
|
logFragment: text ? `**Error:** ${text}` : null,
|
|
1231
1083
|
responseFragment: null,
|
|
1232
1084
|
subagentEvents: [],
|
|
1233
1085
|
providerSessionId,
|
|
1234
|
-
recoveryEvents: [
|
|
1086
|
+
recoveryEvents: recoveryEvent ? [recoveryEvent] : [],
|
|
1235
1087
|
};
|
|
1236
1088
|
}
|
|
1237
1089
|
return emptyParsedStreamLine();
|
|
@@ -1241,7 +1093,7 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1241
1093
|
if (!trimmed) {
|
|
1242
1094
|
return emptyParsedStreamLine();
|
|
1243
1095
|
}
|
|
1244
|
-
const { jsonObjects, remainder } =
|
|
1096
|
+
const { jsonObjects, remainder } = extractLeadingOpencodeJsonObjects(trimmed);
|
|
1245
1097
|
if (jsonObjects.length === 0) {
|
|
1246
1098
|
if (trimmed.startsWith('{')) {
|
|
1247
1099
|
return emptyParsedStreamLine();
|
|
@@ -1259,13 +1111,14 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1259
1111
|
}],
|
|
1260
1112
|
};
|
|
1261
1113
|
}
|
|
1262
|
-
return { logFragment: trimmed, responseFragment: trimmed, subagentEvents: [], providerSessionId: null };
|
|
1114
|
+
return { logFragment: trimmed, responseFragment: trimmed, subagentEvents: [], providerSessionId: null, modelResolutionError: false };
|
|
1263
1115
|
}
|
|
1264
1116
|
const logFragments = [];
|
|
1265
1117
|
const responseFragments = [];
|
|
1266
1118
|
const subagentEvents = [];
|
|
1267
1119
|
const recoveryEvents = [];
|
|
1268
1120
|
let providerSessionId = null;
|
|
1121
|
+
let modelResolutionError = false;
|
|
1269
1122
|
for (const jsonObject of jsonObjects) {
|
|
1270
1123
|
let event;
|
|
1271
1124
|
try {
|
|
@@ -1278,6 +1131,7 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1278
1131
|
const { logFragment, responseFragment } = parsedEvent;
|
|
1279
1132
|
subagentEvents.push(...parsedEvent.subagentEvents);
|
|
1280
1133
|
recoveryEvents.push(...(parsedEvent.recoveryEvents ?? []));
|
|
1134
|
+
modelResolutionError ||= Boolean(parsedEvent.modelResolutionError);
|
|
1281
1135
|
providerSessionId = providerSessionId ?? parsedEvent.providerSessionId;
|
|
1282
1136
|
if (logFragment !== null) {
|
|
1283
1137
|
logFragments.push(logFragment);
|
|
@@ -1308,6 +1162,7 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1308
1162
|
responseFragment: responseFragments.length > 0 ? responseFragments.join('\n\n') : null,
|
|
1309
1163
|
subagentEvents,
|
|
1310
1164
|
providerSessionId,
|
|
1165
|
+
modelResolutionError,
|
|
1311
1166
|
recoveryEvents,
|
|
1312
1167
|
};
|
|
1313
1168
|
}
|
|
@@ -1731,12 +1586,7 @@ const buildResponseTiming = ({ startTime, firstFinalAnswerTime, isStopped, }) =>
|
|
|
1731
1586
|
const responseMs = Date.now() - firstFinalAnswerTime;
|
|
1732
1587
|
return { totalMs, thinkingMs, responseMs };
|
|
1733
1588
|
};
|
|
1734
|
-
const
|
|
1735
|
-
if (typeof message !== 'string') {
|
|
1736
|
-
return false;
|
|
1737
|
-
}
|
|
1738
|
-
return /ProviderModelNotFoundError|Model not found/i.test(message);
|
|
1739
|
-
};
|
|
1589
|
+
const isLegacyModelResolutionError = (message) => (typeof message === 'string' && /ProviderModelNotFoundError|Model not found/i.test(message));
|
|
1740
1590
|
const isProcessAlive = (proc) => {
|
|
1741
1591
|
if (!proc) {
|
|
1742
1592
|
return false;
|
|
@@ -1764,6 +1614,16 @@ const killProcess = (proc, signal) => {
|
|
|
1764
1614
|
catch {
|
|
1765
1615
|
}
|
|
1766
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
|
+
};
|
|
1767
1627
|
const waitForProcessExit = async (proc, timeoutMs) => {
|
|
1768
1628
|
const deadline = Date.now() + timeoutMs;
|
|
1769
1629
|
await new Promise(resolve => {
|
|
@@ -1797,6 +1657,7 @@ const untrackChatProcess = (chatSessionId, state) => {
|
|
|
1797
1657
|
export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
1798
1658
|
const state = {
|
|
1799
1659
|
child: null,
|
|
1660
|
+
interrupt: null,
|
|
1800
1661
|
researchChildren: new Set(),
|
|
1801
1662
|
stopRequested: false,
|
|
1802
1663
|
metadataRefresher: null,
|
|
@@ -1805,7 +1666,7 @@ export const registerChatProcessPlaceholder = (chatSessionId) => {
|
|
|
1805
1666
|
return state;
|
|
1806
1667
|
};
|
|
1807
1668
|
export const registerBuildProcessPlaceholder = (ticketId) => {
|
|
1808
|
-
const state = { child: null, stopRequested: false, sessionLookupTimer: null };
|
|
1669
|
+
const state = { child: null, interrupt: null, stopRequested: false, sessionLookupTimer: null };
|
|
1809
1670
|
activeBuildProcesses.set(ticketId, state);
|
|
1810
1671
|
return state;
|
|
1811
1672
|
};
|
|
@@ -1836,6 +1697,7 @@ const untrackSpecProcess = (specSessionId, state) => {
|
|
|
1836
1697
|
export const registerSpecProcessPlaceholder = (specSessionId) => {
|
|
1837
1698
|
const state = {
|
|
1838
1699
|
child: null,
|
|
1700
|
+
interrupt: null,
|
|
1839
1701
|
researchChildren: new Set(),
|
|
1840
1702
|
stopRequested: false,
|
|
1841
1703
|
metadataRefresher: null,
|
|
@@ -1859,6 +1721,7 @@ const untrackSpecReviewProcess = (specReviewRunId, state) => {
|
|
|
1859
1721
|
export const registerSpecReviewProcessPlaceholder = (specReviewRunId) => {
|
|
1860
1722
|
const state = {
|
|
1861
1723
|
child: null,
|
|
1724
|
+
interrupt: null,
|
|
1862
1725
|
researchChildren: new Set(),
|
|
1863
1726
|
stopRequested: false,
|
|
1864
1727
|
metadataRefresher: null,
|
|
@@ -1904,10 +1767,20 @@ export const stopBuildAgent = async (ticketId) => {
|
|
|
1904
1767
|
if (!state.child) {
|
|
1905
1768
|
return true;
|
|
1906
1769
|
}
|
|
1907
|
-
|
|
1770
|
+
if (state.interrupt) {
|
|
1771
|
+
state.interrupt('SIGTERM');
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
killProcess(state.child, 'SIGTERM');
|
|
1775
|
+
}
|
|
1908
1776
|
await waitForProcessExit(state.child, 5000);
|
|
1909
1777
|
if (isProcessAlive(state.child)) {
|
|
1910
|
-
|
|
1778
|
+
if (state.interrupt) {
|
|
1779
|
+
state.interrupt('SIGKILL');
|
|
1780
|
+
}
|
|
1781
|
+
else {
|
|
1782
|
+
killProcess(state.child, 'SIGKILL');
|
|
1783
|
+
}
|
|
1911
1784
|
await waitForProcessExit(state.child, 1000);
|
|
1912
1785
|
}
|
|
1913
1786
|
return true;
|
|
@@ -1983,15 +1856,15 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
|
1983
1856
|
if (!state.child) {
|
|
1984
1857
|
return true;
|
|
1985
1858
|
}
|
|
1986
|
-
|
|
1859
|
+
interruptProcess(state, 'SIGTERM');
|
|
1987
1860
|
await waitForProcessExit(state.child, 5000);
|
|
1988
1861
|
if (isProcessAlive(state.child)) {
|
|
1989
|
-
|
|
1862
|
+
interruptProcess(state, 'SIGKILL');
|
|
1990
1863
|
await waitForProcessExit(state.child, 1000);
|
|
1991
1864
|
}
|
|
1992
1865
|
return true;
|
|
1993
1866
|
};
|
|
1994
|
-
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, }) => {
|
|
1995
1868
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
1996
1869
|
const subagentTracker = createSubagentRunTracker({
|
|
1997
1870
|
parentType: 'ticket',
|
|
@@ -2089,7 +1962,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2089
1962
|
return hasResolvedOpencodeSessionId;
|
|
2090
1963
|
}
|
|
2091
1964
|
try {
|
|
2092
|
-
const session =
|
|
1965
|
+
const session = hostOpencodeSessionOperations.findLatestSessionAfter(spawnTimestamp);
|
|
2093
1966
|
if (session?.id) {
|
|
2094
1967
|
bindResolvedOpencodeSession(session.id);
|
|
2095
1968
|
}
|
|
@@ -2099,15 +1972,19 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2099
1972
|
}
|
|
2100
1973
|
return hasResolvedOpencodeSessionId;
|
|
2101
1974
|
};
|
|
2102
|
-
const
|
|
2103
|
-
?
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
:
|
|
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;
|
|
2111
1988
|
writeRunStatus('running', null);
|
|
2112
1989
|
if (tool === 'claude' && sessionId) {
|
|
2113
1990
|
writeRunSessionId(sessionId);
|
|
@@ -2116,6 +1993,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2116
1993
|
spawnTimestamp = Date.now();
|
|
2117
1994
|
}
|
|
2118
1995
|
let child;
|
|
1996
|
+
let interrupt;
|
|
1997
|
+
let placementFailure;
|
|
2119
1998
|
try {
|
|
2120
1999
|
if (buildState?.stopRequested) {
|
|
2121
2000
|
throw new AgentSpawnPermitRevokedError();
|
|
@@ -2123,28 +2002,37 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2123
2002
|
if (spawnPermitId && executionContext) {
|
|
2124
2003
|
assertAgentSpawnPermit(spawnPermitId, executionContext.runId);
|
|
2125
2004
|
}
|
|
2126
|
-
|
|
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,
|
|
2127
2014
|
cwd,
|
|
2128
|
-
stdio:
|
|
2015
|
+
stdio: invocation.stdio,
|
|
2129
2016
|
env: createWorkspaceProcessEnvironment(cwd, { PWD: cwd }),
|
|
2130
2017
|
detached: process.platform !== 'win32',
|
|
2131
2018
|
});
|
|
2019
|
+
child = handle.child;
|
|
2020
|
+
interrupt = handle.interrupt;
|
|
2021
|
+
placementFailure = handle.placementFailure;
|
|
2132
2022
|
}
|
|
2133
2023
|
catch (error) {
|
|
2134
|
-
const resolvedError =
|
|
2024
|
+
const resolvedError = agentProvider.classifyExecutionFailure(error);
|
|
2135
2025
|
const transcriptRuns = subagentTracker.finalize('error', resolvedError.message, null);
|
|
2136
2026
|
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2137
2027
|
return;
|
|
2138
2028
|
}
|
|
2139
2029
|
reviewChild = child;
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
2142
|
-
? appendClaudeHeadlessPlanPrompt(prompt)
|
|
2143
|
-
: prompt;
|
|
2144
|
-
writeClaudeStdin(child, buildClaudeStdinMessage(claudePrompt, [], cwd));
|
|
2030
|
+
if (invocation.stdin !== null) {
|
|
2031
|
+
writeClaudeStdin(child, invocation.stdin);
|
|
2145
2032
|
}
|
|
2146
2033
|
if (trackBuildProcess && buildState) {
|
|
2147
2034
|
buildState.child = child;
|
|
2035
|
+
buildState.interrupt = interrupt;
|
|
2148
2036
|
activeBuildProcesses.set(ticketId, buildState);
|
|
2149
2037
|
}
|
|
2150
2038
|
if (reviewProcessState) {
|
|
@@ -2153,6 +2041,14 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2153
2041
|
killProcess(child, 'SIGTERM');
|
|
2154
2042
|
}
|
|
2155
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
|
+
});
|
|
2156
2052
|
startTime = Date.now();
|
|
2157
2053
|
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
2158
2054
|
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
@@ -2221,7 +2117,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2221
2117
|
const errOutput = Buffer.concat(stderrChunks).toString();
|
|
2222
2118
|
const finalLog = logLines.join('\n\n') || errOutput;
|
|
2223
2119
|
const finalResponse = responseLines.join('\n\n') || errOutput;
|
|
2224
|
-
const modelError =
|
|
2120
|
+
const modelError = isLegacyModelResolutionError(errOutput);
|
|
2225
2121
|
const timing = startTime ? { totalMs: Date.now() - startTime } : null;
|
|
2226
2122
|
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2227
2123
|
const transcriptRuns = finalizeRun('stopped', finalLog, finalResponse);
|
|
@@ -2247,8 +2143,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2247
2143
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null, usageSnapshot }, transcriptRuns));
|
|
2248
2144
|
return;
|
|
2249
2145
|
}
|
|
2250
|
-
const
|
|
2251
|
-
|
|
2146
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2147
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2148
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2252
2149
|
});
|
|
2253
2150
|
return;
|
|
2254
2151
|
}
|
|
@@ -2331,14 +2228,14 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2331
2228
|
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
2332
2229
|
? recoveredResponse
|
|
2333
2230
|
: responseLines.join('\n\n') || stderrOutput;
|
|
2334
|
-
const
|
|
2231
|
+
const stderrModelError = parseOpencodeStderrModelResolutionError(stderrOutput);
|
|
2335
2232
|
const timing = buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: Boolean(buildState?.stopRequested || reviewProcessState?.stopRequested) });
|
|
2336
2233
|
if (buildState?.stopRequested || reviewProcessState?.stopRequested) {
|
|
2337
2234
|
const transcriptRuns = finalizeRun('stopped', log, finalResponse);
|
|
2338
2235
|
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing }, transcriptRuns));
|
|
2339
2236
|
return;
|
|
2340
2237
|
}
|
|
2341
|
-
if (isSuccessfulOpencodeExit(code,
|
|
2238
|
+
if (isSuccessfulOpencodeExit(code, stderrModelError, recoveryTracker)) {
|
|
2342
2239
|
const transcriptRuns = finalizeRun('done', log, finalResponse, { emit: emitSuccessUpdate });
|
|
2343
2240
|
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing }, transcriptRuns));
|
|
2344
2241
|
return;
|
|
@@ -2346,7 +2243,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2346
2243
|
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
2347
2244
|
const error = resumeSessionId && !executionCapableEventObserved && isTypedPreExecutionContinuationRejection(failureMessage)
|
|
2348
2245
|
? new AgentContinuationRejectedError(failureMessage)
|
|
2349
|
-
:
|
|
2246
|
+
: createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
2350
2247
|
const transcriptRuns = finalizeRun('error', log, finalResponse);
|
|
2351
2248
|
rejectOnce(attachTranscriptRunsToError(error, transcriptRuns));
|
|
2352
2249
|
});
|
|
@@ -2361,8 +2258,9 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
2361
2258
|
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null }, transcriptRuns));
|
|
2362
2259
|
return;
|
|
2363
2260
|
}
|
|
2364
|
-
const
|
|
2365
|
-
|
|
2261
|
+
const resolvedError = agentProvider.classifyExecutionFailure(err);
|
|
2262
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(resolvedError), null);
|
|
2263
|
+
rejectOnce(attachTranscriptRunsToError(resolvedError, transcriptRuns));
|
|
2366
2264
|
});
|
|
2367
2265
|
});
|
|
2368
2266
|
};
|
|
@@ -2555,7 +2453,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2555
2453
|
}, transcriptRuns));
|
|
2556
2454
|
return;
|
|
2557
2455
|
}
|
|
2558
|
-
const modelError =
|
|
2456
|
+
const modelError = isLegacyModelResolutionError(errOutput);
|
|
2559
2457
|
if (code === 0 && !modelError) {
|
|
2560
2458
|
const finalOutput = getPreservedChatOutput({
|
|
2561
2459
|
chatSessionId,
|
|
@@ -2709,8 +2607,8 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2709
2607
|
}, transcriptRuns));
|
|
2710
2608
|
return;
|
|
2711
2609
|
}
|
|
2712
|
-
const
|
|
2713
|
-
if (isSuccessfulOpencodeExit(code,
|
|
2610
|
+
const stderrModelError = parseOpencodeStderrModelResolutionError(stderrOutput);
|
|
2611
|
+
if (isSuccessfulOpencodeExit(code, stderrModelError, recoveryTracker)) {
|
|
2714
2612
|
const finalOutput = getPreservedChatOutput({
|
|
2715
2613
|
chatSessionId,
|
|
2716
2614
|
log,
|
|
@@ -2737,7 +2635,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2737
2635
|
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
2738
2636
|
const error = resumeSessionId && !executionCapableEventObserved && isTypedPreExecutionContinuationRejection(failureMessage)
|
|
2739
2637
|
? new AgentContinuationRejectedError(failureMessage)
|
|
2740
|
-
:
|
|
2638
|
+
: createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker);
|
|
2741
2639
|
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
2742
2640
|
rejectOnce(attachTranscriptRunsToError(error, transcriptRuns));
|
|
2743
2641
|
});
|
|
@@ -2938,7 +2836,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2938
2836
|
}, transcriptRuns));
|
|
2939
2837
|
return;
|
|
2940
2838
|
}
|
|
2941
|
-
const modelError =
|
|
2839
|
+
const modelError = isLegacyModelResolutionError(errOutput);
|
|
2942
2840
|
if (code === 0 && !modelError) {
|
|
2943
2841
|
const finalOutput = getPreservedSpecOutput({
|
|
2944
2842
|
specSessionId,
|
|
@@ -3083,8 +2981,8 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
3083
2981
|
}, transcriptRuns));
|
|
3084
2982
|
return;
|
|
3085
2983
|
}
|
|
3086
|
-
const
|
|
3087
|
-
if (isSuccessfulOpencodeExit(code,
|
|
2984
|
+
const stderrModelError = parseOpencodeStderrModelResolutionError(stderrOutput);
|
|
2985
|
+
if (isSuccessfulOpencodeExit(code, stderrModelError, recoveryTracker)) {
|
|
3088
2986
|
const finalOutput = getPreservedSpecOutput({
|
|
3089
2987
|
specSessionId,
|
|
3090
2988
|
log,
|
|
@@ -3109,7 +3007,8 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
3109
3007
|
}
|
|
3110
3008
|
updateSpecSessionStatusLocal(specSessionId, 'error', log, io, finalResponse);
|
|
3111
3009
|
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
3112
|
-
|
|
3010
|
+
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
3011
|
+
rejectOnce(attachTranscriptRunsToError(createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker), transcriptRuns));
|
|
3113
3012
|
});
|
|
3114
3013
|
child.on('error', err => {
|
|
3115
3014
|
progressUpdater.cancel();
|
|
@@ -3166,6 +3065,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3166
3065
|
});
|
|
3167
3066
|
return new Promise((resolve, reject) => {
|
|
3168
3067
|
let spawnTimestamp = null;
|
|
3068
|
+
let opencodeSessionId = null;
|
|
3169
3069
|
let opencodeSubagentMonitor = null;
|
|
3170
3070
|
let settled = false;
|
|
3171
3071
|
let startTime = null;
|
|
@@ -3173,6 +3073,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3173
3073
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
3174
3074
|
const reviewState = registeredState ?? {
|
|
3175
3075
|
child: null,
|
|
3076
|
+
interrupt: null,
|
|
3176
3077
|
researchChildren: new Set(),
|
|
3177
3078
|
stopRequested: false,
|
|
3178
3079
|
metadataRefresher: null,
|
|
@@ -3188,21 +3089,56 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3188
3089
|
};
|
|
3189
3090
|
const resolveOnce = settle(resolve);
|
|
3190
3091
|
const rejectOnce = settle(reject);
|
|
3191
|
-
const [cmd, args] =
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
]
|
|
3196
|
-
: ['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
|
+
];
|
|
3197
3096
|
updateSpecReviewRunStreaming(specReviewRunId, null);
|
|
3198
|
-
const
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
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
|
+
}
|
|
3204
3137
|
reviewState.child = child;
|
|
3205
3138
|
activeSpecReviewProcesses.set(specReviewRunId, reviewState);
|
|
3139
|
+
if (reviewState.stopRequested) {
|
|
3140
|
+
interruptProcess(reviewState, 'SIGTERM');
|
|
3141
|
+
}
|
|
3206
3142
|
if (tool === 'claude') {
|
|
3207
3143
|
writeClaudeStdin(child, buildClaudeStdinMessage(appendClaudeHeadlessPlanPrompt(prompt), files, cwd));
|
|
3208
3144
|
}
|
|
@@ -3272,7 +3208,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3272
3208
|
});
|
|
3273
3209
|
return;
|
|
3274
3210
|
}
|
|
3275
|
-
const modelError =
|
|
3211
|
+
const modelError = isLegacyModelResolutionError(errOutput);
|
|
3276
3212
|
if (code === 0 && !modelError) {
|
|
3277
3213
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3278
3214
|
subagentTracker.finalize('done', finalLog || null, finalResponse || null);
|
|
@@ -3326,6 +3262,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3326
3262
|
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
3327
3263
|
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
3328
3264
|
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
3265
|
+
opencodeSessionId = providerSessionId ?? opencodeSessionId;
|
|
3329
3266
|
subagentTracker.handle(subagentEvents);
|
|
3330
3267
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3331
3268
|
firstFinalAnswerTime = null;
|
|
@@ -3343,6 +3280,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3343
3280
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
3344
3281
|
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
3345
3282
|
opencodeSubagentMonitor?.setParentSessionId(parsed.providerSessionId);
|
|
3283
|
+
opencodeSessionId = parsed.providerSessionId ?? opencodeSessionId;
|
|
3346
3284
|
subagentTracker.handle(parsed.subagentEvents);
|
|
3347
3285
|
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3348
3286
|
firstFinalAnswerTime = null;
|
|
@@ -3354,6 +3292,13 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3354
3292
|
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3355
3293
|
? recoveredResponse
|
|
3356
3294
|
: responseLines.join('\n\n') || stderrOutput;
|
|
3295
|
+
if (opencodeSessionId && spawnTimestamp !== null) {
|
|
3296
|
+
usageSnapshot = resolveOpencodeUsageSnapshot({
|
|
3297
|
+
current: usageSnapshot,
|
|
3298
|
+
sessionId: opencodeSessionId,
|
|
3299
|
+
spawnTimestamp,
|
|
3300
|
+
});
|
|
3301
|
+
}
|
|
3357
3302
|
if (reviewState.stopRequested) {
|
|
3358
3303
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3359
3304
|
subagentTracker.finalize('stopped', log || null, finalResponse || null);
|
|
@@ -3365,8 +3310,8 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3365
3310
|
});
|
|
3366
3311
|
return;
|
|
3367
3312
|
}
|
|
3368
|
-
const
|
|
3369
|
-
if (isSuccessfulOpencodeExit(code,
|
|
3313
|
+
const stderrModelError = parseOpencodeStderrModelResolutionError(stderrOutput);
|
|
3314
|
+
if (isSuccessfulOpencodeExit(code, stderrModelError, recoveryTracker)) {
|
|
3370
3315
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
3371
3316
|
subagentTracker.finalize('done', log || null, finalResponse || null);
|
|
3372
3317
|
resolveOnce({
|
|
@@ -3378,12 +3323,30 @@ export const runSpecReviewAgent = ({ specReviewRunId, specSessionId, tool, promp
|
|
|
3378
3323
|
return;
|
|
3379
3324
|
}
|
|
3380
3325
|
subagentTracker.finalize('error', log || null, finalResponse || null);
|
|
3381
|
-
|
|
3326
|
+
const failureMessage = `Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`;
|
|
3327
|
+
rejectOnce(createOpencodeExitError(failureMessage, stderrModelError, recoveryTracker));
|
|
3382
3328
|
});
|
|
3383
3329
|
child.on('error', err => {
|
|
3384
3330
|
progressUpdater.cancel();
|
|
3385
|
-
|
|
3386
|
-
|
|
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);
|
|
3387
3350
|
});
|
|
3388
3351
|
});
|
|
3389
3352
|
};
|