@straiffi/archon 1.4.10 → 1.5.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-CQMqcAiI.js +1 -0
- package/dist/client/assets/{TestsDialog-Cp3Da2W0.js → TestsDialog-iGukzvw2.js} +2 -2
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CFEtd9pl.js → architectureDiagram-3BPJPVTR-BG4rc0pD.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-9UsBOlSn.js → blockDiagram-GPEHLZMM-BifkewDx.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU--TsvWn6X.js → c4Diagram-AAUBKEIU-DEYVUDCs.js} +1 -1
- package/dist/client/assets/channel-gy-JdvF3.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-B3FphDWz.js → chunk-2J33WTMH-DD50YIcA.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-CLbUIPKK.js → chunk-3OPIFGDE-DLbwwbdv.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-CUHKY5eH.js → chunk-5ZQYHXKU-BW-r3gXJ.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DfKJ4Ud0.js → chunk-727SXJPM-B5C_ENX7.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-B0oLVoZs.js → chunk-AQP2D5EJ-BUT3wmDg.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-Czl8GWBW.js → chunk-CSCIHK7Q-BO7wWVh_.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-pOOnuun_.js → chunk-KSCS5N6A-DUNFfKB2.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DzRVeTqf.js → chunk-L5ZTLDWV-SYJQkU8_.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-MVhzvLVp.js → chunk-LZXEDZCA-DSVJIylK.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-BeNiy3nE.js → chunk-ND2GUHAM-CxL5ue9A.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-CnENvQ9d.js → chunk-NZK2D7GU-BwxeYeqR.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-CkqDMX_f.js → chunk-O5CBEL6O-Covi8YcB.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-sJ18xVVM.js → chunk-WU5MYG2G-DlQoHSB3.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B1Xs78Ge.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BU_Soq1k.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-BWnmF2aL.js → dagre-BM42HDAG-CuNExSrh.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-CASRQI5Y.js → diagram-2AECGRRQ-Bk7EYe6Z.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-CY8fXRrV.js → diagram-5GNKFQAL-koHJCfAk.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-vtm-gqIm.js → diagram-KO2AKTUF-DI1Ecx54.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-Ck-iA5q1.js → diagram-LMA3HP47-CmFk1MNf.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-ALBEY2Nn.js → diagram-OG6HWLK6-YTiDn39n.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-BAHPzYR7.js → erDiagram-TEJ5UH35-DyWU43DX.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-pUYj1KCo.js → flowDiagram-I6XJVG4X-DgbU15ZJ.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-zw-EKZcX.js → ganttDiagram-6RSMTGT7-CT39xvdk.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DmPxQKXO.js → gitGraphDiagram-PVQCEYII-DfK_1xRj.js} +1 -1
- package/dist/client/assets/index-BCqJfUGA.css +2 -0
- package/dist/client/assets/index-C_XaTJyb.js +261 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-D0zuFK8l.js → infoDiagram-5YYISTIA-C7z_7194.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BoNe_Ie6.js → ishikawaDiagram-YF4QCWOH-DhJ7h17n.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-ByAP1uL0.js → journeyDiagram-JHISSGLW-CtkhoMAA.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-CGXOqdl0.js → kanban-definition-UN3LZRKU-Bcas7cg9.js} +1 -1
- package/dist/client/assets/{line-BZ3JWBtG.js → line-WO_ReaCl.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-DuHIifHL.js → mermaid-parser.core-WmRuf19A.js} +1 -1
- package/dist/client/assets/{mermaid.core-Co8EqKSU.js → mermaid.core-BMdju4xN.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-Cv3-5Q_b.js → mindmap-definition-RKZ34NQL-DLxsUQmn.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DziVyTJT.js → pieDiagram-4H26LBE5-D-oaWpEg.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DjNtV4An.js → quadrantDiagram-W4KKPZXB-B9krq8I-.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-Cs3npD-z.js → requirementDiagram-4Y6WPE33-m2VnH3vv.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DxepDJwc.js → sankeyDiagram-5OEKKPKP-bCF9Ot9-.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DIPg-NEj.js → sequenceDiagram-3UESZ5HK-C-4zDJcn.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DUA-Ko9b.js → stateDiagram-AJRCARHV-CksbJK59.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BvbN4CmJ.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-wvi8pREg.js → timeline-definition-PNZ67QCA-DDxpdMfo.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CabtIweB.js → vennDiagram-CIIHVFJN-BfQM7_zD.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BiOWydTY.js → wardleyDiagram-YWT4CUSO-CuixXlAX.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-sWfRgDRl.js → xychartDiagram-2RQKCTM6-53YB-tf6.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +14 -3
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +34 -1
- 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/mobileAccess.js +45 -2
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/models.js +89 -36
- 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 +38 -8
- package/dist/server/lib/projectMemory.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/specs.js +9 -0
- package/dist/server/lib/specs.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/transcriptRuns.js +85 -0
- package/dist/server/lib/transcriptRuns.js.map +1 -0
- package/dist/server/workers/build.js +12 -2
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +15 -1
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +12 -2
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +346 -60
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +27 -7
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +50 -9
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specSession.js +36 -1
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-ln1X9xDf.js +0 -1
- package/dist/client/assets/channel-BjRPoR7Z.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BjzBLL5f.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BDLW6NLv.js +0 -1
- package/dist/client/assets/index-BokL1E46.css +0 -2
- package/dist/client/assets/index-WeECH1eZ.js +0 -260
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D6OlcyEp.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,36 @@ 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 } from '../lib/agentRuns.js';
|
|
28
|
+
const createPlannerRecoveryTracker = () => {
|
|
29
|
+
let sawError = false;
|
|
30
|
+
let latestEvent = null;
|
|
31
|
+
let finalAnswerFragments = [];
|
|
32
|
+
return {
|
|
33
|
+
consume: (events) => {
|
|
34
|
+
let shouldResetResponse = false;
|
|
35
|
+
for (const event of events ?? []) {
|
|
36
|
+
latestEvent = event.type;
|
|
37
|
+
if (event.type === 'error') {
|
|
38
|
+
sawError = true;
|
|
39
|
+
finalAnswerFragments = [];
|
|
40
|
+
shouldResetResponse = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (sawError) {
|
|
44
|
+
finalAnswerFragments.push(event.text);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return shouldResetResponse;
|
|
48
|
+
},
|
|
49
|
+
hasRecoveredFinalAnswer: () => (sawError
|
|
50
|
+
&& latestEvent === 'final_answer'
|
|
51
|
+
&& finalAnswerFragments.length > 0),
|
|
52
|
+
hasUnrecoveredError: () => latestEvent === 'error',
|
|
53
|
+
getRecoveredResponse: () => finalAnswerFragments.join('\n\n'),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
25
56
|
const extractLeadingJsonObject = (line) => {
|
|
26
57
|
if (!line.startsWith('{')) {
|
|
27
58
|
return null;
|
|
@@ -63,6 +94,43 @@ const extractLeadingJsonObject = (line) => {
|
|
|
63
94
|
return null;
|
|
64
95
|
};
|
|
65
96
|
const normalizeToolName = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
97
|
+
const isPlannerSubagentTool = (name) => {
|
|
98
|
+
const normalized = normalizeToolName(name);
|
|
99
|
+
return normalized === 'task' || normalized === 'subagent' || normalized === 'agent';
|
|
100
|
+
};
|
|
101
|
+
const getPlannerSubagentDescription = (input, fallback) => {
|
|
102
|
+
if (input) {
|
|
103
|
+
for (const key of ['description', 'title', 'task', 'subagent_type', 'prompt']) {
|
|
104
|
+
const value = input[key];
|
|
105
|
+
if (typeof value === 'string' && value.trim()) {
|
|
106
|
+
const firstLine = value.trim().split('\n')[0] ?? '';
|
|
107
|
+
return firstLine.length > 120 ? `${firstLine.slice(0, 117)}...` : firstLine;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return fallback;
|
|
112
|
+
};
|
|
113
|
+
const stringifyPlannerToolResult = (content) => {
|
|
114
|
+
if (typeof content === 'string') {
|
|
115
|
+
return content.trim() || null;
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(content)) {
|
|
118
|
+
const text = content
|
|
119
|
+
.map(item => item && typeof item === 'object' && typeof item.text === 'string'
|
|
120
|
+
? item.text.trim()
|
|
121
|
+
: '')
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.join('\n\n');
|
|
124
|
+
return text || null;
|
|
125
|
+
}
|
|
126
|
+
return content === null || content === undefined ? null : JSON.stringify(content);
|
|
127
|
+
};
|
|
128
|
+
const emptyParsedPlannerStreamLine = () => ({
|
|
129
|
+
logFragment: null,
|
|
130
|
+
responseFragment: null,
|
|
131
|
+
subagentEvents: [],
|
|
132
|
+
providerSessionId: null,
|
|
133
|
+
});
|
|
66
134
|
const toPlannerInputPath = (input) => {
|
|
67
135
|
if (typeof input?.filePath === 'string') {
|
|
68
136
|
return input.filePath.trim();
|
|
@@ -117,26 +185,6 @@ const formatPlannerWriteToolCall = (input) => {
|
|
|
117
185
|
lines.push('[[/archon-apply-patch]]');
|
|
118
186
|
return lines.join('\n');
|
|
119
187
|
};
|
|
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
188
|
const formatPlannerToolCall = (name, input) => {
|
|
141
189
|
const normalized = normalizeToolName(name);
|
|
142
190
|
if (normalized === 'edit') {
|
|
@@ -144,32 +192,46 @@ const formatPlannerToolCall = (name, input) => {
|
|
|
144
192
|
if (formatted !== null) {
|
|
145
193
|
return formatted;
|
|
146
194
|
}
|
|
147
|
-
return
|
|
195
|
+
return formatGenericToolCall(name, input);
|
|
148
196
|
}
|
|
149
197
|
if (normalized === 'write') {
|
|
150
198
|
const formatted = formatPlannerWriteToolCall(input);
|
|
151
199
|
if (formatted !== null) {
|
|
152
200
|
return formatted;
|
|
153
201
|
}
|
|
154
|
-
return
|
|
202
|
+
return formatGenericToolCall(name, input);
|
|
155
203
|
}
|
|
156
|
-
return
|
|
204
|
+
return formatGenericToolCall(name, input);
|
|
157
205
|
};
|
|
158
206
|
const parseClaudePlannerStreamJsonLine = (line) => {
|
|
159
207
|
const trimmedLine = line.trim();
|
|
160
208
|
if (!trimmedLine) {
|
|
161
|
-
return
|
|
209
|
+
return emptyParsedPlannerStreamLine();
|
|
162
210
|
}
|
|
163
211
|
let event;
|
|
164
212
|
try {
|
|
165
213
|
event = JSON.parse(trimmedLine);
|
|
166
214
|
}
|
|
167
215
|
catch {
|
|
168
|
-
|
|
216
|
+
const envelope = parseOpencodeTaskEnvelope(trimmedLine);
|
|
217
|
+
if (envelope) {
|
|
218
|
+
return {
|
|
219
|
+
...emptyParsedPlannerStreamLine(),
|
|
220
|
+
subagentEvents: [{
|
|
221
|
+
kind: 'complete',
|
|
222
|
+
callId: null,
|
|
223
|
+
sessionId: envelope.childSessionId,
|
|
224
|
+
status: envelope.state?.toLowerCase() === 'error' || envelope.state?.toLowerCase() === 'failed' ? 'error' : 'done',
|
|
225
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
226
|
+
}],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
169
230
|
}
|
|
170
231
|
if (event.type === 'assistant' && Array.isArray(event.message?.content)) {
|
|
171
232
|
const logParts = [];
|
|
172
233
|
const responseParts = [];
|
|
234
|
+
const subagentEvents = [];
|
|
173
235
|
for (const block of event.message.content) {
|
|
174
236
|
if (block.type === 'text' && block.text) {
|
|
175
237
|
const text = block.text.trim();
|
|
@@ -180,15 +242,61 @@ const parseClaudePlannerStreamJsonLine = (line) => {
|
|
|
180
242
|
continue;
|
|
181
243
|
}
|
|
182
244
|
if (block.type === 'tool_use' && block.name) {
|
|
245
|
+
if (isPlannerSubagentTool(block.name)) {
|
|
246
|
+
const description = getPlannerSubagentDescription(block.input, block.name);
|
|
247
|
+
logParts.push(`Subagent: ${description}`);
|
|
248
|
+
subagentEvents.push({
|
|
249
|
+
kind: 'start',
|
|
250
|
+
callId: block.id ?? null,
|
|
251
|
+
label: `Subagent: ${description}`,
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
183
255
|
logParts.push(formatPlannerToolCall(block.name, block.input));
|
|
184
256
|
}
|
|
185
257
|
}
|
|
186
258
|
return {
|
|
187
259
|
logFragment: logParts.length > 0 ? logParts.join('\n\n') : null,
|
|
188
260
|
responseFragment: responseParts.length > 0 ? responseParts.join('\n\n') : null,
|
|
261
|
+
subagentEvents,
|
|
262
|
+
providerSessionId: null,
|
|
189
263
|
};
|
|
190
264
|
}
|
|
191
|
-
|
|
265
|
+
if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
|
|
266
|
+
const block = event.event.content_block;
|
|
267
|
+
if (block?.type === 'tool_use' && block.name && isPlannerSubagentTool(block.name)) {
|
|
268
|
+
const description = getPlannerSubagentDescription(block.input, block.name);
|
|
269
|
+
return {
|
|
270
|
+
...emptyParsedPlannerStreamLine(),
|
|
271
|
+
logFragment: `Subagent: ${description}`,
|
|
272
|
+
subagentEvents: [{
|
|
273
|
+
kind: 'start',
|
|
274
|
+
callId: block.id ?? null,
|
|
275
|
+
label: `Subagent: ${description}`,
|
|
276
|
+
}],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (event.type === 'user' && Array.isArray(event.message?.content)) {
|
|
281
|
+
const subagentEvents = event.message.content.flatMap(block => {
|
|
282
|
+
if (block.type !== 'tool_result' || !block.tool_use_id) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
const rawText = stringifyPlannerToolResult(block.content);
|
|
286
|
+
const envelope = parseOpencodeTaskEnvelope(rawText);
|
|
287
|
+
const text = envelope?.result ?? rawText;
|
|
288
|
+
const state = envelope?.state?.toLowerCase();
|
|
289
|
+
return [{
|
|
290
|
+
kind: 'complete',
|
|
291
|
+
callId: block.tool_use_id,
|
|
292
|
+
status: block.is_error || state === 'error' || state === 'failed' ? 'error' : 'done',
|
|
293
|
+
...(text ? { text } : {}),
|
|
294
|
+
...(envelope ? { sessionId: envelope.childSessionId } : {}),
|
|
295
|
+
}];
|
|
296
|
+
});
|
|
297
|
+
return { ...emptyParsedPlannerStreamLine(), subagentEvents };
|
|
298
|
+
}
|
|
299
|
+
return emptyParsedPlannerStreamLine();
|
|
192
300
|
};
|
|
193
301
|
const formatPlannerOpencodeToolUse = (event) => {
|
|
194
302
|
const toolName = event.part?.tool;
|
|
@@ -204,25 +312,48 @@ const formatPlannerStepTitle = (event) => {
|
|
|
204
312
|
const parseOpencodePlannerJsonLine = (line) => {
|
|
205
313
|
const trimmedLine = line.trim();
|
|
206
314
|
if (!trimmedLine) {
|
|
207
|
-
return
|
|
315
|
+
return emptyParsedPlannerStreamLine();
|
|
208
316
|
}
|
|
209
317
|
const jsonLine = extractLeadingJsonObject(trimmedLine);
|
|
210
318
|
if (!jsonLine) {
|
|
211
|
-
|
|
319
|
+
const envelope = parseOpencodeTaskEnvelope(trimmedLine);
|
|
320
|
+
if (envelope) {
|
|
321
|
+
return {
|
|
322
|
+
...emptyParsedPlannerStreamLine(),
|
|
323
|
+
subagentEvents: [{
|
|
324
|
+
kind: 'complete',
|
|
325
|
+
callId: null,
|
|
326
|
+
sessionId: envelope.childSessionId,
|
|
327
|
+
status: envelope.state?.toLowerCase() === 'error' || envelope.state?.toLowerCase() === 'failed' ? 'error' : 'done',
|
|
328
|
+
...(envelope.result ? { text: envelope.result } : {}),
|
|
329
|
+
}],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
212
333
|
}
|
|
213
334
|
let event;
|
|
214
335
|
try {
|
|
215
336
|
event = JSON.parse(jsonLine);
|
|
216
337
|
}
|
|
217
338
|
catch {
|
|
218
|
-
return { logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
339
|
+
return { ...emptyParsedPlannerStreamLine(), logFragment: trimmedLine, responseFragment: trimmedLine };
|
|
219
340
|
}
|
|
341
|
+
const providerSessionId = event.sessionID?.trim()
|
|
342
|
+
|| event.sessionId?.trim()
|
|
343
|
+
|| event.part?.sessionID?.trim()
|
|
344
|
+
|| event.part?.sessionId?.trim()
|
|
345
|
+
|| null;
|
|
220
346
|
if (event.type === 'text' && event.part?.type === 'text' && event.part.text) {
|
|
221
347
|
const text = event.part.text.trim();
|
|
222
348
|
const phase = event.part.metadata?.openai?.phase;
|
|
223
349
|
return {
|
|
224
350
|
logFragment: text || null,
|
|
225
351
|
responseFragment: text && (!phase || phase === 'final_answer') ? text : null,
|
|
352
|
+
subagentEvents: [],
|
|
353
|
+
providerSessionId,
|
|
354
|
+
recoveryEvents: text && phase === 'final_answer'
|
|
355
|
+
? [{ type: 'final_answer', text }]
|
|
356
|
+
: [],
|
|
226
357
|
};
|
|
227
358
|
}
|
|
228
359
|
if (event.type === 'reasoning' && event.part?.type === 'reasoning') {
|
|
@@ -230,21 +361,88 @@ const parseOpencodePlannerJsonLine = (line) => {
|
|
|
230
361
|
return {
|
|
231
362
|
logFragment: text || null,
|
|
232
363
|
responseFragment: null,
|
|
364
|
+
subagentEvents: [],
|
|
365
|
+
providerSessionId,
|
|
233
366
|
};
|
|
234
367
|
}
|
|
235
|
-
if (event.type === 'tool_use') {
|
|
368
|
+
if (event.type === 'tool' || event.type === 'tool_use' || event.type === 'tool_result') {
|
|
369
|
+
const isToolInvocation = event.type === 'tool' || event.type === 'tool_use';
|
|
370
|
+
const toolName = event.part?.tool;
|
|
371
|
+
if (toolName && isPlannerSubagentTool(toolName)) {
|
|
372
|
+
const description = getPlannerSubagentDescription(event.part?.state?.input, toolName);
|
|
373
|
+
const rawOutput = event.part?.state?.output?.trim();
|
|
374
|
+
const envelope = parseOpencodeTaskEnvelope(rawOutput);
|
|
375
|
+
const output = envelope?.result ?? rawOutput;
|
|
376
|
+
const taskStatus = event.part?.state?.status?.toLowerCase() ?? envelope?.state?.toLowerCase();
|
|
377
|
+
const childSessionId = envelope?.childSessionId
|
|
378
|
+
?? (typeof event.part?.state?.metadata?.sessionID === 'string' ? event.part.state.metadata.sessionID : null)
|
|
379
|
+
?? (typeof event.part?.state?.metadata?.sessionId === 'string' ? event.part.state.metadata.sessionId : null);
|
|
380
|
+
return {
|
|
381
|
+
logFragment: isToolInvocation ? `Subagent: ${description}` : null,
|
|
382
|
+
responseFragment: null,
|
|
383
|
+
subagentEvents: [
|
|
384
|
+
...(isToolInvocation
|
|
385
|
+
? [{
|
|
386
|
+
kind: 'start',
|
|
387
|
+
callId: event.part?.callID ?? null,
|
|
388
|
+
label: `Subagent: ${description}`,
|
|
389
|
+
sessionId: childSessionId,
|
|
390
|
+
}]
|
|
391
|
+
: []),
|
|
392
|
+
...(output
|
|
393
|
+
? [{
|
|
394
|
+
kind: taskStatus === 'running' || taskStatus === 'pending' ? 'fragment' : 'complete',
|
|
395
|
+
callId: event.part?.callID ?? null,
|
|
396
|
+
text: output,
|
|
397
|
+
sessionId: childSessionId,
|
|
398
|
+
status: taskStatus === 'error' || taskStatus === 'failed' ? 'error' : 'done',
|
|
399
|
+
}]
|
|
400
|
+
: []),
|
|
401
|
+
],
|
|
402
|
+
providerSessionId,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (!isToolInvocation) {
|
|
406
|
+
return { ...emptyParsedPlannerStreamLine(), providerSessionId };
|
|
407
|
+
}
|
|
236
408
|
return {
|
|
237
409
|
logFragment: formatPlannerOpencodeToolUse(event),
|
|
238
410
|
responseFragment: null,
|
|
411
|
+
subagentEvents: [],
|
|
412
|
+
providerSessionId,
|
|
239
413
|
};
|
|
240
414
|
}
|
|
241
415
|
if (event.type === 'step_start') {
|
|
242
416
|
return {
|
|
243
417
|
logFragment: formatPlannerStepTitle(event),
|
|
244
418
|
responseFragment: null,
|
|
419
|
+
subagentEvents: [],
|
|
420
|
+
providerSessionId,
|
|
245
421
|
};
|
|
246
422
|
}
|
|
247
|
-
|
|
423
|
+
if (event.type === 'error') {
|
|
424
|
+
const errorText = typeof event.error === 'string'
|
|
425
|
+
? event.error.trim()
|
|
426
|
+
: event.error?.data?.message?.trim()
|
|
427
|
+
|| event.error?.message?.trim()
|
|
428
|
+
|| (event.error ? JSON.stringify(event.error) : '');
|
|
429
|
+
const text = event.part?.text?.trim() || errorText;
|
|
430
|
+
return {
|
|
431
|
+
logFragment: text ? `**Error:** ${text}` : null,
|
|
432
|
+
responseFragment: null,
|
|
433
|
+
subagentEvents: [],
|
|
434
|
+
providerSessionId,
|
|
435
|
+
recoveryEvents: [{ type: 'error' }],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return { ...emptyParsedPlannerStreamLine(), providerSessionId };
|
|
439
|
+
};
|
|
440
|
+
const attachPlannerTranscriptRuns = (error, transcriptRuns) => {
|
|
441
|
+
const resolvedError = (error instanceof Error ? error : new Error(String(error)));
|
|
442
|
+
if (transcriptRuns) {
|
|
443
|
+
resolvedError.transcriptRuns = transcriptRuns;
|
|
444
|
+
}
|
|
445
|
+
return resolvedError;
|
|
248
446
|
};
|
|
249
447
|
const stripSystemReminderBlocks = (output) => {
|
|
250
448
|
return output.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '').trim();
|
|
@@ -439,6 +637,7 @@ const buildSingleTicketPrompt = (title, description, linkedResearchContext = '',
|
|
|
439
637
|
|
|
440
638
|
This is a planning-only task.
|
|
441
639
|
- You may inspect the repository and use read-only tools if helpful.
|
|
640
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
442
641
|
- Do not edit files or make changes.
|
|
443
642
|
- Return a short natural-language planning update first, then the final JSON object on the last line.
|
|
444
643
|
|
|
@@ -497,6 +696,7 @@ This is pass 1 of 2. Your job is to decide the best ticket split and dependency
|
|
|
497
696
|
|
|
498
697
|
This is a planning-only task.
|
|
499
698
|
- You may inspect the repository and use read-only tools if helpful.
|
|
699
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
500
700
|
- Do not edit files or make changes.
|
|
501
701
|
- Return only the final JSON object.
|
|
502
702
|
|
|
@@ -556,6 +756,7 @@ This is pass 2 of 2. The ticket split and dependency graph are already decided.
|
|
|
556
756
|
|
|
557
757
|
This is a planning-only task.
|
|
558
758
|
- You may inspect the repository and use read-only tools if helpful.
|
|
759
|
+
${buildPlanningSubagentGuidancePromptBlock()}
|
|
559
760
|
- Do not edit files or make changes.
|
|
560
761
|
- Return only the final JSON object.
|
|
561
762
|
|
|
@@ -740,11 +941,19 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
740
941
|
let resolvedSessionId = sessionTracking?.resumeSessionId ?? null;
|
|
741
942
|
let lastStdoutAt = Date.now();
|
|
742
943
|
let watchdogTimer = null;
|
|
944
|
+
const subagentTracker = options.agentRunParent
|
|
945
|
+
? createSubagentRunTracker({
|
|
946
|
+
...options.agentRunParent,
|
|
947
|
+
io: options.agentRunParent.io,
|
|
948
|
+
})
|
|
949
|
+
: null;
|
|
950
|
+
let opencodeSubagentMonitor = null;
|
|
743
951
|
const resolveOnce = (value) => {
|
|
744
952
|
if (settled) {
|
|
745
953
|
return;
|
|
746
954
|
}
|
|
747
955
|
settled = true;
|
|
956
|
+
opencodeSubagentMonitor?.stop();
|
|
748
957
|
resolve(value);
|
|
749
958
|
};
|
|
750
959
|
const rejectOnce = (error) => {
|
|
@@ -752,6 +961,7 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
752
961
|
return;
|
|
753
962
|
}
|
|
754
963
|
settled = true;
|
|
964
|
+
opencodeSubagentMonitor?.stop();
|
|
755
965
|
reject(error);
|
|
756
966
|
};
|
|
757
967
|
const clearWatchdogTimer = () => {
|
|
@@ -779,6 +989,9 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
779
989
|
}
|
|
780
990
|
return hasResolvedSessionId;
|
|
781
991
|
};
|
|
992
|
+
if (tool === 'opencode' && (sessionTracking || subagentTracker)) {
|
|
993
|
+
spawnTimestamp = Date.now();
|
|
994
|
+
}
|
|
782
995
|
const child = spawn(cmd, args, {
|
|
783
996
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
784
997
|
env: { ...process.env, ...(cwd ? { PWD: cwd } : {}) },
|
|
@@ -789,7 +1002,6 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
789
1002
|
updatePlanningSessionId(sessionTracking.ticketId, sessionId, sessionTracking.io);
|
|
790
1003
|
}
|
|
791
1004
|
if (sessionTracking && tool === 'opencode') {
|
|
792
|
-
spawnTimestamp = Date.now();
|
|
793
1005
|
if (!sessionTracking.resumeSessionId) {
|
|
794
1006
|
sessionLookupTimer = setTimeout(() => {
|
|
795
1007
|
attemptOpencodeSessionLookup();
|
|
@@ -797,10 +1009,25 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
797
1009
|
}, 1500);
|
|
798
1010
|
}
|
|
799
1011
|
}
|
|
1012
|
+
if (subagentTracker && tool === 'opencode') {
|
|
1013
|
+
spawnTimestamp = spawnTimestamp ?? Date.now();
|
|
1014
|
+
opencodeSubagentMonitor = createOpencodeSubagentMonitor({
|
|
1015
|
+
initialParentSessionId: sessionTracking?.resumeSessionId ?? null,
|
|
1016
|
+
spawnTimestamp,
|
|
1017
|
+
cwd: cwd ?? process.cwd(),
|
|
1018
|
+
tracker: subagentTracker,
|
|
1019
|
+
discoverParentSession: false,
|
|
1020
|
+
onParentSessionId: discoveredSessionId => {
|
|
1021
|
+
resolvedSessionId = resolvedSessionId ?? discoveredSessionId;
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
1024
|
+
opencodeSubagentMonitor.start();
|
|
1025
|
+
}
|
|
800
1026
|
const stdoutChunks = [];
|
|
801
1027
|
const stderrChunks = [];
|
|
802
1028
|
const logLines = [];
|
|
803
1029
|
const responseLines = [];
|
|
1030
|
+
const recoveryTracker = createPlannerRecoveryTracker();
|
|
804
1031
|
let usageSnapshot = EMPTY_CHAT_USAGE_SNAPSHOT;
|
|
805
1032
|
let remainder = '';
|
|
806
1033
|
let insideHiddenBlock = false;
|
|
@@ -839,9 +1066,18 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
839
1066
|
catch { /* best-effort timing */ }
|
|
840
1067
|
}
|
|
841
1068
|
}
|
|
842
|
-
const
|
|
1069
|
+
const parsedLine = tool === 'claude'
|
|
843
1070
|
? parseClaudePlannerStreamJsonLine(line)
|
|
844
1071
|
: parseOpencodePlannerJsonLine(line);
|
|
1072
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
|
|
1073
|
+
if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
|
|
1074
|
+
responseLines.length = 0;
|
|
1075
|
+
firstFinalAnswerTime = null;
|
|
1076
|
+
}
|
|
1077
|
+
subagentTracker?.handle(subagentEvents);
|
|
1078
|
+
if (providerSessionId) {
|
|
1079
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
1080
|
+
}
|
|
845
1081
|
if (isHiddenOpenTag.test(line)) {
|
|
846
1082
|
insideHiddenBlock = true;
|
|
847
1083
|
}
|
|
@@ -863,6 +1099,7 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
863
1099
|
child.stderr?.on('data', (chunk) => stderrChunks.push(chunk));
|
|
864
1100
|
child.on('close', code => {
|
|
865
1101
|
clearWatchdogTimer();
|
|
1102
|
+
opencodeSubagentMonitor?.refresh();
|
|
866
1103
|
const resolvedSessionOnClose = attemptOpencodeSessionLookup();
|
|
867
1104
|
if (sessionLookupTimer && (resolvedSessionOnClose || hasResolvedSessionId)) {
|
|
868
1105
|
clearTimeout(sessionLookupTimer);
|
|
@@ -872,9 +1109,18 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
872
1109
|
if (tool === 'claude') {
|
|
873
1110
|
usageSnapshot = mergeUsageFromJsonLine(usageSnapshot, remainder);
|
|
874
1111
|
}
|
|
875
|
-
const
|
|
1112
|
+
const parsedLine = tool === 'claude'
|
|
876
1113
|
? parseClaudePlannerStreamJsonLine(remainder)
|
|
877
1114
|
: parseOpencodePlannerJsonLine(remainder);
|
|
1115
|
+
const { logFragment, responseFragment, subagentEvents, providerSessionId } = parsedLine;
|
|
1116
|
+
if (tool === 'opencode' && recoveryTracker.consume(parsedLine.recoveryEvents)) {
|
|
1117
|
+
responseLines.length = 0;
|
|
1118
|
+
firstFinalAnswerTime = null;
|
|
1119
|
+
}
|
|
1120
|
+
subagentTracker?.handle(subagentEvents);
|
|
1121
|
+
if (providerSessionId) {
|
|
1122
|
+
opencodeSubagentMonitor?.setParentSessionId(providerSessionId);
|
|
1123
|
+
}
|
|
878
1124
|
if (logFragment !== null) {
|
|
879
1125
|
logLines.push(logFragment);
|
|
880
1126
|
}
|
|
@@ -884,9 +1130,12 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
884
1130
|
}
|
|
885
1131
|
}
|
|
886
1132
|
const rawOutput = Buffer.concat(stdoutChunks).toString().trim();
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
1133
|
+
const recoveredResponse = recoveryTracker.getRecoveredResponse();
|
|
1134
|
+
const output = tool === 'opencode' && recoveryTracker.hasRecoveredFinalAnswer()
|
|
1135
|
+
? recoveredResponse
|
|
1136
|
+
: tool === 'claude' && !rawOutput.startsWith('{')
|
|
1137
|
+
? rawOutput
|
|
1138
|
+
: responseLines.join('\n\n').trim() || rawOutput;
|
|
890
1139
|
const errorOutput = Buffer.concat(stderrChunks).toString().trim();
|
|
891
1140
|
const timing = (() => {
|
|
892
1141
|
const totalMs = Date.now() - startTime;
|
|
@@ -898,17 +1147,22 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
898
1147
|
return { totalMs };
|
|
899
1148
|
})();
|
|
900
1149
|
if (hardTimeoutForced) {
|
|
901
|
-
|
|
1150
|
+
const transcriptRuns = subagentTracker?.finalize('error', logLines.join('\n\n') || errorOutput || null, output || null) ?? null;
|
|
1151
|
+
rejectOnce(attachPlannerTranscriptRuns(new Error(output || errorOutput || `Planner exceeded the ${PLANNER_HARD_TIMEOUT_MS} ms hard timeout.`), transcriptRuns));
|
|
902
1152
|
return;
|
|
903
1153
|
}
|
|
904
|
-
if ((
|
|
905
|
-
|
|
1154
|
+
if (!recoveryTracker.hasUnrecoveredError()
|
|
1155
|
+
&& (code === 0 || completionForced || recoveryTracker.hasRecoveredFinalAnswer())
|
|
1156
|
+
&& output) {
|
|
1157
|
+
const transcriptRuns = subagentTracker?.finalize('done', logLines.join('\n\n') || null, output) ?? null;
|
|
1158
|
+
resolveOnce({ output, responseTiming: timing, usageSnapshot, transcriptRuns });
|
|
906
1159
|
return;
|
|
907
1160
|
}
|
|
908
1161
|
const fallbackError = code === 0
|
|
909
1162
|
? 'Planner did not return a usable plan.'
|
|
910
1163
|
: `Process exited with code ${code}`;
|
|
911
|
-
|
|
1164
|
+
const transcriptRuns = subagentTracker?.finalize('error', logLines.join('\n\n') || errorOutput || null, output || null) ?? null;
|
|
1165
|
+
rejectOnce(attachPlannerTranscriptRuns(new Error(output || errorOutput || fallbackError), transcriptRuns));
|
|
912
1166
|
});
|
|
913
1167
|
child.on('error', err => {
|
|
914
1168
|
clearWatchdogTimer();
|
|
@@ -916,7 +1170,8 @@ export const runPlannerPrompt = (tool, model, variant, prompt, cwd, options = {}
|
|
|
916
1170
|
clearTimeout(sessionLookupTimer);
|
|
917
1171
|
sessionLookupTimer = null;
|
|
918
1172
|
}
|
|
919
|
-
|
|
1173
|
+
const transcriptRuns = subagentTracker?.finalize('error', err.message, null) ?? null;
|
|
1174
|
+
rejectOnce(attachPlannerTranscriptRuns(err, transcriptRuns));
|
|
920
1175
|
});
|
|
921
1176
|
if (tool === 'opencode') {
|
|
922
1177
|
const runWatchdog = () => {
|
|
@@ -1234,7 +1489,7 @@ export const generatePlanText = (title, description, options = {}) => {
|
|
|
1234
1489
|
title,
|
|
1235
1490
|
description,
|
|
1236
1491
|
});
|
|
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), {
|
|
1492
|
+
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
1493
|
onProgress: options.onProgress,
|
|
1239
1494
|
...(options.ticketId && options.io
|
|
1240
1495
|
? {
|
|
@@ -1243,24 +1498,35 @@ export const generatePlanText = (title, description, options = {}) => {
|
|
|
1243
1498
|
io: options.io,
|
|
1244
1499
|
resumeSessionId: options.planningSessionId,
|
|
1245
1500
|
},
|
|
1501
|
+
agentRunParent: {
|
|
1502
|
+
parentType: 'ticket',
|
|
1503
|
+
parentId: options.ticketId,
|
|
1504
|
+
io: options.io,
|
|
1505
|
+
},
|
|
1246
1506
|
}
|
|
1247
1507
|
: {}),
|
|
1248
1508
|
});
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1509
|
+
try {
|
|
1510
|
+
const expanded = extractTaggedJsonObjectWithLeadingText(plannerOutput, PLAN_OUTPUT_TAG)
|
|
1511
|
+
?? extractFirstJsonObjectWithLeadingText(plannerOutput);
|
|
1512
|
+
const expandedTicket = expanded.parsed.tickets.find(ticket => ticket.key === 'main-ticket');
|
|
1513
|
+
if (!expandedTicket?.description?.trim()) {
|
|
1514
|
+
throw new Error('Planner did not return a usable plan.');
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
...normalizePlanningDraftTicket(expandedTicket),
|
|
1518
|
+
key: 'main-ticket',
|
|
1519
|
+
title: title.trim(),
|
|
1520
|
+
description: expandedTicket.description.trim(),
|
|
1521
|
+
response: expanded.before.trim(),
|
|
1522
|
+
responseTiming: plannerTiming,
|
|
1523
|
+
usageSnapshot,
|
|
1524
|
+
transcriptRuns,
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
catch (error) {
|
|
1528
|
+
throw attachPlannerTranscriptRuns(error, transcriptRuns ?? null);
|
|
1254
1529
|
}
|
|
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
1530
|
})();
|
|
1265
1531
|
};
|
|
1266
1532
|
export const generatePlanningTicketOutline = async (description, options = {}) => {
|
|
@@ -1308,6 +1574,7 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1308
1574
|
registerBundleActivity(ticket, 'plan');
|
|
1309
1575
|
beginTicketThinkingRun(ticket.id);
|
|
1310
1576
|
db.prepare('UPDATE tickets SET agent_status = ?, agent_log = NULL, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run('running', ticket.id);
|
|
1577
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1311
1578
|
emitTicketUpdate(ticket.id, io);
|
|
1312
1579
|
try {
|
|
1313
1580
|
const project = getEffectiveProject(ticket);
|
|
@@ -1356,7 +1623,7 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1356
1623
|
const assistantMessage = buildPlanningAssistantMessage({
|
|
1357
1624
|
response: planned.response,
|
|
1358
1625
|
openQuestions: planned.open_questions,
|
|
1359
|
-
});
|
|
1626
|
+
}) || (planned.transcriptRuns?.length ? planned.description : '');
|
|
1360
1627
|
if (assistantMessage) {
|
|
1361
1628
|
const messageId = createTicketMessage({
|
|
1362
1629
|
ticketId: ticket.id,
|
|
@@ -1364,14 +1631,19 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1364
1631
|
kind: 'planning_response',
|
|
1365
1632
|
content: assistantMessage,
|
|
1366
1633
|
responseTiming: planned.responseTiming,
|
|
1634
|
+
transcriptRuns: planned.transcriptRuns,
|
|
1367
1635
|
});
|
|
1368
1636
|
if (messageId) {
|
|
1369
1637
|
bindTicketThinkingMessage(ticket.id, messageId);
|
|
1370
1638
|
}
|
|
1371
1639
|
}
|
|
1640
|
+
if (planned.transcriptRuns) {
|
|
1641
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1642
|
+
}
|
|
1372
1643
|
emitTicketUpdate(ticket.id, io);
|
|
1373
1644
|
}
|
|
1374
1645
|
catch (err) {
|
|
1646
|
+
const transcriptRuns = getAgentErrorTranscriptRuns(err);
|
|
1375
1647
|
const message = err instanceof PlanningReferenceError
|
|
1376
1648
|
? err.message
|
|
1377
1649
|
: err instanceof JiraApiError
|
|
@@ -1381,6 +1653,20 @@ export const startPlan = (ticket, io, options = {}) => {
|
|
|
1381
1653
|
: err instanceof Error
|
|
1382
1654
|
? err.message
|
|
1383
1655
|
: String(err);
|
|
1656
|
+
const errorContent = formatAgentErrorForTranscript(err);
|
|
1657
|
+
let errorMessageId = null;
|
|
1658
|
+
if (errorContent) {
|
|
1659
|
+
errorMessageId = createTicketMessage({
|
|
1660
|
+
ticketId: ticket.id,
|
|
1661
|
+
role: 'assistant',
|
|
1662
|
+
kind: 'error',
|
|
1663
|
+
content: errorContent,
|
|
1664
|
+
transcriptRuns,
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
if (transcriptRuns && errorMessageId) {
|
|
1668
|
+
clearAndEmitAgentRunsForParent(io, 'ticket', ticket.id);
|
|
1669
|
+
}
|
|
1384
1670
|
db.prepare('UPDATE tickets SET agent_status = ?, agent_log = ?, streaming_response = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run('error', message, ticket.id);
|
|
1385
1671
|
emitTicketUpdate(ticket.id, io);
|
|
1386
1672
|
}
|