wave-agent-sdk 0.13.1 → 0.13.3

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.
Files changed (66) hide show
  1. package/dist/agent.d.ts +7 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +37 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +65 -33
  9. package/dist/managers/backgroundTaskManager.d.ts +1 -0
  10. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundTaskManager.js +49 -0
  12. package/dist/managers/forkedAgentManager.d.ts +49 -0
  13. package/dist/managers/forkedAgentManager.d.ts.map +1 -0
  14. package/dist/managers/forkedAgentManager.js +111 -0
  15. package/dist/managers/messageManager.d.ts +8 -1
  16. package/dist/managers/messageManager.d.ts.map +1 -1
  17. package/dist/managers/messageManager.js +14 -1
  18. package/dist/managers/notificationQueue.d.ts +8 -0
  19. package/dist/managers/notificationQueue.d.ts.map +1 -0
  20. package/dist/managers/notificationQueue.js +17 -0
  21. package/dist/managers/permissionManager.d.ts.map +1 -1
  22. package/dist/managers/permissionManager.js +2 -0
  23. package/dist/managers/subagentManager.d.ts +7 -9
  24. package/dist/managers/subagentManager.d.ts.map +1 -1
  25. package/dist/managers/subagentManager.js +101 -21
  26. package/dist/services/autoMemoryService.d.ts +1 -1
  27. package/dist/services/autoMemoryService.d.ts.map +1 -1
  28. package/dist/services/autoMemoryService.js +7 -8
  29. package/dist/services/interactionService.d.ts.map +1 -1
  30. package/dist/services/interactionService.js +12 -0
  31. package/dist/types/agent.d.ts +1 -0
  32. package/dist/types/agent.d.ts.map +1 -1
  33. package/dist/types/messaging.d.ts +9 -1
  34. package/dist/types/messaging.d.ts.map +1 -1
  35. package/dist/utils/containerSetup.d.ts.map +1 -1
  36. package/dist/utils/containerSetup.js +6 -0
  37. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  38. package/dist/utils/convertMessagesForAPI.js +8 -0
  39. package/dist/utils/messageOperations.d.ts +9 -0
  40. package/dist/utils/messageOperations.d.ts.map +1 -1
  41. package/dist/utils/messageOperations.js +17 -0
  42. package/dist/utils/notificationXml.d.ts +4 -0
  43. package/dist/utils/notificationXml.d.ts.map +1 -0
  44. package/dist/utils/notificationXml.js +40 -0
  45. package/dist/utils/pathEncoder.d.ts +0 -1
  46. package/dist/utils/pathEncoder.d.ts.map +1 -1
  47. package/dist/utils/pathEncoder.js +1 -5
  48. package/package.json +1 -1
  49. package/src/agent.ts +44 -0
  50. package/src/index.ts +1 -0
  51. package/src/managers/aiManager.ts +76 -41
  52. package/src/managers/backgroundTaskManager.ts +72 -1
  53. package/src/managers/forkedAgentManager.ts +193 -0
  54. package/src/managers/messageManager.ts +25 -0
  55. package/src/managers/notificationQueue.ts +19 -0
  56. package/src/managers/permissionManager.ts +2 -0
  57. package/src/managers/subagentManager.ts +135 -41
  58. package/src/services/autoMemoryService.ts +11 -17
  59. package/src/services/interactionService.ts +18 -0
  60. package/src/types/agent.ts +1 -0
  61. package/src/types/messaging.ts +11 -1
  62. package/src/utils/containerSetup.ts +8 -0
  63. package/src/utils/convertMessagesForAPI.ts +9 -0
  64. package/src/utils/messageOperations.ts +42 -1
  65. package/src/utils/notificationXml.ts +52 -0
  66. package/src/utils/pathEncoder.ts +1 -6
