wave-agent-sdk 0.14.3 → 0.14.4

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.
@@ -42,6 +42,9 @@ When this skill is invoked, follow these steps:
42
42
  - `allowed-tools`: (Optional) List of tools the skill can use.
43
43
  - `context: fork`: (Optional) Run the skill in a separate subagent.
44
44
  - `agent`: (Optional) Specify the subagent type (default: `general-purpose`).
45
+ - `disable-model-invocation`: (Optional, default: `false`) Set to `true` to hide the skill from the AI's available skills list. The skill can still be invoked by users via slash commands.
46
+ - `user-invocable`: (Optional, default: `true`) Set to `false` to hide the skill from the `/` slash command menu. The AI can still invoke it unless `disable-model-invocation` is also set.
47
+ - `model`: (Optional) Override the AI model used for skill execution (e.g., `"gpt-4o"`, `"o3-mini"`).
45
48
 
46
49
  ## Skill Locations
47
50
 
@@ -14,6 +14,8 @@ export interface AIManagerOptions {
14
14
  stream?: boolean;
15
15
  /**Optional model override (e.g. for subagents) */
16
16
  modelOverride?: string;
17
+ /**Optional max turns limit to prevent runaway recursion (e.g. for auto-memory extraction) */
18
+ maxTurns?: number;
17
19
  }
