jinzd-ai-cli 0.4.186 → 0.4.188

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/{batch-IPALJR2D.js → batch-D6K2KHJK.js} +2 -2
  2. package/dist/{chat-index-2I7ZHRE5.js → chat-index-JXTYDRCY.js} +1 -1
  3. package/dist/{chat-index-BE4TPLFH.js → chat-index-WDMVP7BN.js} +1 -1
  4. package/dist/{chunk-MM3F43H6.js → chunk-4UZE4ADL.js} +54 -23
  5. package/dist/{chunk-V7NTQ6UB.js → chunk-DFQSQQEU.js} +1 -1
  6. package/dist/{chunk-RADH6ECW.js → chunk-IQ7JE43O.js} +1442 -1318
  7. package/dist/{chunk-JBTVDYJM.js → chunk-J3XSJCO5.js} +4 -4
  8. package/dist/{chunk-T5VKNPLD.js → chunk-KIGVJVX4.js} +52 -23
  9. package/dist/{chunk-ZLWYP3RB.js → chunk-MUQZOUV5.js} +1 -1
  10. package/dist/{chunk-2CLMIRKL.js → chunk-NRSAAMIF.js} +1 -1
  11. package/dist/{chunk-7HMX2MTY.js → chunk-ODAAPNSL.js} +1 -1
  12. package/dist/{chunk-KNGDSMMF.js → chunk-Q7SB3R25.js} +1 -1
  13. package/dist/{chunk-MIXN7VBY.js → chunk-UK6E2563.js} +1 -1
  14. package/dist/{chunk-OFP5BE7H.js → chunk-VPTRE7IW.js} +2 -2
  15. package/dist/{ci-FYXVC5MX.js → ci-42ZBP2SY.js} +3 -3
  16. package/dist/{constants-RB5H7L34.js → constants-NCWVAAI7.js} +1 -1
  17. package/dist/{doctor-cli-ZWLHBS43.js → doctor-cli-R3SWTL5Z.js} +5 -5
  18. package/dist/electron-server.js +1341 -1131
  19. package/dist/{hub-X4OBH5A3.js → hub-3ZGIM2FN.js} +1 -1
  20. package/dist/index.js +480 -584
  21. package/dist/{run-tests-625NA546.js → run-tests-IJYP6BMT.js} +1 -1
  22. package/dist/{run-tests-CRVIUT4O.js → run-tests-NS3SPH6S.js} +2 -2
  23. package/dist/{server-Q3A737OP.js → server-SVTSJ3PK.js} +5 -5
  24. package/dist/{server-O6ZMNWNS.js → server-TZRMRT3O.js} +208 -260
  25. package/dist/{task-orchestrator-TLUGDQMO.js → task-orchestrator-GMJ5PLVV.js} +5 -5
  26. package/dist/{usage-B4OU5CDJ.js → usage-IYMFSHDX.js} +2 -2
  27. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-7HMX2MTY.js";
4
+ } from "./chunk-ODAAPNSL.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  executeTests,
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-2CLMIRKL.js";
6
- import "./chunk-MIXN7VBY.js";
5
+ } from "./chunk-NRSAAMIF.js";
6
+ import "./chunk-UK6E2563.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
  export {
9
9
  executeTests,
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-JBTVDYJM.js";
4
+ } from "./chunk-J3XSJCO5.js";
5
5
  import "./chunk-HDSKW7Q3.js";
6
6
  import "./chunk-ZWVIDFGY.js";
7
- import "./chunk-2CLMIRKL.js";
7
+ import "./chunk-NRSAAMIF.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-V7NTQ6UB.js";
10
+ } from "./chunk-DFQSQQEU.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,9 +15,9 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-MIXN7VBY.js";
18
+ } from "./chunk-UK6E2563.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
- import "./chunk-MM3F43H6.js";
20
+ import "./chunk-4UZE4ADL.js";
21
21
  import "./chunk-KHYD3WXE.js";
22
22
  import "./chunk-SLSWPBK3.js";
23
23
  import "./chunk-VNNYHW6N.js";
