jinzd-ai-cli 0.4.66 → 0.4.68
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-NIJZBQ6I.js → chunk-3LCVJ4AF.js} +1 -1
- package/dist/{chunk-HOBLE365.js → chunk-G5AISHJE.js} +23 -3
- package/dist/{chunk-X5RX2VGQ.js → chunk-Q5QSCO5D.js} +73 -52
- package/dist/{chunk-UIFW7G4A.js → chunk-VO5IZN2C.js} +1 -1
- package/dist/{hub-JA7HH44N.js → hub-4VPTOMBP.js} +1 -1
- package/dist/index.js +241 -55
- package/dist/{run-tests-E55HLJDD.js → run-tests-OZ3OEOOB.js} +1 -1
- package/dist/{run-tests-47TTKJTB.js → run-tests-WD53PYVA.js} +1 -1
- package/dist/{server-A3SB52SS.js → server-MDBQX5UZ.js} +22 -47
- package/dist/{task-orchestrator-KOCFBDPK.js → task-orchestrator-WDRXASIC.js} +2 -2
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RateLimitError,
|
|
9
9
|
schemaToJsonSchema,
|
|
10
10
|
truncateForPersist
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Q5QSCO5D.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-3LCVJ4AF.js";
|
|
25
25
|
|
|
26
26
|
// src/config/config-manager.ts
|
|
27
27
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -191,7 +191,27 @@ var ConfigSchema = z.object({
|
|
|
191
191
|
// 插件以完整 Node.js 权限在主进程中执行(可读写文件、访问网络、执行命令),
|
|
192
192
|
// 必须确认插件来源可信后,再设为 true 启用。
|
|
193
193
|
// 可通过 /config 命令或直接编辑 ~/.aicli/config.json 开启。
|
|
194
|
-
allowPlugins: z.boolean().default(false)
|
|
194
|
+
allowPlugins: z.boolean().default(false),
|
|
195
|
+
// 智能模型路由(v0.4.68+)
|
|
196
|
+
// 按用户每轮输入的内容/标签/长度动态选择模型,在同一 provider 内切换,
|
|
197
|
+
// 例:短问题走 haiku(省钱),planning 走 opus(质量)。
|
|
198
|
+
// enabled=false 时永远返回当前模型。rules 按顺序匹配,首个命中的规则生效。
|
|
199
|
+
// 每个 rule 的 match 必须至少有一个条件(tag/contains/maxLength/minLength)。
|
|
200
|
+
// 详见 src/core/model-router.ts。
|
|
201
|
+
routing: z.object({
|
|
202
|
+
enabled: z.boolean().default(false),
|
|
203
|
+
rules: z.array(z.object({
|
|
204
|
+
match: z.object({
|
|
205
|
+
contains: z.array(z.string()).optional(),
|
|
206
|
+
maxLength: z.number().int().positive().optional(),
|
|
207
|
+
minLength: z.number().int().positive().optional(),
|
|
208
|
+
tag: z.string().optional()
|
|
209
|
+
}),
|
|
210
|
+
model: z.string(),
|
|
211
|
+
name: z.string().optional()
|
|
212
|
+
})).default([]),
|
|
213
|
+
fallback: z.string().optional()
|
|
214
|
+
}).default({ enabled: false, rules: [] })
|
|
195
215
|
});
|
|
196
216
|
|
|
197
217
|
// src/config/config-manager.ts
|
|
@@ -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-3LCVJ4AF.js";
|
|
14
14
|
|
|
15
15
|
// src/tools/builtin/bash.ts
|
|
16
16
|
import { execSync } from "child_process";
|
|
@@ -793,6 +793,11 @@ import { dirname as dirname2 } from "path";
|
|
|
793
793
|
import chalk3 from "chalk";
|
|
794
794
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
795
795
|
|
|
796
|
+
// src/core/readline-internal.ts
|
|
797
|
+
function rlInternal(rl) {
|
|
798
|
+
return rl;
|
|
799
|
+
}
|
|
800
|
+
|
|
796
801
|
// src/tools/types.ts
|
|
797
802
|
function isFileWriteTool(name) {
|
|
798
803
|
return name === "write_file" || name === "edit_file" || name === "notebook_edit";
|
|
@@ -842,6 +847,51 @@ function schemaToJsonSchema(schema) {
|
|
|
842
847
|
return result;
|
|
843
848
|
}
|
|
844
849
|
|
|
850
|
+
// src/tools/executor-phases.ts
|
|
851
|
+
function groupCallsByPhase(calls) {
|
|
852
|
+
const safeParallel = [];
|
|
853
|
+
const safeBash = [];
|
|
854
|
+
const fileWriteCalls = [];
|
|
855
|
+
const otherCalls = [];
|
|
856
|
+
for (let i = 0; i < calls.length; i++) {
|
|
857
|
+
const call = calls[i];
|
|
858
|
+
const level = getDangerLevel(call.name, call.arguments);
|
|
859
|
+
if (level === "safe") {
|
|
860
|
+
if (call.name === "bash") {
|
|
861
|
+
safeBash.push({ idx: i, call });
|
|
862
|
+
} else {
|
|
863
|
+
safeParallel.push({ idx: i, call });
|
|
864
|
+
}
|
|
865
|
+
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
866
|
+
fileWriteCalls.push({ idx: i, call });
|
|
867
|
+
} else {
|
|
868
|
+
otherCalls.push({ idx: i, call });
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return { safeParallel, safeBash, fileWriteCalls, otherCalls };
|
|
872
|
+
}
|
|
873
|
+
async function runSafePhases(phase, execute, results, logTag = "executor") {
|
|
874
|
+
const t0 = Date.now();
|
|
875
|
+
const { safeParallel, safeBash } = phase;
|
|
876
|
+
const wrapExec = async (idx, call) => {
|
|
877
|
+
try {
|
|
878
|
+
results[idx] = await execute(call);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
881
|
+
console.error(`[${logTag}] Unexpected error in tool "${call.name}":`, err);
|
|
882
|
+
results[idx] = { callId: call.id, content: `Tool execution failed: ${msg}`, isError: true };
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
const parallelPhase = safeParallel.length > 0 ? Promise.all(safeParallel.map(({ idx, call }) => wrapExec(idx, call))) : Promise.resolve();
|
|
886
|
+
const bashPhase = (async () => {
|
|
887
|
+
for (const { idx, call } of safeBash) {
|
|
888
|
+
await wrapExec(idx, call);
|
|
889
|
+
}
|
|
890
|
+
})();
|
|
891
|
+
await Promise.all([parallelPhase, bashPhase]);
|
|
892
|
+
return Date.now() - t0;
|
|
893
|
+
}
|
|
894
|
+
|
|
845
895
|
// src/tools/diff-utils.ts
|
|
846
896
|
import chalk from "chalk";
|
|
847
897
|
function renderDiff(oldText, newText, opts = {}) {
|
|
@@ -1365,57 +1415,22 @@ var ToolExecutor = class {
|
|
|
1365
1415
|
}
|
|
1366
1416
|
}
|
|
1367
1417
|
async executeAll(calls) {
|
|
1368
|
-
const
|
|
1369
|
-
const safeBash = [];
|
|
1370
|
-
const fileWriteCalls = [];
|
|
1371
|
-
const otherCalls = [];
|
|
1372
|
-
for (let i = 0; i < calls.length; i++) {
|
|
1373
|
-
const call = calls[i];
|
|
1374
|
-
const level = getDangerLevel(call.name, call.arguments);
|
|
1375
|
-
if (level === "safe") {
|
|
1376
|
-
if (call.name === "bash") {
|
|
1377
|
-
safeBash.push({ idx: i, call });
|
|
1378
|
-
} else {
|
|
1379
|
-
safeParallel.push({ idx: i, call });
|
|
1380
|
-
}
|
|
1381
|
-
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
1382
|
-
fileWriteCalls.push({ idx: i, call });
|
|
1383
|
-
} else {
|
|
1384
|
-
otherCalls.push({ idx: i, call });
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1418
|
+
const phase = groupCallsByPhase(calls);
|
|
1387
1419
|
const results = new Array(calls.length);
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
console.error(`[executor] Unexpected error in tool "${call.name}":`, err);
|
|
1395
|
-
results[idx] = { callId: call.id, content: `Tool execution failed: ${msg}`, isError: true };
|
|
1396
|
-
}
|
|
1397
|
-
};
|
|
1398
|
-
const parallelPhase = safeParallel.length > 0 ? Promise.all(safeParallel.map(({ idx, call }) => wrapExec(idx, call))) : Promise.resolve();
|
|
1399
|
-
const bashPhase = (async () => {
|
|
1400
|
-
for (const { idx, call } of safeBash) {
|
|
1401
|
-
await wrapExec(idx, call);
|
|
1402
|
-
}
|
|
1403
|
-
})();
|
|
1404
|
-
await Promise.all([parallelPhase, bashPhase]);
|
|
1405
|
-
if (safeParallel.length >= 2) {
|
|
1406
|
-
const elapsed = Date.now() - t0;
|
|
1407
|
-
console.log(theme.dim(` \u26A1 ${safeParallel.length} tools executed in parallel (${elapsed}ms)`));
|
|
1408
|
-
}
|
|
1409
|
-
if (fileWriteCalls.length === 1) {
|
|
1410
|
-
const { idx, call } = fileWriteCalls[0];
|
|
1420
|
+
const elapsed = await runSafePhases(phase, (c) => this.execute(c), results, "executor");
|
|
1421
|
+
if (phase.safeParallel.length >= 2) {
|
|
1422
|
+
console.log(theme.dim(` \u26A1 ${phase.safeParallel.length} tools executed in parallel (${elapsed}ms)`));
|
|
1423
|
+
}
|
|
1424
|
+
if (phase.fileWriteCalls.length === 1) {
|
|
1425
|
+
const { idx, call } = phase.fileWriteCalls[0];
|
|
1411
1426
|
results[idx] = await this.execute(call);
|
|
1412
|
-
} else if (fileWriteCalls.length >= 2) {
|
|
1413
|
-
const batchResults = await this.executeBatchFileWrites(fileWriteCalls.map((f) => f.call));
|
|
1414
|
-
for (let i = 0; i < fileWriteCalls.length; i++) {
|
|
1415
|
-
results[fileWriteCalls[i].idx] = batchResults[i];
|
|
1427
|
+
} else if (phase.fileWriteCalls.length >= 2) {
|
|
1428
|
+
const batchResults = await this.executeBatchFileWrites(phase.fileWriteCalls.map((f) => f.call));
|
|
1429
|
+
for (let i = 0; i < phase.fileWriteCalls.length; i++) {
|
|
1430
|
+
results[phase.fileWriteCalls[i].idx] = batchResults[i];
|
|
1416
1431
|
}
|
|
1417
1432
|
}
|
|
1418
|
-
for (const { idx, call } of otherCalls) {
|
|
1433
|
+
for (const { idx, call } of phase.otherCalls) {
|
|
1419
1434
|
results[idx] = await this.execute(call);
|
|
1420
1435
|
}
|
|
1421
1436
|
return results;
|
|
@@ -1450,6 +1465,10 @@ var ToolExecutor = class {
|
|
|
1450
1465
|
results[i] = { callId: calls[i].id, content: `[User rejected] The user rejected this ${calls[i].name} operation. Do not retry without asking.`, isError: true };
|
|
1451
1466
|
}
|
|
1452
1467
|
}
|
|
1468
|
+
if (approvedIndices.length >= 2) {
|
|
1469
|
+
const labels = approvedIndices.map((i) => `[${i + 1}]`).join(" ");
|
|
1470
|
+
console.log(theme.dim(` \u26A1 Writing ${labels} in parallel (results may interleave)`));
|
|
1471
|
+
}
|
|
1453
1472
|
const t0 = Date.now();
|
|
1454
1473
|
await Promise.all(
|
|
1455
1474
|
approvedIndices.map(async (i) => {
|
|
@@ -1488,7 +1507,7 @@ var ToolExecutor = class {
|
|
|
1488
1507
|
return Promise.resolve("none");
|
|
1489
1508
|
}
|
|
1490
1509
|
const rl = this.rl;
|
|
1491
|
-
const rlAny = rl;
|
|
1510
|
+
const rlAny = rlInternal(rl);
|
|
1492
1511
|
const savedOutput = rlAny.output;
|
|
1493
1512
|
rlAny.output = process.stdout;
|
|
1494
1513
|
rl.resume();
|
|
@@ -1668,7 +1687,7 @@ var ToolExecutor = class {
|
|
|
1668
1687
|
return Promise.resolve(false);
|
|
1669
1688
|
}
|
|
1670
1689
|
const rl = this.rl;
|
|
1671
|
-
const rlAny = rl;
|
|
1690
|
+
const rlAny = rlInternal(rl);
|
|
1672
1691
|
const savedOutput = rlAny.output;
|
|
1673
1692
|
rlAny.output = process.stdout;
|
|
1674
1693
|
rl.resume();
|
|
@@ -2930,7 +2949,7 @@ var askUserTool = {
|
|
|
2930
2949
|
}
|
|
2931
2950
|
};
|
|
2932
2951
|
function promptUser(rl, question) {
|
|
2933
|
-
const rlAny = rl;
|
|
2952
|
+
const rlAny = rlInternal(rl);
|
|
2934
2953
|
const savedOutput = rlAny.output;
|
|
2935
2954
|
rlAny.output = process.stdout;
|
|
2936
2955
|
rl.resume();
|
|
@@ -4332,13 +4351,15 @@ export {
|
|
|
4332
4351
|
RateLimitError,
|
|
4333
4352
|
ConfigError,
|
|
4334
4353
|
ProviderNotFoundError,
|
|
4335
|
-
isFileWriteTool,
|
|
4336
4354
|
getDangerLevel,
|
|
4337
4355
|
schemaToJsonSchema,
|
|
4338
4356
|
initTheme,
|
|
4339
4357
|
theme,
|
|
4340
4358
|
undoStack,
|
|
4341
4359
|
renderDiff,
|
|
4360
|
+
rlInternal,
|
|
4361
|
+
groupCallsByPhase,
|
|
4362
|
+
runSafePhases,
|
|
4342
4363
|
runHook,
|
|
4343
4364
|
checkPermission,
|
|
4344
4365
|
setMaxOutputCap,
|
|
@@ -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-WDRXASIC.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
saveDevState,
|
|
32
32
|
sessionHasMeaningfulContent,
|
|
33
33
|
setupProxy
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-G5AISHJE.js";
|
|
35
35
|
import {
|
|
36
36
|
ToolExecutor,
|
|
37
37
|
ToolRegistry,
|
|
@@ -41,12 +41,13 @@ import {
|
|
|
41
41
|
initTheme,
|
|
42
42
|
lastResponseStore,
|
|
43
43
|
renderDiff,
|
|
44
|
+
rlInternal,
|
|
44
45
|
setContextWindow,
|
|
45
46
|
setMaxOutputCap,
|
|
46
47
|
spawnAgentContext,
|
|
47
48
|
theme,
|
|
48
49
|
undoStack
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-Q5QSCO5D.js";
|
|
50
51
|
import {
|
|
51
52
|
fileCheckpoints
|
|
52
53
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -71,7 +72,7 @@ import {
|
|
|
71
72
|
SKILLS_DIR_NAME,
|
|
72
73
|
VERSION,
|
|
73
74
|
buildUserIdentityPrompt
|
|
74
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-3LCVJ4AF.js";
|
|
75
76
|
|
|
76
77
|
// src/index.ts
|
|
77
78
|
import { program } from "commander";
|
|
@@ -1074,6 +1075,79 @@ function createDefaultCommands() {
|
|
|
1074
1075
|
);
|
|
1075
1076
|
}
|
|
1076
1077
|
},
|
|
1078
|
+
{
|
|
1079
|
+
name: "route",
|
|
1080
|
+
description: "Smart model routing \u2014 enable/disable or inspect routing rules",
|
|
1081
|
+
usage: "/route [on|off|show|test <message>]",
|
|
1082
|
+
async execute(args, ctx) {
|
|
1083
|
+
const sub = (args[0] ?? "show").toLowerCase();
|
|
1084
|
+
const routing = ctx.config.get("routing");
|
|
1085
|
+
if (sub === "on" || sub === "enable") {
|
|
1086
|
+
ctx.config.setByPath("routing.enabled", "true");
|
|
1087
|
+
ctx.renderer.printSuccess("Smart model routing enabled.");
|
|
1088
|
+
if (!routing || routing.rules.length === 0) {
|
|
1089
|
+
ctx.renderer.printInfo(
|
|
1090
|
+
'No rules configured yet. Add rules under `routing.rules` in ~/.aicli/config.json.\nExample: { match: { tag: "fast" }, model: "claude-haiku-4-5" }'
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (sub === "off" || sub === "disable") {
|
|
1096
|
+
ctx.config.setByPath("routing.enabled", "false");
|
|
1097
|
+
ctx.renderer.printSuccess("Smart model routing disabled.");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (sub === "test") {
|
|
1101
|
+
const msg = args.slice(1).join(" ").trim();
|
|
1102
|
+
if (!msg) {
|
|
1103
|
+
console.log(theme.warning(" Usage: /route test <message>"));
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const decision = ctx.computeRoutingDecision(msg);
|
|
1107
|
+
const marker = decision.overridden ? theme.accent("\u2192 ROUTED") : theme.dim("(unchanged)");
|
|
1108
|
+
console.log();
|
|
1109
|
+
console.log(` Input: ${theme.dim(msg)}`);
|
|
1110
|
+
console.log(` Current: ${theme.info(ctx.getCurrentModel())}`);
|
|
1111
|
+
console.log(` Decision: ${theme.info(decision.model)} ${marker}`);
|
|
1112
|
+
console.log(` Reason: ${theme.dim(decision.reason)}`);
|
|
1113
|
+
if (typeof decision.ruleIdx === "number") {
|
|
1114
|
+
console.log(` Rule: #${decision.ruleIdx}`);
|
|
1115
|
+
}
|
|
1116
|
+
console.log();
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (sub === "show" || sub === "status") {
|
|
1120
|
+
console.log();
|
|
1121
|
+
console.log(` ${theme.heading("Smart Model Routing")}`);
|
|
1122
|
+
console.log(` Status: ${routing?.enabled ? theme.success("enabled") : theme.dim("disabled")}`);
|
|
1123
|
+
console.log(` Provider: ${theme.info(ctx.getCurrentProvider())}`);
|
|
1124
|
+
console.log(` Current: ${theme.info(ctx.getCurrentModel())}`);
|
|
1125
|
+
if (routing?.fallback) {
|
|
1126
|
+
console.log(` Fallback: ${theme.info(routing.fallback)}`);
|
|
1127
|
+
}
|
|
1128
|
+
console.log();
|
|
1129
|
+
if (!routing || routing.rules.length === 0) {
|
|
1130
|
+
console.log(` ${theme.dim("(no rules configured \u2014 edit ~/.aicli/config.json `routing.rules`)")}`);
|
|
1131
|
+
} else {
|
|
1132
|
+
console.log(` ${theme.heading("Rules")} ${theme.dim(`(evaluated top-to-bottom)`)}:`);
|
|
1133
|
+
routing.rules.forEach((r, i) => {
|
|
1134
|
+
const parts = [];
|
|
1135
|
+
if (r.match.tag) parts.push(`tag=#${r.match.tag}`);
|
|
1136
|
+
if (r.match.contains && r.match.contains.length > 0) parts.push(`contains=[${r.match.contains.slice(0, 3).join(", ")}${r.match.contains.length > 3 ? ", \u2026" : ""}]`);
|
|
1137
|
+
if (typeof r.match.maxLength === "number") parts.push(`maxLen=${r.match.maxLength}`);
|
|
1138
|
+
if (typeof r.match.minLength === "number") parts.push(`minLen=${r.match.minLength}`);
|
|
1139
|
+
const cond = parts.length > 0 ? parts.join(" & ") : theme.warning("(empty \u2014 never matches)");
|
|
1140
|
+
console.log(` ${theme.dim(`#${i}`)} ${r.name ? theme.accent(r.name) + " " : ""}${cond} ${theme.dim("\u2192")} ${theme.info(r.model)}`);
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
console.log();
|
|
1144
|
+
console.log(` ${theme.dim("Commands: /route on | off | test <msg> | show")}`);
|
|
1145
|
+
console.log();
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
console.log(theme.warning(` Unknown subcommand: ${sub}. Usage: /route [on|off|show|test <message>]`));
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1077
1151
|
{
|
|
1078
1152
|
name: "clear",
|
|
1079
1153
|
description: "Clear conversation history",
|
|
@@ -1123,18 +1197,18 @@ function createDefaultCommands() {
|
|
|
1123
1197
|
return;
|
|
1124
1198
|
}
|
|
1125
1199
|
const sessions = ctx.sessions.listSessions();
|
|
1126
|
-
const
|
|
1127
|
-
if (
|
|
1200
|
+
const matches2 = sessions.filter((s) => s.id.startsWith(id));
|
|
1201
|
+
if (matches2.length === 0) {
|
|
1128
1202
|
ctx.renderer.renderError(`Session '${id}' not found.`);
|
|
1129
1203
|
return;
|
|
1130
1204
|
}
|
|
1131
|
-
if (
|
|
1132
|
-
console.log(theme.warning(` \u26A0 Ambiguous prefix '${id}' matches ${
|
|
1133
|
-
for (const m of
|
|
1205
|
+
if (matches2.length > 1) {
|
|
1206
|
+
console.log(theme.warning(` \u26A0 Ambiguous prefix '${id}' matches ${matches2.length} sessions \u2014 loading most recent:`));
|
|
1207
|
+
for (const m of matches2.slice(0, 5)) {
|
|
1134
1208
|
console.log(theme.dim(` ${m.id.slice(0, 12)} ${m.title ?? "(untitled)"}`));
|
|
1135
1209
|
}
|
|
1136
1210
|
}
|
|
1137
|
-
const match =
|
|
1211
|
+
const match = matches2[0];
|
|
1138
1212
|
ctx.sessions.loadSession(match.id);
|
|
1139
1213
|
ctx.setProvider(match.provider, match.model);
|
|
1140
1214
|
ctx.resetSessionTokenUsage();
|
|
@@ -1372,13 +1446,13 @@ ${text}
|
|
|
1372
1446
|
${theme.heading(`Found ${results.length} session(s) containing "${query}"`)}
|
|
1373
1447
|
`);
|
|
1374
1448
|
for (const r of results) {
|
|
1375
|
-
const { sessionMeta, matches } = r;
|
|
1449
|
+
const { sessionMeta, matches: matches2 } = r;
|
|
1376
1450
|
const dateStr = sessionMeta.updated.toLocaleDateString();
|
|
1377
1451
|
console.log(
|
|
1378
1452
|
` ${theme.accent(sessionMeta.id.slice(0, 8))}` + theme.dim(` [${dateStr}] ${sessionMeta.provider} / ${sessionMeta.model}`) + (sessionMeta.title ? `
|
|
1379
1453
|
${theme.dim(" " + sessionMeta.title)}` : "")
|
|
1380
1454
|
);
|
|
1381
|
-
for (const m of
|
|
1455
|
+
for (const m of matches2) {
|
|
1382
1456
|
const icon = m.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
1383
1457
|
console.log(` ${icon} ${theme.warning(m.snippet)}`);
|
|
1384
1458
|
}
|
|
@@ -2193,7 +2267,7 @@ ${hint}` : "")
|
|
|
2193
2267
|
usage: "/test [command|filter]",
|
|
2194
2268
|
async execute(args, ctx) {
|
|
2195
2269
|
try {
|
|
2196
|
-
const { executeTests } = await import("./run-tests-
|
|
2270
|
+
const { executeTests } = await import("./run-tests-WD53PYVA.js");
|
|
2197
2271
|
const argStr = args.join(" ").trim();
|
|
2198
2272
|
let testArgs = {};
|
|
2199
2273
|
if (argStr) {
|
|
@@ -3273,7 +3347,7 @@ var CostTracker = class {
|
|
|
3273
3347
|
if (existsSync4(this.filePath)) {
|
|
3274
3348
|
const data = JSON.parse(readFileSync3(this.filePath, "utf-8"));
|
|
3275
3349
|
if (data.version === 1 && Array.isArray(data.records)) {
|
|
3276
|
-
this.records = data.records;
|
|
3350
|
+
this.records = [...data.records].sort((a, b) => a.date.localeCompare(b.date));
|
|
3277
3351
|
}
|
|
3278
3352
|
}
|
|
3279
3353
|
} catch {
|
|
@@ -3393,6 +3467,74 @@ var CostTracker = class {
|
|
|
3393
3467
|
}
|
|
3394
3468
|
};
|
|
3395
3469
|
|
|
3470
|
+
// src/core/model-router.ts
|
|
3471
|
+
var TAG_REGEX = /(?:^|\s)#([a-zA-Z][\w-]{0,31})\b/g;
|
|
3472
|
+
function extractTags(message) {
|
|
3473
|
+
const tags = /* @__PURE__ */ new Set();
|
|
3474
|
+
let m;
|
|
3475
|
+
TAG_REGEX.lastIndex = 0;
|
|
3476
|
+
while ((m = TAG_REGEX.exec(message)) !== null) {
|
|
3477
|
+
tags.add(m[1].toLowerCase());
|
|
3478
|
+
}
|
|
3479
|
+
return tags;
|
|
3480
|
+
}
|
|
3481
|
+
function matches(message, matcher) {
|
|
3482
|
+
const trimmed = message.trim();
|
|
3483
|
+
const lower = trimmed.toLowerCase();
|
|
3484
|
+
if (matcher.tag) {
|
|
3485
|
+
const tags = extractTags(trimmed);
|
|
3486
|
+
if (!tags.has(matcher.tag.toLowerCase())) return false;
|
|
3487
|
+
}
|
|
3488
|
+
if (matcher.contains && matcher.contains.length > 0) {
|
|
3489
|
+
const hit = matcher.contains.some((kw) => lower.includes(kw.toLowerCase()));
|
|
3490
|
+
if (!hit) return false;
|
|
3491
|
+
}
|
|
3492
|
+
if (typeof matcher.maxLength === "number") {
|
|
3493
|
+
if (trimmed.length > matcher.maxLength) return false;
|
|
3494
|
+
}
|
|
3495
|
+
if (typeof matcher.minLength === "number") {
|
|
3496
|
+
if (trimmed.length < matcher.minLength) return false;
|
|
3497
|
+
}
|
|
3498
|
+
const hasAnyCondition = !!matcher.tag || matcher.contains && matcher.contains.length > 0 || typeof matcher.maxLength === "number" || typeof matcher.minLength === "number";
|
|
3499
|
+
return !!hasAnyCondition;
|
|
3500
|
+
}
|
|
3501
|
+
function pickModel(message, currentModel, config, availableModels = []) {
|
|
3502
|
+
if (!config.enabled || config.rules.length === 0) {
|
|
3503
|
+
return { model: currentModel, reason: "routing disabled", overridden: false };
|
|
3504
|
+
}
|
|
3505
|
+
const isAvailable = (m) => availableModels.length === 0 || availableModels.includes(m);
|
|
3506
|
+
for (let i = 0; i < config.rules.length; i++) {
|
|
3507
|
+
const rule = config.rules[i];
|
|
3508
|
+
if (!matches(message, rule.match)) continue;
|
|
3509
|
+
if (!isAvailable(rule.model)) continue;
|
|
3510
|
+
if (rule.model === currentModel) {
|
|
3511
|
+
return {
|
|
3512
|
+
model: currentModel,
|
|
3513
|
+
reason: `rule "${rule.name ?? `#${i}`}" matched (same as current)`,
|
|
3514
|
+
overridden: false,
|
|
3515
|
+
ruleIdx: i
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
return {
|
|
3519
|
+
model: rule.model,
|
|
3520
|
+
reason: `rule "${rule.name ?? `#${i}`}" matched`,
|
|
3521
|
+
overridden: true,
|
|
3522
|
+
ruleIdx: i
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
if (config.fallback && config.fallback !== currentModel && isAvailable(config.fallback)) {
|
|
3526
|
+
return {
|
|
3527
|
+
model: config.fallback,
|
|
3528
|
+
reason: "fallback",
|
|
3529
|
+
overridden: true
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
return { model: currentModel, reason: "no rule matched", overridden: false };
|
|
3533
|
+
}
|
|
3534
|
+
function stripRoutingTags(message) {
|
|
3535
|
+
return message.replace(/(?:^|\s)#(fast|deep|default)\b/gi, " ").replace(/\s{2,}/g, " ").trim();
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3396
3538
|
// src/repl/notify.ts
|
|
3397
3539
|
import { spawn } from "child_process";
|
|
3398
3540
|
import { platform as platform2 } from "os";
|
|
@@ -3569,13 +3711,15 @@ var Repl = class {
|
|
|
3569
3711
|
contextLayers = [];
|
|
3570
3712
|
/** 本次会话累计 token 用量 */
|
|
3571
3713
|
sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
3572
|
-
/** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage + cost tracker.
|
|
3573
|
-
|
|
3714
|
+
/** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage + cost tracker.
|
|
3715
|
+
* modelOverride lets the smart router attribute cost to the actually-used model
|
|
3716
|
+
* when it differs from the UI-selected currentModel. */
|
|
3717
|
+
addSessionUsage(u, modelOverride) {
|
|
3574
3718
|
this.sessionTokenUsage.inputTokens += u.inputTokens;
|
|
3575
3719
|
this.sessionTokenUsage.outputTokens += u.outputTokens;
|
|
3576
3720
|
this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
3577
3721
|
this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
3578
|
-
this.costTracker.addCost(this.currentProvider, this.currentModel, u);
|
|
3722
|
+
this.costTracker.addCost(this.currentProvider, modelOverride ?? this.currentModel, u);
|
|
3579
3723
|
}
|
|
3580
3724
|
/** 启动时检测到的 Git 分支(无 git 仓库时为 null) */
|
|
3581
3725
|
gitBranch = null;
|
|
@@ -4388,7 +4532,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4388
4532
|
}
|
|
4389
4533
|
processing = true;
|
|
4390
4534
|
this.rl.pause();
|
|
4391
|
-
const rlAny = this.rl;
|
|
4535
|
+
const rlAny = rlInternal(this.rl);
|
|
4392
4536
|
const savedOutput = rlAny.output;
|
|
4393
4537
|
rlAny.output = null;
|
|
4394
4538
|
try {
|
|
@@ -4403,10 +4547,10 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4403
4547
|
processing = false;
|
|
4404
4548
|
if (this.running) {
|
|
4405
4549
|
rlAny.output = savedOutput;
|
|
4406
|
-
const
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4550
|
+
const rlInternal2 = this.rl;
|
|
4551
|
+
rlInternal2.line = "";
|
|
4552
|
+
rlInternal2.cursor = 0;
|
|
4553
|
+
rlInternal2.paused = false;
|
|
4410
4554
|
process.stdin.resume();
|
|
4411
4555
|
this.showPrompt();
|
|
4412
4556
|
} else {
|
|
@@ -4467,7 +4611,16 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4467
4611
|
`));
|
|
4468
4612
|
}
|
|
4469
4613
|
}
|
|
4470
|
-
const
|
|
4614
|
+
const routingDecision = this.computeRoutingDecision(userInput);
|
|
4615
|
+
const cleanInput = stripRoutingTags(userInput);
|
|
4616
|
+
let effectiveParts = parts;
|
|
4617
|
+
if (cleanInput !== userInput && parts.length > 0 && parts[0].type === "text") {
|
|
4618
|
+
effectiveParts = [
|
|
4619
|
+
{ type: "text", text: stripRoutingTags(parts[0].text ?? "") },
|
|
4620
|
+
...parts.slice(1)
|
|
4621
|
+
];
|
|
4622
|
+
}
|
|
4623
|
+
const messageContent = effectiveParts.length > 0 ? effectiveParts.length === 1 && effectiveParts[0].type === "text" ? effectiveParts[0].text : effectiveParts : cleanInput;
|
|
4471
4624
|
if (hasImage) {
|
|
4472
4625
|
const visionHint = this.getVisionModelHint();
|
|
4473
4626
|
if (visionHint) {
|
|
@@ -4488,6 +4641,12 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4488
4641
|
timestamp: /* @__PURE__ */ new Date()
|
|
4489
4642
|
});
|
|
4490
4643
|
this.events.emit("message.before", { input: userInput });
|
|
4644
|
+
if (routingDecision.overridden) {
|
|
4645
|
+
process.stdout.write(
|
|
4646
|
+
theme.dim(` \u2192 Routed to ${routingDecision.model} (${routingDecision.reason})
|
|
4647
|
+
`)
|
|
4648
|
+
);
|
|
4649
|
+
}
|
|
4491
4650
|
const t0 = Date.now();
|
|
4492
4651
|
try {
|
|
4493
4652
|
const provider = this.providers.get(this.currentProvider);
|
|
@@ -4495,10 +4654,11 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4495
4654
|
if (supportsTools) {
|
|
4496
4655
|
await this.handleChatWithTools(
|
|
4497
4656
|
provider,
|
|
4498
|
-
session.messages
|
|
4657
|
+
session.messages,
|
|
4658
|
+
routingDecision.model
|
|
4499
4659
|
);
|
|
4500
4660
|
} else {
|
|
4501
|
-
await this.handleChatSimple(provider, session.messages);
|
|
4661
|
+
await this.handleChatSimple(provider, session.messages, routingDecision.model);
|
|
4502
4662
|
}
|
|
4503
4663
|
if (this.config.get("session").autoSave) {
|
|
4504
4664
|
if (autoTrimSessionIfNeeded(session)) {
|
|
@@ -4576,9 +4736,10 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4576
4736
|
*/
|
|
4577
4737
|
/** 运行时 thinking 模式覆盖:null=使用配置值,true/false=运行时覆盖 */
|
|
4578
4738
|
runtimeThinking = null;
|
|
4579
|
-
getModelParams() {
|
|
4739
|
+
getModelParams(modelOverride) {
|
|
4580
4740
|
const allParams = this.config.get("modelParams");
|
|
4581
|
-
const
|
|
4741
|
+
const modelId = modelOverride ?? this.currentModel;
|
|
4742
|
+
const params = allParams[modelId] ?? {};
|
|
4582
4743
|
return {
|
|
4583
4744
|
...params,
|
|
4584
4745
|
maxTokens: params.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
@@ -4586,6 +4747,25 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4586
4747
|
thinkingBudget: params.thinkingBudget
|
|
4587
4748
|
};
|
|
4588
4749
|
}
|
|
4750
|
+
/**
|
|
4751
|
+
* Compute smart-routing decision for this user turn.
|
|
4752
|
+
* Only considers models available for the current provider (rule skipped otherwise).
|
|
4753
|
+
* When routing is disabled or no rule matches, returns the current model unchanged.
|
|
4754
|
+
*/
|
|
4755
|
+
computeRoutingDecision(userInput) {
|
|
4756
|
+
const routingConfig = this.config.get("routing");
|
|
4757
|
+
if (!routingConfig || !routingConfig.enabled) {
|
|
4758
|
+
return { model: this.currentModel, reason: "routing disabled", overridden: false };
|
|
4759
|
+
}
|
|
4760
|
+
let availableModels = [];
|
|
4761
|
+
try {
|
|
4762
|
+
const provider = this.providers.get(this.currentProvider);
|
|
4763
|
+
availableModels = provider.info.models.map((m) => m.id);
|
|
4764
|
+
} catch {
|
|
4765
|
+
availableModels = [];
|
|
4766
|
+
}
|
|
4767
|
+
return pickModel(userInput, this.currentModel, routingConfig, availableModels);
|
|
4768
|
+
}
|
|
4589
4769
|
// ─── Context 自动管理 ───────────────────────────────────────────────────
|
|
4590
4770
|
/**
|
|
4591
4771
|
* 估算文本的 token 数。
|
|
@@ -4638,12 +4818,15 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4638
4818
|
return total;
|
|
4639
4819
|
}
|
|
4640
4820
|
/**
|
|
4641
|
-
*
|
|
4821
|
+
* 获取指定模型的 context window 大小(默认当前模型)。
|
|
4822
|
+
* 智能路由可能在 handleChatWithTools 内把 effectiveModel 暂时切到别的模型,
|
|
4823
|
+
* 故此处接受可选的 modelOverride 以保持计算一致性。
|
|
4642
4824
|
*/
|
|
4643
|
-
getContextWindowSize() {
|
|
4825
|
+
getContextWindowSize(modelOverride) {
|
|
4644
4826
|
try {
|
|
4645
4827
|
const provider = this.providers.get(this.currentProvider);
|
|
4646
|
-
const
|
|
4828
|
+
const modelId = modelOverride ?? this.currentModel;
|
|
4829
|
+
const modelInfo = provider.info.models.find((m) => m.id === modelId);
|
|
4647
4830
|
return modelInfo?.contextWindow ?? 0;
|
|
4648
4831
|
} catch {
|
|
4649
4832
|
return 0;
|
|
@@ -4880,16 +5063,17 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4880
5063
|
}
|
|
4881
5064
|
});
|
|
4882
5065
|
}
|
|
4883
|
-
async handleChatSimple(provider, messages) {
|
|
5066
|
+
async handleChatSimple(provider, messages, modelOverride) {
|
|
4884
5067
|
const session = this.sessions.current;
|
|
4885
5068
|
const useStreaming = this.config.get("ui").streaming;
|
|
4886
|
-
const
|
|
5069
|
+
const effectiveModel = modelOverride ?? this.currentModel;
|
|
5070
|
+
const modelParams = this.getModelParams(effectiveModel);
|
|
4887
5071
|
if (useStreaming) {
|
|
4888
5072
|
const ac = this.setupStreamInterrupt();
|
|
4889
5073
|
try {
|
|
4890
5074
|
const stream = provider.chatStream({
|
|
4891
5075
|
messages,
|
|
4892
|
-
model:
|
|
5076
|
+
model: effectiveModel,
|
|
4893
5077
|
systemPrompt: this.buildCurrentSystemPrompt(),
|
|
4894
5078
|
stream: true,
|
|
4895
5079
|
temperature: modelParams.temperature,
|
|
@@ -4909,7 +5093,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4909
5093
|
session.addMessage({ role: "assistant", content, timestamp: /* @__PURE__ */ new Date() });
|
|
4910
5094
|
this.events.emit("message.after", { content });
|
|
4911
5095
|
if (usage) {
|
|
4912
|
-
this.addSessionUsage(usage);
|
|
5096
|
+
this.addSessionUsage(usage, effectiveModel);
|
|
4913
5097
|
session.addTokenUsage(usage);
|
|
4914
5098
|
if (showTokens && !tokensShown) {
|
|
4915
5099
|
this.renderer.renderUsage(usage, this.sessionTokenUsage);
|
|
@@ -4923,7 +5107,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4923
5107
|
try {
|
|
4924
5108
|
const response = await provider.chat({
|
|
4925
5109
|
messages,
|
|
4926
|
-
model:
|
|
5110
|
+
model: effectiveModel,
|
|
4927
5111
|
systemPrompt: this.buildCurrentSystemPrompt(),
|
|
4928
5112
|
stream: false,
|
|
4929
5113
|
temperature: modelParams.temperature,
|
|
@@ -4938,7 +5122,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4938
5122
|
session.addMessage({ role: "assistant", content: response.content, timestamp: /* @__PURE__ */ new Date() });
|
|
4939
5123
|
this.events.emit("message.after", { content: response.content });
|
|
4940
5124
|
if (response.usage) {
|
|
4941
|
-
this.addSessionUsage(response.usage);
|
|
5125
|
+
this.addSessionUsage(response.usage, effectiveModel);
|
|
4942
5126
|
session.addTokenUsage(response.usage);
|
|
4943
5127
|
if (this.shouldShowTokens()) {
|
|
4944
5128
|
this.renderer.renderUsage(response.usage, this.sessionTokenUsage);
|
|
@@ -5051,8 +5235,9 @@ Session '${this.resumeSessionId}' not found.
|
|
|
5051
5235
|
rawContent
|
|
5052
5236
|
};
|
|
5053
5237
|
}
|
|
5054
|
-
async handleChatWithTools(provider, messages) {
|
|
5238
|
+
async handleChatWithTools(provider, messages, modelOverride) {
|
|
5055
5239
|
const session = this.sessions.current;
|
|
5240
|
+
const effectiveModel = modelOverride ?? this.currentModel;
|
|
5056
5241
|
let toolDefs;
|
|
5057
5242
|
let mcpBudgetNote = null;
|
|
5058
5243
|
const usedMcpToolNames = /* @__PURE__ */ new Set();
|
|
@@ -5063,7 +5248,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
5063
5248
|
if (skillFilter) {
|
|
5064
5249
|
toolDefs = this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name));
|
|
5065
5250
|
} else {
|
|
5066
|
-
const contextWindow = this.getContextWindowSize();
|
|
5251
|
+
const contextWindow = this.getContextWindowSize(effectiveModel);
|
|
5067
5252
|
if (contextWindow > 0) {
|
|
5068
5253
|
const toolBudget = Math.floor(contextWindow * 0.2);
|
|
5069
5254
|
const { definitions, trimmedCount, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget, usedMcpToolNames);
|
|
@@ -5113,7 +5298,7 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
5113
5298
|
const systemPrompt = baseSystemPrompt + roundBudgetHint + (mcpBudgetNote ? `
|
|
5114
5299
|
|
|
5115
5300
|
${mcpBudgetNote}` : "");
|
|
5116
|
-
const modelParams = this.getModelParams();
|
|
5301
|
+
const modelParams = this.getModelParams(effectiveModel);
|
|
5117
5302
|
const useStreaming = this.config.get("ui").streaming;
|
|
5118
5303
|
const spinner = this.renderer.showSpinner("Thinking...");
|
|
5119
5304
|
const roundUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
@@ -5203,7 +5388,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5203
5388
|
)
|
|
5204
5389
|
);
|
|
5205
5390
|
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
5206
|
-
this.addSessionUsage(roundUsage);
|
|
5391
|
+
this.addSessionUsage(roundUsage, effectiveModel);
|
|
5207
5392
|
session.addTokenUsage(roundUsage);
|
|
5208
5393
|
if (this.shouldShowTokens()) {
|
|
5209
5394
|
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
@@ -5231,7 +5416,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5231
5416
|
let alreadyRendered = false;
|
|
5232
5417
|
const chatRequest = {
|
|
5233
5418
|
messages: apiMessages,
|
|
5234
|
-
model:
|
|
5419
|
+
model: effectiveModel,
|
|
5235
5420
|
systemPrompt,
|
|
5236
5421
|
stream: false,
|
|
5237
5422
|
temperature: modelParams.temperature,
|
|
@@ -5333,7 +5518,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5333
5518
|
)
|
|
5334
5519
|
);
|
|
5335
5520
|
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
5336
|
-
this.addSessionUsage(roundUsage);
|
|
5521
|
+
this.addSessionUsage(roundUsage, effectiveModel);
|
|
5337
5522
|
session.addTokenUsage(roundUsage);
|
|
5338
5523
|
if (this.shouldShowTokens()) {
|
|
5339
5524
|
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
@@ -5368,7 +5553,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5368
5553
|
});
|
|
5369
5554
|
this.events.emit("message.after", { content: finalContent });
|
|
5370
5555
|
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
5371
|
-
this.addSessionUsage(roundUsage);
|
|
5556
|
+
this.addSessionUsage(roundUsage, effectiveModel);
|
|
5372
5557
|
session.addTokenUsage(roundUsage);
|
|
5373
5558
|
if (this.shouldShowTokens()) {
|
|
5374
5559
|
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
@@ -5386,7 +5571,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5386
5571
|
try {
|
|
5387
5572
|
const genStream = provider.chatStream({
|
|
5388
5573
|
messages: apiMessages,
|
|
5389
|
-
model:
|
|
5574
|
+
model: effectiveModel,
|
|
5390
5575
|
systemPrompt,
|
|
5391
5576
|
stream: true,
|
|
5392
5577
|
temperature: modelParams.temperature,
|
|
@@ -5422,7 +5607,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5422
5607
|
const newMsgs2 = provider.buildToolResultMessages(result.toolCalls, syntheticResults, reasoningContent2);
|
|
5423
5608
|
extraMessages.push(...newMsgs2);
|
|
5424
5609
|
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
5425
|
-
this.addSessionUsage(roundUsage);
|
|
5610
|
+
this.addSessionUsage(roundUsage, effectiveModel);
|
|
5426
5611
|
session.addTokenUsage(roundUsage);
|
|
5427
5612
|
if (teeShowTokens && !teeTokShown) {
|
|
5428
5613
|
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
@@ -5437,14 +5622,14 @@ ${mcpBudgetNote}` : "");
|
|
|
5437
5622
|
askUserContext.rl = this.rl;
|
|
5438
5623
|
googleSearchContext.configManager = this.config;
|
|
5439
5624
|
streamToFileContext.provider = provider;
|
|
5440
|
-
streamToFileContext.model =
|
|
5625
|
+
streamToFileContext.model = effectiveModel;
|
|
5441
5626
|
streamToFileContext.systemPrompt = systemPrompt;
|
|
5442
5627
|
streamToFileContext.messages = apiMessages;
|
|
5443
5628
|
streamToFileContext.extraMessages = extraMessages;
|
|
5444
5629
|
streamToFileContext.temperature = modelParams.temperature;
|
|
5445
5630
|
streamToFileContext.timeout = modelParams.timeout;
|
|
5446
5631
|
spawnAgentContext.provider = provider;
|
|
5447
|
-
spawnAgentContext.model =
|
|
5632
|
+
spawnAgentContext.model = effectiveModel;
|
|
5448
5633
|
spawnAgentContext.systemPrompt = systemPrompt;
|
|
5449
5634
|
spawnAgentContext.modelParams = modelParams;
|
|
5450
5635
|
spawnAgentContext.configManager = this.config;
|
|
@@ -5559,7 +5744,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5559
5744
|
process.stdout.write(theme.dim(" (Press ") + theme.info("n") + theme.dim(" or ") + theme.info("Esc") + theme.dim(" to stop)\n"));
|
|
5560
5745
|
this.teardownInterjectionListener();
|
|
5561
5746
|
const pauseResponse = await new Promise((resolve3) => {
|
|
5562
|
-
const rlWithOutput = this.rl;
|
|
5747
|
+
const rlWithOutput = rlInternal(this.rl);
|
|
5563
5748
|
const savedOutput = rlWithOutput.output;
|
|
5564
5749
|
rlWithOutput.output = process.stdout;
|
|
5565
5750
|
this.rl.question(theme.warning(" \u25B8 "), (answer) => {
|
|
@@ -5604,7 +5789,7 @@ ${mcpBudgetNote}` : "");
|
|
|
5604
5789
|
const summaryResult = await provider.chatWithTools(
|
|
5605
5790
|
{
|
|
5606
5791
|
messages: apiMessages,
|
|
5607
|
-
model:
|
|
5792
|
+
model: effectiveModel,
|
|
5608
5793
|
systemPrompt,
|
|
5609
5794
|
stream: false,
|
|
5610
5795
|
temperature: modelParams.temperature,
|
|
@@ -5642,7 +5827,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5642
5827
|
);
|
|
5643
5828
|
}
|
|
5644
5829
|
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
5645
|
-
this.addSessionUsage(roundUsage);
|
|
5830
|
+
this.addSessionUsage(roundUsage, effectiveModel);
|
|
5646
5831
|
session.addTokenUsage(roundUsage);
|
|
5647
5832
|
if (this.shouldShowTokens()) {
|
|
5648
5833
|
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
@@ -5702,9 +5887,9 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5702
5887
|
select: (prompt, items, initialIndex) => {
|
|
5703
5888
|
this.selecting = true;
|
|
5704
5889
|
return selectFromList(prompt, items, initialIndex).finally(() => {
|
|
5705
|
-
const
|
|
5706
|
-
|
|
5707
|
-
|
|
5890
|
+
const rlInternal2 = this.rl;
|
|
5891
|
+
rlInternal2.line = "";
|
|
5892
|
+
rlInternal2.cursor = 0;
|
|
5708
5893
|
process.stdin.pause();
|
|
5709
5894
|
setImmediate(() => {
|
|
5710
5895
|
this.selecting = false;
|
|
@@ -5749,7 +5934,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5749
5934
|
getGitBranch: () => this.gitBranch,
|
|
5750
5935
|
getLastResponse: () => lastResponseStore.content,
|
|
5751
5936
|
runSetupWizard: async () => {
|
|
5752
|
-
const rlAny = this.rl;
|
|
5937
|
+
const rlAny = rlInternal(this.rl);
|
|
5753
5938
|
rlAny.output = process.stdout;
|
|
5754
5939
|
process.stdin.resume();
|
|
5755
5940
|
try {
|
|
@@ -5818,6 +6003,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5818
6003
|
forkSession: (messageCount, title) => this.sessions.forkSession(messageCount, title),
|
|
5819
6004
|
getToolExecutor: () => this.toolExecutor,
|
|
5820
6005
|
getCostTracker: () => this.costTracker,
|
|
6006
|
+
computeRoutingDecision: (userInput) => this.computeRoutingDecision(userInput),
|
|
5821
6007
|
exit: () => this.handleExit()
|
|
5822
6008
|
};
|
|
5823
6009
|
await cmd.execute(args, ctx);
|
|
@@ -5922,7 +6108,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5922
6108
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5923
6109
|
process.exit(1);
|
|
5924
6110
|
}
|
|
5925
|
-
const { startWebServer } = await import("./server-
|
|
6111
|
+
const { startWebServer } = await import("./server-MDBQX5UZ.js");
|
|
5926
6112
|
await startWebServer({ port, host: options.host });
|
|
5927
6113
|
});
|
|
5928
6114
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -6155,7 +6341,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
6155
6341
|
}),
|
|
6156
6342
|
config.get("customProviders")
|
|
6157
6343
|
);
|
|
6158
|
-
const { startHub } = await import("./hub-
|
|
6344
|
+
const { startHub } = await import("./hub-4VPTOMBP.js");
|
|
6159
6345
|
await startHub(
|
|
6160
6346
|
{
|
|
6161
6347
|
topic: topic ?? "",
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
persistToolRound,
|
|
22
22
|
rebuildExtraMessages,
|
|
23
23
|
setupProxy
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-G5AISHJE.js";
|
|
25
25
|
import {
|
|
26
26
|
AuthManager
|
|
27
27
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -33,15 +33,16 @@ import {
|
|
|
33
33
|
estimateTokens,
|
|
34
34
|
getDangerLevel,
|
|
35
35
|
googleSearchContext,
|
|
36
|
-
|
|
36
|
+
groupCallsByPhase,
|
|
37
37
|
renderDiff,
|
|
38
38
|
runHook,
|
|
39
|
+
runSafePhases,
|
|
39
40
|
setContextWindow,
|
|
40
41
|
setMaxOutputCap,
|
|
41
42
|
spawnAgentContext,
|
|
42
43
|
truncateOutput,
|
|
43
44
|
undoStack
|
|
44
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-Q5QSCO5D.js";
|
|
45
46
|
import "./chunk-4BKXL7SM.js";
|
|
46
47
|
import {
|
|
47
48
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -61,7 +62,7 @@ import {
|
|
|
61
62
|
SKILLS_DIR_NAME,
|
|
62
63
|
VERSION,
|
|
63
64
|
buildUserIdentityPrompt
|
|
64
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-3LCVJ4AF.js";
|
|
65
66
|
|
|
66
67
|
// src/web/server.ts
|
|
67
68
|
import express from "express";
|
|
@@ -325,52 +326,19 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
async executeAll(calls) {
|
|
328
|
-
const
|
|
329
|
-
const safeBash = [];
|
|
330
|
-
const fileWriteCalls = [];
|
|
331
|
-
const otherCalls = [];
|
|
332
|
-
for (let i = 0; i < calls.length; i++) {
|
|
333
|
-
const call = calls[i];
|
|
334
|
-
const level = getDangerLevel(call.name, call.arguments);
|
|
335
|
-
if (level === "safe") {
|
|
336
|
-
if (call.name === "bash") {
|
|
337
|
-
safeBash.push({ idx: i, call });
|
|
338
|
-
} else {
|
|
339
|
-
safeParallel.push({ idx: i, call });
|
|
340
|
-
}
|
|
341
|
-
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
342
|
-
fileWriteCalls.push({ idx: i, call });
|
|
343
|
-
} else {
|
|
344
|
-
otherCalls.push({ idx: i, call });
|
|
345
|
-
}
|
|
346
|
-
}
|
|
329
|
+
const phase = groupCallsByPhase(calls);
|
|
347
330
|
const results = new Array(calls.length);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
} catch (err) {
|
|
352
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
353
|
-
console.error(`[tool-executor-web] Unexpected error in tool "${call.name}":`, err);
|
|
354
|
-
results[idx] = { callId: call.id, content: `Tool execution failed: ${msg}`, isError: true };
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
const parallelPhase = safeParallel.length > 0 ? Promise.all(safeParallel.map(({ idx, call }) => wrapExec(idx, call))) : Promise.resolve();
|
|
358
|
-
const bashPhase = (async () => {
|
|
359
|
-
for (const { idx, call } of safeBash) {
|
|
360
|
-
await wrapExec(idx, call);
|
|
361
|
-
}
|
|
362
|
-
})();
|
|
363
|
-
await Promise.all([parallelPhase, bashPhase]);
|
|
364
|
-
if (fileWriteCalls.length === 1) {
|
|
365
|
-
const { idx, call } = fileWriteCalls[0];
|
|
331
|
+
await runSafePhases(phase, (c) => this.execute(c), results, "tool-executor-web");
|
|
332
|
+
if (phase.fileWriteCalls.length === 1) {
|
|
333
|
+
const { idx, call } = phase.fileWriteCalls[0];
|
|
366
334
|
results[idx] = await this.execute(call);
|
|
367
|
-
} else if (fileWriteCalls.length >= 2) {
|
|
368
|
-
const batchResult = await this.executeBatchFileWrites(fileWriteCalls);
|
|
369
|
-
for (let i = 0; i < fileWriteCalls.length; i++) {
|
|
370
|
-
results[fileWriteCalls[i].idx] = batchResult[i];
|
|
335
|
+
} else if (phase.fileWriteCalls.length >= 2) {
|
|
336
|
+
const batchResult = await this.executeBatchFileWrites(phase.fileWriteCalls);
|
|
337
|
+
for (let i = 0; i < phase.fileWriteCalls.length; i++) {
|
|
338
|
+
results[phase.fileWriteCalls[i].idx] = batchResult[i];
|
|
371
339
|
}
|
|
372
340
|
}
|
|
373
|
-
for (const { idx, call } of otherCalls) {
|
|
341
|
+
for (const { idx, call } of phase.otherCalls) {
|
|
374
342
|
results[idx] = await this.execute(call);
|
|
375
343
|
}
|
|
376
344
|
return results;
|
|
@@ -391,6 +359,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
391
359
|
};
|
|
392
360
|
}
|
|
393
361
|
}
|
|
362
|
+
if (approvedIndices.length >= 2) {
|
|
363
|
+
const labels = approvedIndices.map((i) => `[${i + 1}]`).join(" ");
|
|
364
|
+
this.send({
|
|
365
|
+
type: "info",
|
|
366
|
+
message: `\u26A1 Writing ${labels} in parallel (results may interleave)`
|
|
367
|
+
});
|
|
368
|
+
}
|
|
394
369
|
await Promise.all(
|
|
395
370
|
approvedIndices.map(async (i) => {
|
|
396
371
|
const call = calls[i];
|
|
@@ -1971,7 +1946,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1971
1946
|
case "test": {
|
|
1972
1947
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1973
1948
|
try {
|
|
1974
|
-
const { executeTests } = await import("./run-tests-
|
|
1949
|
+
const { executeTests } = await import("./run-tests-WD53PYVA.js");
|
|
1975
1950
|
const argStr = args.join(" ").trim();
|
|
1976
1951
|
let testArgs = {};
|
|
1977
1952
|
if (argStr) {
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Q5QSCO5D.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import {
|
|
10
10
|
SUBAGENT_ALLOWED_TOOLS
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3LCVJ4AF.js";
|
|
12
12
|
|
|
13
13
|
// src/hub/task-orchestrator.ts
|
|
14
14
|
import { createInterface } from "readline";
|