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.
- package/dist/agent.d.ts +7 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +37 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +65 -33
- package/dist/managers/backgroundTaskManager.d.ts +1 -0
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +49 -0
- package/dist/managers/forkedAgentManager.d.ts +49 -0
- package/dist/managers/forkedAgentManager.d.ts.map +1 -0
- package/dist/managers/forkedAgentManager.js +111 -0
- package/dist/managers/messageManager.d.ts +8 -1
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +14 -1
- package/dist/managers/notificationQueue.d.ts +8 -0
- package/dist/managers/notificationQueue.d.ts.map +1 -0
- package/dist/managers/notificationQueue.js +17 -0
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +2 -0
- package/dist/managers/subagentManager.d.ts +7 -9
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +101 -21
- package/dist/services/autoMemoryService.d.ts +1 -1
- package/dist/services/autoMemoryService.d.ts.map +1 -1
- package/dist/services/autoMemoryService.js +7 -8
- package/dist/services/interactionService.d.ts.map +1 -1
- package/dist/services/interactionService.js +12 -0
- package/dist/types/agent.d.ts +1 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +9 -1
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +6 -0
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +8 -0
- package/dist/utils/messageOperations.d.ts +9 -0
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +17 -0
- package/dist/utils/notificationXml.d.ts +4 -0
- package/dist/utils/notificationXml.d.ts.map +1 -0
- package/dist/utils/notificationXml.js +40 -0
- package/dist/utils/pathEncoder.d.ts +0 -1
- package/dist/utils/pathEncoder.d.ts.map +1 -1
- package/dist/utils/pathEncoder.js +1 -5
- package/package.json +1 -1
- package/src/agent.ts +44 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +76 -41
- package/src/managers/backgroundTaskManager.ts +72 -1
- package/src/managers/forkedAgentManager.ts +193 -0
- package/src/managers/messageManager.ts +25 -0
- package/src/managers/notificationQueue.ts +19 -0
- package/src/managers/permissionManager.ts +2 -0
- package/src/managers/subagentManager.ts +135 -41
- package/src/services/autoMemoryService.ts +11 -17
- package/src/services/interactionService.ts +18 -0
- package/src/types/agent.ts +1 -0
- package/src/types/messaging.ts +11 -1
- package/src/utils/containerSetup.ts +8 -0
- package/src/utils/convertMessagesForAPI.ts +9 -0
- package/src/utils/messageOperations.ts +42 -1
- package/src/utils/notificationXml.ts +52 -0
- 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
|
|
675
|
-
|
|
676
|
-
100
|
|
677
|
-
);
|
|
769
|
+
const displayParams =
|
|
770
|
+
params.compactParams ||
|
|
771
|
+
(params.parameters || "{}").substring(0, 100);
|
|
678
772
|
instance.logStream.write(
|
|
679
|
-
`[${new Date().toISOString()}]
|
|
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 {
|
|
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
|
|
28
|
-
return this.container.get<
|
|
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
|
-
|
|
147
|
-
|
|
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({
|
package/src/types/agent.ts
CHANGED
package/src/types/messaging.ts
CHANGED
|
@@ -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
|
+
}
|