@@ -19,7 +19,7 @@ import {
19
19
  loadDevState,
20
20
  persistToolRound,
21
21
  setupProxy
22
- } from "./chunk-OFP5BE7H.js";
22
+ } from "./chunk-VPTRE7IW.js";
23
23
  import {
24
24
  ToolExecutor,
25
25
  ToolRegistry,
@@ -37,10 +37,10 @@ import {
37
37
  spawnAgentContext,
38
38
  truncateOutput,
39
39
  undoStack
40
- } from "./chunk-JBTVDYJM.js";
40
+ } from "./chunk-J3XSJCO5.js";
41
41
  import "./chunk-HDSKW7Q3.js";
42
42
  import "./chunk-ZWVIDFGY.js";
43
- import "./chunk-2CLMIRKL.js";
43
+ import "./chunk-NRSAAMIF.js";
44
44
  import {
45
45
  SessionManager,
46
46
  getContentText
@@ -50,39 +50,28 @@ import {
50
50
  formatCost
51
51
  } from "./chunk-V37XOYOE.js";
52
52
  import {
53
- BudgetWarner,
54
53
  CONTENT_ONLY_STREAM_REMINDER,
55
- ContextPressureMonitor,
56
- EmptyResponseGuard,
57
- FreeRoundTracker,
58
- HALLUCINATION_CORRECTION_MESSAGE,
59
54
  ProviderRegistry,
60
55
  TEE_FINAL_USER_NUDGE,
61
56
  TOOL_CALL_REMINDER,
62
- accumulateUsage,
63
57
  buildRoundBudgetHint,
64
- buildRoundsExhaustedPrompt,
65
- buildUserStopMessage,
66
58
  consumeToolCallStream,
67
59
  detectMetaNarration,
68
60
  detectPseudoToolCalls,
69
- detectsHallucinatedFileOp,
70
- extractBashCommands,
71
- hadPreviousWriteToolCalls,
72
61
  looksLikeDocumentBody,
62
+ runAgentLoop,
73
63
  stripPseudoToolCalls,
74
- stripToolCallReminder,
75
- summarizeRecentTools
76
- } from "./chunk-RADH6ECW.js";
64
+ stripToolCallReminder
65
+ } from "./chunk-IQ7JE43O.js";
77
66
  import {
78
67
  runTool
79
- } from "./chunk-V7NTQ6UB.js";
68
+ } from "./chunk-DFQSQQEU.js";
80
69
  import {
81
70
  getDangerLevel
82
71
  } from "./chunk-HIU2SH4V.js";
83
72
  import {
84
73
  ConfigManager
85
- } from "./chunk-KNGDSMMF.js";
74
+ } from "./chunk-Q7SB3R25.js";
86
75
  import "./chunk-TZQHYZKT.js";
87
76
  import {
88
77
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -102,14 +91,14 @@ import {
102
91
  SKILLS_DIR_NAME,
103
92
  VERSION,
104
93
  buildUserIdentityPrompt
105
- } from "./chunk-MIXN7VBY.js";
94
+ } from "./chunk-UK6E2563.js";
106
95
  import {
107
96
  formatGitContextForPrompt,
108
97
  getGitContext,
109
98
  getGitRoot
110
99
  } from "./chunk-HOSJZMQS.js";
111
100
  import "./chunk-4BKXL7SM.js";
112
- import "./chunk-MM3F43H6.js";
101
+ import "./chunk-4UZE4ADL.js";
113
102
  import "./chunk-KHYD3WXE.js";
114
103
  import "./chunk-SLSWPBK3.js";
115
104
  import "./chunk-VNNYHW6N.js";
