wave-agent-sdk 1.0.0 → 1.0.2

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 (43) hide show
  1. package/dist/agent.d.ts +9 -20
  2. package/dist/agent.js +35 -97
  3. package/dist/managers/aiManager.d.ts +63 -8
  4. package/dist/managers/aiManager.js +274 -80
  5. package/dist/managers/messageManager.d.ts +9 -5
  6. package/dist/managers/messageManager.js +36 -12
  7. package/dist/managers/permissionManager.js +13 -11
  8. package/dist/managers/subagentManager.d.ts +6 -0
  9. package/dist/managers/subagentManager.js +33 -22
  10. package/dist/prompts/index.d.ts +0 -2
  11. package/dist/prompts/index.js +71 -47
  12. package/dist/services/aiService.d.ts +1 -34
  13. package/dist/services/aiService.js +18 -130
  14. package/dist/services/autoMemoryService.d.ts +27 -2
  15. package/dist/services/autoMemoryService.js +124 -36
  16. package/dist/services/configurationService.js +14 -1
  17. package/dist/services/session.d.ts +13 -0
  18. package/dist/services/session.js +64 -0
  19. package/dist/types/agent.d.ts +0 -2
  20. package/dist/types/config.d.ts +7 -0
  21. package/dist/types/core.d.ts +1 -1
  22. package/dist/utils/containerSetup.js +12 -3
  23. package/package.json +1 -1
  24. package/src/agent.ts +50 -110
  25. package/src/managers/aiManager.ts +370 -105
  26. package/src/managers/messageManager.ts +51 -23
  27. package/src/managers/permissionManager.ts +15 -13
  28. package/src/managers/subagentManager.ts +36 -25
  29. package/src/prompts/index.ts +75 -56
  30. package/src/services/aiService.ts +25 -203
  31. package/src/services/autoMemoryService.ts +145 -39
  32. package/src/services/configurationService.ts +16 -1
  33. package/src/services/session.ts +68 -0
  34. package/src/types/agent.ts +0 -6
  35. package/src/types/config.ts +7 -0
  36. package/src/types/core.ts +1 -1
  37. package/src/utils/containerSetup.ts +12 -4
  38. package/dist/constants/goalPrompts.d.ts +0 -1
  39. package/dist/constants/goalPrompts.js +0 -10
  40. package/dist/managers/goalManager.d.ts +0 -42
  41. package/dist/managers/goalManager.js +0 -177
  42. package/src/constants/goalPrompts.ts +0 -10
  43. package/src/managers/goalManager.ts +0 -232
@@ -16,7 +16,6 @@ import { SlashCommandManager } from "../managers/slashCommandManager.js";
16
16
  import { PluginManager } from "../managers/pluginManager.js";
17
17
  import { BangManager } from "../managers/bangManager.js";
18
18
  import { CronManager } from "../managers/cronManager.js";
19
- import { GoalManager } from "../managers/goalManager.js";
20
19
  import { WorkflowManager } from "../managers/workflowManager.js";
21
20
  import { MemoryRuleManager } from "../managers/MemoryRuleManager.js";
22
21
  import { ReversionManager } from "../managers/reversionManager.js";
@@ -25,6 +24,7 @@ import { ForkedAgentManager } from "../managers/forkedAgentManager.js";
25
24
  import { LiveConfigManager } from "../managers/liveConfigManager.js";
26
25
  import { ConfigurationService } from "../services/configurationService.js";
27
26
  import { ReversionService } from "../services/reversionService.js";
27
+ import { cleanupMetaOnlySessions } from "../services/session.js";
28
28
  import { MemoryService } from "../services/memory.js";
29
29
  import { AutoMemoryService } from "../services/autoMemoryService.js";
30
30
  import { USER_MEMORY_FILE } from "./constants.js";
