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.
Files changed (34) hide show
  1. package/builtin/skills/settings/MCP.md +17 -2
  2. package/builtin/skills/settings/SKILL.md +1 -0
  3. package/dist/managers/aiManager.d.ts.map +1 -1
  4. package/dist/managers/aiManager.js +11 -0
  5. package/dist/managers/mcpManager.d.ts.map +1 -1
  6. package/dist/managers/mcpManager.js +91 -43
  7. package/dist/managers/subagentManager.d.ts +9 -0
  8. package/dist/managers/subagentManager.d.ts.map +1 -1
  9. package/dist/managers/subagentManager.js +18 -0
  10. package/dist/prompts/autoMemoryExtraction.d.ts +5 -0
  11. package/dist/prompts/autoMemoryExtraction.d.ts.map +1 -0
  12. package/dist/prompts/autoMemoryExtraction.js +136 -0
  13. package/dist/services/autoMemoryService.d.ts +24 -0
  14. package/dist/services/autoMemoryService.d.ts.map +1 -0
  15. package/dist/services/autoMemoryService.js +134 -0
  16. package/dist/services/configurationService.d.ts +6 -0
  17. package/dist/services/configurationService.d.ts.map +1 -1
  18. package/dist/services/configurationService.js +33 -0
  19. package/dist/types/configuration.d.ts +2 -0
  20. package/dist/types/configuration.d.ts.map +1 -1
  21. package/dist/types/mcp.d.ts +3 -1
  22. package/dist/types/mcp.d.ts.map +1 -1
  23. package/dist/utils/containerSetup.d.ts.map +1 -1
  24. package/dist/utils/containerSetup.js +3 -0
  25. package/package.json +1 -1
  26. package/src/managers/aiManager.ts +15 -0
  27. package/src/managers/mcpManager.ts +116 -57
  28. package/src/managers/subagentManager.ts +36 -0
  29. package/src/prompts/autoMemoryExtraction.ts +146 -0
  30. package/src/services/autoMemoryService.ts +178 -0
  31. package/src/services/configurationService.ts +43 -0
  32. package/src/types/configuration.ts +2 -0
  33. package/src/types/mcp.ts +3 -1
  34. 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
@@ -4,9 +4,11 @@
4
4
  */
5
5
 
6
6
  export interface McpServerConfig {
7
- command: string;
7
+ command?: string;
8
8
  args?: string[];
9
9
  env?: Record<string, string>;
10
+ url?: string;
11
+ headers?: Record<string, string>;
10
12
  }
11
13
 
12
14
  export interface McpConfig {
@@ -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