wave-agent-sdk 0.13.2 → 0.13.4
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 +0 -10
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +60 -20
- package/dist/services/autoMemoryService.d.ts +1 -1
- package/dist/services/autoMemoryService.d.ts.map +1 -1
- package/dist/services/autoMemoryService.js +7 -9
- package/dist/services/interactionService.d.ts.map +1 -1
- package/dist/services/interactionService.js +12 -0
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +0 -9
- 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 +86 -42
- package/src/services/autoMemoryService.ts +11 -18
- package/src/services/interactionService.ts +18 -0
- package/src/tools/bashTool.ts +0 -9
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathEncoder.d.ts","sourceRoot":"","sources":["../../src/utils/pathEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,
|
|
1
|
+
{"version":3,"file":"pathEncoder.d.ts","sourceRoot":"","sources":["../../src/utils/pathEncoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwB;gBAExC,OAAO,GAAE,mBAAwB;IAW7C;;OAEG;IACG,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD;;;OAGG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAsCxC;;;OAGG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzD;;;OAGG;IACH,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAgC9C;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBhD;;OAEG;IACG,mBAAmB,CACvB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,gBAAgB,CAAC;IAmC5B;;OAEG;IACG,sBAAsB,CAC1B,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,gBAAgB,CAAC;IAgB5B;;OAEG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IA+BjD;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM;IAqBtE;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAsDhC;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,WAAW;CAMpB;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,aAAoB,CAAC"}
|
|
@@ -16,7 +16,6 @@ export class PathEncoder {
|
|
|
16
16
|
pathSeparatorReplacement: options.pathSeparatorReplacement ?? "-",
|
|
17
17
|
spaceReplacement: options.spaceReplacement ?? "_",
|
|
18
18
|
invalidCharReplacement: options.invalidCharReplacement ?? "_",
|
|
19
|
-
preserveCase: options.preserveCase ?? false,
|
|
20
19
|
hashLength: options.hashLength ?? 8,
|
|
21
20
|
};
|
|
22
21
|
this.constraints = this.getFilesystemConstraints();
|
|
@@ -50,10 +49,7 @@ export class PathEncoder {
|
|
|
50
49
|
.join("");
|
|
51
50
|
const invalidChars = new RegExp(`[${escapedChars}]`, "g");
|
|
52
51
|
encoded = encoded.replace(invalidChars, this.options.invalidCharReplacement);
|
|
53
|
-
//
|
|
54
|
-
if (!this.options.preserveCase) {
|
|
55
|
-
encoded = encoded.toLowerCase();
|
|
56
|
-
}
|
|
52
|
+
// Case is preserved
|
|
57
53
|
// Handle length limit with hash
|
|
58
54
|
if (encoded.length > this.options.maxLength) {
|
|
59
55
|
const hash = this.generateHash(pathToEncode, this.options.hashLength);
|
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -3,11 +3,13 @@ import { MessageManager } from "./managers/messageManager.js";
|
|
|
3
3
|
import { AIManager } from "./managers/aiManager.js";
|
|
4
4
|
import { ToolManager } from "./managers/toolManager.js";
|
|
5
5
|
import { SubagentManager } from "./managers/subagentManager.js";
|
|
6
|
+
import { ForkedAgentManager } from "./managers/forkedAgentManager.js";
|
|
6
7
|
import { McpManager } from "./managers/mcpManager.js";
|
|
7
8
|
import { LspManager } from "./managers/lspManager.js";
|
|
8
9
|
import { BangManager } from "./managers/bangManager.js";
|
|
9
10
|
import { CronManager } from "./managers/cronManager.js";
|
|
10
11
|
import { BackgroundTaskManager } from "./managers/backgroundTaskManager.js";
|
|
12
|
+
import { NotificationQueue } from "./managers/notificationQueue.js";
|
|
11
13
|
import { SlashCommandManager } from "./managers/slashCommandManager.js";
|
|
12
14
|
import { PluginManager } from "./managers/pluginManager.js";
|
|
13
15
|
import { HookManager } from "./managers/hookManager.js";
|
|
@@ -37,6 +39,7 @@ import { SkillManager } from "./managers/skillManager.js";
|
|
|
37
39
|
import { TaskManager } from "./services/taskManager.js";
|
|
38
40
|
import { btw } from "./services/aiService.js";
|
|
39
41
|
import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
|
|
42
|
+
import { parseTaskNotificationXml } from "./utils/notificationXml.js";
|
|
40
43
|
import { InitializationService } from "./services/initializationService.js";
|
|
41
44
|
import { InteractionService } from "./services/interactionService.js";
|
|
42
45
|
import { ConfigurationService } from "./services/configurationService.js";
|
|
@@ -56,12 +59,14 @@ export class Agent {
|
|
|
56
59
|
private permissionManager: PermissionManager; // Add permission manager instance
|
|
57
60
|
private planManager: PlanManager; // Add plan manager instance
|
|
58
61
|
private subagentManager: SubagentManager; // Add subagent manager instance
|
|
62
|
+
private forkedAgentManager: ForkedAgentManager; // Add forked agent manager instance
|
|
59
63
|
private slashCommandManager: SlashCommandManager; // Add slash command manager instance
|
|
60
64
|
private pluginManager: PluginManager; // Add plugin manager instance
|
|
61
65
|
private skillManager: SkillManager; // Add skill manager instance
|
|
62
66
|
private cronManager: CronManager; // Add cron manager instance
|
|
63
67
|
private hookManager: HookManager; // Add hooks manager instance
|
|
64
68
|
private reversionManager: ReversionManager;
|
|
69
|
+
private notificationQueue: NotificationQueue; // Add notification queue instance
|
|
65
70
|
private memoryRuleManager: MemoryRuleManager; // Add memory rule manager instance
|
|
66
71
|
private liveConfigManager: LiveConfigManager; // Add live configuration manager
|
|
67
72
|
private taskManager: TaskManager;
|
|
@@ -185,11 +190,23 @@ export class Agent {
|
|
|
185
190
|
this.toolManager = this.container.get("ToolManager")!;
|
|
186
191
|
this.liveConfigManager = this.container.get("LiveConfigManager")!;
|
|
187
192
|
this.subagentManager = this.container.get("SubagentManager")!;
|
|
193
|
+
this.forkedAgentManager = this.container.get("ForkedAgentManager")!;
|
|
188
194
|
this.aiManager = this.container.get("AIManager")!;
|
|
189
195
|
this.slashCommandManager = this.container.get("SlashCommandManager")!;
|
|
190
196
|
this.pluginManager = this.container.get("PluginManager")!;
|
|
191
197
|
this.bangManager = this.container.get("BangManager")!;
|
|
192
198
|
this.cronManager = this.container.get("CronManager")!;
|
|
199
|
+
this.notificationQueue = this.container.get("NotificationQueue")!;
|
|
200
|
+
|
|
201
|
+
// Wire up notification queue to trigger AI when notifications arrive while idle
|
|
202
|
+
this.notificationQueue.onNotificationsEnqueued = () => {
|
|
203
|
+
// If the AI is NOT loading (idle), trigger a new AI cycle to process notifications
|
|
204
|
+
if (!this.aiManager.isLoading) {
|
|
205
|
+
this.processPendingNotifications().catch((error) => {
|
|
206
|
+
this.logger?.error("Failed to process pending notifications:", error);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
193
210
|
|
|
194
211
|
// Set initial permission mode if provided
|
|
195
212
|
if (options.permissionMode) {
|
|
@@ -425,6 +442,31 @@ export class Agent {
|
|
|
425
442
|
this.aiManager.abortAIMessage();
|
|
426
443
|
}
|
|
427
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Process pending background task notifications by injecting them as user messages
|
|
447
|
+
* and triggering a new AI response cycle.
|
|
448
|
+
*/
|
|
449
|
+
private async processPendingNotifications(): Promise<void> {
|
|
450
|
+
const notifications = this.notificationQueue.dequeueAll();
|
|
451
|
+
if (notifications.length === 0) return;
|
|
452
|
+
|
|
453
|
+
for (const notification of notifications) {
|
|
454
|
+
const block = parseTaskNotificationXml(notification);
|
|
455
|
+
if (block) {
|
|
456
|
+
this.messageManager.addNotificationMessage({
|
|
457
|
+
taskId: block.taskId,
|
|
458
|
+
taskType: block.taskType,
|
|
459
|
+
status: block.status,
|
|
460
|
+
summary: block.summary,
|
|
461
|
+
outputFile: block.outputFile,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Trigger AI to process the notifications
|
|
467
|
+
await this.aiManager.sendAIMessage({ recursionDepth: 0 });
|
|
468
|
+
}
|
|
469
|
+
|
|
428
470
|
/** Execute bash command (bang command) */
|
|
429
471
|
public async bang(command: string): Promise<void> {
|
|
430
472
|
await this.bangManager?.executeCommand(command);
|
|
@@ -491,6 +533,8 @@ export class Agent {
|
|
|
491
533
|
}
|
|
492
534
|
// Cleanup subagent manager
|
|
493
535
|
this.subagentManager.cleanup();
|
|
536
|
+
// Cleanup forked agent manager
|
|
537
|
+
this.forkedAgentManager.cleanup();
|
|
494
538
|
// Cleanup skill manager
|
|
495
539
|
await this.skillManager.destroy();
|
|
496
540
|
// Cleanup live configuration reload
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./utils/fileSearch.js";
|
|
|
16
16
|
export * from "./utils/globalLogger.js";
|
|
17
17
|
export * from "./utils/mcpUtils.js";
|
|
18
18
|
export * from "./utils/messageOperations.js";
|
|
19
|
+
export * from "./utils/notificationXml.js";
|
|
19
20
|
export * from "./utils/path.js";
|
|
20
21
|
export * from "./utils/promptHistory.js";
|
|
21
22
|
export * from "./utils/stringUtils.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CallAgentOptions } from "../services/aiService.js";
|
|
2
2
|
import * as aiService from "../services/aiService.js";
|
|
3
3
|
import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
|
|
4
|
+
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
|
|
4
5
|
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
|
|
5
6
|
import * as fs from "node:fs/promises";
|
|
6
7
|
import type {
|
|
@@ -14,6 +15,7 @@ import type { ToolManager } from "./toolManager.js";
|
|
|
14
15
|
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
15
16
|
import type { MessageManager } from "./messageManager.js";
|
|
16
17
|
import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
|
|
18
|
+
import type { NotificationQueue } from "./notificationQueue.js";
|
|
17
19
|
import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
18
20
|
import type { HookManager } from "./hookManager.js";
|
|
19
21
|
import type { ExtendedHookExecutionContext } from "../types/hooks.js";
|
|
@@ -178,6 +180,11 @@ export class AIManager {
|
|
|
178
180
|
|
|
179
181
|
public setIsLoading(isLoading: boolean): void {
|
|
180
182
|
this.isLoading = isLoading;
|
|
183
|
+
const options =
|
|
184
|
+
this.container.get<import("../types/agent.js").AgentOptions>(
|
|
185
|
+
"AgentOptions",
|
|
186
|
+
);
|
|
187
|
+
options?.callbacks?.onLoadingChange?.(isLoading);
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
public abortAIMessage(): void {
|
|
@@ -340,6 +347,14 @@ export class AIManager {
|
|
|
340
347
|
return;
|
|
341
348
|
}
|
|
342
349
|
|
|
350
|
+
// Set loading state early for the initial call, before any async work
|
|
351
|
+
if (recursionDepth === 0) {
|
|
352
|
+
this.setIsLoading(true);
|
|
353
|
+
if (allowedRules && allowedRules.length > 0) {
|
|
354
|
+
this.permissionManager?.addTemporaryRules(allowedRules);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
343
358
|
// Scan for file mentions in the last user message
|
|
344
359
|
if (recursionDepth === 0) {
|
|
345
360
|
const messages = this.messageManager.getMessages();
|
|
@@ -380,14 +395,6 @@ export class AIManager {
|
|
|
380
395
|
toolAbortController = this.toolAbortController!;
|
|
381
396
|
}
|
|
382
397
|
|
|
383
|
-
// Only set loading state for the initial call
|
|
384
|
-
if (recursionDepth === 0) {
|
|
385
|
-
this.setIsLoading(true);
|
|
386
|
-
if (allowedRules && allowedRules.length > 0) {
|
|
387
|
-
this.permissionManager?.addTemporaryRules(allowedRules);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
398
|
// Get recent message history
|
|
392
399
|
const recentMessages = convertMessagesForAPI(
|
|
393
400
|
this.messageManager.getMessages(),
|
|
@@ -899,44 +906,72 @@ export class AIManager {
|
|
|
899
906
|
// Set loading to false first
|
|
900
907
|
this.setIsLoading(false);
|
|
901
908
|
|
|
902
|
-
//
|
|
903
|
-
this.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (snapshots.length > 0) {
|
|
919
|
-
this.messageManager.addFileHistoryBlock(snapshots);
|
|
909
|
+
// Inject pending notifications from background tasks
|
|
910
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
911
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
912
|
+
: undefined;
|
|
913
|
+
if (notificationQueue && notificationQueue.hasPending()) {
|
|
914
|
+
const notifications = notificationQueue.dequeueAll();
|
|
915
|
+
for (const notification of notifications) {
|
|
916
|
+
const block = parseTaskNotificationXml(notification);
|
|
917
|
+
if (block) {
|
|
918
|
+
this.messageManager.addNotificationMessage({
|
|
919
|
+
taskId: block.taskId,
|
|
920
|
+
taskType: block.taskType,
|
|
921
|
+
status: block.status,
|
|
922
|
+
summary: block.summary,
|
|
923
|
+
outputFile: block.outputFile,
|
|
924
|
+
});
|
|
920
925
|
}
|
|
921
926
|
}
|
|
927
|
+
// Recursively process the notifications
|
|
928
|
+
await this.sendAIMessage({
|
|
929
|
+
recursionDepth: 0,
|
|
930
|
+
model,
|
|
931
|
+
allowedRules,
|
|
932
|
+
maxTokens,
|
|
933
|
+
});
|
|
934
|
+
} else {
|
|
935
|
+
// Clear temporary rules
|
|
936
|
+
this.permissionManager?.clearTemporaryRules();
|
|
937
|
+
|
|
938
|
+
// Clear abort controllers
|
|
939
|
+
this.abortController = null;
|
|
940
|
+
this.toolAbortController = null;
|
|
941
|
+
|
|
942
|
+
// Execute Stop/SubagentStop hooks only if the operation was not aborted
|
|
943
|
+
const isCurrentlyAborted =
|
|
944
|
+
abortController.signal.aborted ||
|
|
945
|
+
toolAbortController.signal.aborted;
|
|
946
|
+
|
|
947
|
+
if (!isCurrentlyAborted) {
|
|
948
|
+
// Record committed snapshots to message history for the final turn
|
|
949
|
+
if (this.reversionManager) {
|
|
950
|
+
const snapshots =
|
|
951
|
+
this.reversionManager.getAndClearCommittedSnapshots();
|
|
952
|
+
if (snapshots.length > 0) {
|
|
953
|
+
this.messageManager.addFileHistoryBlock(snapshots);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
922
956
|
|
|
923
|
-
|
|
957
|
+
const shouldContinue = await this.executeStopHooks();
|
|
924
958
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
959
|
+
// If Stop/SubagentStop hooks indicate we should continue (due to blocking errors),
|
|
960
|
+
// restart the AI conversation cycle
|
|
961
|
+
if (shouldContinue) {
|
|
962
|
+
logger?.info(
|
|
963
|
+
`${this.subagentType ? "SubagentStop" : "Stop"} hooks indicate issues need fixing, continuing conversation...`,
|
|
964
|
+
);
|
|
931
965
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
966
|
+
// Restart the conversation to let AI fix the issues
|
|
967
|
+
// Use recursionDepth = 0 to set loading false again for continuation
|
|
968
|
+
await this.sendAIMessage({
|
|
969
|
+
recursionDepth: 0,
|
|
970
|
+
model,
|
|
971
|
+
allowedRules,
|
|
972
|
+
maxTokens,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
940
975
|
}
|
|
941
976
|
}
|
|
942
977
|
}
|
|
@@ -2,10 +2,15 @@ import { spawn, type ChildProcess } from "child_process";
|
|
|
2
2
|
import * as os from "os";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BackgroundTask,
|
|
7
|
+
BackgroundShell,
|
|
8
|
+
BackgroundSubagent,
|
|
9
|
+
} from "../types/processes.js";
|
|
6
10
|
import { stripAnsiColors } from "../utils/stringUtils.js";
|
|
7
11
|
import { logger } from "../utils/globalLogger.js";
|
|
8
12
|
import { Container } from "../utils/container.js";
|
|
13
|
+
import { NotificationQueue } from "./notificationQueue.js";
|
|
9
14
|
|
|
10
15
|
export interface BackgroundTaskManagerCallbacks {
|
|
11
16
|
onBackgroundTasksChange?: (tasks: BackgroundTask[]) => void;
|
|
@@ -30,6 +35,10 @@ export class BackgroundTaskManager {
|
|
|
30
35
|
this.workdir = options.workdir;
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
private get notificationQueue(): NotificationQueue {
|
|
39
|
+
return this.container.get<NotificationQueue>("NotificationQueue")!;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
private notifyTasksChange(): void {
|
|
34
43
|
this.callbacks.onBackgroundTasksChange?.(Array.from(this.tasks.values()));
|
|
35
44
|
}
|
|
@@ -153,6 +162,18 @@ export class BackgroundTaskManager {
|
|
|
153
162
|
shell.endTime = Date.now();
|
|
154
163
|
shell.runtime = shell.endTime - startTime;
|
|
155
164
|
this.notifyTasksChange();
|
|
165
|
+
|
|
166
|
+
// Enqueue completion notification
|
|
167
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
168
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
169
|
+
: undefined;
|
|
170
|
+
if (notificationQueue) {
|
|
171
|
+
const statusStr = shell.status;
|
|
172
|
+
const summary = `Command "${command}" ${statusStr} with exit code ${code ?? 0}`;
|
|
173
|
+
notificationQueue.enqueue(
|
|
174
|
+
`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>${statusStr}</status>\n<summary>${summary}</summary>\n</task-notification>`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
156
177
|
};
|
|
157
178
|
|
|
158
179
|
const onError = (error: Error) => {
|
|
@@ -170,6 +191,17 @@ export class BackgroundTaskManager {
|
|
|
170
191
|
shell.endTime = Date.now();
|
|
171
192
|
shell.runtime = shell.endTime - startTime;
|
|
172
193
|
this.notifyTasksChange();
|
|
194
|
+
|
|
195
|
+
// Enqueue error notification
|
|
196
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
197
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
198
|
+
: undefined;
|
|
199
|
+
if (notificationQueue) {
|
|
200
|
+
const summary = `Command "${command}" failed with error: ${stripAnsiColors(error.message)}`;
|
|
201
|
+
notificationQueue.enqueue(
|
|
202
|
+
`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
173
205
|
};
|
|
174
206
|
|
|
175
207
|
child.stdout?.on("data", onStdout);
|
|
@@ -278,6 +310,18 @@ export class BackgroundTaskManager {
|
|
|
278
310
|
shell.endTime = Date.now();
|
|
279
311
|
shell.runtime = shell.endTime - startTime;
|
|
280
312
|
this.notifyTasksChange();
|
|
313
|
+
|
|
314
|
+
// Enqueue completion notification
|
|
315
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
316
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
317
|
+
: undefined;
|
|
318
|
+
if (notificationQueue) {
|
|
319
|
+
const statusStr = shell.status;
|
|
320
|
+
const summary = `Command "${command}" ${statusStr} with exit code ${code ?? 0}`;
|
|
321
|
+
notificationQueue.enqueue(
|
|
322
|
+
`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>${statusStr}</status>\n<summary>${summary}</summary>\n</task-notification>`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
281
325
|
});
|
|
282
326
|
|
|
283
327
|
child.on("error", (error) => {
|
|
@@ -292,6 +336,17 @@ export class BackgroundTaskManager {
|
|
|
292
336
|
shell.endTime = Date.now();
|
|
293
337
|
shell.runtime = shell.endTime - startTime;
|
|
294
338
|
this.notifyTasksChange();
|
|
339
|
+
|
|
340
|
+
// Enqueue error notification
|
|
341
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
342
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
343
|
+
: undefined;
|
|
344
|
+
if (notificationQueue) {
|
|
345
|
+
const summary = `Command "${command}" failed with error: ${stripAnsiColors(error.message)}`;
|
|
346
|
+
notificationQueue.enqueue(
|
|
347
|
+
`<task-notification>\n<task-id>${id}</task-id>\n<task-type>shell</task-type>\n<output-file>${logPath}</output-file>\n<status>failed</status>\n<summary>${summary}</summary>\n</task-notification>`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
295
350
|
});
|
|
296
351
|
|
|
297
352
|
return id;
|
|
@@ -371,6 +426,22 @@ export class BackgroundTaskManager {
|
|
|
371
426
|
task.endTime = Date.now();
|
|
372
427
|
task.runtime = task.endTime - task.startTime;
|
|
373
428
|
this.notifyTasksChange();
|
|
429
|
+
|
|
430
|
+
// Enqueue killed notification
|
|
431
|
+
const notificationQueue = this.container.has("NotificationQueue")
|
|
432
|
+
? this.container.get<NotificationQueue>("NotificationQueue")
|
|
433
|
+
: undefined;
|
|
434
|
+
if (notificationQueue) {
|
|
435
|
+
const description = (task as BackgroundSubagent).description || "";
|
|
436
|
+
const command = (task as BackgroundShell).command || "";
|
|
437
|
+
const summary =
|
|
438
|
+
task.type === "subagent"
|
|
439
|
+
? `Agent task "${description}" was stopped`
|
|
440
|
+
: `Command "${command}" was stopped`;
|
|
441
|
+
notificationQueue.enqueue(
|
|
442
|
+
`<task-notification>\n<task-id>${id}</task-id>\n<task-type>${task.type}</task-type>\n<status>killed</status>\n<summary>${summary}</summary>\n</task-notification>`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
374
445
|
return true;
|
|
375
446
|
}
|
|
376
447
|
|
|
@@ -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
|