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
@@ -481,6 +481,70 @@ export async function cleanupEmptyProjectDirectories() {
481
481
  // Ignore errors if base directory doesn't exist or can't be accessed
482
482
  }
483
483
  }
484
+ /**
485
+ * Clean up "ghost" session files that contain only meta messages
486
+ * (isMeta: true) — e.g. sessions where a SessionStart hook injected a
487
+ * system-reminder but no real user/assistant message was ever sent.
488
+ *
489
+ * Such sessions are no longer created thanks to lazy materialization in
490
+ * saveSession(); this one-time sweep removes files that predate that change
491
+ * so they stop showing up as "0 tokens / No content" entries in the resume
492
+ * list.
493
+ *
494
+ * @returns Promise that resolves to the number of files deleted
495
+ */
496
+ export async function cleanupMetaOnlySessions() {
497
+ // Do not perform cleanup operations in test environment
498
+ if (process.env.NODE_ENV === "test") {
499
+ return 0;
500
+ }
501
+ let deletedCount = 0;
502
+ try {
503
+ const projectDirs = await fs.readdir(SESSION_DIR);
504
+ for (const projectDirName of projectDirs) {
505
+ const projectPath = join(SESSION_DIR, projectDirName);
506
+ try {
507
+ const stat = await fs.stat(projectPath);
508
+ if (!stat.isDirectory()) {
509
+ continue;
510
+ }
511
+ const files = await fs.readdir(projectPath);
512
+ for (const file of files) {
513
+ if (!file.endsWith(".jsonl")) {
514
+ continue;
515
+ }
516
+ const filePath = join(projectPath, file);
517
+ try {
518
+ // Fast path: a file whose last message is not meta contains a
519
+ // real message, so it can never be meta-only.
520
+ const jsonlHandler = new JsonlHandler();
521
+ const lastMessage = await jsonlHandler.getLastMessage(filePath);
522
+ if (!lastMessage?.isMeta) {
523
+ continue;
524
+ }
525
+ const messages = await jsonlHandler.read(filePath);
526
+ if (messages.length > 0 && messages.every((m) => m.isMeta)) {
527
+ await fs.unlink(filePath);
528
+ deletedCount++;
529
+ }
530
+ }
531
+ catch {
532
+ // Skip corrupted or unreadable files
533
+ continue;
534
+ }
535
+ }
536
+ }
537
+ catch {
538
+ // Skip directories we can't access
539
+ continue;
540
+ }
541
+ }
542
+ }
543
+ catch {
544
+ // Ignore errors if base directory doesn't exist or can't be accessed
545
+ }
546
+ return deletedCount;
547
+ }
484
548
  /**
485
549
  * Check if a session exists in JSONL storage (new approach)
486
550
  *
@@ -98,6 +98,4 @@ export interface AgentCallbacks extends MessageManagerCallbacks, BackgroundTaskM
98
98
  onCommandRunningChange?: (running: boolean) => void;
99
99
  onWorkdirChange?: (newCwd: string) => void;
100
100
  onQueuedMessagesChange?: (messages: QueuedMessage[]) => void;
101
- onGoalStateChange?: (active: boolean, condition?: string, elapsed?: string) => void;
102
- onGoalEvaluating?: (evaluating: boolean) => void;
103
101
  }
@@ -29,4 +29,11 @@ export interface ModelConfig {
29
29
  options?: Record<string, unknown>;
30
30
  /** Fast model generation params (resolved from models[fastModel].options) */
31
31
  fastModelOptions?: Record<string, unknown>;
32
+ /**
33
+ * Fast-model-only disable-thinking params passed through verbatim
34
+ * (e.g. `{ thinking: { type: "disabled" } }`). Applied only in fast-model
35
+ * scenarios (webFetch content processing, `model: fastModel` subagents),
36
+ * never in the agent loop. `{}` clears the default.
37
+ */
38
+ disableThinkingOptions?: Record<string, unknown>;
32
39
  }
