wave-agent-sdk 0.16.13 → 0.17.0
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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ASK_USER_QUESTION_TOOL_NAME,
|
|
3
|
+
EDIT_TOOL_NAME,
|
|
4
|
+
WRITE_TOOL_NAME,
|
|
5
|
+
EXIT_PLAN_MODE_TOOL_NAME,
|
|
6
|
+
AGENT_TOOL_NAME,
|
|
7
|
+
} from "../constants/tools.js";
|
|
8
|
+
import {
|
|
9
|
+
EXPLORE_SUBAGENT_TYPE,
|
|
10
|
+
PLAN_SUBAGENT_TYPE,
|
|
11
|
+
} from "../constants/subagents.js";
|
|
12
|
+
|
|
13
|
+
export function wrapInSystemReminder(content: string): string {
|
|
14
|
+
return `<system-reminder>\n${content}\n</system-reminder>`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function buildPlanModeReminder(
|
|
18
|
+
planFilePath: string,
|
|
19
|
+
planExists: boolean,
|
|
20
|
+
isSubagent: boolean = false,
|
|
21
|
+
): string {
|
|
22
|
+
const planFileInfo = planExists
|
|
23
|
+
? `A plan file already exists at ${planFilePath}. You can read it and make incremental edits using the ${EDIT_TOOL_NAME} tool if you need to.`
|
|
24
|
+
: `No plan file exists yet. You should create your plan at ${planFilePath} using the ${WRITE_TOOL_NAME} tool if you need to.`;
|
|
25
|
+
|
|
26
|
+
const subagentPlanFileInfo = planExists
|
|
27
|
+
? `A plan file already exists at ${planFilePath}. You can read it for context if needed.`
|
|
28
|
+
: `No plan file exists yet.`;
|
|
29
|
+
|
|
30
|
+
if (isSubagent) {
|
|
31
|
+
return wrapInSystemReminder(`Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received (for example, to make edits). Instead, your role is to explore the codebase and return your findings as text output. Do NOT attempt to write or edit any files — the parent agent will write the plan file based on your text response.
|
|
32
|
+
|
|
33
|
+
## Plan File Info:
|
|
34
|
+
${subagentPlanFileInfo}
|
|
35
|
+
Answer the user's query comprehensively, using the ${ASK_USER_QUESTION_TOOL_NAME} tool if you need to ask the user clarifying questions. If you do use the ${ASK_USER_QUESTION_TOOL_NAME}, make sure to ask all clarifying questions you need to fully understand the user's intent before proceeding.`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return wrapInSystemReminder(`Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including making configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
|
|
39
|
+
|
|
40
|
+
## Plan File Info:
|
|
41
|
+
${planFileInfo}
|
|
42
|
+
You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
|
|
43
|
+
|
|
44
|
+
## Plan Workflow
|
|
45
|
+
|
|
46
|
+
### Phase 1: Initial Understanding
|
|
47
|
+
Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the ${AGENT_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE}.
|
|
48
|
+
|
|
49
|
+
1. Focus on understanding the user's request and the code associated with their request. Actively search for existing functions, utilities, and patterns that can be reused — avoid proposing new code when suitable implementations already exist.
|
|
50
|
+
|
|
51
|
+
2. **Launch up to 3 ${EXPLORE_SUBAGENT_TYPE} agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase.
|
|
52
|
+
- Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.
|
|
53
|
+
- Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
|
|
54
|
+
- Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
|
|
55
|
+
- If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigating testing patterns
|
|
56
|
+
|
|
57
|
+
### Phase 2: Design
|
|
58
|
+
Goal: Design an implementation approach.
|
|
59
|
+
|
|
60
|
+
Launch agent(s) with subagent_type=${PLAN_SUBAGENT_TYPE} to design the implementation based on the user's intent and your exploration results from Phase 1.
|
|
61
|
+
|
|
62
|
+
You can launch up to 3 agent(s) in parallel.
|
|
63
|
+
|
|
64
|
+
**Guidelines:**
|
|
65
|
+
- **Default**: Launch at least 1 Plan agent for most tasks - it helps validate your understanding and consider alternatives
|
|
66
|
+
- **Skip agents**: Only for truly trivial tasks (typo fixes, single-line changes, simple renames)
|
|
67
|
+
- **Multiple agents**: Use up to 3 agents for complex tasks that benefit from different perspectives
|
|
68
|
+
|
|
69
|
+
Examples of when to use multiple agents:
|
|
70
|
+
- The task touches multiple parts of the codebase
|
|
71
|
+
- It's a large refactor or architectural change
|
|
72
|
+
- There are many edge cases to consider
|
|
73
|
+
- You'd benefit from exploring different approaches
|
|
74
|
+
|
|
75
|
+
Example perspectives by task type:
|
|
76
|
+
- New feature: simplicity vs performance vs maintainability
|
|
77
|
+
- Bug fix: root cause vs workaround vs prevention
|
|
78
|
+
- Refactoring: minimal change vs clean architecture
|
|
79
|
+
|
|
80
|
+
In the agent prompt:
|
|
81
|
+
- Provide comprehensive background context from Phase 1 exploration including filenames and code path traces
|
|
82
|
+
- Describe requirements and constraints
|
|
83
|
+
- Request a detailed implementation plan
|
|
84
|
+
|
|
85
|
+
### Phase 3: Review
|
|
86
|
+
Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.
|
|
87
|
+
1. Read the critical files identified by agents to deepen your understanding
|
|
88
|
+
2. Ensure that the plans align with the user's original request
|
|
89
|
+
3. Use ${ASK_USER_QUESTION_TOOL_NAME} to clarify any remaining questions with the user
|
|
90
|
+
|
|
91
|
+
### Phase 4: Final Plan
|
|
92
|
+
Goal: Write your final plan to the plan file (the only file you can edit).
|
|
93
|
+
- Begin with a **Context** section: explain why this change is being made — the problem or need it addresses, what prompted it, and the intended outcome
|
|
94
|
+
- Include only your recommended approach, not all alternatives
|
|
95
|
+
- Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
|
|
96
|
+
- Include the paths of critical files to be modified
|
|
97
|
+
- Reference existing functions and utilities you found that should be reused, with their file paths
|
|
98
|
+
- Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)
|
|
99
|
+
|
|
100
|
+
### Phase 5: Call ${EXIT_PLAN_MODE_TOOL_NAME}
|
|
101
|
+
At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call ${EXIT_PLAN_MODE_TOOL_NAME} to indicate to the user that you are done planning.
|
|
102
|
+
This is critical - your turn should only end with either using the ${ASK_USER_QUESTION_TOOL_NAME} tool OR calling ${EXIT_PLAN_MODE_TOOL_NAME}. Do not stop unless it's for these 2 reasons
|
|
103
|
+
|
|
104
|
+
**Important:** Use ${ASK_USER_QUESTION_TOOL_NAME} ONLY to clarify requirements or choose between approaches. Use ${EXIT_PLAN_MODE_TOOL_NAME} to request plan approval. Do NOT ask about plan approval in any other way - no text questions, no AskUserQuestion. Phrases like "Is this plan okay?", "Should I proceed?", "How does this plan look?", "Any changes before we start?", or similar MUST use ${EXIT_PLAN_MODE_TOOL_NAME}.
|
|
105
|
+
|
|
106
|
+
NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications using the ${ASK_USER_QUESTION_TOOL_NAME} tool. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildPlanModeSparseReminder(planFilePath: string): string {
|
|
110
|
+
return wrapInSystemReminder(
|
|
111
|
+
`Plan mode still active (see full instructions earlier in conversation). Read-only except plan file at ${planFilePath}. End turns with ${ASK_USER_QUESTION_TOOL_NAME} or ${EXIT_PLAN_MODE_TOOL_NAME}.`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function buildPlanModeReEntryReminder(planFilePath: string): string {
|
|
116
|
+
return wrapInSystemReminder(`## Re-entering Plan Mode
|
|
117
|
+
|
|
118
|
+
You are returning to plan mode after having previously exited it. A plan file exists at ${planFilePath} from your previous planning session.
|
|
119
|
+
|
|
120
|
+
**Before proceeding with any new planning, you should:**
|
|
121
|
+
1. Read the existing plan file to understand what was previously planned
|
|
122
|
+
2. Evaluate the user's current request against that plan
|
|
123
|
+
3. Decide how to proceed:
|
|
124
|
+
- **Different task**: If the user's request is for a different task—even if it's similar or related—start fresh by overwriting the existing plan
|
|
125
|
+
- **Same task, continuing**: If this is explicitly a continuation or refinement of the exact same task, modify the existing plan while cleaning up outdated or irrelevant sections
|
|
126
|
+
4. Continue on with the plan process and most importantly you should always edit the plan file one way or the other before calling ${EXIT_PLAN_MODE_TOOL_NAME}
|
|
127
|
+
|
|
128
|
+
Treat this as a fresh planning session. Do not assume the existing plan is relevant without evaluating it first.`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function buildExitedPlanModeReminder(
|
|
132
|
+
planFilePath?: string,
|
|
133
|
+
planExists?: boolean,
|
|
134
|
+
): string {
|
|
135
|
+
return wrapInSystemReminder(`## Exited Plan Mode
|
|
136
|
+
|
|
137
|
+
You have exited plan mode. You can now make edits, run tools, and take actions.${planExists ? ` The plan file is located at ${planFilePath} if you need to reference it.` : ""}`);
|
|
138
|
+
}
|
|
@@ -758,6 +758,7 @@ export interface CompactMessagesOptions {
|
|
|
758
758
|
messages: ChatCompletionMessageParam[];
|
|
759
759
|
abortSignal?: AbortSignal;
|
|
760
760
|
model?: string;
|
|
761
|
+
customInstructions?: string;
|
|
761
762
|
}
|
|
762
763
|
|
|
763
764
|
export interface CompactMessagesResult {
|
|
@@ -835,7 +836,9 @@ export async function compactMessages(
|
|
|
835
836
|
...cleanedMessages,
|
|
836
837
|
{
|
|
837
838
|
role: "user",
|
|
838
|
-
content:
|
|
839
|
+
content: options.customInstructions
|
|
840
|
+
? `Please create a detailed summary of the conversation so far. Pay special attention to these instructions: ${options.customInstructions}`
|
|
841
|
+
: `Please create a detailed summary of the conversation so far.`,
|
|
839
842
|
},
|
|
840
843
|
],
|
|
841
844
|
},
|
|
@@ -511,12 +511,14 @@ export class ConfigurationService {
|
|
|
511
511
|
maxTokens?: number,
|
|
512
512
|
permissionMode?: PermissionMode,
|
|
513
513
|
): ModelConfig {
|
|
514
|
-
// Resolve agent model: override > options >
|
|
514
|
+
// Resolve agent model: override > options > currentConfiguration (settings.json model, possibly remote-merged) > process.env
|
|
515
|
+
// Priority: user's explicit model field > admin's env.WAVE_MODEL default.
|
|
516
|
+
// If admin wants hard enforcement, they set the `model` scalar field (overwrites local in mergeRemoteSettings).
|
|
515
517
|
const resolvedAgentModel =
|
|
516
518
|
model ||
|
|
517
519
|
this.options.model ||
|
|
518
|
-
|
|
519
|
-
|
|
520
|
+
this.currentConfiguration?.model ||
|
|
521
|
+
process.env.WAVE_MODEL;
|
|
520
522
|
|
|
521
523
|
// Resolve fast model: override > options > process.env (includes settings.json env)
|
|
522
524
|
const resolvedFastModel =
|
|
@@ -77,6 +77,9 @@ export class InitializationService {
|
|
|
77
77
|
|
|
78
78
|
const startTime = performance.now();
|
|
79
79
|
|
|
80
|
+
// Set global logger early so managers can use it during initialization
|
|
81
|
+
setGlobalLogger(logger || null);
|
|
82
|
+
|
|
80
83
|
// Initialize managers first
|
|
81
84
|
try {
|
|
82
85
|
const phaseStart = performance.now();
|
|
@@ -123,6 +126,19 @@ export class InitializationService {
|
|
|
123
126
|
// Don't throw error to prevent app startup failure
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
// Initialize remote settings (load disk cache synchronously, then fetch in background)
|
|
130
|
+
// Must happen BEFORE loadMergedConfiguration so remote env vars are available
|
|
131
|
+
try {
|
|
132
|
+
const phaseStart = performance.now();
|
|
133
|
+
await remoteSettingsService.initialize();
|
|
134
|
+
logger?.debug(
|
|
135
|
+
`Initialization Phase [Remote Settings] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
136
|
+
);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger?.error("Failed to initialize remote settings:", error);
|
|
139
|
+
// Don't throw error to prevent app startup failure - continue without remote settings
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
// Initialize hooks configuration
|
|
127
143
|
try {
|
|
128
144
|
const phaseStart = performance.now();
|
|
@@ -263,9 +279,6 @@ export class InitializationService {
|
|
|
263
279
|
logger?.error("Failed to initialize auto-memory directory:", error);
|
|
264
280
|
}
|
|
265
281
|
|
|
266
|
-
// Set global logger for SDK-wide access before discovering rules
|
|
267
|
-
setGlobalLogger(logger || null);
|
|
268
|
-
|
|
269
282
|
// Discover modular memory rules
|
|
270
283
|
try {
|
|
271
284
|
const phaseStart = performance.now();
|
|
@@ -289,18 +302,6 @@ export class InitializationService {
|
|
|
289
302
|
// Don't throw error to prevent app startup failure - continue without live reload
|
|
290
303
|
}
|
|
291
304
|
|
|
292
|
-
// Initialize remote settings (fetch server-managed config)
|
|
293
|
-
try {
|
|
294
|
-
const phaseStart = performance.now();
|
|
295
|
-
await remoteSettingsService.initialize();
|
|
296
|
-
logger?.debug(
|
|
297
|
-
`Initialization Phase [Remote Settings] took ${(performance.now() - phaseStart).toFixed(2)}ms`,
|
|
298
|
-
);
|
|
299
|
-
} catch (error) {
|
|
300
|
-
logger?.error("Failed to initialize remote settings:", error);
|
|
301
|
-
// Don't throw error to prevent app startup failure - continue without remote settings
|
|
302
|
-
}
|
|
303
|
-
|
|
304
305
|
// Memory is lazy-cached on first getCombinedMemoryContent call
|
|
305
306
|
// No explicit loading needed during initialization
|
|
306
307
|
|
package/src/tools/bashTool.ts
CHANGED
|
@@ -432,18 +432,17 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// If CWD changed, call the onCwdChange callback and add notification
|
|
435
|
-
let
|
|
435
|
+
let cwdMessage: string | undefined;
|
|
436
436
|
if (newCwd && newCwd !== context.workdir && context.onCwdChange) {
|
|
437
437
|
const isInSafeZone =
|
|
438
438
|
context.permissionManager?.isPathInSafeZone?.(newCwd) ?? true;
|
|
439
439
|
|
|
440
|
-
if (isInSafeZone) {
|
|
441
|
-
context.onCwdChange(newCwd);
|
|
442
|
-
} else if (context.originalWorkdir) {
|
|
440
|
+
if (!isInSafeZone && context.originalWorkdir) {
|
|
443
441
|
context.onCwdChange(context.originalWorkdir);
|
|
444
|
-
|
|
442
|
+
cwdMessage = `Shell cwd was reset to ${context.originalWorkdir}`;
|
|
445
443
|
} else {
|
|
446
444
|
context.onCwdChange(newCwd);
|
|
445
|
+
cwdMessage = `Shell working directory changed to ${newCwd}`;
|
|
447
446
|
}
|
|
448
447
|
}
|
|
449
448
|
|
|
@@ -451,9 +450,9 @@ The working directory persists between commands. Try to maintain your current wo
|
|
|
451
450
|
const combinedOutput =
|
|
452
451
|
outputBuffer + (errorBuffer ? "\n" + errorBuffer : "");
|
|
453
452
|
|
|
454
|
-
// Prepend CWD
|
|
453
|
+
// Prepend CWD change message to output if present
|
|
455
454
|
const finalOutput =
|
|
456
|
-
(
|
|
455
|
+
(cwdMessage ? cwdMessage + "\n" : "") +
|
|
457
456
|
(combinedOutput || `Command executed with exit code: ${exitCode}`);
|
|
458
457
|
const content = processToolResult(
|
|
459
458
|
finalOutput,
|
package/src/tools/editTool.ts
CHANGED
|
@@ -30,6 +30,7 @@ Usage:
|
|
|
30
30
|
- When editing text from ${READ_TOOL_NAME} tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
|
|
31
31
|
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
32
32
|
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
|
33
|
+
- Use the smallest \`old_string\` that's clearly unique — usually 2-4 adjacent lines is sufficient. Avoid including 10+ lines of context when less uniquely identifies the target. Shorter matches are less likely to contain reproduction errors.
|
|
33
34
|
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
|
|
34
35
|
- Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
|
|
35
36
|
config: {
|
|
@@ -109,6 +110,18 @@ Usage:
|
|
|
109
110
|
// Touch file to track it in context
|
|
110
111
|
context.messageManager?.touchFile(filePath);
|
|
111
112
|
|
|
113
|
+
// Enforce read-before-edit: the file must have been read first
|
|
114
|
+
if (
|
|
115
|
+
context.messageManager &&
|
|
116
|
+
!context.messageManager.hasFileInContext(filePath)
|
|
117
|
+
) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
content: "",
|
|
121
|
+
error: `You must read the file with the ${READ_TOOL_NAME} tool before editing it. Use ${READ_TOOL_NAME} on ${filePath} first.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
112
125
|
try {
|
|
113
126
|
const resolvedPath = resolvePath(filePath, context.workdir);
|
|
114
127
|
|
|
@@ -124,19 +137,23 @@ Usage:
|
|
|
124
137
|
};
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
// Normalize line endings for matching
|
|
141
|
+
const normalizedContent = originalContent.replace(/\r\n/g, "\n");
|
|
142
|
+
const normalizedOldString = oldString.replace(/\r\n/g, "\n");
|
|
143
|
+
|
|
127
144
|
// Check if old_string exists
|
|
128
|
-
const index =
|
|
129
|
-
const matchedOldString = index !== -1 ?
|
|
145
|
+
const index = normalizedContent.indexOf(normalizedOldString);
|
|
146
|
+
const matchedOldString = index !== -1 ? normalizedOldString : null;
|
|
130
147
|
const startLineNumber =
|
|
131
148
|
index !== -1
|
|
132
|
-
?
|
|
149
|
+
? normalizedContent.substring(0, index).split("\n").length
|
|
133
150
|
: undefined;
|
|
134
151
|
|
|
135
152
|
if (!matchedOldString) {
|
|
136
153
|
return {
|
|
137
154
|
success: false,
|
|
138
155
|
content: "",
|
|
139
|
-
error: analyzeEditMismatch(),
|
|
156
|
+
error: analyzeEditMismatch(normalizedOldString),
|
|
140
157
|
};
|
|
141
158
|
}
|
|
142
159
|
|
|
@@ -146,11 +163,11 @@ Usage:
|
|
|
146
163
|
if (replaceAll) {
|
|
147
164
|
// Replace all matches
|
|
148
165
|
const regex = new RegExp(escapeRegExp(matchedOldString), "g");
|
|
149
|
-
newContent =
|
|
150
|
-
replacementCount = (
|
|
166
|
+
newContent = normalizedContent.replace(regex, newString);
|
|
167
|
+
replacementCount = (normalizedContent.match(regex) || []).length;
|
|
151
168
|
} else {
|
|
152
169
|
// Replace only the first match, but first check if it's unique
|
|
153
|
-
const matches =
|
|
170
|
+
const matches = normalizedContent.split(matchedOldString).length - 1;
|
|
154
171
|
if (matches > 1) {
|
|
155
172
|
return {
|
|
156
173
|
success: false,
|
|
@@ -159,7 +176,7 @@ Usage:
|
|
|
159
176
|
};
|
|
160
177
|
}
|
|
161
178
|
|
|
162
|
-
newContent =
|
|
179
|
+
newContent = normalizedContent.replace(matchedOldString, newString);
|
|
163
180
|
replacementCount = 1;
|
|
164
181
|
}
|
|
165
182
|
|
|
@@ -107,6 +107,9 @@ Ensure your plan is complete and unambiguous:
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
context.permissionManager.setHasExitedPlanMode(true);
|
|
111
|
+
context.permissionManager.setNeedsPlanModeExitAttachment(true);
|
|
112
|
+
|
|
110
113
|
return {
|
|
111
114
|
success: true,
|
|
112
115
|
content: "Plan approved. Exiting plan mode.",
|
package/src/types/agent.ts
CHANGED
|
@@ -30,6 +30,8 @@ export interface AgentOptions {
|
|
|
30
30
|
/** Wave server URL for SSO authentication (fallback to WAVE_SERVER_URL env var) */
|
|
31
31
|
serverUrl?: string;
|
|
32
32
|
defaultHeaders?: Record<string, string>;
|
|
33
|
+
/** Per-subagent-type headers, merged into defaultHeaders for matching subagents */
|
|
34
|
+
subagentHeaders?: Record<string, Record<string, string>>;
|
|
33
35
|
fetchOptions?: ClientOptions["fetchOptions"];
|
|
34
36
|
fetch?: ClientOptions["fetch"];
|
|
35
37
|
model?: string;
|
package/src/types/hooks.ts
CHANGED
|
@@ -24,7 +24,9 @@ export type HookEvent =
|
|
|
24
24
|
| "WorktreeRemove"
|
|
25
25
|
| "CwdChanged"
|
|
26
26
|
| "SessionStart"
|
|
27
|
-
| "SessionEnd"
|
|
27
|
+
| "SessionEnd"
|
|
28
|
+
| "PreCompact"
|
|
29
|
+
| "PostCompact";
|
|
28
30
|
|
|
29
31
|
// Individual hook command configuration
|
|
30
32
|
export interface HookCommand {
|
|
@@ -117,6 +119,8 @@ export function isValidHookEvent(event: string): event is HookEvent {
|
|
|
117
119
|
"CwdChanged",
|
|
118
120
|
"SessionStart",
|
|
119
121
|
"SessionEnd",
|
|
122
|
+
"PreCompact",
|
|
123
|
+
"PostCompact",
|
|
120
124
|
].includes(event);
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -187,6 +191,8 @@ export interface HookJsonInput {
|
|
|
187
191
|
source?: SessionStartSource; // Present for SessionStart events
|
|
188
192
|
agent_type?: string; // Present for SessionStart events
|
|
189
193
|
end_source?: SessionEndSource; // Present for SessionEnd events
|
|
194
|
+
compact_instructions?: string; // Present for PreCompact events
|
|
195
|
+
compact_summary?: string; // Present for PostCompact events
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
// Extended context interface for passing additional data to hook executor
|
|
@@ -205,6 +211,8 @@ export interface ExtendedHookExecutionContext extends HookExecutionContext {
|
|
|
205
211
|
source?: SessionStartSource; // Session start source (SessionStart only)
|
|
206
212
|
agentType?: string; // Agent type identifier (SessionStart only)
|
|
207
213
|
endSource?: SessionEndSource; // Session end source (SessionEnd only)
|
|
214
|
+
compactInstructions?: string; // Custom instructions for PreCompact
|
|
215
|
+
compactSummary?: string; // Summary text for PostCompact
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
// Environment variables injected into hook processes
|
package/src/types/mcp.ts
CHANGED
package/src/utils/editUtils.ts
CHANGED
|
@@ -10,8 +10,11 @@ export function escapeRegExp(string: string): string {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Returns
|
|
13
|
+
* Returns an error message when old_string is not found, including
|
|
14
|
+
* the attempted string to help the model self-correct on retry.
|
|
14
15
|
*/
|
|
15
|
-
export function analyzeEditMismatch(): string {
|
|
16
|
-
|
|
16
|
+
export function analyzeEditMismatch(oldString: string): string {
|
|
17
|
+
const displayString =
|
|
18
|
+
oldString.length > 200 ? oldString.substring(0, 200) + "..." : oldString;
|
|
19
|
+
return `String to replace not found in file.\nString: ${displayString}`;
|
|
17
20
|
}
|