wave-agent-sdk 0.12.6 → 0.12.8
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 +17 -2
- package/builtin/skills/settings/SKILL.md +1 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +11 -0
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +91 -43
- package/dist/managers/subagentManager.d.ts +9 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +18 -0
- package/dist/prompts/autoMemoryExtraction.d.ts +5 -0
- package/dist/prompts/autoMemoryExtraction.d.ts.map +1 -0
- package/dist/prompts/autoMemoryExtraction.js +136 -0
- package/dist/services/autoMemoryService.d.ts +24 -0
- package/dist/services/autoMemoryService.d.ts.map +1 -0
- package/dist/services/autoMemoryService.js +134 -0
- package/dist/services/configurationService.d.ts +6 -0
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +33 -0
- package/dist/types/configuration.d.ts +2 -0
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/mcp.d.ts +3 -1
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +3 -0
- package/package.json +1 -1
- package/src/managers/aiManager.ts +15 -0
- package/src/managers/mcpManager.ts +116 -57
- package/src/managers/subagentManager.ts +36 -0
- package/src/prompts/autoMemoryExtraction.ts +146 -0
- package/src/services/autoMemoryService.ts +178 -0
- package/src/services/configurationService.ts +43 -0
- package/src/types/configuration.ts +2 -0
- package/src/types/mcp.ts +3 -1
- package/src/utils/containerSetup.ts +4 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import { Container } from "../utils/container.js";
|
|
4
|
+
import { MessageManager } from "../managers/messageManager.js";
|
|
5
|
+
import { SubagentManager } from "../managers/subagentManager.js";
|
|
6
|
+
import { MemoryService } from "./memory.js";
|
|
7
|
+
import { ConfigurationService } from "./configurationService.js";
|
|
8
|
+
import { logger } from "../utils/globalLogger.js";
|
|
9
|
+
import { isPathInside } from "../utils/pathSafety.js";
|
|
10
|
+
import { buildAutoMemoryExtractionPrompt } from "../prompts/autoMemoryExtraction.js";
|
|
11
|
+
import type { Message } from "../types/index.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service responsible for managing the auto-memory background agent lifecycle.
|
|
15
|
+
* Extracts and updates persistent project-level memory from conversation history.
|
|
16
|
+
*/
|
|
17
|
+
export class AutoMemoryService {
|
|
18
|
+
private lastMemoryMessageId: string | null = null;
|
|
19
|
+
private turnsSinceLastExtraction: number = 0;
|
|
20
|
+
|
|
21
|
+
constructor(private container: Container) {}
|
|
22
|
+
|
|
23
|
+
private get messageManager(): MessageManager {
|
|
24
|
+
return this.container.get<MessageManager>("MessageManager")!;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private get subagentManager(): SubagentManager {
|
|
28
|
+
return this.container.get<SubagentManager>("SubagentManager")!;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private get memoryService(): MemoryService {
|
|
32
|
+
return this.container.get<MemoryService>("MemoryService")!;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private get configurationService(): ConfigurationService {
|
|
36
|
+
return this.container.get<ConfigurationService>("ConfigurationService")!;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Called at the end of each conversation turn to trigger auto-memory extraction if needed.
|
|
41
|
+
*/
|
|
42
|
+
async onTurnEnd(workdir: string): Promise<void> {
|
|
43
|
+
if (!this.configurationService.resolveAutoMemoryEnabled()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.turnsSinceLastExtraction++;
|
|
48
|
+
|
|
49
|
+
const messages = this.messageManager.getMessages();
|
|
50
|
+
if (messages.length === 0) return;
|
|
51
|
+
|
|
52
|
+
// 1. Check if we should run based on throttling
|
|
53
|
+
const frequency = this.configurationService.resolveAutoMemoryFrequency();
|
|
54
|
+
if (this.turnsSinceLastExtraction < frequency) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Check for mutual exclusion: skip if main agent manually updated memory in this turn
|
|
59
|
+
const memoryDir = this.memoryService.getAutoMemoryDirectory(workdir);
|
|
60
|
+
|
|
61
|
+
// Find messages since last extraction
|
|
62
|
+
let startIndex = 0;
|
|
63
|
+
if (this.lastMemoryMessageId) {
|
|
64
|
+
startIndex =
|
|
65
|
+
messages.findIndex((m) => m.id === this.lastMemoryMessageId) + 1;
|
|
66
|
+
if (startIndex <= 0) startIndex = 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const recentMessages = messages.slice(startIndex);
|
|
70
|
+
const hasManualMemoryWrite = recentMessages.some(
|
|
71
|
+
(m) =>
|
|
72
|
+
m.role === "assistant" &&
|
|
73
|
+
m.blocks.some((b) => {
|
|
74
|
+
if (b.type === "tool" && (b.name === "Write" || b.name === "Edit")) {
|
|
75
|
+
try {
|
|
76
|
+
const params = b.parameters ? JSON.parse(b.parameters) : null;
|
|
77
|
+
const filePath = params?.file_path || params?.path;
|
|
78
|
+
if (filePath) {
|
|
79
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
80
|
+
? filePath
|
|
81
|
+
: path.resolve(workdir, filePath);
|
|
82
|
+
return isPathInside(absolutePath, memoryDir);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (hasManualMemoryWrite) {
|
|
93
|
+
logger.debug(
|
|
94
|
+
"Skipping auto-memory extraction: manual memory write detected in this turn.",
|
|
95
|
+
);
|
|
96
|
+
this.lastMemoryMessageId = messages[messages.length - 1].id || null;
|
|
97
|
+
this.turnsSinceLastExtraction = 0;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 3. Trigger background extraction using a forked subagent
|
|
102
|
+
try {
|
|
103
|
+
await this.runExtraction(workdir, messages);
|
|
104
|
+
this.lastMemoryMessageId = messages[messages.length - 1].id || null;
|
|
105
|
+
this.turnsSinceLastExtraction = 0;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logger.error("Auto-memory extraction failed to trigger:", error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize and execute the background extraction subagent.
|
|
113
|
+
*/
|
|
114
|
+
private async runExtraction(
|
|
115
|
+
workdir: string,
|
|
116
|
+
messages: Message[],
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const memoryDir = this.memoryService.getAutoMemoryDirectory(workdir);
|
|
119
|
+
|
|
120
|
+
// Ensure memory directory exists before starting
|
|
121
|
+
await this.memoryService.ensureAutoMemoryDirectory(workdir);
|
|
122
|
+
|
|
123
|
+
// Prepare manifest of existing memory files
|
|
124
|
+
let existingMemoriesManifest = "";
|
|
125
|
+
try {
|
|
126
|
+
const files = await fs.readdir(memoryDir);
|
|
127
|
+
existingMemoriesManifest = files
|
|
128
|
+
.filter((f) => f.endsWith(".md"))
|
|
129
|
+
.map((f) => `- ${f}`)
|
|
130
|
+
.join("\n");
|
|
131
|
+
} catch {
|
|
132
|
+
// Ignore if directory doesn't exist yet
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Calculate how many new messages to analyze
|
|
136
|
+
let newMessageCount = messages.length;
|
|
137
|
+
if (this.lastMemoryMessageId) {
|
|
138
|
+
const lastIndex = messages.findIndex(
|
|
139
|
+
(m) => m.id === this.lastMemoryMessageId,
|
|
140
|
+
);
|
|
141
|
+
if (lastIndex !== -1) {
|
|
142
|
+
newMessageCount = messages.length - 1 - lastIndex;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fork the general-purpose agent with restricted tool access
|
|
147
|
+
const instance = await this.subagentManager.forkAgent(
|
|
148
|
+
"general-purpose",
|
|
149
|
+
messages,
|
|
150
|
+
{
|
|
151
|
+
description: "Auto-memory extraction background agent",
|
|
152
|
+
allowedTools: [
|
|
153
|
+
"Read",
|
|
154
|
+
"Glob",
|
|
155
|
+
"Grep",
|
|
156
|
+
`Write(${memoryDir}/**/*)`,
|
|
157
|
+
`Edit(${memoryDir}/**/*)`,
|
|
158
|
+
],
|
|
159
|
+
model: "fastModel", // Use fast model for background tasks to reduce latency and cost
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const prompt = buildAutoMemoryExtractionPrompt(
|
|
164
|
+
newMessageCount,
|
|
165
|
+
existingMemoriesManifest,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Execute in background so it doesn't block the main conversation flow
|
|
169
|
+
await this.subagentManager.executeAgent(
|
|
170
|
+
instance,
|
|
171
|
+
`${prompt}\n\nThe memory directory for this project is: ${memoryDir}`,
|
|
172
|
+
undefined,
|
|
173
|
+
true, // runInBackground
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
logger.debug("Auto-memory extraction started in background.");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -275,6 +275,18 @@ export class ConfigurationService {
|
|
|
275
275
|
result.errors.push("autoMemoryEnabled configuration must be a boolean");
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
// Validate autoMemoryFrequency if present
|
|
279
|
+
if (
|
|
280
|
+
config.autoMemoryFrequency !== undefined &&
|
|
281
|
+
(typeof config.autoMemoryFrequency !== "number" ||
|
|
282
|
+
config.autoMemoryFrequency <= 0)
|
|
283
|
+
) {
|
|
284
|
+
result.isValid = false;
|
|
285
|
+
result.errors.push(
|
|
286
|
+
"autoMemoryFrequency configuration must be a positive number",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
278
290
|
// Validate models if present
|
|
279
291
|
if (config.models !== undefined) {
|
|
280
292
|
if (typeof config.models !== "object" || config.models === null) {
|
|
@@ -595,6 +607,32 @@ export class ConfigurationService {
|
|
|
595
607
|
return true;
|
|
596
608
|
}
|
|
597
609
|
|
|
610
|
+
/**
|
|
611
|
+
* Resolves auto-memory extraction frequency with fallbacks
|
|
612
|
+
* Resolution priority: settings.json > WAVE_AUTO_MEMORY_FREQUENCY > default (1)
|
|
613
|
+
* @returns Resolved auto-memory extraction frequency (turns)
|
|
614
|
+
*/
|
|
615
|
+
resolveAutoMemoryFrequency(): number {
|
|
616
|
+
// 1. settings.json (merged)
|
|
617
|
+
if (this.currentConfiguration?.autoMemoryFrequency !== undefined) {
|
|
618
|
+
return this.currentConfiguration.autoMemoryFrequency;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// 2. WAVE_AUTO_MEMORY_FREQUENCY environment variable
|
|
622
|
+
const envFrequency =
|
|
623
|
+
this.env.WAVE_AUTO_MEMORY_FREQUENCY ||
|
|
624
|
+
process.env.WAVE_AUTO_MEMORY_FREQUENCY;
|
|
625
|
+
if (envFrequency) {
|
|
626
|
+
const parsed = parseInt(envFrequency, 10);
|
|
627
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
628
|
+
return parsed;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// 3. Default (1)
|
|
633
|
+
return 1;
|
|
634
|
+
}
|
|
635
|
+
|
|
598
636
|
/**
|
|
599
637
|
* Resolves max output tokens with fallbacks
|
|
600
638
|
* Resolution priority: override > options > env (from settings.json) > process.env > default
|
|
@@ -1081,6 +1119,11 @@ export function loadMergedWaveConfig(
|
|
|
1081
1119
|
mergedConfig.autoMemoryEnabled = config.autoMemoryEnabled;
|
|
1082
1120
|
}
|
|
1083
1121
|
|
|
1122
|
+
// Merge autoMemoryFrequency (last one wins)
|
|
1123
|
+
if (config.autoMemoryFrequency !== undefined) {
|
|
1124
|
+
mergedConfig.autoMemoryFrequency = config.autoMemoryFrequency;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1084
1127
|
// Merge models
|
|
1085
1128
|
if (config.models) {
|
|
1086
1129
|
if (!mergedConfig.models) mergedConfig.models = {};
|
|
@@ -35,6 +35,8 @@ export interface WaveConfiguration {
|
|
|
35
35
|
language?: string;
|
|
36
36
|
/** Whether auto-memory is enabled */
|
|
37
37
|
autoMemoryEnabled?: boolean;
|
|
38
|
+
/** Frequency of auto-memory extraction turns */
|
|
39
|
+
autoMemoryFrequency?: number;
|
|
38
40
|
/** Model-specific configuration overrides */
|
|
39
41
|
models?: Record<string, Partial<ModelConfig>>;
|
|
40
42
|
}
|
package/src/types/mcp.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { LiveConfigManager } from "../managers/liveConfigManager.js";
|
|
|
22
22
|
import { ConfigurationService } from "../services/configurationService.js";
|
|
23
23
|
import { ReversionService } from "../services/reversionService.js";
|
|
24
24
|
import { MemoryService } from "../services/memory.js";
|
|
25
|
+
import { AutoMemoryService } from "../services/autoMemoryService.js";
|
|
25
26
|
import { getGitMainRepoRoot } from "./gitUtils.js";
|
|
26
27
|
import type { AgentOptions } from "../types/index.js";
|
|
27
28
|
import type {
|
|
@@ -85,6 +86,9 @@ export function setupAgentContainer(
|
|
|
85
86
|
const memoryService = new MemoryService(container);
|
|
86
87
|
container.register("MemoryService", memoryService);
|
|
87
88
|
|
|
89
|
+
const autoMemoryService = new AutoMemoryService(container);
|
|
90
|
+
container.register("AutoMemoryService", autoMemoryService);
|
|
91
|
+
|
|
88
92
|
const memoryRuleManager = new MemoryRuleManager(container, { workdir });
|
|
89
93
|
container.register("MemoryRuleManager", memoryRuleManager);
|
|
90
94
|
|