jinzd-ai-cli 0.4.60 → 0.4.62
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/dist/{chunk-C32FFHMY.js → chunk-672OV76Z.js} +92 -1
- package/dist/{chunk-3YVHYAXK.js → chunk-6OTF2ILP.js} +64 -5
- package/dist/{chunk-2DWWB4KD.js → chunk-LLI6COMK.js} +1 -1
- package/dist/{chunk-X4GL6D5L.js → chunk-YUUCUJHU.js} +1 -1
- package/dist/{hub-JTMNY7JR.js → hub-2XLQSZY2.js} +1 -1
- package/dist/index.js +43 -9
- package/dist/{run-tests-WGBDMO4H.js → run-tests-HHIQ3HLM.js} +1 -1
- package/dist/{run-tests-QGJHXL5Z.js → run-tests-ORVJAUJG.js} +1 -1
- package/dist/{server-L2XJYXMB.js → server-HBAOUOIC.js} +49 -22
- package/dist/{task-orchestrator-Z4IK3UEA.js → task-orchestrator-2ATTBG2S.js} +2 -2
- package/dist/web/client/app.js +95 -16
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
11
11
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
12
12
|
runTestsTool
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-LLI6COMK.js";
|
|
14
14
|
|
|
15
15
|
// src/tools/builtin/bash.ts
|
|
16
16
|
import { execSync } from "child_process";
|
|
@@ -4062,6 +4062,28 @@ var notebookEditTool = {
|
|
|
4062
4062
|
}
|
|
4063
4063
|
};
|
|
4064
4064
|
|
|
4065
|
+
// src/core/token-estimator.ts
|
|
4066
|
+
var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
|
|
4067
|
+
function estimateTokens(text) {
|
|
4068
|
+
if (!text) return 0;
|
|
4069
|
+
const cjkMatches = text.match(CJK_REGEX);
|
|
4070
|
+
const cjkCount = cjkMatches ? cjkMatches.length : 0;
|
|
4071
|
+
const nonCjkCount = text.length - cjkCount;
|
|
4072
|
+
const tokens = cjkCount * 1.5 + nonCjkCount * 0.25;
|
|
4073
|
+
return Math.ceil(tokens) + 4;
|
|
4074
|
+
}
|
|
4075
|
+
function estimateToolDefinitionTokens(def) {
|
|
4076
|
+
let charCount = def.name.length + def.description.length;
|
|
4077
|
+
for (const [key, param] of Object.entries(def.parameters)) {
|
|
4078
|
+
charCount += key.length + param.type.length + param.description.length;
|
|
4079
|
+
if (param.enum) {
|
|
4080
|
+
charCount += param.enum.join(" ").length;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
const structuralOverhead = 40 + Object.keys(def.parameters).length * 30;
|
|
4084
|
+
return Math.ceil((charCount + structuralOverhead) * 0.25) + 10;
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4065
4087
|
// src/tools/registry.ts
|
|
4066
4088
|
import { pathToFileURL } from "url";
|
|
4067
4089
|
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync6 } from "fs";
|
|
@@ -4110,6 +4132,74 @@ var ToolRegistry = class {
|
|
|
4110
4132
|
getDefinitions() {
|
|
4111
4133
|
return [...this.tools.values()].map((t) => t.definition);
|
|
4112
4134
|
}
|
|
4135
|
+
/**
|
|
4136
|
+
* Return tool definitions within a token budget, trimming MCP tools if needed.
|
|
4137
|
+
*
|
|
4138
|
+
* Strategy: always include all builtin + plugin tools. If total tokens exceed
|
|
4139
|
+
* the budget, keep only MCP tools that were used in this session (by name),
|
|
4140
|
+
* trimming the rest. Returns { definitions, trimmedCount, systemNote }.
|
|
4141
|
+
*
|
|
4142
|
+
* @param tokenBudget Max tokens for tool definitions (typically 20% of context window)
|
|
4143
|
+
* @param usedToolNames Names of MCP tools already called this session (always kept)
|
|
4144
|
+
*/
|
|
4145
|
+
getDefinitionsWithBudget(tokenBudget, usedToolNames) {
|
|
4146
|
+
const allDefs = this.getDefinitions();
|
|
4147
|
+
let totalTokens = 0;
|
|
4148
|
+
for (const def of allDefs) {
|
|
4149
|
+
totalTokens += estimateToolDefinitionTokens(def);
|
|
4150
|
+
}
|
|
4151
|
+
if (totalTokens <= tokenBudget) {
|
|
4152
|
+
return { definitions: allDefs, trimmedCount: 0, systemNote: null };
|
|
4153
|
+
}
|
|
4154
|
+
const builtinDefs = [];
|
|
4155
|
+
const mcpDefs = [];
|
|
4156
|
+
for (const def of allDefs) {
|
|
4157
|
+
if (this.mcpToolNames.has(def.name)) {
|
|
4158
|
+
mcpDefs.push(def);
|
|
4159
|
+
} else {
|
|
4160
|
+
builtinDefs.push(def);
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
let budget = tokenBudget;
|
|
4164
|
+
for (const def of builtinDefs) {
|
|
4165
|
+
budget -= estimateToolDefinitionTokens(def);
|
|
4166
|
+
}
|
|
4167
|
+
const kept = [...builtinDefs];
|
|
4168
|
+
const used = usedToolNames ?? /* @__PURE__ */ new Set();
|
|
4169
|
+
const remaining = [];
|
|
4170
|
+
for (const def of mcpDefs) {
|
|
4171
|
+
if (used.has(def.name)) {
|
|
4172
|
+
const cost = estimateToolDefinitionTokens(def);
|
|
4173
|
+
budget -= cost;
|
|
4174
|
+
kept.push(def);
|
|
4175
|
+
} else {
|
|
4176
|
+
remaining.push(def);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
let trimmed = 0;
|
|
4180
|
+
for (const def of remaining) {
|
|
4181
|
+
const cost = estimateToolDefinitionTokens(def);
|
|
4182
|
+
if (budget >= cost) {
|
|
4183
|
+
budget -= cost;
|
|
4184
|
+
kept.push(def);
|
|
4185
|
+
} else {
|
|
4186
|
+
trimmed++;
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
let systemNote = null;
|
|
4190
|
+
if (trimmed > 0) {
|
|
4191
|
+
const trimmedServers = /* @__PURE__ */ new Set();
|
|
4192
|
+
const keptNames = new Set(kept.map((d) => d.name));
|
|
4193
|
+
for (const def of mcpDefs) {
|
|
4194
|
+
if (!keptNames.has(def.name)) {
|
|
4195
|
+
const parts = def.name.split("__");
|
|
4196
|
+
if (parts.length >= 2) trimmedServers.add(parts[1]);
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
systemNote = `[MCP Tool Budget] ${trimmed} MCP tool(s) from server(s) [${[...trimmedServers].join(", ")}] were excluded to fit the context window. If you need a specific MCP tool that isn't listed, tell the user to ask for it explicitly.`;
|
|
4200
|
+
}
|
|
4201
|
+
return { definitions: kept, trimmedCount: trimmed, systemNote };
|
|
4202
|
+
}
|
|
4113
4203
|
listAll() {
|
|
4114
4204
|
return [...this.tools.values()];
|
|
4115
4205
|
}
|
|
@@ -4229,5 +4319,6 @@ export {
|
|
|
4229
4319
|
askUserContext,
|
|
4230
4320
|
googleSearchContext,
|
|
4231
4321
|
spawnAgentContext,
|
|
4322
|
+
estimateTokens,
|
|
4232
4323
|
ToolRegistry
|
|
4233
4324
|
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RateLimitError,
|
|
9
9
|
schemaToJsonSchema,
|
|
10
10
|
truncateForPersist
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-672OV76Z.js";
|
|
12
12
|
import {
|
|
13
13
|
APP_NAME,
|
|
14
14
|
CONFIG_DIR_NAME,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
MCP_TOOL_PREFIX,
|
|
22
22
|
PLUGINS_DIR_NAME,
|
|
23
23
|
VERSION
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-LLI6COMK.js";
|
|
25
25
|
|
|
26
26
|
// src/config/config-manager.ts
|
|
27
27
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -2462,14 +2462,25 @@ var Session = class _Session {
|
|
|
2462
2462
|
/**
|
|
2463
2463
|
* 上下文压缩:用摘要消息替换旧消息,保留最近 keepLast 条。
|
|
2464
2464
|
*
|
|
2465
|
+
* Tool-history-aware: if the cut point lands inside a tool round
|
|
2466
|
+
* (assistant+toolCalls followed by tool results), expand to keep the
|
|
2467
|
+
* entire round intact. This prevents orphaned tool results.
|
|
2468
|
+
*
|
|
2465
2469
|
* 压缩后消息结构:
|
|
2466
|
-
* [summaryMsg(user), ackMsg(assistant), ...最近
|
|
2470
|
+
* [summaryMsg(user), ackMsg(assistant), ...最近 N 条原始消息]
|
|
2467
2471
|
*
|
|
2468
2472
|
* @returns 被删除的消息条数
|
|
2469
2473
|
*/
|
|
2470
2474
|
compact(summaryMsg, ackMsg, keepLast) {
|
|
2471
|
-
|
|
2472
|
-
|
|
2475
|
+
let cutIndex = this.messages.length - keepLast;
|
|
2476
|
+
if (cutIndex <= 0) {
|
|
2477
|
+
return 0;
|
|
2478
|
+
}
|
|
2479
|
+
while (cutIndex > 0 && this.messages[cutIndex]?.role === "tool") {
|
|
2480
|
+
cutIndex--;
|
|
2481
|
+
}
|
|
2482
|
+
const preserved = this.messages.slice(cutIndex);
|
|
2483
|
+
const removedCount = cutIndex;
|
|
2473
2484
|
this.messages = [summaryMsg, ackMsg, ...preserved];
|
|
2474
2485
|
this.updated = /* @__PURE__ */ new Date();
|
|
2475
2486
|
return removedCount;
|
|
@@ -3687,6 +3698,7 @@ function formatCost(amount) {
|
|
|
3687
3698
|
}
|
|
3688
3699
|
|
|
3689
3700
|
// src/session/tool-history.ts
|
|
3701
|
+
var SESSION_SIZE_LIMIT = 2 * 1024 * 1024;
|
|
3690
3702
|
function persistToolRound(session, toolCalls, toolResults, opts) {
|
|
3691
3703
|
session.addMessage({
|
|
3692
3704
|
role: "assistant",
|
|
@@ -3757,6 +3769,52 @@ function rebuildExtraMessages(provider, toolHistory) {
|
|
|
3757
3769
|
}
|
|
3758
3770
|
return result;
|
|
3759
3771
|
}
|
|
3772
|
+
function trimOldToolOutput(messages, keepRecentRounds = 10) {
|
|
3773
|
+
const roundStarts = [];
|
|
3774
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3775
|
+
if (messages[i].role === "assistant" && messages[i].toolCalls?.length) {
|
|
3776
|
+
roundStarts.push(i);
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
if (roundStarts.length <= keepRecentRounds) return 0;
|
|
3780
|
+
const cutoffRoundIdx = roundStarts.length - keepRecentRounds;
|
|
3781
|
+
let trimmed = 0;
|
|
3782
|
+
for (let r = 0; r < cutoffRoundIdx; r++) {
|
|
3783
|
+
const start = roundStarts[r];
|
|
3784
|
+
const end = r + 1 < roundStarts.length ? roundStarts[r + 1] : messages.length;
|
|
3785
|
+
const assistantMsg = messages[start];
|
|
3786
|
+
if (typeof assistantMsg.content === "string" && assistantMsg.content.length > 200) {
|
|
3787
|
+
assistantMsg.content = assistantMsg.content.slice(0, 100) + "\u2026 [trimmed for size]";
|
|
3788
|
+
trimmed++;
|
|
3789
|
+
}
|
|
3790
|
+
for (let j = start + 1; j < end; j++) {
|
|
3791
|
+
const msg = messages[j];
|
|
3792
|
+
if (msg.role === "tool") {
|
|
3793
|
+
const status = msg.isError ? "\u2717 error" : "\u2713 ok";
|
|
3794
|
+
const name = msg.toolName ?? "unknown";
|
|
3795
|
+
const currentContent = typeof msg.content === "string" ? msg.content : "";
|
|
3796
|
+
if (currentContent.length > 200) {
|
|
3797
|
+
msg.content = `[${name}: ${status}] (output trimmed for size \u2014 ${currentContent.length} chars)`;
|
|
3798
|
+
trimmed++;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return trimmed;
|
|
3804
|
+
}
|
|
3805
|
+
function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
3806
|
+
const json = JSON.stringify(session.toJSON());
|
|
3807
|
+
if (json.length <= sizeLimit) return false;
|
|
3808
|
+
let keepRecent = 10;
|
|
3809
|
+
while (keepRecent >= 2) {
|
|
3810
|
+
const trimmed = trimOldToolOutput(session.messages, keepRecent);
|
|
3811
|
+
if (trimmed === 0) break;
|
|
3812
|
+
const newSize = JSON.stringify(session.toJSON()).length;
|
|
3813
|
+
if (newSize <= sizeLimit) return true;
|
|
3814
|
+
keepRecent = Math.max(2, Math.floor(keepRecent / 2));
|
|
3815
|
+
}
|
|
3816
|
+
return true;
|
|
3817
|
+
}
|
|
3760
3818
|
|
|
3761
3819
|
// src/repl/dev-state.ts
|
|
3762
3820
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
@@ -3871,6 +3929,7 @@ export {
|
|
|
3871
3929
|
persistToolRound,
|
|
3872
3930
|
extractToolHistory,
|
|
3873
3931
|
rebuildExtraMessages,
|
|
3932
|
+
autoTrimSessionIfNeeded,
|
|
3874
3933
|
SNAPSHOT_PROMPT,
|
|
3875
3934
|
sessionHasMeaningfulContent,
|
|
3876
3935
|
saveDevState,
|
|
@@ -385,7 +385,7 @@ ${content}`);
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
388
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
388
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-2ATTBG2S.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SessionManager,
|
|
9
9
|
SkillManager,
|
|
10
10
|
TOOL_CALL_REMINDER,
|
|
11
|
+
autoTrimSessionIfNeeded,
|
|
11
12
|
buildPhantomCorrectionMessage,
|
|
12
13
|
buildWriteRoundReminder,
|
|
13
14
|
clearDevState,
|
|
@@ -30,11 +31,12 @@ import {
|
|
|
30
31
|
saveDevState,
|
|
31
32
|
sessionHasMeaningfulContent,
|
|
32
33
|
setupProxy
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-6OTF2ILP.js";
|
|
34
35
|
import {
|
|
35
36
|
ToolExecutor,
|
|
36
37
|
ToolRegistry,
|
|
37
38
|
askUserContext,
|
|
39
|
+
estimateTokens,
|
|
38
40
|
googleSearchContext,
|
|
39
41
|
initTheme,
|
|
40
42
|
lastResponseStore,
|
|
@@ -44,7 +46,7 @@ import {
|
|
|
44
46
|
spawnAgentContext,
|
|
45
47
|
theme,
|
|
46
48
|
undoStack
|
|
47
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-672OV76Z.js";
|
|
48
50
|
import {
|
|
49
51
|
fileCheckpoints
|
|
50
52
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -69,7 +71,7 @@ import {
|
|
|
69
71
|
SKILLS_DIR_NAME,
|
|
70
72
|
VERSION,
|
|
71
73
|
buildUserIdentityPrompt
|
|
72
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-LLI6COMK.js";
|
|
73
75
|
|
|
74
76
|
// src/index.ts
|
|
75
77
|
import { program } from "commander";
|
|
@@ -2164,7 +2166,7 @@ ${hint}` : "")
|
|
|
2164
2166
|
usage: "/test [command|filter]",
|
|
2165
2167
|
async execute(args, ctx) {
|
|
2166
2168
|
try {
|
|
2167
|
-
const { executeTests } = await import("./run-tests-
|
|
2169
|
+
const { executeTests } = await import("./run-tests-ORVJAUJG.js");
|
|
2168
2170
|
const argStr = args.join(" ").trim();
|
|
2169
2171
|
let testArgs = {};
|
|
2170
2172
|
if (argStr) {
|
|
@@ -3910,6 +3912,11 @@ ${skillContent}`);
|
|
|
3910
3912
|
"4. Current status and tasks not yet completed",
|
|
3911
3913
|
"5. Key details to remember (file paths, config values, error messages, variable names, etc.)",
|
|
3912
3914
|
"",
|
|
3915
|
+
"If the conversation contains tool call rounds (assistant calling tools like read_file, write_file, bash, etc.), ",
|
|
3916
|
+
'summarize each round as a single line: "- [tool_name] target \u2192 result/status". ',
|
|
3917
|
+
'Do NOT include raw tool output. Group consecutive similar operations (e.g., "read 5 files in src/") ',
|
|
3918
|
+
"instead of listing each one individually.",
|
|
3919
|
+
"",
|
|
3913
3920
|
"Requirements: Be well-organized so that subsequent conversation can seamlessly continue from this summary."
|
|
3914
3921
|
];
|
|
3915
3922
|
if (instruction) summaryPromptLines.push("", `Additional requirement: ${instruction}`);
|
|
@@ -4317,6 +4324,9 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4317
4324
|
await this.handleChatSimple(provider, session.messages);
|
|
4318
4325
|
}
|
|
4319
4326
|
if (this.config.get("session").autoSave) {
|
|
4327
|
+
if (autoTrimSessionIfNeeded(session)) {
|
|
4328
|
+
process.stderr.write(theme.dim(" [session] Trimmed old tool output to reduce file size\n"));
|
|
4329
|
+
}
|
|
4320
4330
|
await this.sessions.save();
|
|
4321
4331
|
}
|
|
4322
4332
|
const elapsed = Date.now() - t0;
|
|
@@ -4399,7 +4409,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4399
4409
|
* 混合 CJK / ASCII 文本平均约 2.5 字符 = 1 token(与 renderer.ts 中的估算公式一致)。
|
|
4400
4410
|
*/
|
|
4401
4411
|
estimateTokens(text) {
|
|
4402
|
-
return
|
|
4412
|
+
return estimateTokens(text);
|
|
4403
4413
|
}
|
|
4404
4414
|
/**
|
|
4405
4415
|
* 估算当前对话的总 token 消耗(system prompt + 所有 session messages)。
|
|
@@ -4418,6 +4428,11 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4418
4428
|
if (part.type === "text" && part.text) total += this.estimateTokens(part.text);
|
|
4419
4429
|
}
|
|
4420
4430
|
}
|
|
4431
|
+
if (msg.toolCalls) {
|
|
4432
|
+
for (const tc of msg.toolCalls) {
|
|
4433
|
+
total += this.estimateTokens(JSON.stringify(tc.arguments));
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4421
4436
|
}
|
|
4422
4437
|
}
|
|
4423
4438
|
return total;
|
|
@@ -4856,6 +4871,8 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4856
4871
|
async handleChatWithTools(provider, messages) {
|
|
4857
4872
|
const session = this.sessions.current;
|
|
4858
4873
|
let toolDefs;
|
|
4874
|
+
let mcpBudgetNote = null;
|
|
4875
|
+
const usedMcpToolNames = /* @__PURE__ */ new Set();
|
|
4859
4876
|
if (this.planMode) {
|
|
4860
4877
|
toolDefs = this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name));
|
|
4861
4878
|
} else {
|
|
@@ -4863,7 +4880,19 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4863
4880
|
if (skillFilter) {
|
|
4864
4881
|
toolDefs = this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name));
|
|
4865
4882
|
} else {
|
|
4866
|
-
|
|
4883
|
+
const contextWindow = this.getContextWindowSize();
|
|
4884
|
+
if (contextWindow > 0) {
|
|
4885
|
+
const toolBudget = Math.floor(contextWindow * 0.2);
|
|
4886
|
+
const { definitions, trimmedCount, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget, usedMcpToolNames);
|
|
4887
|
+
toolDefs = definitions;
|
|
4888
|
+
mcpBudgetNote = systemNote;
|
|
4889
|
+
if (trimmedCount > 0) {
|
|
4890
|
+
process.stderr.write(theme.dim(` [MCP budget] ${trimmedCount} MCP tool(s) trimmed to fit ${toolBudget.toLocaleString()} token budget
|
|
4891
|
+
`));
|
|
4892
|
+
}
|
|
4893
|
+
} else {
|
|
4894
|
+
toolDefs = this.toolRegistry.getDefinitions();
|
|
4895
|
+
}
|
|
4867
4896
|
}
|
|
4868
4897
|
}
|
|
4869
4898
|
if (this.allowedTools) {
|
|
@@ -4898,7 +4927,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
4898
4927
|
- Do NOT read the same file more than once \u2014 use the content from previous reads.
|
|
4899
4928
|
- Prioritize the most critical tasks first in case rounds run out.
|
|
4900
4929
|
- When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
|
|
4901
|
-
const systemPrompt = baseSystemPrompt + roundBudgetHint
|
|
4930
|
+
const systemPrompt = baseSystemPrompt + roundBudgetHint + (mcpBudgetNote ? `
|
|
4931
|
+
|
|
4932
|
+
${mcpBudgetNote}` : "");
|
|
4902
4933
|
const modelParams = this.getModelParams();
|
|
4903
4934
|
const useStreaming = this.config.get("ui").streaming;
|
|
4904
4935
|
const spinner = this.renderer.showSpinner("Thinking...");
|
|
@@ -5264,6 +5295,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
5264
5295
|
const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
|
|
5265
5296
|
const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
|
|
5266
5297
|
extraMessages.push(...newMsgs);
|
|
5298
|
+
for (const tc of result.toolCalls) {
|
|
5299
|
+
if (tc.name.startsWith("mcp__")) usedMcpToolNames.add(tc.name);
|
|
5300
|
+
}
|
|
5267
5301
|
const streamedContent = "content" in result ? result.content : void 0;
|
|
5268
5302
|
persistToolRound(session, result.toolCalls, toolResults, {
|
|
5269
5303
|
assistantContent: streamedContent,
|
|
@@ -5704,7 +5738,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5704
5738
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5705
5739
|
process.exit(1);
|
|
5706
5740
|
}
|
|
5707
|
-
const { startWebServer } = await import("./server-
|
|
5741
|
+
const { startWebServer } = await import("./server-HBAOUOIC.js");
|
|
5708
5742
|
await startWebServer({ port, host: options.host });
|
|
5709
5743
|
});
|
|
5710
5744
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5937,7 +5971,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5937
5971
|
}),
|
|
5938
5972
|
config.get("customProviders")
|
|
5939
5973
|
);
|
|
5940
|
-
const { startHub } = await import("./hub-
|
|
5974
|
+
const { startHub } = await import("./hub-2XLQSZY2.js");
|
|
5941
5975
|
await startHub(
|
|
5942
5976
|
{
|
|
5943
5977
|
topic: topic ?? "",
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SessionManager,
|
|
8
8
|
SkillManager,
|
|
9
9
|
TOOL_CALL_REMINDER,
|
|
10
|
+
autoTrimSessionIfNeeded,
|
|
10
11
|
computeCost,
|
|
11
12
|
detectsHallucinatedFileOp,
|
|
12
13
|
extractToolHistory,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
persistToolRound,
|
|
21
22
|
rebuildExtraMessages,
|
|
22
23
|
setupProxy
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-6OTF2ILP.js";
|
|
24
25
|
import {
|
|
25
26
|
AuthManager
|
|
26
27
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
ToolRegistry,
|
|
30
31
|
askUserContext,
|
|
31
32
|
checkPermission,
|
|
33
|
+
estimateTokens,
|
|
32
34
|
getDangerLevel,
|
|
33
35
|
googleSearchContext,
|
|
34
36
|
isFileWriteTool,
|
|
@@ -39,7 +41,7 @@ import {
|
|
|
39
41
|
spawnAgentContext,
|
|
40
42
|
truncateOutput,
|
|
41
43
|
undoStack
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-672OV76Z.js";
|
|
43
45
|
import "./chunk-4BKXL7SM.js";
|
|
44
46
|
import {
|
|
45
47
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -59,7 +61,7 @@ import {
|
|
|
59
61
|
SKILLS_DIR_NAME,
|
|
60
62
|
VERSION,
|
|
61
63
|
buildUserIdentityPrompt
|
|
62
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-LLI6COMK.js";
|
|
63
65
|
|
|
64
66
|
// src/web/server.ts
|
|
65
67
|
import express from "express";
|
|
@@ -657,7 +659,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
657
659
|
}
|
|
658
660
|
/** 粗略估算文本 token 数(2.5 chars/token)*/
|
|
659
661
|
estTokens(text) {
|
|
660
|
-
return
|
|
662
|
+
return estimateTokens(text);
|
|
661
663
|
}
|
|
662
664
|
/**
|
|
663
665
|
* 估算当前 agentic 请求总 token 数(session messages + extraMessages + system prompt)。
|
|
@@ -678,6 +680,11 @@ var SessionHandler = class _SessionHandler {
|
|
|
678
680
|
}
|
|
679
681
|
}
|
|
680
682
|
}
|
|
683
|
+
if (msg.toolCalls) {
|
|
684
|
+
for (const tc of msg.toolCalls) {
|
|
685
|
+
total += this.estTokens(JSON.stringify(tc.arguments));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
681
688
|
}
|
|
682
689
|
}
|
|
683
690
|
if (extraMessages.length > 0) {
|
|
@@ -691,6 +698,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
691
698
|
/** Save session only if it exists and has messages (never persist empty "Untitled" sessions). */
|
|
692
699
|
saveIfNeeded() {
|
|
693
700
|
if (this.sessions.current && this.sessions.current.messages.length > 0) {
|
|
701
|
+
autoTrimSessionIfNeeded(this.sessions.current);
|
|
694
702
|
const id = this.sessions.current.id;
|
|
695
703
|
this.sessions.save();
|
|
696
704
|
this.unsavedSessions.delete(id);
|
|
@@ -748,9 +756,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
748
756
|
return;
|
|
749
757
|
}
|
|
750
758
|
const hasToolSupport = typeof provider.chatWithTools === "function";
|
|
751
|
-
const toolDefs = hasToolSupport ? this.getFilteredToolDefs() : [];
|
|
759
|
+
const { toolDefs, mcpBudgetNote } = hasToolSupport ? this.getFilteredToolDefs() : { toolDefs: [], mcpBudgetNote: null };
|
|
752
760
|
if (hasToolSupport && toolDefs.length > 0) {
|
|
753
|
-
await this.handleChatWithTools(provider, session.messages, toolDefs);
|
|
761
|
+
await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote);
|
|
754
762
|
} else {
|
|
755
763
|
await this.handleChatSimple(provider, session.messages);
|
|
756
764
|
}
|
|
@@ -815,7 +823,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
815
823
|
this.abortController = null;
|
|
816
824
|
}
|
|
817
825
|
}
|
|
818
|
-
async handleChatWithTools(provider, messages, toolDefs) {
|
|
826
|
+
async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
|
|
819
827
|
const session = this.sessions.current;
|
|
820
828
|
const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
|
|
821
829
|
const apiMessages = [...cleanMessages];
|
|
@@ -833,7 +841,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
833
841
|
- Prefer batch operations (e.g. global find-and-replace) over repetitive single edits.
|
|
834
842
|
- Prioritize the most critical tasks first in case rounds run out.
|
|
835
843
|
- When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
|
|
836
|
-
const systemPrompt = baseSystemPrompt + roundBudgetHint
|
|
844
|
+
const systemPrompt = baseSystemPrompt + roundBudgetHint + (mcpBudgetNote ? `
|
|
845
|
+
|
|
846
|
+
${mcpBudgetNote}` : "");
|
|
837
847
|
const modelParams = this.getModelParams();
|
|
838
848
|
const roundUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
839
849
|
const supportsStreamingTools = typeof provider.chatWithToolsStream === "function";
|
|
@@ -1923,7 +1933,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1923
1933
|
case "test": {
|
|
1924
1934
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1925
1935
|
try {
|
|
1926
|
-
const { executeTests } = await import("./run-tests-
|
|
1936
|
+
const { executeTests } = await import("./run-tests-ORVJAUJG.js");
|
|
1927
1937
|
const argStr = args.join(" ").trim();
|
|
1928
1938
|
let testArgs = {};
|
|
1929
1939
|
if (argStr) {
|
|
@@ -2440,11 +2450,18 @@ Add .md files to create commands.` });
|
|
|
2440
2450
|
sendSessionMessages() {
|
|
2441
2451
|
const session = this.sessions.current;
|
|
2442
2452
|
if (!session) return;
|
|
2443
|
-
const messages = session.messages.map((m) =>
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2453
|
+
const messages = session.messages.map((m) => {
|
|
2454
|
+
const out = {
|
|
2455
|
+
role: m.role,
|
|
2456
|
+
content: getContentText(m.content),
|
|
2457
|
+
timestamp: m.timestamp?.toISOString()
|
|
2458
|
+
};
|
|
2459
|
+
if (m.toolCalls) out.toolCalls = m.toolCalls;
|
|
2460
|
+
if (m.toolCallId) out.toolCallId = m.toolCallId;
|
|
2461
|
+
if (m.toolName) out.toolName = m.toolName;
|
|
2462
|
+
if (m.isError !== void 0) out.isError = m.isError;
|
|
2463
|
+
return out;
|
|
2464
|
+
});
|
|
2448
2465
|
this.send({
|
|
2449
2466
|
type: "session_messages",
|
|
2450
2467
|
sessionId: session.id,
|
|
@@ -2504,16 +2521,26 @@ Add .md files to create commands.` });
|
|
|
2504
2521
|
};
|
|
2505
2522
|
}
|
|
2506
2523
|
getFilteredToolDefs() {
|
|
2507
|
-
let defs = this.toolRegistry.getDefinitions();
|
|
2508
2524
|
if (this.planMode) {
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2525
|
+
return {
|
|
2526
|
+
toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name)),
|
|
2527
|
+
mcpBudgetNote: null
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
const skillFilter = this.skillManager?.getActiveToolFilter();
|
|
2531
|
+
if (skillFilter) {
|
|
2532
|
+
return {
|
|
2533
|
+
toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name)),
|
|
2534
|
+
mcpBudgetNote: null
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
const contextWindow = this.getContextWindowSize();
|
|
2538
|
+
if (contextWindow > 0) {
|
|
2539
|
+
const toolBudget = Math.floor(contextWindow * 0.2);
|
|
2540
|
+
const { definitions, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget);
|
|
2541
|
+
return { toolDefs: definitions, mcpBudgetNote: systemNote };
|
|
2515
2542
|
}
|
|
2516
|
-
return
|
|
2543
|
+
return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
|
|
2517
2544
|
}
|
|
2518
2545
|
/**
|
|
2519
2546
|
* Find first matching context file in a directory.
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-672OV76Z.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import {
|
|
10
10
|
SUBAGENT_ALLOWED_TOOLS
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LLI6COMK.js";
|
|
12
12
|
|
|
13
13
|
// src/hub/task-orchestrator.ts
|
|
14
14
|
import { createInterface } from "readline";
|
package/dist/web/client/app.js
CHANGED
|
@@ -717,6 +717,49 @@ function escapeHtml(str) {
|
|
|
717
717
|
return div.innerHTML;
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
/**
|
|
721
|
+
* Create a static tool-call card for session history view.
|
|
722
|
+
* Groups an assistant tool-call message with its subsequent tool results.
|
|
723
|
+
* Unlike live tool cards, these have no timer — just the final state.
|
|
724
|
+
*
|
|
725
|
+
* @param {Object} assistantMsg - The assistant message with toolCalls array
|
|
726
|
+
* @param {Object[]} resultMsgs - Subsequent tool-result messages
|
|
727
|
+
*/
|
|
728
|
+
function createHistoryToolCards(assistantMsg, resultMsgs) {
|
|
729
|
+
const toolCalls = assistantMsg.toolCalls || [];
|
|
730
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
731
|
+
const tc = toolCalls[i];
|
|
732
|
+
const rm = resultMsgs[i]; // may be undefined if results are missing
|
|
733
|
+
const isError = rm ? rm.isError : false;
|
|
734
|
+
const resultContent = rm ? rm.content : '';
|
|
735
|
+
|
|
736
|
+
const el = document.createElement('details');
|
|
737
|
+
el.className = 'tool-card tool-border-safe my-1';
|
|
738
|
+
|
|
739
|
+
const statusIcon = rm ? (isError ? '✗' : '✓') : '?';
|
|
740
|
+
const statusClass = rm ? (isError ? 'text-error' : 'text-success') : 'opacity-50';
|
|
741
|
+
const badgeClass = isError ? 'badge-error' : 'badge-info';
|
|
742
|
+
const levelIcon = isError ? '⚠' : '⚙';
|
|
743
|
+
|
|
744
|
+
const argsHtml = tc.arguments ? formatToolArgs(tc.arguments) : '';
|
|
745
|
+
const truncResult = resultContent.length > 500
|
|
746
|
+
? resultContent.slice(0, 500) + '...'
|
|
747
|
+
: resultContent;
|
|
748
|
+
|
|
749
|
+
el.innerHTML = `
|
|
750
|
+
<summary class="flex items-center gap-2 w-full cursor-pointer select-none py-1">
|
|
751
|
+
<span class="badge ${badgeClass} badge-sm gap-1">${levelIcon} ${escapeHtml(tc.name)}</span>
|
|
752
|
+
<span class="tool-result-badge text-xs ml-auto ${statusClass}">${statusIcon}</span>
|
|
753
|
+
</summary>
|
|
754
|
+
<div class="tool-details-body pt-1">
|
|
755
|
+
${argsHtml ? `<div class="tool-args w-full">${argsHtml}</div>` : ''}
|
|
756
|
+
${rm ? `<div class="tool-result-content mt-2 pt-2 border-t border-base-content/10 w-full ${isError ? 'text-error' : 'text-success'}">${statusIcon} ${escapeHtml(truncResult)}</div>` : ''}
|
|
757
|
+
</div>
|
|
758
|
+
`;
|
|
759
|
+
messagesEl.appendChild(el);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
720
763
|
function scrollToBottom() {
|
|
721
764
|
requestAnimationFrame(() => {
|
|
722
765
|
chatArea.scrollTop = chatArea.scrollHeight;
|
|
@@ -1074,6 +1117,56 @@ function updateBatchBar() {
|
|
|
1074
1117
|
* write it into the tab's `messagesHtml` cache; the live DOM is left
|
|
1075
1118
|
* untouched so the active tab's content never flashes wrong data.
|
|
1076
1119
|
*/
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Render a messages array into the live DOM (messagesEl).
|
|
1123
|
+
* Handles user, assistant (text), assistant (toolCalls), and tool result messages.
|
|
1124
|
+
* Groups consecutive assistant+toolCalls with subsequent tool results into cards.
|
|
1125
|
+
*/
|
|
1126
|
+
function renderMessagesArray(messages) {
|
|
1127
|
+
let i = 0;
|
|
1128
|
+
while (i < messages.length) {
|
|
1129
|
+
const m = messages[i];
|
|
1130
|
+
if (m.role === 'user') {
|
|
1131
|
+
addUserMessage(m.content);
|
|
1132
|
+
i++;
|
|
1133
|
+
} else if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
|
|
1134
|
+
// Assistant message with tool calls — collect subsequent tool results
|
|
1135
|
+
const resultMsgs = [];
|
|
1136
|
+
let j = i + 1;
|
|
1137
|
+
while (j < messages.length && messages[j].role === 'tool') {
|
|
1138
|
+
resultMsgs.push(messages[j]);
|
|
1139
|
+
j++;
|
|
1140
|
+
}
|
|
1141
|
+
// If the assistant also had text content, render it first
|
|
1142
|
+
if (m.content && m.content.trim()) {
|
|
1143
|
+
const el = createAssistantMessage();
|
|
1144
|
+
renderMarkdown(el, m.content);
|
|
1145
|
+
}
|
|
1146
|
+
createHistoryToolCards(m, resultMsgs);
|
|
1147
|
+
i = j;
|
|
1148
|
+
} else if (m.role === 'tool') {
|
|
1149
|
+
// Orphan tool result (no preceding assistant+toolCalls) — render as info card
|
|
1150
|
+
const statusIcon = m.isError ? '✗' : '✓';
|
|
1151
|
+
const statusClass = m.isError ? 'text-error' : 'text-success';
|
|
1152
|
+
const el = document.createElement('div');
|
|
1153
|
+
el.className = `tool-card tool-border-safe my-1 p-2 ${statusClass}`;
|
|
1154
|
+
const toolLabel = m.toolName ? escapeHtml(m.toolName) : 'tool';
|
|
1155
|
+
const truncContent = m.content && m.content.length > 300
|
|
1156
|
+
? m.content.slice(0, 300) + '...' : (m.content || '');
|
|
1157
|
+
el.innerHTML = `<span class="badge badge-info badge-sm">${toolLabel}</span> ${statusIcon} ${escapeHtml(truncContent)}`;
|
|
1158
|
+
messagesEl.appendChild(el);
|
|
1159
|
+
i++;
|
|
1160
|
+
} else if (m.role === 'assistant') {
|
|
1161
|
+
const el = createAssistantMessage();
|
|
1162
|
+
renderMarkdown(el, m.content);
|
|
1163
|
+
i++;
|
|
1164
|
+
} else {
|
|
1165
|
+
// system or unknown — skip
|
|
1166
|
+
i++;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1077
1170
|
function renderSessionMessages(msg) {
|
|
1078
1171
|
// Back-compat: if called with a bare array (legacy), treat as active-tab apply
|
|
1079
1172
|
const messages = Array.isArray(msg) ? msg : msg.messages;
|
|
@@ -1096,14 +1189,7 @@ function renderSessionMessages(msg) {
|
|
|
1096
1189
|
// Active tab: paint directly into the live DOM (preserves any in-flight
|
|
1097
1190
|
// streaming helpers that rely on messagesEl)
|
|
1098
1191
|
messagesEl.innerHTML = '';
|
|
1099
|
-
|
|
1100
|
-
if (m.role === 'user') {
|
|
1101
|
-
addUserMessage(m.content);
|
|
1102
|
-
} else if (m.role === 'assistant') {
|
|
1103
|
-
const el = createAssistantMessage();
|
|
1104
|
-
renderMarkdown(el, m.content);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1192
|
+
renderMessagesArray(messages);
|
|
1107
1193
|
scrollToBottom();
|
|
1108
1194
|
// Snapshot into cache so subsequent tab-snapshots see the latest content
|
|
1109
1195
|
sessionTabs[targetIdx].messagesHtml = messagesEl.innerHTML;
|
|
@@ -1136,14 +1222,7 @@ function buildMessagesHtmlOffDom(messages) {
|
|
|
1136
1222
|
currentAssistantContent = '';
|
|
1137
1223
|
currentThinkingEl = null;
|
|
1138
1224
|
currentThinkingContent = '';
|
|
1139
|
-
|
|
1140
|
-
if (m.role === 'user') {
|
|
1141
|
-
addUserMessage(m.content);
|
|
1142
|
-
} else if (m.role === 'assistant') {
|
|
1143
|
-
const el = createAssistantMessage();
|
|
1144
|
-
renderMarkdown(el, m.content);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1225
|
+
renderMessagesArray(messages);
|
|
1147
1226
|
return messagesEl.innerHTML;
|
|
1148
1227
|
} finally {
|
|
1149
1228
|
messagesEl.innerHTML = savedHtml;
|