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
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
@@ -523,9 +503,14 @@ export class Agent {
523
503
  await this.messageManager.saveSession();
524
504
  }
525
505
  async clearMessages() {
506
+ // Aligned with webview (ChatApp handleClearChat): /clear is ignored
507
+ // while the agent is running, instead of aborting the AI mid-turn
508
+ // (which used to inject a late "Request was aborted" error into the
509
+ // newly cleared session).
510
+ if (this.aiManager.isLoading) {
511
+ return;
512
+ }
526
513
  this.aiManager.abortAIMessage();
527
- // Clear any active goal
528
- this.goalManager.clearGoal();
529
514
  // Capture old session info before clearing
530
515
  const oldSessionId = this.messageManager.getSessionId();
531
516
  const transcriptPath = this.messageManager.getTranscriptPath();
@@ -568,74 +553,17 @@ export class Agent {
568
553
  * @param customInstructions - Optional custom instructions for compaction
569
554
  */
570
555
  async compact(customInstructions) {
556
+ // Aligned with webview: /compact is ignored while the agent is running,
557
+ // instead of aborting the AI mid-turn before compacting.
558
+ if (this.aiManager.isLoading) {
559
+ return;
560
+ }
571
561
  this.aiManager.abortAIMessage();
572
562
  await this.aiManager.compactConversation({
573
563
  customInstructions,
574
564
  });
575
565
  await this.messageManager.saveSession();
576
566
  }
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
567
  /** Unified interrupt method, interrupts both AI messages and command execution */
640
568
  abortMessage() {
641
569
  // Guard: prevent tryDispatch (triggered by abortAIMessage → setIsLoading(false))
@@ -757,6 +685,17 @@ export class Agent {
757
685
  this.subagentManager.cleanup();
758
686
  // Cleanup forked agent manager
759
687
  await this.forkedAgentManager.cleanup();
688
+ // Drain an in-flight auto-memory extraction fork so the process doesn't
689
+ // exit mid-extraction
690
+ try {
691
+ const autoMemoryService = this.container.get("AutoMemoryService");
692
+ if (autoMemoryService) {
693
+ await autoMemoryService.drain();
694
+ }
695
+ }
696
+ catch (error) {
697
+ this.logger?.warn(`Auto-memory extraction drain failed: ${error.message}`);
698
+ }
760
699
  // Cleanup skill manager
761
700
  await this.skillManager.destroy();
762
701
  // Cleanup live configuration reload
@@ -777,21 +716,20 @@ export class Agent {
777
716
  this.messageManager.triggerShowRewind();
778
717
  }
779
718
  /**
780
- * Ask a side question without tool use
719
+ * Ask a side question without interrupting the main agent.
720
+ *
721
+ * Runs a single-turn fork of the conversation that reuses the main loop's
722
+ * system prompt, tools, and memory injection (prompt-cache friendly) and
723
+ * never executes tools. The abort signal lets the UI cancel a pending
724
+ * question (loading dismiss); aborts propagate as thrown errors.
725
+ *
781
726
  * @param question - The side question to ask
727
+ * @param abortSignal - Optional signal to abort the pending request
782
728
  * @returns Promise that resolves to the AI's answer
783
729
  */
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;
730
+ async askBtw(question, abortSignal, onContent, onReasoning) {
731
+ const result = await this.aiManager.runBtwFork(question, abortSignal, onContent, onReasoning);
732
+ return result.content ?? result.error ?? "No response received";
795
733
  }
796
734
  /**
797
735
  * Send a message to the AI agent with optional images
@@ -821,7 +759,7 @@ export class Agent {
821
759
  */
822
760
  async sendMessage(content, images) {
823
761
  // 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
762
+ // slash command (e.g., /clear, /compact) that should execute
825
763
  // right away even while AI is processing
826
764
  if (this.aiManager.isLoading || this.isCommandRunning) {
827
765
  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 {};