wave-agent-sdk 0.18.3 → 0.18.5
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/ENV.md +1 -1
- package/builtin/skills/settings/MEMORY.md +76 -0
- package/builtin/skills/settings/SKILL.md +4 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +0 -2
- package/dist/managers/MemoryRuleManager.d.ts +9 -0
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +18 -0
- package/dist/managers/aiManager.d.ts +1 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +370 -345
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +22 -15
- package/dist/managers/messageManager.d.ts +32 -13
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +82 -76
- package/dist/prompts/index.d.ts +10 -4
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +21 -20
- package/dist/services/aiService.d.ts +2 -1
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +37 -10
- package/dist/services/memory.d.ts +1 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +35 -17
- package/dist/tools/agentTool.d.ts.map +1 -1
- package/dist/tools/agentTool.js +24 -2
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +17 -10
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +7 -6
- package/dist/tools/readTool.js +2 -2
- package/dist/tools/writeTool.js +2 -2
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +1 -0
- package/dist/utils/gitUtils.js +6 -6
- package/dist/utils/openaiClient.d.ts.map +1 -1
- package/dist/utils/openaiClient.js +33 -32
- package/dist/utils/path.d.ts +7 -0
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +9 -0
- package/dist/utils/taskReminder.d.ts +2 -3
- package/dist/utils/taskReminder.d.ts.map +1 -1
- package/dist/utils/taskReminder.js +7 -18
- package/dist/utils/worktreeUtils.d.ts.map +1 -1
- package/dist/utils/worktreeUtils.js +19 -13
- package/package.json +1 -1
- package/scripts/install_ripgrep.js +21 -1
- package/src/agent.ts +0 -5
- package/src/managers/MemoryRuleManager.ts +23 -0
- package/src/managers/aiManager.ts +473 -434
- package/src/managers/mcpManager.ts +26 -15
- package/src/managers/messageManager.ts +99 -82
- package/src/prompts/index.ts +33 -24
- package/src/services/aiService.ts +39 -12
- package/src/services/memory.ts +34 -16
- package/src/tools/agentTool.ts +24 -2
- package/src/tools/bashTool.ts +18 -9
- package/src/tools/editTool.ts +7 -9
- package/src/tools/readTool.ts +2 -2
- package/src/tools/writeTool.ts +2 -2
- package/src/utils/constants.ts +1 -1
- package/src/utils/containerSetup.ts +1 -0
- package/src/utils/gitUtils.ts +6 -6
- package/src/utils/openaiClient.ts +40 -31
- package/src/utils/path.ts +10 -0
- package/src/utils/taskReminder.ts +7 -19
- package/src/utils/worktreeUtils.ts +46 -28
- package/builtin/skills/settings/MEMORY_RULES.md +0 -60
- package/vendor/ripgrep/linux-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-x86_64/rg +0 -0
- package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
- package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
|
@@ -21,7 +21,7 @@ import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
|
21
21
|
import type { MessageManager } from "./messageManager.js";
|
|
22
22
|
import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
|
|
23
23
|
import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
import type { HookManager } from "./hookManager.js";
|
|
26
26
|
import type { ExtendedHookExecutionContext } from "../types/hooks.js";
|
|
27
27
|
import type { PermissionManager } from "./permissionManager.js";
|
|
@@ -297,12 +297,11 @@ export class AIManager {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
private async
|
|
301
|
-
messages: ChatCompletionMessageParam[],
|
|
300
|
+
private async maybeGetTaskReminderText(
|
|
302
301
|
toolNames: Set<string>,
|
|
303
|
-
): Promise<
|
|
302
|
+
): Promise<string | null> {
|
|
304
303
|
// Guard: no task tools available
|
|
305
|
-
if (!toolNames.has("TaskUpdate")) return;
|
|
304
|
+
if (!toolNames.has("TaskUpdate")) return null;
|
|
306
305
|
|
|
307
306
|
const internalMessages = this.messageManager.getMessages();
|
|
308
307
|
const turnCounts = getTaskReminderTurnCounts(internalMessages);
|
|
@@ -313,11 +312,11 @@ export class AIManager {
|
|
|
313
312
|
turnCounts.turnsSinceLastReminder <
|
|
314
313
|
TASK_REMINDER_CONFIG.TURNS_BETWEEN_REMINDERS
|
|
315
314
|
) {
|
|
316
|
-
return;
|
|
315
|
+
return null;
|
|
317
316
|
}
|
|
318
317
|
|
|
319
318
|
const tasks = await this.taskManager.listTasks();
|
|
320
|
-
maybeInjectTaskReminder(
|
|
319
|
+
return maybeInjectTaskReminder(turnCounts, tasks);
|
|
321
320
|
}
|
|
322
321
|
|
|
323
322
|
public setIsLoading(isLoading: boolean): void {
|
|
@@ -743,481 +742,531 @@ export class AIManager {
|
|
|
743
742
|
} = {},
|
|
744
743
|
): Promise<void> {
|
|
745
744
|
const { recursionDepth = 0, model, allowedRules, maxTokens } = options;
|
|
745
|
+
let turnOffset = recursionDepth;
|
|
746
746
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
turnSequence =
|
|
752
|
-
|
|
753
|
-
.
|
|
754
|
-
.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
747
|
+
outer: while (true) {
|
|
748
|
+
let shouldRestart = false;
|
|
749
|
+
|
|
750
|
+
// OpenTelemetry: start interaction span for this turn (initial call only)
|
|
751
|
+
let turnSequence = 0;
|
|
752
|
+
if (turnOffset === 0) {
|
|
753
|
+
const messages = this.messageManager.getMessages();
|
|
754
|
+
turnSequence = messages.filter((m) => m.role === "user").length;
|
|
755
|
+
const lastUserMessage = [...messages]
|
|
756
|
+
.reverse()
|
|
757
|
+
.find((m) => m.role === "user");
|
|
758
|
+
const userPromptText =
|
|
759
|
+
lastUserMessage?.blocks.find((b) => b.type === "text")?.content || "";
|
|
760
|
+
startInteractionSpan(userPromptText, turnSequence);
|
|
761
|
+
|
|
762
|
+
// Log user_prompt event
|
|
763
|
+
logOTelEvent("user_prompt", {
|
|
764
|
+
prompt_length: String(userPromptText.length),
|
|
765
|
+
}).catch(() => {}); // Non-blocking
|
|
766
|
+
}
|
|
764
767
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
768
|
+
// Set loading state early for the initial call, before any async work
|
|
769
|
+
if (turnOffset === 0) {
|
|
770
|
+
this.setIsLoading(true);
|
|
771
|
+
if (allowedRules && allowedRules.length > 0) {
|
|
772
|
+
this.permissionManager?.addTemporaryRules(allowedRules);
|
|
773
|
+
}
|
|
770
774
|
}
|
|
771
|
-
}
|
|
772
775
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
776
|
+
// Scan for file mentions in the last user message to trigger conditional rules
|
|
777
|
+
if (turnOffset === 0) {
|
|
778
|
+
const messages = this.messageManager.getMessages();
|
|
779
|
+
const lastMessage = messages[messages.length - 1];
|
|
780
|
+
if (lastMessage && lastMessage.role === "user") {
|
|
781
|
+
for (const block of lastMessage.blocks) {
|
|
782
|
+
if (block.type === "text") {
|
|
783
|
+
const content = block.content;
|
|
784
|
+
const fileMentionRegex = /(?:^|\s)@([\w.\-/]+)/g;
|
|
785
|
+
let match;
|
|
786
|
+
while ((match = fileMentionRegex.exec(content)) !== null) {
|
|
787
|
+
const filePath = match[1];
|
|
788
|
+
this.messageManager.triggerFileRead(filePath);
|
|
789
|
+
}
|
|
786
790
|
}
|
|
787
791
|
}
|
|
788
792
|
}
|
|
789
793
|
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Save session in each recursion to ensure message persistence
|
|
793
|
-
await this.messageManager.saveSession();
|
|
794
794
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
795
|
+
// Only create new AbortControllers for the initial call (turnOffset === 0)
|
|
796
|
+
// For restarts, reuse existing controllers to maintain abort signal
|
|
797
|
+
let abortController: AbortController;
|
|
798
|
+
let toolAbortController: AbortController;
|
|
799
799
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
800
|
+
if (turnOffset === 0) {
|
|
801
|
+
// Create new AbortControllers for initial call
|
|
802
|
+
abortController = new AbortController();
|
|
803
|
+
this.abortController = abortController;
|
|
804
804
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// Get current permission mode
|
|
814
|
-
const currentMode = this.permissionManager?.getCurrentEffectiveMode(
|
|
815
|
-
this.getModelConfig().permissionMode,
|
|
816
|
-
);
|
|
805
|
+
toolAbortController = new AbortController();
|
|
806
|
+
this.toolAbortController = toolAbortController;
|
|
807
|
+
} else {
|
|
808
|
+
// Reuse existing controllers
|
|
809
|
+
abortController = this.abortController!;
|
|
810
|
+
toolAbortController = this.toolAbortController!;
|
|
811
|
+
}
|
|
817
812
|
|
|
818
|
-
|
|
819
|
-
this.maybeAddPlanModeMessage(currentMode);
|
|
813
|
+
let turnDepth = turnOffset;
|
|
820
814
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
815
|
+
inner: while (true) {
|
|
816
|
+
try {
|
|
817
|
+
// Save session in each iteration to ensure message persistence
|
|
818
|
+
await this.messageManager.saveSession();
|
|
824
819
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
820
|
+
// Get current permission mode
|
|
821
|
+
const currentMode = this.permissionManager?.getCurrentEffectiveMode(
|
|
822
|
+
this.getModelConfig().permissionMode,
|
|
823
|
+
);
|
|
828
824
|
|
|
829
|
-
|
|
830
|
-
|
|
825
|
+
// Add plan mode reminder as persistent meta message before getting messages
|
|
826
|
+
this.maybeAddPlanModeMessage(currentMode);
|
|
831
827
|
|
|
832
|
-
|
|
828
|
+
// Process conditional rules triggered by file reads (persist as meta messages)
|
|
829
|
+
const triggeredRules = this.messageManager.processTriggeredRules();
|
|
830
|
+
for (const rule of triggeredRules) {
|
|
831
|
+
this.messageManager.addUserMessage({
|
|
832
|
+
content: `<!-- rule: ${rule.id} -->\n<system-reminder>\nThe following rules from .wave/rules apply to your work. Be sure to adhere to these instructions.\n\n${rule.content}\n</system-reminder>`,
|
|
833
|
+
isMeta: true,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
833
836
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
.getTools()
|
|
838
|
-
.filter((t) => toolNames.has(t.name));
|
|
837
|
+
// Get recent message history
|
|
838
|
+
const rawMessages = this.messageManager.getMessages();
|
|
839
|
+
const recentMessages = convertMessagesForAPI(rawMessages);
|
|
839
840
|
|
|
840
|
-
|
|
841
|
+
// Track if assistant message has been created
|
|
842
|
+
let assistantMessageCreated = false;
|
|
841
843
|
|
|
842
|
-
|
|
843
|
-
const directory = this.memoryService.getAutoMemoryDirectory(
|
|
844
|
-
this.getWorkdir(),
|
|
845
|
-
);
|
|
846
|
-
const content = await this.memoryService.getAutoMemoryContent(
|
|
847
|
-
this.getWorkdir(),
|
|
848
|
-
);
|
|
849
|
-
autoMemoryOptions = { directory, content };
|
|
850
|
-
}
|
|
844
|
+
logger?.debug("modelConfig in sendAIMessage", this.getModelConfig());
|
|
851
845
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
sessionId: this.messageManager.getSessionId(),
|
|
858
|
-
abortSignal: abortController.signal,
|
|
859
|
-
workdir: this.getWorkdir(), // Pass working directory
|
|
860
|
-
tools: toolsConfig, // Pass filtered tool configuration
|
|
861
|
-
model: model, // Use passed model
|
|
862
|
-
systemPrompt: buildSystemPrompt(
|
|
863
|
-
this.systemPrompt,
|
|
864
|
-
filteredToolPlugins,
|
|
865
|
-
{
|
|
866
|
-
workdir: this.getWorkdir(),
|
|
867
|
-
originalWorkdir: this.getOriginalWorkdir(),
|
|
868
|
-
memory: combinedMemory,
|
|
869
|
-
language: this.getLanguage(),
|
|
870
|
-
isSubagent: !!this.subagentType,
|
|
871
|
-
autoMemory: autoMemoryOptions,
|
|
872
|
-
permissionMode: currentMode,
|
|
873
|
-
},
|
|
874
|
-
), // Pass custom system prompt
|
|
875
|
-
maxTokens: maxTokens, // Pass max tokens override
|
|
876
|
-
toolChoice: this.toolChoiceOverride, // Pass tool_choice override
|
|
877
|
-
};
|
|
846
|
+
const toolsConfig = this.getFilteredToolsConfig();
|
|
847
|
+
const toolNames = new Set(toolsConfig.map((t) => t.function.name));
|
|
848
|
+
const filteredToolPlugins = this.toolManager
|
|
849
|
+
.getTools()
|
|
850
|
+
.filter((t) => toolNames.has(t.name));
|
|
878
851
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
toolNames,
|
|
883
|
-
);
|
|
852
|
+
let autoMemoryOptions:
|
|
853
|
+
| { directory: string; content: string }
|
|
854
|
+
| undefined;
|
|
884
855
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
this.messageManager.updateCurrentMessageContent(content);
|
|
894
|
-
};
|
|
895
|
-
callAgentOptions.onToolUpdate = (toolCall) => {
|
|
896
|
-
// Create assistant message on first tool update if not already created
|
|
897
|
-
if (!assistantMessageCreated) {
|
|
898
|
-
this.messageManager.addAssistantMessage();
|
|
899
|
-
assistantMessageCreated = true;
|
|
856
|
+
if (this.getAutoMemoryEnabled()) {
|
|
857
|
+
const directory = this.memoryService.getAutoMemoryDirectory(
|
|
858
|
+
this.getWorkdir(),
|
|
859
|
+
);
|
|
860
|
+
const content = await this.memoryService.getAutoMemoryContent(
|
|
861
|
+
this.getWorkdir(),
|
|
862
|
+
);
|
|
863
|
+
autoMemoryOptions = { directory, content };
|
|
900
864
|
}
|
|
901
865
|
|
|
902
|
-
//
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
866
|
+
// Get memory for message-array injection (not system prompt)
|
|
867
|
+
const { prependContent } =
|
|
868
|
+
await this.messageManager.getMemoryForInjection();
|
|
869
|
+
|
|
870
|
+
// Call AI service with streaming callbacks if enabled
|
|
871
|
+
const callAgentOptions: CallAgentOptions = {
|
|
872
|
+
gatewayConfig: this.getGatewayConfig(),
|
|
873
|
+
modelConfig: this.getModelConfig(),
|
|
874
|
+
messages: recentMessages,
|
|
875
|
+
sessionId: this.messageManager.getSessionId(),
|
|
876
|
+
abortSignal: abortController.signal,
|
|
877
|
+
workdir: this.getWorkdir(), // Pass working directory
|
|
878
|
+
tools: toolsConfig, // Pass filtered tool configuration
|
|
879
|
+
model: model, // Use passed model
|
|
880
|
+
systemPrompt: buildSystemPrompt(
|
|
881
|
+
this.systemPrompt,
|
|
882
|
+
filteredToolPlugins,
|
|
883
|
+
{
|
|
884
|
+
workdir: this.getWorkdir(),
|
|
885
|
+
originalWorkdir: this.getOriginalWorkdir(),
|
|
886
|
+
language: this.getLanguage(),
|
|
887
|
+
isSubagent: !!this.subagentType,
|
|
888
|
+
autoMemory: autoMemoryOptions,
|
|
889
|
+
},
|
|
890
|
+
), // Pass custom system prompt
|
|
891
|
+
maxTokens: maxTokens, // Pass max tokens override
|
|
892
|
+
toolChoice: this.toolChoiceOverride, // Pass tool_choice override
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// Prepend: AGENTS.md + user memory + unconditional rules as system-reminder
|
|
896
|
+
if (prependContent.trim()) {
|
|
897
|
+
callAgentOptions.messages.unshift({
|
|
898
|
+
role: "user",
|
|
899
|
+
content: `<system-reminder>\n${prependContent}\n</system-reminder>`,
|
|
900
|
+
});
|
|
919
901
|
}
|
|
920
|
-
this.messageManager.updateCurrentMessageReasoning(reasoning);
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
startLLMRequestSpan(model || this.getModelConfig().model || "");
|
|
925
902
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const createdByStreaming = assistantMessageCreated;
|
|
936
|
-
|
|
937
|
-
// For non-streaming mode, create assistant message after callAgent returns
|
|
938
|
-
// Also create if streaming mode but no streaming callbacks were called (e.g., when content comes directly in result)
|
|
939
|
-
if (
|
|
940
|
-
!this.stream ||
|
|
941
|
-
(!assistantMessageCreated &&
|
|
942
|
-
(result.content || result.tool_calls || result.reasoning_content))
|
|
943
|
-
) {
|
|
944
|
-
this.messageManager.addAssistantMessage();
|
|
945
|
-
assistantMessageCreated = true;
|
|
946
|
-
}
|
|
903
|
+
// Task reminder: persist as meta message (conditional rules already persisted above)
|
|
904
|
+
const taskReminderText =
|
|
905
|
+
await this.maybeGetTaskReminderText(toolNames);
|
|
906
|
+
if (taskReminderText) {
|
|
907
|
+
this.messageManager.addUserMessage({
|
|
908
|
+
content: taskReminderText,
|
|
909
|
+
isMeta: true,
|
|
910
|
+
});
|
|
911
|
+
}
|
|
947
912
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
913
|
+
// Add streaming callbacks only if streaming is enabled
|
|
914
|
+
if (this.stream) {
|
|
915
|
+
callAgentOptions.onContentUpdate = (content: string) => {
|
|
916
|
+
// Create assistant message on first chunk if not already created
|
|
917
|
+
if (!assistantMessageCreated) {
|
|
918
|
+
this.messageManager.addAssistantMessage();
|
|
919
|
+
assistantMessageCreated = true;
|
|
920
|
+
}
|
|
921
|
+
this.messageManager.updateCurrentMessageContent(content);
|
|
922
|
+
};
|
|
923
|
+
callAgentOptions.onToolUpdate = (toolCall) => {
|
|
924
|
+
// Create assistant message on first tool update if not already created
|
|
925
|
+
if (!assistantMessageCreated) {
|
|
926
|
+
this.messageManager.addAssistantMessage();
|
|
927
|
+
assistantMessageCreated = true;
|
|
928
|
+
}
|
|
958
929
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
Object.keys(result.additionalFields).length > 0
|
|
962
|
-
) {
|
|
963
|
-
this.messageManager.mergeAssistantAdditionalFields(
|
|
964
|
-
result.additionalFields,
|
|
965
|
-
);
|
|
966
|
-
}
|
|
930
|
+
// Use parametersChunk as compact param for better performance
|
|
931
|
+
// No need to extract params or generate compact params during streaming
|
|
967
932
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
933
|
+
// Update tool block with streaming parameters using parametersChunk as compact param
|
|
934
|
+
this.messageManager.updateToolBlock({
|
|
935
|
+
id: toolCall.id,
|
|
936
|
+
name: toolCall.name,
|
|
937
|
+
parameters: toolCall.parameters,
|
|
938
|
+
parametersChunk: toolCall.parametersChunk,
|
|
939
|
+
stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
|
|
940
|
+
});
|
|
941
|
+
};
|
|
942
|
+
callAgentOptions.onReasoningUpdate = (reasoning: string) => {
|
|
943
|
+
// Create assistant message on first reasoning update if not already created
|
|
944
|
+
if (!assistantMessageCreated) {
|
|
945
|
+
this.messageManager.addAssistantMessage();
|
|
946
|
+
assistantMessageCreated = true;
|
|
947
|
+
}
|
|
948
|
+
this.messageManager.updateCurrentMessageReasoning(reasoning);
|
|
949
|
+
};
|
|
950
|
+
}
|
|
974
951
|
|
|
975
|
-
|
|
976
|
-
if (result.content && !createdByStreaming) {
|
|
977
|
-
this.messageManager.updateCurrentMessageContent(result.content);
|
|
978
|
-
}
|
|
952
|
+
startLLMRequestSpan(model || this.getModelConfig().model || "");
|
|
979
953
|
|
|
980
|
-
|
|
981
|
-
let usage: Usage | undefined;
|
|
982
|
-
if (result.usage) {
|
|
983
|
-
usage = {
|
|
984
|
-
prompt_tokens: result.usage.prompt_tokens,
|
|
985
|
-
completion_tokens: result.usage.completion_tokens,
|
|
986
|
-
total_tokens: result.usage.total_tokens,
|
|
987
|
-
model: model || this.getModelConfig().model,
|
|
988
|
-
operation_type: "agent",
|
|
989
|
-
// Preserve cache fields if present
|
|
990
|
-
...(result.usage.cache_read_input_tokens !== undefined && {
|
|
991
|
-
cache_read_input_tokens: result.usage.cache_read_input_tokens,
|
|
992
|
-
}),
|
|
993
|
-
...(result.usage.cache_creation_input_tokens !== undefined && {
|
|
994
|
-
cache_creation_input_tokens:
|
|
995
|
-
result.usage.cache_creation_input_tokens,
|
|
996
|
-
}),
|
|
997
|
-
...(result.usage.cache_creation && {
|
|
998
|
-
cache_creation: result.usage.cache_creation,
|
|
999
|
-
}),
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
954
|
+
const result = await aiService.callAgent(callAgentOptions);
|
|
1002
955
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
this.messageManager.setMessages(messages);
|
|
1010
|
-
}
|
|
956
|
+
// End LLM span with usage data
|
|
957
|
+
endLLMRequestSpan({
|
|
958
|
+
model: model || this.getModelConfig().model || "",
|
|
959
|
+
success: true,
|
|
960
|
+
hasToolCall: !!(result.tool_calls && result.tool_calls.length > 0),
|
|
961
|
+
});
|
|
1011
962
|
|
|
1012
|
-
|
|
1013
|
-
if (this.callbacks?.onUsageAdded) {
|
|
1014
|
-
this.callbacks.onUsageAdded(usage);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
963
|
+
const createdByStreaming = assistantMessageCreated;
|
|
1017
964
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
965
|
+
// For non-streaming mode, create assistant message after callAgent returns
|
|
966
|
+
// Also create if streaming mode but no streaming callbacks were called (e.g., when content comes directly in result)
|
|
967
|
+
if (
|
|
968
|
+
!this.stream ||
|
|
969
|
+
(!assistantMessageCreated &&
|
|
970
|
+
(result.content || result.tool_calls || result.reasoning_content))
|
|
971
|
+
) {
|
|
972
|
+
this.messageManager.addAssistantMessage();
|
|
973
|
+
assistantMessageCreated = true;
|
|
1024
974
|
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
975
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const toolName = call.function?.name || "";
|
|
1038
|
-
const safe = this.toolManager.isConcurrencySafe(toolName);
|
|
1039
|
-
const lastBatch = batches[batches.length - 1];
|
|
1040
|
-
if (lastBatch && lastBatch.safe && safe) {
|
|
1041
|
-
lastBatch.calls.push(call);
|
|
1042
|
-
} else {
|
|
1043
|
-
batches.push({ calls: [call], safe });
|
|
976
|
+
// Log finish reason and response headers if available
|
|
977
|
+
if (result.finish_reason) {
|
|
978
|
+
// Log warning headers when finish reason is length
|
|
979
|
+
if (result.finish_reason === "length") {
|
|
980
|
+
logger?.warn(
|
|
981
|
+
"AI response truncated due to length limit. Response headers:",
|
|
982
|
+
result.response_headers,
|
|
983
|
+
);
|
|
984
|
+
}
|
|
1044
985
|
}
|
|
1045
|
-
}
|
|
1046
986
|
|
|
1047
|
-
// Execute batches sequentially; within a safe batch, tools run in parallel
|
|
1048
|
-
for (const batch of batches) {
|
|
1049
987
|
if (
|
|
1050
|
-
|
|
1051
|
-
|
|
988
|
+
result.additionalFields &&
|
|
989
|
+
Object.keys(result.additionalFields).length > 0
|
|
1052
990
|
) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (batch.calls.length === 1) {
|
|
1056
|
-
await this.executeToolCall(
|
|
1057
|
-
batch.calls[0],
|
|
1058
|
-
abortController,
|
|
1059
|
-
toolAbortController,
|
|
1060
|
-
result.finish_reason,
|
|
991
|
+
this.messageManager.mergeAssistantAdditionalFields(
|
|
992
|
+
result.additionalFields,
|
|
1061
993
|
);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
toolAbortController,
|
|
1069
|
-
result.finish_reason,
|
|
1070
|
-
),
|
|
1071
|
-
),
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Handle result reasoning content from non-streaming mode
|
|
997
|
+
if (result.reasoning_content && !createdByStreaming) {
|
|
998
|
+
this.messageManager.updateCurrentMessageReasoning(
|
|
999
|
+
result.reasoning_content,
|
|
1072
1000
|
);
|
|
1073
1001
|
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
1002
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1003
|
+
// Handle result content from non-streaming mode
|
|
1004
|
+
if (result.content && !createdByStreaming) {
|
|
1005
|
+
this.messageManager.updateCurrentMessageContent(result.content);
|
|
1006
|
+
}
|
|
1079
1007
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1008
|
+
// Handle usage tracking for agent operations
|
|
1009
|
+
let usage: Usage | undefined;
|
|
1010
|
+
if (result.usage) {
|
|
1011
|
+
usage = {
|
|
1012
|
+
prompt_tokens: result.usage.prompt_tokens,
|
|
1013
|
+
completion_tokens: result.usage.completion_tokens,
|
|
1014
|
+
total_tokens: result.usage.total_tokens,
|
|
1015
|
+
model: model || this.getModelConfig().model,
|
|
1016
|
+
operation_type: "agent",
|
|
1017
|
+
// Preserve cache fields if present
|
|
1018
|
+
...(result.usage.cache_read_input_tokens !== undefined && {
|
|
1019
|
+
cache_read_input_tokens: result.usage.cache_read_input_tokens,
|
|
1020
|
+
}),
|
|
1021
|
+
...(result.usage.cache_creation_input_tokens !== undefined && {
|
|
1022
|
+
cache_creation_input_tokens:
|
|
1023
|
+
result.usage.cache_creation_input_tokens,
|
|
1024
|
+
}),
|
|
1025
|
+
...(result.usage.cache_creation && {
|
|
1026
|
+
cache_creation: result.usage.cache_creation,
|
|
1027
|
+
}),
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1082
1030
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
this.
|
|
1095
|
-
if (snapshots.length > 0) {
|
|
1096
|
-
this.messageManager.addFileHistoryBlock(snapshots);
|
|
1031
|
+
// Set usage on the assistant message if available
|
|
1032
|
+
if (usage) {
|
|
1033
|
+
const messages = this.messageManager.getMessages();
|
|
1034
|
+
const lastMessage = messages[messages.length - 1];
|
|
1035
|
+
if (lastMessage && lastMessage.role === "assistant") {
|
|
1036
|
+
lastMessage.usage = usage;
|
|
1037
|
+
this.messageManager.setMessages(messages);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Notify Agent to add to usage tracking
|
|
1041
|
+
if (this.callbacks?.onUsageAdded) {
|
|
1042
|
+
this.callbacks.onUsageAdded(usage);
|
|
1097
1043
|
}
|
|
1098
1044
|
}
|
|
1099
1045
|
|
|
1100
|
-
//
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1046
|
+
// Collect tool calls for processing
|
|
1047
|
+
const toolCalls: ChatCompletionMessageFunctionToolCall[] = [];
|
|
1048
|
+
if (result.tool_calls) {
|
|
1049
|
+
for (const toolCall of result.tool_calls) {
|
|
1050
|
+
if (toolCall.type === "function") {
|
|
1051
|
+
toolCalls.push(toolCall);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1104
1055
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
);
|
|
1123
|
-
} else if (!isCurrentlyAborted) {
|
|
1124
|
-
// If response was truncated, add a hidden continuation message
|
|
1125
|
-
if (result.finish_reason === "length") {
|
|
1126
|
-
this.messageManager.addUserMessage({
|
|
1127
|
-
content:
|
|
1128
|
-
"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.",
|
|
1129
|
-
isMeta: true,
|
|
1130
|
-
});
|
|
1056
|
+
if (toolCalls.length > 0) {
|
|
1057
|
+
// Partition tool calls into batches: consecutive concurrency-safe
|
|
1058
|
+
// tools run in parallel, non-safe tools (Edit, Write, MCP) run one
|
|
1059
|
+
// at a time to prevent read-modify-write races on the same file.
|
|
1060
|
+
const batches: {
|
|
1061
|
+
calls: ChatCompletionMessageFunctionToolCall[];
|
|
1062
|
+
safe: boolean;
|
|
1063
|
+
}[] = [];
|
|
1064
|
+
for (const call of toolCalls) {
|
|
1065
|
+
const toolName = call.function?.name || "";
|
|
1066
|
+
const safe = this.toolManager.isConcurrencySafe(toolName);
|
|
1067
|
+
const lastBatch = batches[batches.length - 1];
|
|
1068
|
+
if (lastBatch && lastBatch.safe && safe) {
|
|
1069
|
+
lastBatch.calls.push(call);
|
|
1070
|
+
} else {
|
|
1071
|
+
batches.push({ calls: [call], safe });
|
|
1072
|
+
}
|
|
1131
1073
|
}
|
|
1132
1074
|
|
|
1133
|
-
//
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1075
|
+
// Execute batches sequentially; within a safe batch, tools run in parallel
|
|
1076
|
+
for (const batch of batches) {
|
|
1077
|
+
if (
|
|
1078
|
+
abortController.signal.aborted ||
|
|
1079
|
+
toolAbortController.signal.aborted
|
|
1080
|
+
) {
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
if (batch.calls.length === 1) {
|
|
1084
|
+
await this.executeToolCall(
|
|
1085
|
+
batch.calls[0],
|
|
1086
|
+
abortController,
|
|
1087
|
+
toolAbortController,
|
|
1088
|
+
result.finish_reason,
|
|
1089
|
+
);
|
|
1090
|
+
} else {
|
|
1091
|
+
await Promise.all(
|
|
1092
|
+
batch.calls.map((call) =>
|
|
1093
|
+
this.executeToolCall(
|
|
1094
|
+
call,
|
|
1095
|
+
abortController,
|
|
1096
|
+
toolAbortController,
|
|
1097
|
+
result.finish_reason,
|
|
1098
|
+
),
|
|
1099
|
+
),
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Handle token statistics and message compaction
|
|
1106
|
+
await this.handleTokenUsageAndCompaction(
|
|
1107
|
+
result.usage,
|
|
1108
|
+
abortController,
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
// Finalize text/reasoning blocks for the final response (no tools)
|
|
1112
|
+
this.messageManager.finalizeStreamingBlocks();
|
|
1113
|
+
|
|
1114
|
+
// Check if there are tool operations or response was truncated, if so automatically initiate next AI service call
|
|
1115
|
+
if (toolCalls.length > 0 || result.finish_reason === "length") {
|
|
1116
|
+
// Check maxTurns limit before continuing
|
|
1117
|
+
if (this.maxTurns && turnDepth + 1 >= this.maxTurns) {
|
|
1118
|
+
logger?.debug(`Max turns (${this.maxTurns}) reached, stopping.`);
|
|
1119
|
+
} else {
|
|
1120
|
+
// Record committed snapshots to message history
|
|
1121
|
+
if (this.reversionManager) {
|
|
1122
|
+
const snapshots =
|
|
1123
|
+
this.reversionManager.getAndClearCommittedSnapshots();
|
|
1124
|
+
if (snapshots.length > 0) {
|
|
1125
|
+
this.messageManager.addFileHistoryBlock(snapshots);
|
|
1147
1126
|
}
|
|
1148
1127
|
}
|
|
1149
1128
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1129
|
+
// Check interruption status
|
|
1130
|
+
const isCurrentlyAborted =
|
|
1131
|
+
abortController.signal.aborted ||
|
|
1132
|
+
toolAbortController.signal.aborted;
|
|
1133
|
+
|
|
1134
|
+
// Check if all tools were manually backgrounded
|
|
1135
|
+
const lastMessage =
|
|
1136
|
+
this.messageManager.getMessages()[
|
|
1137
|
+
this.messageManager.getMessages().length - 1
|
|
1138
|
+
];
|
|
1139
|
+
const toolBlocks =
|
|
1140
|
+
lastMessage?.blocks.filter(
|
|
1141
|
+
(block): block is import("../types/messaging.js").ToolBlock =>
|
|
1142
|
+
block.type === "tool",
|
|
1143
|
+
) || [];
|
|
1144
|
+
const hasBackgrounded =
|
|
1145
|
+
toolBlocks.length > 0 &&
|
|
1146
|
+
toolBlocks.some((block) => block.isManuallyBackgrounded);
|
|
1147
|
+
|
|
1148
|
+
if (hasBackgrounded) {
|
|
1149
|
+
logger?.info(
|
|
1150
|
+
"Some tools were manually backgrounded, stopping.",
|
|
1151
|
+
);
|
|
1152
|
+
} else if (!isCurrentlyAborted) {
|
|
1153
|
+
// If response was truncated, add a hidden continuation message
|
|
1154
|
+
if (result.finish_reason === "length") {
|
|
1155
|
+
this.messageManager.addUserMessage({
|
|
1156
|
+
content:
|
|
1157
|
+
"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.",
|
|
1158
|
+
isMeta: true,
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Duplicate Tool Call Detection
|
|
1163
|
+
if (toolCalls.length > 0) {
|
|
1164
|
+
const messages = this.messageManager.getMessages();
|
|
1165
|
+
// Find the most recent assistant message BEFORE the current one that has tool blocks
|
|
1166
|
+
// The current assistant message is messages[messages.length - 1]
|
|
1167
|
+
let previousAssistantWithTools: Message | undefined;
|
|
1168
|
+
for (let i = messages.length - 2; i >= 0; i--) {
|
|
1169
|
+
const msg = messages[i];
|
|
1170
|
+
if (
|
|
1171
|
+
msg.role === "assistant" &&
|
|
1172
|
+
msg.blocks.some((b) => b.type === "tool")
|
|
1173
|
+
) {
|
|
1174
|
+
previousAssistantWithTools = msg;
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (previousAssistantWithTools) {
|
|
1180
|
+
const previousToolBlocks =
|
|
1181
|
+
previousAssistantWithTools.blocks.filter(
|
|
1182
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
1183
|
+
b.type === "tool",
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
for (const currentToolCall of toolCalls) {
|
|
1187
|
+
const currentName = currentToolCall.function?.name;
|
|
1188
|
+
const currentArgs = currentToolCall.function?.arguments;
|
|
1189
|
+
|
|
1190
|
+
const isDuplicate = previousToolBlocks.some(
|
|
1191
|
+
(prevBlock) =>
|
|
1192
|
+
prevBlock.name === currentName &&
|
|
1193
|
+
prevBlock.parameters === currentArgs,
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
if (isDuplicate && currentName) {
|
|
1197
|
+
const toolId = currentToolCall.id;
|
|
1198
|
+
const lastMessage = messages[messages.length - 1];
|
|
1199
|
+
const toolBlock = lastMessage.blocks.find(
|
|
1200
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
1201
|
+
b.type === "tool" && b.id === toolId,
|
|
1202
|
+
);
|
|
1203
|
+
if (toolBlock) {
|
|
1204
|
+
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.`;
|
|
1205
|
+
this.messageManager.updateToolBlock({
|
|
1206
|
+
id: toolId,
|
|
1207
|
+
result: (toolBlock.result || "") + warning,
|
|
1208
|
+
stage: "end",
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1181
1212
|
}
|
|
1182
1213
|
}
|
|
1183
1214
|
}
|
|
1215
|
+
|
|
1216
|
+
// Yield to the event loop so macrotasks (abort timers,
|
|
1217
|
+
// signals) can be processed between turns. Without this,
|
|
1218
|
+
// mocked async operations (microtasks) would starve the
|
|
1219
|
+
// event loop and abort timers would never fire.
|
|
1220
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1221
|
+
|
|
1222
|
+
// Re-check abort status after yielding — the signal may
|
|
1223
|
+
// have fired during the setImmediate gap.
|
|
1224
|
+
if (
|
|
1225
|
+
abortController.signal.aborted ||
|
|
1226
|
+
toolAbortController.signal.aborted
|
|
1227
|
+
) {
|
|
1228
|
+
break inner;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
turnDepth++;
|
|
1232
|
+
continue inner;
|
|
1184
1233
|
}
|
|
1185
1234
|
}
|
|
1186
|
-
|
|
1187
|
-
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
1188
|
-
await this.sendAIMessage({
|
|
1189
|
-
recursionDepth: recursionDepth + 1,
|
|
1190
|
-
model,
|
|
1191
|
-
allowedRules,
|
|
1192
|
-
maxTokens,
|
|
1193
|
-
});
|
|
1194
1235
|
}
|
|
1236
|
+
|
|
1237
|
+
// No tool calls (or stop conditions) → inner loop done
|
|
1238
|
+
break inner;
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
// End LLM span with error
|
|
1241
|
+
endLLMRequestSpan({
|
|
1242
|
+
model: model || this.getModelConfig().model || "",
|
|
1243
|
+
success: false,
|
|
1244
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// Log error event
|
|
1248
|
+
logOTelEvent("error", {
|
|
1249
|
+
error_type:
|
|
1250
|
+
error instanceof Error ? error.constructor.name : "Unknown",
|
|
1251
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1252
|
+
}).catch(() => {}); // Non-blocking
|
|
1253
|
+
|
|
1254
|
+
this.messageManager.addErrorBlock(
|
|
1255
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
1256
|
+
);
|
|
1257
|
+
|
|
1258
|
+
// Exit inner loop on error
|
|
1259
|
+
break inner;
|
|
1195
1260
|
}
|
|
1196
1261
|
}
|
|
1197
|
-
} catch (error) {
|
|
1198
|
-
// End LLM span with error
|
|
1199
|
-
endLLMRequestSpan({
|
|
1200
|
-
model: model || this.getModelConfig().model || "",
|
|
1201
|
-
success: false,
|
|
1202
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
// Log error event
|
|
1206
|
-
logOTelEvent("error", {
|
|
1207
|
-
error_type: error instanceof Error ? error.constructor.name : "Unknown",
|
|
1208
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1209
|
-
}).catch(() => {}); // Non-blocking
|
|
1210
1262
|
|
|
1211
|
-
|
|
1212
|
-
error instanceof Error ? error.message : "Unknown error occurred",
|
|
1213
|
-
);
|
|
1214
|
-
} finally {
|
|
1263
|
+
// Finally-equivalent (runs once per outer iteration):
|
|
1215
1264
|
// Only execute cleanup and hooks for the initial call
|
|
1216
|
-
if (
|
|
1265
|
+
if (turnOffset === 0) {
|
|
1217
1266
|
// OpenTelemetry: end interaction span
|
|
1218
1267
|
endInteractionSpan();
|
|
1219
1268
|
|
|
1220
|
-
// Save session in each
|
|
1269
|
+
// Save session in each iteration to ensure message persistence
|
|
1221
1270
|
await this.messageManager.saveSession();
|
|
1222
1271
|
// Set loading to false first
|
|
1223
1272
|
this.setIsLoading(false);
|
|
@@ -1240,13 +1289,9 @@ export class AIManager {
|
|
|
1240
1289
|
});
|
|
1241
1290
|
}
|
|
1242
1291
|
}
|
|
1243
|
-
//
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
model,
|
|
1247
|
-
allowedRules,
|
|
1248
|
-
maxTokens,
|
|
1249
|
-
});
|
|
1292
|
+
// Restart outer loop to process the notifications
|
|
1293
|
+
shouldRestart = true;
|
|
1294
|
+
turnOffset = 0;
|
|
1250
1295
|
} else {
|
|
1251
1296
|
// Clear temporary rules
|
|
1252
1297
|
this.permissionManager?.clearTemporaryRules();
|
|
@@ -1317,12 +1362,9 @@ export class AIManager {
|
|
|
1317
1362
|
// Keep loading state active to prevent UI flicker
|
|
1318
1363
|
this.setIsLoading(true);
|
|
1319
1364
|
goalContinuing = true;
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
allowedRules,
|
|
1324
|
-
maxTokens,
|
|
1325
|
-
});
|
|
1365
|
+
// Restart outer loop to continue goal pursuit
|
|
1366
|
+
shouldRestart = true;
|
|
1367
|
+
turnOffset = 0;
|
|
1326
1368
|
}
|
|
1327
1369
|
}
|
|
1328
1370
|
}
|
|
@@ -1341,18 +1383,15 @@ export class AIManager {
|
|
|
1341
1383
|
);
|
|
1342
1384
|
|
|
1343
1385
|
// Restart the conversation to let AI fix the issues
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
recursionDepth: 0,
|
|
1347
|
-
model,
|
|
1348
|
-
allowedRules,
|
|
1349
|
-
maxTokens,
|
|
1350
|
-
});
|
|
1386
|
+
shouldRestart = true;
|
|
1387
|
+
turnOffset = 0;
|
|
1351
1388
|
}
|
|
1352
1389
|
}
|
|
1353
1390
|
}
|
|
1354
1391
|
}
|
|
1355
1392
|
}
|
|
1393
|
+
|
|
1394
|
+
if (!shouldRestart) break outer;
|
|
1356
1395
|
}
|
|
1357
1396
|
}
|
|
1358
1397
|
|