18
20
  export declare class AIManager {
19
21
  private container;
@@ -29,6 +31,7 @@ export declare class AIManager {
29
31
  private modelOverride?;
30
32
  private _onCwdChange?;
31
33
  private consecutiveCompactionFailures;
34
+ private readonly maxTurns?;
32
35
  constructor(container: Container, options: AIManagerOptions);
33
36
  private get toolManager();
34
37
  private get messageManager();
@@ -1 +1 @@
1
- {"version":3,"file":"aiManager.d.ts","sourceRoot":"","sources":["../../src/managers/aiManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiManager.d.ts","sourceRoot":"","sources":["../../src/managers/aiManager.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { parseTaskNotificationXml } from "../utils/notificationXml.js";
5
5
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
6
6
  import * as fs from "node:fs/promises";
7
7
  import { buildSystemPrompt } from "../prompts/index.js";
8
+ import { recoverTruncatedJson } from "../utils/stringUtils.js";
8
9
  import { logger } from "../utils/globalLogger.js";
9
10
  export class AIManager {
10
11
  // Service overrides
@@ -23,6 +24,7 @@ export class AIManager {
23
24
  this.callbacks = options.callbacks ?? {};
24
25
  this.modelOverride = options.modelOverride;
25
26
  this._onCwdChange = options.callbacks?.onCwdChange; // Initialize onCwdChange
27
+ this.maxTurns = options.maxTurns;
26
28
  }
27
29
  get toolManager() {
28
30
  return this.container.get("ToolManager");
@@ -584,35 +586,45 @@ export class AIManager {
584
586
  const toolName = functionToolCall.function?.name || "";
585
587
  // Safely parse tool parameters, handle tools without parameters
586
588
  let toolArgs = {};
589
+ let jsonRecovered = false;
587
590
  const argsString = functionToolCall.function?.arguments?.trim();
588
591
  if (!argsString || argsString === "") {
589
592
  // Tool without parameters, use empty object
590
593
  toolArgs = {};
591
594
  }
592
595
  else {
596
+ let recoveredArgs = argsString;
593
597
  try {
594
598
  toolArgs = JSON.parse(argsString);
595
599
  }
596
- catch (parseError) {
597
- // For non-empty but malformed JSON, still throw exception
598
- let errorMessage = `Failed to parse tool arguments`;
599
- if (result.finish_reason === "length") {
600
- errorMessage +=
601
- " (output truncated, please reduce your output)";
600
+ catch {
601
+ // Attempt to recover truncated JSON (e.g., missing closing braces)
602
+ recoveredArgs = recoverTruncatedJson(argsString);
603
+ try {
604
+ toolArgs = JSON.parse(recoveredArgs);
605
+ jsonRecovered = true;
606
+ logger.warn(`Recovered truncated JSON for tool "${toolName}"`);
607
+ }
608
+ catch (parseError) {
609
+ let errorMessage = `Failed to parse tool arguments`;
610
+ if (result.finish_reason === "length") {
611
+ errorMessage +=
612
+ " (output truncated, please reduce your output)";
613
+ }
614
+ logger?.error(errorMessage, parseError);
615
+ this.messageManager.updateToolBlock({
616
+ id: toolId,
617
+ parameters: argsString,
618
+ result: errorMessage,
619
+ success: false,
620
+ error: errorMessage,
621
+ stage: "end",
622
+ name: toolName,
623
+ compactParams: "",
624
+ timestamp: Date.now(),
625
+ });
626
+ return;
602
627
  }
603
- logger?.error(errorMessage, parseError);
604
- this.messageManager.updateToolBlock({
605
- id: toolId,
606
- parameters: argsString,
607
- result: errorMessage,
608
- success: false,
609
- error: errorMessage,
610
- stage: "end",
611
- name: toolName,
612
- compactParams: "",
613
- timestamp: Date.now(),
614
- });
615
- return;
616
628
  }
617
629
  }
618
630
  const compactParams = this.generateCompactParams(toolName, toolArgs);
@@ -681,12 +693,18 @@ export class AIManager {
681
693
  };
682
694
  // Execute tool
683
695
  const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
696
+ // Build result content, adding truncation warning if JSON was recovered
697
+ let toolResultContent = toolResult.content ||
698
+ (toolResult.error ? `Error: ${toolResult.error}` : "");
699
+ if (jsonRecovered) {
700
+ toolResultContent +=
701
+ "\n\n⚠️ Tool arguments were truncated (likely exceeded max output tokens). Please reduce your output or split into multiple tool calls.";
702
+ }
684
703
  // Update message state - tool execution completed
685
704
  this.messageManager.updateToolBlock({
686
705
  id: toolId,
687
706
  parameters: argsString,
688
- result: toolResult.content ||
689
- (toolResult.error ? `Error: ${toolResult.error}` : ""),
707
+ result: toolResultContent,
690
708
  success: toolResult.success,
691
709
  error: toolResult.error,
692
710
  stage: "end",
@@ -727,75 +745,82 @@ export class AIManager {
727
745
  this.messageManager.finalizeStreamingBlocks();
728
746
  // Check if there are tool operations or response was truncated, if so automatically initiate next AI service call
729
747
  if (toolCalls.length > 0 || result.finish_reason === "length") {
730
- // Record committed snapshots to message history
731
- if (this.reversionManager) {
732
- const snapshots = this.reversionManager.getAndClearCommittedSnapshots();
733
- if (snapshots.length > 0) {
734
- this.messageManager.addFileHistoryBlock(snapshots);
735
- }
748
+ // Check maxTurns limit before recursing
749
+ if (this.maxTurns && recursionDepth + 1 >= this.maxTurns) {
750
+ logger?.debug(`Max turns (${this.maxTurns}) reached, stopping recursion.`);
736
751
  }
737
- // Check interruption status
738
- const isCurrentlyAborted = abortController.signal.aborted || toolAbortController.signal.aborted;
739
- // Check if all tools were manually backgrounded
740
- const lastMessage = this.messageManager.getMessages()[this.messageManager.getMessages().length - 1];
741
- const toolBlocks = lastMessage?.blocks.filter((block) => block.type === "tool") || [];
742
- const hasBackgrounded = toolBlocks.length > 0 &&
743
- toolBlocks.some((block) => block.isManuallyBackgrounded);
744
- if (hasBackgrounded) {
745
- logger?.info("Some tools were manually backgrounded, stopping recursion.");
746
- }
747
- else if (!isCurrentlyAborted) {
748
- // If response was truncated, add a hidden continuation message
749
- if (result.finish_reason === "length") {
750
- this.messageManager.addUserMessage({
751
- content: "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
752
- isMeta: true,
753
- });
752
+ else {
753
+ // Record committed snapshots to message history
754
+ if (this.reversionManager) {
755
+ const snapshots = this.reversionManager.getAndClearCommittedSnapshots();
756
+ if (snapshots.length > 0) {
757
+ this.messageManager.addFileHistoryBlock(snapshots);
758
+ }
754
759
  }
755
- // Duplicate Tool Call Detection
756
- if (toolCalls.length > 0) {
757
- const messages = this.messageManager.getMessages();
758
- // Find the most recent assistant message BEFORE the current one that has tool blocks
759
- // The current assistant message is messages[messages.length - 1]
760
- let previousAssistantWithTools;
761
- for (let i = messages.length - 2; i >= 0; i--) {
762
- const msg = messages[i];
763
- if (msg.role === "assistant" &&
764
- msg.blocks.some((b) => b.type === "tool")) {
765
- previousAssistantWithTools = msg;
766
- break;
767
- }
760
+ // Check interruption status
761
+ const isCurrentlyAborted = abortController.signal.aborted ||
762
+ toolAbortController.signal.aborted;
763
+ // Check if all tools were manually backgrounded
764
+ const lastMessage = this.messageManager.getMessages()[this.messageManager.getMessages().length - 1];
765
+ const toolBlocks = lastMessage?.blocks.filter((block) => block.type === "tool") || [];
766
+ const hasBackgrounded = toolBlocks.length > 0 &&
767
+ toolBlocks.some((block) => block.isManuallyBackgrounded);
768
+ if (hasBackgrounded) {
769
+ logger?.info("Some tools were manually backgrounded, stopping recursion.");
770
+ }
771
+ else if (!isCurrentlyAborted) {
772
+ // If response was truncated, add a hidden continuation message
773
+ if (result.finish_reason === "length") {
774
+ this.messageManager.addUserMessage({
775
+ content: "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
776
+ isMeta: true,
777
+ });
768
778
  }
769
- if (previousAssistantWithTools) {
770
- const previousToolBlocks = previousAssistantWithTools.blocks.filter((b) => b.type === "tool");
771
- for (const currentToolCall of toolCalls) {
772
- const currentName = currentToolCall.function?.name;
773
- const currentArgs = currentToolCall.function?.arguments;
774
- const isDuplicate = previousToolBlocks.some((prevBlock) => prevBlock.name === currentName &&
775
- prevBlock.parameters === currentArgs);
776
- if (isDuplicate && currentName) {
777
- const toolId = currentToolCall.id;
778
- const lastMessage = messages[messages.length - 1];
779
- const toolBlock = lastMessage.blocks.find((b) => b.type === "tool" && b.id === toolId);
780
- if (toolBlock) {
781
- const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
782
- this.messageManager.updateToolBlock({
783
- id: toolId,
784
- result: (toolBlock.result || "") + warning,
785
- stage: "end",
786
- });
779
+ // Duplicate Tool Call Detection
780
+ if (toolCalls.length > 0) {
781
+ const messages = this.messageManager.getMessages();
782
+ // Find the most recent assistant message BEFORE the current one that has tool blocks
783
+ // The current assistant message is messages[messages.length - 1]
784
+ let previousAssistantWithTools;
785
+ for (let i = messages.length - 2; i >= 0; i--) {
786
+ const msg = messages[i];
787
+ if (msg.role === "assistant" &&
788
+ msg.blocks.some((b) => b.type === "tool")) {
789
+ previousAssistantWithTools = msg;
790
+ break;
791
+ }
792
+ }
793
+ if (previousAssistantWithTools) {
794
+ const previousToolBlocks = previousAssistantWithTools.blocks.filter((b) => b.type === "tool");
795
+ for (const currentToolCall of toolCalls) {
796
+ const currentName = currentToolCall.function?.name;
797
+ const currentArgs = currentToolCall.function?.arguments;
798
+ const isDuplicate = previousToolBlocks.some((prevBlock) => prevBlock.name === currentName &&
799
+ prevBlock.parameters === currentArgs);
800
+ if (isDuplicate && currentName) {
801
+ const toolId = currentToolCall.id;
802
+ const lastMessage = messages[messages.length - 1];
803
+ const toolBlock = lastMessage.blocks.find((b) => b.type === "tool" && b.id === toolId);
804
+ if (toolBlock) {
805
+ const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
806
+ this.messageManager.updateToolBlock({
807
+ id: toolId,
808
+ result: (toolBlock.result || "") + warning,
809
+ stage: "end",
810
+ });
811
+ }
787
812
  }
788
813
  }
789
814
  }
790
815
  }
816
+ // Recursively call AI service, increment recursion depth, and pass same configuration
817
+ await this.sendAIMessage({
818
+ recursionDepth: recursionDepth + 1,
819
+ model,
820
+ allowedRules,
821
+ maxTokens,
822
+ });
791
823
  }
792
- // Recursively call AI service, increment recursion depth, and pass same configuration
793
- await this.sendAIMessage({
794
- recursionDepth: recursionDepth + 1,
795
- model,
796
- allowedRules,
797
- maxTokens,
798
- });
799
824
  }
800
825
  }
801
826
  }
@@ -30,6 +30,7 @@ export declare class ForkedAgentManager {
30
30
  allowedTools?: string[];
31
31
  model?: string;
32
32
  permissionModeOverride?: PermissionMode;
33
+ maxTurns?: number;
33
34
  }, prompt: string): Promise<string>;
34
35
  private executeFork;
35
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"forkedAgentManager.d.ts","sourceRoot":"","sources":["../../src/managers/forkedAgentManager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;IAC3B,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA2B;IAC1C,yBAAyB,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CACnE;AAED,qBAAa,kBAAkB;IAK3B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,SAAS,CAA8B;gBAGrC,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,2BAA2B,CAAA;KAAO;IAK3D,OAAO,KAAK,eAAe,GAE1B;IAED;;;OAGG;IACG,cAAc,CAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,sBAAsB,CAAC,EAAE,cAAc,CAAC;KACzC,EACD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC;YA0BJ,WAAW;IA2EzB;;OAEG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAazB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,cAAc,IAAI,gBAAgB,EAAE;IAIpC,OAAO,CAAC,YAAY;CAKrB"}
1
+ {"version":3,"file":"forkedAgentManager.d.ts","sourceRoot":"","sources":["../../src/managers/forkedAgentManager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;IAC3B,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA2B;IAC1C,yBAAyB,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CACnE;AAED,qBAAa,kBAAkB;IAK3B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,SAAS,CAA8B;gBAGrC,SAAS,EAAE,SAAS,EAC5B,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,2BAA2B,CAAA;KAAO;IAK3D,OAAO,KAAK,eAAe,GAE1B;IAED;;;OAGG;IACG,cAAc,CAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,sBAAsB,CAAC,EAAE,cAAc,CAAC;QACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,EACD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC;YA0BJ,WAAW;IA6EzB;;OAEG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAazB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,cAAc,IAAI,gBAAgB,EAAE;IAIpC,OAAO,CAAC,YAAY;CAKrB"}
@@ -48,6 +48,7 @@ export class ForkedAgentManager {
48
48
  allowedTools: parameters.allowedTools,
49
49
  model: parameters.model,
50
50
  permissionModeOverride: parameters.permissionModeOverride,
51
+ maxTurns: parameters.maxTurns,
51
52
  }, false);
52
53
  // Pre-load the message manager with conversation history
53
54
  instance.messageManager.setMessages(messages);
@@ -93,6 +93,7 @@ export declare class SubagentManager {
93
93
  model?: string;
94
94
  stream?: boolean;
95
95
  permissionModeOverride?: PermissionMode;
96
+ maxTurns?: number;
96
97
  }, runInBackground?: boolean, onUpdate?: () => void): Promise<SubagentInstance>;
97
98
  /**
98
99
  * Execute agent using subagent instance
@@ -1 +1 @@
1
- {"version":3,"file":"subagentManager.d.ts","sourceRoot":"","sources":["../../src/managers/subagentManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subagentManager.d.ts","sourceRoot":"","sources":["../../src/managers/subagentManager.ts"],"names":[],"mappings":"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"}
@@ -203,6 +203,7 @@ export class SubagentManager {
203
203
  subagentType: parameters.subagent_type, // Pass subagent type for hook context
204
204
  modelOverride: parameters.model || configuration.model, // Pass model override
205
205
  stream: parameters.stream ?? this.stream, // Pass streaming mode flag
206
+ maxTurns: parameters.maxTurns, // Pass maxTurns limit
206
207
  callbacks: {
207
208
  onUsageAdded: this.onUsageAdded,
208
209
  },
@@ -1 +1 @@
1
- {"version":3,"file":"autoMemoryService.d.ts","sourceRoot":"","sources":["../../src/services/autoMemoryService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUlD;;;GAGG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,SAAS;IAH7B,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,wBAAwB,CAAa;gBAEzB,SAAS,EAAE,SAAS;IAExC,OAAO,KAAK,cAAc,GAEzB;IAED,OAAO,KAAK,kBAAkB,GAE7B;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,oBAAoB,GAE/B;IAED;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqE/C;;OAEG;YACW,aAAa;CA2D5B"}
1
+ {"version":3,"file":"autoMemoryService.d.ts","sourceRoot":"","sources":["../../src/services/autoMemoryService.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAUlD;;;GAGG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,SAAS;IAH7B,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,wBAAwB,CAAa;gBAEzB,SAAS,EAAE,SAAS;IAExC,OAAO,KAAK,cAAc,GAEzB;IAED,OAAO,KAAK,kBAAkB,GAE7B;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,oBAAoB,GAE/B;IAED;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqE/C;;OAEG;YACW,aAAa;CA4D5B"}
@@ -128,6 +128,7 @@ export class AutoMemoryService {
128
128
  ],
129
129
  model: "fastModel", // Use fast model for background tasks to reduce latency and cost
130
130
  permissionModeOverride: "dontAsk", // Auto-deny out-of-scope writes without prompting user
131
+ maxTurns: 5, // Limit turns to prevent verification rabbit-holes
131
132
  }, `${prompt}\n\nThe memory directory for this project is: ${memoryDir}`);
132
133
  logger.debug("Auto-memory extraction started in background.");
133
134
  }
@@ -21,6 +21,6 @@ export declare const USER_MEMORY_FILE: string;
21
21
  /**
22
22
  * AI related constants
23
23
  */
24
- export declare const DEFAULT_WAVE_MAX_INPUT_TOKENS = 96000;
25
- export declare const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 8192;
24
+ export declare const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000;
25
+ export declare const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384;
26
26
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAmC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAA6C,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAA0C,CAAC;AAE3E;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,6BAA6B,QAAQ,CAAC;AACnD,eAAO,MAAM,8BAA8B,OAAO,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAmC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAA6C,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAA0C,CAAC;AAE3E;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AAEvE;;GAEG;AACH,eAAO,MAAM,6BAA6B,SAAS,CAAC;AACpD,eAAO,MAAM,8BAA8B,QAAQ,CAAC"}
@@ -23,5 +23,5 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
23
23
  /**
24
24
  * AI related constants
25
25
  */
26
- export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 96000; // Default token limit
27
- export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 8192; // Default output token limit
26
+ export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000; // Default token limit
27
+ export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384; // Default output token limit
@@ -1 +1 @@
1
- {"version":3,"file":"convertMessagesForAPI.d.ts","sourceRoot":"","sources":["../../src/utils/convertMessagesForAPI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,OAAO,EAEL,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AA0B7B;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,EAAE,GAClB,0BAA0B,EAAE,CAsP9B"}
1
+ {"version":3,"file":"convertMessagesForAPI.d.ts","sourceRoot":"","sources":["../../src/utils/convertMessagesForAPI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAKjD,OAAO,EAEL,0BAA0B,EAC3B,MAAM,qBAAqB,CAAC;AAiC7B;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,EAAE,GAClB,0BAA0B,EAAE,CAsP9B"}
@@ -1,9 +1,10 @@
1
1
  import { convertImageToBase64 } from "./messageOperations.js";
2
2
  import { taskNotificationToXml } from "./notificationXml.js";
3
- import { stripAnsiColors } from "./stringUtils.js";
3
+ import { recoverTruncatedJson, stripAnsiColors } from "./stringUtils.js";
4
4
  import { logger } from "./globalLogger.js";
5
5
  /**
6
- * Safely handle tool call parameters, ensuring a legal JSON string is returned
6
+ * Safely handle tool call parameters, ensuring a legal JSON string is returned.
7
+ * Attempts to recover truncated JSON (e.g., missing closing braces).
7
8
  * @param args Tool call parameters
8
9
  * @returns Legal JSON string
9
10
  */
@@ -16,12 +17,19 @@ function safeToolArguments(args) {
16
17
  JSON.parse(args);
17
18
  return args;
18
19
  }
19
- catch (error) {
20
- logger.error(`Invalid tool arguments: ${args}`, error);
21
- // If not valid JSON, return a fallback empty object with the original string as a comment or property
22
- return JSON.stringify({
23
- invalid_arguments: args,
24
- });
20
+ catch {
21
+ // Attempt to recover truncated JSON
22
+ const recovered = recoverTruncatedJson(args);
23
+ try {
24
+ JSON.parse(recovered);
25
+ return recovered;
26
+ }
27
+ catch {
28
+ // Truly malformed JSON — return sanitized fallback
29
+ return JSON.stringify({
30
+ invalid_arguments: args,
31
+ });
32
+ }
25
33
  }
26
34
  }
27
35
  /**
@@ -23,6 +23,14 @@ export declare const stripAnsiColors: (text: string) => string;
23
23
  * @returns Formatted line number prefix
24
24
  */
25
25
  export declare function formatLineNumberPrefix(lineNumber: number): string;
26
+ /**
27
+ * Attempt to recover truncated JSON (e.g., missing closing braces due to max tokens).
28
+ * Tracks brace depth and only recovers if there are unclosed `{` braces.
29
+ * Will NOT recover if there are unclosed `[` brackets (can't guess the content).
30
+ * @param jsonStr Potentially truncated JSON string
31
+ * @returns Recovered JSON string, or the original if unrecoverable
32
+ */
33
+ export declare function recoverTruncatedJson(jsonStr: string): string;
26
34
  /**
27
35
  * Efficiently get the last N lines of a string without splitting the whole string.
28
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"stringUtils.d.ts","sourceRoot":"","sources":["../../src/utils/stringUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgC/D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBxB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MAK9C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAehE"}
1
+ {"version":3,"file":"stringUtils.d.ts","sourceRoot":"","sources":["../../src/utils/stringUtils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgC/D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBxB;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MAK9C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkC5D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAehE"}
@@ -77,6 +77,51 @@ export const stripAnsiColors = (text) => {
77
77
  export function formatLineNumberPrefix(lineNumber) {
78
78
  return `${lineNumber.toString().padStart(6)}\t`;
79
79
  }
80
+ /**
81
+ * Attempt to recover truncated JSON (e.g., missing closing braces due to max tokens).
82
+ * Tracks brace depth and only recovers if there are unclosed `{` braces.
83
+ * Will NOT recover if there are unclosed `[` brackets (can't guess the content).
84
+ * @param jsonStr Potentially truncated JSON string
85
+ * @returns Recovered JSON string, or the original if unrecoverable
86
+ */
87
+ export function recoverTruncatedJson(jsonStr) {
88
+ let braceDepth = 0;
89
+ let bracketDepth = 0;
90
+ let inString = false;
91
+ let escaped = false;
92
+ for (const ch of jsonStr) {
93
+ if (escaped) {
94
+ escaped = false;
95
+ continue;
96
+ }
97
+ if (ch === "\\" && inString) {
98
+ escaped = true;
99
+ continue;
100
+ }
101
+ if (ch === '"') {
102
+ inString = !inString;
103
+ continue;
104
+ }
105
+ if (!inString) {
106
+ if (ch === "{")
107
+ braceDepth++;
108
+ if (ch === "}")
109
+ braceDepth--;
110
+ if (ch === "[")
111
+ bracketDepth++;
112
+ if (ch === "]")
113
+ bracketDepth--;
114
+ }
115
+ }
116
+ // Build recovery suffix
117
+ let suffix = "";
118
+ if (inString)
119
+ suffix += '"'; // Close unclosed string
120
+ if (braceDepth > 0 && bracketDepth === 0) {
121
+ suffix += "}".repeat(braceDepth);
122
+ }
123
+ return suffix ? jsonStr + suffix : jsonStr;
124
+ }
80
125
  /**
81
126
  * Efficiently get the last N lines of a string without splitting the whole string.
82
127
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.14.3",
3
+ "version": "0.14.4",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -24,6 +24,7 @@ import type { SubagentManager } from "./subagentManager.js";
24
24
  import type { SkillManager } from "./skillManager.js";
25
25
  import { buildSystemPrompt } from "../prompts/index.js";
26
26
  import { Container } from "../utils/container.js";
27
+ import { recoverTruncatedJson } from "../utils/stringUtils.js";
27
28
  import { ConfigurationService } from "../services/configurationService.js";
28
29
  import type { NotificationQueue } from "./notificationQueue.js";
29
30
 
@@ -44,6 +45,8 @@ export interface AIManagerOptions {
44
45
  stream?: boolean;
45
46
  /**Optional model override (e.g. for subagents) */
46
47
  modelOverride?: string;
48
+ /**Optional max turns limit to prevent runaway recursion (e.g. for auto-memory extraction) */
49
+ maxTurns?: number;
47
50
  }
48
51
 
49
52
  export class AIManager {
@@ -59,6 +62,7 @@ export class AIManager {
59
62
  private modelOverride?: string;
60
63
  private _onCwdChange?: (newCwd: string) => void; // Store callback for CWD changes
61
64
  private consecutiveCompactionFailures: number = 0;
65
+ private readonly maxTurns?: number;
62
66
 
63
67
  // Service overrides
64
68
  constructor(
@@ -73,6 +77,7 @@ export class AIManager {
73
77
  this.callbacks = options.callbacks ?? {};
74
78
  this.modelOverride = options.modelOverride;
75
79
  this._onCwdChange = options.callbacks?.onCwdChange; // Initialize onCwdChange
80
+ this.maxTurns = options.maxTurns;
76
81
  }
77
82
 
78
83
  private get toolManager(): ToolManager {
@@ -814,34 +819,45 @@ export class AIManager {
814
819
  const toolName = functionToolCall.function?.name || "";
815
820
  // Safely parse tool parameters, handle tools without parameters
816
821
  let toolArgs: Record<string, unknown> = {};
822
+ let jsonRecovered = false;
817
823
  const argsString = functionToolCall.function?.arguments?.trim();
818
824
 
819
825
  if (!argsString || argsString === "") {
820
826
  // Tool without parameters, use empty object
821
827
  toolArgs = {};
822
828
  } else {
829
+ let recoveredArgs = argsString;
823
830
  try {
824
831
  toolArgs = JSON.parse(argsString);
825
- } catch (parseError) {
826
- // For non-empty but malformed JSON, still throw exception
827
- let errorMessage = `Failed to parse tool arguments`;
828
- if (result.finish_reason === "length") {
829
- errorMessage +=
830
- " (output truncated, please reduce your output)";
832
+ } catch {
833
+ // Attempt to recover truncated JSON (e.g., missing closing braces)
834
+ recoveredArgs = recoverTruncatedJson(argsString);
835
+ try {
836
+ toolArgs = JSON.parse(recoveredArgs);
837
+ jsonRecovered = true;
838
+ logger.warn(
839
+ `Recovered truncated JSON for tool "${toolName}"`,
840
+ );
841
+ } catch (parseError) {
842
+ let errorMessage = `Failed to parse tool arguments`;
843
+ if (result.finish_reason === "length") {
844
+ errorMessage +=
845
+ " (output truncated, please reduce your output)";
846
+ }
847
+ logger?.error(errorMessage, parseError);
848
+ this.messageManager.updateToolBlock({
849
+ id: toolId,
850
+ parameters: argsString,
851
+ result: errorMessage,
852
+ success: false,
853
+ error: errorMessage,
854
+ stage: "end",
855
+ name: toolName,
856
+ compactParams: "",
857
+ timestamp: Date.now(),
858
+ });
859
+ return;
831
860
  }
832
- logger?.error(errorMessage, parseError);
833
- this.messageManager.updateToolBlock({
834
- id: toolId,
835
- parameters: argsString,
836
- result: errorMessage,
837
- success: false,
838
- error: errorMessage,
839
- stage: "end",
840
- name: toolName,
841
- compactParams: "",
842
- timestamp: Date.now(),
843
- });
844
- return;
845
861
  }
846
862
  }
847
863
 
@@ -942,13 +958,20 @@ export class AIManager {
942
958
  context,
943
959
  );
944
960
 
961
+ // Build result content, adding truncation warning if JSON was recovered
962
+ let toolResultContent =
963
+ toolResult.content ||
964
+ (toolResult.error ? `Error: ${toolResult.error}` : "");
965
+ if (jsonRecovered) {
966
+ toolResultContent +=
967
+ "\n\n⚠️ Tool arguments were truncated (likely exceeded max output tokens). Please reduce your output or split into multiple tool calls.";
968
+ }
969
+
945
970
  // Update message state - tool execution completed
946
971
  this.messageManager.updateToolBlock({
947
972
  id: toolId,
948
973
  parameters: argsString,
949
- result:
950
- toolResult.content ||
951
- (toolResult.error ? `Error: ${toolResult.error}` : ""),
974
+ result: toolResultContent,
952
975
  success: toolResult.success,
953
976
  error: toolResult.error,
954
977
  stage: "end",
@@ -1001,108 +1024,116 @@ export class AIManager {
1001
1024
 
1002
1025
  // Check if there are tool operations or response was truncated, if so automatically initiate next AI service call
1003
1026
  if (toolCalls.length > 0 || result.finish_reason === "length") {
1004
- // Record committed snapshots to message history
1005
- if (this.reversionManager) {
1006
- const snapshots =
1007
- this.reversionManager.getAndClearCommittedSnapshots();
1008
- if (snapshots.length > 0) {
1009
- this.messageManager.addFileHistoryBlock(snapshots);
1010
- }
1011
- }
1012
-
1013
- // Check interruption status
1014
- const isCurrentlyAborted =
1015
- abortController.signal.aborted || toolAbortController.signal.aborted;
1016
-
1017
- // Check if all tools were manually backgrounded
1018
- const lastMessage =
1019
- this.messageManager.getMessages()[
1020
- this.messageManager.getMessages().length - 1
1021
- ];
1022
- const toolBlocks =
1023
- lastMessage?.blocks.filter(
1024
- (block): block is import("../types/messaging.js").ToolBlock =>
1025
- block.type === "tool",
1026
- ) || [];
1027
- const hasBackgrounded =
1028
- toolBlocks.length > 0 &&
1029
- toolBlocks.some((block) => block.isManuallyBackgrounded);
1030
-
1031
- if (hasBackgrounded) {
1032
- logger?.info(
1033
- "Some tools were manually backgrounded, stopping recursion.",
1027
+ // Check maxTurns limit before recursing
1028
+ if (this.maxTurns && recursionDepth + 1 >= this.maxTurns) {
1029
+ logger?.debug(
1030
+ `Max turns (${this.maxTurns}) reached, stopping recursion.`,
1034
1031
  );
1035
- } else if (!isCurrentlyAborted) {
1036
- // If response was truncated, add a hidden continuation message
1037
- if (result.finish_reason === "length") {
1038
- this.messageManager.addUserMessage({
1039
- content:
1040
- "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
1041
- isMeta: true,
1042
- });
1032
+ } else {
1033
+ // Record committed snapshots to message history
1034
+ if (this.reversionManager) {
1035
+ const snapshots =
1036
+ this.reversionManager.getAndClearCommittedSnapshots();
1037
+ if (snapshots.length > 0) {
1038
+ this.messageManager.addFileHistoryBlock(snapshots);
1039
+ }
1043
1040
  }
1044
1041
 
1045
- // Duplicate Tool Call Detection
1046
- if (toolCalls.length > 0) {
1047
- const messages = this.messageManager.getMessages();
1048
- // Find the most recent assistant message BEFORE the current one that has tool blocks
1049
- // The current assistant message is messages[messages.length - 1]
1050
- let previousAssistantWithTools: Message | undefined;
1051
- for (let i = messages.length - 2; i >= 0; i--) {
1052
- const msg = messages[i];
1053
- if (
1054
- msg.role === "assistant" &&
1055
- msg.blocks.some((b) => b.type === "tool")
1056
- ) {
1057
- previousAssistantWithTools = msg;
1058
- break;
1059
- }
1042
+ // Check interruption status
1043
+ const isCurrentlyAborted =
1044
+ abortController.signal.aborted ||
1045
+ toolAbortController.signal.aborted;
1046
+
1047
+ // Check if all tools were manually backgrounded
1048
+ const lastMessage =
1049
+ this.messageManager.getMessages()[
1050
+ this.messageManager.getMessages().length - 1
1051
+ ];
1052
+ const toolBlocks =
1053
+ lastMessage?.blocks.filter(
1054
+ (block): block is import("../types/messaging.js").ToolBlock =>
1055
+ block.type === "tool",
1056
+ ) || [];
1057
+ const hasBackgrounded =
1058
+ toolBlocks.length > 0 &&
1059
+ toolBlocks.some((block) => block.isManuallyBackgrounded);
1060
+
1061
+ if (hasBackgrounded) {
1062
+ logger?.info(
1063
+ "Some tools were manually backgrounded, stopping recursion.",
1064
+ );
1065
+ } else if (!isCurrentlyAborted) {
1066
+ // If response was truncated, add a hidden continuation message
1067
+ if (result.finish_reason === "length") {
1068
+ this.messageManager.addUserMessage({
1069
+ content:
1070
+ "Output token limit hit. Resume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.",
1071
+ isMeta: true,
1072
+ });
1060
1073
  }
1061
1074
 
1062
- if (previousAssistantWithTools) {
1063
- const previousToolBlocks =
1064
- previousAssistantWithTools.blocks.filter(
1065
- (b): b is import("../types/messaging.js").ToolBlock =>
1066
- b.type === "tool",
1067
- );
1075
+ // Duplicate Tool Call Detection
1076
+ if (toolCalls.length > 0) {
1077
+ const messages = this.messageManager.getMessages();
1078
+ // Find the most recent assistant message BEFORE the current one that has tool blocks
1079
+ // The current assistant message is messages[messages.length - 1]
1080
+ let previousAssistantWithTools: Message | undefined;
1081
+ for (let i = messages.length - 2; i >= 0; i--) {
1082
+ const msg = messages[i];
1083
+ if (
1084
+ msg.role === "assistant" &&
1085
+ msg.blocks.some((b) => b.type === "tool")
1086
+ ) {
1087
+ previousAssistantWithTools = msg;
1088
+ break;
1089
+ }
1090
+ }
1068
1091
 
1069
- for (const currentToolCall of toolCalls) {
1070
- const currentName = currentToolCall.function?.name;
1071
- const currentArgs = currentToolCall.function?.arguments;
1092
+ if (previousAssistantWithTools) {
1093
+ const previousToolBlocks =
1094
+ previousAssistantWithTools.blocks.filter(
1095
+ (b): b is import("../types/messaging.js").ToolBlock =>
1096
+ b.type === "tool",
1097
+ );
1072
1098
 
1073
- const isDuplicate = previousToolBlocks.some(
1074
- (prevBlock) =>
1075
- prevBlock.name === currentName &&
1076
- prevBlock.parameters === currentArgs,
1077
- );
1099
+ for (const currentToolCall of toolCalls) {
1100
+ const currentName = currentToolCall.function?.name;
1101
+ const currentArgs = currentToolCall.function?.arguments;
1078
1102
 
1079
- if (isDuplicate && currentName) {
1080
- const toolId = currentToolCall.id;
1081
- const lastMessage = messages[messages.length - 1];
1082
- const toolBlock = lastMessage.blocks.find(
1083
- (b): b is import("../types/messaging.js").ToolBlock =>
1084
- b.type === "tool" && b.id === toolId,
1103
+ const isDuplicate = previousToolBlocks.some(
1104
+ (prevBlock) =>
1105
+ prevBlock.name === currentName &&
1106
+ prevBlock.parameters === currentArgs,
1085
1107
  );
1086
- if (toolBlock) {
1087
- const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
1088
- this.messageManager.updateToolBlock({
1089
- id: toolId,
1090
- result: (toolBlock.result || "") + warning,
1091
- stage: "end",
1092
- });
1108
+
1109
+ if (isDuplicate && currentName) {
1110
+ const toolId = currentToolCall.id;
1111
+ const lastMessage = messages[messages.length - 1];
1112
+ const toolBlock = lastMessage.blocks.find(
1113
+ (b): b is import("../types/messaging.js").ToolBlock =>
1114
+ b.type === "tool" && b.id === toolId,
1115
+ );
1116
+ if (toolBlock) {
1117
+ const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
1118
+ this.messageManager.updateToolBlock({
1119
+ id: toolId,
1120
+ result: (toolBlock.result || "") + warning,
1121
+ stage: "end",
1122
+ });
1123
+ }
1093
1124
  }
1094
1125
  }
1095
1126
  }
1096
1127
  }
1097
- }
1098
1128
 
1099
- // Recursively call AI service, increment recursion depth, and pass same configuration
1100
- await this.sendAIMessage({
1101
- recursionDepth: recursionDepth + 1,
1102
- model,
1103
- allowedRules,
1104
- maxTokens,
1105
- });
1129
+ // Recursively call AI service, increment recursion depth, and pass same configuration
1130
+ await this.sendAIMessage({
1131
+ recursionDepth: recursionDepth + 1,
1132
+ model,
1133
+ allowedRules,
1134
+ maxTokens,
1135
+ });
1136
+ }
1106
1137
  }
1107
1138
  }
1108
1139
  } catch (error) {
@@ -47,6 +47,7 @@ export class ForkedAgentManager {
47
47
  allowedTools?: string[];
48
48
  model?: string;
49
49
  permissionModeOverride?: PermissionMode;
50
+ maxTurns?: number;
50
51
  },
51
52
  prompt: string,
52
53
  ): Promise<string> {
@@ -84,6 +85,7 @@ export class ForkedAgentManager {
84
85
  allowedTools?: string[];
85
86
  model?: string;
86
87
  permissionModeOverride?: PermissionMode;
88
+ maxTurns?: number;
87
89
  },
88
90
  prompt: string,
89
91
  ): Promise<void> {
@@ -103,6 +105,7 @@ export class ForkedAgentManager {
103
105
  allowedTools: parameters.allowedTools,
104
106
  model: parameters.model,
105
107
  permissionModeOverride: parameters.permissionModeOverride,
108
+ maxTurns: parameters.maxTurns,
106
109
  },
107
110
  false,
108
111
  );
@@ -251,6 +251,7 @@ export class SubagentManager {
251
251
  model?: string;
252
252
  stream?: boolean;
253
253
  permissionModeOverride?: PermissionMode;
254
+ maxTurns?: number;
254
255
  },
255
256
  runInBackground?: boolean,
256
257
  onUpdate?: () => void,
@@ -356,6 +357,7 @@ export class SubagentManager {
356
357
  subagentType: parameters.subagent_type, // Pass subagent type for hook context
357
358
  modelOverride: parameters.model || configuration.model, // Pass model override
358
359
  stream: parameters.stream ?? this.stream, // Pass streaming mode flag
360
+ maxTurns: parameters.maxTurns, // Pass maxTurns limit
359
361
  callbacks: {
360
362
  onUsageAdded: this.onUsageAdded,
361
363
  },
@@ -164,6 +164,7 @@ export class AutoMemoryService {
164
164
  ],
165
165
  model: "fastModel", // Use fast model for background tasks to reduce latency and cost
166
166
  permissionModeOverride: "dontAsk", // Auto-deny out-of-scope writes without prompting user
167
+ maxTurns: 5, // Limit turns to prevent verification rabbit-holes
167
168
  },
168
169
  `${prompt}\n\nThe memory directory for this project is: ${memoryDir}`,
169
170
  );
@@ -29,5 +29,5 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "AGENTS.md");
29
29
  /**
30
30
  * AI related constants
31
31
  */
32
- export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 96000; // Default token limit
33
- export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 8192; // Default output token limit
32
+ export const DEFAULT_WAVE_MAX_INPUT_TOKENS = 128000; // Default token limit
33
+ export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS = 16384; // Default output token limit
@@ -2,7 +2,7 @@ import type { Message } from "../types/index.js";
2
2
  import { convertImageToBase64 } from "./messageOperations.js";
3
3
  import { taskNotificationToXml } from "./notificationXml.js";
4
4
  import { ChatCompletionMessageToolCall } from "openai/resources";
5
- import { stripAnsiColors } from "./stringUtils.js";
5
+ import { recoverTruncatedJson, stripAnsiColors } from "./stringUtils.js";
6
6
  import {
7
7
  ChatCompletionContentPart,
8
8
  ChatCompletionMessageParam,
@@ -10,7 +10,8 @@ import {
10
10
  import { logger } from "./globalLogger.js";
11
11
 
12
12
  /**
13
- * Safely handle tool call parameters, ensuring a legal JSON string is returned
13
+ * Safely handle tool call parameters, ensuring a legal JSON string is returned.
14
+ * Attempts to recover truncated JSON (e.g., missing closing braces).
14
15
  * @param args Tool call parameters
15
16
  * @returns Legal JSON string
16
17
  */
@@ -23,12 +24,18 @@ function safeToolArguments(args: string): string {
23
24
  // Try to parse as JSON to validate format
24
25
  JSON.parse(args);
25
26
  return args;
26
- } catch (error) {
27
- logger.error(`Invalid tool arguments: ${args}`, error);
28
- // If not valid JSON, return a fallback empty object with the original string as a comment or property
29
- return JSON.stringify({
30
- invalid_arguments: args,
31
- });
27
+ } catch {
28
+ // Attempt to recover truncated JSON
29
+ const recovered = recoverTruncatedJson(args);
30
+ try {
31
+ JSON.parse(recovered);
32
+ return recovered;
33
+ } catch {
34
+ // Truly malformed JSON — return sanitized fallback
35
+ return JSON.stringify({
36
+ invalid_arguments: args,
37
+ });
38
+ }
32
39
  }
33
40
  }
34
41
 
@@ -92,6 +92,49 @@ export function formatLineNumberPrefix(lineNumber: number): string {
92
92
  return `${lineNumber.toString().padStart(6)}\t`;
93
93
  }
94
94
 
95
+ /**
96
+ * Attempt to recover truncated JSON (e.g., missing closing braces due to max tokens).
97
+ * Tracks brace depth and only recovers if there are unclosed `{` braces.
98
+ * Will NOT recover if there are unclosed `[` brackets (can't guess the content).
99
+ * @param jsonStr Potentially truncated JSON string
100
+ * @returns Recovered JSON string, or the original if unrecoverable
101
+ */
102
+ export function recoverTruncatedJson(jsonStr: string): string {
103
+ let braceDepth = 0;
104
+ let bracketDepth = 0;
105
+ let inString = false;
106
+ let escaped = false;
107
+
108
+ for (const ch of jsonStr) {
109
+ if (escaped) {
110
+ escaped = false;
111
+ continue;
112
+ }
113
+ if (ch === "\\" && inString) {
114
+ escaped = true;
115
+ continue;
116
+ }
117
+ if (ch === '"') {
118
+ inString = !inString;
119
+ continue;
120
+ }
121
+ if (!inString) {
122
+ if (ch === "{") braceDepth++;
123
+ if (ch === "}") braceDepth--;
124
+ if (ch === "[") bracketDepth++;
125
+ if (ch === "]") bracketDepth--;
126
+ }
127
+ }
128
+
129
+ // Build recovery suffix
130
+ let suffix = "";
131
+ if (inString) suffix += '"'; // Close unclosed string
132
+ if (braceDepth > 0 && bracketDepth === 0) {
133
+ suffix += "}".repeat(braceDepth);
134
+ }
135
+ return suffix ? jsonStr + suffix : jsonStr;
136
+ }
137
+
95
138
  /**
96
139
  * Efficiently get the last N lines of a string without splitting the whole string.
97
140
  */