wave-agent-sdk 1.0.0 → 1.0.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 (41) hide show
  1. package/dist/agent.d.ts +9 -20
  2. package/dist/agent.js +23 -97
  3. package/dist/managers/aiManager.d.ts +63 -8
  4. package/dist/managers/aiManager.js +270 -80
  5. package/dist/managers/messageManager.d.ts +9 -5
  6. package/dist/managers/messageManager.js +36 -12
  7. package/dist/managers/subagentManager.d.ts +6 -0
  8. package/dist/managers/subagentManager.js +33 -22
  9. package/dist/prompts/index.d.ts +0 -1
  10. package/dist/prompts/index.js +0 -4
  11. package/dist/services/aiService.d.ts +1 -34
  12. package/dist/services/aiService.js +18 -130
  13. package/dist/services/autoMemoryService.d.ts +27 -2
  14. package/dist/services/autoMemoryService.js +124 -36
  15. package/dist/services/configurationService.js +14 -1
  16. package/dist/services/session.d.ts +13 -0
  17. package/dist/services/session.js +64 -0
  18. package/dist/types/agent.d.ts +0 -2
  19. package/dist/types/config.d.ts +7 -0
  20. package/dist/types/core.d.ts +1 -1
  21. package/dist/utils/containerSetup.js +12 -3
  22. package/package.json +1 -1
  23. package/src/agent.ts +36 -110
  24. package/src/managers/aiManager.ts +366 -105
  25. package/src/managers/messageManager.ts +51 -23
  26. package/src/managers/subagentManager.ts +36 -25
  27. package/src/prompts/index.ts +0 -4
  28. package/src/services/aiService.ts +25 -203
  29. package/src/services/autoMemoryService.ts +145 -39
  30. package/src/services/configurationService.ts +16 -1
  31. package/src/services/session.ts +68 -0
  32. package/src/types/agent.ts +0 -6
  33. package/src/types/config.ts +7 -0
  34. package/src/types/core.ts +1 -1
  35. package/src/utils/containerSetup.ts +12 -4
  36. package/dist/constants/goalPrompts.d.ts +0 -1
  37. package/dist/constants/goalPrompts.js +0 -10
  38. package/dist/managers/goalManager.d.ts +0 -42
  39. package/dist/managers/goalManager.js +0 -177
  40. package/src/constants/goalPrompts.ts +0 -10
  41. package/src/managers/goalManager.ts +0 -232
