wave-agent-sdk 0.14.2 → 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.
- package/builtin/skills/settings/SKILL.md +1 -1
- package/builtin/skills/settings/SKILLS.md +3 -0
- package/builtin/skills/settings/SUBAGENTS.md +21 -2
- package/dist/managers/aiManager.d.ts +3 -0
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +107 -82
- package/dist/managers/forkedAgentManager.d.ts +1 -0
- package/dist/managers/forkedAgentManager.d.ts.map +1 -1
- package/dist/managers/forkedAgentManager.js +1 -0
- package/dist/managers/pluginManager.d.ts +1 -0
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +7 -0
- package/dist/managers/subagentManager.d.ts +6 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +36 -0
- package/dist/services/autoMemoryService.d.ts.map +1 -1
- package/dist/services/autoMemoryService.js +1 -0
- package/dist/services/pluginLoader.d.ts +5 -0
- package/dist/services/pluginLoader.d.ts.map +1 -1
- package/dist/services/pluginLoader.js +29 -0
- package/dist/types/plugins.d.ts +2 -0
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +2 -2
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +27 -8
- package/dist/utils/stringUtils.d.ts +8 -0
- package/dist/utils/stringUtils.d.ts.map +1 -1
- package/dist/utils/stringUtils.js +45 -0
- package/dist/utils/subagentParser.d.ts +8 -1
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +18 -3
- package/package.json +1 -1
- package/src/managers/aiManager.ts +141 -110
- package/src/managers/forkedAgentManager.ts +3 -0
- package/src/managers/pluginManager.ts +10 -0
- package/src/managers/subagentManager.ts +49 -0
- package/src/services/autoMemoryService.ts +1 -0
- package/src/services/pluginLoader.ts +37 -0
- package/src/types/plugins.ts +2 -0
- package/src/utils/constants.ts +2 -2
- package/src/utils/convertMessagesForAPI.ts +31 -9
- package/src/utils/stringUtils.ts +43 -0
- package/src/utils/subagentParser.ts +31 -4
|
@@ -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,
|
|
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"}
|
package/dist/utils/constants.js
CHANGED
|
@@ -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 =
|
|
27
|
-
export const DEFAULT_WAVE_MAX_OUTPUT_TOKENS =
|
|
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;
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
/**
|
|
@@ -114,6 +122,16 @@ export function convertMessagesForAPI(messages) {
|
|
|
114
122
|
.map((block) => (block.type === "text" ? block.content : ""))
|
|
115
123
|
.join("\n");
|
|
116
124
|
}
|
|
125
|
+
// Extract reasoning content from reasoning blocks
|
|
126
|
+
const reasoningBlocks = message.blocks.filter((block) => block.type === "reasoning" &&
|
|
127
|
+
block.content &&
|
|
128
|
+
block.content.trim().length > 0);
|
|
129
|
+
let reasoning_content;
|
|
130
|
+
if (reasoningBlocks.length > 0) {
|
|
131
|
+
reasoning_content = reasoningBlocks
|
|
132
|
+
.map((block) => (block.type === "reasoning" ? block.content : ""))
|
|
133
|
+
.join("\n");
|
|
134
|
+
}
|
|
117
135
|
// Construct tool calls from tool blocks
|
|
118
136
|
if (toolBlocks.length > 0) {
|
|
119
137
|
tool_calls = toolBlocks
|
|
@@ -138,6 +156,7 @@ export function convertMessagesForAPI(messages) {
|
|
|
138
156
|
role: "assistant",
|
|
139
157
|
content: hasContent ? content : undefined,
|
|
140
158
|
tool_calls,
|
|
159
|
+
...(reasoning_content ? { reasoning_content } : {}),
|
|
141
160
|
...(message.additionalFields ? { ...message.additionalFields } : {}),
|
|
142
161
|
};
|
|
143
162
|
recentMessages.unshift(assistantMessage);
|
|
@@ -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
|
*/
|
|
@@ -5,9 +5,16 @@ export interface SubagentConfiguration {
|
|
|
5
5
|
model?: string;
|
|
6
6
|
systemPrompt: string;
|
|
7
7
|
filePath: string;
|
|
8
|
-
scope: "project" | "user" | "builtin";
|
|
8
|
+
scope: "project" | "user" | "builtin" | "plugin";
|
|
9
9
|
priority: number;
|
|
10
|
+
/** Plugin root directory path, set when scope is "plugin" */
|
|
11
|
+
pluginRoot?: string;
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse a plugin agent markdown file.
|
|
15
|
+
* Exposed as a public API for PluginLoader to use.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseAgentFile(filePath: string, scope: "plugin", pluginRoot: string): SubagentConfiguration;
|
|
11
18
|
/**
|
|
12
19
|
* Load all subagent configurations from project and user directories, plus built-in subagents
|
|
13
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkKD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,MAAM,GACjB,qBAAqB,CAEvB;AAqCD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAsBlC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
|
|
@@ -80,9 +80,9 @@ function validateConfiguration(config, filePath) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Parse a single subagent markdown file
|
|
83
|
+
* Parse a single subagent markdown file with optional pluginRoot support
|
|
84
84
|
*/
|
|
85
|
-
function parseSubagentFile(filePath, scope) {
|
|
85
|
+
function parseSubagentFile(filePath, scope, pluginRoot) {
|
|
86
86
|
try {
|
|
87
87
|
const content = readFileSync(filePath, "utf-8");
|
|
88
88
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
@@ -99,21 +99,36 @@ function parseSubagentFile(filePath, scope) {
|
|
|
99
99
|
priority = 2;
|
|
100
100
|
if (scope === "builtin")
|
|
101
101
|
priority = 3;
|
|
102
|
+
if (scope === "plugin")
|
|
103
|
+
priority = 2; // Same priority as user-level
|
|
104
|
+
let systemPrompt = body;
|
|
105
|
+
// Substitute ${WAVE_PLUGIN_ROOT} for plugin scope at parse time
|
|
106
|
+
if (scope === "plugin" && pluginRoot) {
|
|
107
|
+
systemPrompt = systemPrompt.replace(/\$\{WAVE_PLUGIN_ROOT\}/g, pluginRoot);
|
|
108
|
+
}
|
|
102
109
|
return {
|
|
103
110
|
name: frontmatter.name,
|
|
104
111
|
description: frontmatter.description,
|
|
105
112
|
tools: frontmatter.tools,
|
|
106
113
|
model: frontmatter.model,
|
|
107
|
-
systemPrompt
|
|
114
|
+
systemPrompt,
|
|
108
115
|
filePath,
|
|
109
116
|
scope,
|
|
110
117
|
priority,
|
|
118
|
+
pluginRoot: scope === "plugin" ? pluginRoot : undefined,
|
|
111
119
|
};
|
|
112
120
|
}
|
|
113
121
|
catch (error) {
|
|
114
122
|
throw new Error(`Failed to parse subagent file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
123
|
}
|
|
116
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse a plugin agent markdown file.
|
|
127
|
+
* Exposed as a public API for PluginLoader to use.
|
|
128
|
+
*/
|
|
129
|
+
export function parseAgentFile(filePath, scope, pluginRoot) {
|
|
130
|
+
return parseSubagentFile(filePath, scope, pluginRoot);
|
|
131
|
+
}
|
|
117
132
|
/**
|
|
118
133
|
* Scan directory for subagent files
|
|
119
134
|
*/
|
package/package.json
CHANGED
|
@@ -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
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
//
|
|
1005
|
-
if (this.
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
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
|
|
1036
|
-
//
|
|
1037
|
-
if (
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
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
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
);
|
|
@@ -7,6 +7,7 @@ import { HookManager } from "./hookManager.js";
|
|
|
7
7
|
import { LspManager } from "./lspManager.js";
|
|
8
8
|
import { McpManager } from "./mcpManager.js";
|
|
9
9
|
import { SlashCommandManager } from "./slashCommandManager.js";
|
|
10
|
+
import { SubagentManager } from "./subagentManager.js";
|
|
10
11
|
import { MarketplaceService } from "../services/MarketplaceService.js";
|
|
11
12
|
import { ConfigurationService } from "../services/configurationService.js";
|
|
12
13
|
import { Container } from "../utils/container.js";
|
|
@@ -53,6 +54,10 @@ export class PluginManager {
|
|
|
53
54
|
return this.container.get<ConfigurationService>("ConfigurationService");
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
private get subagentManager(): SubagentManager | undefined {
|
|
58
|
+
return this.container.get<SubagentManager>("SubagentManager");
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
/**
|
|
57
62
|
* Update enabled plugins configuration
|
|
58
63
|
*/
|
|
@@ -155,6 +160,7 @@ export class PluginManager {
|
|
|
155
160
|
path: absolutePath,
|
|
156
161
|
commands: PluginLoader.loadCommands(absolutePath),
|
|
157
162
|
skills: await PluginLoader.loadSkills(absolutePath),
|
|
163
|
+
agents: await PluginLoader.loadAgents(absolutePath),
|
|
158
164
|
lspConfig: await PluginLoader.loadLspConfig(absolutePath),
|
|
159
165
|
mcpConfig: await PluginLoader.loadMcpConfig(absolutePath),
|
|
160
166
|
hooksConfig: await PluginLoader.loadHooksConfig(absolutePath),
|
|
@@ -192,6 +198,10 @@ export class PluginManager {
|
|
|
192
198
|
this.hookManager.registerPluginHooks(plugin.path, plugin.hooksConfig);
|
|
193
199
|
}
|
|
194
200
|
|
|
201
|
+
if (this.subagentManager && plugin.agents.length > 0) {
|
|
202
|
+
this.subagentManager.registerPluginAgents(plugin.name, plugin.agents);
|
|
203
|
+
}
|
|
204
|
+
|
|
195
205
|
this.plugins.set(manifest.name, plugin);
|
|
196
206
|
logger?.info(`Loaded plugin: ${manifest.name} v${manifest.version}`);
|
|
197
207
|
} catch (error) {
|