@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
|
@@ -5,14 +5,16 @@ import db from '../db.js';
|
|
|
5
5
|
import config from '../lib/config.js';
|
|
6
6
|
import { JiraApiError } from '../lib/integrations/jira.js';
|
|
7
7
|
import { GitHubApiError } from '../lib/integrations/github.js';
|
|
8
|
+
import { formatGenericToolCall } from '../lib/genericToolCall.js';
|
|
8
9
|
import { PlanningReferenceError, resolveExternalPlanningDescription } from '../lib/integrations/planning.js';
|
|
9
10
|
import { resolveExistingWorktreePath } from '../lib/git.js';
|
|
10
11
|
import { normalizeModelId } from '../lib/models.js';
|
|
12
|
+
import { parseOpencodeTaskEnvelope } from '../lib/opencodeTaskSessions.js';
|
|
11
13
|
import { CLAUDE_NO_PLAN_FILE_DIRECTIVE, PLAN_MODE_QUESTION_TOOL_NAME } from '../lib/planModeQuestions.js';
|
|
12
14
|
import { persistProjectMemorySuggestions } from '../lib/projectMemory.js';
|
|
13
15
|
import { getProjectMemoryPromptBlock } from '../lib/projectMemoryPrompt.js';
|
|
14
16
|
import { normalizeProjectMemorySuggestions, PROJECT_MEMORY_SUGGESTION_COMMON_RULE_LINES } from '../lib/projectMemorySuggestions.js';
|
|
15
|
-
import { prependSkillPromptBlock } from '../lib/promptSkills.js';
|
|
17
|
+
import { buildPlanningSubagentGuidancePromptBlock, prependSkillPromptBlock } from '../lib/promptSkills.js';
|
|
16
18
|
import { registerBundleActivity, unregisterBundleActivity } from '../lib/bundleActivity.js';
|
|
17
19
|
import { enqueue, isRunning } from '../lib/queue.js';
|
|
18
20
|
import { getEffectiveProject, getProjectById, listLinkedProjects } from '../lib/projects.js';
|
|
@@ -21,7 +23,59 @@ import { IMPLEMENTATION_BODY_DEDUPLICATION_RULES, IMPLEMENTATION_DETAIL_RULES, I
|
|
|
21
23
|
import { beginTicketThinkingRun, bindTicketThinkingMessage } from '../lib/thinkingState.js';
|
|
22
24
|
import { createTicketMessage, upsertTicketDescriptionMessage } from '../lib/ticketMessages.js';
|
|
23
25
|
import { getTicket, resolveTicketBranch, resolveTicketTool, resolveTicketWorkerKey } from '../lib/tickets.js';
|
|
24
|
-
import { storeTicketUsageSnapshot, storeTicketUsageSnapshotDirect, mergeUsageFromJsonLine, EMPTY_CHAT_USAGE_SNAPSHOT } from '../lib/agent.js';
|
|
26
|
+
import { createOpencodeSubagentMonitor, createSubagentRunTracker, formatAgentErrorForTranscript, getAgentErrorTranscriptRuns, storeTicketUsageSnapshot, storeTicketUsageSnapshotDirect, mergeUsageFromJsonLine, EMPTY_CHAT_USAGE_SNAPSHOT } from '../lib/agent.js';
|
|
27
|
+
import { clearAndEmitAgentRunsForParent, createAgentRun, finalizeAgentRun, getNextAgentRunIndex } from '../lib/agentRuns.js';
|
|
28
|
+
const createPlanningInvocation = (options, label, role) => {
|
|
29
|
+
const parentType = options.ticketId ? 'ticket' : options.projectId ? 'planning' : null;
|
|
30
|
+
const parentId = options.ticketId ?? options.projectId ?? null;
|
|
31
|
+
if (!parentType || !parentId) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
parentType,
|
|
36
|
+
parentId,
|
|
37
|
+
runId: createAgentRun({
|
|
38
|
+
parentType,
|
|
39
|
+
parentId,
|
|
40
|
+
runIndex: getNextAgentRunIndex(parentType, parentId),
|
|
41
|
+
label,
|
|
42
|
+
role,
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const finalizePlanningInvocation = (invocation, status, response) => {
|
|
47
|
+
if (invocation) {
|
|
48
|
+
finalizeAgentRun(invocation.runId, status, response);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const createPlannerRecoveryTracker = () => {
|
|
52
|
+
let sawError = false;
|
|
53
|
+
let latestEvent = null;
|
|
54
|
+
let finalAnswerFragments = [];
|
|
55
|
+
return {
|
|
56
|
+
consume: (events) => {
|
|
57
|
+
let shouldResetResponse = false;
|
|
58
|
+
for (const event of events ?? []) {
|
|
59
|
+
latestEvent = event.type;
|
|
60
|
+
if (event.type === 'error') {
|
|
61
|
+
sawError = true;
|
|
62
|
+
finalAnswerFragments = [];
|
|
63
|
+
shouldResetResponse = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (sawError) {
|
|
67
|
+
finalAnswerFragments.push(event.text);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return shouldResetResponse;
|
|
71
|
+
},
|
|
72
|
+
hasRecoveredFinalAnswer: () => (sawError
|
|
73
|
+
&& latestEvent === 'final_answer'
|
|
74
|
+
&& finalAnswerFragments.length > 0),
|
|
75
|
+
hasUnrecoveredError: () => latestEvent === 'error',
|
|
76
|
+
getRecoveredResponse: () => finalAnswerFragments.join('\n\n'),
|
|
77
|
+
};
|
|
78
|
+
};
|
|
25
79
|
const extractLeadingJsonObject = (line) => {
|
|
26
80
|
if (!line.startsWith('{')) {
|
|
27
81
|
return null;
|
|
@@ -63,6 +117,43 @@ const extractLeadingJsonObject = (line) => {
|
|
|
63
117
|
return null;
|
|
64
118
|
};
|
|
65
119
|
const normalizeToolName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
120
|
+
const isPlannerSubagentTool = (name) => {
|
|
121
|
+
const normalized = normalizeToolName(name);
|
|
122
|
+
return normalized === 'task' || normalized === 'subagent' || normalized === 'agent';
|
|
123
|
+
};
|
|
124
|
+
const getPlannerSubagentDescription = (input, fallback) => {
|
|
125
|
+
if (input) {
|
|
126
|
+
for (const key of ['description', 'title', 'task', 'subagent_type', 'prompt']) {
|
|
127
|
+
const value = input[key];
|
|
128
|
+
if (typeof value === 'string' && value.trim()) {
|
|
129
|
+
const firstLine = value.trim().split('\n')[0] ?? '';
|
|
130
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return fallback;
|
|
135
|
+
};
|
|
136
|
+
const stringifyPlannerToolResult = (content) => {
|
|
137
|
+
if (typeof content === 'string') {
|
|
138
|
+
return content.trim() || null;
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(content)) {
|
|
141
|
+
const text = content
|
|
142
|
+
.map(item => item && typeof item === 'object' && typeof item.text === 'string'
|
|
143
|
+
? item.text.trim()
|
|
144
|
+
: '')
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.join('\n\n');
|
|
147
|
+
return text || null;
|
|
148
|
+
}
|
|
149
|
+
return content === null || content === undefined ? null : JSON.stringify(content);
|
|
150
|
+
};
|
|
151
|
+
const emptyParsedPlannerStreamLine = () => ({
|
|
152
|
+
logFragment: null,
|
|
153
|
+
responseFragment: null,
|
|
154
|
+
subagentEvents: [],
|
|
155
|
+
providerSessionId: null,
|
|
156
|
+
});
|
|
66
157
|
const toPlannerInputPath = (input) => {
|
|
67
158
|
if (typeof input?.filePath === 'string') {
|
|
68
159
|
return input.filePath.trim();
|
|
@@ -117,26 +208,6 @@ const formatPlannerWriteToolCall = (input) => {
|
|
|
117
208
|
lines.push('[[/archon-apply-patch]]');
|
|
118
209
|
return lines.join('\n');
|
|
119
210
|
};
|
|
120
|
-
const formatPlannerGenericToolCall = (name, input) => {
|
|
121
|
-
const explicitVal = {
|
|
122
|
-
Read: input?.filePath ?? input?.file_path,
|
|
123
|
-
Bash: input?.command,
|
|
124
|
-
Glob: input?.pattern,
|
|
125
|
-
Grep: input?.pattern,
|
|
126
|
-
WebFetch: input?.url,
|
|
127
|
-
WebSearch: input?.query,
|
|
128
|
-
Task: input?.description,
|
|
129
|
-
NotebookEdit: input?.notebook_path,
|
|
130
|
-
}[name];
|
|
131
|
-
const value = typeof explicitVal === 'string' && explicitVal.length > 0
|
|
132
|
-
? explicitVal
|
|
133
|
-
: Object.values(input ?? {}).find(entry => typeof entry === 'string');
|
|
134
|
-
if (typeof value !== 'string' || value.length === 0) {
|
|
135
|
-
return `_${name}_`;
|
|
136
|
-
}
|
|
137
|
-
const display = value.length > 100 ? value.slice(0, 100) + '…' : value;
|
|
138
|
-
return `_${name}_ \`${display}\``;
|
|
139
|
-
};
|
|
140
211
|
const formatPlannerToolCall = (name, input) => {
|
|
141
212
|
const normalized = normalizeToolName(name);
|
|
142
213
|
if (normalized === 'edit') {
|
|
@@ -144,32 +215,46 @@ const formatPlannerToolCall = (name, input) => {
|
|
|
144
215
|
if (formatted !== null) {
|
|
145
216
|
return formatted;
|
|
146
217
|
}
|
|
147
|
-
return
|
|
218
|
+
return formatGenericToolCall(name, input);
|
|
148
219
|
}
|
|
149
220
|
if (normalized === 'write') {
|
|
150
221
|
const formatted = formatPlannerWriteToolCall(input);
|
|
151
222
|
if (formatted !== null) {
|
|
152
223
|
return formatted;
|
|
153
224
|
}
|
|
154
|
-
return
|
|
225
|
+
return formatGenericToolCall(name, input);
|
|
155
226
|
}
|
|
156
|
-
return
|
|
227
|
+
return formatGenericToolCall(name, input);
|
|
157
228
|
};
|
|
158
229
|
const parseClaudePlannerStreamJsonLine = (line) => {
|
|
159
230
|
const trimmedLine = line.trim();
|
|
160
231
|
if (!trimmedLine) {
|
|
161
|
-
return
|
|
232
|
+
return emptyParsedPlannerStreamLine();
|
|
162
233
|
}
|
|
163
234
|
let event;
|
|
164
235
|
try {
|
|
165
236
|
event = JSON.parse(trimmedLine);
|
|
166
237
|
}
|
|
167
238
|
catch {
|
|
168
|
-
|
|
239
|
+
const envelope = parseOpencodeTaskEnvelope(trimmedLine);
|
|
240
|
+
if (envelope) {
|
|
241
|
+
return {
|
|
242
|
+
...emptyParsedPlannerStreamLine(),
|
|
243
|
+
subagentEvents: [{
|
|
244
|
+
kind: 'complete',
|
|
245
|
+
callId: null,
|
|
246
|
+
sessionId: envelope.childSessionId,
|
|
247
|
+
status: envelope.state?.toLowerCase() === 'error' || envelope.state?.toLowerCase() === 'failed' ? 'error' : 'done',
|
|
248
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
249
|
+
}],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
169
253
|
}
|
|
170
254
|
if (event.type === 'assistant' && Array.isArray(event.message?.content)) {
|
|
171
255
|
const logParts = [];
|
|
172
256
|
const responseParts = [];
|
|
257
|
+
const subagentEvents = [];
|
|
173
258
|
for (const block of event.message.content) {
|
|
174
259
|
if (block.type === 'text' && block.text) {
|
|
175
260
|
const text = block.text.trim();
|
|
@@ -180,15 +265,61 @@ const parseClaudePlannerStreamJsonLine = (line) => {
|
|
|
180
265
|
continue;
|
|
181
266
|
}
|
|
182
267
|
if (block.type === 'tool_use' && block.name) {
|
|
268
|
+
if (isPlannerSubagentTool(block.name)) {
|
|
269
|
+
const description = getPlannerSubagentDescription(block.input, block.name);
|
|
270
|
+
logParts.push(`Subagent: ${description}`);
|
|
271
|
+
subagentEvents.push({
|
|
272
|
+
kind: 'start',
|
|
273
|
+
callId: block.id ?? null,
|
|
274
|
+
label: `Subagent: ${description}`,
|
|
275
|
+
});
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
183
278
|
logParts.push(formatPlannerToolCall(block.name, block.input));
|
|
184
279
|
}
|
|
185
280
|
}
|
|
186
281
|
return {
|
|
187
282
|
logFragment: logParts.length > 0 ? logParts.join('\n\n') : null,
|
|
188
283
|
responseFragment: responseParts.length > 0 ? responseParts.join('\n\n') : null,
|
|
284
|
+
subagentEvents,
|
|
285
|
+
providerSessionId: null,
|
|
189
286
|
};
|
|
190
287
|
}
|
|
191
|
-
|
|
288
|
+
if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
|
|
289
|
+
const block = event.event.content_block;
|
|
290
|
+
if (block?.type === 'tool_use' && block.name && isPlannerSubagentTool(block.name)) {
|
|
291
|
+
const description = getPlannerSubagentDescription(block.input, block.name);
|
|
292
|
+
return {
|
|
293
|
+
...emptyParsedPlannerStreamLine(),
|
|
294
|
+
logFragment: `Subagent: ${description}`,
|
|
295
|
+
subagentEvents: [{
|
|
296
|
+
kind: 'start',
|
|
297
|
+
callId: block.id ?? null,
|
|
298
|
+
label: `Subagent: ${description}`,
|
|
299
|
+
}],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (event.type === 'user' && Array.isArray(event.message?.content)) {
|
|
304
|
+
const subagentEvents = event.message.content.flatMap(block => {
|
|
305
|
+
if (block.type !== 'tool_result' || !block.tool_use_id) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const rawText = stringifyPlannerToolResult(block.content);
|
|
309
|
+
const envelope = parseOpencodeTaskEnvelope(rawText);
|
|
310
|
+
const text = envelope?.result ?? rawText;
|
|
311
|
+
const state = envelope?.state?.toLowerCase();
|
|
312
|
+
return [{
|
|
313
|
+
kind: 'complete',
|
|
314
|
+
callId: block.tool_use_id,
|
|
315
|
+
status: block.is_error || state === 'error' || state === 'failed' ? 'error' : 'done',
|
|
316
|
+
...(text ? { text } : {}),
|
|
317
|
+
...(envelope ? { sessionId: envelope.childSessionId } : {}),
|
|
318
|
+
}];
|
|
319
|
+
});
|
|
320
|
+
return { ...emptyParsedPlannerStreamLine(), subagentEvents };
|
|
321
|
+
}
|
|
322
|
+
return emptyParsedPlannerStreamLine();
|
|
192
323
|
};
|
|
193
324
|
const formatPlannerOpencodeToolUse = (event) => {
|
|
194
325
|
const toolName = event.part?.tool;
|
|
@@ -204,25 +335,48 @@ const formatPlannerStepTitle = (event) => {
|
|
|
204
335
|
const parseOpencodePlannerJsonLine = (line) => {
|
|
205
336
|
const trimmedLine = line.trim();
|
|
206
337
|
if (!trimmedLine) {
|
|
207
|
-
return
|
|
338
|
+
return emptyParsedPlannerStreamLine();
|
|
208
339
|
}
|
|
209
340
|
const jsonLine = extractLeadingJsonObject(trimmedLine);
|
|
210
341
|
if (!jsonLine) {
|
|
211
|
-
|
|
342
|
+
const envelope = parseOpencodeTaskEnvelope(trimmedLine);
|
|
343
|
+
if (envelope) {
|
|
344
|
+
return {
|
|
345
|
+
...emptyParsedPlannerStreamLine(),
|
|
346
|
+
subagentEvents: [{
|
|
347
|
+
kind: 'complete',
|
|
348
|
+
callId: null,
|
|
349
|
+
sessionId: envelope.childSessionId,
|
|
350
|
+
status: envelope.state?.toLowerCase() === 'error' || envelope.state?.toLowerCase() === 'failed' ? 'error' : 'done',
|
|
351
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
352
|
+
}],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
212
356
|
}
|
|
213
357
|
let event;
|
|
214
358
|
try {
|
|
215
359
|
event = JSON.parse(jsonLine);
|
|
216
360
|
}
|
|
217
361
|
catch {
|
|
218
|
-
return { logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
362
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
219
363
|
}
|
|
364
|
+
const providerSessionId = event.sessionID?.trim()
|
|
365
|
+
|| event.sessionId?.trim()
|
|
366
|
+
|| event.part?.sessionID?.trim()
|
|
367
|
+
|| event.part?.sessionId?.trim()
|
|
368
|
+
|| null;
|
|
220
369
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
221
370
|
const text = event.part.text.trim();
|
|
222
371
|
const phase = event.part.metadata?.openai?.phase;
|
|
223
372
|
return {
|
|
224
373
|
logFragment: text || null,
|
|
225
374
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
375
|
+
subagentEvents: [],
|
|
376
|
+
providerSessionId,
|
|
377
|
+
recoveryEvents: text && phase === 'final_answer'
|
|
378
|
+
? [{ type: 'final_answer', text }]
|
|
379
|
+
: [],
|
|
226
380
|
};
|
|
227
381
|
}
|
|
228
382
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -230,21 +384,88 @@ const parseOpencodePlannerJsonLine = (line) => {
|
|
|
230
384
|
return {
|
|
231
385
|
logFragment: text || null,
|
|
232
386
|
responseFragment: null,
|
|
387
|
+
subagentEvents: [],
|
|
388
|
+
providerSessionId,
|
|
233
389
|
};
|
|
234
390
|
}
|
|
235
|
-
if (event.type === 'tool_use') {
|
|
391
|
+
if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
|
|
392
|
+
const isToolInvocation = event.type === 'tool' || event.type === 'tool_use';
|
|
393
|
+
const toolName = event.part?.tool;
|
|
394
|
+
if (toolName && isPlannerSubagentTool(toolName)) {
|
|
395
|
+
const description = getPlannerSubagentDescription(event.part?.state?.input, toolName);
|
|
396
|
+
const rawOutput = event.part?.state?.output?.trim();
|
|
397
|
+
const envelope = parseOpencodeTaskEnvelope(rawOutput);
|
|
398
|
+
const output = envelope?.result ?? rawOutput;
|
|
399
|
+
const taskStatus = event.part?.state?.status?.toLowerCase() ?? envelope?.state?.toLowerCase();
|
|
400
|
+
const childSessionId = envelope?.childSessionId
|
|
401
|
+
?? (typeof event.part?.state?.metadata?.sessionID === 'string' ? event.part.state.metadata.sessionID : null)
|
|
402
|
+
?? (typeof event.part?.state?.metadata?.sessionId === 'string' ? event.part.state.metadata.sessionId : null);
|
|
403
|
+
return {
|
|
404
|
+
logFragment: isToolInvocation ? `Subagent: ${description}` : null,
|
|
405
|
+
responseFragment: null,
|
|
406
|
+
subagentEvents: [
|
|
407
|
+
...(isToolInvocation
|
|
408
|
+
? [{
|
|
409
|
+
kind: 'start',
|
|
410
|
+
callId: event.part?.callID ?? null,
|
|
411
|
+
label: `Subagent: ${description}`,
|
|
412
|
+
sessionId: childSessionId,
|
|
413
|
+
}]
|
|
414
|
+
: []),
|
|
415
|
+
...(output
|
|
416
|
+
? [{
|
|
417
|
+
kind: taskStatus === 'running' || taskStatus === 'pending' ? 'fragment' : 'complete',
|
|
418
|
+
callId: event.part?.callID ?? null,
|
|
419
|
+
text: output,
|
|
420
|
+
sessionId: childSessionId,
|
|
421
|
+
status: taskStatus === 'error' || taskStatus === 'failed' ? 'error' : 'done',
|
|
422
|
+
}]
|
|
423
|
+
: []),
|
|
424
|
+
],
|
|
425
|
+
providerSessionId,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (!isToolInvocation) {
|
|
429
|
+
return { ...emptyParsedPlannerStreamLine(), providerSessionId };
|
|
430
|
+
}
|
|
236
431
|
return {
|
|
237
432
|
logFragment: formatPlannerOpencodeToolUse(event),
|
|
238
433
|
responseFragment: null,
|
|
434
|
+
subagentEvents: [],
|
|
435
|
+
providerSessionId,
|
|
239
436
|
};
|
|
240
437
|
}
|
|
241
438
|
if (event.type === 'step_start') {
|
|
242
439
|
return {
|
|
243
440
|
logFragment: formatPlannerStepTitle(event),
|
|
244
441
|
responseFragment: null,
|
|
442
|
+
subagentEvents: [],
|
|
443
|
+
providerSessionId,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (event.type === 'error') {
|
|
447
|
+
const errorText = typeof event.error === 'string'
|
|
448
|
+
? event.error.trim()
|
|
449
|
+
: event.error?.data?.message?.trim()
|
|
450
|
+
|| event.error?.message?.trim()
|
|
451
|
+
|| (event.error ? JSON.stringify(event.error) : '');
|
|
452
|
+
const text = event.part?.text?.trim() || errorText;
|
|
453
|
+
return {
|
|
454
|
+
logFragment: text ? `**Error:** ${text}` : null,
|
|
455
|
+
responseFragment: null,
|
|
456
|
+
subagentEvents: [],
|
|
457
|
+
providerSessionId,
|
|
458
|
+
recoveryEvents: [{ type: 'error' }],
|
|
245
459
|
};
|
|
246
460
|
}
|
|
247
|
-
return {
|
|
461
|
+
return { ...emptyParsedPlannerStreamLine(), providerSessionId };
|
|
462
|
+
};
|
|
463
|
+
const attachPlannerTranscriptRuns = (error, transcriptRuns) => {
|
|
464
|
+
const resolvedError = (error instanceof Error ? error : new Error(String(error)));
|
|
465
|
+
if (transcriptRuns) {
|
|
466
|
+
resolvedError.transcriptRuns = transcriptRuns;
|
|
467
|
+
}
|
|
468
|
+
return resolvedError;
|
|
248
469
|
};
|
|
249
470
|
const stripSystemReminderBlocks = (output) => {
|
|
250
471
|
return output.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
|
|
@@ -268,10 +489,10 @@ const PLANNING_QUESTIONS_RULES = [
|
|
|
268
489
|
'- Return an empty `open_questions` array when the ticket is already clear enough to implement.',
|
|
269
490
|
].join('\n');
|
|
270
491
|
const MEMORY_SUGGESTION_RULES = [
|
|
492
|
+
'- Prioritize explicit durable decisions established by the user.',
|
|
271
493
|
...PROJECT_MEMORY_SUGGESTION_COMMON_RULE_LINES,
|
|
272
494
|
'- Do not suggest planner assumptions as project memory.',
|
|
273
|
-
'-
|
|
274
|
-
'- Use the exact shape `{ "kind": "convention", "title": "...", "scope": "relevant area", "instruction": "...", "rationale": "optional why", "stages": ["build"], "priority": "normal" }` for conventions.',
|
|
495
|
+
'- Include structured applicability, evidence, and a create/update/supersede recommendation using the common suggestion contract.',
|
|
275
496
|
].join('\n');
|
|
276
497
|
const getPlanningMemorySuggestionRules = (memoryEnabled) => {
|
|
277
498
|
return memoryEnabled ? MEMORY_SUGGESTION_RULES : '';
|
|
@@ -439,6 +660,7 @@ const buildSingleTicketPrompt = (title, description, linkedResearchContext = '',
|
|
|
439
660
|
|
|
440
661
|
This is a planning-only task.
|
|
441
662
|
- You may inspect the repository and use read-only tools if helpful.
|
|
663
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
442
664
|
- Do not edit files or make changes.
|
|
443
665
|
- Return a short natural-language planning update first, then the final JSON object on the last line.
|
|
444
666
|
|
|
@@ -497,6 +719,7 @@ This is pass 1 of 2. Your job is to decide the best ticket split and dependency
|
|
|
497
719
|
|
|
498
720
|
This is a planning-only task.
|
|
499
721
|
- You may inspect the repository and use read-only tools if helpful.
|
|
722
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
500
723
|
- Do not edit files or make changes.
|
|
501
724
|
- Return only the final JSON object.
|
|
502
725
|
|
|
@@ -548,7 +771,7 @@ ${projectMemoryBlock ? `${projectMemoryBlock}
|
|
|
548
771
|
|
|
549
772
|
` : ''}
|
|
550
773
|
${linkedResearchContext}`;
|
|
551
|
-
const buildPlanningAgentExpansionPrompt = (description, outline, linkedResearchContext = '', projectMemoryBlock = ''
|
|
774
|
+
const buildPlanningAgentExpansionPrompt = (description, outline, linkedResearchContext = '', projectMemoryBlock = '') => `You are helping expand a previously decomposed software development initiative into implementation-ready tickets for a kanban board.
|
|
552
775
|
|
|
553
776
|
Each generated ticket will later be implemented by a separate AI agent, so every ticket description must be self-contained and implementation-ready without needing any further context or clarification.
|
|
554
777
|
|
|
@@ -556,6 +779,7 @@ This is pass 2 of 2. The ticket split and dependency graph are already decided.
|
|
|
556
779
|
|
|
557
780
|
This is a planning-only task.
|
|
558
781
|
- You may inspect the repository and use read-only tools if helpful.
|
|
782
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
559
783
|
- Do not edit files or make changes.
|
|
560
784
|
- Return only the final JSON object.
|
|
561
785
|
|
|
@@ -566,7 +790,7 @@ Return ONLY a JSON object with this exact shape:
|
|
|
566
790
|
"key": "short-kebab-key",
|
|
567
791
|
"title": "Ticket title",
|
|
568
792
|
"description": "Detailed markdown description",
|
|
569
|
-
"open_questions": ["Optional question"]
|
|
793
|
+
"open_questions": ["Optional question"]
|
|
570
794
|
}
|
|
571
795
|
],
|
|
572
796
|
"dependencies": [
|
|
@@ -604,8 +828,7 @@ ${IMPLEMENTATION_BODY_DEDUPLICATION_RULES}
|
|
|
604
828
|
- Include those resources verbatim in the final ticket description instead of replacing them with vague summaries.
|
|
605
829
|
- Clarification handling rules:
|
|
606
830
|
${PLANNING_QUESTIONS_RULES}
|
|
607
|
-
-
|
|
608
|
-
${getPlanningMemorySuggestionRules(memoryEnabled)}` : ' Omit memory suggestions entirely when project memory is disabled.'}
|
|
831
|
+
- Do not return \`memory_suggestions\`. Multi-ticket expansion has no durable source record to own them.
|
|
609
832
|
|
|
610
833
|
Example of the expected depth and structure for a single expanded ticket:
|
|
611
834
|
${EXPANDED_TICKET_EXAMPLE}
|
|
@@ -740,11 +963,19 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
740
963
|
let resolvedSessionId = sessionTracking?.resumeSessionId ?? null;
|
|
741
964
|
let lastStdoutAt = Date.now();
|
|
742
965
|
let watchdogTimer = null;
|
|
966
|
+
const subagentTracker = options.agentRunParent
|
|
967
|
+
? createSubagentRunTracker({
|
|
968
|
+
...options.agentRunParent,
|
|
969
|
+
io: options.agentRunParent.io,
|
|
970
|
+
})
|
|
971
|
+
: null;
|
|
972
|
+
let opencodeSubagentMonitor = null;
|
|
743
973
|
const resolveOnce = (value) => {
|
|
744
974
|
if (settled) {
|
|
745
975
|
return;
|
|
746
976
|
}
|
|
747
977
|
settled = true;
|
|
978
|
+
opencodeSubagentMonitor?.stop();
|
|
748
979
|
resolve(value);
|
|
749
980
|
};
|
|
750
981
|
const rejectOnce = (error) => {
|
|
@@ -752,6 +983,7 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
752
983
|
return;
|
|
753
984
|
}
|
|
754
985
|
settled = true;
|
|
986
|
+
opencodeSubagentMonitor?.stop();
|
|
755
987
|
reject(error);
|
|
756
988
|
};
|
|
757
989
|
const clearWatchdogTimer = () => {
|
|
@@ -779,6 +1011,9 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
779
1011
|
}
|
|
780
1012
|
return hasResolvedSessionId;
|
|
781
1013
|
};
|
|
1014
|
+
if (tool === 'opencode' && (sessionTracking || subagentTracker)) {
|
|
1015
|
+
spawnTimestamp = Date.now();
|
|
1016
|
+
}
|
|
782
1017
|
const child = spawn(cmd, args, {
|
|
783
1018
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
784
1019
|
env: { ...process.env, ...(cwd ? { PWD: cwd } : {}) },
|
|
@@ -789,7 +1024,6 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
789
1024
|
updatePlanningSessionId(sessionTracking.ticketId, sessionId, sessionTracking.io);
|
|
790
1025
|
}
|
|
791
1026
|
if (sessionTracking && tool === 'opencode') {
|
|
792
|
-
spawnTimestamp = Date.now();
|
|
793
1027
|
if (!sessionTracking.resumeSessionId) {
|
|
794
1028
|
sessionLookupTimer = setTimeout(() => {
|
|
795
1029
|
attemptOpencodeSessionLookup();
|
|
@@ -797,10 +1031,25 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
797
1031
|
}, 1500);
|
|
798
1032
|
}
|
|
799
1033
|
}
|
|
1034
|
+
if (subagentTracker && tool === 'opencode') {
|
|
1035
|
+
spawnTimestamp = spawnTimestamp ?? Date.now();
|
|
1036
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
1037
|
+
initialParentSessionId: sessionTracking?.resumeSessionId ?? null,
|
|
1038
|
+
spawnTimestamp,
|
|
1039
|
+
cwd: cwd ?? process.cwd(),
|
|
1040
|
+
tracker: subagentTracker,
|
|
1041
|
+
discoverParentSession: false,
|
|
1042
|
+
onParentSessionId: discoveredSessionId => {
|
|
1043
|
+
resolvedSessionId = resolvedSessionId ?? discoveredSessionId;
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
opencodeSubagentMonitor.start();
|
|
1047
|
+
}
|
|
800
1048
|
const stdoutChunks = [];
|
|
801
1049
|
const stderrChunks = [];
|
|
802
1050
|
const logLines = [];
|
|
803
1051
|
const responseLines = [];
|
|
1052
|
+
const recoveryTracker = createPlannerRecoveryTracker();
|
|
804
1053
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
805
1054
|
let remainder = '';
|
|
806
1055
|
let insideHiddenBlock = false;
|
|
@@ -839,9 +1088,18 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
839
1088
|
catch { /* best-effort timing */ }
|
|
840
1089
|
}
|
|
841
1090
|
}
|
|
842
|
-
const
|
|
1091
|
+
const parsedLine = tool === 'claude'
|
|
843
1092
|
? parseClaudePlannerStreamJsonLine(line)
|
|
844
1093
|
: parseOpencodePlannerJsonLine(line);
|
|
1094
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
|
|
1095
|
+
if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
|
|
1096
|
+
responseLines.length = 0;
|
|
1097
|
+
firstFinalAnswerTime = null;
|
|
1098
|
+
}
|
|
1099
|
+
subagentTracker?.handle(subagentEvents);
|
|
1100
|
+
if (providerSessionId) {
|
|
1101
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
1102
|
+
}
|
|
845
1103
|
if (isHiddenOpenTag.test(line)) {
|
|
846
1104
|
insideHiddenBlock = true;
|
|
847
1105
|
}
|
|
@@ -863,6 +1121,7 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
863
1121
|
child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
|
|
864
1122
|
child.on('close', code => {
|
|
865
1123
|
clearWatchdogTimer();
|
|
1124
|
+
opencodeSubagentMonitor?.refresh();
|
|
866
1125
|
const resolvedSessionOnClose = attemptOpencodeSessionLookup();
|
|
867
1126
|
if (sessionLookupTimer && (resolvedSessionOnClose || hasResolvedSessionId)) {
|
|
868
1127
|
clearTimeout(sessionLookupTimer);
|
|
@@ -872,9 +1131,18 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
872
1131
|
if (tool === 'claude') {
|
|
873
1132
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
874
1133
|
}
|
|
875
|
-
const
|
|
1134
|
+
const parsedLine = tool === 'claude'
|
|
876
1135
|
? parseClaudePlannerStreamJsonLine(remainder)
|
|
877
1136
|
: parseOpencodePlannerJsonLine(remainder);
|
|
1137
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
|
|
1138
|
+
if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
|
|
1139
|
+
responseLines.length = 0;
|
|
1140
|
+
firstFinalAnswerTime = null;
|
|
1141
|
+
}
|
|
1142
|
+
subagentTracker?.handle(subagentEvents);
|
|
1143
|
+
if (providerSessionId) {
|
|
1144
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
1145
|
+
}
|
|
878
1146
|
if (logFragment !== null) {
|
|
879
1147
|
logLines.push(logFragment);
|
|
880
1148
|
}
|
|
@@ -884,9 +1152,12 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
884
1152
|
}
|
|
885
1153
|
}
|
|
886
1154
|
const rawOutput = Buffer.concat(stdoutChunks).toString().trim();
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
1155
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
1156
|
+
const output = tool === 'opencode' && recoveryTracker.hasRecoveredFinalAnswer()
|
|
1157
|
+
? recoveredResponse
|
|
1158
|
+
: tool === 'claude' && !rawOutput.startsWith('{')
|
|
1159
|
+
? rawOutput
|
|
1160
|
+
: responseLines.join('\n\n').trim() || rawOutput;
|
|
890
1161
|
const errorOutput = Buffer.concat(stderrChunks).toString().trim();
|
|
891
1162
|
const timing = (() => {
|
|
892
1163
|
const totalMs = Date.now() - startTime;
|
|
@@ -898,17 +1169,22 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
898
1169
|
return { totalMs };
|
|
899
1170
|
})();
|
|
900
1171
|
if (hardTimeoutForced) {
|
|
901
|
-
|
|
1172
|
+
const transcriptRuns = subagentTracker?.finalize('error', logLines.join('\n\n') || errorOutput || null, output || null) ?? null;
|
|
1173
|
+
rejectOnce(attachPlannerTranscriptRuns(new Error(output || errorOutput || `Planner exceeded the ${PLANNER_HARD_TIMEOUT_MS} ms hard timeout.`), transcriptRuns));
|
|
902
1174
|
return;
|
|
903
1175
|
}
|
|
904
|
-
if ((
|
|
905
|
-
|
|
1176
|
+
if (!recoveryTracker.hasUnrecoveredError()
|
|
1177
|
+
&& (code === 0 || completionForced || recoveryTracker.hasRecoveredFinalAnswer())
|
|
1178
|
+
&& output) {
|
|
1179
|
+
const transcriptRuns = subagentTracker?.finalize('done', logLines.join('\n\n') || null, output) ?? null;
|
|
1180
|
+
resolveOnce({ output, responseTiming: timing, usageSnapshot, transcriptRuns });
|
|
906
1181
|
return;
|
|
907
1182
|
}
|
|
908
1183
|
const fallbackError = code === 0
|
|
909
1184
|
? 'Planner did not return a usable plan.'
|
|
910
1185
|
: `Process exited with code ${code}`;
|
|
911
|
-
|
|
1186
|
+
const transcriptRuns = subagentTracker?.finalize('error', logLines.join('\n\n') || errorOutput || null, output || null) ?? null;
|
|
1187
|
+
rejectOnce(attachPlannerTranscriptRuns(new Error(output || errorOutput || fallbackError), transcriptRuns));
|
|
912
1188
|
});
|
|
913
1189
|
child.on('error', err => {
|
|
914
1190
|
clearWatchdogTimer();
|
|
@@ -916,7 +1192,8 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
916
1192
|
clearTimeout(sessionLookupTimer);
|
|
917
1193
|
sessionLookupTimer = null;
|
|
918
1194
|
}
|
|
919
|
-
|
|
1195
|
+
const transcriptRuns = subagentTracker?.finalize('error', err.message, null) ?? null;
|
|
1196
|
+
rejectOnce(attachPlannerTranscriptRuns(err, transcriptRuns));
|
|
920
1197
|
});
|
|
921
1198
|
if (tool === 'opencode') {
|
|
922
1199
|
const runWatchdog = () => {
|
|
@@ -1227,14 +1504,17 @@ export const generatePlanText = (title, description, options = {}) => {
|
|
|
1227
1504
|
const tool = options.tool ?? config.tool ?? 'opencode';
|
|
1228
1505
|
const project = options.projectId ? getProjectById(options.projectId) : null;
|
|
1229
1506
|
return (async () => {
|
|
1507
|
+
const invocation = createPlanningInvocation(options, 'Ticket Planning', 'planning');
|
|
1230
1508
|
const linkedResearch = options.linkedResearch ?? await prepareLinkedPlanningResearch(`${title}\n\n${description}`, options);
|
|
1231
1509
|
const linkedResearchContext = project ? buildLinkedResearchContext(project, linkedResearch) : '';
|
|
1232
1510
|
const projectMemoryBlock = getProjectMemoryPromptBlock(project?.id, {
|
|
1511
|
+
flow: 'plan',
|
|
1233
1512
|
stage: 'plan',
|
|
1234
1513
|
title,
|
|
1235
1514
|
description,
|
|
1515
|
+
...(invocation ? { invocation: { parentType: invocation.parentType, parentId: invocation.parentId, agentRunId: invocation.runId } } : {}),
|
|
1236
1516
|
});
|
|
1237
|
-
const { output: plannerOutput, responseTiming: plannerTiming, usageSnapshot } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildSingleTicketPrompt(title, description, linkedResearchContext, projectMemoryBlock, Boolean(project?.memory_enabled)), options.skills ?? []), resolvePlannerCwd(project, options.branch), {
|
|
1517
|
+
const { output: plannerOutput, responseTiming: plannerTiming, usageSnapshot, transcriptRuns } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildSingleTicketPrompt(title, description, linkedResearchContext, projectMemoryBlock, Boolean(project?.memory_enabled)), options.skills ?? []), resolvePlannerCwd(project, options.branch), {
|
|
1238
1518
|
onProgress: options.onProgress,
|
|
1239
1519
|
...(options.ticketId && options.io
|
|
1240
1520
|
? {
|
|
@@ -1243,51 +1523,87 @@ export const generatePlanText = (title, description, options = {}) => {
|
|
|
1243
1523
|
io: options.io,
|
|
1244
1524
|
resumeSessionId: options.planningSessionId,
|
|
1245
1525
|
},
|
|
1526
|
+
agentRunParent: {
|
|
1527
|
+
parentType: 'ticket',
|
|
1528
|
+
parentId: options.ticketId,
|
|
1529
|
+
io: options.io,
|
|
1530
|
+
},
|
|
1246
1531
|
}
|
|
1247
1532
|
: {}),
|
|
1248
1533
|
});
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1534
|
+
try {
|
|
1535
|
+
const expanded = extractTaggedJsonObjectWithLeadingText(plannerOutput, PLAN_OUTPUT_TAG)
|
|
1536
|
+
?? extractFirstJsonObjectWithLeadingText(plannerOutput);
|
|
1537
|
+
const expandedTicket = expanded.parsed.tickets.find(ticket => ticket.key === 'main-ticket');
|
|
1538
|
+
if (!expandedTicket?.description?.trim()) {
|
|
1539
|
+
throw new Error('Planner did not return a usable plan.');
|
|
1540
|
+
}
|
|
1541
|
+
const result = {
|
|
1542
|
+
...normalizePlanningDraftTicket(expandedTicket),
|
|
1543
|
+
key: 'main-ticket',
|
|
1544
|
+
title: title.trim(),
|
|
1545
|
+
description: expandedTicket.description.trim(),
|
|
1546
|
+
response: expanded.before.trim(),
|
|
1547
|
+
responseTiming: plannerTiming,
|
|
1548
|
+
usageSnapshot,
|
|
1549
|
+
transcriptRuns,
|
|
1550
|
+
};
|
|
1551
|
+
finalizePlanningInvocation(invocation, 'done', plannerOutput);
|
|
1552
|
+
return result;
|
|
1553
|
+
}
|
|
1554
|
+
catch (error) {
|
|
1555
|
+
finalizePlanningInvocation(invocation, 'error', plannerOutput);
|
|
1556
|
+
throw attachPlannerTranscriptRuns(error, transcriptRuns ?? null);
|
|
1254
1557
|
}
|
|
1255
|
-
return {
|
|
1256
|
-
...normalizePlanningDraftTicket(expandedTicket),
|
|
1257
|
-
key: 'main-ticket',
|
|
1258
|
-
title: title.trim(),
|
|
1259
|
-
description: expandedTicket.description.trim(),
|
|
1260
|
-
response: expanded.before.trim(),
|
|
1261
|
-
responseTiming: plannerTiming,
|
|
1262
|
-
usageSnapshot,
|
|
1263
|
-
};
|
|
1264
1558
|
})();
|
|
1265
1559
|
};
|
|
1266
1560
|
export const generatePlanningTicketOutline = async (description, options = {}) => {
|
|
1267
1561
|
const tool = options.tool ?? config.tool ?? 'opencode';
|
|
1268
1562
|
const project = options.projectId ? getProjectById(options.projectId) : null;
|
|
1563
|
+
const invocation = createPlanningInvocation(options, 'Planning Outline', 'planning-outline');
|
|
1269
1564
|
const linkedResearch = options.linkedResearch ?? await prepareLinkedPlanningResearch(description, options);
|
|
1270
1565
|
const linkedResearchContext = project ? buildLinkedResearchContext(project, linkedResearch) : '';
|
|
1271
1566
|
const projectMemoryBlock = getProjectMemoryPromptBlock(project?.id, {
|
|
1567
|
+
flow: 'plan',
|
|
1272
1568
|
stage: 'plan',
|
|
1273
1569
|
title: description,
|
|
1274
1570
|
description,
|
|
1571
|
+
...(invocation ? { invocation: { parentType: invocation.parentType, parentId: invocation.parentId, agentRunId: invocation.runId } } : {}),
|
|
1275
1572
|
});
|
|
1276
1573
|
const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildPlanningAgentOutlinePrompt(description, linkedResearchContext, projectMemoryBlock), options.skills ?? []), resolvePlannerCwd(project, options.branch));
|
|
1277
|
-
|
|
1574
|
+
try {
|
|
1575
|
+
const result = extractFirstJsonObject(output);
|
|
1576
|
+
finalizePlanningInvocation(invocation, 'done', output);
|
|
1577
|
+
return result;
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
finalizePlanningInvocation(invocation, 'error', output);
|
|
1581
|
+
throw error;
|
|
1582
|
+
}
|
|
1278
1583
|
};
|
|
1279
1584
|
export const expandPlanningTicketOutline = async (description, outline, options = {}) => {
|
|
1280
1585
|
const tool = options.tool ?? config.tool ?? 'opencode';
|
|
1281
1586
|
const project = options.projectId ? getProjectById(options.projectId) : null;
|
|
1587
|
+
const invocation = createPlanningInvocation(options, 'Planning Expansion', 'planning-expansion');
|
|
1282
1588
|
const linkedResearch = options.linkedResearch ?? await prepareLinkedPlanningResearch(description, options);
|
|
1283
1589
|
const linkedResearchContext = project ? buildLinkedResearchContext(project, linkedResearch) : '';
|
|
1284
1590
|
const projectMemoryBlock = getProjectMemoryPromptBlock(project?.id, {
|
|
1591
|
+
flow: 'plan',
|
|
1285
1592
|
stage: 'plan',
|
|
1286
1593
|
title: description,
|
|
1287
1594
|
description,
|
|
1595
|
+
...(invocation ? { invocation: { parentType: invocation.parentType, parentId: invocation.parentId, agentRunId: invocation.runId } } : {}),
|
|
1288
1596
|
});
|
|
1289
|
-
const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildPlanningAgentExpansionPrompt(description, outline, linkedResearchContext, projectMemoryBlock
|
|
1290
|
-
|
|
1597
|
+
const { output } = await runPlannerPrompt(tool, options.model ?? null, options.variant ?? null, prependSkillPromptBlock(buildPlanningAgentExpansionPrompt(description, outline, linkedResearchContext, projectMemoryBlock), options.skills ?? []), resolvePlannerCwd(project, options.branch));
|
|
1598
|
+
try {
|
|
1599
|
+
const result = extractFirstJsonObject(output);
|
|
1600
|
+
finalizePlanningInvocation(invocation, 'done', output);
|
|
1601
|
+
return result;
|
|
1602
|
+
}
|
|
1603
|
+
catch (error) {
|
|
1604
|
+
finalizePlanningInvocation(invocation, 'error', output);
|
|
1605
|
+
throw error;
|
|
1606
|
+
}
|
|
1291
1607
|
};
|
|
1292
1608
|
const emitTicketUpdate = (ticketId, io) => {
|
|
1293
1609
|
const updated = getTicket(ticketId);
|
|
@@ -1308,6 +1624,7 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1308
1624
|
registerBundleActivity(ticket, 'plan');
|
|
1309
1625
|
beginTicketThinkingRun(ticket.id);
|
|
1310
1626
|
db.prepare('UPDATE tickets SET agent_status = ?, agent_log = NULL, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run('running', ticket.id);
|
|
1627
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1311
1628
|
emitTicketUpdate(ticket.id, io);
|
|
1312
1629
|
try {
|
|
1313
1630
|
const project = getEffectiveProject(ticket);
|
|
@@ -1356,7 +1673,7 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1356
1673
|
const assistantMessage = buildPlanningAssistantMessage({
|
|
1357
1674
|
response: planned.response,
|
|
1358
1675
|
openQuestions: planned.open_questions,
|
|
1359
|
-
});
|
|
1676
|
+
}) || (planned.transcriptRuns?.length ? planned.description : '');
|
|
1360
1677
|
if (assistantMessage) {
|
|
1361
1678
|
const messageId = createTicketMessage({
|
|
1362
1679
|
ticketId: ticket.id,
|
|
@@ -1364,14 +1681,19 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1364
1681
|
kind: 'planning_response',
|
|
1365
1682
|
content: assistantMessage,
|
|
1366
1683
|
responseTiming: planned.responseTiming,
|
|
1684
|
+
transcriptRuns: planned.transcriptRuns,
|
|
1367
1685
|
});
|
|
1368
1686
|
if (messageId) {
|
|
1369
1687
|
bindTicketThinkingMessage(ticket.id, messageId);
|
|
1370
1688
|
}
|
|
1371
1689
|
}
|
|
1690
|
+
if (planned.transcriptRuns) {
|
|
1691
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1692
|
+
}
|
|
1372
1693
|
emitTicketUpdate(ticket.id, io);
|
|
1373
1694
|
}
|
|
1374
1695
|
catch (err) {
|
|
1696
|
+
const transcriptRuns = getAgentErrorTranscriptRuns(err);
|
|
1375
1697
|
const message = err instanceof PlanningReferenceError
|
|
1376
1698
|
? err.message
|
|
1377
1699
|
: err instanceof JiraApiError
|
|
@@ -1381,6 +1703,20 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1381
1703
|
: err instanceof Error
|
|
1382
1704
|
? err.message
|
|
1383
1705
|
: String(err);
|
|
1706
|
+
const errorContent = formatAgentErrorForTranscript(err);
|
|
1707
|
+
let errorMessageId = null;
|
|
1708
|
+
if (errorContent) {
|
|
1709
|
+
errorMessageId = createTicketMessage({
|
|
1710
|
+
ticketId: ticket.id,
|
|
1711
|
+
role: 'assistant',
|
|
1712
|
+
kind: 'error',
|
|
1713
|
+
content: errorContent,
|
|
1714
|
+
transcriptRuns,
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
if (transcriptRuns && errorMessageId) {
|
|
1718
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1719
|
+
}
|
|
1384
1720
|
db.prepare('UPDATE tickets SET agent_status = ?, agent_log = ?, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run('error', message, ticket.id);
|
|
1385
1721
|
emitTicketUpdate(ticket.id, io);
|
|
1386
1722
|
}
|