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.
Files changed (75) hide show
  1. package/builtin/skills/settings/MCP.md +49 -4
  2. package/builtin/skills/settings/PERMISSIONS.md +31 -0
  3. package/dist/managers/aiManager.d.ts +19 -0
  4. package/dist/managers/aiManager.d.ts.map +1 -1
  5. package/dist/managers/aiManager.js +335 -209
  6. package/dist/managers/hookManager.d.ts +22 -0
  7. package/dist/managers/hookManager.d.ts.map +1 -1
  8. package/dist/managers/hookManager.js +95 -17
  9. package/dist/managers/mcpManager.d.ts.map +1 -1
  10. package/dist/managers/mcpManager.js +49 -39
  11. package/dist/managers/messageManager.d.ts +4 -0
  12. package/dist/managers/messageManager.d.ts.map +1 -1
  13. package/dist/managers/messageManager.js +9 -0
  14. package/dist/managers/permissionManager.d.ts +6 -0
  15. package/dist/managers/permissionManager.d.ts.map +1 -1
  16. package/dist/managers/permissionManager.js +14 -0
  17. package/dist/managers/planManager.d.ts.map +1 -1
  18. package/dist/managers/planManager.js +10 -0
  19. package/dist/managers/pluginManager.d.ts.map +1 -1
  20. package/dist/managers/pluginManager.js +28 -3
  21. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  22. package/dist/managers/slashCommandManager.js +14 -0
  23. package/dist/managers/subagentManager.d.ts.map +1 -1
  24. package/dist/managers/subagentManager.js +4 -0
  25. package/dist/prompts/index.d.ts +0 -4
  26. package/dist/prompts/index.d.ts.map +1 -1
  27. package/dist/prompts/index.js +0 -3
  28. package/dist/prompts/planModeReminders.d.ts +6 -0
  29. package/dist/prompts/planModeReminders.d.ts.map +1 -0
  30. package/dist/prompts/planModeReminders.js +112 -0
  31. package/dist/services/aiService.d.ts +1 -0
  32. package/dist/services/aiService.d.ts.map +1 -1
  33. package/dist/services/aiService.js +3 -1
  34. package/dist/services/configurationService.d.ts.map +1 -1
  35. package/dist/services/configurationService.js +5 -3
  36. package/dist/services/initializationService.d.ts.map +1 -1
  37. package/dist/services/initializationService.js +13 -12
  38. package/dist/tools/bashTool.d.ts.map +1 -1
  39. package/dist/tools/bashTool.js +6 -8
  40. package/dist/tools/editTool.d.ts.map +1 -1
  41. package/dist/tools/editTool.js +21 -8
  42. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  43. package/dist/tools/exitPlanMode.js +2 -0
  44. package/dist/types/agent.d.ts +2 -0
  45. package/dist/types/agent.d.ts.map +1 -1
  46. package/dist/types/hooks.d.ts +5 -1
  47. package/dist/types/hooks.d.ts.map +1 -1
  48. package/dist/types/hooks.js +2 -0
  49. package/dist/types/mcp.d.ts +1 -0
  50. package/dist/types/mcp.d.ts.map +1 -1
  51. package/dist/utils/editUtils.d.ts +3 -2
  52. package/dist/utils/editUtils.d.ts.map +1 -1
  53. package/dist/utils/editUtils.js +5 -3
  54. package/package.json +2 -2
  55. package/src/managers/aiManager.ts +416 -253
  56. package/src/managers/hookManager.ts +122 -18
  57. package/src/managers/mcpManager.ts +60 -47
  58. package/src/managers/messageManager.ts +10 -0
  59. package/src/managers/permissionManager.ts +18 -0
  60. package/src/managers/planManager.ts +11 -0
  61. package/src/managers/pluginManager.ts +52 -6
  62. package/src/managers/slashCommandManager.ts +17 -0
  63. package/src/managers/subagentManager.ts +4 -0
  64. package/src/prompts/index.ts +0 -8
  65. package/src/prompts/planModeReminders.ts +138 -0
  66. package/src/services/aiService.ts +4 -1
  67. package/src/services/configurationService.ts +5 -3
  68. package/src/services/initializationService.ts +16 -15
  69. package/src/tools/bashTool.ts +6 -7
  70. package/src/tools/editTool.ts +25 -8
  71. package/src/tools/exitPlanMode.ts +3 -0
  72. package/src/types/agent.ts +2 -0
  73. package/src/types/hooks.ts +9 -1
  74. package/src/types/mcp.ts +1 -0
  75. 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: `Please create a detailed summary of the conversation so far.`,
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 > process.env (includes settings.json env)
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
- process.env.WAVE_MODEL ||
519
- this.currentConfiguration?.model;
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
 
@@ -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 cwdResetMessage: string | undefined;
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
- cwdResetMessage = `Shell cwd was reset to ${context.originalWorkdir}`;
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 reset message to output if present (like Claude Code's stderr approach)
453
+ // Prepend CWD change message to output if present
455
454
  const finalOutput =
456
- (cwdResetMessage ? cwdResetMessage + "\n" : "") +
455
+ (cwdMessage ? cwdMessage + "\n" : "") +
457
456
  (combinedOutput || `Command executed with exit code: ${exitCode}`);
458
457
  const content = processToolResult(
459
458
  finalOutput,
@@ -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 = originalContent.indexOf(oldString);
129
- const matchedOldString = index !== -1 ? oldString : null;
145
+ const index = normalizedContent.indexOf(normalizedOldString);
146
+ const matchedOldString = index !== -1 ? normalizedOldString : null;
130
147
  const startLineNumber =
131
148
  index !== -1
132
- ? originalContent.substring(0, index).split("\n").length
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 = originalContent.replace(regex, newString);
150
- replacementCount = (originalContent.match(regex) || []).length;
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 = originalContent.split(matchedOldString).length - 1;
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 = originalContent.replace(matchedOldString, newString);
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.",
@@ -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;
@@ -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
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  export interface McpServerConfig {
7
+ type?: "stdio" | "sse" | "http";
7
8
  command?: string;
8
9
  args?: string[];
9
10
  env?: Record<string, string>;
@@ -10,8 +10,11 @@ export function escapeRegExp(string: string): string {
10
10
  }
11
11
 
12
12
  /**
13
- * Returns a generic error message when old_string is not found.
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
- return "old_string not found in file";
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
  }