package/dist/agent.d.ts CHANGED
@@ -19,7 +19,6 @@ export declare class Agent {
19
19
  private pluginManager;
20
20
  private skillManager;
21
21
  private cronManager;
22
- private goalManager;
23
22
  private hookManager;
24
23
  private reversionManager;
25
24
  private messageQueue;
@@ -92,10 +91,6 @@ export declare class Agent {
92
91
  get isCommandRunning(): boolean;
93
92
  /** Get queued user-facing messages (excludes background notifications) */
94
93
  get queuedMessages(): QueuedMessage[];
95
- /** Get goal status string */
96
- get goalStatus(): string;
97
- /** Check if a goal is active */
98
- get isGoalActive(): boolean;
99
94
  /**
100
95
  * Remove a queued message by index
101
96
  * @param index - The index of the message to remove
@@ -231,19 +226,6 @@ export declare class Agent {
231
226
  * @param customInstructions - Optional custom instructions for compaction
232
227
  */
233
228
  compact(customInstructions?: string): Promise<void>;
234
- /**
235
- * Set an autonomous goal for the session
236
- * @param condition - The goal condition to achieve
237
- */
238
- setGoal(condition: string): Promise<void>;
239
- /**
240
- * Clear the current autonomous goal
241
- */
242
- clearGoal(): Promise<void>;
243
- /**
244
- * Show the current goal status
245
- */
246
- showGoalStatus(): Promise<void>;
247
229
  /** Unified interrupt method, interrupts both AI messages and command execution */
248
230
  abortMessage(): void;
249
231
  /** Interrupt bash command execution */
@@ -275,11 +257,18 @@ export declare class Agent {
275
257
  */
276
258
  triggerShowRewind(): void;
277
259
  /**
278
- * Ask a side question without tool use
260
+ * Ask a side question without interrupting the main agent.
261
+ *
262
+ * Runs a single-turn fork of the conversation that reuses the main loop's
263
+ * system prompt, tools, and memory injection (prompt-cache friendly) and
264
+ * never executes tools. The abort signal lets the UI cancel a pending
265
+ * question (loading dismiss); aborts propagate as thrown errors.
266
+ *
279
267
  * @param question - The side question to ask
268
+ * @param abortSignal - Optional signal to abort the pending request
280
269
  * @returns Promise that resolves to the AI's answer
281
270
  */
282
- askBtw(question: string): Promise<string>;
271
+ askBtw(question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void, onReasoning?: (content: string) => void): Promise<string>;
283
272
  /**
284
273
  * Send a message to the AI agent with optional images
285
274
  *
package/dist/agent.js CHANGED
@@ -1,8 +1,5 @@
1
1
  import { LspManager } from "./managers/lspManager.js";
2
2
  import { configValidator } from "./utils/configValidator.js";
3
- import { btw } from "./services/aiService.js";
4
- import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
5
- import { supportsVision } from "./utils/modelCapabilities.js";
6
3
  import { parseTaskNotificationXml } from "./utils/notificationXml.js";
7
4
  import { InitializationService } from "./services/initializationService.js";
8
5
  import { InteractionService } from "./services/interactionService.js";
@@ -114,7 +111,6 @@ export class Agent {
114
111
  this.pluginManager = this.container.get("PluginManager");
115
112
  this.bangManager = this.container.get("BangManager");
116
113
  this.cronManager = this.container.get("CronManager");
117
- this.goalManager = this.container.get("GoalManager");
118
114
  this.messageQueue = this.container.get("MessageQueue");
119
115
  // Wire up CWD change callback from AIManager to sync Agent's workdir
120
116
  this.aiManager.setOnCwdChange((newCwd) => {
@@ -141,14 +137,6 @@ export class Agent {
141
137
  if (options.permissionMode) {
142
138
  this.setPermissionMode(options.permissionMode);
143
139
  }
144
- // Wire up goal state change callback
145
- this.goalManager.setOnGoalStateChange((active, condition, elapsed) => {
146
- this.options.callbacks?.onGoalStateChange?.(active, condition, elapsed);
147
- });
148
- // Wire up goal evaluating callback
149
- this.goalManager.setOnGoalEvaluating((evaluating) => {
150
- this.options.callbacks?.onGoalEvaluating?.(evaluating);
151
- });
152
140
  }
153
141
  // Public getter methods
154
142
  get sessionId() {
@@ -219,14 +207,6 @@ export class Agent {
219
207
  .getQueue()
220
208
  .filter((m) => m.type !== "notification");
221
209
  }
222
- /** Get goal status string */
223
- get goalStatus() {
224
- return this.goalManager.getStatusString();
225
- }
226
- /** Check if a goal is active */
227
- get isGoalActive() {
228
- return this.goalManager.isGoalActive();
229
- }
230
210
  /**
231
211
  * Remove a queued message by index
232
212
  * @param index - The index of the message to remove
@@ -524,8 +504,6 @@ export class Agent {
524
504
  }
525
505
  async clearMessages() {
526
506
  this.aiManager.abortAIMessage();
527
- // Clear any active goal
528
- this.goalManager.clearGoal();
529
507
  // Capture old session info before clearing
530
508
  const oldSessionId = this.messageManager.getSessionId();
531
509
  const transcriptPath = this.messageManager.getTranscriptPath();
@@ -574,68 +552,6 @@ export class Agent {
574
552
  });
575
553
  await this.messageManager.saveSession();
576
554
  }
577
- /**
578
- * Set an autonomous goal for the session
579
- * @param condition - The goal condition to achieve
580
- */
581
- async setGoal(condition) {
582
- // Check plan mode
583
- if (this.getPermissionMode() === "plan") {
584
- this.messageManager.addUserMessage({
585
- content: "<system-reminder>Cannot set a goal in plan mode. Exit plan mode first.</system-reminder>",
586
- isMeta: true,
587
- });
588
- return;
589
- }
590
- this.goalManager.setGoal(condition);
591
- this.messageManager.addUserMessage({
592
- content: `<system-reminder>Goal set: ${condition}. The agent will work autonomously until this goal is achieved.</system-reminder>`,
593
- isMeta: true,
594
- });
595
- // Add the goal as a user directive to start working
596
- this.messageManager.addUserMessage({
597
- content: condition,
598
- });
599
- this.aiManager.sendAIMessage();
600
- await this.messageManager.saveSession();
601
- }
602
- /**
603
- * Clear the current autonomous goal
604
- */
605
- async clearGoal() {
606
- if (this.goalManager.isGoalActive()) {
607
- this.goalManager.clearGoal();
608
- this.messageManager.addUserMessage({
609
- content: "<system-reminder>Goal cleared.</system-reminder>",
610
- isMeta: true,
611
- });
612
- }
613
- else {
614
- this.messageManager.addUserMessage({
615
- content: "<system-reminder>No active goal to clear.</system-reminder>",
616
- isMeta: true,
617
- });
618
- }
619
- await this.messageManager.saveSession();
620
- }
621
- /**
622
- * Show the current goal status
623
- */
624
- async showGoalStatus() {
625
- if (this.goalManager.isGoalActive()) {
626
- this.messageManager.addUserMessage({
627
- content: `<system-reminder>${this.goalManager.getStatusString()}</system-reminder>`,
628
- isMeta: true,
629
- });
630
- }
631
- else {
632
- this.messageManager.addUserMessage({
633
- content: "<system-reminder>No active goal. Use /goal <condition> to set one.</system-reminder>",
634
- isMeta: true,
635
- });
636
- }
637
- await this.messageManager.saveSession();
638
- }
639
555
  /** Unified interrupt method, interrupts both AI messages and command execution */
640
556
  abortMessage() {
641
557
  // Guard: prevent tryDispatch (triggered by abortAIMessage → setIsLoading(false))
@@ -757,6 +673,17 @@ export class Agent {
757
673
  this.subagentManager.cleanup();
758
674
  // Cleanup forked agent manager
759
675
  await this.forkedAgentManager.cleanup();
676
+ // Drain an in-flight auto-memory extraction fork so the process doesn't
677
+ // exit mid-extraction
678
+ try {
679
+ const autoMemoryService = this.container.get("AutoMemoryService");
680
+ if (autoMemoryService) {
681
+ await autoMemoryService.drain();
682
+ }
683
+ }
684
+ catch (error) {
685
+ this.logger?.warn(`Auto-memory extraction drain failed: ${error.message}`);
686
+ }
760
687
  // Cleanup skill manager
761
688
  await this.skillManager.destroy();
762
689
  // Cleanup live configuration reload
@@ -777,21 +704,20 @@ export class Agent {
777
704
  this.messageManager.triggerShowRewind();
778
705
  }
779
706
  /**
780
- * Ask a side question without tool use
707
+ * Ask a side question without interrupting the main agent.
708
+ *
709
+ * Runs a single-turn fork of the conversation that reuses the main loop's
710
+ * system prompt, tools, and memory injection (prompt-cache friendly) and
711
+ * never executes tools. The abort signal lets the UI cancel a pending
712
+ * question (loading dismiss); aborts propagate as thrown errors.
713
+ *
781
714
  * @param question - The side question to ask
715
+ * @param abortSignal - Optional signal to abort the pending request
782
716
  * @returns Promise that resolves to the AI's answer
783
717
  */
784
- async askBtw(question) {
785
- const messages = convertMessagesForAPI(this.messageManager.getMessages(), {
786
- supportsVision: supportsVision(this.getModelConfig().capabilities),
787
- });
788
- const result = await btw({
789
- gatewayConfig: this.getGatewayConfig(),
790
- modelConfig: this.getModelConfig(),
791
- messages,
792
- question,
793
- });
794
- return result.content;
718
+ async askBtw(question, abortSignal, onContent, onReasoning) {
719
+ const result = await this.aiManager.runBtwFork(question, abortSignal, onContent, onReasoning);
720
+ return result.content ?? result.error ?? "No response received";
795
721
  }
796
722
  /**
797
723
  * Send a message to the AI agent with optional images
@@ -821,7 +747,7 @@ export class Agent {
821
747
  */
822
748
  async sendMessage(content, images) {
823
749
  // If the agent is busy, enqueue the message — unless it's an immediate
824
- // slash command (e.g., /goal clear, /clear, /compact) that should execute
750
+ // slash command (e.g., /clear, /compact) that should execute
825
751
  // right away even while AI is processing
826
752
  if (this.aiManager.isLoading || this.isCommandRunning) {
827
753
  const trimmed = content.trim();
@@ -1,6 +1,15 @@
1
- import type { GatewayConfig, ModelConfig, Usage } from "../types/index.js";
1
+ import type { GatewayConfig, ModelConfig, Usage, Message } from "../types/index.js";
2
2
  import { Container } from "../utils/container.js";
3
3
  import type { WorktreeSession } from "../utils/worktreeSession.js";
4
+ /** Result of a fork-path agent loop (compaction or auto-memory extraction). */
5
+ interface ForkLoopResult {
6
+ content?: string;
7
+ usage?: {
8
+ prompt_tokens: number;
9
+ completion_tokens: number;
10
+ total_tokens: number;
11
+ };
12
+ }
4
13
  export interface AIManagerCallbacks {
5
14
  onCompactionStateChange?: (isCompacting: boolean) => void;
6
15
  onUsageAdded?: (usage: Usage) => void;
@@ -119,15 +128,60 @@ export declare class AIManager {
119
128
  private buildMainSystemPrompt;
120
129
  private resolveFilteredTools;
121
130
  /**
122
- * Fork-path compaction: run a bounded agent loop over a copy of the
123
- * conversation using the same system prompt, tools, model, and generation
124
- * params as the main loop, so the forked request prefix matches exactly
125
- * and the prompt cache is reused. Tool calls are denied locally (the model
126
- * is told to summarize, not act) and their rejections are fed back for
127
- * another turn. Returns undefined content when the model never produces
128
- * text; the caller treats that as a compaction failure.
131
+ * Fork-path loop: run a bounded agent loop over a copy of the conversation
132
+ * using the same system prompt, tools, model, and generation params as the
133
+ * main loop, so the forked request prefix matches exactly and the prompt
134
+ * cache is reused. A `canUseTool` gate decides whether each tool call
135
+ * executes locally (with a stripped context) or is denied and fed back to
136
+ * the model for another turn. Returns undefined content when the model never
137
+ * produces text; the caller treats that as a failure.
138
+ */
139
+ private runForkLoop;
140
+ /**
141
+ * Fork-path compaction: deny all tool calls locally (the model is told to
142
+ * summarize, not act) and feed the rejections back for another turn.
129
143
  */
130
144
  private runCompactFork;
145
+ /**
146
+ * Auto-memory extraction via the perfect fork: the extraction prompt is run
147
+ * against the same request prefix as the main conversation (same system
148
+ * prompt, tools, model, and message history) so the prompt cache is reused.
149
+ * Gate-approved tools execute locally in a stripped context; everything else
150
+ * is denied. Usage is reported with operation_type "agent" so extraction
151
+ * token costs stay visible in session accounting.
152
+ */
153
+ runAutoMemoryFork(messages: Message[], prompt: string, options: {
154
+ canUseTool: (name: string, args: Record<string, unknown>) => boolean;
155
+ deniedToolMessage?: string;
156
+ maxTurns?: number;
157
+ }, abortSignal?: AbortSignal): Promise<ForkLoopResult>;
158
+ /**
159
+ * Parse a fork tool call's JSON arguments, recovering truncated JSON the
160
+ * same way the main loop does. Unparseable arguments fall back to `{}` and
161
+ * are rejected by the gate or the tool's own parameter validation.
162
+ */
163
+ private parseForkToolArgs;
164
+ /**
165
+ * Execute a single tool call inside a fork with a stripped context: no
166
+ * permission manager (never prompts the user), no message manager (no
167
+ * conditional-rule triggering), no messageId (no file-history snapshots),
168
+ * and no background task manager (commands run in the foreground). Only
169
+ * gate-approved tool names reach this path.
170
+ */
171
+ private executeForkTool;
172
+ /**
173
+ * Fork-path side question ("/btw"): run a single-turn fork of the
174
+ * conversation using the same system prompt, tools, model, and generation
175
+ * params as the main loop, so the forked request prefix matches exactly and
176
+ * the prompt cache is reused. The in-progress assistant message (if any) is
177
+ * stripped so the fork starts from the last completed request prefix. Tools
178
+ * are never executed — the wrapped question instructs the model to answer
179
+ * directly; an attempted tool call is surfaced as an error string.
180
+ */
181
+ runBtwFork(question: string, abortSignal?: AbortSignal, onContent?: (content: string) => void, onReasoning?: (content: string) => void): Promise<{
182
+ content?: string;
183
+ error?: string;
184
+ }>;
131
185
  /**
132
186
  * Build post-compact context restoration content.
133
187
  * Restores file reads, working directory, plan mode, skills, and background tasks.
@@ -166,3 +220,4 @@ export declare class AIManager {
166
220
  */
167
221
  private executePostToolUseHooks;
168
222
  }
223
+ export {};