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
|
@@ -4,7 +4,9 @@ import { microcompactMessages } from "../utils/microcompact.js";
|
|
|
4
4
|
import { parseTaskNotificationXml } from "../utils/notificationXml.js";
|
|
5
5
|
import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
7
8
|
import { buildSystemPrompt } from "../prompts/index.js";
|
|
9
|
+
import { buildPlanModeReminder, buildPlanModeSparseReminder, buildPlanModeReEntryReminder, buildExitedPlanModeReminder, } from "../prompts/planModeReminders.js";
|
|
8
10
|
import { recoverTruncatedJson } from "../utils/stringUtils.js";
|
|
9
11
|
import { logger } from "../utils/globalLogger.js";
|
|
10
12
|
import { startInteractionSpan, endInteractionSpan, startLLMRequestSpan, endLLMRequestSpan, } from "../telemetry/sessionTracing.js";
|
|
@@ -116,6 +118,97 @@ export class AIManager {
|
|
|
116
118
|
isSubagent: !!this.subagentType,
|
|
117
119
|
});
|
|
118
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Build plan mode system-reminder messages to inject into the API message stream.
|
|
123
|
+
* These are transient messages not stored in the message history.
|
|
124
|
+
* This preserves prompt caching by keeping the system prompt constant.
|
|
125
|
+
*/
|
|
126
|
+
buildPlanModeMessages(currentMode) {
|
|
127
|
+
const messages = [];
|
|
128
|
+
if (!this.permissionManager)
|
|
129
|
+
return messages;
|
|
130
|
+
// Handle exit notification (one-time after leaving plan mode)
|
|
131
|
+
if (this.permissionManager.getNeedsPlanModeExitAttachment()) {
|
|
132
|
+
const planFilePath = this.permissionManager.getPlanFilePath();
|
|
133
|
+
const planExists = planFilePath ? existsSync(planFilePath) : false;
|
|
134
|
+
messages.push({
|
|
135
|
+
role: "user",
|
|
136
|
+
content: buildExitedPlanModeReminder(planFilePath, planExists),
|
|
137
|
+
});
|
|
138
|
+
this.permissionManager.setNeedsPlanModeExitAttachment(false);
|
|
139
|
+
}
|
|
140
|
+
// Handle plan mode reminders
|
|
141
|
+
if (currentMode !== "plan")
|
|
142
|
+
return messages;
|
|
143
|
+
const planFilePath = this.permissionManager.getPlanFilePath();
|
|
144
|
+
if (!planFilePath)
|
|
145
|
+
return messages;
|
|
146
|
+
const planExists = existsSync(planFilePath);
|
|
147
|
+
// Check for re-entry: flag is set AND plan file exists
|
|
148
|
+
if (this.permissionManager.hasExitedPlanModeInSession() && planExists) {
|
|
149
|
+
messages.push({
|
|
150
|
+
role: "user",
|
|
151
|
+
content: buildPlanModeReEntryReminder(planFilePath),
|
|
152
|
+
});
|
|
153
|
+
this.permissionManager.setHasExitedPlanMode(false); // One-time
|
|
154
|
+
}
|
|
155
|
+
// Count plan_mode system-reminders in existing messages to determine full vs sparse
|
|
156
|
+
// and count human turns since last reminder for throttling
|
|
157
|
+
const recentApiMessages = this.messageManager.getMessages();
|
|
158
|
+
let planModeReminderCount = 0;
|
|
159
|
+
let humanTurnsSinceLastReminder = 0;
|
|
160
|
+
let foundLastReminder = false;
|
|
161
|
+
for (let i = recentApiMessages.length - 1; i >= 0; i--) {
|
|
162
|
+
const msg = recentApiMessages[i];
|
|
163
|
+
if (msg.role === "user" && !msg.isMeta) {
|
|
164
|
+
// Count human turns (non-meta user messages without tool results)
|
|
165
|
+
const hasToolResult = msg.blocks?.some((b) => b.type === "tool");
|
|
166
|
+
if (!hasToolResult) {
|
|
167
|
+
if (!foundLastReminder) {
|
|
168
|
+
humanTurnsSinceLastReminder++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Check for existing plan mode system-reminders
|
|
173
|
+
if (msg.role === "user" && msg.isMeta) {
|
|
174
|
+
const textContent = msg.blocks
|
|
175
|
+
?.filter((b) => b.type === "text")
|
|
176
|
+
.map((b) => ("content" in b ? b.content : ""))
|
|
177
|
+
.join("");
|
|
178
|
+
if (textContent?.includes("Plan mode is active") ||
|
|
179
|
+
textContent?.includes("Plan mode still active")) {
|
|
180
|
+
planModeReminderCount++;
|
|
181
|
+
if (!foundLastReminder) {
|
|
182
|
+
foundLastReminder = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Throttle: only inject every 5 human turns (but always inject on first turn)
|
|
188
|
+
const TURNS_BETWEEN_REMINDERS = 5;
|
|
189
|
+
const FULL_REMINDER_EVERY_N = 5;
|
|
190
|
+
if (foundLastReminder &&
|
|
191
|
+
humanTurnsSinceLastReminder < TURNS_BETWEEN_REMINDERS) {
|
|
192
|
+
return messages; // Throttled — skip reminder
|
|
193
|
+
}
|
|
194
|
+
// Determine full vs sparse
|
|
195
|
+
// Every 5th reminder is full; rest are sparse
|
|
196
|
+
const reminderNumber = planModeReminderCount + 1;
|
|
197
|
+
const isFull = reminderNumber % FULL_REMINDER_EVERY_N === 1;
|
|
198
|
+
if (isFull) {
|
|
199
|
+
messages.push({
|
|
200
|
+
role: "user",
|
|
201
|
+
content: buildPlanModeReminder(planFilePath, planExists, !!this.subagentType),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
messages.push({
|
|
206
|
+
role: "user",
|
|
207
|
+
content: buildPlanModeSparseReminder(planFilePath),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return messages;
|
|
211
|
+
}
|
|
119
212
|
setIsLoading(isLoading) {
|
|
120
213
|
this.isLoading = isLoading;
|
|
121
214
|
this.onLoadingChange?.(isLoading);
|
|
@@ -176,211 +269,254 @@ export class AIManager {
|
|
|
176
269
|
(usage.cache_creation_input_tokens || 0) >
|
|
177
270
|
this.getMaxInputTokens()) {
|
|
178
271
|
logger?.debug(`Token usage exceeded ${this.getMaxInputTokens()}, compacting messages...`);
|
|
179
|
-
// Check if messages need compaction
|
|
180
272
|
const messagesToCompact = this.messageManager.getMessages();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
273
|
+
if (messagesToCompact.length === 0)
|
|
274
|
+
return;
|
|
275
|
+
// Circuit breaker: skip compaction after 3 consecutive failures
|
|
276
|
+
if (this.consecutiveCompactionFailures >= 3) {
|
|
277
|
+
logger?.warn(`Skipping compaction: ${this.consecutiveCompactionFailures} consecutive failures`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
await this.compactConversation({
|
|
281
|
+
abortSignal: abortController.signal,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Manually compact the conversation history.
|
|
287
|
+
* Called by /compact slash command or auto-compaction trigger.
|
|
288
|
+
*/
|
|
289
|
+
async compactConversation(options = {}) {
|
|
290
|
+
const messagesToCompact = this.messageManager.getMessages();
|
|
291
|
+
if (messagesToCompact.length === 0) {
|
|
292
|
+
logger?.debug("No messages to compact");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Circuit breaker: skip if already compacting
|
|
296
|
+
if (this.isCompacting) {
|
|
297
|
+
logger?.warn("Compaction already in progress");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// 1. Run PreCompact hooks
|
|
301
|
+
let hookInstructions;
|
|
302
|
+
if (this.hookManager) {
|
|
303
|
+
try {
|
|
304
|
+
const preResult = await this.hookManager.executePreCompactHooks(this.messageManager.getSessionId(), this.messageManager.getTranscriptPath(), options.customInstructions);
|
|
305
|
+
hookInstructions = preResult.additionalInstructions;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
logger?.warn(`PreCompact hooks failed: ${error.message}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// 2. Merge custom instructions
|
|
312
|
+
const mergedInstructions = [options.customInstructions, hookInstructions]
|
|
313
|
+
.filter(Boolean)
|
|
314
|
+
.join("\n") || undefined;
|
|
315
|
+
// 3. Save session before compaction
|
|
316
|
+
await this.messageManager.saveSession();
|
|
317
|
+
this.setIsCompacting(true);
|
|
318
|
+
try {
|
|
319
|
+
const recentChatMessages = convertMessagesForAPI(messagesToCompact);
|
|
320
|
+
// 4. Call compactMessages with optional custom instructions
|
|
321
|
+
const compactResult = await aiService.compactMessages({
|
|
322
|
+
gatewayConfig: this.getGatewayConfig(),
|
|
323
|
+
modelConfig: this.getModelConfig(),
|
|
324
|
+
messages: recentChatMessages,
|
|
325
|
+
abortSignal: options.abortSignal,
|
|
326
|
+
model: this.getModelConfig().fastModel,
|
|
327
|
+
customInstructions: mergedInstructions,
|
|
328
|
+
});
|
|
329
|
+
// 5. Handle usage tracking
|
|
330
|
+
let compactUsage;
|
|
331
|
+
if (compactResult.usage) {
|
|
332
|
+
compactUsage = {
|
|
333
|
+
prompt_tokens: compactResult.usage.prompt_tokens,
|
|
334
|
+
completion_tokens: compactResult.usage.completion_tokens,
|
|
335
|
+
total_tokens: compactResult.usage.total_tokens,
|
|
336
|
+
model: this.getModelConfig().fastModel,
|
|
337
|
+
operation_type: "compact",
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
// 6. Build post-compact context restoration
|
|
341
|
+
const enhancedSummary = await this.buildPostCompactContext(compactResult.content);
|
|
342
|
+
// 7. Execute message reconstruction
|
|
343
|
+
this.messageManager.compactMessagesAndUpdateSession(enhancedSummary, compactUsage);
|
|
344
|
+
// 8. Track usage
|
|
345
|
+
if (compactUsage && this.callbacks?.onUsageAdded) {
|
|
346
|
+
this.callbacks.onUsageAdded(compactUsage);
|
|
347
|
+
}
|
|
348
|
+
this.consecutiveCompactionFailures = 0;
|
|
349
|
+
// 9. Log OTEL event
|
|
350
|
+
logOTelEvent("compaction", {
|
|
351
|
+
beforeTokens: String(messagesToCompact.length),
|
|
352
|
+
afterTokens: "1",
|
|
353
|
+
model: this.getModelConfig().fastModel,
|
|
354
|
+
}).catch(() => { });
|
|
355
|
+
// 10. Run SessionStart hooks (existing behavior)
|
|
356
|
+
if (this.hookManager) {
|
|
192
357
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Handle usage tracking for compaction operations
|
|
201
|
-
let compactUsage;
|
|
202
|
-
if (compactResult.usage) {
|
|
203
|
-
compactUsage = {
|
|
204
|
-
prompt_tokens: compactResult.usage.prompt_tokens,
|
|
205
|
-
completion_tokens: compactResult.usage.completion_tokens,
|
|
206
|
-
total_tokens: compactResult.usage.total_tokens,
|
|
207
|
-
model: this.getModelConfig().fastModel,
|
|
208
|
-
operation_type: "compact",
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
// Build post-compact context restoration
|
|
212
|
-
const POST_COMPACT_TOKEN_BUDGET = 50000;
|
|
213
|
-
const POST_COMPACT_MAX_TOKENS_PER_FILE = 5000;
|
|
214
|
-
const POST_COMPACT_MAX_FILES_TO_RESTORE = 5;
|
|
215
|
-
const contextParts = [];
|
|
216
|
-
// 1. File context restoration
|
|
217
|
-
const recentFiles = this.messageManager.getRecentFileReads(POST_COMPACT_MAX_FILES_TO_RESTORE, POST_COMPACT_MAX_TOKENS_PER_FILE);
|
|
218
|
-
let usedTokens = 0;
|
|
219
|
-
for (const file of recentFiles) {
|
|
220
|
-
const fileTokens = Math.ceil(file.content.length / 4);
|
|
221
|
-
if (usedTokens + fileTokens > POST_COMPACT_MAX_TOKENS_PER_FILE)
|
|
222
|
-
continue;
|
|
223
|
-
if (fileTokens > 0)
|
|
224
|
-
usedTokens += fileTokens;
|
|
225
|
-
contextParts.push(`\n\n## ${file.path}\n\`\`\`\n${file.content}\n\`\`\``);
|
|
226
|
-
if (contextParts.length >= POST_COMPACT_MAX_FILES_TO_RESTORE)
|
|
227
|
-
break;
|
|
228
|
-
if (usedTokens >= POST_COMPACT_TOKEN_BUDGET)
|
|
229
|
-
break;
|
|
358
|
+
const newSessionId = this.messageManager.getSessionId();
|
|
359
|
+
const sessionStartResult = await this.hookManager.executeSessionStartHooks("compact", newSessionId, this.messageManager.getTranscriptPath(), this.subagentType);
|
|
360
|
+
if (sessionStartResult.additionalContext) {
|
|
361
|
+
this.messageManager.addUserMessage({
|
|
362
|
+
content: `<system-reminder>\nSessionStart hook additional context: ${sessionStartResult.additionalContext}\n</system-reminder>`,
|
|
363
|
+
isMeta: true,
|
|
364
|
+
});
|
|
230
365
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const planFilePath = this.permissionManager?.getPlanFilePath();
|
|
237
|
-
if (planFilePath) {
|
|
238
|
-
let planExists = false;
|
|
239
|
-
try {
|
|
240
|
-
await fs.access(planFilePath);
|
|
241
|
-
planExists = true;
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
// Plan file doesn't exist yet
|
|
245
|
-
}
|
|
246
|
-
contextParts.push(`\n\n[Plan Mode]\nYou are in plan mode. Plan file: ${planFilePath} (exists: ${planExists})`);
|
|
247
|
-
}
|
|
366
|
+
if (sessionStartResult.initialUserMessage) {
|
|
367
|
+
this.messageManager.addUserMessage({
|
|
368
|
+
content: sessionStartResult.initialUserMessage,
|
|
369
|
+
isMeta: true,
|
|
370
|
+
});
|
|
248
371
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
logger?.warn(`SessionStart hooks on compact failed: ${error.message}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// 11. Run PostCompact hooks
|
|
378
|
+
if (this.hookManager) {
|
|
379
|
+
try {
|
|
380
|
+
await this.hookManager.executePostCompactHooks(this.messageManager.getSessionId(), this.messageManager.getTranscriptPath(), compactResult.content);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
logger?.warn(`PostCompact hooks failed: ${error.message}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
logger?.debug(`Successfully compacted ${messagesToCompact.length} messages`);
|
|
387
|
+
}
|
|
388
|
+
catch (compactError) {
|
|
389
|
+
this.consecutiveCompactionFailures++;
|
|
390
|
+
logger?.error(`Failed to compact messages (${this.consecutiveCompactionFailures} consecutive):`, compactError);
|
|
391
|
+
this.messageManager.addErrorBlock(`Failed to compact conversation history: ${compactError instanceof Error ? compactError.message : String(compactError)}. You may encounter context limit issues.`);
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
this.setIsCompacting(false);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Build post-compact context restoration content.
|
|
399
|
+
* Restores file reads, working directory, plan mode, skills, and background tasks.
|
|
400
|
+
*/
|
|
401
|
+
async buildPostCompactContext(summary) {
|
|
402
|
+
const POST_COMPACT_TOKEN_BUDGET = 50000;
|
|
403
|
+
const POST_COMPACT_MAX_TOKENS_PER_FILE = 5000;
|
|
404
|
+
const POST_COMPACT_MAX_FILES_TO_RESTORE = 5;
|
|
405
|
+
const contextParts = [];
|
|
406
|
+
// 1. File context restoration
|
|
407
|
+
const recentFiles = this.messageManager.getRecentFileReads(POST_COMPACT_MAX_FILES_TO_RESTORE, POST_COMPACT_MAX_TOKENS_PER_FILE);
|
|
408
|
+
let usedTokens = 0;
|
|
409
|
+
for (const file of recentFiles) {
|
|
410
|
+
const fileTokens = Math.ceil(file.content.length / 4);
|
|
411
|
+
if (usedTokens + fileTokens > POST_COMPACT_MAX_TOKENS_PER_FILE)
|
|
412
|
+
continue;
|
|
413
|
+
if (fileTokens > 0)
|
|
414
|
+
usedTokens += fileTokens;
|
|
415
|
+
contextParts.push(`\n\n## ${file.path}\n\`\`\`\n${file.content}\n\`\`\``);
|
|
416
|
+
if (contextParts.length >= POST_COMPACT_MAX_FILES_TO_RESTORE)
|
|
417
|
+
break;
|
|
418
|
+
if (usedTokens >= POST_COMPACT_TOKEN_BUDGET)
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
// 2. Working directory
|
|
422
|
+
contextParts.push(`\n\n[Working Directory]\nCurrent working directory: ${this.getWorkdir()}`);
|
|
423
|
+
// 3. Plan mode context
|
|
424
|
+
const currentMode = this.permissionManager?.getCurrentEffectiveMode(this.getModelConfig().permissionMode);
|
|
425
|
+
if (currentMode === "plan") {
|
|
426
|
+
const planFilePath = this.permissionManager?.getPlanFilePath();
|
|
427
|
+
if (planFilePath) {
|
|
428
|
+
let planExists = false;
|
|
429
|
+
try {
|
|
430
|
+
await fs.access(planFilePath);
|
|
431
|
+
planExists = true;
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Plan file doesn't exist yet
|
|
435
|
+
}
|
|
436
|
+
contextParts.push(`\n\n${buildPlanModeReminder(planFilePath, planExists, !!this.subagentType)}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// 4. Invoked skills context (with token budget, matching Claude Code)
|
|
440
|
+
const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25000;
|
|
441
|
+
const POST_COMPACT_MAX_TOKENS_PER_SKILL = 5000;
|
|
442
|
+
const invokedSkillNames = this.messageManager.getInvokedSkillNames(10);
|
|
443
|
+
if (invokedSkillNames.length > 0 && this.skillManager) {
|
|
444
|
+
const invokedSkillParts = [];
|
|
445
|
+
let skillsUsedTokens = 0;
|
|
446
|
+
for (const skillName of invokedSkillNames) {
|
|
447
|
+
try {
|
|
448
|
+
const skill = await this.skillManager.loadSkill(skillName);
|
|
449
|
+
if (!skill)
|
|
450
|
+
continue;
|
|
451
|
+
const contentMatch = skill.content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
452
|
+
let skillContent = contentMatch
|
|
453
|
+
? contentMatch[1].trim()
|
|
454
|
+
: skill.content;
|
|
455
|
+
const maxSkillChars = POST_COMPACT_MAX_TOKENS_PER_SKILL * 4;
|
|
456
|
+
if (skillContent.length > maxSkillChars) {
|
|
457
|
+
skillContent =
|
|
458
|
+
skillContent.slice(0, maxSkillChars) + "\n\n...[truncated]...";
|
|
287
459
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
if (a.outputPath) {
|
|
321
|
-
parts.push(`Read the output file to retrieve the result: ${a.outputPath}.`);
|
|
322
|
-
}
|
|
323
|
-
agentParts.push(parts.join(" "));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (agentParts.length > 0) {
|
|
327
|
-
contextParts.push(`\n\n[Background Tasks]\n${agentParts.join("\n")}`);
|
|
328
|
-
}
|
|
460
|
+
const skillTokens = Math.ceil(skillContent.length / 4);
|
|
461
|
+
if (skillsUsedTokens + skillTokens > POST_COMPACT_SKILLS_TOKEN_BUDGET)
|
|
462
|
+
break;
|
|
463
|
+
skillsUsedTokens += skillTokens;
|
|
464
|
+
invokedSkillParts.push(`\n\n## ${skill.name}\n${skill.description ? `*${skill.description}*\n\n` : ""}\`\`\`\n${skillContent}\n\`\`\``);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// Skip skills that can't be loaded
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (invokedSkillParts.length > 0) {
|
|
471
|
+
contextParts.push(`\n\n[Invoked Skills]\n${invokedSkillParts.join("")}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// 5. Background subagent status (shell tasks excluded, matching Claude Code's createAsyncAgentAttachmentsIfNeeded)
|
|
475
|
+
const agents = this.backgroundTaskManager
|
|
476
|
+
?.getAllTasks()
|
|
477
|
+
.filter((a) => a.type === "subagent") || [];
|
|
478
|
+
if (agents.length > 0) {
|
|
479
|
+
const agentParts = [];
|
|
480
|
+
for (const a of agents) {
|
|
481
|
+
if (a.status === "killed") {
|
|
482
|
+
agentParts.push(`Task "${a.description}" (${a.id}) was stopped by the user.`);
|
|
483
|
+
}
|
|
484
|
+
else if (a.status === "running") {
|
|
485
|
+
const parts = [
|
|
486
|
+
`Background agent "${a.description}" (${a.id}) is still running.`,
|
|
487
|
+
`Do NOT spawn a duplicate. You will be notified when it completes.`,
|
|
488
|
+
];
|
|
489
|
+
if (a.outputPath) {
|
|
490
|
+
parts.push(`You can read partial output at ${a.outputPath}.`);
|
|
329
491
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
492
|
+
agentParts.push(parts.join(" "));
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
// completed or failed
|
|
496
|
+
const parts = [
|
|
497
|
+
`Task ${a.id} (status: ${a.status}) (description: ${a.description}).`,
|
|
498
|
+
];
|
|
499
|
+
const deltaText = a.status === "failed" ? a.stderr : a.stdout;
|
|
500
|
+
if (deltaText && deltaText.length > 0) {
|
|
501
|
+
const summary = deltaText.length > 500
|
|
502
|
+
? deltaText.slice(0, 500) + "..."
|
|
503
|
+
: deltaText;
|
|
504
|
+
parts.push(`Delta: ${summary}`);
|
|
340
505
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Log compaction event
|
|
344
|
-
logOTelEvent("compaction", {
|
|
345
|
-
beforeTokens: String(messagesToCompact.length),
|
|
346
|
-
afterTokens: "1",
|
|
347
|
-
model: this.getModelConfig().fastModel,
|
|
348
|
-
}).catch(() => { });
|
|
349
|
-
// Run SessionStart hooks after compaction to restore context
|
|
350
|
-
if (this.hookManager) {
|
|
351
|
-
try {
|
|
352
|
-
const newSessionId = this.messageManager.getSessionId();
|
|
353
|
-
const sessionStartResult = await this.hookManager.executeSessionStartHooks("compact", newSessionId, this.messageManager.getTranscriptPath(), this.subagentType);
|
|
354
|
-
// Inject additionalContext as a meta user message
|
|
355
|
-
if (sessionStartResult.additionalContext) {
|
|
356
|
-
this.messageManager.addUserMessage({
|
|
357
|
-
content: `<system-reminder>\nSessionStart hook additional context: ${sessionStartResult.additionalContext}\n</system-reminder>`,
|
|
358
|
-
isMeta: true,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
// Inject initialUserMessage as a meta user message
|
|
362
|
-
if (sessionStartResult.initialUserMessage) {
|
|
363
|
-
this.messageManager.addUserMessage({
|
|
364
|
-
content: sessionStartResult.initialUserMessage,
|
|
365
|
-
isMeta: true,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
logger?.warn(`SessionStart hooks on compact failed: ${error.message}`);
|
|
371
|
-
}
|
|
506
|
+
if (a.outputPath) {
|
|
507
|
+
parts.push(`Read the output file to retrieve the result: ${a.outputPath}.`);
|
|
372
508
|
}
|
|
373
|
-
|
|
374
|
-
catch (compactError) {
|
|
375
|
-
this.consecutiveCompactionFailures++;
|
|
376
|
-
logger?.error(`Failed to compact messages (${this.consecutiveCompactionFailures} consecutive):`, compactError);
|
|
377
|
-
this.messageManager.addErrorBlock(`Failed to compact conversation history: ${compactError instanceof Error ? compactError.message : String(compactError)}. You may encounter context limit issues.`);
|
|
378
|
-
}
|
|
379
|
-
finally {
|
|
380
|
-
this.setIsCompacting(false);
|
|
509
|
+
agentParts.push(parts.join(" "));
|
|
381
510
|
}
|
|
382
511
|
}
|
|
512
|
+
if (agentParts.length > 0) {
|
|
513
|
+
contextParts.push(`\n\n[Background Tasks]\n${agentParts.join("\n")}`);
|
|
514
|
+
}
|
|
383
515
|
}
|
|
516
|
+
return (summary +
|
|
517
|
+
(contextParts.length > 0
|
|
518
|
+
? `\n\n[Context Restoration]` + contextParts.join("")
|
|
519
|
+
: ""));
|
|
384
520
|
}
|
|
385
521
|
getIsCompacting() {
|
|
386
522
|
return this.isCompacting;
|
|
@@ -477,20 +613,11 @@ export class AIManager {
|
|
|
477
613
|
const filteredToolPlugins = this.toolManager
|
|
478
614
|
.getTools()
|
|
479
615
|
.filter((t) => toolNames.has(t.name));
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
try {
|
|
486
|
-
await fs.access(planFilePath);
|
|
487
|
-
planExists = true;
|
|
488
|
-
}
|
|
489
|
-
catch {
|
|
490
|
-
planExists = false;
|
|
491
|
-
}
|
|
492
|
-
planModeOptions = { planFilePath, planExists };
|
|
493
|
-
}
|
|
616
|
+
// Inject plan mode system-reminder messages (not system prompt)
|
|
617
|
+
// This preserves prompt caching by keeping the system prompt constant
|
|
618
|
+
const planModeMessages = this.buildPlanModeMessages(currentMode);
|
|
619
|
+
if (planModeMessages.length > 0) {
|
|
620
|
+
recentMessages.push(...planModeMessages);
|
|
494
621
|
}
|
|
495
622
|
let autoMemoryOptions;
|
|
496
623
|
if (this.getAutoMemoryEnabled()) {
|
|
@@ -513,7 +640,6 @@ export class AIManager {
|
|
|
513
640
|
memory: combinedMemory,
|
|
514
641
|
language: this.getLanguage(),
|
|
515
642
|
isSubagent: !!this.subagentType,
|
|
516
|
-
planMode: planModeOptions,
|
|
517
643
|
autoMemory: autoMemoryOptions,
|
|
518
644
|
permissionMode: currentMode,
|
|
519
645
|
}), // Pass custom system prompt
|
|
@@ -12,6 +12,9 @@ import { Container } from "../utils/container.js";
|
|
|
12
12
|
export declare class HookManager {
|
|
13
13
|
private container;
|
|
14
14
|
private configuration;
|
|
15
|
+
private programmaticHooks;
|
|
16
|
+
private pluginHooks;
|
|
17
|
+
private waveConfigHooks;
|
|
15
18
|
private readonly matcher;
|
|
16
19
|
private readonly workdir;
|
|
17
20
|
constructor(container: Container, workdir: string, matcher?: HookMatcher);
|
|
@@ -24,6 +27,12 @@ export declare class HookManager {
|
|
|
24
27
|
* Configuration loading is now handled by ConfigurationService
|
|
25
28
|
*/
|
|
26
29
|
loadConfigurationFromWaveConfig(waveConfig: WaveConfiguration | null): void;
|
|
30
|
+
/**
|
|
31
|
+
* Rebuild the full configuration from all sources:
|
|
32
|
+
* programmatic (AgentOptions.hooks) + plugin + wave config (settings.json)
|
|
33
|
+
* Order determines precedence on conflict (later sources append).
|
|
34
|
+
*/
|
|
35
|
+
private rebuildConfiguration;
|
|
27
36
|
/**
|
|
28
37
|
* Execute hooks for a specific event
|
|
29
38
|
*/
|
|
@@ -120,5 +129,18 @@ export declare class HookManager {
|
|
|
120
129
|
* No stdout processing needed (SessionEnd hooks are fire-and-forget cleanup).
|
|
121
130
|
*/
|
|
122
131
|
executeSessionEndHooks(source: SessionEndSource, sessionId: string, transcriptPath: string): Promise<HookExecutionResult[]>;
|
|
132
|
+
/**
|
|
133
|
+
* Execute PreCompact hooks before compaction.
|
|
134
|
+
* Returns custom instructions from hook stdout.
|
|
135
|
+
*/
|
|
136
|
+
executePreCompactHooks(sessionId: string, transcriptPath: string, customInstructions?: string): Promise<{
|
|
137
|
+
results: HookExecutionResult[];
|
|
138
|
+
additionalInstructions?: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Execute PostCompact hooks after compaction.
|
|
142
|
+
* Receives the compact summary text.
|
|
143
|
+
*/
|
|
144
|
+
executePostCompactHooks(sessionId: string, transcriptPath: string, compactSummary: string): Promise<HookExecutionResult[]>;
|
|
123
145
|
}
|
|
124
146
|
//# sourceMappingURL=hookManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hookManager.d.ts","sourceRoot":"","sources":["../../src/managers/hookManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EAItB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIlD,qBAAa,WAAW;
|
|
1
|
+
{"version":3,"file":"hookManager.d.ts","sourceRoot":"","sources":["../../src/managers/hookManager.ts"],"names":[],"mappings":"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"}
|