wave-agent-sdk 0.8.4 → 0.9.1

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 (68) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +5 -8
  3. package/dist/managers/aiManager.d.ts +6 -10
  4. package/dist/managers/aiManager.d.ts.map +1 -1
  5. package/dist/managers/aiManager.js +38 -13
  6. package/dist/managers/hookManager.d.ts.map +1 -1
  7. package/dist/managers/hookManager.js +15 -1
  8. package/dist/managers/messageManager.d.ts +1 -0
  9. package/dist/managers/messageManager.d.ts.map +1 -1
  10. package/dist/managers/messageManager.js +8 -2
  11. package/dist/managers/subagentManager.d.ts +2 -11
  12. package/dist/managers/subagentManager.d.ts.map +1 -1
  13. package/dist/managers/subagentManager.js +4 -53
  14. package/dist/prompts/autoMemory.d.ts +2 -0
  15. package/dist/prompts/autoMemory.d.ts.map +1 -0
  16. package/dist/prompts/autoMemory.js +33 -0
  17. package/dist/prompts/index.d.ts +4 -0
  18. package/dist/prompts/index.d.ts.map +1 -1
  19. package/dist/prompts/index.js +7 -0
  20. package/dist/services/aiService.js +6 -7
  21. package/dist/services/configurationService.d.ts +28 -16
  22. package/dist/services/configurationService.d.ts.map +1 -1
  23. package/dist/services/configurationService.js +87 -28
  24. package/dist/services/hook.js +2 -2
  25. package/dist/services/initializationService.d.ts.map +1 -1
  26. package/dist/services/initializationService.js +20 -9
  27. package/dist/services/memory.d.ts +22 -4
  28. package/dist/services/memory.d.ts.map +1 -1
  29. package/dist/services/memory.js +132 -73
  30. package/dist/types/configuration.d.ts +2 -0
  31. package/dist/types/configuration.d.ts.map +1 -1
  32. package/dist/types/history.d.ts +2 -0
  33. package/dist/types/history.d.ts.map +1 -1
  34. package/dist/types/hooks.d.ts +2 -0
  35. package/dist/types/hooks.d.ts.map +1 -1
  36. package/dist/types/hooks.js +17 -7
  37. package/dist/utils/containerSetup.d.ts +0 -5
  38. package/dist/utils/containerSetup.d.ts.map +1 -1
  39. package/dist/utils/containerSetup.js +11 -24
  40. package/dist/utils/fileSearch.d.ts +1 -6
  41. package/dist/utils/fileSearch.d.ts.map +1 -1
  42. package/dist/utils/fileSearch.js +104 -75
  43. package/dist/utils/gitUtils.d.ts +6 -0
  44. package/dist/utils/gitUtils.d.ts.map +1 -1
  45. package/dist/utils/gitUtils.js +18 -0
  46. package/dist/utils/promptHistory.d.ts +3 -3
  47. package/dist/utils/promptHistory.d.ts.map +1 -1
  48. package/dist/utils/promptHistory.js +12 -6
  49. package/package.json +2 -1
  50. package/src/agent.ts +7 -18
  51. package/src/managers/aiManager.ts +73 -28
  52. package/src/managers/hookManager.ts +22 -1
  53. package/src/managers/messageManager.ts +12 -2
  54. package/src/managers/subagentManager.ts +7 -69
  55. package/src/prompts/autoMemory.ts +33 -0
  56. package/src/prompts/index.ts +12 -0
  57. package/src/services/aiService.ts +8 -8
  58. package/src/services/configurationService.ts +100 -28
  59. package/src/services/hook.ts +2 -2
  60. package/src/services/initializationService.ts +24 -12
  61. package/src/services/memory.ts +144 -82
  62. package/src/types/configuration.ts +2 -0
  63. package/src/types/history.ts +2 -0
  64. package/src/types/hooks.ts +25 -9
  65. package/src/utils/containerSetup.ts +11 -33
  66. package/src/utils/fileSearch.ts +112 -80
  67. package/src/utils/gitUtils.ts +18 -0
  68. package/src/utils/promptHistory.ts +20 -6
