@simonepri/refined-antigravity-acp 1.3.1 → 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.
@@ -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
- 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,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 newEntries = subagents.map((s) => ({
141
- content: formatSubagentContent(s),
142
- priority: "high",
143
- status: "in_progress",
144
- }));
145
- tracker.entries.push(...newEntries);
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 = "in_progress";
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 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);
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 bgDesc = extractBackgroundTaskDescription(u.rawOutput, u.toolCallId, storedCmd);
452
- if (bgDesc) {
453
- const entries = tracker.recordCustomTask(sessionId, `Background task: ${bgDesc}`, "high", u.toolCallId);
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 (!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)];
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 toolArgs = u.rawInput ?? u.arguments;
473
- const toolName = inferToolName(u.name ?? undefined, u.title ?? undefined, toolArgs);
474
- if (!toolName)
475
- return [msg];
476
- const entries = handleInboundToolCall(sessionId, toolName, toolArgs, tracker, u.toolCallId);
477
- return entries ? [msg, createPlanUpdateMessage(sessionId, entries)] : [msg];
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
- return planMsg ? [planMsg] : [];
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();