wave-agent-sdk 0.14.3 → 0.15.0
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/SKILLS.md +34 -6
- package/dist/agent.d.ts +0 -5
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +0 -15
- package/dist/constants/toolLimits.d.ts +10 -0
- package/dist/constants/toolLimits.d.ts.map +1 -0
- package/dist/constants/toolLimits.js +9 -0
- package/dist/managers/aiManager.d.ts +3 -5
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +107 -104
- 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/hookManager.d.ts +0 -4
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +0 -25
- package/dist/managers/permissionManager.d.ts +1 -1
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +5 -5
- package/dist/managers/subagentManager.d.ts +1 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +1 -0
- package/dist/prompts/index.d.ts +0 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +3 -4
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +10 -8
- package/dist/services/autoMemoryService.d.ts.map +1 -1
- package/dist/services/autoMemoryService.js +1 -0
- package/dist/services/hook.d.ts +0 -4
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +0 -10
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +4 -1
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +2 -45
- package/dist/tools/editTool.js +1 -1
- package/dist/tools/types.d.ts +0 -3
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/agent.d.ts +0 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +1 -5
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +0 -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 +16 -8
- package/dist/utils/editUtils.d.ts +5 -2
- package/dist/utils/editUtils.d.ts.map +1 -1
- package/dist/utils/editUtils.js +3 -57
- package/dist/utils/markdownParser.d.ts +8 -1
- package/dist/utils/markdownParser.d.ts.map +1 -1
- package/dist/utils/markdownParser.js +64 -11
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +0 -11
- 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/package.json +1 -1
- package/src/agent.ts +0 -17
- package/src/constants/toolLimits.ts +12 -0
- package/src/managers/aiManager.ts +141 -148
- package/src/managers/forkedAgentManager.ts +3 -0
- package/src/managers/hookManager.ts +0 -32
- package/src/managers/permissionManager.ts +6 -6
- package/src/managers/subagentManager.ts +2 -0
- package/src/prompts/index.ts +3 -5
- package/src/services/aiService.ts +10 -12
- package/src/services/autoMemoryService.ts +1 -0
- package/src/services/hook.ts +0 -15
- package/src/services/session.ts +6 -1
- package/src/tools/bashTool.ts +2 -51
- package/src/tools/editTool.ts +1 -1
- package/src/tools/types.ts +0 -3
- package/src/types/agent.ts +0 -1
- package/src/types/hooks.ts +1 -7
- package/src/utils/constants.ts +2 -2
- package/src/utils/convertMessagesForAPI.ts +15 -8
- package/src/utils/editUtils.ts +3 -73
- package/src/utils/markdownParser.ts +85 -11
- package/src/utils/openaiClient.ts +0 -11
- package/src/utils/stringUtils.ts +43 -0
|
@@ -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
|
|
|
@@ -32,7 +33,6 @@ import { logger } from "../utils/globalLogger.js";
|
|
|
32
33
|
export interface AIManagerCallbacks {
|
|
33
34
|
onCompactionStateChange?: (isCompacting: boolean) => void;
|
|
34
35
|
onUsageAdded?: (usage: Usage) => void;
|
|
35
|
-
onCwdChange?: (newCwd: string) => void;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface AIManagerOptions {
|
|
@@ -44,6 +44,8 @@ export interface AIManagerOptions {
|
|
|
44
44
|
stream?: boolean;
|
|
45
45
|
/**Optional model override (e.g. for subagents) */
|
|
46
46
|
modelOverride?: string;
|
|
47
|
+
/**Optional max turns limit to prevent runaway recursion (e.g. for auto-memory extraction) */
|
|
48
|
+
maxTurns?: number;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export class AIManager {
|
|
@@ -52,13 +54,12 @@ export class AIManager {
|
|
|
52
54
|
onLoadingChange?: (loading: boolean) => void;
|
|
53
55
|
private toolAbortController: AbortController | null = null;
|
|
54
56
|
private workdir: string;
|
|
55
|
-
private originalWorkdir: string;
|
|
56
57
|
private systemPrompt?: string;
|
|
57
58
|
private subagentType?: string; // Store subagent type for hook context
|
|
58
59
|
private stream: boolean; // Streaming mode flag
|
|
59
60
|
private modelOverride?: string;
|
|
60
|
-
private _onCwdChange?: (newCwd: string) => void; // Store callback for CWD changes
|
|
61
61
|
private consecutiveCompactionFailures: number = 0;
|
|
62
|
+
private readonly maxTurns?: number;
|
|
62
63
|
|
|
63
64
|
// Service overrides
|
|
64
65
|
constructor(
|
|
@@ -66,13 +67,12 @@ export class AIManager {
|
|
|
66
67
|
options: AIManagerOptions,
|
|
67
68
|
) {
|
|
68
69
|
this.workdir = options.workdir;
|
|
69
|
-
this.originalWorkdir = options.workdir;
|
|
70
70
|
this.systemPrompt = options.systemPrompt;
|
|
71
71
|
this.subagentType = options.subagentType; // Store subagent type
|
|
72
72
|
this.stream = options.stream ?? true; // Default to true if not specified
|
|
73
73
|
this.callbacks = options.callbacks ?? {};
|
|
74
74
|
this.modelOverride = options.modelOverride;
|
|
75
|
-
this.
|
|
75
|
+
this.maxTurns = options.maxTurns;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
private get toolManager(): ToolManager {
|
|
@@ -169,14 +169,6 @@ export class AIManager {
|
|
|
169
169
|
return this.workdir;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
public getOriginalWorkdir(): string {
|
|
173
|
-
return this.originalWorkdir;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
public setOnCwdChange(callback: (newCwd: string) => void): void {
|
|
177
|
-
this._onCwdChange = callback;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
172
|
private isCompacting: boolean = false;
|
|
181
173
|
private callbacks: AIManagerCallbacks;
|
|
182
174
|
|
|
@@ -242,7 +234,6 @@ export class AIManager {
|
|
|
242
234
|
if (toolPlugin?.formatCompactParams) {
|
|
243
235
|
const context: ToolContext = {
|
|
244
236
|
workdir: this.workdir,
|
|
245
|
-
originalWorkdir: this.originalWorkdir,
|
|
246
237
|
taskManager: this.taskManager,
|
|
247
238
|
};
|
|
248
239
|
return toolPlugin.formatCompactParams(toolArgs, context);
|
|
@@ -652,7 +643,6 @@ export class AIManager {
|
|
|
652
643
|
filteredToolPlugins,
|
|
653
644
|
{
|
|
654
645
|
workdir: this.workdir,
|
|
655
|
-
originalWorkdir: this.originalWorkdir,
|
|
656
646
|
memory: combinedMemory,
|
|
657
647
|
language: this.getLanguage(),
|
|
658
648
|
isSubagent: !!this.subagentType,
|
|
@@ -814,34 +804,45 @@ export class AIManager {
|
|
|
814
804
|
const toolName = functionToolCall.function?.name || "";
|
|
815
805
|
// Safely parse tool parameters, handle tools without parameters
|
|
816
806
|
let toolArgs: Record<string, unknown> = {};
|
|
807
|
+
let jsonRecovered = false;
|
|
817
808
|
const argsString = functionToolCall.function?.arguments?.trim();
|
|
818
809
|
|
|
819
810
|
if (!argsString || argsString === "") {
|
|
820
811
|
// Tool without parameters, use empty object
|
|
821
812
|
toolArgs = {};
|
|
822
813
|
} else {
|
|
814
|
+
let recoveredArgs = argsString;
|
|
823
815
|
try {
|
|
824
816
|
toolArgs = JSON.parse(argsString);
|
|
825
|
-
} catch
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
817
|
+
} catch {
|
|
818
|
+
// Attempt to recover truncated JSON (e.g., missing closing braces)
|
|
819
|
+
recoveredArgs = recoverTruncatedJson(argsString);
|
|
820
|
+
try {
|
|
821
|
+
toolArgs = JSON.parse(recoveredArgs);
|
|
822
|
+
jsonRecovered = true;
|
|
823
|
+
logger.warn(
|
|
824
|
+
`Recovered truncated JSON for tool "${toolName}"`,
|
|
825
|
+
);
|
|
826
|
+
} catch (parseError) {
|
|
827
|
+
let errorMessage = `Failed to parse tool arguments`;
|
|
828
|
+
if (result.finish_reason === "length") {
|
|
829
|
+
errorMessage +=
|
|
830
|
+
" (output truncated, please reduce your output)";
|
|
831
|
+
}
|
|
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;
|
|
831
845
|
}
|
|
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
846
|
}
|
|
846
847
|
}
|
|
847
848
|
|
|
@@ -892,7 +893,6 @@ export class AIManager {
|
|
|
892
893
|
abortSignal: toolAbortController.signal,
|
|
893
894
|
backgroundTaskManager: this.backgroundTaskManager,
|
|
894
895
|
workdir: this.workdir,
|
|
895
|
-
originalWorkdir: this.originalWorkdir,
|
|
896
896
|
messageId: this.messageManager.getMessages().slice(-1)[0]?.id,
|
|
897
897
|
sessionId: this.messageManager.getSessionId(),
|
|
898
898
|
toolCallId: toolId,
|
|
@@ -911,28 +911,6 @@ export class AIManager {
|
|
|
911
911
|
stage: "running", // Keep it in running stage while updating result
|
|
912
912
|
});
|
|
913
913
|
},
|
|
914
|
-
onCwdChange: async (newCwd: string) => {
|
|
915
|
-
const oldCwd = this.workdir;
|
|
916
|
-
this.workdir = newCwd;
|
|
917
|
-
this._onCwdChange?.(newCwd);
|
|
918
|
-
if (this.hookManager) {
|
|
919
|
-
const sessionId = this.messageManager.getSessionId();
|
|
920
|
-
const transcriptPath =
|
|
921
|
-
this.messageManager.getTranscriptPath();
|
|
922
|
-
const env = Object.fromEntries(
|
|
923
|
-
Object.entries(process.env).filter(
|
|
924
|
-
(e) => e[1] !== undefined,
|
|
925
|
-
),
|
|
926
|
-
) as Record<string, string>;
|
|
927
|
-
await this.hookManager.executeCwdChangedHooks(
|
|
928
|
-
oldCwd,
|
|
929
|
-
newCwd,
|
|
930
|
-
sessionId,
|
|
931
|
-
transcriptPath,
|
|
932
|
-
env,
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
},
|
|
936
914
|
};
|
|
937
915
|
|
|
938
916
|
// Execute tool
|
|
@@ -942,13 +920,20 @@ export class AIManager {
|
|
|
942
920
|
context,
|
|
943
921
|
);
|
|
944
922
|
|
|
923
|
+
// Build result content, adding truncation warning if JSON was recovered
|
|
924
|
+
let toolResultContent =
|
|
925
|
+
toolResult.content ||
|
|
926
|
+
(toolResult.error ? `Error: ${toolResult.error}` : "");
|
|
927
|
+
if (jsonRecovered) {
|
|
928
|
+
toolResultContent +=
|
|
929
|
+
"\n\n⚠️ Tool arguments were truncated (likely exceeded max output tokens). Please reduce your output or split into multiple tool calls.";
|
|
930
|
+
}
|
|
931
|
+
|
|
945
932
|
// Update message state - tool execution completed
|
|
946
933
|
this.messageManager.updateToolBlock({
|
|
947
934
|
id: toolId,
|
|
948
935
|
parameters: argsString,
|
|
949
|
-
result:
|
|
950
|
-
toolResult.content ||
|
|
951
|
-
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
936
|
+
result: toolResultContent,
|
|
952
937
|
success: toolResult.success,
|
|
953
938
|
error: toolResult.error,
|
|
954
939
|
stage: "end",
|
|
@@ -1001,108 +986,116 @@ export class AIManager {
|
|
|
1001
986
|
|
|
1002
987
|
// Check if there are tool operations or response was truncated, if so automatically initiate next AI service call
|
|
1003
988
|
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.",
|
|
989
|
+
// Check maxTurns limit before recursing
|
|
990
|
+
if (this.maxTurns && recursionDepth + 1 >= this.maxTurns) {
|
|
991
|
+
logger?.debug(
|
|
992
|
+
`Max turns (${this.maxTurns}) reached, stopping recursion.`,
|
|
1034
993
|
);
|
|
1035
|
-
} else
|
|
1036
|
-
//
|
|
1037
|
-
if (
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
994
|
+
} else {
|
|
995
|
+
// Record committed snapshots to message history
|
|
996
|
+
if (this.reversionManager) {
|
|
997
|
+
const snapshots =
|
|
998
|
+
this.reversionManager.getAndClearCommittedSnapshots();
|
|
999
|
+
if (snapshots.length > 0) {
|
|
1000
|
+
this.messageManager.addFileHistoryBlock(snapshots);
|
|
1001
|
+
}
|
|
1043
1002
|
}
|
|
1044
1003
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1004
|
+
// Check interruption status
|
|
1005
|
+
const isCurrentlyAborted =
|
|
1006
|
+
abortController.signal.aborted ||
|
|
1007
|
+
toolAbortController.signal.aborted;
|
|
1008
|
+
|
|
1009
|
+
// Check if all tools were manually backgrounded
|
|
1010
|
+
const lastMessage =
|
|
1011
|
+
this.messageManager.getMessages()[
|
|
1012
|
+
this.messageManager.getMessages().length - 1
|
|
1013
|
+
];
|
|
1014
|
+
const toolBlocks =
|
|
1015
|
+
lastMessage?.blocks.filter(
|
|
1016
|
+
(block): block is import("../types/messaging.js").ToolBlock =>
|
|
1017
|
+
block.type === "tool",
|
|
1018
|
+
) || [];
|
|
1019
|
+
const hasBackgrounded =
|
|
1020
|
+
toolBlocks.length > 0 &&
|
|
1021
|
+
toolBlocks.some((block) => block.isManuallyBackgrounded);
|
|
1022
|
+
|
|
1023
|
+
if (hasBackgrounded) {
|
|
1024
|
+
logger?.info(
|
|
1025
|
+
"Some tools were manually backgrounded, stopping recursion.",
|
|
1026
|
+
);
|
|
1027
|
+
} else if (!isCurrentlyAborted) {
|
|
1028
|
+
// If response was truncated, add a hidden continuation message
|
|
1029
|
+
if (result.finish_reason === "length") {
|
|
1030
|
+
this.messageManager.addUserMessage({
|
|
1031
|
+
content:
|
|
1032
|
+
"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.",
|
|
1033
|
+
isMeta: true,
|
|
1034
|
+
});
|
|
1060
1035
|
}
|
|
1061
1036
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1037
|
+
// Duplicate Tool Call Detection
|
|
1038
|
+
if (toolCalls.length > 0) {
|
|
1039
|
+
const messages = this.messageManager.getMessages();
|
|
1040
|
+
// Find the most recent assistant message BEFORE the current one that has tool blocks
|
|
1041
|
+
// The current assistant message is messages[messages.length - 1]
|
|
1042
|
+
let previousAssistantWithTools: Message | undefined;
|
|
1043
|
+
for (let i = messages.length - 2; i >= 0; i--) {
|
|
1044
|
+
const msg = messages[i];
|
|
1045
|
+
if (
|
|
1046
|
+
msg.role === "assistant" &&
|
|
1047
|
+
msg.blocks.some((b) => b.type === "tool")
|
|
1048
|
+
) {
|
|
1049
|
+
previousAssistantWithTools = msg;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1068
1053
|
|
|
1069
|
-
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1054
|
+
if (previousAssistantWithTools) {
|
|
1055
|
+
const previousToolBlocks =
|
|
1056
|
+
previousAssistantWithTools.blocks.filter(
|
|
1057
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
1058
|
+
b.type === "tool",
|
|
1059
|
+
);
|
|
1072
1060
|
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
prevBlock.parameters === currentArgs,
|
|
1077
|
-
);
|
|
1061
|
+
for (const currentToolCall of toolCalls) {
|
|
1062
|
+
const currentName = currentToolCall.function?.name;
|
|
1063
|
+
const currentArgs = currentToolCall.function?.arguments;
|
|
1078
1064
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
1084
|
-
b.type === "tool" && b.id === toolId,
|
|
1065
|
+
const isDuplicate = previousToolBlocks.some(
|
|
1066
|
+
(prevBlock) =>
|
|
1067
|
+
prevBlock.name === currentName &&
|
|
1068
|
+
prevBlock.parameters === currentArgs,
|
|
1085
1069
|
);
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1070
|
+
|
|
1071
|
+
if (isDuplicate && currentName) {
|
|
1072
|
+
const toolId = currentToolCall.id;
|
|
1073
|
+
const lastMessage = messages[messages.length - 1];
|
|
1074
|
+
const toolBlock = lastMessage.blocks.find(
|
|
1075
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
1076
|
+
b.type === "tool" && b.id === toolId,
|
|
1077
|
+
);
|
|
1078
|
+
if (toolBlock) {
|
|
1079
|
+
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.`;
|
|
1080
|
+
this.messageManager.updateToolBlock({
|
|
1081
|
+
id: toolId,
|
|
1082
|
+
result: (toolBlock.result || "") + warning,
|
|
1083
|
+
stage: "end",
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1093
1086
|
}
|
|
1094
1087
|
}
|
|
1095
1088
|
}
|
|
1096
1089
|
}
|
|
1097
|
-
}
|
|
1098
1090
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1091
|
+
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
1092
|
+
await this.sendAIMessage({
|
|
1093
|
+
recursionDepth: recursionDepth + 1,
|
|
1094
|
+
model,
|
|
1095
|
+
allowedRules,
|
|
1096
|
+
maxTokens,
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1106
1099
|
}
|
|
1107
1100
|
}
|
|
1108
1101
|
} 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
|
);
|
|
@@ -669,7 +669,6 @@ export class HookManager {
|
|
|
669
669
|
event === "Stop" ||
|
|
670
670
|
event === "SubagentStop" ||
|
|
671
671
|
event === "WorktreeCreate" ||
|
|
672
|
-
event === "CwdChanged" ||
|
|
673
672
|
event === "SessionStart" ||
|
|
674
673
|
event === "SessionEnd"
|
|
675
674
|
) {
|
|
@@ -773,7 +772,6 @@ export class HookManager {
|
|
|
773
772
|
SubagentStop: 0,
|
|
774
773
|
PermissionRequest: 0,
|
|
775
774
|
WorktreeCreate: 0,
|
|
776
|
-
CwdChanged: 0,
|
|
777
775
|
SessionStart: 0,
|
|
778
776
|
SessionEnd: 0,
|
|
779
777
|
},
|
|
@@ -788,7 +786,6 @@ export class HookManager {
|
|
|
788
786
|
SubagentStop: 0,
|
|
789
787
|
PermissionRequest: 0,
|
|
790
788
|
WorktreeCreate: 0,
|
|
791
|
-
CwdChanged: 0,
|
|
792
789
|
SessionStart: 0,
|
|
793
790
|
SessionEnd: 0,
|
|
794
791
|
};
|
|
@@ -815,35 +812,6 @@ export class HookManager {
|
|
|
815
812
|
};
|
|
816
813
|
}
|
|
817
814
|
|
|
818
|
-
/**
|
|
819
|
-
* Execute CwdChanged hooks.
|
|
820
|
-
*/
|
|
821
|
-
async executeCwdChangedHooks(
|
|
822
|
-
oldCwd: string,
|
|
823
|
-
newCwd: string,
|
|
824
|
-
sessionId: string,
|
|
825
|
-
transcriptPath: string,
|
|
826
|
-
env: Record<string, string>,
|
|
827
|
-
): Promise<HookExecutionResult[]> {
|
|
828
|
-
const context: ExtendedHookExecutionContext = {
|
|
829
|
-
event: "CwdChanged",
|
|
830
|
-
projectDir: this.workdir,
|
|
831
|
-
timestamp: new Date(),
|
|
832
|
-
sessionId,
|
|
833
|
-
transcriptPath,
|
|
834
|
-
cwd: newCwd,
|
|
835
|
-
oldCwd,
|
|
836
|
-
newCwd,
|
|
837
|
-
env,
|
|
838
|
-
};
|
|
839
|
-
const results = await this.executeHooks("CwdChanged", context);
|
|
840
|
-
if (results.length > 0) {
|
|
841
|
-
// For CwdChanged hooks, we don't block, just log errors
|
|
842
|
-
this.processHookResults("CwdChanged", results);
|
|
843
|
-
}
|
|
844
|
-
return results;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
815
|
/**
|
|
848
816
|
* Register hooks provided by a plugin
|
|
849
817
|
*/
|
|
@@ -131,7 +131,7 @@ export class PermissionManager {
|
|
|
131
131
|
private planFilePath?: string;
|
|
132
132
|
private worktreeName?: string;
|
|
133
133
|
private mainRepoRoot?: string;
|
|
134
|
-
private
|
|
134
|
+
private workdir?: string;
|
|
135
135
|
private onConfiguredPermissionModeChange?: (mode: PermissionMode) => void;
|
|
136
136
|
private _logger?: Logger;
|
|
137
137
|
|
|
@@ -153,7 +153,7 @@ export class PermissionManager {
|
|
|
153
153
|
|
|
154
154
|
this.worktreeName = this.container.get<string>("WorktreeName");
|
|
155
155
|
this.mainRepoRoot = this.container.get<string>("MainRepoRoot");
|
|
156
|
-
this.
|
|
156
|
+
this.workdir = this.container.get<string>("Workdir");
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/**
|
|
@@ -277,7 +277,7 @@ export class PermissionManager {
|
|
|
277
277
|
* Update the additional directories (e.g., when configuration reloads)
|
|
278
278
|
*/
|
|
279
279
|
updateAdditionalDirectories(directories: string[]): void {
|
|
280
|
-
const workdir = this.
|
|
280
|
+
const workdir = this.workdir;
|
|
281
281
|
this.additionalDirectories = directories.map((dir) => {
|
|
282
282
|
if (workdir && !path.isAbsolute(dir)) {
|
|
283
283
|
return path.resolve(workdir, dir);
|
|
@@ -290,7 +290,7 @@ export class PermissionManager {
|
|
|
290
290
|
* Add a system-level additional directory that is persistent across configuration reloads
|
|
291
291
|
*/
|
|
292
292
|
public addSystemAdditionalDirectory(directory: string): void {
|
|
293
|
-
const workdir = this.
|
|
293
|
+
const workdir = this.workdir;
|
|
294
294
|
const resolvedPath =
|
|
295
295
|
workdir && !path.isAbsolute(directory)
|
|
296
296
|
? path.resolve(workdir, directory)
|
|
@@ -329,7 +329,7 @@ export class PermissionManager {
|
|
|
329
329
|
targetPath: string,
|
|
330
330
|
workdir?: string,
|
|
331
331
|
): { isInside: boolean; resolvedPath: string } {
|
|
332
|
-
const effectiveWorkdir = this.
|
|
332
|
+
const effectiveWorkdir = this.workdir || workdir;
|
|
333
333
|
|
|
334
334
|
// Resolve the target path relative to effectiveWorkdir if it's not absolute
|
|
335
335
|
const absolutePath =
|
|
@@ -1068,7 +1068,7 @@ export class PermissionManager {
|
|
|
1068
1068
|
* @param rule - The rule to add (e.g., "Bash(ls)")
|
|
1069
1069
|
*/
|
|
1070
1070
|
public async addPermissionRule(rule: string): Promise<void> {
|
|
1071
|
-
const workdir = this.
|
|
1071
|
+
const workdir = this.workdir;
|
|
1072
1072
|
if (!workdir) {
|
|
1073
1073
|
throw new Error("Working directory not set in PermissionManager");
|
|
1074
1074
|
}
|
|
@@ -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
|
},
|
package/src/prompts/index.ts
CHANGED
|
@@ -238,7 +238,6 @@ export function buildSystemPrompt(
|
|
|
238
238
|
tools: ToolPlugin[],
|
|
239
239
|
options: {
|
|
240
240
|
workdir?: string;
|
|
241
|
-
originalWorkdir?: string;
|
|
242
241
|
memory?: string;
|
|
243
242
|
language?: string;
|
|
244
243
|
isSubagent?: boolean;
|
|
@@ -276,9 +275,8 @@ export function buildSystemPrompt(
|
|
|
276
275
|
prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
|
|
277
276
|
}
|
|
278
277
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const isGitRepo = isGitRepository(workdirForPrompt);
|
|
278
|
+
if (options.workdir) {
|
|
279
|
+
const isGitRepo = isGitRepository(options.workdir);
|
|
282
280
|
const platform = os.platform();
|
|
283
281
|
const osVersion = `${os.type()} ${os.release()}`;
|
|
284
282
|
const today = new Date().toISOString().split("T")[0];
|
|
@@ -293,7 +291,7 @@ export function buildSystemPrompt(
|
|
|
293
291
|
|
|
294
292
|
Here is useful information about the environment you are running in:
|
|
295
293
|
<env>
|
|
296
|
-
Working directory: ${
|
|
294
|
+
Working directory: ${options.workdir}
|
|
297
295
|
Is directory a git repo: ${isGitRepo}
|
|
298
296
|
Platform: ${platform}
|
|
299
297
|
Shell: ${shellName}
|
|
@@ -377,10 +377,7 @@ export async function callAgent(
|
|
|
377
377
|
result.content = finalContent;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
if (
|
|
381
|
-
typeof finalReasoningContent === "string" &&
|
|
382
|
-
finalReasoningContent.length > 0
|
|
383
|
-
) {
|
|
380
|
+
if (typeof finalReasoningContent === "string") {
|
|
384
381
|
result.reasoning_content = finalReasoningContent;
|
|
385
382
|
}
|
|
386
383
|
|
|
@@ -544,6 +541,7 @@ async function processStreamingResponse(
|
|
|
544
541
|
): Promise<CallAgentResult> {
|
|
545
542
|
let accumulatedContent = "";
|
|
546
543
|
let accumulatedReasoningContent = "";
|
|
544
|
+
let hasReasoningContent = false;
|
|
547
545
|
const toolCalls: {
|
|
548
546
|
id: string;
|
|
549
547
|
type: "function";
|
|
@@ -618,13 +616,13 @@ async function processStreamingResponse(
|
|
|
618
616
|
}
|
|
619
617
|
}
|
|
620
618
|
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
reasoning_content.length > 0
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
619
|
+
if (typeof reasoning_content === "string") {
|
|
620
|
+
hasReasoningContent = true;
|
|
621
|
+
if (reasoning_content.length > 0) {
|
|
622
|
+
accumulatedReasoningContent += reasoning_content;
|
|
623
|
+
if (onReasoningUpdate) {
|
|
624
|
+
onReasoningUpdate(accumulatedReasoningContent);
|
|
625
|
+
}
|
|
628
626
|
}
|
|
629
627
|
}
|
|
630
628
|
|
|
@@ -716,7 +714,7 @@ async function processStreamingResponse(
|
|
|
716
714
|
result.content = accumulatedContent.trim();
|
|
717
715
|
}
|
|
718
716
|
|
|
719
|
-
if (
|
|
717
|
+
if (hasReasoningContent) {
|
|
720
718
|
result.reasoning_content = accumulatedReasoningContent.trim();
|
|
721
719
|
}
|
|
722
720
|
|
|
@@ -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
|
);
|