@@ -10,6 +10,12 @@ export declare function isGitRepository(dirPath: string): string;
10
10
  * @returns Repository root path
11
11
  */
12
12
  export declare function getGitRepoRoot(cwd: string): string;
13
+ /**
14
+ * Get the common directory of the git repository (handles worktrees)
15
+ * @param cwd Working directory
16
+ * @returns Repository common directory path
17
+ */
18
+ export declare function getGitCommonDir(cwd: string): string;
13
19
  /**
14
20
  * Get the default remote branch (e.g., origin/main)
15
21
  * @param cwd Working directory
@@ -1 +1 @@
1
- {"version":3,"file":"gitUtils.d.ts","sourceRoot":"","sources":["../../src/utils/gitUtils.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAevD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2D1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAYvE"}
1
+ {"version":3,"file":"gitUtils.d.ts","sourceRoot":"","sources":["../../src/utils/gitUtils.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAevD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2D1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAYvE"}
@@ -40,6 +40,24 @@ export function getGitRepoRoot(cwd) {
40
40
  return cwd;
41
41
  }
42
42
  }
43
+ /**
44
+ * Get the common directory of the git repository (handles worktrees)
45
+ * @param cwd Working directory
46
+ * @returns Repository common directory path
47
+ */
48
+ export function getGitCommonDir(cwd) {
49
+ try {
50
+ const commonDir = execSync("git rev-parse --git-common-dir", {
51
+ cwd,
52
+ encoding: "utf8",
53
+ stdio: ["ignore", "pipe", "ignore"],
54
+ }).trim();
55
+ return path.resolve(cwd, commonDir);
56
+ }
57
+ catch {
58
+ return getGitRepoRoot(cwd);
59
+ }
60
+ }
43
61
  /**
44
62
  * Get the default remote branch (e.g., origin/main)
45
63
  * @param cwd Working directory
@@ -3,7 +3,7 @@ export declare class PromptHistoryManager {
3
3
  /**
4
4
  * Add a new prompt to history
5
5
  */
6
- static addEntry(prompt: string): Promise<void>;
6
+ static addEntry(prompt: string, sessionId?: string, longTextMap?: Record<string, string>): Promise<void>;
7
7
  /**
8
8
  * Trim history file to MAX_HISTORY_ENTRIES
9
9
  */
@@ -11,10 +11,10 @@ export declare class PromptHistoryManager {
11
11
  /**
12
12
  * Get all history entries
13
13
  */
14
- static getHistory(): Promise<PromptEntry[]>;
14
+ static getHistory(sessionId?: string): Promise<PromptEntry[]>;
15
15
  /**
16
16
  * Search history by query
17
17
  */
18
- static searchHistory(query: string): Promise<PromptEntry[]>;
18
+ static searchHistory(query: string, sessionId?: string): Promise<PromptEntry[]>;
19
19
  }
20
20
  //# sourceMappingURL=promptHistory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"promptHistory.d.ts","sourceRoot":"","sources":["../../src/utils/promptHistory.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBlD,qBAAa,oBAAoB;IAC/B;;OAEG;WACU,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBpD;;OAEG;mBACkB,WAAW;IAoBhC;;OAEG;WACU,UAAU,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAwCjD;;OAEG;WACU,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAgBlE"}