@@ -1007,7 +996,6 @@ var SessionHandler = class _SessionHandler {
1007
996
  async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
1008
997
  const session = this.sessions.current;
1009
998
  const apiMessages = [...messages];
1010
- const extraMessages = [];
1011
999
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
1012
1000
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
1013
1001
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
@@ -1018,37 +1006,113 @@ var SessionHandler = class _SessionHandler {
1018
1006
  ${mcpBudgetNote}` : "");
1019
1007
  const systemPromptVolatile = toolVolatile;
1020
1008
  const modelParams = this.getModelParams();
1021
- const roundUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
1009
+ const usage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
1022
1010
  const supportsStreamingTools = typeof provider.chatWithToolsStream === "function";
1023
- const roundToolHistory = [];
1024
- const budgetWarner = new BudgetWarner(maxToolRounds);
1025
- const emptyGuard = new EmptyResponseGuard();
1026
- const ctxMonitor = new ContextPressureMonitor();
1027
- const freeRounds = new FreeRoundTracker();
1028
1011
  const ac = new AbortController();
1029
1012
  this.abortController = ac;
1030
1013
  try {
1031
- for (let round = 0; round < maxToolRounds; round++) {
1032
- if (ac.signal.aborted) break;
1033
- this.toolExecutor.setRoundInfo(round + 1, maxToolRounds);
1034
- this.send({ type: "round_progress", current: round + 1, total: maxToolRounds });
1035
- const budgetWarning = budgetWarner.check(maxToolRounds - round);
1036
- if (budgetWarning) {
1037
- extraMessages.push({ role: "user", content: budgetWarning.injectMessage });
1038
- if (budgetWarning.displayMessage) {
1039
- this.send({ type: "info", message: budgetWarning.displayMessage });
1040
- }
1041
- }
1042
- if (this.userInterjection) {
1014
+ const loopResult = await runAgentLoop({
1015
+ maxToolRounds,
1016
+ autoPauseInterval,
1017
+ planMode: this.planMode,
1018
+ providerId: this.currentProvider,
1019
+ toolDefs,
1020
+ signal: ac.signal,
1021
+ usage,
1022
+ callModel: async (_round, extraMessages) => {
1023
+ const chatRequest = {
1024
+ messages: apiMessages,
1025
+ model: this.currentModel,
1026
+ systemPrompt,
1027
+ systemPromptVolatile,
1028
+ stream: false,
1029
+ temperature: modelParams.temperature,
1030
+ maxTokens: modelParams.maxTokens,
1031
+ timeout: modelParams.timeout,
1032
+ thinking: modelParams.thinking,
1033
+ thinkingBudget: modelParams.thinkingBudget,
1034
+ signal: ac.signal,
1035
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
1036
+ };
1037
+ try {
1038
+ if (supportsStreamingTools) {
1039
+ const streamGen = provider.chatWithToolsStream(chatRequest, toolDefs);
1040
+ return await this.consumeToolStream(streamGen, ac);
1041
+ }
1042
+ const result = await provider.chatWithTools(chatRequest, toolDefs);
1043
+ return result;
1044
+ } catch (providerErr) {
1045
+ const errMsg = providerErr instanceof Error ? providerErr.message : String(providerErr);
1046
+ const isCtxLengthError = /maximum context length|context_length_exceeded|context window|too many tokens|reduce the length of the messages/i.test(errMsg);
1047
+ if (isCtxLengthError) {
1048
+ this.send({
1049
+ type: "response_done",
1050
+ content: `\u26A0 Context length exceeded \u2014 the conversation is too long for this model.
1051
+
1052
+ Details: ${errMsg.split("\n")[0]}
1053
+
1054
+ **Recovery options**:
1055
+ 1. Run \`/compact\` to summarize old messages and free context
1056
+ 2. Run \`/clear\` to start a fresh session
1057
+ 3. Run \`/model\` to switch to a model with a larger context window`,
1058
+ usage
1059
+ });
1060
+ return { stopLoop: true };
1061
+ }
1062
+ throw providerErr;
1063
+ }
1064
+ },
1065
+ callSummary: async (summaryExtra) => {
1066
+ const summaryResult = await provider.chatWithTools(
1067
+ {
1068
+ messages: apiMessages,
1069
+ model: this.currentModel,
1070
+ systemPrompt,
1071
+ systemPromptVolatile,
1072
+ stream: false,
1073
+ temperature: modelParams.temperature,
1074
+ maxTokens: modelParams.maxTokens,
1075
+ timeout: modelParams.timeout,
1076
+ _extraMessages: summaryExtra
1077
+ },
1078
+ []
1079
+ );
1080
+ return "content" in summaryResult ? { content: summaryResult.content, usage: summaryResult.usage } : { usage: summaryResult.usage };
1081
+ },
1082
+ executeTools: async (toolCalls) => {
1083
+ googleSearchContext.configManager = this.config;
1084
+ spawnAgentContext.provider = provider;
1085
+ spawnAgentContext.model = this.currentModel;
1086
+ spawnAgentContext.systemPrompt = systemPromptVolatile ? `${systemPrompt}
1087
+
1088
+ ---
1089
+
1090
+ ${systemPromptVolatile}` : systemPrompt;
1091
+ spawnAgentContext.modelParams = modelParams;
1092
+ spawnAgentContext.configManager = this.config;
1093
+ ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
1094
+ return this.toolExecutor.executeAll(toolCalls);
1095
+ },
1096
+ buildToolResultMessages: (toolCalls, results, reasoningContent) => provider.buildToolResultMessages(toolCalls, results, reasoningContent),
1097
+ getContextWindow: () => this.getContextWindowSize(),
1098
+ estimateRequestTokens: (extraMessages) => this.estimateRequestTokens(systemPrompt, extraMessages),
1099
+ pollInterjection: () => {
1100
+ if (!this.userInterjection) return null;
1043
1101
  const msg = this.userInterjection;
1044
1102
  this.userInterjection = null;
1045
1103
  this.send({ type: "info", message: `\u26A1 Interjection: "${msg}"` });
1046
- extraMessages.push({ role: "user", content: msg });
1047
- }
1048
- const ctxWindow = this.getContextWindowSize();
1049
- if (ctxWindow > 0) {
1050
- const reqTokens = this.estimateRequestTokens(systemPrompt, extraMessages);
1051
- const pressure = ctxMonitor.check(reqTokens, ctxWindow);
1104
+ return msg;
1105
+ },
1106
+ onRoundStart: (round, total) => {
1107
+ this.toolExecutor.setRoundInfo(round + 1, total);
1108
+ this.send({ type: "round_progress", current: round + 1, total });
1109
+ },
1110
+ onBudgetWarning: (warning) => {
1111
+ if (warning.displayMessage) {
1112
+ this.send({ type: "info", message: warning.displayMessage });
1113
+ }
1114
+ },
1115
+ onContextPressure: (pressure, ctxWindow) => {
1052
1116
  if (pressure.action === "abort") {
1053
1117
  this.send({
1054
1118
  type: "response_done",
@@ -1060,191 +1124,48 @@ Too much tool output accumulated this turn. Your work so far is preserved.
1060
1124
  1. Run \`/compact\` to shrink history, then ask the AI to continue
1061
1125
  2. Run \`/clear\` to start fresh
1062
1126
  3. Switch to a larger-context model`,
1063
- usage: roundUsage
1127
+ usage
1064
1128
  });
1065
- this.addWebSessionUsage(roundUsage);
1066
- session.addTokenUsage(roundUsage);
1067
- return;
1068
- } else if (pressure.action === "warn") {
1129
+ } else {
1069
1130
  this.send({
1070
1131
  type: "info",
1071
1132
  message: `\u26A0 Context at ${Math.round(pressure.ratio * 100)}% \u2014 asking AI to wrap up`
1072
1133
  });
1073
- extraMessages.push({ role: "user", content: pressure.injectMessage });
1074
- }
1075
- }
1076
- const chatRequest = {
1077
- messages: apiMessages,
1078
- model: this.currentModel,
1079
- systemPrompt,
1080
- systemPromptVolatile,
1081
- stream: false,
1082
- temperature: modelParams.temperature,
1083
- maxTokens: modelParams.maxTokens,
1084
- timeout: modelParams.timeout,
1085
- thinking: modelParams.thinking,
1086
- thinkingBudget: modelParams.thinkingBudget,
1087
- signal: ac.signal,
1088
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
1089
- };
1090
- let result;
1091
- try {
1092
- if (supportsStreamingTools) {
1093
- const streamGen = provider.chatWithToolsStream(chatRequest, toolDefs);
1094
- result = await this.consumeToolStream(streamGen, ac);
1095
- } else {
1096
- result = await provider.chatWithTools(chatRequest, toolDefs);
1097
1134
  }
1098
- } catch (providerErr) {
1099
- const errMsg = providerErr instanceof Error ? providerErr.message : String(providerErr);
1100
- const isCtxLengthError = /maximum context length|context_length_exceeded|context window|too many tokens|reduce the length of the messages/i.test(errMsg);
1101
- if (isCtxLengthError) {
1102
- this.send({
1103
- type: "response_done",
1104
- content: `\u26A0 Context length exceeded \u2014 the conversation is too long for this model.
1105
-
1106
- Details: ${errMsg.split("\n")[0]}
1107
-
1108
- **Recovery options**:
1109
- 1. Run \`/compact\` to summarize old messages and free context
1110
- 2. Run \`/clear\` to start a fresh session
1111
- 3. Run \`/model\` to switch to a model with a larger context window`,
1112
- usage: roundUsage
1113
- });
1114
- this.addWebSessionUsage(roundUsage);
1115
- session.addTokenUsage(roundUsage);
1116
- return;
1117
- }
1118
- throw providerErr;
1119
- }
1120
- if (ac.signal.aborted) break;
1121
- accumulateUsage(roundUsage, result.usage);
1122
- const hasToolCalls = !!(result.toolCalls && result.toolCalls.length > 0);
1123
- const contentBlank = !result.content || result.content.trim() === "";
1124
- if (!hasToolCalls && contentBlank) {
1125
- const decision = emptyGuard.onEmpty(round < maxToolRounds - 1, result.finishReason);
1135
+ },
1136
+ onHallucinationRetry: ({ phantomPaths }) => {
1137
+ const detail = phantomPaths.length > 0 ? ` (phantom files: ${phantomPaths.join(", ")})` : "";
1138
+ this.send({ type: "info", message: `\u26A0 Hallucinated completion detected, forcing retry...${detail}` });
1139
+ },
1140
+ onEmptyResponse: (decision) => {
1126
1141
  if (decision.action === "nudge") {
1127
1142
  this.send({ type: "info", message: decision.displayMessage });
1128
- extraMessages.push({ role: "user", content: decision.injectMessage });
1129
- continue;
1130
- }
1131
- this.send({
1132
- type: "response_done",
1133
- content: `${decision.displayMessage}
1143
+ } else {
1144
+ this.send({
1145
+ type: "response_done",
1146
+ content: `${decision.displayMessage}
1134
1147
 
1135
1148
  ${decision.hint}
1136
1149
  Try: /compact to reduce context, /clear to reset, or switch to a larger-context model.`,
1137
- usage: roundUsage
1138
- });
1139
- this.addWebSessionUsage(roundUsage);
1140
- session.addTokenUsage(roundUsage);
1141
- return;
1142
- }
1143
- emptyGuard.onNonEmpty();
1144
- if (result.content && !result.toolCalls) {
1145
- const hasWriteTools = toolDefs.some((t) => t.name === "write_file" || t.name === "edit_file");
1146
- const alreadyWrote = hadPreviousWriteToolCalls(extraMessages);
1147
- const bashRanThisTurn = extractBashCommands(extraMessages).length > 0;
1148
- if (hasWriteTools && !alreadyWrote && !bashRanThisTurn && detectsHallucinatedFileOp(result.content) && round < maxToolRounds - 1) {
1149
- this.send({ type: "info", message: "\u26A0 Hallucinated completion detected, forcing retry..." });
1150
- const reasoningField = result.reasoningContent ? { reasoning_content: result.reasoningContent } : this.currentProvider === "deepseek" ? { reasoning_content: "" } : {};
1151
- extraMessages.push(
1152
- { role: "assistant", content: result.content, ...reasoningField },
1153
- { role: "user", content: HALLUCINATION_CORRECTION_MESSAGE }
1154
- );
1155
- continue;
1156
- }
1157
- this.send({ type: "response_done", content: result.content, usage: roundUsage });
1150
+ usage
1151
+ });
1152
+ }
1153
+ },
1154
+ onFinalContent: (content, { reasoningContent }) => {
1155
+ this.send({ type: "response_done", content, usage });
1158
1156
  session.addMessage({
1159
1157
  role: "assistant",
1160
- content: result.content,
1158
+ content,
1161
1159
  timestamp: /* @__PURE__ */ new Date(),
1162
- ...result.reasoningContent ? { reasoningContent: result.reasoningContent } : {}
1160
+ ...reasoningContent ? { reasoningContent } : {}
1163
1161
  });
1164
- this.addWebSessionUsage(roundUsage);
1165
- session.addTokenUsage(roundUsage);
1166
- return;
1167
- }
1168
- if (result.toolCalls && result.toolCalls.length > 0) {
1169
- roundToolHistory.push({
1170
- round: round + 1,
1171
- tools: result.toolCalls.map((tc) => tc.name)
1172
- });
1173
- for (const tc of result.toolCalls) {
1174
- if (tc.name.startsWith("mcp__")) this.usedMcpToolNames.add(tc.name);
1175
- }
1176
- googleSearchContext.configManager = this.config;
1177
- spawnAgentContext.provider = provider;
1178
- spawnAgentContext.model = this.currentModel;
1179
- spawnAgentContext.systemPrompt = systemPromptVolatile ? `${systemPrompt}
1180
-
1181
- ---
1182
-
1183
- ${systemPromptVolatile}` : systemPrompt;
1184
- spawnAgentContext.modelParams = modelParams;
1185
- spawnAgentContext.configManager = this.config;
1186
- ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
1187
- const saveLastResponseCall = result.toolCalls.find((tc) => tc.name === "save_last_response");
1188
- const saveLastResponsePath = saveLastResponseCall ? String(saveLastResponseCall.arguments["path"] ?? "") : "";
1189
- if (saveLastResponseCall && saveLastResponsePath) {
1190
- const teeResult = await this.runSaveLastResponseTee(
1191
- provider,
1192
- saveLastResponseCall,
1193
- saveLastResponsePath,
1194
- apiMessages,
1195
- extraMessages,
1196
- systemPrompt,
1197
- systemPromptVolatile,
1198
- modelParams,
1199
- ac,
1200
- roundUsage
1201
- );
1202
- const teeToolResults = result.toolCalls.map((tc) => {
1203
- if (tc.id === saveLastResponseCall.id) {
1204
- return {
1205
- callId: tc.id,
1206
- content: teeResult.summary,
1207
- isError: teeResult.isError
1208
- };
1209
- }
1210
- return {
1211
- callId: tc.id,
1212
- content: "[skipped: file already saved by tee streaming]",
1213
- isError: false
1214
- };
1215
- });
1216
- const reasoningContent2 = result.reasoningContent;
1217
- const newMsgs2 = provider.buildToolResultMessages(result.toolCalls, teeToolResults, reasoningContent2);
1218
- extraMessages.push(...newMsgs2);
1219
- persistToolRound(session, result.toolCalls, teeToolResults, {
1220
- assistantContent: teeResult.content,
1221
- reasoningContent: reasoningContent2
1222
- });
1223
- freeRounds.apply(result.toolCalls.map((tc) => tc.name));
1224
- continue;
1225
- }
1226
- const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
1227
- const reasoningContent = result.reasoningContent;
1228
- const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
1229
- extraMessages.push(...newMsgs);
1230
- persistToolRound(session, result.toolCalls, toolResults, {
1231
- assistantContent: result.content,
1232
- reasoningContent
1233
- });
1234
- if (freeRounds.apply(result.toolCalls.map((tc) => tc.name))) {
1235
- round--;
1236
- }
1237
- if (this.userInterjection) {
1238
- const msg = this.userInterjection;
1239
- this.userInterjection = null;
1240
- this.send({ type: "info", message: `\u26A1 Interjection: "${msg}"` });
1241
- extraMessages.push({ role: "user", content: msg });
1242
- }
1243
- }
1244
- const effectiveRound = round + 1;
1245
- const remaining = maxToolRounds - effectiveRound;
1246
- if (autoPauseInterval > 0 && effectiveRound > 0 && effectiveRound % autoPauseInterval === 0 && remaining > 0 && !ac.signal.aborted) {
1247
- const toolSummary = summarizeRecentTools(roundToolHistory, autoPauseInterval);
1162
+ },
1163
+ persistRound: (toolCalls, results, info) => {
1164
+ persistToolRound(session, toolCalls, results, info);
1165
+ },
1166
+ // Track MCP tool usage for next-turn budget prioritization (C1)
1167
+ onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
1168
+ requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
1248
1169
  const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1249
1170
  const pauseResp = await new Promise((resolve3) => {
1250
1171
  this.pendingAutoPause.set(requestId, resolve3);
@@ -1252,58 +1173,85 @@ ${systemPromptVolatile}` : systemPrompt;
1252
1173
  type: "auto_pause_request",
1253
1174
  requestId,
1254
1175
  currentRound: effectiveRound,
1255
- totalRounds: maxToolRounds,
1176
+ totalRounds,
1256
1177
  toolSummary
1257
1178
  });
1258
1179
  });
