wave-agent-sdk 0.16.13 → 0.17.1
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/MCP.md +49 -4
- package/builtin/skills/settings/PERMISSIONS.md +31 -0
- package/dist/managers/aiManager.d.ts +19 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +335 -209
- package/dist/managers/hookManager.d.ts +22 -0
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +95 -17
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +49 -39
- package/dist/managers/messageManager.d.ts +4 -0
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +9 -0
- package/dist/managers/permissionManager.d.ts +6 -0
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +14 -0
- package/dist/managers/planManager.d.ts.map +1 -1
- package/dist/managers/planManager.js +10 -0
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +28 -3
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +14 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +4 -0
- package/dist/prompts/index.d.ts +0 -4
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +0 -3
- package/dist/prompts/planModeReminders.d.ts +6 -0
- package/dist/prompts/planModeReminders.d.ts.map +1 -0
- package/dist/prompts/planModeReminders.js +112 -0
- package/dist/services/aiService.d.ts +1 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +3 -1
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +5 -3
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +13 -12
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +6 -8
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +21 -8
- package/dist/tools/exitPlanMode.d.ts.map +1 -1
- package/dist/tools/exitPlanMode.js +2 -0
- package/dist/types/agent.d.ts +2 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +5 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +2 -0
- package/dist/types/mcp.d.ts +1 -0
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/editUtils.d.ts +3 -2
- package/dist/utils/editUtils.d.ts.map +1 -1
- package/dist/utils/editUtils.js +5 -3
- package/package.json +2 -2
- package/src/managers/aiManager.ts +416 -253
- package/src/managers/hookManager.ts +122 -18
- package/src/managers/mcpManager.ts +60 -47
- package/src/managers/messageManager.ts +10 -0
- package/src/managers/permissionManager.ts +18 -0
- package/src/managers/planManager.ts +11 -0
- package/src/managers/pluginManager.ts +52 -6
- package/src/managers/slashCommandManager.ts +17 -0
- package/src/managers/subagentManager.ts +4 -0
- package/src/prompts/index.ts +0 -8
- package/src/prompts/planModeReminders.ts +138 -0
- package/src/services/aiService.ts +4 -1
- package/src/services/configurationService.ts +5 -3
- package/src/services/initializationService.ts +16 -15
- package/src/tools/bashTool.ts +6 -7
- package/src/tools/editTool.ts +25 -8
- package/src/tools/exitPlanMode.ts +3 -0
- package/src/types/agent.ts +2 -0
- package/src/types/hooks.ts +9 -1
- package/src/types/mcp.ts +1 -0
- package/src/utils/editUtils.ts +6 -3
|
@@ -31,6 +31,9 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
31
31
|
|
|
32
32
|
export class HookManager {
|
|
33
33
|
private configuration: PartialHookConfiguration | undefined;
|
|
34
|
+
private programmaticHooks: PartialHookConfiguration = {};
|
|
35
|
+
private pluginHooks: PartialHookConfiguration = {};
|
|
36
|
+
private waveConfigHooks: PartialHookConfiguration = {};
|
|
34
37
|
private readonly matcher: HookMatcher;
|
|
35
38
|
private readonly workdir: string;
|
|
36
39
|
|
|
@@ -47,14 +50,16 @@ export class HookManager {
|
|
|
47
50
|
* Load hook configuration from programmatic source (AgentOptions.hooks)
|
|
48
51
|
*/
|
|
49
52
|
loadConfiguration(hooks?: PartialHookConfiguration): void {
|
|
50
|
-
|
|
53
|
+
this.programmaticHooks = {};
|
|
51
54
|
|
|
52
55
|
if (hooks) {
|
|
53
|
-
this.mergeHooksConfiguration(
|
|
56
|
+
this.mergeHooksConfiguration(this.programmaticHooks, hooks);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// Validate merged configuration
|
|
57
|
-
const validation = this.validatePartialConfiguration(
|
|
60
|
+
const validation = this.validatePartialConfiguration(
|
|
61
|
+
this.programmaticHooks,
|
|
62
|
+
);
|
|
58
63
|
if (!validation.valid) {
|
|
59
64
|
throw new HookConfigurationError(
|
|
60
65
|
"merged configuration",
|
|
@@ -62,7 +67,7 @@ export class HookManager {
|
|
|
62
67
|
);
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
this.
|
|
70
|
+
this.rebuildConfiguration();
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
/**
|
|
@@ -71,8 +76,9 @@ export class HookManager {
|
|
|
71
76
|
*/
|
|
72
77
|
loadConfigurationFromWaveConfig(waveConfig: WaveConfiguration | null): void {
|
|
73
78
|
try {
|
|
74
|
-
//
|
|
75
|
-
|
|
79
|
+
// Replace (not append) wave config hooks to avoid duplicates on reload
|
|
80
|
+
this.waveConfigHooks = {};
|
|
81
|
+
|
|
76
82
|
if (waveConfig?.hooks) {
|
|
77
83
|
const validation = this.validatePartialConfiguration(waveConfig.hooks);
|
|
78
84
|
if (!validation.valid) {
|
|
@@ -81,11 +87,10 @@ export class HookManager {
|
|
|
81
87
|
validation.errors,
|
|
82
88
|
);
|
|
83
89
|
}
|
|
84
|
-
|
|
85
|
-
this.configuration = {};
|
|
86
|
-
}
|
|
87
|
-
this.mergeHooksConfiguration(this.configuration, waveConfig.hooks);
|
|
90
|
+
this.waveConfigHooks = { ...waveConfig.hooks };
|
|
88
91
|
}
|
|
92
|
+
|
|
93
|
+
this.rebuildConfiguration();
|
|
89
94
|
} catch (error) {
|
|
90
95
|
// Re-throw configuration errors, but handle other errors gracefully
|
|
91
96
|
if (error instanceof HookConfigurationError) {
|
|
@@ -98,6 +103,19 @@ export class HookManager {
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Rebuild the full configuration from all sources:
|
|
108
|
+
* programmatic (AgentOptions.hooks) + plugin + wave config (settings.json)
|
|
109
|
+
* Order determines precedence on conflict (later sources append).
|
|
110
|
+
*/
|
|
111
|
+
private rebuildConfiguration(): void {
|
|
112
|
+
const rebuilt: PartialHookConfiguration = {};
|
|
113
|
+
this.mergeHooksConfiguration(rebuilt, this.programmaticHooks);
|
|
114
|
+
this.mergeHooksConfiguration(rebuilt, this.pluginHooks);
|
|
115
|
+
this.mergeHooksConfiguration(rebuilt, this.waveConfigHooks);
|
|
116
|
+
this.configuration = Object.keys(rebuilt).length > 0 ? rebuilt : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
/**
|
|
102
120
|
* Execute hooks for a specific event
|
|
103
121
|
*/
|
|
@@ -382,6 +400,16 @@ export class HookManager {
|
|
|
382
400
|
messageManager.addErrorBlock(errorMessage);
|
|
383
401
|
return { shouldBlock: false };
|
|
384
402
|
|
|
403
|
+
case "PreCompact":
|
|
404
|
+
// Non-blocking for compaction, show error in error block
|
|
405
|
+
messageManager.addErrorBlock(errorMessage);
|
|
406
|
+
return { shouldBlock: false };
|
|
407
|
+
|
|
408
|
+
case "PostCompact":
|
|
409
|
+
// Non-blocking for compaction, show error in error block
|
|
410
|
+
messageManager.addErrorBlock(errorMessage);
|
|
411
|
+
return { shouldBlock: false };
|
|
412
|
+
|
|
385
413
|
default:
|
|
386
414
|
return { shouldBlock: false };
|
|
387
415
|
}
|
|
@@ -588,7 +616,9 @@ export class HookManager {
|
|
|
588
616
|
event === "WorktreeCreate" ||
|
|
589
617
|
event === "WorktreeRemove" ||
|
|
590
618
|
event === "SessionStart" ||
|
|
591
|
-
event === "SessionEnd"
|
|
619
|
+
event === "SessionEnd" ||
|
|
620
|
+
event === "PreCompact" ||
|
|
621
|
+
event === "PostCompact") &&
|
|
592
622
|
context.toolName !== undefined
|
|
593
623
|
) {
|
|
594
624
|
logger?.warn(
|
|
@@ -671,7 +701,9 @@ export class HookManager {
|
|
|
671
701
|
event === "WorktreeRemove" ||
|
|
672
702
|
event === "CwdChanged" ||
|
|
673
703
|
event === "SessionStart" ||
|
|
674
|
-
event === "SessionEnd"
|
|
704
|
+
event === "SessionEnd" ||
|
|
705
|
+
event === "PreCompact" ||
|
|
706
|
+
event === "PostCompact"
|
|
675
707
|
) {
|
|
676
708
|
return true;
|
|
677
709
|
}
|
|
@@ -734,7 +766,9 @@ export class HookManager {
|
|
|
734
766
|
event === "WorktreeCreate" ||
|
|
735
767
|
event === "WorktreeRemove" ||
|
|
736
768
|
event === "SessionStart" ||
|
|
737
|
-
event === "SessionEnd"
|
|
769
|
+
event === "SessionEnd" ||
|
|
770
|
+
event === "PreCompact" ||
|
|
771
|
+
event === "PostCompact") &&
|
|
738
772
|
config.matcher
|
|
739
773
|
) {
|
|
740
774
|
errors.push(`${prefix}: Event ${event} should not have a matcher`);
|
|
@@ -778,6 +812,8 @@ export class HookManager {
|
|
|
778
812
|
CwdChanged: 0,
|
|
779
813
|
SessionStart: 0,
|
|
780
814
|
SessionEnd: 0,
|
|
815
|
+
PreCompact: 0,
|
|
816
|
+
PostCompact: 0,
|
|
781
817
|
},
|
|
782
818
|
};
|
|
783
819
|
}
|
|
@@ -794,6 +830,8 @@ export class HookManager {
|
|
|
794
830
|
CwdChanged: 0,
|
|
795
831
|
SessionStart: 0,
|
|
796
832
|
SessionEnd: 0,
|
|
833
|
+
PreCompact: 0,
|
|
834
|
+
PostCompact: 0,
|
|
797
835
|
};
|
|
798
836
|
|
|
799
837
|
let totalConfigs = 0;
|
|
@@ -854,10 +892,6 @@ export class HookManager {
|
|
|
854
892
|
pluginRoot: string,
|
|
855
893
|
hooks: PartialHookConfiguration,
|
|
856
894
|
): void {
|
|
857
|
-
if (!this.configuration) {
|
|
858
|
-
this.configuration = {};
|
|
859
|
-
}
|
|
860
|
-
|
|
861
895
|
// Stamp pluginRoot on each hook command
|
|
862
896
|
const stampedHooks: PartialHookConfiguration = {};
|
|
863
897
|
for (const [event, configs] of Object.entries(hooks)) {
|
|
@@ -868,7 +902,8 @@ export class HookManager {
|
|
|
868
902
|
}));
|
|
869
903
|
}
|
|
870
904
|
|
|
871
|
-
this.mergeHooksConfiguration(this.
|
|
905
|
+
this.mergeHooksConfiguration(this.pluginHooks, stampedHooks);
|
|
906
|
+
this.rebuildConfiguration();
|
|
872
907
|
}
|
|
873
908
|
|
|
874
909
|
/**
|
|
@@ -962,4 +997,73 @@ export class HookManager {
|
|
|
962
997
|
|
|
963
998
|
return results;
|
|
964
999
|
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Execute PreCompact hooks before compaction.
|
|
1003
|
+
* Returns custom instructions from hook stdout.
|
|
1004
|
+
*/
|
|
1005
|
+
async executePreCompactHooks(
|
|
1006
|
+
sessionId: string,
|
|
1007
|
+
transcriptPath: string,
|
|
1008
|
+
customInstructions?: string,
|
|
1009
|
+
): Promise<{
|
|
1010
|
+
results: HookExecutionResult[];
|
|
1011
|
+
additionalInstructions?: string;
|
|
1012
|
+
}> {
|
|
1013
|
+
const context: ExtendedHookExecutionContext = {
|
|
1014
|
+
event: "PreCompact",
|
|
1015
|
+
projectDir: this.workdir,
|
|
1016
|
+
timestamp: new Date(),
|
|
1017
|
+
sessionId,
|
|
1018
|
+
transcriptPath,
|
|
1019
|
+
cwd: this.workdir,
|
|
1020
|
+
compactInstructions: customInstructions,
|
|
1021
|
+
env: Object.fromEntries(
|
|
1022
|
+
Object.entries(process.env).filter((e) => e[1] !== undefined),
|
|
1023
|
+
) as Record<string, string>,
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
const results = await this.executeHooks("PreCompact", context);
|
|
1027
|
+
|
|
1028
|
+
let additionalInstructions: string | undefined;
|
|
1029
|
+
for (const result of results) {
|
|
1030
|
+
if (result.success && result.stdout?.trim()) {
|
|
1031
|
+
const trimmed = result.stdout.trim();
|
|
1032
|
+
additionalInstructions = additionalInstructions
|
|
1033
|
+
? additionalInstructions + "\n" + trimmed
|
|
1034
|
+
: trimmed;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
return { results, additionalInstructions };
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Execute PostCompact hooks after compaction.
|
|
1043
|
+
* Receives the compact summary text.
|
|
1044
|
+
*/
|
|
1045
|
+
async executePostCompactHooks(
|
|
1046
|
+
sessionId: string,
|
|
1047
|
+
transcriptPath: string,
|
|
1048
|
+
compactSummary: string,
|
|
1049
|
+
): Promise<HookExecutionResult[]> {
|
|
1050
|
+
const context: ExtendedHookExecutionContext = {
|
|
1051
|
+
event: "PostCompact",
|
|
1052
|
+
projectDir: this.workdir,
|
|
1053
|
+
timestamp: new Date(),
|
|
1054
|
+
sessionId,
|
|
1055
|
+
transcriptPath,
|
|
1056
|
+
cwd: this.workdir,
|
|
1057
|
+
compactSummary,
|
|
1058
|
+
env:
|
|
1059
|
+
this.container.get<Record<string, string>>("MergedEnv") ||
|
|
1060
|
+
(process.env as Record<string, string>),
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const results = await this.executeHooks("PostCompact", context);
|
|
1064
|
+
if (results.length > 0) {
|
|
1065
|
+
this.processHookResults("PostCompact", results);
|
|
1066
|
+
}
|
|
1067
|
+
return results;
|
|
1068
|
+
}
|
|
965
1069
|
}
|
|
@@ -369,61 +369,69 @@ export class McpManager {
|
|
|
369
369
|
version: "1.0.0",
|
|
370
370
|
},
|
|
371
371
|
{
|
|
372
|
-
capabilities: {
|
|
373
|
-
tools: {},
|
|
374
|
-
},
|
|
372
|
+
capabilities: {},
|
|
375
373
|
},
|
|
376
374
|
);
|
|
377
375
|
|
|
378
|
-
|
|
376
|
+
const serverType = server.config.type;
|
|
377
|
+
|
|
378
|
+
if (serverType === "http" || (!serverType && server.config.url)) {
|
|
379
|
+
if (!server.config.url) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`MCP server ${name} with type "http" requires a 'url'`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
379
384
|
const url = new URL(server.config.url);
|
|
380
385
|
const headers = server.config.headers;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
386
|
+
logger?.debug(
|
|
387
|
+
`Connecting to MCP server ${name} using Streamable HTTP at ${url.href}`,
|
|
388
|
+
);
|
|
389
|
+
transport = new StreamableHTTPClientTransport(url, {
|
|
390
|
+
requestInit: { headers },
|
|
391
|
+
});
|
|
392
|
+
client = createClient();
|
|
393
|
+
await client.connect(transport);
|
|
394
|
+
const toolsResponse = await client.listTools();
|
|
395
|
+
tools =
|
|
396
|
+
toolsResponse.tools?.map((tool) => ({
|
|
397
|
+
name: tool.name,
|
|
398
|
+
description: tool.description,
|
|
399
|
+
inputSchema: tool.inputSchema,
|
|
400
|
+
})) || [];
|
|
401
|
+
logger?.info(`Connected to MCP server ${name} using Streamable HTTP`);
|
|
402
|
+
} else if (serverType === "sse") {
|
|
403
|
+
if (!server.config.url) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`MCP server ${name} with type "sse" requires a 'url'`,
|
|
385
406
|
);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
407
|
+
}
|
|
408
|
+
const url = new URL(server.config.url);
|
|
409
|
+
const headers = server.config.headers;
|
|
410
|
+
logger?.debug(
|
|
411
|
+
`Connecting to MCP server ${name} using SSE at ${url.href}`,
|
|
412
|
+
);
|
|
413
|
+
transport = new SSEClientTransport(url, {
|
|
414
|
+
requestInit: { headers },
|
|
415
|
+
});
|
|
416
|
+
client = createClient();
|
|
417
|
+
await client.connect(transport);
|
|
418
|
+
const toolsResponse = await client.listTools();
|
|
419
|
+
tools =
|
|
420
|
+
toolsResponse.tools?.map((tool) => ({
|
|
421
|
+
name: tool.name,
|
|
422
|
+
description: tool.description,
|
|
423
|
+
inputSchema: tool.inputSchema,
|
|
424
|
+
})) || [];
|
|
425
|
+
logger?.info(`Connected to MCP server ${name} using SSE`);
|
|
426
|
+
} else if (
|
|
427
|
+
serverType === "stdio" ||
|
|
428
|
+
(!serverType && server.config.command)
|
|
429
|
+
) {
|
|
430
|
+
if (!server.config.command) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`MCP server ${name} with type "stdio" requires a 'command'`,
|
|
409
433
|
);
|
|
410
|
-
transport = new SSEClientTransport(url, {
|
|
411
|
-
requestInit: { headers },
|
|
412
|
-
});
|
|
413
|
-
client = createClient();
|
|
414
|
-
await client.connect(transport);
|
|
415
|
-
|
|
416
|
-
const toolsResponse = await client.listTools();
|
|
417
|
-
tools =
|
|
418
|
-
toolsResponse.tools?.map((tool) => ({
|
|
419
|
-
name: tool.name,
|
|
420
|
-
description: tool.description,
|
|
421
|
-
inputSchema: tool.inputSchema,
|
|
422
|
-
})) || [];
|
|
423
|
-
|
|
424
|
-
logger?.info(`Connected to MCP server ${name} using SSE (fallback)`);
|
|
425
434
|
}
|
|
426
|
-
} else if (server.config.command) {
|
|
427
435
|
const agentEnv =
|
|
428
436
|
this.container.get<Record<string, string>>("MergedEnv") ||
|
|
429
437
|
(process.env as Record<string, string>);
|
|
@@ -492,6 +500,11 @@ export class McpManager {
|
|
|
492
500
|
description: tool.description,
|
|
493
501
|
inputSchema: tool.inputSchema,
|
|
494
502
|
})) || [];
|
|
503
|
+
} else if (serverType) {
|
|
504
|
+
// Unknown type value
|
|
505
|
+
throw new Error(
|
|
506
|
+
`MCP server ${name} has unknown type "${serverType}". Must be "stdio", "sse", or "http"`,
|
|
507
|
+
);
|
|
495
508
|
} else {
|
|
496
509
|
throw new Error(
|
|
497
510
|
`MCP server ${name} configuration must include either 'command' or 'url'`,
|
|
@@ -241,6 +241,16 @@ export class MessageManager {
|
|
|
241
241
|
this.filesInContext.add(normalizedPath);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Checks if a file has been read or edited in this conversation.
|
|
246
|
+
*/
|
|
247
|
+
public hasFileInContext(filePath: string): boolean {
|
|
248
|
+
const normalizedPath = isAbsolute(filePath)
|
|
249
|
+
? relative(this.workdir, filePath)
|
|
250
|
+
: filePath;
|
|
251
|
+
return this.filesInContext.has(normalizedPath);
|
|
252
|
+
}
|
|
253
|
+
|
|
244
254
|
/**
|
|
245
255
|
* Extracts and adds file paths from a message's tool blocks.
|
|
246
256
|
*/
|
|
@@ -129,6 +129,8 @@ export class PermissionManager {
|
|
|
129
129
|
private additionalDirectories: string[] = [];
|
|
130
130
|
private systemAdditionalDirectories: string[] = [];
|
|
131
131
|
private planFilePath?: string;
|
|
132
|
+
private hasExitedPlanMode: boolean = false;
|
|
133
|
+
private needsPlanModeExitAttachment: boolean = false;
|
|
132
134
|
private workdir?: string;
|
|
133
135
|
private worktreeName?: string;
|
|
134
136
|
private mainRepoRoot?: string;
|
|
@@ -315,6 +317,22 @@ export class PermissionManager {
|
|
|
315
317
|
return this.planFilePath;
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
public setHasExitedPlanMode(value: boolean): void {
|
|
321
|
+
this.hasExitedPlanMode = value;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public hasExitedPlanModeInSession(): boolean {
|
|
325
|
+
return this.hasExitedPlanMode;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public setNeedsPlanModeExitAttachment(value: boolean): void {
|
|
329
|
+
this.needsPlanModeExitAttachment = value;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public getNeedsPlanModeExitAttachment(): boolean {
|
|
333
|
+
return this.needsPlanModeExitAttachment;
|
|
334
|
+
}
|
|
335
|
+
|
|
318
336
|
/**
|
|
319
337
|
* Public wrapper for isInsideSafeZone to check if a path is in the safe zone
|
|
320
338
|
*/
|
|
@@ -74,7 +74,13 @@ export class PlanManager {
|
|
|
74
74
|
this.container.get<PermissionManager>("PermissionManager");
|
|
75
75
|
const messageManager = this.container.get<MessageManager>("MessageManager");
|
|
76
76
|
|
|
77
|
+
const previousMode = this.container.get<PermissionMode>("PermissionMode");
|
|
78
|
+
|
|
77
79
|
if (mode === "plan") {
|
|
80
|
+
// Entering plan mode: clear any pending exit attachment
|
|
81
|
+
// (prevents sending both plan_mode and plan_mode_exit on rapid toggle)
|
|
82
|
+
permissionManager?.setNeedsPlanModeExitAttachment(false);
|
|
83
|
+
|
|
78
84
|
this.getOrGeneratePlanFilePath(messageManager?.getRootSessionId())
|
|
79
85
|
.then(({ path }) => {
|
|
80
86
|
logger?.debug("Plan file path generated", { path });
|
|
@@ -83,6 +89,11 @@ export class PlanManager {
|
|
|
83
89
|
.catch((error) => {
|
|
84
90
|
logger?.error("Failed to generate plan file path", error);
|
|
85
91
|
});
|
|
92
|
+
} else if (previousMode === "plan") {
|
|
93
|
+
// Leaving plan mode: set flags for exit notification and re-entry detection
|
|
94
|
+
permissionManager?.setHasExitedPlanMode(true);
|
|
95
|
+
permissionManager?.setNeedsPlanModeExitAttachment(true);
|
|
96
|
+
permissionManager?.setPlanFilePath(undefined);
|
|
86
97
|
} else {
|
|
87
98
|
permissionManager?.setPlanFilePath(undefined);
|
|
88
99
|
}
|
|
@@ -109,14 +109,60 @@ export class PluginManager {
|
|
|
109
109
|
);
|
|
110
110
|
|
|
111
111
|
if (isMarketplaceKnown) {
|
|
112
|
+
// Pre-check: verify the plugin still exists in the marketplace manifest
|
|
113
|
+
// before acquiring the lock in installPlugin (which can block ~8s during autoUpdate)
|
|
114
|
+
const marketplace = knownMarketplaces.find(
|
|
115
|
+
(m) => m.name === marketplaceName,
|
|
116
|
+
);
|
|
117
|
+
if (!marketplace) continue;
|
|
118
|
+
try {
|
|
119
|
+
const marketplacePath = marketplaceService.getMarketplacePath(
|
|
120
|
+
marketplace.source,
|
|
121
|
+
);
|
|
122
|
+
const manifest =
|
|
123
|
+
await marketplaceService.loadMarketplaceManifest(
|
|
124
|
+
marketplacePath,
|
|
125
|
+
);
|
|
126
|
+
const pluginExists = manifest.plugins.some(
|
|
127
|
+
(p) => p.name === name,
|
|
128
|
+
);
|
|
129
|
+
if (!pluginExists) {
|
|
130
|
+
logger?.warn(
|
|
131
|
+
`Plugin ${pluginId} is enabled but no longer exists in marketplace ${marketplaceName}. Removing from enabledPlugins.`,
|
|
132
|
+
);
|
|
133
|
+
await this.configurationService?.removeEnabledPlugin(
|
|
134
|
+
this.workdir,
|
|
135
|
+
"user",
|
|
136
|
+
pluginId,
|
|
137
|
+
);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Manifest read failed (marketplace not cloned yet?) — fall through to installPlugin
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
logger?.info(`Auto-installing missing plugin: ${pluginId}`);
|
|
113
145
|
try {
|
|
114
146
|
await marketplaceService.installPlugin(pluginId);
|
|
115
147
|
} catch (installError) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
installError
|
|
119
|
-
)
|
|
148
|
+
if (
|
|
149
|
+
installError instanceof Error &&
|
|
150
|
+
installError.message.includes("not found in marketplace")
|
|
151
|
+
) {
|
|
152
|
+
logger?.warn(
|
|
153
|
+
`Plugin ${pluginId} no longer found in marketplace. Removing from enabledPlugins.`,
|
|
154
|
+
);
|
|
155
|
+
await this.configurationService?.removeEnabledPlugin(
|
|
156
|
+
this.workdir,
|
|
157
|
+
"user",
|
|
158
|
+
pluginId,
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
logger?.error(
|
|
162
|
+
`Failed to auto-install plugin ${pluginId}:`,
|
|
163
|
+
installError,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
120
166
|
}
|
|
121
167
|
} else {
|
|
122
168
|
logger?.warn(
|
|
@@ -132,7 +178,7 @@ export class PluginManager {
|
|
|
132
178
|
for (const p of installedRegistry.plugins) {
|
|
133
179
|
const pluginId = `${p.name}@${p.marketplace}`;
|
|
134
180
|
if (this.enabledPlugins[pluginId] !== true) {
|
|
135
|
-
logger?.
|
|
181
|
+
logger?.debug(`Plugin ${pluginId} is not enabled via configuration`);
|
|
136
182
|
continue;
|
|
137
183
|
}
|
|
138
184
|
await this.loadSinglePlugin(p.cachePath);
|
|
@@ -203,7 +249,7 @@ export class PluginManager {
|
|
|
203
249
|
}
|
|
204
250
|
|
|
205
251
|
this.plugins.set(manifest.name, plugin);
|
|
206
|
-
logger?.
|
|
252
|
+
logger?.debug(`Loaded plugin: ${manifest.name} v${manifest.version}`);
|
|
207
253
|
} catch (error) {
|
|
208
254
|
logger?.error(`Failed to load plugin from ${absolutePath}`, error);
|
|
209
255
|
}
|
|
@@ -158,6 +158,23 @@ export class SlashCommandManager {
|
|
|
158
158
|
}
|
|
159
159
|
},
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
// Register built-in compact command
|
|
163
|
+
this.registerCommand({
|
|
164
|
+
id: "compact",
|
|
165
|
+
name: "compact",
|
|
166
|
+
description: "Compact conversation history to reduce context usage",
|
|
167
|
+
handler: async (args?: string, signal?: AbortSignal) => {
|
|
168
|
+
this.aiManager.abortAIMessage();
|
|
169
|
+
|
|
170
|
+
const customInstructions = args?.trim() || undefined;
|
|
171
|
+
|
|
172
|
+
await this.aiManager.compactConversation({
|
|
173
|
+
customInstructions,
|
|
174
|
+
abortSignal: signal,
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
});
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
/**
|
|
@@ -287,6 +287,10 @@ export class SubagentManager {
|
|
|
287
287
|
if (parentOptions) {
|
|
288
288
|
const subagentOptions: import("../types/agent.js").AgentOptions = {
|
|
289
289
|
...parentOptions,
|
|
290
|
+
defaultHeaders: {
|
|
291
|
+
...parentOptions.defaultHeaders,
|
|
292
|
+
...parentOptions.subagentHeaders?.[parameters.subagent_type],
|
|
293
|
+
},
|
|
290
294
|
callbacks: {
|
|
291
295
|
...parentOptions.callbacks,
|
|
292
296
|
onLoadingChange: undefined,
|
package/src/prompts/index.ts
CHANGED
|
@@ -239,10 +239,6 @@ export function buildSystemPrompt(
|
|
|
239
239
|
memory?: string;
|
|
240
240
|
language?: string;
|
|
241
241
|
isSubagent?: boolean;
|
|
242
|
-
planMode?: {
|
|
243
|
-
planFilePath: string;
|
|
244
|
-
planExists: boolean;
|
|
245
|
-
};
|
|
246
242
|
autoMemory?: {
|
|
247
243
|
directory: string;
|
|
248
244
|
content: string;
|
|
@@ -269,10 +265,6 @@ export function buildSystemPrompt(
|
|
|
269
265
|
prompt += `\n\n# Language\nAlways respond in ${options.language}. Use ${options.language} for all explanations, comments, and communications with the user. Technical terms and code identifiers should remain in their original form.`;
|
|
270
266
|
}
|
|
271
267
|
|
|
272
|
-
if (options.planMode) {
|
|
273
|
-
prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
268
|
if (options.workdir) {
|
|
277
269
|
const isGitRepo = isGitRepository(options.workdir);
|
|
278
270
|
const platform = os.platform();
|