@@ -0,0 +1,193 @@
1
+ import { randomUUID } from "crypto";
2
+ import * as os from "os";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import type { Message } from "../types/index.js";
6
+ import { logger } from "../utils/globalLogger.js";
7
+ import { Container } from "../utils/container.js";
8
+ import { SubagentManager, type SubagentInstance } from "./subagentManager.js";
9
+ import type { PermissionMode } from "../types/permissions.js";
10
+
11
+ export interface ForkedAgentEntry {
12
+ id: string;
13
+ instance: SubagentInstance;
14
+ logPath: string;
15
+ logStream?: fs.WriteStream;
16
+ status: "running" | "completed" | "failed";
17
+ }
18
+
19
+ export interface ForkedAgentManagerCallbacks {
20
+ onForkedAgentStatusChange?: (entries: ForkedAgentEntry[]) => void;
21
+ }
22
+
23
+ export class ForkedAgentManager {
24
+ private activeForks = new Map<string, ForkedAgentEntry>();
25
+ private callbacks: ForkedAgentManagerCallbacks;
26
+
27
+ constructor(
28
+ private container: Container,
29
+ options: { callbacks?: ForkedAgentManagerCallbacks } = {},
30
+ ) {
31
+ this.callbacks = options.callbacks || {};
32
+ }
33
+
34
+ private get subagentManager(): SubagentManager {
35
+ return this.container.get<SubagentManager>("SubagentManager")!;
36
+ }
37
+
38
+ /**
39
+ * Creates a forked subagent with conversation history and executes it asynchronously (fire-and-forget).
40
+ * Does NOT interact with BackgroundTaskManager.
41
+ */
42
+ async forkAndExecute(
43
+ subagentType: string,
44
+ messages: Message[],
45
+ parameters: {
46
+ description: string;
47
+ allowedTools?: string[];
48
+ model?: string;
49
+ permissionModeOverride?: PermissionMode;
50
+ },
51
+ prompt: string,
52
+ ): Promise<string> {
53
+ const id = randomUUID();
54
+
55
+ // Create log file for debugging
56
+ const logPath = path.join(os.tmpdir(), `wave-forked-agent-${id}.log`);
57
+ const logStream = fs.createWriteStream(logPath, { flags: "a" });
58
+
59
+ const entry: ForkedAgentEntry = {
60
+ id,
61
+ instance: {} as SubagentInstance, // Temporary placeholder
62
+ logPath,
63
+ logStream,
64
+ status: "running",
65
+ };
66
+ this.activeForks.set(id, entry);
67
+
68
+ // Fire-and-forget execution
69
+ this.executeFork(entry, subagentType, messages, parameters, prompt).catch(
70
+ (error) => {
71
+ logger.error("Forked agent execution failed:", error);
72
+ },
73
+ );
74
+
75
+ return id;
76
+ }
77
+
78
+ private async executeFork(
79
+ entry: ForkedAgentEntry,
80
+ subagentType: string,
81
+ messages: Message[],
82
+ parameters: {
83
+ description: string;
84
+ allowedTools?: string[];
85
+ model?: string;
86
+ permissionModeOverride?: PermissionMode;
87
+ },
88
+ prompt: string,
89
+ ): Promise<void> {
90
+ try {
91
+ const configuration =
92
+ await this.subagentManager.findSubagent(subagentType);
93
+ if (!configuration) {
94
+ throw new Error(`Subagent type ${subagentType} not found`);
95
+ }
96
+
97
+ const instance = await this.subagentManager.createInstance(
98
+ configuration,
99
+ {
100
+ description: parameters.description,
101
+ subagent_type: subagentType,
102
+ prompt: "",
103
+ allowedTools: parameters.allowedTools,
104
+ model: parameters.model,
105
+ permissionModeOverride: parameters.permissionModeOverride,
106
+ },
107
+ false,
108
+ );
109
+
110
+ // Pre-load the message manager with conversation history
111
+ instance.messageManager.setMessages(messages);
112
+ instance.logStream = entry.logStream;
113
+
114
+ entry.instance = instance;
115
+
116
+ // Execute the agent asynchronously
117
+ const result = await this.subagentManager.executeAgent(
118
+ instance,
119
+ prompt,
120
+ undefined,
121
+ false, // NOT runInBackground — we handle logging ourselves
122
+ );
123
+
124
+ // Write final response and completion to log
125
+ if (entry.logStream) {
126
+ entry.logStream.write(
127
+ `[${new Date().toISOString()}] Final response:\n${result}\n`,
128
+ );
129
+ entry.logStream.write(
130
+ `[${new Date().toISOString()}] Agent completed successfully\n`,
131
+ );
132
+ entry.logStream.end();
133
+ }
134
+
135
+ entry.status = "completed";
136
+ this.notifyChange();
137
+ } catch (error) {
138
+ const errorMessage =
139
+ error instanceof Error ? error.message : String(error);
140
+
141
+ if (entry.logStream) {
142
+ entry.logStream.write(
143
+ `[${new Date().toISOString()}] Agent failed: ${errorMessage}\n`,
144
+ );
145
+ entry.logStream.end();
146
+ }
147
+
148
+ entry.status = "failed";
149
+ this.notifyChange();
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Abort a running forked agent.
155
+ */
156
+ stop(id: string): boolean {
157
+ const entry = this.activeForks.get(id);
158
+ if (!entry) {
159
+ return false;
160
+ }
161
+
162
+ entry.instance?.aiManager?.abortAIMessage();
163
+ entry.logStream?.destroy();
164
+ this.activeForks.delete(id);
165
+ this.notifyChange();
166
+ return true;
167
+ }
168
+
169
+ /**
170
+ * Stop all running forked agents and clear the map.
171
+ */
172
+ cleanup(): void {
173
+ for (const [, entry] of this.activeForks) {
174
+ entry.instance?.aiManager?.abortAIMessage();
175
+ entry.logStream?.destroy();
176
+ }
177
+ this.activeForks.clear();
178
+ this.notifyChange();
179
+ }
180
+
181
+ /**
182
+ * Returns list of active forked agents.
183
+ */
184
+ getActiveForks(): ForkedAgentEntry[] {
185
+ return Array.from(this.activeForks.values());
186
+ }
187
+
188
+ private notifyChange(): void {
189
+ this.callbacks.onForkedAgentStatusChange?.(
190
+ Array.from(this.activeForks.values()),
191
+ );
192
+ }
193
+ }
@@ -9,8 +9,10 @@ import {
9
9
  completeBangInMessage,
10
10
  removeLastUserMessage,
11
11
  addToolBlockToMessageInMessages,
12
+ addNotificationMessageToMessages,
12
13
  UserMessageParams,
13
14
  type AgentToolBlockUpdateParams,
15
+ type AddNotificationMessageParams,
14
16
  generateMessageId,
15
17
  } from "../utils/messageOperations.js";
16
18
  import type { Message, Usage } from "../types/index.js";
@@ -56,6 +58,13 @@ export interface MessageManagerCallbacks {
56
58
  onFileHistoryBlockAdded?: (
57
59
  snapshots: import("../types/reversion.js").FileSnapshot[],
58
60
  ) => void;
61
+ // Notification callback
62
+ onNotificationMessageAdded?: (params: {
63
+ taskId: string;
64
+ taskType: "shell" | "agent";
65
+ status: "completed" | "failed" | "killed";
66
+ summary: string;
67
+ }) => void;
59
68
  }
60
69
 
61
70
  import { logger } from "../utils/globalLogger.js";
@@ -587,6 +596,22 @@ export class MessageManager {
587
596
  this.callbacks.onCompleteBangMessage?.(command, exitCode);
588
597
  }
589
598
 
599
+ public addNotificationMessage(
600
+ params: Omit<AddNotificationMessageParams, "messages">,
601
+ ): void {
602
+ const newMessages = addNotificationMessageToMessages({
603
+ messages: this.messages,
604
+ ...params,
605
+ });
606
+ this.setMessages(newMessages);
607
+ this.callbacks.onNotificationMessageAdded?.({
608
+ taskId: params.taskId,
609
+ taskType: params.taskType,
610
+ status: params.status,
611
+ summary: params.summary,
612
+ });
613
+ }
614
+
590
615
  /**
591
616
  * Rebuild usage array from messages containing usage metadata
592
617
  * Called during session restoration to reconstruct usage tracking
@@ -0,0 +1,19 @@
1
+ export class NotificationQueue {
2
+ private queue: string[] = [];
3
+ onNotificationsEnqueued?: () => void;
4
+
5
+ enqueue(notification: string): void {
6
+ this.queue.push(notification);
7
+ this.onNotificationsEnqueued?.();
8
+ }
9
+
10
+ dequeueAll(): string[] {
11
+ const items = [...this.queue];
12
+ this.queue = [];
13
+ return items;
14
+ }
15
+
16
+ hasPending(): boolean {
17
+ return this.queue.length > 0;
18
+ }
19
+ }
@@ -50,6 +50,7 @@ const SAFE_COMMANDS = [
50
50
  "wc",
51
51
  "sleep",
52
52
  "find",
53
+ "sort",
53
54
  ];
54
55
 
55
56
  const DEFAULT_ALLOWED_RULES = [
@@ -867,6 +868,7 @@ export class PermissionManager {
867
868
  cmd === "tail" ||
868
869
  cmd === "wc" ||
869
870
  cmd === "sleep" ||
871
+ cmd === "sort" ||
870
872
  (cmd === "find" && !isDangerousFind(part))
871
873
  ) {
872
874
  return true;
@@ -13,6 +13,7 @@ import {
13
13
  createAbortPromise,
14
14
  } from "../utils/abortUtils.js";
15
15
  import { BackgroundTaskManager } from "./backgroundTaskManager.js";
16
+ import { NotificationQueue } from "./notificationQueue.js";
16
17
  import { logger } from "../utils/globalLogger.js";
17
18
  import {
18
19
  UserMessageParams,
@@ -21,6 +22,7 @@ import {
21
22
 
22
23
  import { Container } from "../utils/container.js";
23
24
  import type { PermissionManager } from "./permissionManager.js";
25
+ import type { PermissionMode } from "../types/permissions.js";
24
26
  import { ConfigurationService } from "../services/configurationService.js";
25
27
 
26
28
  export interface SubagentManagerCallbacks {
@@ -85,6 +87,7 @@ export interface SubagentManagerOptions {
85
87
 
86
88
  export class SubagentManager {
87
89
  private instances = new Map<string, SubagentInstance>();
90
+ private subagentPermissionManagers = new Map<string, PermissionManager>();
88
91
  private cachedConfigurations: SubagentConfiguration[] | null = null;
89
92
 
90
93
  private workdir: string;
@@ -110,6 +113,47 @@ export class SubagentManager {
110
113
  */
111
114
  async initialize(): Promise<void> {
112
115
  await this.loadConfigurations();
116
+
117
+ // Hook into parent PermissionManager's update methods to propagate rules to subagents
118
+ const parentPm = this.container.get<PermissionManager>("PermissionManager");
119
+ if (
120
+ parentPm &&
121
+ typeof parentPm.updateAllowedRules === "function" &&
122
+ typeof parentPm.updateDeniedRules === "function" &&
123
+ typeof parentPm.updateAdditionalDirectories === "function"
124
+ ) {
125
+ const origUpdateAllowed = parentPm.updateAllowedRules.bind(parentPm);
126
+ const origUpdateDenied = parentPm.updateDeniedRules.bind(parentPm);
127
+ const origUpdateDirs =
128
+ parentPm.updateAdditionalDirectories.bind(parentPm);
129
+
130
+ parentPm.updateAllowedRules = (rules: string[]) => {
131
+ origUpdateAllowed(rules);
132
+ this.syncPermissionRulesToSubagents();
133
+ };
134
+ parentPm.updateDeniedRules = (rules: string[]) => {
135
+ origUpdateDenied(rules);
136
+ this.syncPermissionRulesToSubagents();
137
+ };
138
+ parentPm.updateAdditionalDirectories = (directories: string[]) => {
139
+ origUpdateDirs(directories);
140
+ this.syncPermissionRulesToSubagents();
141
+ };
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Sync parent permission rules to all running subagents
147
+ */
148
+ private syncPermissionRulesToSubagents(): void {
149
+ const parentPm = this.container.get<PermissionManager>("PermissionManager");
150
+ if (!parentPm) return;
151
+
152
+ for (const [, pm] of this.subagentPermissionManagers) {
153
+ pm.updateAllowedRules(parentPm.getAllowedRules());
154
+ pm.updateDeniedRules(parentPm.getDeniedRules());
155
+ pm.updateAdditionalDirectories(parentPm.getAdditionalDirectories());
156
+ }
113
157
  }
114
158
 
115
159
  /**
@@ -159,6 +203,7 @@ export class SubagentManager {
159
203
  allowedTools?: string[];
160
204
  model?: string;
161
205
  stream?: boolean;
206
+ permissionModeOverride?: PermissionMode;
162
207
  },
163
208
  runInBackground?: boolean,
164
209
  onUpdate?: () => void,
@@ -176,6 +221,23 @@ export class SubagentManager {
176
221
  // Create a child container for the subagent to isolate its managers
177
222
  const subagentContainer = this.container.createChild();
178
223
 
224
+ // Register a modified AgentOptions without onLoadingChange to prevent subagent loading
225
+ // from affecting the parent agent's loading state
226
+ const parentOptions =
227
+ this.container.get<import("../types/agent.js").AgentOptions>(
228
+ "AgentOptions",
229
+ );
230
+ if (parentOptions) {
231
+ const subagentOptions: import("../types/agent.js").AgentOptions = {
232
+ ...parentOptions,
233
+ callbacks: {
234
+ ...parentOptions.callbacks,
235
+ onLoadingChange: undefined,
236
+ },
237
+ };
238
+ subagentContainer.register("AgentOptions", subagentOptions);
239
+ }
240
+
179
241
  // Create isolated PermissionManager for the subagent
180
242
  const { PermissionManager } = await import("./permissionManager.js");
181
243
  const parentPermissionManager =
@@ -183,6 +245,7 @@ export class SubagentManager {
183
245
  const subagentPermissionManager = new PermissionManager(subagentContainer, {
184
246
  workdir: this.workdir,
185
247
  configuredPermissionMode:
248
+ parameters.permissionModeOverride ??
186
249
  parentPermissionManager?.getConfiguredPermissionMode(),
187
250
  allowedRules: parentPermissionManager?.getAllowedRules(),
188
251
  deniedRules: parentPermissionManager?.getDeniedRules(),
@@ -200,6 +263,9 @@ export class SubagentManager {
200
263
  });
201
264
  subagentContainer.register("PermissionManager", subagentPermissionManager);
202
265
 
266
+ // Track this subagent's PermissionManager for rule sync
267
+ this.subagentPermissionManagers.set(subagentId, subagentPermissionManager);
268
+
203
269
  // Add temporary permission rules if provided
204
270
  if (parameters.allowedTools) {
205
271
  logger.debug(
@@ -241,6 +307,22 @@ export class SubagentManager {
241
307
  });
242
308
  subagentContainer.register("AIManager", aiManager);
243
309
 
310
+ // Create isolated NotificationQueue for the subagent/forked agent
311
+ const subagentNotificationQueue = new NotificationQueue();
312
+ subagentContainer.register("NotificationQueue", subagentNotificationQueue);
313
+
314
+ // Create isolated BackgroundTaskManager for the subagent/forked agent
315
+ const subagentBackgroundTaskManager = new BackgroundTaskManager(
316
+ subagentContainer,
317
+ {
318
+ workdir: this.workdir,
319
+ },
320
+ );
321
+ subagentContainer.register(
322
+ "BackgroundTaskManager",
323
+ subagentBackgroundTaskManager,
324
+ );
325
+
244
326
  const instance: SubagentInstance = {
245
327
  subagentId,
246
328
  configuration,
@@ -262,42 +344,6 @@ export class SubagentManager {
262
344
  return instance;
263
345
  }
264
346
 
265
- /**
266
- * Create a new subagent instance initialized with a copy of the current message history.
267
- * This is used for background tasks like auto-memory extraction.
268
- */
269
- async forkAgent(
270
- subagentType: string,
271
- messages: Message[],
272
- parameters: {
273
- description: string;
274
- allowedTools?: string[];
275
- model?: string;
276
- },
277
- onUpdate?: () => void,
278
- ): Promise<SubagentInstance> {
279
- const configuration = await this.findSubagent(subagentType);
280
- if (!configuration) {
281
- throw new Error(`Subagent type ${subagentType} not found`);
282
- }
283
-
284
- const instance = await this.createInstance(
285
- configuration,
286
- {
287
- ...parameters,
288
- subagent_type: subagentType,
289
- prompt: "", // Forked agents start with history
290
- },
291
- false,
292
- onUpdate,
293
- );
294
-
295
- // Initialize the message manager with provided messages
296
- instance.messageManager.setMessages(messages);
297
-
298
- return instance;
299
- }
300
-
301
347
  /**
302
348
  * Execute agent using subagent instance
303
349
  *
@@ -367,6 +413,17 @@ export class SubagentManager {
367
413
  task.endTime = Date.now();
368
414
  task.runtime = task.endTime - startTime;
369
415
  }
416
+
417
+ // Enqueue completion notification
418
+ const notificationQueue = this.container.has("NotificationQueue")
419
+ ? this.container.get<NotificationQueue>("NotificationQueue")
420
+ : undefined;
421
+ if (notificationQueue) {
422
+ const summary = `Agent task "${instance.description}" completed`;
423
+ notificationQueue.enqueue(
424
+ `<task-notification>\n<task-id>${taskId}</task-id>\n<task-type>agent</task-type>\n<status>completed</status>\n<summary>${summary}</summary>\n</task-notification>`,
425
+ );
426
+ }
370
427
  } catch (error) {
371
428
  const task = backgroundTaskManager?.getTask(taskId);
372
429
  if (task) {
@@ -376,6 +433,19 @@ export class SubagentManager {
376
433
  task.endTime = Date.now();
377
434
  task.runtime = task.endTime - startTime;
378
435
  }
436
+
437
+ // Enqueue error notification
438
+ const notificationQueue = this.container.has("NotificationQueue")
439
+ ? this.container.get<NotificationQueue>("NotificationQueue")
440
+ : undefined;
441
+ if (notificationQueue) {
442
+ const errorMsg =
443
+ error instanceof Error ? error.message : String(error);
444
+ const summary = `Agent task "${instance.description}" failed: ${errorMsg}`;
445
+ notificationQueue.enqueue(
446
+ `<task-notification>\n<task-id>${taskId}</task-id>\n<task-type>agent</task-type>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`,
447
+ );
448
+ }
379
449
  }
380
450
  })();
381
451
 
@@ -519,6 +589,17 @@ export class SubagentManager {
519
589
  task.runtime = task.endTime - task.startTime;
520
590
  }
521
591
  }
592
+
593
+ // Enqueue completion notification
594
+ const notificationQueue = this.container.has("NotificationQueue")
595
+ ? this.container.get<NotificationQueue>("NotificationQueue")
596
+ : undefined;
597
+ if (notificationQueue) {
598
+ const summary = `Agent task "${instance.description}" completed`;
599
+ notificationQueue.enqueue(
600
+ `<task-notification>\n<task-id>${instance.backgroundTaskId}</task-id>\n<task-type>agent</task-type>\n<status>completed</status>\n<summary>${summary}</summary>\n</task-notification>`,
601
+ );
602
+ }
522
603
  }
523
604
 
524
605
  return response || "Agent completed with no text response";
@@ -543,6 +624,19 @@ export class SubagentManager {
543
624
  task.runtime = task.endTime - task.startTime;
544
625
  }
545
626
  }
627
+
628
+ // Enqueue error notification
629
+ const notificationQueue = this.container.has("NotificationQueue")
630
+ ? this.container.get<NotificationQueue>("NotificationQueue")
631
+ : undefined;
632
+ if (notificationQueue) {
633
+ const errorMsg =
634
+ error instanceof Error ? error.message : String(error);
635
+ const summary = `Agent task "${instance.description}" failed: ${errorMsg}`;
636
+ notificationQueue.enqueue(
637
+ `<task-notification>\n<task-id>${instance.backgroundTaskId}</task-id>\n<task-type>agent</task-type>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`,
638
+ );
639
+ }
546
640
  }
547
641
  throw error;
548
642
  } finally {
@@ -595,6 +689,7 @@ export class SubagentManager {
595
689
  instance.status === "aborted")
596
690
  ) {
597
691
  this.instances.delete(subagentId);
692
+ this.subagentPermissionManagers.delete(subagentId);
598
693
  }
599
694
  }
600
695
 
@@ -671,12 +766,11 @@ export class SubagentManager {
671
766
 
672
767
  // Log tool execution to file
673
768
  if (instance.logStream) {
674
- const compactParams = (params.parameters || "{}").substring(
675
- 0,
676
- 100,
677
- );
769
+ const displayParams =
770
+ params.compactParams ||
771
+ (params.parameters || "{}").substring(0, 100);
678
772
  instance.logStream.write(
679
- `[${new Date().toISOString()}] Running tool: ${params.name} with params: ${compactParams}${compactParams.length >= 100 ? "..." : ""}\n`,
773
+ `[${new Date().toISOString()}] ${params.name}${displayParams ? ` ${displayParams}` : ""}\n`,
680
774
  );
681
775
  }
682
776
  }
@@ -2,7 +2,7 @@ import * as path from "node:path";
2
2
  import * as fs from "node:fs/promises";
3
3
  import { Container } from "../utils/container.js";
4
4
  import { MessageManager } from "../managers/messageManager.js";
5
- import { SubagentManager } from "../managers/subagentManager.js";
5
+ import { ForkedAgentManager } from "../managers/forkedAgentManager.js";
6
6
  import { MemoryService } from "./memory.js";
7
7
  import { ConfigurationService } from "./configurationService.js";
8
8
  import { logger } from "../utils/globalLogger.js";
@@ -24,8 +24,8 @@ export class AutoMemoryService {
24
24
  return this.container.get<MessageManager>("MessageManager")!;
25
25
  }
26
26
 
27
- private get subagentManager(): SubagentManager {
28
- return this.container.get<SubagentManager>("SubagentManager")!;
27
+ private get forkedAgentManager(): ForkedAgentManager {
28
+ return this.container.get<ForkedAgentManager>("ForkedAgentManager")!;
29
29
  }
30
30
 
31
31
  private get memoryService(): MemoryService {
@@ -143,8 +143,13 @@ export class AutoMemoryService {
143
143
  }
144
144
  }
145
145
 
146
- // Fork the general-purpose agent with restricted tool access
147
- const instance = await this.subagentManager.forkAgent(
146
+ const prompt = buildAutoMemoryExtractionPrompt(
147
+ newMessageCount,
148
+ existingMemoriesManifest,
149
+ );
150
+
151
+ // Execute the forked agent in background (fire-and-forget, decoupled from BackgroundTaskManager)
152
+ await this.forkedAgentManager.forkAndExecute(
148
153
  "general-purpose",
149
154
  messages,
150
155
  {
@@ -157,20 +162,9 @@ export class AutoMemoryService {
157
162
  `Edit(${memoryDir}/**/*)`,
158
163
  ],
159
164
  model: "fastModel", // Use fast model for background tasks to reduce latency and cost
165
+ permissionModeOverride: "dontAsk", // Auto-deny out-of-scope writes without prompting user
160
166
  },
161
- );
162
-
163
- const prompt = buildAutoMemoryExtractionPrompt(
164
- newMessageCount,
165
- existingMemoriesManifest,
166
- );
167
-
168
- // Execute in background so it doesn't block the main conversation flow
169
- await this.subagentManager.executeAgent(
170
- instance,
171
167
  `${prompt}\n\nThe memory directory for this project is: ${memoryDir}`,
172
- undefined,
173
- true, // runInBackground
174
168
  );
175
169
 
176
170
  logger.debug("Auto-memory extraction started in background.");
@@ -7,6 +7,7 @@ import type { ConfigurationService } from "./configurationService.js";
7
7
  import type { AIManager } from "../managers/aiManager.js";
8
8
  import type { SubagentManager } from "../managers/subagentManager.js";
9
9
  import type { TaskManager } from "./taskManager.js";
10
+ import type { NotificationQueue } from "../managers/notificationQueue.js";
10
11
 
11
12
  export interface InteractionContext {
12
13
  messageManager: MessageManager;
@@ -58,6 +59,23 @@ export class InteractionService {
58
59
  // Don't add to history, let normal message processing logic below handle it
59
60
  }
60
61
 
62
+ // Inject pending notifications from background tasks
63
+ const notificationQueue = context.aiManager["container"].has(
64
+ "NotificationQueue",
65
+ )
66
+ ? context.aiManager["container"].get<NotificationQueue>(
67
+ "NotificationQueue",
68
+ )
69
+ : undefined;
70
+ if (notificationQueue && notificationQueue.hasPending()) {
71
+ const notifications = notificationQueue.dequeueAll();
72
+ for (const notification of notifications) {
73
+ messageManager.addUserMessage({
74
+ content: notification,
75
+ });
76
+ }
77
+ }
78
+
61
79
  // Handle normal AI message
62
80
  // Add user message first, will automatically sync to UI
63
81
  messageManager.addUserMessage({
@@ -95,4 +95,5 @@ export interface AgentCallbacks
95
95
  onBackgroundCurrentTask?: () => void;
96
96
  onModelChange?: (model: string) => void;
97
97
  onConfiguredModelsChange?: (models: string[]) => void;
98
+ onLoadingChange?: (loading: boolean) => void;
98
99
  }
@@ -27,7 +27,8 @@ export type MessageBlock =
27
27
  | BangBlock
28
28
  | CompressBlock
29
29
  | ReasoningBlock
30
- | FileHistoryBlock;
30
+ | FileHistoryBlock
31
+ | TaskNotificationBlock;
31
32
 
32
33
  export interface TextBlock {
33
34
  type: "text";
@@ -99,3 +100,12 @@ export interface FileHistoryBlock {
99
100
  type: "file_history";
100
101
  snapshots: import("./reversion.js").FileSnapshot[];
101
102
  }
103
+
104
+ export interface TaskNotificationBlock {
105
+ type: "task_notification";
106
+ taskId: string;
107
+ taskType: "shell" | "agent";
108
+ status: "completed" | "failed" | "killed";
109
+ summary: string;
110
+ outputFile?: string;
111
+ }