@simonepri/refined-antigravity-acp 1.3.2 → 1.4.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/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/silent-background-tasks/index.d.ts +30 -0
- package/dist/src/fixes/silent-background-tasks/index.d.ts.map +1 -1
- package/dist/src/fixes/silent-background-tasks/index.js +518 -39
- package/dist/src/fixes/silent-background-tasks/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* standard ACP `session/update` notifications with `sessionUpdate: "plan"` to expose live status.
|
|
10
10
|
*/
|
|
11
11
|
import { ACP_METHODS, SESSION_UPDATES, STOP_REASONS, isMethod, } from "../../core/types.js";
|
|
12
|
-
import { extractSessionId, setFixData } from "../../core/session-cache.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,15 @@ 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(),
|
|
349
|
+
taskIds: new Map(),
|
|
91
350
|
deferredPromptResponse: null,
|
|
351
|
+
pendingPlan: null,
|
|
92
352
|
};
|
|
93
353
|
this.sessions.set(sessionId, tracker);
|
|
94
354
|
}
|
|
@@ -97,17 +357,47 @@ export class BackgroundTasksTracker {
|
|
|
97
357
|
getEntries(sessionId) {
|
|
98
358
|
return this.sessions.get(sessionId)?.entries ?? [];
|
|
99
359
|
}
|
|
360
|
+
hasActiveTasksOrSubagents(sessionId) {
|
|
361
|
+
const tracker = this.sessions.get(sessionId);
|
|
362
|
+
if (!tracker)
|
|
363
|
+
return false;
|
|
364
|
+
return (tracker.isWaiting ||
|
|
365
|
+
tracker.trackedSubagents.length > 0 ||
|
|
366
|
+
tracker.entries.some((e) => e.status !== "completed"));
|
|
367
|
+
}
|
|
368
|
+
setPendingPlan(sessionId, entries) {
|
|
369
|
+
this.getOrCreate(sessionId).pendingPlan = entries;
|
|
370
|
+
}
|
|
371
|
+
flushPendingPlan(sessionId) {
|
|
372
|
+
const tracker = this.sessions.get(sessionId);
|
|
373
|
+
if (!tracker || !tracker.pendingPlan)
|
|
374
|
+
return null;
|
|
375
|
+
const plan = tracker.pendingPlan;
|
|
376
|
+
tracker.pendingPlan = null;
|
|
377
|
+
return createPlanUpdateMessage(sessionId, plan);
|
|
378
|
+
}
|
|
100
379
|
onPromptStart(sessionId, promptId) {
|
|
101
380
|
if (promptId !== undefined) {
|
|
102
381
|
this.promptIdToSessionId.set(promptId, sessionId);
|
|
103
382
|
}
|
|
104
383
|
const tracker = this.getOrCreate(sessionId);
|
|
105
384
|
tracker.entries = [];
|
|
385
|
+
tracker.trackedSubagents = [];
|
|
386
|
+
tracker.recentText = "";
|
|
106
387
|
tracker.hasEmittedWaitingPlan = false;
|
|
107
388
|
tracker.isWaiting = false;
|
|
108
389
|
tracker.deferredPromptResponse = null;
|
|
390
|
+
tracker.pendingPlan = null;
|
|
109
391
|
tracker.seenToolCallIds.clear();
|
|
110
392
|
tracker.activeToolCalls.clear();
|
|
393
|
+
tracker.taskIds.clear();
|
|
394
|
+
}
|
|
395
|
+
recordTaskId(sessionId, taskId, content) {
|
|
396
|
+
let id = taskId;
|
|
397
|
+
if (id.includes("/")) {
|
|
398
|
+
id = id.split("/").pop() ?? id;
|
|
399
|
+
}
|
|
400
|
+
this.getOrCreate(sessionId).taskIds.set(id, content);
|
|
111
401
|
}
|
|
112
402
|
getSessionForPromptId(promptId) {
|
|
113
403
|
return this.promptIdToSessionId.get(promptId);
|
|
@@ -137,14 +427,68 @@ export class BackgroundTasksTracker {
|
|
|
137
427
|
if (toolCallId) {
|
|
138
428
|
tracker.seenToolCallIds.add(toolCallId);
|
|
139
429
|
}
|
|
140
|
-
const
|
|
141
|
-
content
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
430
|
+
for (const s of subagents) {
|
|
431
|
+
const content = formatSubagentContent(s);
|
|
432
|
+
const existing = tracker.trackedSubagents.find((t) => t.content === content ||
|
|
433
|
+
(s.role && t.role && t.role.trim().toLowerCase() === s.role.trim().toLowerCase()));
|
|
434
|
+
if (existing) {
|
|
435
|
+
if (s.conversationId && !existing.conversationId) {
|
|
436
|
+
existing.conversationId = s.conversationId;
|
|
437
|
+
}
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
tracker.trackedSubagents.push({
|
|
441
|
+
...s,
|
|
442
|
+
content,
|
|
443
|
+
status: "in_progress",
|
|
444
|
+
});
|
|
445
|
+
tracker.entries.push({
|
|
446
|
+
content,
|
|
447
|
+
priority: "high",
|
|
448
|
+
status: "in_progress",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
146
451
|
return tracker.entries;
|
|
147
452
|
}
|
|
453
|
+
updateFromManageSubagents(sessionId, rawOutput) {
|
|
454
|
+
const reports = parseSubagentStatusReports(rawOutput);
|
|
455
|
+
if (reports.length === 0)
|
|
456
|
+
return null;
|
|
457
|
+
const tracker = this.getOrCreate(sessionId);
|
|
458
|
+
let changed = false;
|
|
459
|
+
for (const report of reports) {
|
|
460
|
+
if (applyReportToTracker(report, tracker)) {
|
|
461
|
+
changed = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return changed ? tracker.entries : null;
|
|
465
|
+
}
|
|
466
|
+
updateFromText(sessionId, text) {
|
|
467
|
+
const tracker = this.getOrCreate(sessionId);
|
|
468
|
+
let changed = Boolean(this.updateFromManageTask(sessionId, text));
|
|
469
|
+
if (registerLaunchedTasksFromText(tracker, text))
|
|
470
|
+
changed = true;
|
|
471
|
+
if (updateSubagentsFromText(tracker, text))
|
|
472
|
+
changed = true;
|
|
473
|
+
return changed ? tracker.entries : null;
|
|
474
|
+
}
|
|
475
|
+
updateFromManageTask(sessionId, rawOutput) {
|
|
476
|
+
if (typeof rawOutput !== "string")
|
|
477
|
+
return null;
|
|
478
|
+
const taskId = extractFinishedTaskId(rawOutput);
|
|
479
|
+
if (taskId === undefined && !rawOutput.includes("finished with result:")) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
const tracker = this.getOrCreate(sessionId);
|
|
483
|
+
let changed = false;
|
|
484
|
+
for (const entry of tracker.entries) {
|
|
485
|
+
if (isTaskMatchingEntry(entry, taskId, tracker.entries.length, tracker.taskIds)) {
|
|
486
|
+
entry.status = "completed";
|
|
487
|
+
changed = true;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return changed ? tracker.entries : null;
|
|
491
|
+
}
|
|
148
492
|
recordCustomTask(sessionId, content, priority = "medium", toolCallId) {
|
|
149
493
|
const tracker = this.getOrCreate(sessionId);
|
|
150
494
|
if (toolCallId && tracker.seenToolCallIds.has(toolCallId)) {
|
|
@@ -172,7 +516,9 @@ export class BackgroundTasksTracker {
|
|
|
172
516
|
}
|
|
173
517
|
else {
|
|
174
518
|
for (const entry of tracker.entries) {
|
|
175
|
-
entry.status
|
|
519
|
+
if (entry.status !== "completed") {
|
|
520
|
+
entry.status = "in_progress";
|
|
521
|
+
}
|
|
176
522
|
}
|
|
177
523
|
}
|
|
178
524
|
tracker.hasEmittedWaitingPlan = true;
|
|
@@ -189,6 +535,9 @@ export class BackgroundTasksTracker {
|
|
|
189
535
|
}
|
|
190
536
|
tracker.isWaiting = false;
|
|
191
537
|
tracker.hasEmittedWaitingPlan = false;
|
|
538
|
+
if (tracker.trackedSubagents.length > 0) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
192
541
|
for (const entry of tracker.entries) {
|
|
193
542
|
entry.status = "completed";
|
|
194
543
|
}
|
|
@@ -225,6 +574,7 @@ export class BackgroundTasksTracker {
|
|
|
225
574
|
if (!tracker)
|
|
226
575
|
return { planMsg: null, cancelPromptMsg: null };
|
|
227
576
|
tracker.isWaiting = false;
|
|
577
|
+
tracker.pendingPlan = null;
|
|
228
578
|
let cancelPromptMsg = null;
|
|
229
579
|
if (tracker.deferredPromptResponse && "id" in tracker.deferredPromptResponse) {
|
|
230
580
|
cancelPromptMsg = {
|
|
@@ -380,20 +730,39 @@ function extractCommandLine(rawArgs) {
|
|
|
380
730
|
}
|
|
381
731
|
return null;
|
|
382
732
|
}
|
|
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);
|
|
733
|
+
function extractTaskDescFromText(text, storedCommand) {
|
|
734
|
+
const match = text.match(/Task Description:\s*([^\n\r]+)/i);
|
|
391
735
|
let desc = match?.[1]?.trim() || storedCommand || "command";
|
|
392
736
|
if (desc.startsWith('"') && desc.endsWith('"')) {
|
|
393
737
|
desc = desc.slice(1, -1);
|
|
394
738
|
}
|
|
395
739
|
return desc;
|
|
396
740
|
}
|
|
741
|
+
function extractTaskIdFromText(text) {
|
|
742
|
+
const idMatch = text.match(/task id:\s*([^\s\n\r]+)/i);
|
|
743
|
+
let taskId = idMatch?.[1]?.trim();
|
|
744
|
+
if (taskId && taskId.includes("/")) {
|
|
745
|
+
taskId = taskId.split("/").pop();
|
|
746
|
+
}
|
|
747
|
+
return taskId;
|
|
748
|
+
}
|
|
749
|
+
function isBackgroundTaskOutput(text) {
|
|
750
|
+
return (text.includes("Tool is running as a background task") ||
|
|
751
|
+
text.includes("is running as a background task") ||
|
|
752
|
+
text.includes("task id:"));
|
|
753
|
+
}
|
|
754
|
+
export function extractBackgroundTaskInfo(rawOutput, _toolCallId, storedCommand) {
|
|
755
|
+
const text = typeof rawOutput === "string" ? rawOutput : extractContentText(rawOutput);
|
|
756
|
+
if (!text || !isBackgroundTaskOutput(text))
|
|
757
|
+
return null;
|
|
758
|
+
return {
|
|
759
|
+
desc: extractTaskDescFromText(text, storedCommand),
|
|
760
|
+
taskId: extractTaskIdFromText(text),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
export function extractBackgroundTaskDescription(rawOutput, toolCallId, storedCommand) {
|
|
764
|
+
return extractBackgroundTaskInfo(rawOutput, toolCallId, storedCommand)?.desc ?? null;
|
|
765
|
+
}
|
|
397
766
|
function handleInboundToolCall(sessionId, toolName, toolArgs, tracker, toolCallId) {
|
|
398
767
|
if (toolName === TOOL_INVOKE_SUBAGENT) {
|
|
399
768
|
const subagents = parseSubagentsFromArgs(toolArgs);
|
|
@@ -403,7 +772,13 @@ function handleInboundToolCall(sessionId, toolName, toolArgs, tracker, toolCallI
|
|
|
403
772
|
return entries.length > prevCount ? entries : null;
|
|
404
773
|
}
|
|
405
774
|
if (toolName === TOOL_SCHEDULE) {
|
|
775
|
+
if (tracker.hasActiveTasksOrSubagents(sessionId)) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
406
778
|
const taskName = formatScheduleTask(toolArgs);
|
|
779
|
+
if (taskName === "Timer" || taskName.startsWith("Timer: Wait")) {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
407
782
|
const prevCount = tracker.getEntries(sessionId).length;
|
|
408
783
|
const entries = tracker.recordCustomTask(sessionId, taskName, "medium", toolCallId);
|
|
409
784
|
tracker.setWaiting(sessionId, true);
|
|
@@ -447,34 +822,121 @@ function handleToolCallState(sessionId, u, tracker) {
|
|
|
447
822
|
return null;
|
|
448
823
|
}
|
|
449
824
|
if (u.sessionUpdate === SESSION_UPDATES.TOOL_CALL_UPDATE) {
|
|
825
|
+
const text = getPayloadText(u);
|
|
450
826
|
const storedCmd = u.toolCallId ? tracker.getToolCommand(sessionId, u.toolCallId) : undefined;
|
|
451
|
-
const
|
|
452
|
-
if (
|
|
453
|
-
const
|
|
827
|
+
const bgInfo = extractBackgroundTaskInfo(text, u.toolCallId, storedCmd);
|
|
828
|
+
if (bgInfo) {
|
|
829
|
+
const content = `Background task: ${bgInfo.desc}`;
|
|
830
|
+
const entries = tracker.recordCustomTask(sessionId, content, "high", u.toolCallId);
|
|
831
|
+
if (bgInfo.taskId) {
|
|
832
|
+
tracker.recordTaskId(sessionId, bgInfo.taskId, content);
|
|
833
|
+
}
|
|
454
834
|
tracker.setWaiting(sessionId, true);
|
|
455
835
|
return entries;
|
|
456
836
|
}
|
|
457
837
|
}
|
|
458
838
|
return null;
|
|
459
839
|
}
|
|
840
|
+
function extractDeltaText(delta) {
|
|
841
|
+
if (typeof delta === "string")
|
|
842
|
+
return delta;
|
|
843
|
+
if (delta && typeof delta === "object") {
|
|
844
|
+
const text = delta.text;
|
|
845
|
+
if (typeof text === "string")
|
|
846
|
+
return text;
|
|
847
|
+
}
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
function extractItemText(item) {
|
|
851
|
+
if (typeof item === "string")
|
|
852
|
+
return item;
|
|
853
|
+
if (item && typeof item === "object") {
|
|
854
|
+
const t = item.text;
|
|
855
|
+
if (typeof t === "string")
|
|
856
|
+
return t;
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
function extractContentText(content) {
|
|
861
|
+
if (typeof content === "string")
|
|
862
|
+
return content;
|
|
863
|
+
if (Array.isArray(content)) {
|
|
864
|
+
const parts = content.map(extractItemText).filter((t) => Boolean(t));
|
|
865
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
866
|
+
}
|
|
867
|
+
return extractItemText(content);
|
|
868
|
+
}
|
|
869
|
+
function getPayloadText(u) {
|
|
870
|
+
if (typeof u.rawOutput === "string")
|
|
871
|
+
return u.rawOutput;
|
|
872
|
+
if (u.rawOutput && typeof u.rawOutput === "object") {
|
|
873
|
+
try {
|
|
874
|
+
return JSON.stringify(u.rawOutput);
|
|
875
|
+
}
|
|
876
|
+
catch { }
|
|
877
|
+
}
|
|
878
|
+
return extractContentText(u.content);
|
|
879
|
+
}
|
|
880
|
+
function extractUpdateText(msg) {
|
|
881
|
+
if (!("params" in msg) || !msg.params)
|
|
882
|
+
return null;
|
|
883
|
+
const p = msg.params?.update;
|
|
884
|
+
if (!p)
|
|
885
|
+
return null;
|
|
886
|
+
return (extractContentText(p.content) ??
|
|
887
|
+
extractDeltaText(p.delta) ??
|
|
888
|
+
(typeof p.rawOutput === "string" ? p.rawOutput : null));
|
|
889
|
+
}
|
|
890
|
+
function processToolCallUpdate(sessionId, u, tracker) {
|
|
891
|
+
if (u.sessionUpdate !== SESSION_UPDATES.TOOL_CALL_UPDATE) {
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
const text = getPayloadText(u);
|
|
895
|
+
if (!text)
|
|
896
|
+
return null;
|
|
897
|
+
const subEntries = tracker.updateFromManageSubagents(sessionId, text);
|
|
898
|
+
if (subEntries)
|
|
899
|
+
return subEntries;
|
|
900
|
+
const taskEntries = tracker.updateFromManageTask(sessionId, text);
|
|
901
|
+
if (taskEntries)
|
|
902
|
+
return taskEntries;
|
|
903
|
+
return tracker.updateFromText(sessionId, text);
|
|
904
|
+
}
|
|
905
|
+
function processInboundToolCall(sessionId, u, tracker) {
|
|
906
|
+
const bgEntries = handleToolCallState(sessionId, u, tracker);
|
|
907
|
+
if (bgEntries)
|
|
908
|
+
return bgEntries;
|
|
909
|
+
const updatedPlan = processToolCallUpdate(sessionId, u, tracker);
|
|
910
|
+
if (updatedPlan)
|
|
911
|
+
return updatedPlan;
|
|
912
|
+
const toolArgs = u.rawInput ?? u.arguments;
|
|
913
|
+
const toolName = inferToolName(u.name ?? undefined, u.title ?? undefined, toolArgs);
|
|
914
|
+
if (!toolName)
|
|
915
|
+
return null;
|
|
916
|
+
return handleInboundToolCall(sessionId, toolName, toolArgs, tracker, u.toolCallId);
|
|
917
|
+
}
|
|
918
|
+
function processInboundSessionUpdate(msg, sessionId, tracker) {
|
|
919
|
+
if (!isMethod(msg, ACP_METHODS.SESSION_UPDATE))
|
|
920
|
+
return null;
|
|
921
|
+
const text = extractUpdateText(msg);
|
|
922
|
+
return text ? tracker.updateFromText(sessionId, text) : null;
|
|
923
|
+
}
|
|
460
924
|
function processInbound(msg, tracker, context) {
|
|
461
925
|
if (tryInterceptWaitingPromptResponse(msg, tracker, context)) {
|
|
462
926
|
return [];
|
|
463
927
|
}
|
|
464
928
|
const extracted = extractToolCallUpdate(msg);
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const bgEntries = handleToolCallState(sessionId, u, tracker);
|
|
469
|
-
if (bgEntries) {
|
|
470
|
-
return [msg, createPlanUpdateMessage(sessionId, bgEntries)];
|
|
929
|
+
if (extracted) {
|
|
930
|
+
const plan = processInboundToolCall(extracted.sessionId, extracted.update, tracker);
|
|
931
|
+
return plan ? [msg, createPlanUpdateMessage(extracted.sessionId, plan)] : [msg];
|
|
471
932
|
}
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
933
|
+
const sessionId = extractSessionId(msg) ?? context?.session?.sessionId;
|
|
934
|
+
if (sessionId) {
|
|
935
|
+
const plan = processInboundSessionUpdate(msg, sessionId, tracker);
|
|
936
|
+
if (plan)
|
|
937
|
+
return [msg, createPlanUpdateMessage(sessionId, plan)];
|
|
938
|
+
}
|
|
939
|
+
return [msg];
|
|
478
940
|
}
|
|
479
941
|
function processStderrLine(line, tracker, context) {
|
|
480
942
|
const tsu = parseTrajectoryStateUpdate(line);
|
|
@@ -505,11 +967,22 @@ function forwardCancelPromptMsg(cancelPromptMsg, context) {
|
|
|
505
967
|
context.forwardInbound?.(cancelPromptMsg);
|
|
506
968
|
}
|
|
507
969
|
function handleOutboundCancel(sessionId, tracker, context) {
|
|
970
|
+
const hadActiveTasks = tracker.hasActiveTasksOrSubagents(sessionId);
|
|
508
971
|
const { planMsg, cancelPromptMsg } = tracker.onCancel(sessionId);
|
|
509
972
|
if (planMsg)
|
|
510
973
|
context.forwardInbound?.(planMsg);
|
|
511
974
|
if (cancelPromptMsg)
|
|
512
975
|
forwardCancelPromptMsg(cancelPromptMsg, context);
|
|
976
|
+
const session = context.session ??
|
|
977
|
+
(context.sessionCache ? getSession(context.sessionCache, sessionId) : undefined);
|
|
978
|
+
if (hadActiveTasks) {
|
|
979
|
+
const targetSession = session ??
|
|
980
|
+
(context.sessionCache ? getOrCreateSession(context.sessionCache, sessionId) : undefined);
|
|
981
|
+
if (targetSession) {
|
|
982
|
+
targetSession.needsRecycle = true;
|
|
983
|
+
setFixData(targetSession, "hadActiveTasksOnCancel", true);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
513
986
|
}
|
|
514
987
|
function handleOutbound(msg, tracker, context) {
|
|
515
988
|
const sessionId = extractSessionId(msg);
|
|
@@ -540,8 +1013,14 @@ export function createSilentBackgroundTasksFix(_options) {
|
|
|
540
1013
|
return processStderrLine(line, tracker, context);
|
|
541
1014
|
},
|
|
542
1015
|
onTurnEnd(sessionId, _context) {
|
|
1016
|
+
const pendingPlan = tracker.flushPendingPlan(sessionId);
|
|
543
1017
|
const planMsg = tracker.onTurnEnd(sessionId);
|
|
544
|
-
|
|
1018
|
+
const messages = [];
|
|
1019
|
+
if (pendingPlan)
|
|
1020
|
+
messages.push(pendingPlan);
|
|
1021
|
+
if (planMsg && (!pendingPlan || planMsg !== pendingPlan))
|
|
1022
|
+
messages.push(planMsg);
|
|
1023
|
+
return messages;
|
|
545
1024
|
},
|
|
546
1025
|
dispose() {
|
|
547
1026
|
tracker.dispose();
|