1259
- if (ac.signal.aborted) break;
1260
1180
  if (pauseResp.action === "stop") {
1261
- this.send({ type: "info", message: `\u23F8 Stopped by user at ${effectiveRound}/${maxToolRounds}` });
1262
- extraMessages.push({ role: "user", content: buildUserStopMessage(effectiveRound, maxToolRounds) });
1263
- break;
1181
+ this.send({ type: "info", message: `\u23F8 Stopped by user at ${effectiveRound}/${totalRounds}` });
1264
1182
  } else if (pauseResp.action === "redirect" && pauseResp.message) {
1265
1183
  this.send({ type: "info", message: `\u26A1 Redirect: "${pauseResp.message}"` });
1266
- extraMessages.push({ role: "user", content: pauseResp.message });
1267
1184
  }
1268
- }
1269
- }
1270
- try {
1271
- const summaryExtra = [
1272
- ...extraMessages,
1273
- { role: "user", content: buildRoundsExhaustedPrompt(maxToolRounds) }
1274
- ];
1275
- const summaryResult = await provider.chatWithTools(
1276
- {
1277
- messages: apiMessages,
1278
- model: this.currentModel,
1185
+ return pauseResp;
1186
+ },
1187
+ onRoundsExhausted: (summaryContent) => {
1188
+ if (summaryContent !== null) {
1189
+ this.send({
1190
+ type: "response_done",
1191
+ content: `\u26A0 Reached maximum tool call rounds (${maxToolRounds}).
1192
+
1193
+ ${summaryContent}`,
1194
+ usage
1195
+ });
1196
+ session.addMessage({ role: "assistant", content: summaryContent, timestamp: /* @__PURE__ */ new Date() });
1197
+ } else {
1198
+ this.send({
1199
+ type: "error",
1200
+ message: `Reached maximum tool call rounds (${maxToolRounds}). You can continue by asking the AI to proceed.`
1201
+ });
1202
+ }
1203
+ },
1204
+ // ─── save_last_response tee-streaming(v0.4.102+)─────────────────
1205
+ // AI 在 Web 模式下调用 save_last_response 时,复刻 REPL 的 tee 流式路径:
1206
+ // 发起一次新的 chatStream → 实时通过 WS 推送文本 + 同步写盘 → 注入合成
1207
+ // 工具结果。否则该工具会落到默认 executor,读到空的 lastResponseStore
1208
+ // 直接报错;用户被迫退到 write_file,又因 tool_call arguments 截断
1209
+ // (~2KB) 只能写出片段,再用 edit_file 反复 insert 才能补全(v0.4.101 报告)。
1210
+ // 与 REPL 不同:Web 端 tee 成功后继续 agentic 循环(返回 'continue'),
1211
+ // 让模型基于工具结果给出最终文本。
1212
+ runSaveLastResponseTee: async ({ toolCalls, call, saveToFile, extraMessages, reasoningContent }) => {
1213
+ const teeResult = await this.runSaveLastResponseTee(
1214
+ provider,
1215
+ call,
1216
+ saveToFile,
1217
+ apiMessages,
1218
+ extraMessages,
1279
1219
  systemPrompt,
1280
1220
  systemPromptVolatile,
1281
- stream: false,
1282
- temperature: modelParams.temperature,
1283
- maxTokens: modelParams.maxTokens,
1284
- timeout: modelParams.timeout,
1285
- _extraMessages: summaryExtra
1286
- },
1287
- []
1288
- );
1289
- if ("content" in summaryResult && summaryResult.content) {
1290
- this.send({
1291
- type: "response_done",
1292
- content: `\u26A0 Reached maximum tool call rounds (${maxToolRounds}).
1293
-
1294
- ${summaryResult.content}`,
1295
- usage: roundUsage
1221
+ modelParams,
1222
+ ac,
1223
+ usage
1224
+ );
1225
+ const teeToolResults = toolCalls.map((tc) => {
1226
+ if (tc.id === call.id) {
1227
+ return {
1228
+ callId: tc.id,
1229
+ content: teeResult.summary,
1230
+ isError: teeResult.isError
1231
+ };
1232
+ }
1233
+ return {
1234
+ callId: tc.id,
1235
+ content: "[skipped: file already saved by tee streaming]",
1236
+ isError: false
1237
+ };
1238
+ });
1239
+ const newMsgs = provider.buildToolResultMessages(toolCalls, teeToolResults, reasoningContent);
1240
+ extraMessages.push(...newMsgs);
1241
+ persistToolRound(session, toolCalls, teeToolResults, {
1242
+ assistantContent: teeResult.content,
1243
+ reasoningContent
1296
1244
  });
1297
- session.addMessage({ role: "assistant", content: summaryResult.content, timestamp: /* @__PURE__ */ new Date() });
1245
+ return "continue";
1298
1246
  }
1299
- } catch {
1300
- this.send({
1301
- type: "error",
1302
- message: `Reached maximum tool call rounds (${maxToolRounds}). You can continue by asking the AI to proceed.`
1303
- });
1247
+ });
1248
+ if (loopResult.reason !== "tee-stop") {
1249
+ this.addWebSessionUsage(usage);
1250
+ session.addTokenUsage(usage);
1251
+ }
1252
+ if (loopResult.reason === "aborted") {
1253
+ this.send({ type: "info", message: "[interrupted]" });
1304
1254
  }
1305
- this.addWebSessionUsage(roundUsage);
1306
- session.addTokenUsage(roundUsage);
1307
1255
  } catch (err) {
1308
1256
  if (err.name === "AbortError") {
1309
1257
  this.send({ type: "info", message: "[interrupted]" });
@@ -2489,7 +2437,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2489
2437
  case "test": {
2490
2438
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2491
2439
  try {
2492
- const { executeTests } = await import("./run-tests-CRVIUT4O.js");
2440
+ const { executeTests } = await import("./run-tests-NS3SPH6S.js");
2493
2441
  const argStr = args.join(" ").trim();
2494
2442
  let testArgs = {};
2495
2443
  if (argStr) {
@@ -3013,7 +2961,7 @@ Add .md files to create commands.` });
3013
2961
  return;
3014
2962
  }
3015
2963
  try {
3016
- const { searchChatMemory, loadChatIndex } = await import("./chat-index-2I7ZHRE5.js");
2964
+ const { searchChatMemory, loadChatIndex } = await import("./chat-index-JXTYDRCY.js");
3017
2965
  const loaded = loadChatIndex();
3018
2966
  if (!loaded || loaded.idx.chunks.length === 0) {
3019
2967
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -3049,7 +2997,7 @@ Add .md files to create commands.` });
3049
2997
  }
3050
2998
  async handleMemoryStatus() {
3051
2999
  try {
3052
- const { getChatIndexStatus } = await import("./chat-index-2I7ZHRE5.js");
3000
+ const { getChatIndexStatus } = await import("./chat-index-JXTYDRCY.js");
3053
3001
  const s = getChatIndexStatus();
3054
3002
  this.send({
3055
3003
  type: "memory_status",
@@ -3074,7 +3022,7 @@ Add .md files to create commands.` });
3074
3022
  type: "info",
3075
3023
  message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
3076
3024
  });
3077
- const { buildChatIndex } = await import("./chat-index-2I7ZHRE5.js");
3025
+ const { buildChatIndex } = await import("./chat-index-JXTYDRCY.js");
3078
3026
  const stats = await buildChatIndex({
3079
3027
  full,
3080
3028
  onProgress: (p) => {