@simonepri/refined-antigravity-acp 1.3.2 → 1.4.1
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/src/core/supervisor.d.ts.map +1 -1
- package/dist/src/core/supervisor.js +17 -2
- package/dist/src/core/supervisor.js.map +1 -1
- package/dist/src/fixes/cancellation-leak/index.d.ts.map +1 -1
- package/dist/src/fixes/cancellation-leak/index.js +73 -28
- package/dist/src/fixes/cancellation-leak/index.js.map +1 -1
- package/dist/src/fixes/dangling-tool-calls/index.d.ts +6 -0
- package/dist/src/fixes/dangling-tool-calls/index.d.ts.map +1 -1
- package/dist/src/fixes/dangling-tool-calls/index.js +40 -3
- package/dist/src/fixes/dangling-tool-calls/index.js.map +1 -1
- package/dist/src/fixes/dropped-history-chunks/index.d.ts.map +1 -1
- package/dist/src/fixes/dropped-history-chunks/index.js +7 -1
- package/dist/src/fixes/dropped-history-chunks/index.js.map +1 -1
- package/dist/src/fixes/repetitive-tool-loop/index.d.ts +1 -0
- package/dist/src/fixes/repetitive-tool-loop/index.d.ts.map +1 -1
- package/dist/src/fixes/repetitive-tool-loop/index.js +13 -1
- package/dist/src/fixes/repetitive-tool-loop/index.js.map +1 -1
- package/dist/src/fixes/silent-background-tasks/index.d.ts +31 -2
- package/dist/src/fixes/silent-background-tasks/index.d.ts.map +1 -1
- package/dist/src/fixes/silent-background-tasks/index.js +532 -89
- package/dist/src/fixes/silent-background-tasks/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Tracks subagent invocations and background tasks via telemetry and stream events, synthesizing
|
|
9
9
|
* standard ACP `session/update` notifications with `sessionUpdate: "plan"` to expose live status.
|
|
10
10
|
*/
|
|
11
|
-
import { ACP_METHODS, SESSION_UPDATES,
|
|
12
|
-
import { extractSessionId, setFixData } from "../../core/session-cache.js";
|
|
11
|
+
import { ACP_METHODS, SESSION_UPDATES, isMethod, } from "../../core/types.js";
|
|
12
|
+
import { extractSessionId, setFixData, getSession, getOrCreateSession, } from "../../core/session-cache.js";
|
|
13
13
|
import { TELEMETRY_STATES, parseTrajectoryStateUpdate } from "../../core/telemetry.js";
|
|
14
14
|
export const TOOL_INVOKE_SUBAGENT = "invoke_subagent";
|
|
15
15
|
export const TOOL_SCHEDULE = "schedule";
|
|
@@ -27,17 +27,38 @@ export function createPlanUpdateMessage(sessionId, entries) {
|
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
export function isSubagentCompletedState(state) {
|
|
31
|
+
if (!state)
|
|
32
|
+
return false;
|
|
33
|
+
const s = state.toLowerCase();
|
|
34
|
+
return s === "idle" || s === "completed" || s === "done" || s === "finished";
|
|
35
|
+
}
|
|
36
|
+
function getStringProp(obj, ...keys) {
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
const val = obj[key];
|
|
39
|
+
if (typeof val === "string")
|
|
40
|
+
return val;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
30
44
|
function parseSubagentItem(item) {
|
|
31
45
|
if (!item || typeof item !== "object")
|
|
32
46
|
return null;
|
|
33
47
|
const s = item;
|
|
48
|
+
const role = getStringProp(s, "Role", "role");
|
|
49
|
+
const prompt = getStringProp(s, "Prompt", "prompt");
|
|
50
|
+
const typeName = getStringProp(s, "TypeName", "typeName", "type");
|
|
51
|
+
const conversationId = getStringProp(s, "conversationId", "ConversationId");
|
|
52
|
+
const id = getStringProp(s, "id", "Id") ?? conversationId;
|
|
34
53
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
id,
|
|
55
|
+
role,
|
|
56
|
+
prompt,
|
|
57
|
+
typeName,
|
|
58
|
+
conversationId,
|
|
38
59
|
};
|
|
39
60
|
}
|
|
40
|
-
|
|
61
|
+
function extractSubagentsArray(rawArgs) {
|
|
41
62
|
if (!rawArgs)
|
|
42
63
|
return [];
|
|
43
64
|
let obj = rawArgs;
|
|
@@ -51,11 +72,121 @@ export function parseSubagentsFromArgs(rawArgs) {
|
|
|
51
72
|
}
|
|
52
73
|
if (!obj || typeof obj !== "object")
|
|
53
74
|
return [];
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
const record = obj;
|
|
76
|
+
let subs = record.Subagents ?? record.subagents;
|
|
77
|
+
if (typeof subs === "string") {
|
|
78
|
+
try {
|
|
79
|
+
subs = JSON.parse(subs);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Array.isArray(subs) ? subs : [];
|
|
86
|
+
}
|
|
87
|
+
export function parseSubagentsFromArgs(rawArgs) {
|
|
88
|
+
const subs = extractSubagentsArray(rawArgs);
|
|
57
89
|
return subs.map(parseSubagentItem).filter((s) => s !== null);
|
|
58
90
|
}
|
|
91
|
+
export function parseSubagentStatusReports(rawOutput) {
|
|
92
|
+
if (!rawOutput)
|
|
93
|
+
return [];
|
|
94
|
+
let text = "";
|
|
95
|
+
if (typeof rawOutput === "string") {
|
|
96
|
+
text = rawOutput;
|
|
97
|
+
}
|
|
98
|
+
else if (typeof rawOutput === "object") {
|
|
99
|
+
try {
|
|
100
|
+
text = JSON.stringify(rawOutput);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const startIdx = text.indexOf("[");
|
|
107
|
+
const endIdx = text.lastIndexOf("]");
|
|
108
|
+
if (startIdx === -1 || endIdx <= startIdx)
|
|
109
|
+
return [];
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(text.slice(startIdx, endIdx + 1));
|
|
112
|
+
if (!Array.isArray(parsed))
|
|
113
|
+
return [];
|
|
114
|
+
return parsed
|
|
115
|
+
.filter((item) => Boolean(item) && typeof item === "object")
|
|
116
|
+
.map(mapSubagentReport);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function mapSubagentReport(item) {
|
|
123
|
+
return {
|
|
124
|
+
role: typeof item.role === "string"
|
|
125
|
+
? item.role
|
|
126
|
+
: typeof item.Role === "string"
|
|
127
|
+
? item.Role
|
|
128
|
+
: undefined,
|
|
129
|
+
type: typeof item.type === "string"
|
|
130
|
+
? item.type
|
|
131
|
+
: typeof item.TypeName === "string"
|
|
132
|
+
? item.TypeName
|
|
133
|
+
: undefined,
|
|
134
|
+
conversationId: typeof item.conversationId === "string"
|
|
135
|
+
? item.conversationId
|
|
136
|
+
: typeof item.ConversationId === "string"
|
|
137
|
+
? item.ConversationId
|
|
138
|
+
: undefined,
|
|
139
|
+
state: typeof item.state === "string"
|
|
140
|
+
? item.state
|
|
141
|
+
: typeof item.State === "string"
|
|
142
|
+
? item.State
|
|
143
|
+
: undefined,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function isCompletionPhrasePresent(lowerText) {
|
|
147
|
+
return (lowerText.includes("complete") ||
|
|
148
|
+
lowerText.includes("finished") ||
|
|
149
|
+
lowerText.includes("idle") ||
|
|
150
|
+
lowerText.includes("success") ||
|
|
151
|
+
lowerText.includes("passed") ||
|
|
152
|
+
lowerText.includes("verified"));
|
|
153
|
+
}
|
|
154
|
+
function matchSubagentByRoleOrPrompt(sub, lowerText) {
|
|
155
|
+
if (sub.role && sub.role.trim().length > 3) {
|
|
156
|
+
const roleLower = sub.role.toLowerCase().trim();
|
|
157
|
+
if (lowerText.includes(roleLower) && isCompletionPhrasePresent(lowerText)) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (sub.prompt) {
|
|
162
|
+
const itemMatch = sub.prompt.match(/\bItem\s+(\d+)\b/i);
|
|
163
|
+
if (itemMatch?.[1]) {
|
|
164
|
+
const itemNum = itemMatch[1];
|
|
165
|
+
const pattern1 = new RegExp(`\\bItem\\s+${itemNum}\\b[^.\\n]*\\b(complete|completed|finished|success|verified)\\b`, "i");
|
|
166
|
+
const pattern2 = new RegExp(`\\b(completed|finished|verified)\\s+Item\\s+${itemNum}\\b`, "i");
|
|
167
|
+
return pattern1.test(lowerText) || pattern2.test(lowerText);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
export function detectCompletedSubagentsInText(text, trackedSubagents) {
|
|
173
|
+
const completed = [];
|
|
174
|
+
const lower = text.toLowerCase();
|
|
175
|
+
for (const sub of trackedSubagents) {
|
|
176
|
+
if (sub.status === "completed")
|
|
177
|
+
continue;
|
|
178
|
+
if (sub.conversationId && text.includes(sub.conversationId)) {
|
|
179
|
+
if (isCompletionPhrasePresent(lower)) {
|
|
180
|
+
completed.push(sub);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (matchSubagentByRoleOrPrompt(sub, lower)) {
|
|
185
|
+
completed.push(sub);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return completed;
|
|
189
|
+
}
|
|
59
190
|
export function formatSubagentContent(info) {
|
|
60
191
|
if (info.role) {
|
|
61
192
|
return `Subagent: ${info.role}`;
|
|
@@ -70,7 +201,132 @@ export function formatSubagentContent(info) {
|
|
|
70
201
|
}
|
|
71
202
|
return "Subagent: Background Task";
|
|
72
203
|
}
|
|
204
|
+
export function detectLaunchedTasksInText(text) {
|
|
205
|
+
const launched = [];
|
|
206
|
+
const p1 = /(?:launched|running|started)\s+`([^`]+)`\s+in\s+the\s+background(?:\s*\(task\s+`?([^`)\s]+)`?\))?/gi;
|
|
207
|
+
for (const m of text.matchAll(p1)) {
|
|
208
|
+
if (m[1]) {
|
|
209
|
+
let taskId = m[2]?.trim();
|
|
210
|
+
if (taskId && taskId.includes("/")) {
|
|
211
|
+
taskId = taskId.split("/").pop();
|
|
212
|
+
}
|
|
213
|
+
launched.push({ desc: m[1].trim(), taskId });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return launched;
|
|
217
|
+
}
|
|
218
|
+
function appendReconstructedTask(content, seen, entries) {
|
|
219
|
+
if (!seen.has(content)) {
|
|
220
|
+
seen.add(content);
|
|
221
|
+
entries.push({ content, priority: "high", status: "completed" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function processStepForReconstruction(step, seen, entries) {
|
|
225
|
+
const s = step;
|
|
226
|
+
if (s.kind === "tool_call" && s.name === TOOL_INVOKE_SUBAGENT && s.rawInputJson) {
|
|
227
|
+
for (const sub of parseSubagentsFromArgs(s.rawInputJson)) {
|
|
228
|
+
appendReconstructedTask(formatSubagentContent(sub), seen, entries);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (s.kind === "assistant" && s.text) {
|
|
232
|
+
for (const t of detectLaunchedTasksInText(s.text)) {
|
|
233
|
+
appendReconstructedTask(`Background task: ${t.desc}`, seen, entries);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export function reconstructPlanFromSteps(steps) {
|
|
238
|
+
const entries = [];
|
|
239
|
+
const seenContent = new Set();
|
|
240
|
+
for (const step of steps) {
|
|
241
|
+
processStepForReconstruction(step, seenContent, entries);
|
|
242
|
+
}
|
|
243
|
+
return entries;
|
|
244
|
+
}
|
|
73
245
|
const MAX_TRACKED_SESSIONS = 100;
|
|
246
|
+
function applyReportToExistingSubagent(report, matched, tracker) {
|
|
247
|
+
if (report.conversationId && !matched.conversationId) {
|
|
248
|
+
matched.conversationId = report.conversationId;
|
|
249
|
+
}
|
|
250
|
+
if (isSubagentCompletedState(report.state) && matched.status !== "completed") {
|
|
251
|
+
matched.status = "completed";
|
|
252
|
+
const entry = tracker.entries.find((e) => e.content === matched.content);
|
|
253
|
+
if (entry)
|
|
254
|
+
entry.status = "completed";
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
function applyReportToTracker(report, tracker) {
|
|
260
|
+
const isDone = isSubagentCompletedState(report.state);
|
|
261
|
+
const matched = tracker.trackedSubagents.find((s) => (report.conversationId && s.conversationId === report.conversationId) ||
|
|
262
|
+
(report.role && s.role && s.role.trim().toLowerCase() === report.role.trim().toLowerCase()));
|
|
263
|
+
if (matched) {
|
|
264
|
+
return applyReportToExistingSubagent(report, matched, tracker);
|
|
265
|
+
}
|
|
266
|
+
const subInfo = {
|
|
267
|
+
role: report.role,
|
|
268
|
+
typeName: report.type,
|
|
269
|
+
conversationId: report.conversationId,
|
|
270
|
+
};
|
|
271
|
+
const content = formatSubagentContent(subInfo);
|
|
272
|
+
const status = isDone ? "completed" : "in_progress";
|
|
273
|
+
tracker.trackedSubagents.push({ ...subInfo, content, status });
|
|
274
|
+
tracker.entries.push({ content, priority: "high", status });
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
function isTaskMatchingEntry(entry, taskId, totalEntries = 0, taskIds) {
|
|
278
|
+
if (entry.status === "completed" || !entry.content.startsWith("Background task:")) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (!taskId)
|
|
282
|
+
return true;
|
|
283
|
+
if (entry.content.includes(taskId))
|
|
284
|
+
return true;
|
|
285
|
+
if (taskIds?.get(taskId) === entry.content)
|
|
286
|
+
return true;
|
|
287
|
+
return totalEntries === 1;
|
|
288
|
+
}
|
|
289
|
+
function extractFinishedTaskId(rawOutput) {
|
|
290
|
+
if (!rawOutput.includes("finished with result:") && !rawOutput.includes("exited with code")) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const match = rawOutput.match(/Task id ["']?([^"'\s]+)["']?\s+finished/i);
|
|
294
|
+
let taskId = match?.[1];
|
|
295
|
+
if (taskId && taskId.includes("/")) {
|
|
296
|
+
taskId = taskId.split("/").pop();
|
|
297
|
+
}
|
|
298
|
+
return taskId;
|
|
299
|
+
}
|
|
300
|
+
function updateSubagentsFromText(tracker, text) {
|
|
301
|
+
if (tracker.trackedSubagents.length === 0)
|
|
302
|
+
return false;
|
|
303
|
+
tracker.recentText = `${tracker.recentText} ${text}`.slice(-3000);
|
|
304
|
+
const completed = detectCompletedSubagentsInText(tracker.recentText, tracker.trackedSubagents);
|
|
305
|
+
let changed = false;
|
|
306
|
+
for (const sub of completed) {
|
|
307
|
+
if (sub.status !== "completed") {
|
|
308
|
+
sub.status = "completed";
|
|
309
|
+
const entry = tracker.entries.find((e) => e.content === sub.content);
|
|
310
|
+
if (entry)
|
|
311
|
+
entry.status = "completed";
|
|
312
|
+
changed = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return changed;
|
|
316
|
+
}
|
|
317
|
+
function registerLaunchedTasksFromText(tracker, text) {
|
|
318
|
+
let changed = false;
|
|
319
|
+
for (const t of detectLaunchedTasksInText(text)) {
|
|
320
|
+
const content = `Background task: ${t.desc}`;
|
|
321
|
+
if (!tracker.entries.some((e) => e.content === content)) {
|
|
322
|
+
tracker.entries.push({ content, priority: "high", status: "in_progress" });
|
|
323
|
+
if (t.taskId)
|
|
324
|
+
tracker.taskIds.set(t.taskId, content);
|
|
325
|
+
changed = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return changed;
|
|
329
|
+
}
|
|
74
330
|
export class BackgroundTasksTracker {
|
|
75
331
|
sessions = new Map();
|
|
76
332
|
promptIdToSessionId = new Map();
|
|
@@ -84,11 +340,14 @@ export class BackgroundTasksTracker {
|
|
|
84
340
|
}
|
|
85
341
|
tracker = {
|
|
86
342
|
entries: [],
|
|
343
|
+
trackedSubagents: [],
|
|
344
|
+
recentText: "",
|
|
87
345
|
hasEmittedWaitingPlan: false,
|
|
88
346
|
isWaiting: false,
|
|
89
347
|
seenToolCallIds: new Set(),
|
|
90
348
|
activeToolCalls: new Map(),
|
|
91
|
-
|
|
349
|
+
taskIds: new Map(),
|
|
350
|
+
pendingPlan: null,
|
|
92
351
|
};
|
|
93
352
|
this.sessions.set(sessionId, tracker);
|
|
94
353
|
}
|
|
@@ -97,17 +356,47 @@ export class BackgroundTasksTracker {
|
|
|
97
356
|
getEntries(sessionId) {
|
|
98
357
|
return this.sessions.get(sessionId)?.entries ?? [];
|
|
99
358
|
}
|
|
359
|
+
hasActiveTasksOrSubagents(sessionId) {
|
|
360
|
+
const tracker = this.sessions.get(sessionId);
|
|
361
|
+
if (!tracker)
|
|
362
|
+
return false;
|
|
363
|
+
return (tracker.isWaiting ||
|
|
364
|
+
tracker.trackedSubagents.length > 0 ||
|
|
365
|
+
tracker.entries.some((e) => e.status !== "completed"));
|
|
366
|
+
}
|
|
367
|
+
setPendingPlan(sessionId, entries) {
|
|
368
|
+
this.getOrCreate(sessionId).pendingPlan = entries;
|
|
369
|
+
}
|
|
370
|
+
flushPendingPlan(sessionId) {
|
|
371
|
+
const tracker = this.sessions.get(sessionId);
|
|
372
|
+
if (!tracker || !tracker.pendingPlan)
|
|
373
|
+
return null;
|
|
374
|
+
const plan = tracker.pendingPlan;
|
|
375
|
+
tracker.pendingPlan = null;
|
|
376
|
+
return createPlanUpdateMessage(sessionId, plan);
|
|
377
|
+
}
|
|
100
378
|
onPromptStart(sessionId, promptId) {
|
|
101
379
|
if (promptId !== undefined) {
|
|
102
380
|
this.promptIdToSessionId.set(promptId, sessionId);
|
|
103
381
|
}
|
|
104
382
|
const tracker = this.getOrCreate(sessionId);
|
|
105
|
-
tracker.entries
|
|
383
|
+
const activeEntries = tracker.entries.filter((e) => e.status !== "completed");
|
|
384
|
+
tracker.entries = activeEntries;
|
|
385
|
+
tracker.trackedSubagents = tracker.trackedSubagents.filter((s) => s.status !== "completed");
|
|
386
|
+
tracker.recentText = "";
|
|
106
387
|
tracker.hasEmittedWaitingPlan = false;
|
|
107
|
-
tracker.isWaiting =
|
|
108
|
-
tracker.
|
|
388
|
+
tracker.isWaiting = activeEntries.length > 0;
|
|
389
|
+
tracker.pendingPlan = null;
|
|
109
390
|
tracker.seenToolCallIds.clear();
|
|
110
391
|
tracker.activeToolCalls.clear();
|
|
392
|
+
tracker.taskIds.clear();
|
|
393
|
+
}
|
|
394
|
+
recordTaskId(sessionId, taskId, content) {
|
|
395
|
+
let id = taskId;
|
|
396
|
+
if (id.includes("/")) {
|
|
397
|
+
id = id.split("/").pop() ?? id;
|
|
398
|
+
}
|
|
399
|
+
this.getOrCreate(sessionId).taskIds.set(id, content);
|
|
111
400
|
}
|
|
112
401
|
getSessionForPromptId(promptId) {
|
|
113
402
|
return this.promptIdToSessionId.get(promptId);
|
|
@@ -124,9 +413,6 @@ export class BackgroundTasksTracker {
|
|
|
124
413
|
getToolCommand(sessionId, toolCallId) {
|
|
125
414
|
return this.sessions.get(sessionId)?.activeToolCalls.get(toolCallId);
|
|
126
415
|
}
|
|
127
|
-
deferPromptResponse(sessionId, response) {
|
|
128
|
-
this.getOrCreate(sessionId).deferredPromptResponse = response;
|
|
129
|
-
}
|
|
130
416
|
recordSubagents(sessionId, subagents, toolCallId) {
|
|
131
417
|
if (subagents.length === 0)
|
|
132
418
|
return [];
|
|
@@ -137,14 +423,77 @@ export class BackgroundTasksTracker {
|
|
|
137
423
|
if (toolCallId) {
|
|
138
424
|
tracker.seenToolCallIds.add(toolCallId);
|
|
139
425
|
}
|
|
140
|
-
const
|
|
141
|
-
content
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
426
|
+
for (const s of subagents) {
|
|
427
|
+
const content = formatSubagentContent(s);
|
|
428
|
+
const existing = tracker.trackedSubagents.find((t) => t.content === content ||
|
|
429
|
+
(s.role && t.role && t.role.trim().toLowerCase() === s.role.trim().toLowerCase()));
|
|
430
|
+
if (existing) {
|
|
431
|
+
if (s.conversationId && !existing.conversationId) {
|
|
432
|
+
existing.conversationId = s.conversationId;
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
tracker.trackedSubagents.push({
|
|
437
|
+
...s,
|
|
438
|
+
content,
|
|
439
|
+
status: "in_progress",
|
|
440
|
+
});
|
|
441
|
+
tracker.entries.push({
|
|
442
|
+
content,
|
|
443
|
+
priority: "high",
|
|
444
|
+
status: "in_progress",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
146
447
|
return tracker.entries;
|
|
147
448
|
}
|
|
449
|
+
updateFromManageSubagents(sessionId, rawOutput) {
|
|
450
|
+
const reports = parseSubagentStatusReports(rawOutput);
|
|
451
|
+
if (reports.length === 0)
|
|
452
|
+
return null;
|
|
453
|
+
const tracker = this.getOrCreate(sessionId);
|
|
454
|
+
let changed = false;
|
|
455
|
+
for (const report of reports) {
|
|
456
|
+
if (applyReportToTracker(report, tracker)) {
|
|
457
|
+
changed = true;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (changed && !tracker.entries.some((e) => e.status !== "completed")) {
|
|
461
|
+
tracker.isWaiting = false;
|
|
462
|
+
}
|
|
463
|
+
return changed ? tracker.entries : null;
|
|
464
|
+
}
|
|
465
|
+
updateFromText(sessionId, text) {
|
|
466
|
+
const tracker = this.getOrCreate(sessionId);
|
|
467
|
+
let changed = Boolean(this.updateFromManageTask(sessionId, text));
|
|
468
|
+
if (registerLaunchedTasksFromText(tracker, text))
|
|
469
|
+
changed = true;
|
|
470
|
+
if (updateSubagentsFromText(tracker, text))
|
|
471
|
+
changed = true;
|
|
472
|
+
if (changed && !tracker.entries.some((e) => e.status !== "completed")) {
|
|
473
|
+
tracker.isWaiting = false;
|
|
474
|
+
}
|
|
475
|
+
return changed ? tracker.entries : null;
|
|
476
|
+
}
|
|
477
|
+
updateFromManageTask(sessionId, rawOutput) {
|
|
478
|
+
if (typeof rawOutput !== "string")
|
|
479
|
+
return null;
|
|
480
|
+
const taskId = extractFinishedTaskId(rawOutput);
|
|
481
|
+
if (taskId === undefined && !rawOutput.includes("finished with result:")) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const tracker = this.getOrCreate(sessionId);
|
|
485
|
+
let changed = false;
|
|
486
|
+
for (const entry of tracker.entries) {
|
|
487
|
+
if (isTaskMatchingEntry(entry, taskId, tracker.entries.length, tracker.taskIds)) {
|
|
488
|
+
entry.status = "completed";
|
|
489
|
+
changed = true;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (changed && !tracker.entries.some((e) => e.status !== "completed")) {
|
|
493
|
+
tracker.isWaiting = false;
|
|
494
|
+
}
|
|
495
|
+
return changed ? tracker.entries : null;
|
|
496
|
+
}
|
|
148
497
|
recordCustomTask(sessionId, content, priority = "medium", toolCallId) {
|
|
149
498
|
const tracker = this.getOrCreate(sessionId);
|
|
150
499
|
if (toolCallId && tracker.seenToolCallIds.has(toolCallId)) {
|
|
@@ -172,7 +521,9 @@ export class BackgroundTasksTracker {
|
|
|
172
521
|
}
|
|
173
522
|
else {
|
|
174
523
|
for (const entry of tracker.entries) {
|
|
175
|
-
entry.status
|
|
524
|
+
if (entry.status !== "completed") {
|
|
525
|
+
entry.status = "in_progress";
|
|
526
|
+
}
|
|
176
527
|
}
|
|
177
528
|
}
|
|
178
529
|
tracker.hasEmittedWaitingPlan = true;
|
|
@@ -189,20 +540,17 @@ export class BackgroundTasksTracker {
|
|
|
189
540
|
}
|
|
190
541
|
tracker.isWaiting = false;
|
|
191
542
|
tracker.hasEmittedWaitingPlan = false;
|
|
543
|
+
if (tracker.trackedSubagents.length > 0) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
192
546
|
for (const entry of tracker.entries) {
|
|
193
547
|
entry.status = "completed";
|
|
194
548
|
}
|
|
195
549
|
return createPlanUpdateMessage(sessionId, tracker.entries);
|
|
196
550
|
}
|
|
197
|
-
onIdle(sessionId,
|
|
551
|
+
onIdle(sessionId, _context) {
|
|
198
552
|
const tracker = this.sessions.get(sessionId);
|
|
199
553
|
if (!tracker || tracker.entries.length === 0) {
|
|
200
|
-
if (tracker?.deferredPromptResponse && context) {
|
|
201
|
-
const deferred = tracker.deferredPromptResponse;
|
|
202
|
-
tracker.deferredPromptResponse = null;
|
|
203
|
-
tracker.isWaiting = false;
|
|
204
|
-
context.forwardInbound(deferred);
|
|
205
|
-
}
|
|
206
554
|
return null;
|
|
207
555
|
}
|
|
208
556
|
tracker.isWaiting = false;
|
|
@@ -210,35 +558,19 @@ export class BackgroundTasksTracker {
|
|
|
210
558
|
for (const entry of tracker.entries) {
|
|
211
559
|
entry.status = "completed";
|
|
212
560
|
}
|
|
213
|
-
|
|
214
|
-
if (tracker.deferredPromptResponse && context) {
|
|
215
|
-
const deferred = tracker.deferredPromptResponse;
|
|
216
|
-
tracker.deferredPromptResponse = null;
|
|
217
|
-
context.forwardInbound(planMsg);
|
|
218
|
-
context.forwardInbound(deferred);
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
return planMsg;
|
|
561
|
+
return createPlanUpdateMessage(sessionId, tracker.entries);
|
|
222
562
|
}
|
|
223
563
|
onCancel(sessionId) {
|
|
224
564
|
const tracker = this.sessions.get(sessionId);
|
|
225
565
|
if (!tracker)
|
|
226
566
|
return { planMsg: null, cancelPromptMsg: null };
|
|
227
567
|
tracker.isWaiting = false;
|
|
228
|
-
|
|
229
|
-
if (tracker.deferredPromptResponse && "id" in tracker.deferredPromptResponse) {
|
|
230
|
-
cancelPromptMsg = {
|
|
231
|
-
jsonrpc: "2.0",
|
|
232
|
-
id: tracker.deferredPromptResponse.id,
|
|
233
|
-
result: { stopReason: STOP_REASONS.CANCELLED },
|
|
234
|
-
};
|
|
235
|
-
tracker.deferredPromptResponse = null;
|
|
236
|
-
}
|
|
568
|
+
tracker.pendingPlan = null;
|
|
237
569
|
for (const entry of tracker.entries) {
|
|
238
570
|
entry.status = "completed";
|
|
239
571
|
}
|
|
240
572
|
const planMsg = tracker.entries.length > 0 ? createPlanUpdateMessage(sessionId, tracker.entries) : null;
|
|
241
|
-
return { planMsg, cancelPromptMsg };
|
|
573
|
+
return { planMsg, cancelPromptMsg: null };
|
|
242
574
|
}
|
|
243
575
|
onTurnEnd(sessionId) {
|
|
244
576
|
const tracker = this.sessions.get(sessionId);
|
|
@@ -380,20 +712,39 @@ function extractCommandLine(rawArgs) {
|
|
|
380
712
|
}
|
|
381
713
|
return null;
|
|
382
714
|
}
|
|
383
|
-
function
|
|
384
|
-
|
|
385
|
-
return null;
|
|
386
|
-
if (!rawOutput.includes("Tool is running as a background task") &&
|
|
387
|
-
!rawOutput.includes("task id:")) {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
const match = rawOutput.match(/Task Description:\s*([^\n\r]+)/i);
|
|
715
|
+
function extractTaskDescFromText(text, storedCommand) {
|
|
716
|
+
const match = text.match(/Task Description:\s*([^\n\r]+)/i);
|
|
391
717
|
let desc = match?.[1]?.trim() || storedCommand || "command";
|
|
392
718
|
if (desc.startsWith('"') && desc.endsWith('"')) {
|
|
393
719
|
desc = desc.slice(1, -1);
|
|
394
720
|
}
|
|
395
721
|
return desc;
|
|
396
722
|
}
|
|
723
|
+
function extractTaskIdFromText(text) {
|
|
724
|
+
const idMatch = text.match(/task id:\s*([^\s\n\r]+)/i);
|
|
725
|
+
let taskId = idMatch?.[1]?.trim();
|
|
726
|
+
if (taskId && taskId.includes("/")) {
|
|
727
|
+
taskId = taskId.split("/").pop();
|
|
728
|
+
}
|
|
729
|
+
return taskId;
|
|
730
|
+
}
|
|
731
|
+
function isBackgroundTaskOutput(text) {
|
|
732
|
+
return (text.includes("Tool is running as a background task") ||
|
|
733
|
+
text.includes("is running as a background task") ||
|
|
734
|
+
text.includes("task id:"));
|
|
735
|
+
}
|
|
736
|
+
export function extractBackgroundTaskInfo(rawOutput, _toolCallId, storedCommand) {
|
|
737
|
+
const text = typeof rawOutput === "string" ? rawOutput : extractContentText(rawOutput);
|
|
738
|
+
if (!text || !isBackgroundTaskOutput(text))
|
|
739
|
+
return null;
|
|
740
|
+
return {
|
|
741
|
+
desc: extractTaskDescFromText(text, storedCommand),
|
|
742
|
+
taskId: extractTaskIdFromText(text),
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
export function extractBackgroundTaskDescription(rawOutput, toolCallId, storedCommand) {
|
|
746
|
+
return extractBackgroundTaskInfo(rawOutput, toolCallId, storedCommand)?.desc ?? null;
|
|
747
|
+
}
|
|
397
748
|
function handleInboundToolCall(sessionId, toolName, toolArgs, tracker, toolCallId) {
|
|
398
749
|
if (toolName === TOOL_INVOKE_SUBAGENT) {
|
|
399
750
|
const subagents = parseSubagentsFromArgs(toolArgs);
|
|
@@ -403,7 +754,13 @@ function handleInboundToolCall(sessionId, toolName, toolArgs, tracker, toolCallI
|
|
|
403
754
|
return entries.length > prevCount ? entries : null;
|
|
404
755
|
}
|
|
405
756
|
if (toolName === TOOL_SCHEDULE) {
|
|
757
|
+
if (tracker.hasActiveTasksOrSubagents(sessionId)) {
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
406
760
|
const taskName = formatScheduleTask(toolArgs);
|
|
761
|
+
if (taskName === "Timer" || taskName.startsWith("Timer: Wait")) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
407
764
|
const prevCount = tracker.getEntries(sessionId).length;
|
|
408
765
|
const entries = tracker.recordCustomTask(sessionId, taskName, "medium", toolCallId);
|
|
409
766
|
tracker.setWaiting(sessionId, true);
|
|
@@ -423,21 +780,6 @@ function extractToolCallUpdate(msg) {
|
|
|
423
780
|
return null;
|
|
424
781
|
return { sessionId, update: u };
|
|
425
782
|
}
|
|
426
|
-
function extractResponseSessionId(msg, tracker, context) {
|
|
427
|
-
if (!("id" in msg) || msg.id === null || msg.id === undefined)
|
|
428
|
-
return undefined;
|
|
429
|
-
return context?.session?.sessionId ?? tracker.getSessionForPromptId(msg.id);
|
|
430
|
-
}
|
|
431
|
-
function tryInterceptWaitingPromptResponse(msg, tracker, context) {
|
|
432
|
-
const result = msg.result;
|
|
433
|
-
if (result?.stopReason !== STOP_REASONS.END_TURN)
|
|
434
|
-
return false;
|
|
435
|
-
const sessionId = extractResponseSessionId(msg, tracker, context);
|
|
436
|
-
if (!sessionId || !tracker.isWaitingForTasks(sessionId))
|
|
437
|
-
return false;
|
|
438
|
-
tracker.deferPromptResponse(sessionId, msg);
|
|
439
|
-
return true;
|
|
440
|
-
}
|
|
441
783
|
function handleToolCallState(sessionId, u, tracker) {
|
|
442
784
|
if (u.sessionUpdate === SESSION_UPDATES.TOOL_CALL) {
|
|
443
785
|
const cmd = extractCommandLine(u.rawInput ?? u.arguments);
|
|
@@ -447,34 +789,118 @@ function handleToolCallState(sessionId, u, tracker) {
|
|
|
447
789
|
return null;
|
|
448
790
|
}
|
|
449
791
|
if (u.sessionUpdate === SESSION_UPDATES.TOOL_CALL_UPDATE) {
|
|
792
|
+
const text = getPayloadText(u);
|
|
450
793
|
const storedCmd = u.toolCallId ? tracker.getToolCommand(sessionId, u.toolCallId) : undefined;
|
|
451
|
-
const
|
|
452
|
-
if (
|
|
453
|
-
const
|
|
794
|
+
const bgInfo = extractBackgroundTaskInfo(text, u.toolCallId, storedCmd);
|
|
795
|
+
if (bgInfo) {
|
|
796
|
+
const content = `Background task: ${bgInfo.desc}`;
|
|
797
|
+
const entries = tracker.recordCustomTask(sessionId, content, "high", u.toolCallId);
|
|
798
|
+
if (bgInfo.taskId) {
|
|
799
|
+
tracker.recordTaskId(sessionId, bgInfo.taskId, content);
|
|
800
|
+
}
|
|
454
801
|
tracker.setWaiting(sessionId, true);
|
|
455
802
|
return entries;
|
|
456
803
|
}
|
|
457
804
|
}
|
|
458
805
|
return null;
|
|
459
806
|
}
|
|
460
|
-
function
|
|
461
|
-
if (
|
|
462
|
-
return
|
|
807
|
+
function extractDeltaText(delta) {
|
|
808
|
+
if (typeof delta === "string")
|
|
809
|
+
return delta;
|
|
810
|
+
if (delta && typeof delta === "object") {
|
|
811
|
+
const text = delta.text;
|
|
812
|
+
if (typeof text === "string")
|
|
813
|
+
return text;
|
|
463
814
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (
|
|
470
|
-
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
function extractItemText(item) {
|
|
818
|
+
if (typeof item === "string")
|
|
819
|
+
return item;
|
|
820
|
+
if (item && typeof item === "object") {
|
|
821
|
+
const t = item.text;
|
|
822
|
+
if (typeof t === "string")
|
|
823
|
+
return t;
|
|
824
|
+
}
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
function extractContentText(content) {
|
|
828
|
+
if (typeof content === "string")
|
|
829
|
+
return content;
|
|
830
|
+
if (Array.isArray(content)) {
|
|
831
|
+
const parts = content.map(extractItemText).filter((t) => Boolean(t));
|
|
832
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
833
|
+
}
|
|
834
|
+
return extractItemText(content);
|
|
835
|
+
}
|
|
836
|
+
function getPayloadText(u) {
|
|
837
|
+
if (typeof u.rawOutput === "string")
|
|
838
|
+
return u.rawOutput;
|
|
839
|
+
if (u.rawOutput && typeof u.rawOutput === "object") {
|
|
840
|
+
try {
|
|
841
|
+
return JSON.stringify(u.rawOutput);
|
|
842
|
+
}
|
|
843
|
+
catch { }
|
|
471
844
|
}
|
|
845
|
+
return extractContentText(u.content);
|
|
846
|
+
}
|
|
847
|
+
function extractUpdateText(msg) {
|
|
848
|
+
if (!("params" in msg) || !msg.params)
|
|
849
|
+
return null;
|
|
850
|
+
const p = msg.params?.update;
|
|
851
|
+
if (!p)
|
|
852
|
+
return null;
|
|
853
|
+
return (extractContentText(p.content) ??
|
|
854
|
+
extractDeltaText(p.delta) ??
|
|
855
|
+
(typeof p.rawOutput === "string" ? p.rawOutput : null));
|
|
856
|
+
}
|
|
857
|
+
function processToolCallUpdate(sessionId, u, tracker) {
|
|
858
|
+
if (u.sessionUpdate !== SESSION_UPDATES.TOOL_CALL_UPDATE) {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
const text = getPayloadText(u);
|
|
862
|
+
if (!text)
|
|
863
|
+
return null;
|
|
864
|
+
const subEntries = tracker.updateFromManageSubagents(sessionId, text);
|
|
865
|
+
if (subEntries)
|
|
866
|
+
return subEntries;
|
|
867
|
+
const taskEntries = tracker.updateFromManageTask(sessionId, text);
|
|
868
|
+
if (taskEntries)
|
|
869
|
+
return taskEntries;
|
|
870
|
+
return tracker.updateFromText(sessionId, text);
|
|
871
|
+
}
|
|
872
|
+
function processInboundToolCall(sessionId, u, tracker) {
|
|
873
|
+
const bgEntries = handleToolCallState(sessionId, u, tracker);
|
|
874
|
+
if (bgEntries)
|
|
875
|
+
return bgEntries;
|
|
876
|
+
const updatedPlan = processToolCallUpdate(sessionId, u, tracker);
|
|
877
|
+
if (updatedPlan)
|
|
878
|
+
return updatedPlan;
|
|
472
879
|
const toolArgs = u.rawInput ?? u.arguments;
|
|
473
880
|
const toolName = inferToolName(u.name ?? undefined, u.title ?? undefined, toolArgs);
|
|
474
881
|
if (!toolName)
|
|
475
|
-
return
|
|
476
|
-
|
|
477
|
-
|
|
882
|
+
return null;
|
|
883
|
+
return handleInboundToolCall(sessionId, toolName, toolArgs, tracker, u.toolCallId);
|
|
884
|
+
}
|
|
885
|
+
function processInboundSessionUpdate(msg, sessionId, tracker) {
|
|
886
|
+
if (!isMethod(msg, ACP_METHODS.SESSION_UPDATE))
|
|
887
|
+
return null;
|
|
888
|
+
const text = extractUpdateText(msg);
|
|
889
|
+
return text ? tracker.updateFromText(sessionId, text) : null;
|
|
890
|
+
}
|
|
891
|
+
function processInbound(msg, tracker, context) {
|
|
892
|
+
const extracted = extractToolCallUpdate(msg);
|
|
893
|
+
if (extracted) {
|
|
894
|
+
const plan = processInboundToolCall(extracted.sessionId, extracted.update, tracker);
|
|
895
|
+
return plan ? [msg, createPlanUpdateMessage(extracted.sessionId, plan)] : [msg];
|
|
896
|
+
}
|
|
897
|
+
const sessionId = extractSessionId(msg) ?? context?.session?.sessionId;
|
|
898
|
+
if (sessionId) {
|
|
899
|
+
const plan = processInboundSessionUpdate(msg, sessionId, tracker);
|
|
900
|
+
if (plan)
|
|
901
|
+
return [msg, createPlanUpdateMessage(sessionId, plan)];
|
|
902
|
+
}
|
|
903
|
+
return [msg];
|
|
478
904
|
}
|
|
479
905
|
function processStderrLine(line, tracker, context) {
|
|
480
906
|
const tsu = parseTrajectoryStateUpdate(line);
|
|
@@ -505,11 +931,22 @@ function forwardCancelPromptMsg(cancelPromptMsg, context) {
|
|
|
505
931
|
context.forwardInbound?.(cancelPromptMsg);
|
|
506
932
|
}
|
|
507
933
|
function handleOutboundCancel(sessionId, tracker, context) {
|
|
934
|
+
const hadActiveTasks = tracker.hasActiveTasksOrSubagents(sessionId);
|
|
508
935
|
const { planMsg, cancelPromptMsg } = tracker.onCancel(sessionId);
|
|
509
936
|
if (planMsg)
|
|
510
937
|
context.forwardInbound?.(planMsg);
|
|
511
938
|
if (cancelPromptMsg)
|
|
512
939
|
forwardCancelPromptMsg(cancelPromptMsg, context);
|
|
940
|
+
const session = context.session ??
|
|
941
|
+
(context.sessionCache ? getSession(context.sessionCache, sessionId) : undefined);
|
|
942
|
+
if (hadActiveTasks) {
|
|
943
|
+
const targetSession = session ??
|
|
944
|
+
(context.sessionCache ? getOrCreateSession(context.sessionCache, sessionId) : undefined);
|
|
945
|
+
if (targetSession) {
|
|
946
|
+
targetSession.needsRecycle = true;
|
|
947
|
+
setFixData(targetSession, "hadActiveTasksOnCancel", true);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
513
950
|
}
|
|
514
951
|
function handleOutbound(msg, tracker, context) {
|
|
515
952
|
const sessionId = extractSessionId(msg);
|
|
@@ -540,8 +977,14 @@ export function createSilentBackgroundTasksFix(_options) {
|
|
|
540
977
|
return processStderrLine(line, tracker, context);
|
|
541
978
|
},
|
|
542
979
|
onTurnEnd(sessionId, _context) {
|
|
980
|
+
const pendingPlan = tracker.flushPendingPlan(sessionId);
|
|
543
981
|
const planMsg = tracker.onTurnEnd(sessionId);
|
|
544
|
-
|
|
982
|
+
const messages = [];
|
|
983
|
+
if (pendingPlan)
|
|
984
|
+
messages.push(pendingPlan);
|
|
985
|
+
if (planMsg && (!pendingPlan || planMsg !== pendingPlan))
|
|
986
|
+
messages.push(planMsg);
|
|
987
|
+
return messages;
|
|
545
988
|
},
|
|
546
989
|
dispose() {
|
|
547
990
|
tracker.dispose();
|