1
+ {"version":3,"file":"promptHistory.d.ts","sourceRoot":"","sources":["../../src/utils/promptHistory.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBlD,qBAAa,oBAAoB;IAC/B;;OAEG;WACU,QAAQ,CACnB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC;IAyBhB;;OAEG;mBACkB,WAAW;IAoBhC;;OAEG;WACU,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA6CnE;;OAEG;WACU,aAAa,CACxB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC;CAgB1B"}
@@ -19,7 +19,7 @@ export class PromptHistoryManager {
19
19
  /**
20
20
  * Add a new prompt to history
21
21
  */
22
- static async addEntry(prompt) {
22
+ static async addEntry(prompt, sessionId, longTextMap) {
23
23
  try {
24
24
  if (!prompt.trim())
25
25
  return;
@@ -27,6 +27,8 @@ export class PromptHistoryManager {
27
27
  const entry = {
28
28
  prompt,
29
29
  timestamp: Date.now(),
30
+ sessionId,
31
+ longTextMap,
30
32
  };
31
33
  const line = JSON.stringify(entry) + "\n";
32
34
  await fs.promises.appendFile(PROMPT_HISTORY_FILE, line, "utf-8");
@@ -61,7 +63,7 @@ export class PromptHistoryManager {
61
63
  /**
62
64
  * Get all history entries
63
65
  */
64
- static async getHistory() {
66
+ static async getHistory(sessionId) {
65
67
  try {
66
68
  if (!fs.existsSync(PROMPT_HISTORY_FILE)) {
67
69
  return [];
@@ -79,12 +81,16 @@ export class PromptHistoryManager {
79
81
  }
80
82
  })
81
83
  .filter((entry) => entry !== null);
84
+ // Filter by sessionId if provided
85
+ const filteredEntries = sessionId
86
+ ? entries.filter((entry) => entry.sessionId === sessionId)
87
+ : entries;
82
88
  // Deduplicate by prompt, keeping the most recent one
83
89
  const uniqueEntries = [];
84
90
  const seenPrompts = new Set();
85
91
  // Process from newest to oldest
86
- for (let i = entries.length - 1; i >= 0; i--) {
87
- const entry = entries[i];
92
+ for (let i = filteredEntries.length - 1; i >= 0; i--) {
93
+ const entry = filteredEntries[i];
88
94
  if (!seenPrompts.has(entry.prompt)) {
89
95
  uniqueEntries.push(entry);
90
96
  seenPrompts.add(entry.prompt);
@@ -100,9 +106,9 @@ export class PromptHistoryManager {
100
106
  /**
101
107
  * Search history by query
102
108
  */
103
- static async searchHistory(query) {
109
+ static async searchHistory(query, sessionId) {
104
110
  try {
105
- const history = await this.getHistory();
111
+ const history = await this.getHistory(sessionId);
106
112
  if (!query.trim()) {
107
113
  return history;
108
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.8.4",
3
+ "version": "0.9.1",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,6 +27,7 @@
27
27
  "@modelcontextprotocol/sdk": "^1.18.2",
28
28
  "@vscode/ripgrep": "^1.15.14",
29
29
  "chokidar": "^5.0.0",
30
+ "fuzzysort": "^3.1.0",
30
31
  "glob": "^13.0.0",
31
32
  "minimatch": "^10.0.3",
32
33
  "openai": "^5.12.2"
package/src/agent.ts CHANGED
@@ -77,32 +77,24 @@ export class Agent {
77
77
 
78
78
  // Dynamic configuration getter methods
79
79
  public getGatewayConfig(): GatewayConfig {
80
- return this.configurationService.resolveGatewayConfig(
81
- this.options.apiKey,
82
- this.options.baseURL,
83
- this.options.defaultHeaders,
84
- this.options.fetchOptions,
85
- this.options.fetch,
86
- );
80
+ return this.configurationService.resolveGatewayConfig();
87
81
  }
88
82
 
89
83
  public getModelConfig(): ModelConfig {
90
84
  return this.configurationService.resolveModelConfig(
91
- this.options.model,
92
- this.options.fastModel,
93
- this.options.maxTokens,
85
+ undefined,
86
+ undefined,
87
+ undefined,
94
88
  this.getPermissionMode(),
95
89
  );
96
90
  }
97
91
 
98
92
  public getMaxInputTokens(): number {
99
- return this.configurationService.resolveMaxInputTokens(
100
- this.options.maxInputTokens,
101
- );
93
+ return this.configurationService.resolveMaxInputTokens();
102
94
  }
103
95
 
104
96
  public getLanguage(): string | undefined {
105
- return this.configurationService.resolveLanguage(this.options.language);
97
+ return this.configurationService.resolveLanguage();
106
98
  }
107
99
 
108
100
  /**
@@ -122,6 +114,7 @@ export class Agent {
122
114
 
123
115
  // Initialize configuration service
124
116
  this.configurationService = new ConfigurationService();
117
+ this.configurationService.setOptions(options);
125
118
 
126
119
  this.logger = logger; // Save the passed logger
127
120
  this.systemPrompt = systemPrompt; // Save custom system prompt
@@ -153,10 +146,6 @@ export class Agent {
153
146
  },
154
147
  addPermissionRule: (rule) => this.addPermissionRule(rule),
155
148
  addUsage: (usage) => this.messageManager.addUsage(usage),
156
- getGatewayConfig: () => this.getGatewayConfig(),
157
- getModelConfig: () => this.getModelConfig(),
158
- getMaxInputTokens: () => this.getMaxInputTokens(),
159
- getLanguage: () => this.getLanguage(),
160
149
  });
161
150
 
162
151
  // Retrieve managers from container
@@ -3,7 +3,12 @@ import * as aiService from "../services/aiService.js";
3
3
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
4
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
5
5
  import * as fs from "node:fs/promises";
6
- import type { GatewayConfig, ModelConfig, Usage } from "../types/index.js";
6
+ import type {
7
+ GatewayConfig,
8
+ ModelConfig,
9
+ Usage,
10
+ PermissionMode,
11
+ } from "../types/index.js";
7
12
  import type { ToolManager } from "./toolManager.js";
8
13
  import type { ToolContext, ToolResult } from "../tools/types.js";
9
14
  import type { MessageManager } from "./messageManager.js";
@@ -16,6 +21,7 @@ import type { SubagentManager } from "./subagentManager.js";
16
21
  import type { SkillManager } from "./skillManager.js";
17
22
  import { buildSystemPrompt } from "../prompts/index.js";
18
23
  import { Container } from "../utils/container.js";
24
+ import { ConfigurationService } from "../services/configurationService.js";
19
25
 
20
26
  import { logger } from "../utils/globalLogger.js";
21
27
 
@@ -31,12 +37,8 @@ export interface AIManagerOptions {
31
37
  subagentType?: string; // Optional subagent type for hook context
32
38
  /**Whether to use streaming mode for AI responses - defaults to true */
33
39
  stream?: boolean;
34
- // Dynamic configuration getters
35
- getGatewayConfig: () => GatewayConfig;
36
- getModelConfig: () => ModelConfig;
37
- getMaxInputTokens: () => number;
38
- getLanguage: () => string | undefined;
39
- getEnvironmentVars?: () => Record<string, string>; // Get configuration environment variables for hooks
40
+ /**Optional model override (e.g. for subagents) */
41
+ modelOverride?: string;
40
42
  }
41
43
 
42
44
  export class AIManager {
@@ -47,13 +49,7 @@ export class AIManager {
47
49
  private systemPrompt?: string;
48
50
  private subagentType?: string; // Store subagent type for hook context
49
51
  private stream: boolean; // Streaming mode flag
50
-
51
- // Configuration properties (replaced with getter function storage)
52
- private getGatewayConfigFn: () => GatewayConfig;
53
- private getModelConfigFn: () => ModelConfig;
54
- private getMaxInputTokensFn: () => number;
55
- private getLanguageFn: () => string | undefined;
56
- private getEnvironmentVarsFn?: () => Record<string, string>;
52
+ private modelOverride?: string;
57
53
 
58
54
  // Service overrides
59
55
  constructor(
@@ -65,13 +61,7 @@ export class AIManager {
65
61
  this.subagentType = options.subagentType; // Store subagent type
66
62
  this.stream = options.stream ?? true; // Default to true if not specified
67
63
  this.callbacks = options.callbacks ?? {};
68
-
69
- // Store configuration getter functions for dynamic resolution
70
- this.getGatewayConfigFn = options.getGatewayConfig;
71
- this.getModelConfigFn = options.getModelConfig;
72
- this.getMaxInputTokensFn = options.getMaxInputTokens;
73
- this.getLanguageFn = options.getLanguage;
74
- this.getEnvironmentVarsFn = options.getEnvironmentVars;
64
+ this.modelOverride = options.modelOverride;
75
65
  }
76
66
 
77
67
  private get toolManager(): ToolManager {
@@ -82,6 +72,12 @@ export class AIManager {
82
72
  return this.container.get<MessageManager>("MessageManager")!;
83
73
  }
84
74
 
75
+ private get memoryService(): import("../services/memory.js").MemoryService {
76
+ return this.container.get<import("../services/memory.js").MemoryService>(
77
+ "MemoryService",
78
+ )!;
79
+ }
80
+
85
81
  private get taskManager(): import("../services/taskManager.js").TaskManager {
86
82
  return this.container.get<import("../services/taskManager.js").TaskManager>(
87
83
  "TaskManager",
@@ -108,21 +104,54 @@ export class AIManager {
108
104
  return this.container.get<PermissionManager>("PermissionManager");
109
105
  }
110
106
 
107
+ private get configurationService(): ConfigurationService {
108
+ return this.container.get<ConfigurationService>("ConfigurationService")!;
109
+ }
110
+
111
111
  // Getter methods for accessing dynamic configuration
112
112
  public getGatewayConfig(): GatewayConfig {
113
- return this.getGatewayConfigFn();
113
+ return this.configurationService.resolveGatewayConfig();
114
114
  }
115
115
 
116
116
  public getModelConfig(): ModelConfig {
117
- return this.getModelConfigFn();
117
+ const permissionMode = this.container.has("PermissionMode")
118
+ ? this.container.get<PermissionMode>("PermissionMode")
119
+ : undefined;
120
+
121
+ const parentModelConfig = this.configurationService.resolveModelConfig(
122
+ undefined,
123
+ undefined,
124
+ undefined,
125
+ permissionMode,
126
+ );
127
+ let modelToUse: string | undefined;
128
+
129
+ if (this.modelOverride) {
130
+ if (this.modelOverride === "fastModel") {
131
+ modelToUse = parentModelConfig.fastModel;
132
+ } else if (this.modelOverride !== "inherit") {
133
+ modelToUse = this.modelOverride;
134
+ }
135
+ }
136
+
137
+ return this.configurationService.resolveModelConfig(
138
+ modelToUse,
139
+ undefined,
140
+ undefined,
141
+ permissionMode,
142
+ );
118
143
  }
119
144
 
120
145
  public getMaxInputTokens(): number {
121
- return this.getMaxInputTokensFn();
146
+ return this.configurationService.resolveMaxInputTokens();
122
147
  }
123
148
 
124
149
  public getLanguage(): string | undefined {
125
- return this.getLanguageFn();
150
+ return this.configurationService.resolveLanguage();
151
+ }
152
+
153
+ public getAutoMemoryEnabled(): boolean {
154
+ return this.configurationService.resolveAutoMemoryEnabled();
126
155
  }
127
156
 
128
157
  private isCompressing: boolean = false;
@@ -272,6 +301,9 @@ export class AIManager {
272
301
  );
273
302
  } catch (compressError) {
274
303
  logger?.error("Failed to compress messages:", compressError);
304
+ this.messageManager.addErrorBlock(
305
+ `Failed to compress conversation history: ${compressError instanceof Error ? compressError.message : String(compressError)}. You may encounter context limit issues.`,
306
+ );
275
307
  } finally {
276
308
  this.setIsCompressing(false);
277
309
  }
@@ -393,6 +425,18 @@ export class AIManager {
393
425
  }
394
426
  }
395
427
 
428
+ let autoMemoryOptions: { directory: string; content: string } | undefined;
429
+
430
+ if (this.getAutoMemoryEnabled()) {
431
+ const directory = this.memoryService.getAutoMemoryDirectory(
432
+ this.workdir,
433
+ );
434
+ const content = await this.memoryService.getAutoMemoryContent(
435
+ this.workdir,
436
+ );
437
+ autoMemoryOptions = { directory, content };
438
+ }
439
+
396
440
  // Call AI service with streaming callbacks if enabled
397
441
  const callAgentOptions: CallAgentOptions = {
398
442
  gatewayConfig: this.getGatewayConfig(),
@@ -412,6 +456,7 @@ export class AIManager {
412
456
  language: this.getLanguage(),
413
457
  isSubagent: !!this.subagentType,
414
458
  planMode: planModeOptions,
459
+ autoMemory: autoMemoryOptions,
415
460
  },
416
461
  ), // Pass custom system prompt
417
462
  maxTokens: maxTokens, // Pass max tokens override
@@ -844,7 +889,7 @@ export class AIManager {
844
889
  cwd: this.workdir,
845
890
  subagentType: this.subagentType, // Include subagent type in hook context
846
891
  // Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
847
- env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
892
+ env: this.configurationService.getEnvironmentVars(), // Include configuration environment variables
848
893
  };
849
894
 
850
895
  const results = await this.hookManager.executeHooks(hookName, context);
@@ -915,7 +960,7 @@ export class AIManager {
915
960
  cwd: this.workdir,
916
961
  toolInput,
917
962
  subagentType: this.subagentType, // Include subagent type in hook context
918
- env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
963
+ env: this.configurationService.getEnvironmentVars(), // Include configuration environment variables
919
964
  };
920
965
 
921
966
  const results = await this.hookManager.executeHooks(
@@ -981,7 +1026,7 @@ export class AIManager {
981
1026
  toolInput,
982
1027
  toolResponse,
983
1028
  subagentType: this.subagentType, // Include subagent type in hook context
984
- env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
1029
+ env: this.configurationService.getEnvironmentVars(), // Include configuration environment variables
985
1030
  };
986
1031
 
987
1032
  const results = await this.hookManager.executeHooks(
@@ -162,10 +162,31 @@ export class HookManager {
162
162
  const hookCommand = config.hooks[commandIndex];
163
163
 
164
164
  try {
165
+ const options = hookCommand.timeout
166
+ ? { timeout: hookCommand.timeout * 1000 }
167
+ : undefined;
168
+
169
+ if (hookCommand.async) {
170
+ // Execute async command without awaiting
171
+ executeCommand(hookCommand.command, context, options).catch(
172
+ (error) => {
173
+ const errorMessage =
174
+ error instanceof Error
175
+ ? error.message
176
+ : "Unknown execution error";
177
+ logger?.error(
178
+ `[HookManager] Async hook command ${commandIndex + 1} failed: ${errorMessage}`,
179
+ );
180
+ },
181
+ );
182
+ // Async hooks are not included in results to prevent blocking
183
+ continue;
184
+ }
185
+
165
186
  const result = await executeCommand(
166
187
  hookCommand.command,
167
188
  context,
168
- undefined,
189
+ options,
169
190
  );
170
191
  results.push(result);
171
192
 
@@ -22,6 +22,7 @@ import {
22
22
  } from "../services/session.js";
23
23
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
24
24
  import type { MemoryRuleManager } from "./MemoryRuleManager.js";
25
+ import type { MemoryService } from "../services/memory.js";
25
26
  import { pathEncoder } from "../utils/pathEncoder.js";
26
27
 
27
28
  import { Container } from "../utils/container.js";
@@ -113,6 +114,14 @@ export class MessageManager {
113
114
  : undefined;
114
115
  }
115
116
 
117
+ private get memoryService(): MemoryService {
118
+ const service = this.container.get<MemoryService>("MemoryService");
119
+ if (!service) {
120
+ throw new Error("MemoryService not found in container");
121
+ }
122
+ return service;
123
+ }
124
+
116
125
  // Getter methods
117
126
  public getSessionId(): string {
118
127
  return this.sessionId;
@@ -161,8 +170,9 @@ export class MessageManager {
161
170
  * Get combined memory content (project memory + user memory + modular rules)
162
171
  */
163
172
  public async getCombinedMemory(): Promise<string> {
164
- const memory = await import("../services/memory.js");
165
- let combined = await memory.getCombinedMemoryContent(this.workdir);
173
+ let combined = await this.memoryService.getCombinedMemoryContent(
174
+ this.workdir,
175
+ );
166
176
 
167
177
  if (this.memoryRuleManager) {
168
178
  const filesInContext = this.getFilesInContext();
@@ -1,11 +1,6 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import type { SubagentConfiguration } from "../utils/subagentParser.js";
3
- import type {
4
- Message,
5
- GatewayConfig,
6
- ModelConfig,
7
- Usage,
8
- } from "../types/index.js";
3
+ import type { Message, Usage } from "../types/index.js";
9
4
  import { AIManager } from "./aiManager.js";
10
5
  import { MessageManager } from "./messageManager.js";
11
6
  import { ToolManager } from "./toolManager.js";
@@ -23,6 +18,7 @@ import {
23
18
 
24
19
  import { Container } from "../utils/container.js";
25
20
  import type { PermissionManager } from "./permissionManager.js";
21
+ import { ConfigurationService } from "../services/configurationService.js";
26
22
 
27
23
  export interface SubagentManagerCallbacks {
28
24
  // Granular subagent message callbacks (015-subagent-message-callbacks)
@@ -79,11 +75,6 @@ export interface SubagentInstance {
79
75
  export interface SubagentManagerOptions {
80
76
  workdir: string;
81
77
  callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
82
- getGatewayConfig: () => GatewayConfig;
83
- getModelConfig: () => ModelConfig;
84
- getMaxInputTokens: () => number;
85
- getLanguage: () => string | undefined;
86
- getEnvironmentVars?: () => Record<string, string>;
87
78
  onUsageAdded?: (usage: Usage) => void;
88
79
  }
89
80
 
@@ -93,11 +84,6 @@ export class SubagentManager {
93
84
 
94
85
  private workdir: string;
95
86
  private callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
96
- private getGatewayConfig: () => GatewayConfig;
97
- private getModelConfig: () => ModelConfig;
98
- private getMaxInputTokens: () => number;
99
- private getLanguage: () => string | undefined;
100
- private getEnvironmentVars?: () => Record<string, string>;
101
87
  private onUsageAdded?: (usage: Usage) => void;
102
88
  private container: Container;
103
89
 
@@ -105,14 +91,13 @@ export class SubagentManager {
105
91
  this.container = container;
106
92
  this.workdir = options.workdir;
107
93
  this.callbacks = options.callbacks; // Store SubagentManagerCallbacks
108
- this.getGatewayConfig = options.getGatewayConfig;
109
- this.getModelConfig = options.getModelConfig;
110
- this.getMaxInputTokens = options.getMaxInputTokens;
111
- this.getLanguage = options.getLanguage;
112
- this.getEnvironmentVars = options.getEnvironmentVars;
113
94
  this.onUsageAdded = options.onUsageAdded;
114
95
  }
115
96
 
97
+ private get configurationService(): ConfigurationService {
98
+ return this.container.get<ConfigurationService>("ConfigurationService")!;
99
+ }
100
+
116
101
  /**
117
102
  * Initialize the SubagentManager by loading and caching configurations
118
103
  */
@@ -232,34 +217,7 @@ export class SubagentManager {
232
217
  workdir: this.workdir,
233
218
  systemPrompt: configuration.systemPrompt,
234
219
  subagentType: parameters.subagent_type, // Pass subagent type for hook context
235
- getGatewayConfig: this.getGatewayConfig,
236
- getModelConfig: () => {
237
- // Determine model dynamically each time
238
- const parentModelConfig = this.getModelConfig();
239
- let modelToUse: string;
240
-
241
- if (parameters.model) {
242
- // Use model override from parameters if provided
243
- modelToUse = parameters.model;
244
- } else if (!configuration.model || configuration.model === "inherit") {
245
- // Use parent's model for "inherit" or undefined
246
- modelToUse = parentModelConfig.model;
247
- } else if (configuration.model === "fastModel") {
248
- // Use parent's fastModel for special "fastModel" value
249
- modelToUse = parentModelConfig.fastModel;
250
- } else {
251
- // Use specific model name
252
- modelToUse = configuration.model;
253
- }
254
-
255
- return {
256
- ...parentModelConfig,
257
- model: modelToUse,
258
- };
259
- },
260
- getMaxInputTokens: this.getMaxInputTokens,
261
- getLanguage: this.getLanguage,
262
- getEnvironmentVars: this.getEnvironmentVars,
220
+ modelOverride: parameters.model || configuration.model, // Pass model override
263
221
  callbacks: {
264
222
  onUsageAdded: this.onUsageAdded,
265
223
  },
@@ -449,28 +407,8 @@ export class SubagentManager {
449
407
 
450
408
  // Execute the AI request with tool restrictions
451
409
  // The AIManager will handle abort signals through its own abort controllers
452
- // Resolve model name for sendAIMessage
453
- let resolvedModel: string | undefined;
454
- if (instance.model) {
455
- resolvedModel = instance.model;
456
- } else if (
457
- instance.configuration.model &&
458
- instance.configuration.model !== "inherit"
459
- ) {
460
- if (instance.configuration.model === "fastModel") {
461
- // Use parent's fastModel for special "fastModel" value
462
- const parentModelConfig = this.getModelConfig();
463
- resolvedModel = parentModelConfig.fastModel;
464
- } else {
465
- // Use specific model name
466
- resolvedModel = instance.configuration.model;
467
- }
468
- }
469
- // For "inherit" or undefined, resolvedModel remains undefined (uses AIManager default)
470
-
471
410
  const executeAI = instance.aiManager.sendAIMessage({
472
411
  tools: enabledTools,
473
- model: resolvedModel,
474
412
  });
475
413
 
476
414
  // If we have an abort signal, race against it using utilities to prevent listener accumulation
@@ -0,0 +1,33 @@
1
+ export function buildAutoMemoryPrompt(memoryDir: string): string {
2
+ return `
3
+ # auto memory
4
+
5
+ You have a persistent auto memory directory at \`${memoryDir}\`. Its contents persist across conversations.
6
+
7
+ As you work, consult your memory files to build on previous experience.
8
+
9
+ ## How to save memories:
10
+ - Organize memory semantically by topic, not chronologically
11
+ - Use the Write and Edit tools to update your memory files
12
+ - \`MEMORY.md\` is always loaded into your conversation context — lines after 200 will be truncated, so keep it concise
13
+ - Create separate topic files (e.g., \`debugging.md\`, \`patterns.md\`) for detailed notes and link to them from MEMORY.md
14
+ - Update or remove memories that turn out to be wrong or outdated
15
+ - Do not write duplicate memories. First check if there is an existing memory you can update before writing a new one.
16
+
17
+ ## What to save:
18
+ - Stable patterns and conventions confirmed across multiple interactions
19
+ - Key architectural decisions, important file paths, and project structure
20
+ - User preferences for workflow, tools, and communication style
21
+ - Solutions to recurring problems and debugging insights
22
+
23
+ ## What NOT to save:
24
+ - Session-specific context (current task details, in-progress work, temporary state)
25
+ - Information that might be incomplete — verify against project docs before writing
26
+ - Anything that duplicates or contradicts existing AGENTS.md instructions
27
+ - Speculative or unverified conclusions from reading a single file
28
+
29
+ ## Explicit user requests:
30
+ - When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions
31
+ - When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files
32
+ `;
33
+ }
@@ -1,6 +1,7 @@
1
1
  import * as os from "node:os";
2
2
  import { ToolPlugin } from "../tools/types.js";
3
3
  import { isGitRepository } from "../utils/gitUtils.js";
4
+ import { buildAutoMemoryPrompt } from "./autoMemory.js";
4
5
  import {
5
6
  EXPLORE_SUBAGENT_TYPE,
6
7
  PLAN_SUBAGENT_TYPE,
@@ -301,6 +302,10 @@ export function buildSystemPrompt(
301
302
  planFilePath: string;
302
303
  planExists: boolean;
303
304
  };
305
+ autoMemory?: {
306
+ directory: string;
307
+ content: string;
308
+ };
304
309
  } = {},
305
310
  ): string {
306
311
  let prompt = basePrompt || DEFAULT_SYSTEM_PROMPT;
@@ -335,6 +340,13 @@ Today's date: ${today}
335
340
  `;
336
341
  }
337
342
 
343
+ if (options.autoMemory) {
344
+ prompt += `\n\n${buildAutoMemoryPrompt(options.autoMemory.directory)}`;
345
+ if (options.autoMemory.content.trim()) {
346
+ prompt += `\n\n## MEMORY.md\n\n${options.autoMemory.content}`;
347
+ }
348
+ }
349
+
338
350
  if (options.memory && options.memory.trim()) {
339
351
  prompt += `\n## Memory Context\n\nThe following is important context and memory from previous interactions:\n\n${options.memory}`;
340
352
  }