@@ -22,7 +22,7 @@ export interface Usage {
22
22
  completion_tokens: number;
23
23
  total_tokens: number;
24
24
  model?: string;
25
- operation_type?: "agent" | "compact" | "goal_evaluation";
25
+ operation_type?: "agent" | "compact";
26
26
  cache_read_input_tokens?: number;
27
27
  cache_creation_input_tokens?: number;
28
28
  cache_creation?: {
@@ -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";
@@ -24,6 +23,7 @@ import { SubagentManager } from "../managers/subagentManager.js";
24
23
  import { ForkedAgentManager } from "../managers/forkedAgentManager.js";
25
24
  import { LiveConfigManager } from "../managers/liveConfigManager.js";
26
25
  import { ReversionService } from "../services/reversionService.js";
26
+ import { cleanupMetaOnlySessions } from "../services/session.js";
27
27
  import { MemoryService } from "../services/memory.js";
28
28
  import { AutoMemoryService } from "../services/autoMemoryService.js";
29
29
  import { USER_MEMORY_FILE } from "./constants.js";
@@ -144,6 +144,17 @@ export function setupAgentContainer(setupOptions) {
144
144
  reversionService.cleanupOldSessions(30).catch((error) => {
145
145
  logger.error("Failed to cleanup old file history:", error);
146
146
  });
147
+ // Remove pre-existing meta-only session files (SessionStart hook context
148
+ // with no real messages) that were persisted before lazy materialization.
149
+ cleanupMetaOnlySessions()
150
+ .then((count) => {
151
+ if (count > 0) {
152
+ logger.debug(`Removed ${count} meta-only session file(s)`);
153
+ }
154
+ })
155
+ .catch((error) => {
156
+ logger.error("Failed to cleanup meta-only session files:", error);
157
+ });
147
158
  const reversionManager = new ReversionManager(container);
148
159
  container.register("ReversionManager", reversionManager);
149
160
  const canUseToolWithPermissionRequest = options.canUseTool
@@ -255,8 +266,6 @@ export function setupAgentContainer(setupOptions) {
255
266
  const cronManager = new CronManager(container, messageManager.getSessionId());
256
267
  container.register("CronManager", cronManager);
257
268
  cronManager.start();
258
- const goalManager = new GoalManager(container);
259
- container.register("GoalManager", goalManager);
260
269
  const workflowManager = new WorkflowManager(container);
261
270
  container.register("WorkflowManager", workflowManager);
262
271
  return container;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
package/src/agent.ts CHANGED
@@ -8,7 +8,6 @@ import { McpManager } from "./managers/mcpManager.js";
8
8
  import { LspManager } from "./managers/lspManager.js";
9
9
  import { BangManager } from "./managers/bangManager.js";
10
10
  import { CronManager } from "./managers/cronManager.js";
11
- import { GoalManager } from "./managers/goalManager.js";
12
11
  import { BackgroundTaskManager } from "./managers/backgroundTaskManager.js";
13
12
  import { MessageQueue, type QueuedMessage } from "./managers/messageQueue.js";
14
13
  import { SlashCommandManager } from "./managers/slashCommandManager.js";
@@ -38,9 +37,6 @@ import { LiveConfigManager } from "./managers/liveConfigManager.js";
38
37
  import { configValidator } from "./utils/configValidator.js";
39
38
  import { SkillManager } from "./managers/skillManager.js";
40
39
  import { TaskManager } from "./services/taskManager.js";
41
- import { btw } from "./services/aiService.js";
42
- import { convertMessagesForAPI } from "./utils/convertMessagesForAPI.js";
43
- import { supportsVision } from "./utils/modelCapabilities.js";
44
40
  import type { WorktreeSession } from "./utils/worktreeSession.js";
45
41
  import { parseTaskNotificationXml } from "./utils/notificationXml.js";
46
42
  import { InitializationService } from "./services/initializationService.js";
@@ -73,7 +69,6 @@ export class Agent {
73
69
  private pluginManager: PluginManager; // Add plugin manager instance
74
70
  private skillManager: SkillManager; // Add skill manager instance
75
71
  private cronManager: CronManager; // Add cron manager instance
76
- private goalManager: GoalManager; // Add goal manager instance
77
72
  private hookManager: HookManager; // Add hooks manager instance
78
73
  private reversionManager: ReversionManager;
79
74
  private messageQueue: MessageQueue; // Unified queue for messages, bang commands, and notifications
@@ -208,7 +203,6 @@ export class Agent {
208
203
  this.pluginManager = this.container.get("PluginManager")!;
209
204
  this.bangManager = this.container.get("BangManager")!;
210
205
  this.cronManager = this.container.get("CronManager")!;
211
- this.goalManager = this.container.get("GoalManager")!;
212
206
  this.messageQueue = this.container.get("MessageQueue")!;
213
207
 
214
208
  // Wire up CWD change callback from AIManager to sync Agent's workdir
@@ -238,16 +232,6 @@ export class Agent {
238
232
  if (options.permissionMode) {
239
233
  this.setPermissionMode(options.permissionMode);
240
234
  }
241
-
242
- // Wire up goal state change callback
243
- this.goalManager.setOnGoalStateChange((active, condition, elapsed) => {
244
- this.options.callbacks?.onGoalStateChange?.(active, condition, elapsed);
245
- });
246
-
247
- // Wire up goal evaluating callback
248
- this.goalManager.setOnGoalEvaluating((evaluating) => {
249
- this.options.callbacks?.onGoalEvaluating?.(evaluating);
250
- });
251
235
  }
252
236
 
253
237
  // Public getter methods
@@ -340,16 +324,6 @@ export class Agent {
340
324
  .filter((m) => m.type !== "notification");
341
325
  }
342
326
 
343
- /** Get goal status string */
344
- public get goalStatus(): string {
345
- return this.goalManager.getStatusString();
346
- }
347
-
348
- /** Check if a goal is active */
349
- public get isGoalActive(): boolean {
350
- return this.goalManager.isGoalActive();
351
- }
352
-
353
327
  /**
354
328
  * Remove a queued message by index
355
329
  * @param index - The index of the message to remove
@@ -709,9 +683,6 @@ export class Agent {
709
683
  public async clearMessages(): Promise<void> {
710
684
  this.aiManager.abortAIMessage();
711
685
 
712
- // Clear any active goal
713
- this.goalManager.clearGoal();
714
-
715
686
  // Capture old session info before clearing
716
687
  const oldSessionId = this.messageManager.getSessionId();
717
688
  const transcriptPath = this.messageManager.getTranscriptPath();
@@ -781,75 +752,6 @@ export class Agent {
781
752
  await this.messageManager.saveSession();
782
753
  }
783
754
 
784
- /**
785
- * Set an autonomous goal for the session
786
- * @param condition - The goal condition to achieve
787
- */
788
- public async setGoal(condition: string): Promise<void> {
789
- // Check plan mode
790
- if (this.getPermissionMode() === "plan") {
791
- this.messageManager.addUserMessage({
792
- content:
793
- "<system-reminder>Cannot set a goal in plan mode. Exit plan mode first.</system-reminder>",
794
- isMeta: true,
795
- });
796
- return;
797
- }
798
-
799
- this.goalManager.setGoal(condition);
800
- this.messageManager.addUserMessage({
801
- content: `<system-reminder>Goal set: ${condition}. The agent will work autonomously until this goal is achieved.</system-reminder>`,
802
- isMeta: true,
803
- });
804
- // Add the goal as a user directive to start working
805
- this.messageManager.addUserMessage({
806
- content: condition,
807
- });
808
- this.aiManager.sendAIMessage();
809
-
810
- await this.messageManager.saveSession();
811
- }
812
-
813
- /**
814
- * Clear the current autonomous goal
815
- */
816
- public async clearGoal(): Promise<void> {
817
- if (this.goalManager.isGoalActive()) {
818
- this.goalManager.clearGoal();
819
- this.messageManager.addUserMessage({
820
- content: "<system-reminder>Goal cleared.</system-reminder>",
821
- isMeta: true,
822
- });
823
- } else {
824
- this.messageManager.addUserMessage({
825
- content: "<system-reminder>No active goal to clear.</system-reminder>",
826
- isMeta: true,
827
- });
828
- }
829
-
830
- await this.messageManager.saveSession();
831
- }
832
-
833
- /**
834
- * Show the current goal status
835
- */
836
- public async showGoalStatus(): Promise<void> {
837
- if (this.goalManager.isGoalActive()) {
838
- this.messageManager.addUserMessage({
839
- content: `<system-reminder>${this.goalManager.getStatusString()}</system-reminder>`,
840
- isMeta: true,
841
- });
842
- } else {
843
- this.messageManager.addUserMessage({
844
- content:
845
- "<system-reminder>No active goal. Use /goal <condition> to set one.</system-reminder>",
846
- isMeta: true,
847
- });
848
- }
849
-
850
- await this.messageManager.saveSession();
851
- }
852
-
853
755
  /** Unified interrupt method, interrupts both AI messages and command execution */
854
756
  public abortMessage(): void {
855
757
  // Guard: prevent tryDispatch (triggered by abortAIMessage → setIsLoading(false))
@@ -994,6 +896,21 @@ export class Agent {
994
896
  this.subagentManager.cleanup();
995
897
  // Cleanup forked agent manager
996
898
  await this.forkedAgentManager.cleanup();
899
+ // Drain an in-flight auto-memory extraction fork so the process doesn't
900
+ // exit mid-extraction
901
+ try {
902
+ const autoMemoryService =
903
+ this.container.get<
904
+ import("./services/autoMemoryService.js").AutoMemoryService
905
+ >("AutoMemoryService");
906
+ if (autoMemoryService) {
907
+ await autoMemoryService.drain();
908
+ }
909
+ } catch (error) {
910
+ this.logger?.warn(
911
+ `Auto-memory extraction drain failed: ${(error as Error).message}`,
912
+ );
913
+ }
997
914
  // Cleanup skill manager
998
915
  await this.skillManager.destroy();
999
916
  // Cleanup live configuration reload
@@ -1018,21 +935,30 @@ export class Agent {
1018
935
  }
1019
936
 
1020
937
  /**
1021
- * Ask a side question without tool use
938
+ * Ask a side question without interrupting the main agent.
939
+ *
940
+ * Runs a single-turn fork of the conversation that reuses the main loop's
941
+ * system prompt, tools, and memory injection (prompt-cache friendly) and
942
+ * never executes tools. The abort signal lets the UI cancel a pending
943
+ * question (loading dismiss); aborts propagate as thrown errors.
944
+ *
1022
945
  * @param question - The side question to ask
946
+ * @param abortSignal - Optional signal to abort the pending request
1023
947
  * @returns Promise that resolves to the AI's answer
1024
948
  */
1025
- public async askBtw(question: string): Promise<string> {
1026
- const messages = convertMessagesForAPI(this.messageManager.getMessages(), {
1027
- supportsVision: supportsVision(this.getModelConfig().capabilities),
1028
- });
1029
- const result = await btw({
1030
- gatewayConfig: this.getGatewayConfig(),
1031
- modelConfig: this.getModelConfig(),
1032
- messages,
949
+ public async askBtw(
950
+ question: string,
951
+ abortSignal?: AbortSignal,
952
+ onContent?: (content: string) => void,
953
+ onReasoning?: (content: string) => void,
954
+ ): Promise<string> {
955
+ const result = await this.aiManager.runBtwFork(
1033
956
  question,
1034
- });
1035
- return result.content;
957
+ abortSignal,
958
+ onContent,
959
+ onReasoning,
960
+ );
961
+ return result.content ?? result.error ?? "No response received";
1036
962
  }
1037
963
 
1038
964
  /**
@@ -1066,7 +992,7 @@ export class Agent {
1066
992
  images?: Array<{ path: string; mimeType: string }>,
1067
993
  ): Promise<void> {
1068
994
  // If the agent is busy, enqueue the message — unless it's an immediate
1069
- // slash command (e.g., /goal clear, /clear, /compact) that should execute
995
+ // slash command (e.g., /clear, /compact) that should execute
1070
996
  // right away even while AI is processing
1071
997
  if (this.aiManager.isLoading || this.isCommandRunning) {
1072
998
  const trimmed = content.trim();