@straiffi/archon 1.4.12 → 1.6.0
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/{GitHubActionsDialog-DcW7fUgz.js → GitHubActionsDialog-oMY61JXF.js} +1 -1
- package/dist/client/assets/{TestsDialog-xNEttQOx.js → TestsDialog-elElGi9L.js} +3 -4
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-DHADo63Y.js → architectureDiagram-3BPJPVTR-BNcdEMha.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-D_LqGLuD.js → blockDiagram-GPEHLZMM-xHTKb0-z.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-mIqv7fCU.js → c4Diagram-AAUBKEIU-kMngbYRg.js} +1 -1
- package/dist/client/assets/channel-DAW1OrrC.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DP-b68f-.js → chunk-2J33WTMH-BVZnJcYa.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-D4GWQbqc.js → chunk-3OPIFGDE-Dq_H_MkG.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-HftAkT9j.js → chunk-5ZQYHXKU-_TwHLQwE.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-CvB0JQZP.js → chunk-727SXJPM-CKkS_93w.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DSB28EAb.js → chunk-AQP2D5EJ-BHSSO0SM.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DuSgBocb.js → chunk-CSCIHK7Q-CgzsiMAL.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-cm_ppJ7v.js → chunk-KSCS5N6A-DM-rnicK.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-BzDHIV9v.js → chunk-L5ZTLDWV-CnMgoXZs.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-BruMwvko.js → chunk-LZXEDZCA-CCr2z1iU.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-DrnvodkL.js → chunk-ND2GUHAM-DtA6gjMn.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-QVsa0LuF.js → chunk-NZK2D7GU-_8fmSSaf.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-DJFhq2gr.js → chunk-O5CBEL6O-B84elqwU.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-ieLjE2uQ.js → chunk-WU5MYG2G-DBvmvPPt.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BmFh-n6n.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-tA1aAbh4.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-CFRh82aT.js → dagre-BM42HDAG-C-QssoNU.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-CPk1WJvx.js → diagram-2AECGRRQ-8RpG5DGb.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-DHMJ735r.js → diagram-5GNKFQAL-Dk2raJWq.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-Cq9d2BWb.js → diagram-KO2AKTUF-7iZuIIef.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CgWJTR_b.js → diagram-LMA3HP47-JLaaaEE6.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-HoFYGw81.js → diagram-OG6HWLK6-jeL3-Rd3.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-B7yuPZck.js → erDiagram-TEJ5UH35-B6uFrMrQ.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DaXXRHQj.js → flowDiagram-I6XJVG4X-BFWnhmx2.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-pKsFmtHB.js → ganttDiagram-6RSMTGT7-nxON-689.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Dwx5kc1M.js → gitGraphDiagram-PVQCEYII-DEAsC0GZ.js} +1 -1
- package/dist/client/assets/index-BzDXl4AF.js +265 -0
- package/dist/client/assets/index-DSap5tSX.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-6Taupp-l.js → infoDiagram-5YYISTIA-CAZHR2I8.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BBKjiFtW.js → ishikawaDiagram-YF4QCWOH-DEe4SNzl.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BQQ1EKJ3.js → journeyDiagram-JHISSGLW-CG2kTAj9.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-BvsroFzw.js → kanban-definition-UN3LZRKU-DUWutAjA.js} +1 -1
- package/dist/client/assets/{line-C6BHsie7.js → line-B1SFN8mz.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-Cb4G8u3H.js → mermaid-parser.core-C-dyz_nb.js} +1 -1
- package/dist/client/assets/{mermaid.core-NCO8Z4wR.js → mermaid.core-nm4BMZuF.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DYzSjAq3.js → mindmap-definition-RKZ34NQL-DZEd-RCz.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CWMyFFuU.js → pieDiagram-4H26LBE5-WXxrzUwX.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-D1ifIucc.js → quadrantDiagram-W4KKPZXB-BClH2kQx.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DW68fC21.js → requirementDiagram-4Y6WPE33-ByolUxfP.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DnnV47BL.js → sankeyDiagram-5OEKKPKP-B2vcb3U1.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Dw-9ZBU_.js → sequenceDiagram-3UESZ5HK-J8Nt0GXG.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-C7dkf8nk.js → stateDiagram-AJRCARHV-CrgZ4Uwa.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CUEBQOsD.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BmVfK_16.js → timeline-definition-PNZ67QCA-CXebl6LI.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-B9j3QbmD.js → vennDiagram-CIIHVFJN-DLBqI39h.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-C520auMy.js → wardleyDiagram-YWT4CUSO-ku4fViV9.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-CPqUJuqs.js → xychartDiagram-2RQKCTM6-5Jh-bBeb.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +291 -3
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +339 -97
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +853 -161
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentRuns.js +22 -0
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/chatMessages.js +24 -8
- package/dist/server/lib/chatMessages.js.map +1 -1
- package/dist/server/lib/genericToolCall.js +151 -0
- package/dist/server/lib/genericToolCall.js.map +1 -0
- package/dist/server/lib/git.js +18 -0
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +45 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/models.js +65 -48
- package/dist/server/lib/models.js.map +1 -1
- package/dist/server/lib/opencodeTaskSessions.js +216 -0
- package/dist/server/lib/opencodeTaskSessions.js.map +1 -0
- package/dist/server/lib/projectMemory.js +1073 -276
- package/dist/server/lib/projectMemory.js.map +1 -1
- package/dist/server/lib/projectMemoryCursor.js +27 -0
- package/dist/server/lib/projectMemoryCursor.js.map +1 -0
- package/dist/server/lib/projectMemoryEvents.js +24 -0
- package/dist/server/lib/projectMemoryEvents.js.map +1 -0
- package/dist/server/lib/projectMemoryLimits.js +61 -0
- package/dist/server/lib/projectMemoryLimits.js.map +1 -0
- package/dist/server/lib/projectMemoryPrompt.js +299 -110
- package/dist/server/lib/projectMemoryPrompt.js.map +1 -1
- package/dist/server/lib/projectMemoryScan.js +461 -493
- package/dist/server/lib/projectMemoryScan.js.map +1 -1
- package/dist/server/lib/projectMemorySelections.js +97 -0
- package/dist/server/lib/projectMemorySelections.js.map +1 -0
- package/dist/server/lib/projectMemorySuggestions.js +19 -4
- package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
- package/dist/server/lib/projectMemoryTransfer.js +635 -83
- package/dist/server/lib/projectMemoryTransfer.js.map +1 -1
- package/dist/server/lib/projectPathGlob.js +40 -0
- package/dist/server/lib/projectPathGlob.js.map +1 -0
- package/dist/server/lib/projects.js +50 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/promptSkills.js +5 -0
- package/dist/server/lib/promptSkills.js.map +1 -1
- package/dist/server/lib/specMessages.js +9 -6
- package/dist/server/lib/specMessages.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +2 -1
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/ticketMessages.js +22 -7
- package/dist/server/lib/ticketMessages.js.map +1 -1
- package/dist/server/lib/tickets.js +6 -0
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/transcriptRuns.js +85 -0
- package/dist/server/lib/transcriptRuns.js.map +1 -0
- package/dist/server/workers/build.js +18 -4
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +24 -7
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +27 -10
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +405 -69
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +30 -7
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +76 -20
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +10 -0
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +37 -3
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-D7PWuiXU.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BNIT931s.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-TRtc0GGS.js +0 -1
- package/dist/client/assets/index-5Eun897r.js +0 -260
- package/dist/client/assets/index-CWWEw76C.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BFmoroJA.js +0 -1
package/dist/server/lib/agent.js
CHANGED
|
@@ -6,13 +6,64 @@ import Database from 'better-sqlite3';
|
|
|
6
6
|
import db from '../db.js';
|
|
7
7
|
import { getChatSession, updateChatSessionUsageSnapshot } from './chats.js';
|
|
8
8
|
import { normalizeClaudeModelId, normalizeModelId } from './models.js';
|
|
9
|
+
import { createOpencodeTaskSessionReader, parseOpencodeTaskEnvelope } from './opencodeTaskSessions.js';
|
|
9
10
|
import { PLAN_MODE_QUESTION_TOOL_NAME } from './planModeQuestions.js';
|
|
11
|
+
import { formatGenericToolCall } from './genericToolCall.js';
|
|
10
12
|
import { getSpecSession, updateSpecSessionResumeId, updateSpecSessionStatus, updateSpecSessionTitle, updateSpecSessionUsageSnapshot } from './specs.js';
|
|
11
13
|
import { updateSpecReviewRunStatus, updateSpecReviewRunStreaming, updateSpecReviewRunUsage } from './specReviews.js';
|
|
12
|
-
import { emitAgentRunsUpdated, finalizeAgentRun, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
14
|
+
import { createAgentRun, emitAgentRunsUpdated, finalizeAgentRun, getNextAgentRunIndex, listAgentRunsForParent, updateAgentRunLabel, updateAgentRunSessionId, updateAgentRunStatus } from './agentRuns.js';
|
|
13
15
|
import { resolveOpencodeDbPath } from './runtimePaths.js';
|
|
14
16
|
import { markTicketDescriptionVisibleInBundle } from './ticketMessages.js';
|
|
15
17
|
import { getTicket } from './tickets.js';
|
|
18
|
+
import { createTranscriptRunSnapshots } from './transcriptRuns.js';
|
|
19
|
+
const withTranscriptRuns = (value, transcriptRuns) => {
|
|
20
|
+
return transcriptRuns ? { ...value, transcriptRuns } : value;
|
|
21
|
+
};
|
|
22
|
+
const createOpencodeRecoveryTracker = () => {
|
|
23
|
+
let sawError = false;
|
|
24
|
+
let latestEvent = null;
|
|
25
|
+
let recoveredFinalAnswerFragments = [];
|
|
26
|
+
return {
|
|
27
|
+
consume: events => {
|
|
28
|
+
let shouldResetResponse = false;
|
|
29
|
+
for (const event of events ?? []) {
|
|
30
|
+
latestEvent = event.type;
|
|
31
|
+
if (event.type === 'error') {
|
|
32
|
+
sawError = true;
|
|
33
|
+
recoveredFinalAnswerFragments = [];
|
|
34
|
+
shouldResetResponse = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (sawError) {
|
|
38
|
+
recoveredFinalAnswerFragments.push(event.text);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return shouldResetResponse;
|
|
42
|
+
},
|
|
43
|
+
hasRecoveredFinalAnswer: () => (sawError
|
|
44
|
+
&& latestEvent === 'final_answer'
|
|
45
|
+
&& recoveredFinalAnswerFragments.length > 0),
|
|
46
|
+
hasUnrecoveredError: () => latestEvent === 'error',
|
|
47
|
+
getRecoveredResponse: () => recoveredFinalAnswerFragments.join('\n\n'),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const isSuccessfulOpencodeExit = (code, modelError, recoveryTracker) => (!modelError
|
|
51
|
+
&& !recoveryTracker.hasUnrecoveredError()
|
|
52
|
+
&& (code === 0 || recoveryTracker.hasRecoveredFinalAnswer()));
|
|
53
|
+
const attachTranscriptRunsToError = (error, transcriptRuns) => {
|
|
54
|
+
const resolvedError = (error instanceof Error ? error : new Error(String(error)));
|
|
55
|
+
if (transcriptRuns) {
|
|
56
|
+
resolvedError.transcriptRuns = transcriptRuns;
|
|
57
|
+
}
|
|
58
|
+
return resolvedError;
|
|
59
|
+
};
|
|
60
|
+
export const getAgentErrorTranscriptRuns = (error) => {
|
|
61
|
+
if (!error || typeof error !== 'object' || !('transcriptRuns' in error)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const transcriptRuns = error.transcriptRuns;
|
|
65
|
+
return Array.isArray(transcriptRuns) && transcriptRuns.length > 0 ? transcriptRuns : null;
|
|
66
|
+
};
|
|
16
67
|
const OPENCODE_PLACEHOLDER_TITLE_PATTERN = /^New session\b/i;
|
|
17
68
|
const OPENCODE_SESSION_METADATA_LOOKUP_DELAY_MS = 1500;
|
|
18
69
|
const AGENT_PROGRESS_UPDATE_INTERVAL_MS = 100;
|
|
@@ -702,7 +753,6 @@ const createSpecOpencodeMetadataRefresher = ({ specSessionId, io, spawnTimestamp
|
|
|
702
753
|
},
|
|
703
754
|
};
|
|
704
755
|
};
|
|
705
|
-
const TOOL_CALL_PREVIEW_MAX_LENGTH = 100;
|
|
706
756
|
const APPLY_PATCH_START_MARKER = '[[archon-apply-patch]]';
|
|
707
757
|
const APPLY_PATCH_END_MARKER = '[[/archon-apply-patch]]';
|
|
708
758
|
const normalizeToolName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
@@ -836,22 +886,6 @@ const formatWriteToolCall = (input, cwd) => {
|
|
|
836
886
|
displayLines.push(APPLY_PATCH_END_MARKER);
|
|
837
887
|
return displayLines.join('\n');
|
|
838
888
|
};
|
|
839
|
-
const pickGenericToolVal = (name, input) => {
|
|
840
|
-
const explicitVal = {
|
|
841
|
-
Read: input?.filePath ?? input?.file_path,
|
|
842
|
-
Bash: input?.command,
|
|
843
|
-
Glob: input?.pattern,
|
|
844
|
-
Grep: input?.pattern,
|
|
845
|
-
WebFetch: input?.url,
|
|
846
|
-
WebSearch: input?.query,
|
|
847
|
-
Task: input?.description,
|
|
848
|
-
NotebookEdit: input?.notebook_path,
|
|
849
|
-
}[name];
|
|
850
|
-
if (typeof explicitVal === 'string' && explicitVal.length > 0) {
|
|
851
|
-
return explicitVal;
|
|
852
|
-
}
|
|
853
|
-
return Object.values(input ?? {}).find(v => typeof v === 'string') ?? null;
|
|
854
|
-
};
|
|
855
889
|
const formatToolCall = (name, input, cwd) => {
|
|
856
890
|
if (isTodoWriteTool(name)) {
|
|
857
891
|
return formatTodoWriteToolCall(input);
|
|
@@ -865,40 +899,87 @@ const formatToolCall = (name, input, cwd) => {
|
|
|
865
899
|
if (formatted !== null) {
|
|
866
900
|
return formatted;
|
|
867
901
|
}
|
|
868
|
-
return
|
|
902
|
+
return formatGenericToolCall(name, input);
|
|
869
903
|
}
|
|
870
904
|
if (normalized === 'write') {
|
|
871
905
|
const formatted = formatWriteToolCall(input, cwd);
|
|
872
906
|
if (formatted !== null) {
|
|
873
907
|
return formatted;
|
|
874
908
|
}
|
|
875
|
-
return
|
|
909
|
+
return formatGenericToolCall(name, input);
|
|
876
910
|
}
|
|
877
|
-
return
|
|
911
|
+
return formatGenericToolCall(name, input);
|
|
878
912
|
};
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
913
|
+
const isSubagentTool = (name) => {
|
|
914
|
+
const normalized = normalizeToolName(name);
|
|
915
|
+
return normalized === 'task' || normalized === 'subagent' || normalized === 'agent';
|
|
916
|
+
};
|
|
917
|
+
const getSubagentDescription = (input, fallback) => {
|
|
918
|
+
if (input) {
|
|
919
|
+
for (const key of ['description', 'title', 'task', 'subagent_type', 'prompt']) {
|
|
920
|
+
const value = input[key];
|
|
921
|
+
if (typeof value === 'string' && value.trim()) {
|
|
922
|
+
const firstLine = value.trim().split('\n')[0] ?? '';
|
|
923
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
883
926
|
}
|
|
884
|
-
|
|
885
|
-
return `_${name}_ \`${display}\``;
|
|
927
|
+
return fallback;
|
|
886
928
|
};
|
|
929
|
+
const stringifyToolResult = (content) => {
|
|
930
|
+
if (typeof content === 'string') {
|
|
931
|
+
return content.trim() || null;
|
|
932
|
+
}
|
|
933
|
+
if (Array.isArray(content)) {
|
|
934
|
+
const text = content
|
|
935
|
+
.map(item => item && typeof item === 'object' && typeof item.text === 'string'
|
|
936
|
+
? item.text.trim()
|
|
937
|
+
: '')
|
|
938
|
+
.filter(Boolean)
|
|
939
|
+
.join('\n\n');
|
|
940
|
+
return text || null;
|
|
941
|
+
}
|
|
942
|
+
return content === null || content === undefined ? null : JSON.stringify(content);
|
|
943
|
+
};
|
|
944
|
+
const getSubagentCompletionStatus = (state, isError = false) => {
|
|
945
|
+
const normalizedState = state?.toLowerCase();
|
|
946
|
+
return isError || normalizedState === 'failed' || normalizedState === 'error' ? 'error' : 'done';
|
|
947
|
+
};
|
|
948
|
+
const emptyParsedStreamLine = () => ({
|
|
949
|
+
logFragment: null,
|
|
950
|
+
responseFragment: null,
|
|
951
|
+
subagentEvents: [],
|
|
952
|
+
providerSessionId: null,
|
|
953
|
+
});
|
|
887
954
|
const parseClaudeStreamJsonLine = (line, cwd) => {
|
|
888
955
|
const trimmed = line.trim();
|
|
889
956
|
if (!trimmed) {
|
|
890
|
-
return
|
|
957
|
+
return emptyParsedStreamLine();
|
|
891
958
|
}
|
|
892
959
|
let event;
|
|
893
960
|
try {
|
|
894
961
|
event = JSON.parse(trimmed);
|
|
895
962
|
}
|
|
896
963
|
catch {
|
|
897
|
-
|
|
964
|
+
const envelope = parseOpencodeTaskEnvelope(trimmed);
|
|
965
|
+
if (envelope) {
|
|
966
|
+
return {
|
|
967
|
+
...emptyParsedStreamLine(),
|
|
968
|
+
subagentEvents: [{
|
|
969
|
+
kind: 'complete',
|
|
970
|
+
callId: null,
|
|
971
|
+
sessionId: envelope.childSessionId,
|
|
972
|
+
status: getSubagentCompletionStatus(envelope.state),
|
|
973
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
974
|
+
}],
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
return { logFragment: trimmed, responseFragment: trimmed, subagentEvents: [], providerSessionId: null };
|
|
898
978
|
}
|
|
899
979
|
if (event.type === 'assistant' && Array.isArray(event.message?.content)) {
|
|
900
980
|
const logParts = [];
|
|
901
981
|
const responseParts = [];
|
|
982
|
+
const subagentEvents = [];
|
|
902
983
|
for (const block of event.message.content) {
|
|
903
984
|
if (block.type === 'text' && block.text) {
|
|
904
985
|
const text = block.text.trim();
|
|
@@ -908,6 +989,16 @@ const parseClaudeStreamJsonLine = (line, cwd) => {
|
|
|
908
989
|
}
|
|
909
990
|
}
|
|
910
991
|
else if (block.type === 'tool_use' && block.name) {
|
|
992
|
+
if (isSubagentTool(block.name)) {
|
|
993
|
+
const description = getSubagentDescription(block.input, block.name);
|
|
994
|
+
logParts.push(`Subagent: ${description}`);
|
|
995
|
+
subagentEvents.push({
|
|
996
|
+
kind: 'start',
|
|
997
|
+
callId: block.id ?? null,
|
|
998
|
+
label: `Subagent: ${description}`,
|
|
999
|
+
});
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
911
1002
|
const toolFragment = formatToolCall(block.name, block.input, cwd);
|
|
912
1003
|
if (toolFragment) {
|
|
913
1004
|
logParts.push(toolFragment);
|
|
@@ -917,9 +1008,44 @@ const parseClaudeStreamJsonLine = (line, cwd) => {
|
|
|
917
1008
|
return {
|
|
918
1009
|
logFragment: logParts.length > 0 ? logParts.join('\n\n') : null,
|
|
919
1010
|
responseFragment: responseParts.length > 0 ? responseParts.join('\n\n') : null,
|
|
1011
|
+
subagentEvents,
|
|
1012
|
+
providerSessionId: null,
|
|
920
1013
|
};
|
|
921
1014
|
}
|
|
922
|
-
|
|
1015
|
+
if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
|
|
1016
|
+
const block = event.event.content_block;
|
|
1017
|
+
if (block?.type === 'tool_use' && block.name && isSubagentTool(block.name)) {
|
|
1018
|
+
const description = getSubagentDescription(block.input, block.name);
|
|
1019
|
+
return {
|
|
1020
|
+
...emptyParsedStreamLine(),
|
|
1021
|
+
logFragment: `Subagent: ${description}`,
|
|
1022
|
+
subagentEvents: [{
|
|
1023
|
+
kind: 'start',
|
|
1024
|
+
callId: block.id ?? null,
|
|
1025
|
+
label: `Subagent: ${description}`,
|
|
1026
|
+
}],
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (event.type === 'user' && Array.isArray(event.message?.content)) {
|
|
1031
|
+
const subagentEvents = event.message.content.flatMap(block => {
|
|
1032
|
+
if (block.type !== 'tool_result' || !block.tool_use_id) {
|
|
1033
|
+
return [];
|
|
1034
|
+
}
|
|
1035
|
+
const rawText = stringifyToolResult(block.content);
|
|
1036
|
+
const envelope = parseOpencodeTaskEnvelope(rawText);
|
|
1037
|
+
const text = envelope?.result ?? rawText;
|
|
1038
|
+
return [{
|
|
1039
|
+
kind: 'complete',
|
|
1040
|
+
callId: block.tool_use_id,
|
|
1041
|
+
status: getSubagentCompletionStatus(envelope?.state, block.is_error),
|
|
1042
|
+
...(text ? { text } : {}),
|
|
1043
|
+
...(envelope ? { sessionId: envelope.childSessionId } : {}),
|
|
1044
|
+
}];
|
|
1045
|
+
});
|
|
1046
|
+
return { ...emptyParsedStreamLine(), subagentEvents };
|
|
1047
|
+
}
|
|
1048
|
+
return emptyParsedStreamLine();
|
|
923
1049
|
};
|
|
924
1050
|
const formatOpencodeToolUse = (event, cwd) => {
|
|
925
1051
|
const toolName = event.part?.tool;
|
|
@@ -989,12 +1115,22 @@ const extractLeadingJsonObjects = (line) => {
|
|
|
989
1115
|
};
|
|
990
1116
|
};
|
|
991
1117
|
const parseOpencodeJsonEvent = (event, cwd) => {
|
|
1118
|
+
const providerSessionId = event.sessionID?.trim()
|
|
1119
|
+
|| event.sessionId?.trim()
|
|
1120
|
+
|| event.part?.sessionID?.trim()
|
|
1121
|
+
|| event.part?.sessionId?.trim()
|
|
1122
|
+
|| null;
|
|
992
1123
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
993
1124
|
const text = event.part.text.trim();
|
|
994
1125
|
const phase = event.part.metadata?.openai?.phase;
|
|
995
1126
|
return {
|
|
996
1127
|
logFragment: text || null,
|
|
997
1128
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
1129
|
+
subagentEvents: [],
|
|
1130
|
+
providerSessionId,
|
|
1131
|
+
recoveryEvents: text && phase === 'final_answer'
|
|
1132
|
+
? [{ type: 'final_answer', text }]
|
|
1133
|
+
: [],
|
|
998
1134
|
};
|
|
999
1135
|
}
|
|
1000
1136
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -1002,25 +1138,74 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1002
1138
|
return {
|
|
1003
1139
|
logFragment: text || null,
|
|
1004
1140
|
responseFragment: null,
|
|
1141
|
+
subagentEvents: [],
|
|
1142
|
+
providerSessionId,
|
|
1005
1143
|
};
|
|
1006
1144
|
}
|
|
1007
|
-
if (event.type === 'tool_use') {
|
|
1145
|
+
if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
|
|
1146
|
+
const isToolInvocation = event.type === 'tool' || event.type === 'tool_use';
|
|
1147
|
+
const toolName = event.part?.tool;
|
|
1148
|
+
if (toolName && isSubagentTool(toolName)) {
|
|
1149
|
+
const description = getSubagentDescription(event.part?.state?.input, toolName);
|
|
1150
|
+
const rawOutput = event.part?.state?.output?.trim();
|
|
1151
|
+
const envelope = parseOpencodeTaskEnvelope(rawOutput);
|
|
1152
|
+
const output = envelope ? envelope.result : rawOutput;
|
|
1153
|
+
const taskStatus = event.part?.state?.status?.toLowerCase() ?? envelope?.state?.toLowerCase();
|
|
1154
|
+
const outputKind = taskStatus === 'running' || taskStatus === 'pending' ? 'fragment' : 'complete';
|
|
1155
|
+
const completionStatus = taskStatus === 'error' || taskStatus === 'failed' ? 'error' : 'done';
|
|
1156
|
+
const childSessionId = envelope?.childSessionId
|
|
1157
|
+
?? (typeof event.part?.state?.metadata?.sessionID === 'string' ? event.part.state.metadata.sessionID : null)
|
|
1158
|
+
?? (typeof event.part?.state?.metadata?.sessionId === 'string' ? event.part.state.metadata.sessionId : null);
|
|
1159
|
+
return {
|
|
1160
|
+
logFragment: isToolInvocation ? `Subagent: ${description}` : null,
|
|
1161
|
+
responseFragment: null,
|
|
1162
|
+
subagentEvents: [
|
|
1163
|
+
...(isToolInvocation
|
|
1164
|
+
? [{
|
|
1165
|
+
kind: 'start',
|
|
1166
|
+
callId: event.part?.callID ?? null,
|
|
1167
|
+
label: `Subagent: ${description}`,
|
|
1168
|
+
sessionId: childSessionId,
|
|
1169
|
+
}]
|
|
1170
|
+
: []),
|
|
1171
|
+
...(output
|
|
1172
|
+
? [{
|
|
1173
|
+
kind: outputKind,
|
|
1174
|
+
callId: event.part?.callID ?? null,
|
|
1175
|
+
text: output,
|
|
1176
|
+
sessionId: childSessionId,
|
|
1177
|
+
status: completionStatus,
|
|
1178
|
+
}]
|
|
1179
|
+
: []),
|
|
1180
|
+
],
|
|
1181
|
+
providerSessionId,
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
if (!isToolInvocation) {
|
|
1185
|
+
return { ...emptyParsedStreamLine(), providerSessionId };
|
|
1186
|
+
}
|
|
1008
1187
|
const toolFragment = formatOpencodeToolUse(event, cwd);
|
|
1009
1188
|
return {
|
|
1010
1189
|
logFragment: toolFragment,
|
|
1011
1190
|
responseFragment: null,
|
|
1191
|
+
subagentEvents: [],
|
|
1192
|
+
providerSessionId,
|
|
1012
1193
|
};
|
|
1013
1194
|
}
|
|
1014
1195
|
if (event.type === 'step_start') {
|
|
1015
1196
|
return {
|
|
1016
1197
|
logFragment: formatOpencodeStepTitle(event),
|
|
1017
1198
|
responseFragment: null,
|
|
1199
|
+
subagentEvents: [],
|
|
1200
|
+
providerSessionId,
|
|
1018
1201
|
};
|
|
1019
1202
|
}
|
|
1020
1203
|
if (event.type === 'step_finish') {
|
|
1021
1204
|
return {
|
|
1022
1205
|
logFragment: null,
|
|
1023
1206
|
responseFragment: null,
|
|
1207
|
+
subagentEvents: [],
|
|
1208
|
+
providerSessionId,
|
|
1024
1209
|
};
|
|
1025
1210
|
}
|
|
1026
1211
|
if (event.type === 'error') {
|
|
@@ -1032,29 +1217,46 @@ const parseOpencodeJsonEvent = (event, cwd) => {
|
|
|
1032
1217
|
|| JSON.stringify(event.error))
|
|
1033
1218
|
: '';
|
|
1034
1219
|
const text = event.part?.text?.trim() || errorText || '';
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1220
|
+
return {
|
|
1221
|
+
logFragment: text ? `**Error:** ${text}` : null,
|
|
1222
|
+
responseFragment: null,
|
|
1223
|
+
subagentEvents: [],
|
|
1224
|
+
providerSessionId,
|
|
1225
|
+
recoveryEvents: [{ type: 'error' }],
|
|
1226
|
+
};
|
|
1041
1227
|
}
|
|
1042
|
-
return
|
|
1228
|
+
return emptyParsedStreamLine();
|
|
1043
1229
|
};
|
|
1044
1230
|
const parseOpencodeJsonLine = (line, cwd) => {
|
|
1045
1231
|
const trimmed = line.trim();
|
|
1046
1232
|
if (!trimmed) {
|
|
1047
|
-
return
|
|
1233
|
+
return emptyParsedStreamLine();
|
|
1048
1234
|
}
|
|
1049
1235
|
const { jsonObjects, remainder } = extractLeadingJsonObjects(trimmed);
|
|
1050
1236
|
if (jsonObjects.length === 0) {
|
|
1051
1237
|
if (trimmed.startsWith('{')) {
|
|
1052
|
-
return
|
|
1238
|
+
return emptyParsedStreamLine();
|
|
1053
1239
|
}
|
|
1054
|
-
|
|
1240
|
+
const envelope = parseOpencodeTaskEnvelope(trimmed);
|
|
1241
|
+
if (envelope) {
|
|
1242
|
+
return {
|
|
1243
|
+
...emptyParsedStreamLine(),
|
|
1244
|
+
subagentEvents: [{
|
|
1245
|
+
kind: 'complete',
|
|
1246
|
+
callId: null,
|
|
1247
|
+
sessionId: envelope.childSessionId,
|
|
1248
|
+
status: getSubagentCompletionStatus(envelope.state),
|
|
1249
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
1250
|
+
}],
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return { logFragment: trimmed, responseFragment: trimmed, subagentEvents: [], providerSessionId: null };
|
|
1055
1254
|
}
|
|
1056
1255
|
const logFragments = [];
|
|
1057
1256
|
const responseFragments = [];
|
|
1257
|
+
const subagentEvents = [];
|
|
1258
|
+
const recoveryEvents = [];
|
|
1259
|
+
let providerSessionId = null;
|
|
1058
1260
|
for (const jsonObject of jsonObjects) {
|
|
1059
1261
|
let event;
|
|
1060
1262
|
try {
|
|
@@ -1063,7 +1265,11 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1063
1265
|
catch {
|
|
1064
1266
|
continue;
|
|
1065
1267
|
}
|
|
1066
|
-
const
|
|
1268
|
+
const parsedEvent = parseOpencodeJsonEvent(event, cwd);
|
|
1269
|
+
const { logFragment, responseFragment } = parsedEvent;
|
|
1270
|
+
subagentEvents.push(...parsedEvent.subagentEvents);
|
|
1271
|
+
recoveryEvents.push(...(parsedEvent.recoveryEvents ?? []));
|
|
1272
|
+
providerSessionId = providerSessionId ?? parsedEvent.providerSessionId;
|
|
1067
1273
|
if (logFragment !== null) {
|
|
1068
1274
|
logFragments.push(logFragment);
|
|
1069
1275
|
}
|
|
@@ -1071,16 +1277,386 @@ const parseOpencodeJsonLine = (line, cwd) => {
|
|
|
1071
1277
|
responseFragments.push(responseFragment);
|
|
1072
1278
|
}
|
|
1073
1279
|
}
|
|
1074
|
-
if (
|
|
1280
|
+
if (remainder) {
|
|
1281
|
+
const envelope = parseOpencodeTaskEnvelope(remainder);
|
|
1282
|
+
if (envelope) {
|
|
1283
|
+
subagentEvents.push({
|
|
1284
|
+
kind: 'complete',
|
|
1285
|
+
callId: null,
|
|
1286
|
+
sessionId: envelope.childSessionId,
|
|
1287
|
+
status: getSubagentCompletionStatus(envelope.state),
|
|
1288
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
else if (logFragments.length === 0 && responseFragments.length === 0 && subagentEvents.length === 0) {
|
|
1292
|
+
logFragments.push(remainder);
|
|
1293
|
+
responseFragments.push(remainder);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (logFragments.length > 0 || responseFragments.length > 0 || subagentEvents.length > 0) {
|
|
1075
1297
|
return {
|
|
1076
1298
|
logFragment: logFragments.length > 0 ? logFragments.join('\n\n') : null,
|
|
1077
1299
|
responseFragment: responseFragments.length > 0 ? responseFragments.join('\n\n') : null,
|
|
1300
|
+
subagentEvents,
|
|
1301
|
+
providerSessionId,
|
|
1302
|
+
recoveryEvents,
|
|
1078
1303
|
};
|
|
1079
1304
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1305
|
+
return emptyParsedStreamLine();
|
|
1306
|
+
};
|
|
1307
|
+
const appendTranscriptText = (current, fragment) => {
|
|
1308
|
+
const trimmed = fragment.trim();
|
|
1309
|
+
if (!trimmed) {
|
|
1310
|
+
return current;
|
|
1311
|
+
}
|
|
1312
|
+
return current ? `${current}\n\n${trimmed}` : trimmed;
|
|
1313
|
+
};
|
|
1314
|
+
const appendOpencodeTranscriptLine = ({ parsed, logLines, responseLines, recoveryTracker, }) => {
|
|
1315
|
+
const shouldResetResponse = recoveryTracker.consume(parsed.recoveryEvents);
|
|
1316
|
+
if (shouldResetResponse) {
|
|
1317
|
+
responseLines.length = 0;
|
|
1318
|
+
}
|
|
1319
|
+
if (parsed.logFragment !== null) {
|
|
1320
|
+
logLines.push(parsed.logFragment);
|
|
1321
|
+
}
|
|
1322
|
+
if (parsed.responseFragment !== null) {
|
|
1323
|
+
responseLines.push(parsed.responseFragment);
|
|
1324
|
+
}
|
|
1325
|
+
return shouldResetResponse;
|
|
1326
|
+
};
|
|
1327
|
+
export const createSubagentRunTracker = ({ parentType, parentId, io, mainRun, }) => {
|
|
1328
|
+
const trackedRuns = [];
|
|
1329
|
+
let nextRunIndex = Math.max(getNextAgentRunIndex(parentType, parentId), mainRun ? mainRun.runIndex + 1 : 1);
|
|
1330
|
+
const emitUpdate = () => {
|
|
1331
|
+
emitAgentRunsUpdated(io, parentType, parentId);
|
|
1332
|
+
};
|
|
1333
|
+
const findRun = (callId, sessionId) => {
|
|
1334
|
+
if (callId) {
|
|
1335
|
+
const callRun = trackedRuns.find(run => run.callId === callId);
|
|
1336
|
+
if (callRun) {
|
|
1337
|
+
return callRun;
|
|
1338
|
+
}
|
|
1339
|
+
return sessionId ? trackedRuns.find(run => run.sessionId === sessionId) ?? null : null;
|
|
1340
|
+
}
|
|
1341
|
+
if (sessionId) {
|
|
1342
|
+
const sessionRun = trackedRuns.find(run => run.sessionId === sessionId);
|
|
1343
|
+
if (sessionRun) {
|
|
1344
|
+
return sessionRun;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return [...trackedRuns].reverse().find(run => run.status === 'running') ?? null;
|
|
1348
|
+
};
|
|
1349
|
+
const bindSessionId = (run, sessionId) => {
|
|
1350
|
+
const normalized = sessionId?.trim() || null;
|
|
1351
|
+
if (!normalized || normalized === run.sessionId) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
run.sessionId = normalized;
|
|
1355
|
+
updateAgentRunSessionId(run.id, normalized);
|
|
1356
|
+
};
|
|
1357
|
+
const updateLabel = (run, label) => {
|
|
1358
|
+
const normalized = label?.trim() || null;
|
|
1359
|
+
if (!normalized || normalized === run.label) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
run.label = normalized;
|
|
1363
|
+
updateAgentRunLabel(run.id, normalized);
|
|
1364
|
+
};
|
|
1365
|
+
const handle = (events) => {
|
|
1366
|
+
for (const event of events) {
|
|
1367
|
+
if (event.kind === 'start') {
|
|
1368
|
+
const existingRun = event.callId || event.sessionId ? findRun(event.callId, event.sessionId) : null;
|
|
1369
|
+
if (existingRun) {
|
|
1370
|
+
bindSessionId(existingRun, event.sessionId);
|
|
1371
|
+
updateLabel(existingRun, event.label);
|
|
1372
|
+
emitUpdate();
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
const runIndex = Math.max(getNextAgentRunIndex(parentType, parentId), nextRunIndex);
|
|
1376
|
+
const id = createAgentRun({
|
|
1377
|
+
parentType,
|
|
1378
|
+
parentId,
|
|
1379
|
+
runIndex,
|
|
1380
|
+
label: event.label?.trim() || 'Subagent',
|
|
1381
|
+
role: 'subagent',
|
|
1382
|
+
});
|
|
1383
|
+
nextRunIndex = runIndex + 1;
|
|
1384
|
+
const run = {
|
|
1385
|
+
id,
|
|
1386
|
+
callId: event.callId,
|
|
1387
|
+
runIndex,
|
|
1388
|
+
log: '',
|
|
1389
|
+
response: '',
|
|
1390
|
+
status: 'running',
|
|
1391
|
+
sessionId: null,
|
|
1392
|
+
label: event.label?.trim() || 'Subagent',
|
|
1393
|
+
createdAt: new Date().toISOString(),
|
|
1394
|
+
};
|
|
1395
|
+
trackedRuns.push(run);
|
|
1396
|
+
bindSessionId(run, event.sessionId);
|
|
1397
|
+
emitUpdate();
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
let run = findRun(event.callId, event.sessionId);
|
|
1401
|
+
if (!run && event.kind === 'complete' && !event.callId) {
|
|
1402
|
+
handle([{
|
|
1403
|
+
kind: 'start',
|
|
1404
|
+
callId: event.callId,
|
|
1405
|
+
label: 'Subagent',
|
|
1406
|
+
sessionId: event.sessionId,
|
|
1407
|
+
}]);
|
|
1408
|
+
run = findRun(event.callId, event.sessionId);
|
|
1409
|
+
}
|
|
1410
|
+
if (!run || run.status !== 'running') {
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
bindSessionId(run, event.sessionId);
|
|
1414
|
+
if (event.text) {
|
|
1415
|
+
if (event.kind === 'fragment') {
|
|
1416
|
+
run.log = appendTranscriptText(run.log, event.text);
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
run.response = appendTranscriptText(run.response, event.text);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (event.kind === 'complete') {
|
|
1423
|
+
const completionStatus = event.status ?? 'done';
|
|
1424
|
+
run.status = completionStatus;
|
|
1425
|
+
updateAgentRunStatus(run.id, 'running', run.log || null, run.response || null);
|
|
1426
|
+
finalizeAgentRun(run.id, completionStatus, run.response || null);
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
updateAgentRunStatus(run.id, 'running', run.log || null, run.response || null);
|
|
1430
|
+
}
|
|
1431
|
+
emitUpdate();
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
const applySnapshot = ({ callId, label, status, log, response, sessionId, }) => {
|
|
1435
|
+
let run = findRun(callId);
|
|
1436
|
+
if (!run) {
|
|
1437
|
+
handle([{ kind: 'start', callId, label, sessionId }]);
|
|
1438
|
+
run = findRun(callId);
|
|
1439
|
+
}
|
|
1440
|
+
if (!run) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
bindSessionId(run, sessionId);
|
|
1444
|
+
updateLabel(run, label);
|
|
1445
|
+
const nextLog = log?.trim() ?? '';
|
|
1446
|
+
const nextResponse = response?.trim() ?? '';
|
|
1447
|
+
const nextStatus = run.status !== 'running' && status === 'running' ? run.status : status;
|
|
1448
|
+
const hasChanged = run.log !== nextLog || run.response !== nextResponse || run.status !== nextStatus;
|
|
1449
|
+
if (!hasChanged) {
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
run.log = nextLog;
|
|
1453
|
+
run.response = nextResponse;
|
|
1454
|
+
run.status = nextStatus;
|
|
1455
|
+
if (nextStatus === 'running') {
|
|
1456
|
+
updateAgentRunStatus(run.id, 'running', run.log || null, run.response || null);
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
updateAgentRunStatus(run.id, 'running', run.log || null, run.response || null);
|
|
1460
|
+
finalizeAgentRun(run.id, nextStatus, run.response || null);
|
|
1461
|
+
}
|
|
1462
|
+
emitUpdate();
|
|
1463
|
+
};
|
|
1464
|
+
const finalize = (status, parentLog, parentResponse) => {
|
|
1465
|
+
if (trackedRuns.length === 0 && !mainRun) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
for (const run of trackedRuns) {
|
|
1469
|
+
if (run.status !== 'running') {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
run.status = status;
|
|
1473
|
+
finalizeAgentRun(run.id, status, run.response || null);
|
|
1474
|
+
}
|
|
1475
|
+
emitUpdate();
|
|
1476
|
+
const trackedIds = new Set(trackedRuns.map(run => run.id));
|
|
1477
|
+
const persistedRuns = listAgentRunsForParent(parentType, parentId)
|
|
1478
|
+
.filter(run => trackedIds.has(run.id) || run.id === mainRun?.id);
|
|
1479
|
+
const persistedIds = new Set(persistedRuns.map(run => run.id));
|
|
1480
|
+
const timestamp = new Date().toISOString();
|
|
1481
|
+
for (const run of trackedRuns) {
|
|
1482
|
+
if (persistedIds.has(run.id)) {
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
persistedRuns.push({
|
|
1486
|
+
id: run.id,
|
|
1487
|
+
parent_type: parentType,
|
|
1488
|
+
parent_id: parentId,
|
|
1489
|
+
run_index: run.runIndex,
|
|
1490
|
+
label: run.label,
|
|
1491
|
+
role: 'subagent',
|
|
1492
|
+
agent_status: run.status,
|
|
1493
|
+
agent_log: run.log || null,
|
|
1494
|
+
streaming_response: null,
|
|
1495
|
+
response_text: run.response || null,
|
|
1496
|
+
session_id: run.sessionId,
|
|
1497
|
+
created_at: run.createdAt,
|
|
1498
|
+
updated_at: timestamp,
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
if (mainRun) {
|
|
1502
|
+
const persistedMainRun = persistedRuns.find(run => run.id === mainRun.id);
|
|
1503
|
+
const mainSnapshotRun = {
|
|
1504
|
+
id: mainRun.id,
|
|
1505
|
+
parent_type: parentType,
|
|
1506
|
+
parent_id: parentId,
|
|
1507
|
+
run_index: mainRun.runIndex,
|
|
1508
|
+
label: mainRun.label,
|
|
1509
|
+
role: mainRun.role,
|
|
1510
|
+
agent_status: status,
|
|
1511
|
+
agent_log: parentLog,
|
|
1512
|
+
streaming_response: null,
|
|
1513
|
+
response_text: parentResponse,
|
|
1514
|
+
session_id: persistedMainRun?.session_id ?? null,
|
|
1515
|
+
created_at: persistedMainRun?.created_at ?? timestamp,
|
|
1516
|
+
updated_at: persistedMainRun?.updated_at ?? timestamp,
|
|
1517
|
+
};
|
|
1518
|
+
const mainRunIndex = persistedRuns.findIndex(run => run.id === mainRun.id);
|
|
1519
|
+
if (mainRunIndex >= 0) {
|
|
1520
|
+
persistedRuns[mainRunIndex] = mainSnapshotRun;
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
persistedRuns.push(mainSnapshotRun);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
persistedRuns.push({
|
|
1528
|
+
id: `${parentType}:${parentId}:main`,
|
|
1529
|
+
parent_type: parentType,
|
|
1530
|
+
parent_id: parentId,
|
|
1531
|
+
run_index: 0,
|
|
1532
|
+
label: 'Main agent',
|
|
1533
|
+
role: 'main',
|
|
1534
|
+
agent_status: status,
|
|
1535
|
+
agent_log: parentLog,
|
|
1536
|
+
streaming_response: null,
|
|
1537
|
+
response_text: parentResponse,
|
|
1538
|
+
session_id: null,
|
|
1539
|
+
created_at: timestamp,
|
|
1540
|
+
updated_at: timestamp,
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return createTranscriptRunSnapshots(persistedRuns.sort((left, right) => left.run_index - right.run_index));
|
|
1544
|
+
};
|
|
1545
|
+
return { applySnapshot, handle, finalize };
|
|
1546
|
+
};
|
|
1547
|
+
export const reconstructOpencodeSubagentTranscript = (parts, cwd) => {
|
|
1548
|
+
let log = '';
|
|
1549
|
+
let response = '';
|
|
1550
|
+
for (const persistedPart of parts) {
|
|
1551
|
+
if (persistedPart.role && persistedPart.role !== 'assistant') {
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
const part = persistedPart.data;
|
|
1555
|
+
const partType = part?.type;
|
|
1556
|
+
const eventType = partType === 'tool'
|
|
1557
|
+
? 'tool_use'
|
|
1558
|
+
: partType === 'step-start'
|
|
1559
|
+
? 'step_start'
|
|
1560
|
+
: partType === 'step-finish'
|
|
1561
|
+
? 'step_finish'
|
|
1562
|
+
: partType;
|
|
1563
|
+
const parsed = parseOpencodeJsonEvent({ type: eventType, part }, cwd);
|
|
1564
|
+
if (parsed.logFragment && parsed.logFragment !== parsed.responseFragment) {
|
|
1565
|
+
log = appendTranscriptText(log, parsed.logFragment);
|
|
1566
|
+
}
|
|
1567
|
+
if (parsed.responseFragment) {
|
|
1568
|
+
response = appendTranscriptText(response, parsed.responseFragment);
|
|
1569
|
+
}
|
|
1082
1570
|
}
|
|
1083
|
-
return {
|
|
1571
|
+
return { log: log || null, response: response || null };
|
|
1572
|
+
};
|
|
1573
|
+
export const createOpencodeSubagentMonitor = ({ initialParentSessionId, spawnTimestamp, cwd, tracker, onParentSessionId, taskSessionReader, discoverParentSession = true, }) => {
|
|
1574
|
+
const reader = taskSessionReader === undefined ? createOpencodeTaskSessionReader() : taskSessionReader;
|
|
1575
|
+
let parentSessionId = initialParentSessionId;
|
|
1576
|
+
let timer = null;
|
|
1577
|
+
const childSessionByCallId = new Map();
|
|
1578
|
+
const setParentSessionId = (sessionId) => {
|
|
1579
|
+
const normalized = sessionId?.trim() || null;
|
|
1580
|
+
if (!normalized || normalized === parentSessionId) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
parentSessionId = normalized;
|
|
1584
|
+
onParentSessionId?.(normalized);
|
|
1585
|
+
};
|
|
1586
|
+
const resolveChildSessions = (invocations, children) => {
|
|
1587
|
+
const claimedSessionIds = new Set(childSessionByCallId.values());
|
|
1588
|
+
for (const invocation of invocations) {
|
|
1589
|
+
if (invocation.childSessionId) {
|
|
1590
|
+
childSessionByCallId.set(invocation.callId, invocation.childSessionId);
|
|
1591
|
+
claimedSessionIds.add(invocation.childSessionId);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const unmatchedInvocations = invocations.filter(invocation => !childSessionByCallId.has(invocation.callId));
|
|
1595
|
+
const unmatchedChildren = children.filter(child => !claimedSessionIds.has(child.id));
|
|
1596
|
+
for (const invocation of unmatchedInvocations) {
|
|
1597
|
+
const description = invocation.label.replace(/^Subagent:\s*/i, '').toLowerCase();
|
|
1598
|
+
const titleMatch = unmatchedChildren.find(child => child.title?.toLowerCase().includes(description));
|
|
1599
|
+
if (!titleMatch) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
childSessionByCallId.set(invocation.callId, titleMatch.id);
|
|
1603
|
+
claimedSessionIds.add(titleMatch.id);
|
|
1604
|
+
}
|
|
1605
|
+
const stillUnmatchedInvocations = unmatchedInvocations.filter(invocation => !childSessionByCallId.has(invocation.callId));
|
|
1606
|
+
const stillUnmatchedChildren = unmatchedChildren.filter(child => !claimedSessionIds.has(child.id));
|
|
1607
|
+
if (stillUnmatchedInvocations.length === 1 && stillUnmatchedChildren.length === 1) {
|
|
1608
|
+
childSessionByCallId.set(stillUnmatchedInvocations[0].callId, stillUnmatchedChildren[0].id);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
const refresh = () => {
|
|
1612
|
+
if (!reader) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
try {
|
|
1616
|
+
if (!parentSessionId && discoverParentSession) {
|
|
1617
|
+
setParentSessionId(reader.findParentSessionSince(spawnTimestamp));
|
|
1618
|
+
}
|
|
1619
|
+
if (!parentSessionId) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const invocations = reader.listTaskInvocations(parentSessionId, spawnTimestamp);
|
|
1623
|
+
const children = reader.listChildSessions(parentSessionId, spawnTimestamp);
|
|
1624
|
+
resolveChildSessions(invocations, children);
|
|
1625
|
+
for (const invocation of invocations) {
|
|
1626
|
+
const childSessionId = childSessionByCallId.get(invocation.callId) ?? invocation.childSessionId;
|
|
1627
|
+
const transcript = childSessionId
|
|
1628
|
+
? reconstructOpencodeSubagentTranscript(reader.listSessionParts(childSessionId), cwd)
|
|
1629
|
+
: { log: null, response: null };
|
|
1630
|
+
tracker.applySnapshot({
|
|
1631
|
+
callId: invocation.callId,
|
|
1632
|
+
label: invocation.label,
|
|
1633
|
+
status: invocation.status,
|
|
1634
|
+
log: transcript.log,
|
|
1635
|
+
response: transcript.response ?? invocation.output,
|
|
1636
|
+
sessionId: childSessionId ?? null,
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
catch {
|
|
1641
|
+
// OpenCode persistence is a progressive enhancement; stdout remains the fallback.
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
const start = () => {
|
|
1645
|
+
if (!reader || timer) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
refresh();
|
|
1649
|
+
timer = setInterval(refresh, 150);
|
|
1650
|
+
timer.unref?.();
|
|
1651
|
+
};
|
|
1652
|
+
const stop = () => {
|
|
1653
|
+
if (timer) {
|
|
1654
|
+
clearInterval(timer);
|
|
1655
|
+
timer = null;
|
|
1656
|
+
}
|
|
1657
|
+
reader?.close();
|
|
1658
|
+
};
|
|
1659
|
+
return { refresh, setParentSessionId, start, stop };
|
|
1084
1660
|
};
|
|
1085
1661
|
const buildResponseTiming = ({ startTime, firstFinalAnswerTime, isStopped, }) => {
|
|
1086
1662
|
if (!startTime) {
|
|
@@ -1265,6 +1841,21 @@ export const stopSpecReviewAgent = async (specReviewRunId) => {
|
|
|
1265
1841
|
};
|
|
1266
1842
|
export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = null, resumeSessionId = null, sessionField = 'session_id', trackBuildProcess = false, emitSuccessUpdate = true, opencodeAgentMode, runStream, }) => {
|
|
1267
1843
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
1844
|
+
const subagentTracker = createSubagentRunTracker({
|
|
1845
|
+
parentType: 'ticket',
|
|
1846
|
+
parentId: ticketId,
|
|
1847
|
+
io: io,
|
|
1848
|
+
...(runStream
|
|
1849
|
+
? {
|
|
1850
|
+
mainRun: {
|
|
1851
|
+
id: runStream.runId,
|
|
1852
|
+
label: runStream.label,
|
|
1853
|
+
role: runStream.role,
|
|
1854
|
+
runIndex: runStream.runIndex,
|
|
1855
|
+
},
|
|
1856
|
+
}
|
|
1857
|
+
: {}),
|
|
1858
|
+
});
|
|
1268
1859
|
const writeRunStatus = (status, log, streamingResponse = null, options = {}) => {
|
|
1269
1860
|
if (runStream) {
|
|
1270
1861
|
updateAgentRunStatus(runStream.runId, status, log, streamingResponse);
|
|
@@ -1285,15 +1876,17 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1285
1876
|
if (runStream) {
|
|
1286
1877
|
finalizeAgentRun(runStream.runId, status, responseText);
|
|
1287
1878
|
emitAgentRunsUpdated(io, 'ticket', ticketId);
|
|
1288
|
-
return;
|
|
1879
|
+
return subagentTracker.finalize(status, log, responseText);
|
|
1289
1880
|
}
|
|
1290
1881
|
updateStatus(ticketId, status, log, io, responseText, options);
|
|
1882
|
+
return subagentTracker.finalize(status, log, responseText);
|
|
1291
1883
|
};
|
|
1292
1884
|
return new Promise((resolve, reject) => {
|
|
1293
1885
|
let sessionId = null;
|
|
1294
1886
|
let spawnTimestamp = null;
|
|
1295
1887
|
let sessionLookupTimer = null;
|
|
1296
1888
|
let hasResolvedOpencodeSessionId = Boolean(resumeSessionId);
|
|
1889
|
+
let opencodeSubagentMonitor = null;
|
|
1297
1890
|
let settled = false;
|
|
1298
1891
|
let startTime = null;
|
|
1299
1892
|
let firstFinalAnswerTime = null;
|
|
@@ -1305,6 +1898,7 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1305
1898
|
return;
|
|
1306
1899
|
}
|
|
1307
1900
|
settled = true;
|
|
1901
|
+
opencodeSubagentMonitor?.stop();
|
|
1308
1902
|
if (sessionLookupTimer) {
|
|
1309
1903
|
clearTimeout(sessionLookupTimer);
|
|
1310
1904
|
sessionLookupTimer = null;
|
|
@@ -1362,6 +1956,23 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1362
1956
|
activeBuildProcesses.set(ticketId, buildState);
|
|
1363
1957
|
}
|
|
1364
1958
|
startTime = Date.now();
|
|
1959
|
+
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
1960
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
1961
|
+
initialParentSessionId: resumeSessionId,
|
|
1962
|
+
spawnTimestamp,
|
|
1963
|
+
cwd,
|
|
1964
|
+
tracker: subagentTracker,
|
|
1965
|
+
discoverParentSession: false,
|
|
1966
|
+
onParentSessionId: discoveredSessionId => {
|
|
1967
|
+
if (hasResolvedOpencodeSessionId) {
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
hasResolvedOpencodeSessionId = true;
|
|
1971
|
+
writeRunSessionId(discoveredSessionId);
|
|
1972
|
+
},
|
|
1973
|
+
});
|
|
1974
|
+
opencodeSubagentMonitor.start();
|
|
1975
|
+
}
|
|
1365
1976
|
if (tool === 'claude') {
|
|
1366
1977
|
const logLines = [];
|
|
1367
1978
|
const responseLines = [];
|
|
@@ -1377,7 +1988,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1377
1988
|
remainder = lines.pop() ?? '';
|
|
1378
1989
|
for (const line of lines) {
|
|
1379
1990
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
|
|
1380
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(line, cwd);
|
|
1991
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(line, cwd);
|
|
1992
|
+
subagentTracker.handle(subagentEvents);
|
|
1381
1993
|
if (logFragment !== null) {
|
|
1382
1994
|
logLines.push(logFragment);
|
|
1383
1995
|
}
|
|
@@ -1394,7 +2006,8 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1394
2006
|
progressUpdater.cancel();
|
|
1395
2007
|
if (remainder.trim()) {
|
|
1396
2008
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
1397
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(remainder, cwd);
|
|
2009
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(remainder, cwd);
|
|
2010
|
+
subagentTracker.handle(subagentEvents);
|
|
1398
2011
|
if (logFragment !== null) {
|
|
1399
2012
|
logLines.push(logFragment);
|
|
1400
2013
|
}
|
|
@@ -1408,32 +2021,33 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1408
2021
|
const modelError = isModelResolutionError(finalLog);
|
|
1409
2022
|
const timing = startTime ? { totalMs: Date.now() - startTime } : null;
|
|
1410
2023
|
if (buildState?.stopRequested) {
|
|
1411
|
-
finalizeRun('stopped', finalLog, finalResponse);
|
|
1412
|
-
resolveOnce({ response: finalResponse, responseTiming: timing, usageSnapshot });
|
|
2024
|
+
const transcriptRuns = finalizeRun('stopped', finalLog, finalResponse);
|
|
2025
|
+
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing, usageSnapshot }, transcriptRuns));
|
|
1413
2026
|
return;
|
|
1414
2027
|
}
|
|
1415
2028
|
if (code === 0 && !modelError) {
|
|
1416
|
-
finalizeRun('done', finalLog, finalResponse, { emit: emitSuccessUpdate });
|
|
1417
|
-
resolveOnce({ response: finalResponse, responseTiming: timing, usageSnapshot });
|
|
2029
|
+
const transcriptRuns = finalizeRun('done', finalLog, finalResponse, { emit: emitSuccessUpdate });
|
|
2030
|
+
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing, usageSnapshot }, transcriptRuns));
|
|
1418
2031
|
return;
|
|
1419
2032
|
}
|
|
1420
|
-
finalizeRun('error', finalLog + (errOutput ? '\n\n' + errOutput : ''), finalResponse);
|
|
1421
|
-
rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}${errOutput !== finalLog && errOutput ? '\n\n' + errOutput : ''}`));
|
|
2033
|
+
const transcriptRuns = finalizeRun('error', finalLog + (errOutput ? '\n\n' + errOutput : ''), finalResponse);
|
|
2034
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}${errOutput !== finalLog && errOutput ? '\n\n' + errOutput : ''}`), transcriptRuns));
|
|
1422
2035
|
});
|
|
1423
2036
|
child.on('error', err => {
|
|
1424
2037
|
progressUpdater.cancel();
|
|
1425
2038
|
if (buildState?.stopRequested) {
|
|
1426
|
-
finalizeRun('stopped', null, null);
|
|
1427
|
-
resolveOnce({ response: null, responseTiming: null, usageSnapshot });
|
|
2039
|
+
const transcriptRuns = finalizeRun('stopped', null, null);
|
|
2040
|
+
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null, usageSnapshot }, transcriptRuns));
|
|
1428
2041
|
return;
|
|
1429
2042
|
}
|
|
1430
|
-
finalizeRun('error', getErrorMessage(err), null);
|
|
1431
|
-
rejectOnce(err
|
|
2043
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(err), null);
|
|
2044
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
1432
2045
|
});
|
|
1433
2046
|
return;
|
|
1434
2047
|
}
|
|
1435
2048
|
const logLines = [];
|
|
1436
2049
|
const responseLines = [];
|
|
2050
|
+
const recoveryTracker = createOpencodeRecoveryTracker();
|
|
1437
2051
|
let remainder = '';
|
|
1438
2052
|
const stderrChunks = [];
|
|
1439
2053
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
@@ -1459,12 +2073,12 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1459
2073
|
catch { /* best-effort timing */ }
|
|
1460
2074
|
}
|
|
1461
2075
|
}
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
if (
|
|
1467
|
-
|
|
2076
|
+
const parsed = parseOpencodeJsonLine(line);
|
|
2077
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
2078
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2079
|
+
subagentTracker.handle(subagentEvents);
|
|
2080
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2081
|
+
firstFinalAnswerTime = null;
|
|
1468
2082
|
}
|
|
1469
2083
|
if (logFragment !== null || responseFragment !== null) {
|
|
1470
2084
|
progressUpdater.schedule();
|
|
@@ -1489,31 +2103,35 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1489
2103
|
sessionLookupTimer = null;
|
|
1490
2104
|
}
|
|
1491
2105
|
if (remainder.trim()) {
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (
|
|
1497
|
-
|
|
2106
|
+
const parsed = parseOpencodeJsonLine(remainder);
|
|
2107
|
+
const { subagentEvents, providerSessionId } = parsed;
|
|
2108
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2109
|
+
subagentTracker.handle(subagentEvents);
|
|
2110
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2111
|
+
firstFinalAnswerTime = null;
|
|
1498
2112
|
}
|
|
1499
2113
|
}
|
|
1500
2114
|
const stderrOutput = Buffer.concat(stderrChunks).toString();
|
|
2115
|
+
opencodeSubagentMonitor?.refresh();
|
|
1501
2116
|
const log = logLines.join('\n\n') || stderrOutput;
|
|
1502
|
-
const
|
|
2117
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
2118
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
2119
|
+
? recoveredResponse
|
|
2120
|
+
: responseLines.join('\n\n') || stderrOutput;
|
|
1503
2121
|
const modelError = isModelResolutionError(log);
|
|
1504
2122
|
const timing = buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: Boolean(buildState?.stopRequested) });
|
|
1505
2123
|
if (buildState?.stopRequested) {
|
|
1506
|
-
finalizeRun('stopped', log, finalResponse);
|
|
1507
|
-
resolveOnce({ response: finalResponse, responseTiming: timing });
|
|
2124
|
+
const transcriptRuns = finalizeRun('stopped', log, finalResponse);
|
|
2125
|
+
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing }, transcriptRuns));
|
|
1508
2126
|
return;
|
|
1509
2127
|
}
|
|
1510
|
-
if (code
|
|
1511
|
-
finalizeRun('done', log, finalResponse, { emit: emitSuccessUpdate });
|
|
1512
|
-
resolveOnce({ response: finalResponse, responseTiming: timing });
|
|
2128
|
+
if (isSuccessfulOpencodeExit(code, modelError, recoveryTracker)) {
|
|
2129
|
+
const transcriptRuns = finalizeRun('done', log, finalResponse, { emit: emitSuccessUpdate });
|
|
2130
|
+
resolveOnce(withTranscriptRuns({ response: finalResponse, responseTiming: timing }, transcriptRuns));
|
|
1513
2131
|
return;
|
|
1514
2132
|
}
|
|
1515
|
-
finalizeRun('error', log, finalResponse);
|
|
1516
|
-
rejectOnce(new Error(`Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`));
|
|
2133
|
+
const transcriptRuns = finalizeRun('error', log, finalResponse);
|
|
2134
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`), transcriptRuns));
|
|
1517
2135
|
});
|
|
1518
2136
|
child.on('error', err => {
|
|
1519
2137
|
progressUpdater.cancel();
|
|
@@ -1522,21 +2140,27 @@ export const runAgent = ({ ticketId, tool, prompt, cwd, io, model, variant = nul
|
|
|
1522
2140
|
sessionLookupTimer = null;
|
|
1523
2141
|
}
|
|
1524
2142
|
if (buildState?.stopRequested) {
|
|
1525
|
-
finalizeRun('stopped', null, null);
|
|
1526
|
-
resolveOnce({ response: null, responseTiming: null });
|
|
2143
|
+
const transcriptRuns = finalizeRun('stopped', null, null);
|
|
2144
|
+
resolveOnce(withTranscriptRuns({ response: null, responseTiming: null }, transcriptRuns));
|
|
1527
2145
|
return;
|
|
1528
2146
|
}
|
|
1529
|
-
finalizeRun('error', getErrorMessage(err), null);
|
|
1530
|
-
rejectOnce(err
|
|
2147
|
+
const transcriptRuns = finalizeRun('error', getErrorMessage(err), null);
|
|
2148
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
1531
2149
|
});
|
|
1532
2150
|
});
|
|
1533
2151
|
};
|
|
1534
2152
|
export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, opencodeAgentMode = 'build', }) => {
|
|
1535
2153
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2154
|
+
const subagentTracker = createSubagentRunTracker({
|
|
2155
|
+
parentType: 'chat',
|
|
2156
|
+
parentId: chatSessionId,
|
|
2157
|
+
io,
|
|
2158
|
+
});
|
|
1536
2159
|
return new Promise((resolve, reject) => {
|
|
1537
2160
|
let sessionId = null;
|
|
1538
2161
|
let spawnTimestamp = null;
|
|
1539
2162
|
let opencodeMetadataRefresher = null;
|
|
2163
|
+
let opencodeSubagentMonitor = null;
|
|
1540
2164
|
let settled = false;
|
|
1541
2165
|
let startTime = null;
|
|
1542
2166
|
let firstFinalAnswerTime = null;
|
|
@@ -1552,6 +2176,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1552
2176
|
}
|
|
1553
2177
|
settled = true;
|
|
1554
2178
|
opencodeMetadataRefresher?.stop();
|
|
2179
|
+
opencodeSubagentMonitor?.stop();
|
|
1555
2180
|
untrackChatProcess(chatSessionId, chatState);
|
|
1556
2181
|
handler(value);
|
|
1557
2182
|
};
|
|
@@ -1598,6 +2223,19 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1598
2223
|
writeClaudeStdin(child, buildClaudeStdinMessage(prompt, files, cwd));
|
|
1599
2224
|
}
|
|
1600
2225
|
startTime = Date.now();
|
|
2226
|
+
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
2227
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
2228
|
+
initialParentSessionId: resumeSessionId,
|
|
2229
|
+
spawnTimestamp,
|
|
2230
|
+
cwd,
|
|
2231
|
+
tracker: subagentTracker,
|
|
2232
|
+
discoverParentSession: false,
|
|
2233
|
+
onParentSessionId: discoveredSessionId => {
|
|
2234
|
+
updateChatSessionId(chatSessionId, 'opencode', discoveredSessionId, io);
|
|
2235
|
+
},
|
|
2236
|
+
});
|
|
2237
|
+
opencodeSubagentMonitor.start();
|
|
2238
|
+
}
|
|
1601
2239
|
if (tool === 'claude') {
|
|
1602
2240
|
const logLines = [];
|
|
1603
2241
|
const responseLines = [];
|
|
@@ -1611,7 +2249,8 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1611
2249
|
const lines = text.split('\n');
|
|
1612
2250
|
remainder = lines.pop() ?? '';
|
|
1613
2251
|
for (const line of lines) {
|
|
1614
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(line);
|
|
2252
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(line);
|
|
2253
|
+
subagentTracker.handle(subagentEvents);
|
|
1615
2254
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
|
|
1616
2255
|
if (logFragment !== null) {
|
|
1617
2256
|
logLines.push(logFragment);
|
|
@@ -1629,7 +2268,8 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1629
2268
|
progressUpdater.cancel();
|
|
1630
2269
|
if (remainder.trim()) {
|
|
1631
2270
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
1632
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(remainder);
|
|
2271
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(remainder);
|
|
2272
|
+
subagentTracker.handle(subagentEvents);
|
|
1633
2273
|
if (logFragment !== null) {
|
|
1634
2274
|
logLines.push(logFragment);
|
|
1635
2275
|
}
|
|
@@ -1647,12 +2287,13 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1647
2287
|
response: finalResponse,
|
|
1648
2288
|
});
|
|
1649
2289
|
updateChatSessionStatus(chatSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
1650
|
-
|
|
2290
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2291
|
+
resolveOnce(withTranscriptRuns({
|
|
1651
2292
|
completionStatus: 'stopped',
|
|
1652
2293
|
finalResponse: preservedOutput.response,
|
|
1653
2294
|
usageSnapshot,
|
|
1654
2295
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1655
|
-
});
|
|
2296
|
+
}, transcriptRuns));
|
|
1656
2297
|
return;
|
|
1657
2298
|
}
|
|
1658
2299
|
const modelError = isModelResolutionError(finalLog);
|
|
@@ -1663,16 +2304,18 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1663
2304
|
response: finalResponse,
|
|
1664
2305
|
});
|
|
1665
2306
|
updateChatSessionStatus(chatSessionId, 'running', finalOutput.log, io, finalOutput.response);
|
|
1666
|
-
|
|
2307
|
+
const transcriptRuns = subagentTracker.finalize('done', finalOutput.log, finalOutput.response);
|
|
2308
|
+
resolveOnce(withTranscriptRuns({
|
|
1667
2309
|
completionStatus: 'done',
|
|
1668
2310
|
finalResponse: finalOutput.response,
|
|
1669
2311
|
usageSnapshot,
|
|
1670
2312
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: false }),
|
|
1671
|
-
});
|
|
2313
|
+
}, transcriptRuns));
|
|
1672
2314
|
return;
|
|
1673
2315
|
}
|
|
1674
2316
|
updateChatSessionStatus(chatSessionId, 'error', finalLog + (errOutput ? '\n\n' + errOutput : ''), io, finalResponse);
|
|
1675
|
-
|
|
2317
|
+
const transcriptRuns = subagentTracker.finalize('error', finalLog, finalResponse);
|
|
2318
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}${errOutput !== finalLog && errOutput ? '\n\n' + errOutput : ''}`), transcriptRuns));
|
|
1676
2319
|
});
|
|
1677
2320
|
child.on('error', err => {
|
|
1678
2321
|
progressUpdater.cancel();
|
|
@@ -1685,21 +2328,24 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1685
2328
|
response,
|
|
1686
2329
|
});
|
|
1687
2330
|
updateChatSessionStatus(chatSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
1688
|
-
|
|
2331
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2332
|
+
resolveOnce(withTranscriptRuns({
|
|
1689
2333
|
completionStatus: 'stopped',
|
|
1690
2334
|
finalResponse: preservedOutput.response,
|
|
1691
2335
|
usageSnapshot,
|
|
1692
2336
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1693
|
-
});
|
|
2337
|
+
}, transcriptRuns));
|
|
1694
2338
|
return;
|
|
1695
2339
|
}
|
|
1696
2340
|
updateChatSessionStatus(chatSessionId, 'error', getErrorMessage(err), io);
|
|
1697
|
-
|
|
2341
|
+
const transcriptRuns = subagentTracker.finalize('error', getErrorMessage(err), null);
|
|
2342
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
1698
2343
|
});
|
|
1699
2344
|
return;
|
|
1700
2345
|
}
|
|
1701
2346
|
const logLines = [];
|
|
1702
2347
|
const responseLines = [];
|
|
2348
|
+
const recoveryTracker = createOpencodeRecoveryTracker();
|
|
1703
2349
|
let remainder = '';
|
|
1704
2350
|
const stderrChunks = [];
|
|
1705
2351
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
@@ -1726,12 +2372,12 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1726
2372
|
catch { /* best-effort timing */ }
|
|
1727
2373
|
}
|
|
1728
2374
|
}
|
|
1729
|
-
const
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
if (
|
|
1734
|
-
|
|
2375
|
+
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
2376
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
2377
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2378
|
+
subagentTracker.handle(subagentEvents);
|
|
2379
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2380
|
+
firstFinalAnswerTime = null;
|
|
1735
2381
|
}
|
|
1736
2382
|
if (logFragment !== null || responseFragment !== null) {
|
|
1737
2383
|
progressUpdater.schedule();
|
|
@@ -1746,17 +2392,20 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1746
2392
|
progressUpdater.cancel();
|
|
1747
2393
|
if (remainder.trim()) {
|
|
1748
2394
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
if (
|
|
1754
|
-
|
|
2395
|
+
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
2396
|
+
const { subagentEvents, providerSessionId } = parsed;
|
|
2397
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2398
|
+
subagentTracker.handle(subagentEvents);
|
|
2399
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2400
|
+
firstFinalAnswerTime = null;
|
|
1755
2401
|
}
|
|
1756
2402
|
}
|
|
1757
2403
|
const stderrOutput = Buffer.concat(stderrChunks).toString();
|
|
1758
2404
|
const log = logLines.join('\n\n') || stderrOutput;
|
|
1759
|
-
const
|
|
2405
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
2406
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
2407
|
+
? recoveredResponse
|
|
2408
|
+
: responseLines.join('\n\n') || stderrOutput;
|
|
1760
2409
|
const preservedOutput = chatState.stopRequested
|
|
1761
2410
|
? getPreservedChatOutput({
|
|
1762
2411
|
chatSessionId,
|
|
@@ -1769,6 +2418,7 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1769
2418
|
?? preservedOutput?.opencodeSessionId
|
|
1770
2419
|
?? getChatSession(chatSessionId)?.opencode_session_id
|
|
1771
2420
|
?? resumeSessionId;
|
|
2421
|
+
opencodeSubagentMonitor?.refresh();
|
|
1772
2422
|
if (chatState.stopRequested) {
|
|
1773
2423
|
if (resolvedOpencodeSessionId) {
|
|
1774
2424
|
usageSnapshot = resolveOpencodeUsageSnapshot({
|
|
@@ -1778,16 +2428,17 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1778
2428
|
});
|
|
1779
2429
|
}
|
|
1780
2430
|
updateChatSessionStatus(chatSessionId, 'running', preservedOutput?.log ?? null, io, preservedOutput?.response ?? null);
|
|
1781
|
-
|
|
2431
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput?.log ?? null, preservedOutput?.response ?? null);
|
|
2432
|
+
resolveOnce(withTranscriptRuns({
|
|
1782
2433
|
completionStatus: 'stopped',
|
|
1783
2434
|
finalResponse: preservedOutput?.response ?? null,
|
|
1784
2435
|
usageSnapshot,
|
|
1785
2436
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1786
|
-
});
|
|
2437
|
+
}, transcriptRuns));
|
|
1787
2438
|
return;
|
|
1788
2439
|
}
|
|
1789
2440
|
const modelError = isModelResolutionError(log);
|
|
1790
|
-
if (code
|
|
2441
|
+
if (isSuccessfulOpencodeExit(code, modelError, recoveryTracker)) {
|
|
1791
2442
|
const finalOutput = getPreservedChatOutput({
|
|
1792
2443
|
chatSessionId,
|
|
1793
2444
|
log,
|
|
@@ -1801,16 +2452,18 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1801
2452
|
});
|
|
1802
2453
|
}
|
|
1803
2454
|
updateChatSessionStatus(chatSessionId, 'running', finalOutput.log, io, finalOutput.response);
|
|
1804
|
-
|
|
2455
|
+
const transcriptRuns = subagentTracker.finalize('done', finalOutput.log, finalOutput.response);
|
|
2456
|
+
resolveOnce(withTranscriptRuns({
|
|
1805
2457
|
completionStatus: 'done',
|
|
1806
2458
|
finalResponse: finalOutput.response,
|
|
1807
2459
|
usageSnapshot,
|
|
1808
2460
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: false }),
|
|
1809
|
-
});
|
|
2461
|
+
}, transcriptRuns));
|
|
1810
2462
|
return;
|
|
1811
2463
|
}
|
|
1812
2464
|
updateChatSessionStatus(chatSessionId, 'error', log, io, finalResponse);
|
|
1813
|
-
|
|
2465
|
+
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
2466
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`), transcriptRuns));
|
|
1814
2467
|
});
|
|
1815
2468
|
child.on('error', err => {
|
|
1816
2469
|
progressUpdater.cancel();
|
|
@@ -1823,25 +2476,33 @@ export const runChatAgent = ({ chatSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1823
2476
|
response,
|
|
1824
2477
|
});
|
|
1825
2478
|
updateChatSessionStatus(chatSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
1826
|
-
|
|
2479
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2480
|
+
resolveOnce(withTranscriptRuns({
|
|
1827
2481
|
completionStatus: 'stopped',
|
|
1828
2482
|
finalResponse: preservedOutput.response,
|
|
1829
2483
|
usageSnapshot,
|
|
1830
2484
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1831
|
-
});
|
|
2485
|
+
}, transcriptRuns));
|
|
1832
2486
|
return;
|
|
1833
2487
|
}
|
|
1834
2488
|
updateChatSessionStatus(chatSessionId, 'error', getErrorMessage(err), io);
|
|
1835
|
-
|
|
2489
|
+
const transcriptRuns = subagentTracker.finalize('error', getErrorMessage(err), null);
|
|
2490
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
1836
2491
|
});
|
|
1837
2492
|
});
|
|
1838
2493
|
};
|
|
1839
2494
|
export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io, model, variant = null, resumeSessionId = null, }) => {
|
|
1840
2495
|
const resolvedModel = resolveAgentModel(tool, model);
|
|
2496
|
+
const subagentTracker = createSubagentRunTracker({
|
|
2497
|
+
parentType: 'spec',
|
|
2498
|
+
parentId: specSessionId,
|
|
2499
|
+
io,
|
|
2500
|
+
});
|
|
1841
2501
|
return new Promise((resolve, reject) => {
|
|
1842
2502
|
let sessionId = null;
|
|
1843
2503
|
let spawnTimestamp = null;
|
|
1844
2504
|
let opencodeMetadataRefresher = null;
|
|
2505
|
+
let opencodeSubagentMonitor = null;
|
|
1845
2506
|
let settled = false;
|
|
1846
2507
|
let startTime = null;
|
|
1847
2508
|
let firstFinalAnswerTime = null;
|
|
@@ -1857,6 +2518,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1857
2518
|
}
|
|
1858
2519
|
settled = true;
|
|
1859
2520
|
opencodeMetadataRefresher?.stop();
|
|
2521
|
+
opencodeSubagentMonitor?.stop();
|
|
1860
2522
|
untrackSpecProcess(specSessionId, specState);
|
|
1861
2523
|
handler(value);
|
|
1862
2524
|
};
|
|
@@ -1903,6 +2565,19 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1903
2565
|
writeClaudeStdin(child, buildClaudeStdinMessage(prompt, files, cwd));
|
|
1904
2566
|
}
|
|
1905
2567
|
startTime = Date.now();
|
|
2568
|
+
if (tool === 'opencode' && spawnTimestamp !== null) {
|
|
2569
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
2570
|
+
initialParentSessionId: resumeSessionId,
|
|
2571
|
+
spawnTimestamp,
|
|
2572
|
+
cwd,
|
|
2573
|
+
tracker: subagentTracker,
|
|
2574
|
+
discoverParentSession: false,
|
|
2575
|
+
onParentSessionId: discoveredSessionId => {
|
|
2576
|
+
updateSpecSessionIdLocal(specSessionId, 'opencode', discoveredSessionId, io);
|
|
2577
|
+
},
|
|
2578
|
+
});
|
|
2579
|
+
opencodeSubagentMonitor.start();
|
|
2580
|
+
}
|
|
1906
2581
|
if (tool === 'claude') {
|
|
1907
2582
|
const logLines = [];
|
|
1908
2583
|
const responseLines = [];
|
|
@@ -1916,7 +2591,8 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1916
2591
|
const lines = text.split('\n');
|
|
1917
2592
|
remainder = lines.pop() ?? '';
|
|
1918
2593
|
for (const line of lines) {
|
|
1919
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(line);
|
|
2594
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(line);
|
|
2595
|
+
subagentTracker.handle(subagentEvents);
|
|
1920
2596
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, line);
|
|
1921
2597
|
if (logFragment !== null) {
|
|
1922
2598
|
logLines.push(logFragment);
|
|
@@ -1934,7 +2610,8 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1934
2610
|
progressUpdater.cancel();
|
|
1935
2611
|
if (remainder.trim()) {
|
|
1936
2612
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
1937
|
-
const { logFragment, responseFragment } = parseClaudeStreamJsonLine(remainder);
|
|
2613
|
+
const { logFragment, responseFragment, subagentEvents } = parseClaudeStreamJsonLine(remainder);
|
|
2614
|
+
subagentTracker.handle(subagentEvents);
|
|
1938
2615
|
if (logFragment !== null) {
|
|
1939
2616
|
logLines.push(logFragment);
|
|
1940
2617
|
}
|
|
@@ -1952,12 +2629,13 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1952
2629
|
response: finalResponse,
|
|
1953
2630
|
});
|
|
1954
2631
|
updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
1955
|
-
|
|
2632
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2633
|
+
resolveOnce(withTranscriptRuns({
|
|
1956
2634
|
completionStatus: 'stopped',
|
|
1957
2635
|
finalResponse: preservedOutput.response,
|
|
1958
2636
|
usageSnapshot,
|
|
1959
2637
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1960
|
-
});
|
|
2638
|
+
}, transcriptRuns));
|
|
1961
2639
|
return;
|
|
1962
2640
|
}
|
|
1963
2641
|
const modelError = isModelResolutionError(finalLog);
|
|
@@ -1968,16 +2646,18 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1968
2646
|
response: finalResponse,
|
|
1969
2647
|
});
|
|
1970
2648
|
updateSpecSessionStatusLocal(specSessionId, 'running', finalOutput.log, io, finalOutput.response);
|
|
1971
|
-
|
|
2649
|
+
const transcriptRuns = subagentTracker.finalize('done', finalOutput.log, finalOutput.response);
|
|
2650
|
+
resolveOnce(withTranscriptRuns({
|
|
1972
2651
|
completionStatus: 'done',
|
|
1973
2652
|
finalResponse: finalOutput.response,
|
|
1974
2653
|
usageSnapshot,
|
|
1975
2654
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: false }),
|
|
1976
|
-
});
|
|
2655
|
+
}, transcriptRuns));
|
|
1977
2656
|
return;
|
|
1978
2657
|
}
|
|
1979
2658
|
updateSpecSessionStatusLocal(specSessionId, 'error', finalLog + (errOutput ? '\n\n' + errOutput : ''), io, finalResponse);
|
|
1980
|
-
|
|
2659
|
+
const transcriptRuns = subagentTracker.finalize('error', finalLog, finalResponse);
|
|
2660
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${finalLog}${errOutput !== finalLog && errOutput ? '\n\n' + errOutput : ''}`), transcriptRuns));
|
|
1981
2661
|
});
|
|
1982
2662
|
child.on('error', err => {
|
|
1983
2663
|
progressUpdater.cancel();
|
|
@@ -1990,21 +2670,24 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
1990
2670
|
response,
|
|
1991
2671
|
});
|
|
1992
2672
|
updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
1993
|
-
|
|
2673
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2674
|
+
resolveOnce(withTranscriptRuns({
|
|
1994
2675
|
completionStatus: 'stopped',
|
|
1995
2676
|
finalResponse: preservedOutput.response,
|
|
1996
2677
|
usageSnapshot,
|
|
1997
2678
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
1998
|
-
});
|
|
2679
|
+
}, transcriptRuns));
|
|
1999
2680
|
return;
|
|
2000
2681
|
}
|
|
2001
2682
|
updateSpecSessionStatusLocal(specSessionId, 'error', getErrorMessage(err), io);
|
|
2002
|
-
|
|
2683
|
+
const transcriptRuns = subagentTracker.finalize('error', getErrorMessage(err), null);
|
|
2684
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
2003
2685
|
});
|
|
2004
2686
|
return;
|
|
2005
2687
|
}
|
|
2006
2688
|
const logLines = [];
|
|
2007
2689
|
const responseLines = [];
|
|
2690
|
+
const recoveryTracker = createOpencodeRecoveryTracker();
|
|
2008
2691
|
let remainder = '';
|
|
2009
2692
|
const stderrChunks = [];
|
|
2010
2693
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
@@ -2031,12 +2714,12 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2031
2714
|
catch { /* best-effort timing */ }
|
|
2032
2715
|
}
|
|
2033
2716
|
}
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
if (
|
|
2039
|
-
|
|
2717
|
+
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
2718
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsed;
|
|
2719
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2720
|
+
subagentTracker.handle(subagentEvents);
|
|
2721
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2722
|
+
firstFinalAnswerTime = null;
|
|
2040
2723
|
}
|
|
2041
2724
|
if (logFragment !== null || responseFragment !== null) {
|
|
2042
2725
|
progressUpdater.schedule();
|
|
@@ -2049,19 +2732,23 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2049
2732
|
}
|
|
2050
2733
|
child.on('close', code => {
|
|
2051
2734
|
progressUpdater.cancel();
|
|
2735
|
+
opencodeSubagentMonitor?.refresh();
|
|
2052
2736
|
if (remainder.trim()) {
|
|
2053
2737
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2738
|
+
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
2739
|
+
const { subagentEvents, providerSessionId } = parsed;
|
|
2740
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
2741
|
+
subagentTracker.handle(subagentEvents);
|
|
2742
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2743
|
+
firstFinalAnswerTime = null;
|
|
2060
2744
|
}
|
|
2061
2745
|
}
|
|
2062
2746
|
const stderrOutput = Buffer.concat(stderrChunks).toString();
|
|
2063
2747
|
const log = logLines.join('\n\n') || stderrOutput;
|
|
2064
|
-
const
|
|
2748
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
2749
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
2750
|
+
? recoveredResponse
|
|
2751
|
+
: responseLines.join('\n\n') || stderrOutput;
|
|
2065
2752
|
const preservedOutput = specState.stopRequested
|
|
2066
2753
|
? getPreservedSpecOutput({
|
|
2067
2754
|
specSessionId,
|
|
@@ -2074,6 +2761,7 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2074
2761
|
?? preservedOutput?.opencodeSessionId
|
|
2075
2762
|
?? getSpecSession(specSessionId)?.opencode_session_id
|
|
2076
2763
|
?? resumeSessionId;
|
|
2764
|
+
opencodeSubagentMonitor?.refresh();
|
|
2077
2765
|
if (specState.stopRequested) {
|
|
2078
2766
|
if (resolvedOpencodeSessionId) {
|
|
2079
2767
|
usageSnapshot = resolveOpencodeUsageSnapshot({
|
|
@@ -2083,16 +2771,17 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2083
2771
|
});
|
|
2084
2772
|
}
|
|
2085
2773
|
updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput?.log ?? null, io, preservedOutput?.response ?? null);
|
|
2086
|
-
|
|
2774
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput?.log ?? null, preservedOutput?.response ?? null);
|
|
2775
|
+
resolveOnce(withTranscriptRuns({
|
|
2087
2776
|
completionStatus: 'stopped',
|
|
2088
2777
|
finalResponse: preservedOutput?.response ?? null,
|
|
2089
2778
|
usageSnapshot,
|
|
2090
2779
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
2091
|
-
});
|
|
2780
|
+
}, transcriptRuns));
|
|
2092
2781
|
return;
|
|
2093
2782
|
}
|
|
2094
2783
|
const modelError = isModelResolutionError(log);
|
|
2095
|
-
if (code
|
|
2784
|
+
if (isSuccessfulOpencodeExit(code, modelError, recoveryTracker)) {
|
|
2096
2785
|
const finalOutput = getPreservedSpecOutput({
|
|
2097
2786
|
specSessionId,
|
|
2098
2787
|
log,
|
|
@@ -2106,16 +2795,18 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2106
2795
|
});
|
|
2107
2796
|
}
|
|
2108
2797
|
updateSpecSessionStatusLocal(specSessionId, 'running', finalOutput.log, io, finalOutput.response);
|
|
2109
|
-
|
|
2798
|
+
const transcriptRuns = subagentTracker.finalize('done', finalOutput.log, finalOutput.response);
|
|
2799
|
+
resolveOnce(withTranscriptRuns({
|
|
2110
2800
|
completionStatus: 'done',
|
|
2111
2801
|
finalResponse: finalOutput.response,
|
|
2112
2802
|
usageSnapshot,
|
|
2113
2803
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: false }),
|
|
2114
|
-
});
|
|
2804
|
+
}, transcriptRuns));
|
|
2115
2805
|
return;
|
|
2116
2806
|
}
|
|
2117
2807
|
updateSpecSessionStatusLocal(specSessionId, 'error', log, io, finalResponse);
|
|
2118
|
-
|
|
2808
|
+
const transcriptRuns = subagentTracker.finalize('error', log, finalResponse);
|
|
2809
|
+
rejectOnce(attachTranscriptRunsToError(new Error(`Agent exited with code ${code ?? 'null'}\n${log}${stderrOutput !== log && stderrOutput ? '\n\n' + stderrOutput : ''}`), transcriptRuns));
|
|
2119
2810
|
});
|
|
2120
2811
|
child.on('error', err => {
|
|
2121
2812
|
progressUpdater.cancel();
|
|
@@ -2128,16 +2819,18 @@ export const runSpecAgent = ({ specSessionId, tool, prompt, files = [], cwd, io,
|
|
|
2128
2819
|
response,
|
|
2129
2820
|
});
|
|
2130
2821
|
updateSpecSessionStatusLocal(specSessionId, 'running', preservedOutput.log, io, preservedOutput.response);
|
|
2131
|
-
|
|
2822
|
+
const transcriptRuns = subagentTracker.finalize('stopped', preservedOutput.log, preservedOutput.response);
|
|
2823
|
+
resolveOnce(withTranscriptRuns({
|
|
2132
2824
|
completionStatus: 'stopped',
|
|
2133
2825
|
finalResponse: preservedOutput.response,
|
|
2134
2826
|
usageSnapshot,
|
|
2135
2827
|
responseTiming: buildResponseTiming({ startTime, firstFinalAnswerTime, isStopped: true }),
|
|
2136
|
-
});
|
|
2828
|
+
}, transcriptRuns));
|
|
2137
2829
|
return;
|
|
2138
2830
|
}
|
|
2139
2831
|
updateSpecSessionStatusLocal(specSessionId, 'error', getErrorMessage(err), io);
|
|
2140
|
-
|
|
2832
|
+
const transcriptRuns = subagentTracker.finalize('error', getErrorMessage(err), null);
|
|
2833
|
+
rejectOnce(attachTranscriptRunsToError(err, transcriptRuns));
|
|
2141
2834
|
});
|
|
2142
2835
|
});
|
|
2143
2836
|
};
|
|
@@ -2269,6 +2962,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2269
2962
|
}
|
|
2270
2963
|
const logLines = [];
|
|
2271
2964
|
const responseLines = [];
|
|
2965
|
+
const recoveryTracker = createOpencodeRecoveryTracker();
|
|
2272
2966
|
let remainder = '';
|
|
2273
2967
|
const stderrChunks = [];
|
|
2274
2968
|
const progressUpdater = createThrottledProgressUpdater(() => {
|
|
@@ -2295,12 +2989,10 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2295
2989
|
catch { /* best-effort timing */ }
|
|
2296
2990
|
}
|
|
2297
2991
|
}
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
if (responseFragment !== null) {
|
|
2303
|
-
responseLines.push(responseFragment);
|
|
2992
|
+
const parsed = parseOpencodeJsonLine(line, cwd);
|
|
2993
|
+
const { logFragment, responseFragment } = parsed;
|
|
2994
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
2995
|
+
firstFinalAnswerTime = null;
|
|
2304
2996
|
}
|
|
2305
2997
|
if (logFragment !== null || responseFragment !== null) {
|
|
2306
2998
|
progressUpdater.schedule();
|
|
@@ -2312,17 +3004,17 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2312
3004
|
progressUpdater.cancel();
|
|
2313
3005
|
if (remainder.trim()) {
|
|
2314
3006
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
2315
|
-
const
|
|
2316
|
-
if (
|
|
2317
|
-
|
|
2318
|
-
}
|
|
2319
|
-
if (responseFragment !== null) {
|
|
2320
|
-
responseLines.push(responseFragment);
|
|
3007
|
+
const parsed = parseOpencodeJsonLine(remainder, cwd);
|
|
3008
|
+
if (appendOpencodeTranscriptLine({ parsed, logLines, responseLines, recoveryTracker })) {
|
|
3009
|
+
firstFinalAnswerTime = null;
|
|
2321
3010
|
}
|
|
2322
3011
|
}
|
|
2323
3012
|
const stderrOutput = Buffer.concat(stderrChunks).toString();
|
|
2324
3013
|
const log = logLines.join('\n\n') || stderrOutput;
|
|
2325
|
-
const
|
|
3014
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
3015
|
+
const finalResponse = recoveryTracker.hasRecoveredFinalAnswer()
|
|
3016
|
+
? recoveredResponse
|
|
3017
|
+
: responseLines.join('\n\n') || stderrOutput;
|
|
2326
3018
|
if (reviewState.stopRequested) {
|
|
2327
3019
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
2328
3020
|
resolveOnce({
|
|
@@ -2334,7 +3026,7 @@ export const runSpecReviewAgent = ({ specReviewRunId, tool, prompt, files = [],
|
|
|
2334
3026
|
return;
|
|
2335
3027
|
}
|
|
2336
3028
|
const modelError = isModelResolutionError(log);
|
|
2337
|
-
if (code
|
|
3029
|
+
if (isSuccessfulOpencodeExit(code, modelError, recoveryTracker)) {
|
|
2338
3030
|
updateSpecReviewRunStreaming(specReviewRunId, finalResponse || null);
|
|
2339
3031
|
resolveOnce({
|
|
2340
3032
|
completionStatus: 'done',
|