@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.
@@ -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, STOP_REASONS, isMethod, } from "../../core/types.js";
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
- role: typeof s.Role === "string" ? s.Role : undefined,
36
- prompt: typeof s.Prompt === "string" ? s.Prompt : undefined,
37
- typeName: typeof s.TypeName === "string" ? s.TypeName : undefined,
54
+ id,
55
+ role,
56
+ prompt,
57
+ typeName,
58
+ conversationId,
38
59
  };
39
60
  }
40
- export function parseSubagentsFromArgs(rawArgs) {
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 subs = obj.Subagents;
55
- if (!Array.isArray(subs))
56
- return [];
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
- deferredPromptResponse: null,
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 = false;
108
- tracker.deferredPromptResponse = null;
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 newEntries = subagents.map((s) => ({
141
- content: formatSubagentContent(s),
142
- priority: "high",
143
- status: "in_progress",
144
- }));
145
- tracker.entries.push(...newEntries);
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 = "in_progress";
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, context) {
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
- const planMsg = createPlanUpdateMessage(sessionId, tracker.entries);
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
- let cancelPromptMsg = null;
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 extractBackgroundTaskDescription(rawOutput, _toolCallId, storedCommand) {
384
- if (typeof rawOutput !== "string")
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 bgDesc = extractBackgroundTaskDescription(u.rawOutput, u.toolCallId, storedCmd);
452
- if (bgDesc) {
453
- const entries = tracker.recordCustomTask(sessionId, `Background task: ${bgDesc}`, "high", u.toolCallId);
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 processInbound(msg, tracker, context) {
461
- if (tryInterceptWaitingPromptResponse(msg, tracker, context)) {
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
- const extracted = extractToolCallUpdate(msg);
465
- if (!extracted)
466
- return [msg];
467
- const { sessionId, update: u } = extracted;
468
- const bgEntries = handleToolCallState(sessionId, u, tracker);
469
- if (bgEntries) {
470
- return [msg, createPlanUpdateMessage(sessionId, bgEntries)];
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 [msg];
476
- const entries = handleInboundToolCall(sessionId, toolName, toolArgs, tracker, u.toolCallId);
477
- return entries ? [msg, createPlanUpdateMessage(sessionId, entries)] : [msg];
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
- return planMsg ? [planMsg] : [];
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();