wave-agent-sdk 0.14.1 → 0.14.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/builtin/skills/settings/HOOKS.md +69 -0
- package/builtin/skills/settings/PLUGINS.md +171 -0
- package/builtin/skills/settings/SKILL.md +8 -3
- package/builtin/skills/settings/SUBAGENTS.md +21 -2
- package/dist/agent.d.ts +2 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -3
- package/dist/managers/aiManager.d.ts +6 -6
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +122 -59
- package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
- package/dist/managers/backgroundTaskManager.js +28 -18
- package/dist/managers/hookManager.d.ts +16 -1
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +97 -8
- package/dist/managers/messageManager.d.ts +19 -4
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +63 -18
- package/dist/managers/pluginManager.d.ts +1 -0
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +7 -0
- package/dist/managers/subagentManager.d.ts +5 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +35 -0
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -1
- package/dist/services/MarketplaceService.d.ts +0 -11
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +21 -89
- package/dist/services/aiService.d.ts +3 -3
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +7 -7
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +15 -0
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +24 -1
- package/dist/services/interactionService.js +1 -1
- package/dist/services/pluginLoader.d.ts +5 -6
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +43 -53
- package/dist/services/session.d.ts +1 -1
- package/dist/services/session.js +7 -7
- package/dist/services/taskManager.d.ts +1 -1
- package/dist/services/taskManager.js +1 -1
- package/dist/types/core.d.ts +1 -1
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +9 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +2 -0
- package/dist/types/marketplace.d.ts +1 -26
- package/dist/types/marketplace.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +3 -3
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +3 -13
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +18 -7
- package/dist/utils/groupMessagesByApiRound.d.ts +1 -1
- package/dist/utils/groupMessagesByApiRound.js +6 -6
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +3 -3
- package/dist/utils/subagentParser.d.ts +8 -1
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +18 -3
- package/package.json +1 -1
- package/src/agent.ts +16 -3
- package/src/managers/aiManager.ts +142 -63
- package/src/managers/backgroundTaskManager.ts +32 -22
- package/src/managers/hookManager.ts +125 -10
- package/src/managers/messageManager.ts +76 -22
- package/src/managers/pluginManager.ts +10 -0
- package/src/managers/subagentManager.ts +47 -0
- package/src/prompts/index.ts +1 -1
- package/src/services/MarketplaceService.ts +26 -127
- package/src/services/aiService.ts +11 -11
- package/src/services/hook.ts +17 -0
- package/src/services/initializationService.ts +33 -1
- package/src/services/interactionService.ts +1 -1
- package/src/services/pluginLoader.ts +51 -67
- package/src/services/session.ts +7 -7
- package/src/services/taskManager.ts +1 -1
- package/src/types/core.ts +1 -1
- package/src/types/hooks.ts +16 -2
- package/src/types/marketplace.ts +1 -24
- package/src/types/messaging.ts +3 -3
- package/src/types/plugins.ts +3 -13
- package/src/utils/convertMessagesForAPI.ts +24 -9
- package/src/utils/groupMessagesByApiRound.ts +6 -6
- package/src/utils/messageOperations.ts +3 -5
- package/src/utils/subagentParser.ts +31 -4
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ExtendedHookExecutionContext,
|
|
13
13
|
type HookExecutionResult,
|
|
14
14
|
type HookValidationResult,
|
|
15
|
+
type SessionEndSource,
|
|
15
16
|
HookConfigurationError,
|
|
16
17
|
isValidHookEvent,
|
|
17
18
|
isValidHookEventConfig,
|
|
@@ -80,9 +81,8 @@ export class HookManager {
|
|
|
80
81
|
*/
|
|
81
82
|
loadConfigurationFromWaveConfig(waveConfig: WaveConfiguration | null): void {
|
|
82
83
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Validate the loaded configuration if it exists
|
|
84
|
+
// Merge Wave configuration hooks with existing plugin hooks
|
|
85
|
+
// (plugin hooks were registered earlier via registerPluginHooks)
|
|
86
86
|
if (waveConfig?.hooks) {
|
|
87
87
|
const validation = this.validatePartialConfiguration(waveConfig.hooks);
|
|
88
88
|
if (!validation.valid) {
|
|
@@ -91,17 +91,18 @@ export class HookManager {
|
|
|
91
91
|
validation.errors,
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
|
+
if (!this.configuration) {
|
|
95
|
+
this.configuration = {};
|
|
96
|
+
}
|
|
97
|
+
this.mergeHooksConfiguration(this.configuration, waveConfig.hooks);
|
|
94
98
|
}
|
|
95
99
|
} catch (error) {
|
|
96
|
-
// If loading fails, start with undefined configuration (no hooks)
|
|
97
|
-
this.configuration = undefined;
|
|
98
|
-
|
|
99
100
|
// Re-throw configuration errors, but handle other errors gracefully
|
|
100
101
|
if (error instanceof HookConfigurationError) {
|
|
101
102
|
throw error;
|
|
102
103
|
} else {
|
|
103
104
|
logger?.warn(
|
|
104
|
-
`[HookManager] Failed to load configuration, continuing with
|
|
105
|
+
`[HookManager] Failed to load configuration, continuing with existing hooks: ${(error as Error).message}`,
|
|
105
106
|
);
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -298,6 +299,8 @@ export class HookManager {
|
|
|
298
299
|
source: MessageSource.HOOK,
|
|
299
300
|
});
|
|
300
301
|
}
|
|
302
|
+
// For SessionStart, stdout is processed separately in executeSessionStartHooks
|
|
303
|
+
// For SessionEnd, stdout is ignored (fire-and-forget cleanup)
|
|
301
304
|
// For other hook types (PreToolUse, PostToolUse, Stop, PermissionRequest), ignore stdout
|
|
302
305
|
}
|
|
303
306
|
|
|
@@ -374,6 +377,16 @@ export class HookManager {
|
|
|
374
377
|
messageManager.addErrorBlock(errorMessage);
|
|
375
378
|
return { shouldBlock: false };
|
|
376
379
|
|
|
380
|
+
case "SessionStart":
|
|
381
|
+
// Non-blocking for startup, show error in error block
|
|
382
|
+
messageManager.addErrorBlock(errorMessage);
|
|
383
|
+
return { shouldBlock: false };
|
|
384
|
+
|
|
385
|
+
case "SessionEnd":
|
|
386
|
+
// Blocking error (exit code 2): show in error block, don't block shutdown
|
|
387
|
+
messageManager.addErrorBlock(errorMessage);
|
|
388
|
+
return { shouldBlock: false };
|
|
389
|
+
|
|
377
390
|
default:
|
|
378
391
|
return { shouldBlock: false };
|
|
379
392
|
}
|
|
@@ -577,7 +590,9 @@ export class HookManager {
|
|
|
577
590
|
(event === "UserPromptSubmit" ||
|
|
578
591
|
event === "Stop" ||
|
|
579
592
|
event === "SubagentStop" ||
|
|
580
|
-
event === "WorktreeCreate"
|
|
593
|
+
event === "WorktreeCreate" ||
|
|
594
|
+
event === "SessionStart" ||
|
|
595
|
+
event === "SessionEnd") &&
|
|
581
596
|
context.toolName !== undefined
|
|
582
597
|
) {
|
|
583
598
|
logger?.warn(
|
|
@@ -654,7 +669,9 @@ export class HookManager {
|
|
|
654
669
|
event === "Stop" ||
|
|
655
670
|
event === "SubagentStop" ||
|
|
656
671
|
event === "WorktreeCreate" ||
|
|
657
|
-
event === "CwdChanged"
|
|
672
|
+
event === "CwdChanged" ||
|
|
673
|
+
event === "SessionStart" ||
|
|
674
|
+
event === "SessionEnd"
|
|
658
675
|
) {
|
|
659
676
|
return true;
|
|
660
677
|
}
|
|
@@ -714,7 +731,9 @@ export class HookManager {
|
|
|
714
731
|
(event === "UserPromptSubmit" ||
|
|
715
732
|
event === "Stop" ||
|
|
716
733
|
event === "SubagentStop" ||
|
|
717
|
-
event === "WorktreeCreate"
|
|
734
|
+
event === "WorktreeCreate" ||
|
|
735
|
+
event === "SessionStart" ||
|
|
736
|
+
event === "SessionEnd") &&
|
|
718
737
|
config.matcher
|
|
719
738
|
) {
|
|
720
739
|
errors.push(`${prefix}: Event ${event} should not have a matcher`);
|
|
@@ -755,6 +774,8 @@ export class HookManager {
|
|
|
755
774
|
PermissionRequest: 0,
|
|
756
775
|
WorktreeCreate: 0,
|
|
757
776
|
CwdChanged: 0,
|
|
777
|
+
SessionStart: 0,
|
|
778
|
+
SessionEnd: 0,
|
|
758
779
|
},
|
|
759
780
|
};
|
|
760
781
|
}
|
|
@@ -768,6 +789,8 @@ export class HookManager {
|
|
|
768
789
|
PermissionRequest: 0,
|
|
769
790
|
WorktreeCreate: 0,
|
|
770
791
|
CwdChanged: 0,
|
|
792
|
+
SessionStart: 0,
|
|
793
|
+
SessionEnd: 0,
|
|
771
794
|
};
|
|
772
795
|
|
|
773
796
|
let totalConfigs = 0;
|
|
@@ -844,4 +867,96 @@ export class HookManager {
|
|
|
844
867
|
|
|
845
868
|
this.mergeHooksConfiguration(this.configuration, stampedHooks);
|
|
846
869
|
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Execute SessionStart hooks during initialization.
|
|
873
|
+
* Collects additionalContext and initialUserMessage from hook stdout.
|
|
874
|
+
*/
|
|
875
|
+
async executeSessionStartHooks(
|
|
876
|
+
source: "startup" | "resume" | "compact",
|
|
877
|
+
sessionId: string,
|
|
878
|
+
transcriptPath: string,
|
|
879
|
+
agentType?: string,
|
|
880
|
+
): Promise<{
|
|
881
|
+
results: HookExecutionResult[];
|
|
882
|
+
additionalContext?: string;
|
|
883
|
+
initialUserMessage?: string;
|
|
884
|
+
}> {
|
|
885
|
+
const context: ExtendedHookExecutionContext = {
|
|
886
|
+
event: "SessionStart",
|
|
887
|
+
projectDir: this.workdir,
|
|
888
|
+
timestamp: new Date(),
|
|
889
|
+
sessionId,
|
|
890
|
+
transcriptPath,
|
|
891
|
+
cwd: this.workdir,
|
|
892
|
+
source,
|
|
893
|
+
agentType,
|
|
894
|
+
env: Object.fromEntries(
|
|
895
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
896
|
+
) as Record<string, string>,
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
const results = await this.executeHooks("SessionStart", context);
|
|
900
|
+
|
|
901
|
+
let additionalContext: string | undefined;
|
|
902
|
+
let initialUserMessage: string | undefined;
|
|
903
|
+
|
|
904
|
+
// Process stdout from successful hooks
|
|
905
|
+
for (const result of results) {
|
|
906
|
+
if (result.success && result.stdout?.trim()) {
|
|
907
|
+
const trimmed = result.stdout.trim();
|
|
908
|
+
// Try to parse as JSON for structured output
|
|
909
|
+
try {
|
|
910
|
+
const parsed = JSON.parse(trimmed);
|
|
911
|
+
if (parsed.hookSpecificOutput?.additionalContext) {
|
|
912
|
+
additionalContext =
|
|
913
|
+
(additionalContext ? additionalContext + "\n" : "") +
|
|
914
|
+
parsed.hookSpecificOutput.additionalContext;
|
|
915
|
+
}
|
|
916
|
+
if (parsed.initialUserMessage) {
|
|
917
|
+
initialUserMessage = parsed.initialUserMessage;
|
|
918
|
+
}
|
|
919
|
+
} catch {
|
|
920
|
+
// Not JSON, treat as additional context
|
|
921
|
+
additionalContext =
|
|
922
|
+
(additionalContext ? additionalContext + "\n" : "") + trimmed;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return { results, additionalContext, initialUserMessage };
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Execute SessionEnd hooks during agent destruction.
|
|
932
|
+
* Non-blocking: always continues shutdown even if hooks fail.
|
|
933
|
+
* No stdout processing needed (SessionEnd hooks are fire-and-forget cleanup).
|
|
934
|
+
*/
|
|
935
|
+
async executeSessionEndHooks(
|
|
936
|
+
source: SessionEndSource,
|
|
937
|
+
sessionId: string,
|
|
938
|
+
transcriptPath: string,
|
|
939
|
+
): Promise<HookExecutionResult[]> {
|
|
940
|
+
const context: ExtendedHookExecutionContext = {
|
|
941
|
+
event: "SessionEnd",
|
|
942
|
+
projectDir: this.workdir,
|
|
943
|
+
timestamp: new Date(),
|
|
944
|
+
sessionId,
|
|
945
|
+
transcriptPath,
|
|
946
|
+
cwd: this.workdir,
|
|
947
|
+
endSource: source,
|
|
948
|
+
env: Object.fromEntries(
|
|
949
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
950
|
+
) as Record<string, string>,
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const results = await this.executeHooks("SessionEnd", context);
|
|
954
|
+
|
|
955
|
+
// Process results but never block shutdown
|
|
956
|
+
if (results.length > 0) {
|
|
957
|
+
this.processHookResults("SessionEnd", results);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return results;
|
|
961
|
+
}
|
|
847
962
|
}
|
|
@@ -47,8 +47,8 @@ export interface MessageManagerCallbacks {
|
|
|
47
47
|
onAssistantReasoningUpdated?: (chunk: string, accumulated: string) => void;
|
|
48
48
|
onToolBlockUpdated?: (params: AgentToolBlockUpdateParams) => void;
|
|
49
49
|
onErrorBlockAdded?: (error: string) => void;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
onCompactBlockAdded?: (content: string) => void;
|
|
51
|
+
onCompactionStateChange?: (isCompacting: boolean) => void;
|
|
52
52
|
// Bang callback
|
|
53
53
|
onAddBangMessage?: (command: string) => void;
|
|
54
54
|
onUpdateBangMessage?: (command: string, output: string) => void;
|
|
@@ -92,6 +92,8 @@ export class MessageManager {
|
|
|
92
92
|
private filesInContext: Set<string> = new Set(); // Track files mentioned in the conversation
|
|
93
93
|
private recentFileReads: Map<string, { content: string; timestamp: number }> =
|
|
94
94
|
new Map(); // Track file read contents
|
|
95
|
+
private invokedSkills: Map<string, { skillName: string; timestamp: number }> =
|
|
96
|
+
new Map(); // Track invoked skill names
|
|
95
97
|
private sessionType: "main" | "subagent";
|
|
96
98
|
private subagentType?: string;
|
|
97
99
|
private _usages: Usage[] = [];
|
|
@@ -270,12 +272,14 @@ export class MessageManager {
|
|
|
270
272
|
for (const message of newMessages) {
|
|
271
273
|
this.addPathsFromMessage(message);
|
|
272
274
|
this.extractFileReadsFromMessage(message);
|
|
275
|
+
this.extractSkillInvocationsFromMessage(message);
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
// Also check if the last message was updated (common for tool blocks)
|
|
276
279
|
if (messages.length > 0 && messages.length === oldLength) {
|
|
277
280
|
this.addPathsFromMessage(messages[messages.length - 1]);
|
|
278
281
|
this.extractFileReadsFromMessage(messages[messages.length - 1]);
|
|
282
|
+
this.extractSkillInvocationsFromMessage(messages[messages.length - 1]);
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
this.callbacks.onMessagesChange?.([...messages]);
|
|
@@ -494,31 +498,31 @@ export class MessageManager {
|
|
|
494
498
|
}
|
|
495
499
|
|
|
496
500
|
/**
|
|
497
|
-
*
|
|
501
|
+
* Compact messages and update session, delete compacted messages, only keep compacted messages and last 3 messages
|
|
498
502
|
*/
|
|
499
|
-
public
|
|
500
|
-
|
|
503
|
+
public compactMessagesAndUpdateSession(
|
|
504
|
+
compactedContent: string,
|
|
501
505
|
usage?: Usage,
|
|
502
506
|
): void {
|
|
503
507
|
// Get last 2 API rounds to preserve (structurally safe boundary)
|
|
504
508
|
const lastThreeMessages = getLastApiRounds(this.messages, 2);
|
|
505
509
|
|
|
506
|
-
// Create
|
|
507
|
-
const
|
|
510
|
+
// Create compacted message
|
|
511
|
+
const compactMessage: Message = {
|
|
508
512
|
id: generateMessageId(),
|
|
509
513
|
role: "assistant",
|
|
510
514
|
blocks: [
|
|
511
515
|
{
|
|
512
|
-
type: "
|
|
513
|
-
content:
|
|
516
|
+
type: "compact",
|
|
517
|
+
content: compactedContent,
|
|
514
518
|
sessionId: this.sessionId,
|
|
515
519
|
},
|
|
516
520
|
],
|
|
517
521
|
...(usage && { usage }),
|
|
518
522
|
};
|
|
519
523
|
|
|
520
|
-
// Build new message array: keep the
|
|
521
|
-
const newMessages: Message[] = [
|
|
524
|
+
// Build new message array: keep the compacted message and last 3 messages
|
|
525
|
+
const newMessages: Message[] = [compactMessage, ...lastThreeMessages];
|
|
522
526
|
|
|
523
527
|
// Update sessionId and parentSessionId
|
|
524
528
|
const oldSessionId = this.sessionId;
|
|
@@ -539,15 +543,15 @@ export class MessageManager {
|
|
|
539
543
|
this.addPathsFromMessage(message);
|
|
540
544
|
}
|
|
541
545
|
|
|
542
|
-
// Scan
|
|
546
|
+
// Scan compactedContent for file mentions
|
|
543
547
|
const fileMentionRegex = /(?:^|\s)@([\w.\-/]+)/g;
|
|
544
548
|
let match;
|
|
545
|
-
while ((match = fileMentionRegex.exec(
|
|
549
|
+
while ((match = fileMentionRegex.exec(compactedContent)) !== null) {
|
|
546
550
|
this.touchFile(match[1]);
|
|
547
551
|
}
|
|
548
552
|
|
|
549
|
-
// Trigger
|
|
550
|
-
this.callbacks.
|
|
553
|
+
// Trigger compaction callback
|
|
554
|
+
this.callbacks.onCompactBlockAdded?.(compactedContent);
|
|
551
555
|
}
|
|
552
556
|
|
|
553
557
|
public addFileHistoryBlock(
|
|
@@ -861,7 +865,7 @@ export class MessageManager {
|
|
|
861
865
|
let targetSessionId = this.sessionId;
|
|
862
866
|
let targetIndexInSession = index;
|
|
863
867
|
|
|
864
|
-
// We need to be careful here because loadFullMessageThread might have removed "
|
|
868
|
+
// We need to be careful here because loadFullMessageThread might have removed "compact" blocks
|
|
865
869
|
// Let's re-calculate based on the actual messages returned.
|
|
866
870
|
// Actually, it's easier to just load sessions one by one again or keep track of counts.
|
|
867
871
|
|
|
@@ -883,19 +887,19 @@ export class MessageManager {
|
|
|
883
887
|
if (!sessionData) continue;
|
|
884
888
|
|
|
885
889
|
const sessionMessages = sessionData.messages;
|
|
886
|
-
// If this is not the first session in the thread, it might have a
|
|
890
|
+
// If this is not the first session in the thread, it might have a compact block at the start
|
|
887
891
|
// that was removed in getFullMessageThread.
|
|
888
|
-
const
|
|
889
|
-
(b) => b.type === "
|
|
892
|
+
const hasCompactBlock = sessionMessages[0]?.blocks.some(
|
|
893
|
+
(b) => b.type === "compact",
|
|
890
894
|
);
|
|
891
895
|
const effectiveMessages =
|
|
892
|
-
|
|
896
|
+
hasCompactBlock && sid !== sessionIds[0]
|
|
893
897
|
? sessionMessages.slice(1)
|
|
894
898
|
: sessionMessages;
|
|
895
899
|
|
|
896
900
|
if (remainingIndex < effectiveMessages.length) {
|
|
897
901
|
targetSessionId = sid;
|
|
898
|
-
targetIndexInSession =
|
|
902
|
+
targetIndexInSession = hasCompactBlock
|
|
899
903
|
? remainingIndex + 1
|
|
900
904
|
: remainingIndex;
|
|
901
905
|
break;
|
|
@@ -938,7 +942,7 @@ export class MessageManager {
|
|
|
938
942
|
|
|
939
943
|
// Update in-memory messages to the truncated session messages
|
|
940
944
|
// We do NOT include ancestor messages here to avoid exceeding context limits.
|
|
941
|
-
// The '
|
|
945
|
+
// The 'compact' block at the start of the session (if any) already summarizes them.
|
|
942
946
|
this.setMessages(newMessagesInSession);
|
|
943
947
|
|
|
944
948
|
// Update saved message count
|
|
@@ -1056,4 +1060,54 @@ export class MessageManager {
|
|
|
1056
1060
|
}
|
|
1057
1061
|
return result;
|
|
1058
1062
|
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Extract skill invocations from tool blocks in a message.
|
|
1066
|
+
*/
|
|
1067
|
+
private extractSkillInvocationsFromMessage(message: Message): void {
|
|
1068
|
+
for (const block of message.blocks) {
|
|
1069
|
+
if (
|
|
1070
|
+
block.type === "tool" &&
|
|
1071
|
+
block.name === "Skill" &&
|
|
1072
|
+
block.stage === "end" &&
|
|
1073
|
+
block.parameters
|
|
1074
|
+
) {
|
|
1075
|
+
try {
|
|
1076
|
+
const params = JSON.parse(block.parameters) as Record<
|
|
1077
|
+
string,
|
|
1078
|
+
unknown
|
|
1079
|
+
>;
|
|
1080
|
+
const skillName = params.skill_name as string | undefined;
|
|
1081
|
+
if (skillName) {
|
|
1082
|
+
this.invokedSkills.set(skillName, {
|
|
1083
|
+
skillName,
|
|
1084
|
+
timestamp: Date.now(),
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
} catch {
|
|
1088
|
+
// Ignore parse errors
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Get recently invoked skill names, sorted by timestamp (newest first).
|
|
1096
|
+
* @param maxSkills - Maximum number of skills to return
|
|
1097
|
+
* @returns Array of skill names sorted by recency
|
|
1098
|
+
*/
|
|
1099
|
+
public getInvokedSkillNames(maxSkills = 10): string[] {
|
|
1100
|
+
const sorted = Array.from(this.invokedSkills.entries())
|
|
1101
|
+
.sort(([, a], [, b]) => b.timestamp - a.timestamp)
|
|
1102
|
+
.slice(0, maxSkills);
|
|
1103
|
+
|
|
1104
|
+
return sorted.map(([, { skillName }]) => skillName);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Clear all invoked skills (e.g., after compaction).
|
|
1109
|
+
*/
|
|
1110
|
+
public clearInvokedSkills(): void {
|
|
1111
|
+
this.invokedSkills.clear();
|
|
1112
|
+
}
|
|
1059
1113
|
}
|
|
@@ -7,6 +7,7 @@ import { HookManager } from "./hookManager.js";
|
|
|
7
7
|
import { LspManager } from "./lspManager.js";
|
|
8
8
|
import { McpManager } from "./mcpManager.js";
|
|
9
9
|
import { SlashCommandManager } from "./slashCommandManager.js";
|
|
10
|
+
import { SubagentManager } from "./subagentManager.js";
|
|
10
11
|
import { MarketplaceService } from "../services/MarketplaceService.js";
|
|
11
12
|
import { ConfigurationService } from "../services/configurationService.js";
|
|
12
13
|
import { Container } from "../utils/container.js";
|
|
@@ -53,6 +54,10 @@ export class PluginManager {
|
|
|
53
54
|
return this.container.get<ConfigurationService>("ConfigurationService");
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
private get subagentManager(): SubagentManager | undefined {
|
|
58
|
+
return this.container.get<SubagentManager>("SubagentManager");
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
/**
|
|
57
62
|
* Update enabled plugins configuration
|
|
58
63
|
*/
|
|
@@ -155,6 +160,7 @@ export class PluginManager {
|
|
|
155
160
|
path: absolutePath,
|
|
156
161
|
commands: PluginLoader.loadCommands(absolutePath),
|
|
157
162
|
skills: await PluginLoader.loadSkills(absolutePath),
|
|
163
|
+
agents: await PluginLoader.loadAgents(absolutePath),
|
|
158
164
|
lspConfig: await PluginLoader.loadLspConfig(absolutePath),
|
|
159
165
|
mcpConfig: await PluginLoader.loadMcpConfig(absolutePath),
|
|
160
166
|
hooksConfig: await PluginLoader.loadHooksConfig(absolutePath),
|
|
@@ -192,6 +198,10 @@ export class PluginManager {
|
|
|
192
198
|
this.hookManager.registerPluginHooks(plugin.path, plugin.hooksConfig);
|
|
193
199
|
}
|
|
194
200
|
|
|
201
|
+
if (this.subagentManager && plugin.agents.length > 0) {
|
|
202
|
+
this.subagentManager.registerPluginAgents(plugin.name, plugin.agents);
|
|
203
|
+
}
|
|
204
|
+
|
|
195
205
|
this.plugins.set(manifest.name, plugin);
|
|
196
206
|
logger?.info(`Loaded plugin: ${manifest.name} v${manifest.version}`);
|
|
197
207
|
} catch (error) {
|
|
@@ -187,10 +187,57 @@ export class SubagentManager {
|
|
|
187
187
|
* Find subagent by exact name match
|
|
188
188
|
*/
|
|
189
189
|
async findSubagent(name: string) {
|
|
190
|
+
// Check cached configurations first (includes plugin agents)
|
|
191
|
+
if (this.cachedConfigurations !== null) {
|
|
192
|
+
const cached = this.cachedConfigurations.find(
|
|
193
|
+
(config) => config.name === name,
|
|
194
|
+
);
|
|
195
|
+
if (cached) return cached;
|
|
196
|
+
}
|
|
197
|
+
// Fall back to filesystem scan for non-plugin agents
|
|
190
198
|
const { findSubagentByName } = await import("../utils/subagentParser.js");
|
|
191
199
|
return findSubagentByName(name, this.workdir);
|
|
192
200
|
}
|
|
193
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Register plugin agents into the cached configurations.
|
|
204
|
+
* Names each agent as `pluginName:agentName` to avoid collisions.
|
|
205
|
+
*/
|
|
206
|
+
registerPluginAgents(
|
|
207
|
+
pluginName: string,
|
|
208
|
+
agents: SubagentConfiguration[],
|
|
209
|
+
): void {
|
|
210
|
+
if (this.cachedConfigurations === null) {
|
|
211
|
+
// Should not happen if initialization order is correct
|
|
212
|
+
this.cachedConfigurations = [];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Remove any previously registered agents for this plugin (by name prefix)
|
|
216
|
+
this.cachedConfigurations = this.cachedConfigurations.filter(
|
|
217
|
+
(config) => !config.name.startsWith(`${pluginName}:`),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
for (const agent of agents) {
|
|
221
|
+
const namespacedName = `${pluginName}:${agent.name}`;
|
|
222
|
+
const namespacedAgent: SubagentConfiguration = {
|
|
223
|
+
...agent,
|
|
224
|
+
name: namespacedName,
|
|
225
|
+
// Safety net: substitute any remaining ${WAVE_PLUGIN_ROOT} placeholders
|
|
226
|
+
systemPrompt: agent.systemPrompt.replace(
|
|
227
|
+
/\$\{WAVE_PLUGIN_ROOT\}/g,
|
|
228
|
+
agent.pluginRoot ?? "",
|
|
229
|
+
),
|
|
230
|
+
};
|
|
231
|
+
this.cachedConfigurations!.push(namespacedAgent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Re-sort by priority then name
|
|
235
|
+
this.cachedConfigurations!.sort((a, b) => {
|
|
236
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
237
|
+
return a.name.localeCompare(b.name);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
/**
|
|
195
242
|
* Create a new subagent instance with isolated managers
|
|
196
243
|
*/
|
package/src/prompts/index.ts
CHANGED
|
@@ -179,7 +179,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
|
|
179
179
|
|
|
180
180
|
export const DEFAULT_SYSTEM_PROMPT = BASE_SYSTEM_PROMPT;
|
|
181
181
|
|
|
182
|
-
export const
|
|
182
|
+
export const COMPACT_MESSAGES_SYSTEM_PROMPT = `You are continuing work on a software engineering task. Write a detailed continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary.
|
|
183
183
|
|
|
184
184
|
First, write your analysis in <analysis> tags as a thinking scratchpad:
|
|
185
185
|
- Chronologically review the conversation
|