@@ -210,6 +210,17 @@ export function setupAgentContainer(
210
210
  reversionService.cleanupOldSessions(30).catch((error) => {
211
211
  logger.error("Failed to cleanup old file history:", error);
212
212
  });
213
+ // Remove pre-existing meta-only session files (SessionStart hook context
214
+ // with no real messages) that were persisted before lazy materialization.
215
+ cleanupMetaOnlySessions()
216
+ .then((count) => {
217
+ if (count > 0) {
218
+ logger.debug(`Removed ${count} meta-only session file(s)`);
219
+ }
220
+ })
221
+ .catch((error) => {
222
+ logger.error("Failed to cleanup meta-only session files:", error);
223
+ });
213
224
  const reversionManager = new ReversionManager(container);
214
225
  container.register("ReversionManager", reversionManager);
215
226
 
@@ -352,9 +363,6 @@ export function setupAgentContainer(
352
363
  container.register("CronManager", cronManager);
353
364
  cronManager.start();
354
365
 
355
- const goalManager = new GoalManager(container);
356
- container.register("GoalManager", goalManager);
357
-
358
366
  const workflowManager = new WorkflowManager(container);
359
367
  container.register("WorkflowManager", workflowManager);
360
368
 
@@ -1 +0,0 @@
1
- export declare const GOAL_EVALUATION_SYSTEM_PROMPT = "You are a goal evaluator. Given a goal condition and a conversation, determine whether the goal has been achieved.\n\nRules:\n- Judge ONLY based on what the conversation shows \u2014 tool outputs, test results, file contents, etc.\n- Do NOT assume work is done without evidence in the conversation.\n- Be strict and conservative: when uncertain, return met: false.\n- Be concise: your reason should be 1-2 sentences.\n\nRespond with JSON only, no other text:\n{\"met\": boolean, \"reason\": \"short explanation\"}";
@@ -1,10 +0,0 @@
1
- export const GOAL_EVALUATION_SYSTEM_PROMPT = `You are a goal evaluator. Given a goal condition and a conversation, determine whether the goal has been achieved.
2
-
3
- Rules:
4
- - Judge ONLY based on what the conversation shows — tool outputs, test results, file contents, etc.
5
- - Do NOT assume work is done without evidence in the conversation.
6
- - Be strict and conservative: when uncertain, return met: false.
7
- - Be concise: your reason should be 1-2 sentences.
8
-
9
- Respond with JSON only, no other text:
10
- {"met": boolean, "reason": "short explanation"}`;
@@ -1,42 +0,0 @@
1
- import { Container } from "../utils/container.js";
2
- export interface GoalState {
3
- condition: string;
4
- startedAt: number;
5
- turnCount: number;
6
- tokenBaseline: number;
7
- lastReason?: string;
8
- consecutiveEvalFailures: number;
9
- }
10
- export declare class GoalManager {
11
- private container;
12
- private state;
13
- private onGoalStateChange?;
14
- private onGoalEvaluating?;
15
- constructor(container: Container);
16
- private get messageManager();
17
- private get aiManager();
18
- setOnGoalStateChange(callback: (active: boolean, condition?: string, elapsed?: string) => void): void;
19
- setOnGoalEvaluating(callback: (evaluating: boolean) => void): void;
20
- setGoal(condition: string): void;
21
- clearGoal(): void;
22
- getGoal(): GoalState | null;
23
- isGoalActive(): boolean;
24
- incrementTurnCount(): void;
25
- getStatusString(): string;
26
- /**
27
- * Check circuit breakers. Returns a clear reason if goal should be force-cleared, null otherwise.
28
- */
29
- checkCircuitBreakers(): string | null;
30
- /**
31
- * Evaluate whether the goal has been met using the fast model.
32
- */
33
- evaluateGoal(abortSignal?: AbortSignal): Promise<{
34
- isMet: boolean;
35
- reason: string;
36
- }>;
37
- /**
38
- * Parse the evaluation response from the fast model.
39
- */
40
- private parseEvaluationResponse;
41
- private formatElapsed;
42
- }
@@ -1,177 +0,0 @@
1
- import { evaluateGoal as aiEvaluateGoal } from "../services/aiService.js";
2
- import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
3
- import { logger } from "../utils/globalLogger.js";
4
- const MAX_GOAL_TURNS = 50;
5
- const MAX_GOAL_DURATION_MS = 30 * 60 * 1000; // 30 minutes
6
- const MAX_CONSECUTIVE_EVAL_FAILURES = 3;
7
- const MAX_CONDITION_LENGTH = 4000;
8
- export class GoalManager {
9
- constructor(container) {
10
- this.container = container;
11
- this.state = null;
12
- }
13
- get messageManager() {
14
- return this.container.get("MessageManager");
15
- }
16
- get aiManager() {
17
- return this.container.get("AIManager");
18
- }
19
- setOnGoalStateChange(callback) {
20
- this.onGoalStateChange = callback;
21
- }
22
- setOnGoalEvaluating(callback) {
23
- this.onGoalEvaluating = callback;
24
- }
25
- setGoal(condition) {
26
- if (condition.length > MAX_CONDITION_LENGTH) {
27
- throw new Error(`Goal condition exceeds maximum length of ${MAX_CONDITION_LENGTH} characters`);
28
- }
29
- const totalTokens = this.messageManager.getLatestTotalTokens?.() ?? 0;
30
- this.state = {
31
- condition,
32
- startedAt: Date.now(),
33
- turnCount: 0,
34
- tokenBaseline: totalTokens,
35
- consecutiveEvalFailures: 0,
36
- };
37
- this.onGoalStateChange?.(true, condition, "0m");
38
- logger?.info(`[Goal] Set goal: ${condition}`);
39
- }
40
- clearGoal() {
41
- if (this.state) {
42
- logger?.info(`[Goal] Cleared goal: ${this.state.condition}`);
43
- this.state = null;
44
- this.onGoalStateChange?.(false);
45
- }
46
- }
47
- getGoal() {
48
- return this.state;
49
- }
50
- isGoalActive() {
51
- return this.state !== null;
52
- }
53
- incrementTurnCount() {
54
- if (this.state) {
55
- this.state.turnCount++;
56
- }
57
- }
58
- getStatusString() {
59
- if (!this.state)
60
- return "No active goal";
61
- const elapsed = this.formatElapsed(Date.now() - this.state.startedAt);
62
- let status = `Goal: ${this.state.condition}\nElapsed: ${elapsed}\nTurns: ${this.state.turnCount}`;
63
- if (this.state.lastReason) {
64
- status += `\nLast evaluation: ${this.state.lastReason}`;
65
- }
66
- return status;
67
- }
68
- /**
69
- * Check circuit breakers. Returns a clear reason if goal should be force-cleared, null otherwise.
70
- */
71
- checkCircuitBreakers() {
72
- if (!this.state)
73
- return null;
74
- if (this.state.turnCount >= MAX_GOAL_TURNS) {
75
- return `Goal cancelled: maximum turns (${MAX_GOAL_TURNS}) exceeded`;
76
- }
77
- if (Date.now() - this.state.startedAt >= MAX_GOAL_DURATION_MS) {
78
- return `Goal cancelled: time limit (${MAX_GOAL_DURATION_MS / 60000} minutes) exceeded`;
79
- }
80
- if (this.state.consecutiveEvalFailures >= MAX_CONSECUTIVE_EVAL_FAILURES) {
81
- return `Goal cancelled: ${MAX_CONSECUTIVE_EVAL_FAILURES} consecutive evaluation failures`;
82
- }
83
- return null;
84
- }
85
- /**
86
- * Evaluate whether the goal has been met using the fast model.
87
- */
88
- async evaluateGoal(abortSignal) {
89
- if (!this.state) {
90
- return { isMet: false, reason: "No active goal" };
91
- }
92
- try {
93
- const messages = this.messageManager.getMessages();
94
- const apiMessages = convertMessagesForAPI(messages);
95
- const gatewayConfig = this.aiManager.getGatewayConfig();
96
- const modelConfig = this.aiManager.getModelConfig();
97
- const fastModel = modelConfig.fastModel || modelConfig.model;
98
- if (!fastModel) {
99
- return {
100
- isMet: false,
101
- reason: "No model configured for goal evaluation",
102
- };
103
- }
104
- this.onGoalEvaluating?.(true);
105
- const result = await aiEvaluateGoal({
106
- gatewayConfig,
107
- modelConfig,
108
- model: fastModel,
109
- goalCondition: this.state.condition,
110
- messages: apiMessages,
111
- abortSignal,
112
- });
113
- // Track evaluation tokens separately
114
- if (result.usage) {
115
- const usage = {
116
- ...result.usage,
117
- operation_type: "goal_evaluation",
118
- model: fastModel,
119
- };
120
- this.messageManager.addUsage(usage);
121
- }
122
- // Reset failure counter on success
123
- this.state.consecutiveEvalFailures = 0;
124
- this.onGoalEvaluating?.(false);
125
- // Parse the response
126
- return this.parseEvaluationResponse(result.content);
127
- }
128
- catch (error) {
129
- this.onGoalEvaluating?.(false);
130
- this.state.consecutiveEvalFailures++;
131
- logger?.warn(`[Goal] Evaluation failed (${this.state.consecutiveEvalFailures}/${MAX_CONSECUTIVE_EVAL_FAILURES}): ${error.message}`);
132
- return {
133
- isMet: false,
134
- reason: `Evaluation failed: ${error.message}`,
135
- };
136
- }
137
- }
138
- /**
139
- * Parse the evaluation response from the fast model.
140
- */
141
- parseEvaluationResponse(content) {
142
- // Try direct JSON parse
143
- try {
144
- const parsed = JSON.parse(content);
145
- if (typeof parsed.met === "boolean") {
146
- return {
147
- isMet: parsed.met,
148
- reason: parsed.reason || "No reason provided",
149
- };
150
- }
151
- }
152
- catch {
153
- // Fall through to regex
154
- }
155
- // Try regex extraction
156
- const metMatch = content.match(/"met"\s*:\s*(true|false)/);
157
- const reasonMatch = content.match(/"reason"\s*:\s*"([^"]*)"/);
158
- if (metMatch) {
159
- return {
160
- isMet: metMatch[1] === "true",
161
- reason: reasonMatch?.[1] || "No reason provided",
162
- };
163
- }
164
- // Default: not met
165
- return { isMet: false, reason: "Could not parse evaluation response" };
166
- }
167
- formatElapsed(ms) {
168
- const minutes = Math.floor(ms / 60000);
169
- if (minutes < 1)
170
- return "<1m";
171
- if (minutes < 60)
172
- return `${minutes}m`;
173
- const hours = Math.floor(minutes / 60);
174
- const remainingMin = minutes % 60;
175
- return `${hours}h${remainingMin}m`;
176
- }
177
- }
@@ -1,10 +0,0 @@
1
- export const GOAL_EVALUATION_SYSTEM_PROMPT = `You are a goal evaluator. Given a goal condition and a conversation, determine whether the goal has been achieved.
2
-
3
- Rules:
4
- - Judge ONLY based on what the conversation shows — tool outputs, test results, file contents, etc.
5
- - Do NOT assume work is done without evidence in the conversation.
6
- - Be strict and conservative: when uncertain, return met: false.
7
- - Be concise: your reason should be 1-2 sentences.
8
-
9
- Respond with JSON only, no other text:
10
- {"met": boolean, "reason": "short explanation"}`;
@@ -1,232 +0,0 @@
1
- import { Container } from "../utils/container.js";
2
- import type { MessageManager } from "./messageManager.js";
3
- import type { AIManager } from "./aiManager.js";
4
- import type { Usage } from "../types/index.js";
5
- import { evaluateGoal as aiEvaluateGoal } from "../services/aiService.js";
6
- import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
7
- import { logger } from "../utils/globalLogger.js";
8
-
9
- export interface GoalState {
10
- condition: string;
11
- startedAt: number;
12
- turnCount: number;
13
- tokenBaseline: number;
14
- lastReason?: string;
15
- consecutiveEvalFailures: number;
16
- }
17
-
18
- const MAX_GOAL_TURNS = 50;
19
- const MAX_GOAL_DURATION_MS = 30 * 60 * 1000; // 30 minutes
20
- const MAX_CONSECUTIVE_EVAL_FAILURES = 3;
21
- const MAX_CONDITION_LENGTH = 4000;
22
-
23
- export class GoalManager {
24
- private state: GoalState | null = null;
25
- private onGoalStateChange?: (
26
- active: boolean,
27
- condition?: string,
28
- elapsed?: string,
29
- ) => void;
30
- private onGoalEvaluating?: (evaluating: boolean) => void;
31
-
32
- constructor(private container: Container) {}
33
-
34
- private get messageManager(): MessageManager {
35
- return this.container.get<MessageManager>("MessageManager")!;
36
- }
37
-
38
- private get aiManager(): AIManager {
39
- return this.container.get<AIManager>("AIManager")!;
40
- }
41
-
42
- public setOnGoalStateChange(
43
- callback: (active: boolean, condition?: string, elapsed?: string) => void,
44
- ): void {
45
- this.onGoalStateChange = callback;
46
- }
47
-
48
- public setOnGoalEvaluating(callback: (evaluating: boolean) => void): void {
49
- this.onGoalEvaluating = callback;
50
- }
51
-
52
- public setGoal(condition: string): void {
53
- if (condition.length > MAX_CONDITION_LENGTH) {
54
- throw new Error(
55
- `Goal condition exceeds maximum length of ${MAX_CONDITION_LENGTH} characters`,
56
- );
57
- }
58
-
59
- const totalTokens = this.messageManager.getLatestTotalTokens?.() ?? 0;
60
-
61
- this.state = {
62
- condition,
63
- startedAt: Date.now(),
64
- turnCount: 0,
65
- tokenBaseline: totalTokens,
66
- consecutiveEvalFailures: 0,
67
- };
68
-
69
- this.onGoalStateChange?.(true, condition, "0m");
70
- logger?.info(`[Goal] Set goal: ${condition}`);
71
- }
72
-
73
- public clearGoal(): void {
74
- if (this.state) {
75
- logger?.info(`[Goal] Cleared goal: ${this.state.condition}`);
76
- this.state = null;
77
- this.onGoalStateChange?.(false);
78
- }
79
- }
80
-
81
- public getGoal(): GoalState | null {
82
- return this.state;
83
- }
84
-
85
- public isGoalActive(): boolean {
86
- return this.state !== null;
87
- }
88
-
89
- public incrementTurnCount(): void {
90
- if (this.state) {
91
- this.state.turnCount++;
92
- }
93
- }
94
-
95
- public getStatusString(): string {
96
- if (!this.state) return "No active goal";
97
- const elapsed = this.formatElapsed(Date.now() - this.state.startedAt);
98
- let status = `Goal: ${this.state.condition}\nElapsed: ${elapsed}\nTurns: ${this.state.turnCount}`;
99
- if (this.state.lastReason) {
100
- status += `\nLast evaluation: ${this.state.lastReason}`;
101
- }
102
- return status;
103
- }
104
-
105
- /**
106
- * Check circuit breakers. Returns a clear reason if goal should be force-cleared, null otherwise.
107
- */
108
- public checkCircuitBreakers(): string | null {
109
- if (!this.state) return null;
110
-
111
- if (this.state.turnCount >= MAX_GOAL_TURNS) {
112
- return `Goal cancelled: maximum turns (${MAX_GOAL_TURNS}) exceeded`;
113
- }
114
-
115
- if (Date.now() - this.state.startedAt >= MAX_GOAL_DURATION_MS) {
116
- return `Goal cancelled: time limit (${MAX_GOAL_DURATION_MS / 60000} minutes) exceeded`;
117
- }
118
-
119
- if (this.state.consecutiveEvalFailures >= MAX_CONSECUTIVE_EVAL_FAILURES) {
120
- return `Goal cancelled: ${MAX_CONSECUTIVE_EVAL_FAILURES} consecutive evaluation failures`;
121
- }
122
-
123
- return null;
124
- }
125
-
126
- /**
127
- * Evaluate whether the goal has been met using the fast model.
128
- */
129
- public async evaluateGoal(abortSignal?: AbortSignal): Promise<{
130
- isMet: boolean;
131
- reason: string;
132
- }> {
133
- if (!this.state) {
134
- return { isMet: false, reason: "No active goal" };
135
- }
136
-
137
- try {
138
- const messages = this.messageManager.getMessages();
139
- const apiMessages = convertMessagesForAPI(messages);
140
- const gatewayConfig = this.aiManager.getGatewayConfig();
141
- const modelConfig = this.aiManager.getModelConfig();
142
- const fastModel = modelConfig.fastModel || modelConfig.model;
143
- if (!fastModel) {
144
- return {
145
- isMet: false,
146
- reason: "No model configured for goal evaluation",
147
- };
148
- }
149
-
150
- this.onGoalEvaluating?.(true);
151
- const result = await aiEvaluateGoal({
152
- gatewayConfig,
153
- modelConfig,
154
- model: fastModel,
155
- goalCondition: this.state.condition,
156
- messages: apiMessages,
157
- abortSignal,
158
- });
159
-
160
- // Track evaluation tokens separately
161
- if (result.usage) {
162
- const usage: Usage = {
163
- ...result.usage,
164
- operation_type: "goal_evaluation",
165
- model: fastModel,
166
- };
167
- this.messageManager.addUsage(usage);
168
- }
169
-
170
- // Reset failure counter on success
171
- this.state.consecutiveEvalFailures = 0;
172
-
173
- this.onGoalEvaluating?.(false);
174
-
175
- // Parse the response
176
- return this.parseEvaluationResponse(result.content);
177
- } catch (error) {
178
- this.onGoalEvaluating?.(false);
179
- this.state.consecutiveEvalFailures++;
180
- logger?.warn(
181
- `[Goal] Evaluation failed (${this.state.consecutiveEvalFailures}/${MAX_CONSECUTIVE_EVAL_FAILURES}): ${(error as Error).message}`,
182
- );
183
- return {
184
- isMet: false,
185
- reason: `Evaluation failed: ${(error as Error).message}`,
186
- };
187
- }
188
- }
189
-
190
- /**
191
- * Parse the evaluation response from the fast model.
192
- */
193
- private parseEvaluationResponse(content: string): {
194
- isMet: boolean;
195
- reason: string;
196
- } {
197
- // Try direct JSON parse
198
- try {
199
- const parsed = JSON.parse(content);
200
- if (typeof parsed.met === "boolean") {
201
- return {
202
- isMet: parsed.met,
203
- reason: parsed.reason || "No reason provided",
204
- };
205
- }
206
- } catch {
207
- // Fall through to regex
208
- }
209
-
210
- // Try regex extraction
211
- const metMatch = content.match(/"met"\s*:\s*(true|false)/);
212
- const reasonMatch = content.match(/"reason"\s*:\s*"([^"]*)"/);
213
- if (metMatch) {
214
- return {
215
- isMet: metMatch[1] === "true",
216
- reason: reasonMatch?.[1] || "No reason provided",
217
- };
218
- }
219
-
220
- // Default: not met
221
- return { isMet: false, reason: "Could not parse evaluation response" };
222
- }
223
-
224
- private formatElapsed(ms: number): string {
225
- const minutes = Math.floor(ms / 60000);
226
- if (minutes < 1) return "<1m";
227
- if (minutes < 60) return `${minutes}m`;
228
- const hours = Math.floor(minutes / 60);
229
- const remainingMin = minutes % 60;
230
- return `${hours}h${remainingMin}m`;
231
- }
232
- }