jinzd-ai-cli 0.4.61 → 0.4.63
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-2ZCD5F4X.js → chunk-E45EGVSY.js} +132 -17
- package/dist/{chunk-2WGVM2J2.js → chunk-EYRPTNVI.js} +67 -16
- package/dist/{chunk-QUD2AVHH.js → chunk-GUD733DE.js} +1 -1
- package/dist/{chunk-GA74LZ62.js → chunk-MLEM56CR.js} +1 -1
- package/dist/{hub-WA2DZCSQ.js → hub-R6ID4F6J.js} +1 -1
- package/dist/index.js +225 -35
- package/dist/{run-tests-672N6ZKM.js → run-tests-A53KBZPJ.js} +1 -1
- package/dist/{run-tests-CT4PRGGP.js → run-tests-KRGPFYPF.js} +1 -1
- package/dist/{server-YJWIPDF2.js → server-BGMUORHT.js} +77 -37
- package/dist/{task-orchestrator-W66JWWKF.js → task-orchestrator-LNJWNTSJ.js} +2 -2
- 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-MLEM56CR.js";
|
|
14
14
|
|
|
15
15
|
// src/tools/builtin/bash.ts
|
|
16
16
|
import { execSync } from "child_process";
|
|
@@ -1365,14 +1365,19 @@ var ToolExecutor = class {
|
|
|
1365
1365
|
}
|
|
1366
1366
|
}
|
|
1367
1367
|
async executeAll(calls) {
|
|
1368
|
-
const
|
|
1368
|
+
const safeParallel = [];
|
|
1369
|
+
const safeBash = [];
|
|
1369
1370
|
const fileWriteCalls = [];
|
|
1370
1371
|
const otherCalls = [];
|
|
1371
1372
|
for (let i = 0; i < calls.length; i++) {
|
|
1372
1373
|
const call = calls[i];
|
|
1373
1374
|
const level = getDangerLevel(call.name, call.arguments);
|
|
1374
1375
|
if (level === "safe") {
|
|
1375
|
-
|
|
1376
|
+
if (call.name === "bash") {
|
|
1377
|
+
safeBash.push({ idx: i, call });
|
|
1378
|
+
} else {
|
|
1379
|
+
safeParallel.push({ idx: i, call });
|
|
1380
|
+
}
|
|
1376
1381
|
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
1377
1382
|
fileWriteCalls.push({ idx: i, call });
|
|
1378
1383
|
} else {
|
|
@@ -1380,11 +1385,20 @@ var ToolExecutor = class {
|
|
|
1380
1385
|
}
|
|
1381
1386
|
}
|
|
1382
1387
|
const results = new Array(calls.length);
|
|
1383
|
-
|
|
1384
|
-
|
|
1388
|
+
const t0 = Date.now();
|
|
1389
|
+
const parallelPhase = safeParallel.length > 0 ? Promise.all(safeParallel.map(async ({ idx, call }) => {
|
|
1390
|
+
results[idx] = await this.execute(call);
|
|
1391
|
+
})) : Promise.resolve();
|
|
1392
|
+
const bashPhase = (async () => {
|
|
1393
|
+
for (const { idx, call } of safeBash) {
|
|
1385
1394
|
results[idx] = await this.execute(call);
|
|
1386
|
-
}
|
|
1387
|
-
);
|
|
1395
|
+
}
|
|
1396
|
+
})();
|
|
1397
|
+
await Promise.all([parallelPhase, bashPhase]);
|
|
1398
|
+
if (safeParallel.length >= 2) {
|
|
1399
|
+
const elapsed = Date.now() - t0;
|
|
1400
|
+
console.log(theme.dim(` \u26A1 ${safeParallel.length} tools executed in parallel (${elapsed}ms)`));
|
|
1401
|
+
}
|
|
1388
1402
|
if (fileWriteCalls.length === 1) {
|
|
1389
1403
|
const { idx, call } = fileWriteCalls[0];
|
|
1390
1404
|
results[idx] = await this.execute(call);
|
|
@@ -1418,31 +1432,41 @@ var ToolExecutor = class {
|
|
|
1418
1432
|
if (this.sessionAutoApprove) {
|
|
1419
1433
|
console.log(theme.warning(" \u26A1 All auto-approved (session /yolo mode)"));
|
|
1420
1434
|
}
|
|
1421
|
-
const results =
|
|
1435
|
+
const results = new Array(calls.length);
|
|
1436
|
+
const approvedIndices = [];
|
|
1422
1437
|
for (let i = 0; i < calls.length; i++) {
|
|
1423
|
-
const call = calls[i];
|
|
1424
1438
|
const approved = decision === "all" || decision !== "none" && decision.has(i + 1);
|
|
1425
1439
|
if (approved) {
|
|
1440
|
+
approvedIndices.push(i);
|
|
1441
|
+
} else {
|
|
1442
|
+
console.log(theme.dim(` [${i + 1}] `) + theme.dim("rejected"));
|
|
1443
|
+
results[i] = { callId: calls[i].id, content: `[User rejected] The user rejected this ${calls[i].name} operation. Do not retry without asking.`, isError: true };
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
const t0 = Date.now();
|
|
1447
|
+
await Promise.all(
|
|
1448
|
+
approvedIndices.map(async (i) => {
|
|
1449
|
+
const call = calls[i];
|
|
1426
1450
|
const tool = this.registry.get(call.name);
|
|
1427
1451
|
if (!tool) {
|
|
1428
|
-
results
|
|
1429
|
-
|
|
1452
|
+
results[i] = { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
|
|
1453
|
+
return;
|
|
1430
1454
|
}
|
|
1431
1455
|
try {
|
|
1432
1456
|
const rawContent = await tool.execute(call.arguments);
|
|
1433
1457
|
const content = truncateOutput(rawContent, call.name);
|
|
1434
1458
|
const wasTruncated = content !== rawContent;
|
|
1435
1459
|
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
1436
|
-
results
|
|
1460
|
+
results[i] = { callId: call.id, content, isError: false };
|
|
1437
1461
|
} catch (err) {
|
|
1438
1462
|
const message = err instanceof Error ? err.message : String(err);
|
|
1439
1463
|
this.printToolResult(call.name, message, true, false);
|
|
1440
|
-
results
|
|
1464
|
+
results[i] = { callId: call.id, content: message, isError: true };
|
|
1441
1465
|
}
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1466
|
+
})
|
|
1467
|
+
);
|
|
1468
|
+
if (approvedIndices.length >= 2) {
|
|
1469
|
+
console.log(theme.dim(` \u26A1 ${approvedIndices.length} file writes executed in parallel (${Date.now() - t0}ms)`));
|
|
1446
1470
|
}
|
|
1447
1471
|
return results;
|
|
1448
1472
|
}
|
|
@@ -4062,6 +4086,28 @@ var notebookEditTool = {
|
|
|
4062
4086
|
}
|
|
4063
4087
|
};
|
|
4064
4088
|
|
|
4089
|
+
// src/core/token-estimator.ts
|
|
4090
|
+
var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
|
|
4091
|
+
function estimateTokens(text) {
|
|
4092
|
+
if (!text) return 0;
|
|
4093
|
+
const cjkMatches = text.match(CJK_REGEX);
|
|
4094
|
+
const cjkCount = cjkMatches ? cjkMatches.length : 0;
|
|
4095
|
+
const nonCjkCount = text.length - cjkCount;
|
|
4096
|
+
const tokens = cjkCount * 1.5 + nonCjkCount * 0.25;
|
|
4097
|
+
return Math.ceil(tokens) + 4;
|
|
4098
|
+
}
|
|
4099
|
+
function estimateToolDefinitionTokens(def) {
|
|
4100
|
+
let charCount = def.name.length + def.description.length;
|
|
4101
|
+
for (const [key, param] of Object.entries(def.parameters)) {
|
|
4102
|
+
charCount += key.length + param.type.length + param.description.length;
|
|
4103
|
+
if (param.enum) {
|
|
4104
|
+
charCount += param.enum.join(" ").length;
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
const structuralOverhead = 40 + Object.keys(def.parameters).length * 30;
|
|
4108
|
+
return Math.ceil((charCount + structuralOverhead) * 0.25) + 10;
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4065
4111
|
// src/tools/registry.ts
|
|
4066
4112
|
import { pathToFileURL } from "url";
|
|
4067
4113
|
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readdirSync as readdirSync6 } from "fs";
|
|
@@ -4110,6 +4156,74 @@ var ToolRegistry = class {
|
|
|
4110
4156
|
getDefinitions() {
|
|
4111
4157
|
return [...this.tools.values()].map((t) => t.definition);
|
|
4112
4158
|
}
|
|
4159
|
+
/**
|
|
4160
|
+
* Return tool definitions within a token budget, trimming MCP tools if needed.
|
|
4161
|
+
*
|
|
4162
|
+
* Strategy: always include all builtin + plugin tools. If total tokens exceed
|
|
4163
|
+
* the budget, keep only MCP tools that were used in this session (by name),
|
|
4164
|
+
* trimming the rest. Returns { definitions, trimmedCount, systemNote }.
|
|
4165
|
+
*
|
|
4166
|
+
* @param tokenBudget Max tokens for tool definitions (typically 20% of context window)
|
|
4167
|
+
* @param usedToolNames Names of MCP tools already called this session (always kept)
|
|
4168
|
+
*/
|
|
4169
|
+
getDefinitionsWithBudget(tokenBudget, usedToolNames) {
|
|
4170
|
+
const allDefs = this.getDefinitions();
|
|
4171
|
+
let totalTokens = 0;
|
|
4172
|
+
for (const def of allDefs) {
|
|
4173
|
+
totalTokens += estimateToolDefinitionTokens(def);
|
|
4174
|
+
}
|
|
4175
|
+
if (totalTokens <= tokenBudget) {
|
|
4176
|
+
return { definitions: allDefs, trimmedCount: 0, systemNote: null };
|
|
4177
|
+
}
|
|
4178
|
+
const builtinDefs = [];
|
|
4179
|
+
const mcpDefs = [];
|
|
4180
|
+
for (const def of allDefs) {
|
|
4181
|
+
if (this.mcpToolNames.has(def.name)) {
|
|
4182
|
+
mcpDefs.push(def);
|
|
4183
|
+
} else {
|
|
4184
|
+
builtinDefs.push(def);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
let budget = tokenBudget;
|
|
4188
|
+
for (const def of builtinDefs) {
|
|
4189
|
+
budget -= estimateToolDefinitionTokens(def);
|
|
4190
|
+
}
|
|
4191
|
+
const kept = [...builtinDefs];
|
|
4192
|
+
const used = usedToolNames ?? /* @__PURE__ */ new Set();
|
|
4193
|
+
const remaining = [];
|
|
4194
|
+
for (const def of mcpDefs) {
|
|
4195
|
+
if (used.has(def.name)) {
|
|
4196
|
+
const cost = estimateToolDefinitionTokens(def);
|
|
4197
|
+
budget -= cost;
|
|
4198
|
+
kept.push(def);
|
|
4199
|
+
} else {
|
|
4200
|
+
remaining.push(def);
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
let trimmed = 0;
|
|
4204
|
+
for (const def of remaining) {
|
|
4205
|
+
const cost = estimateToolDefinitionTokens(def);
|
|
4206
|
+
if (budget >= cost) {
|
|
4207
|
+
budget -= cost;
|
|
4208
|
+
kept.push(def);
|
|
4209
|
+
} else {
|
|
4210
|
+
trimmed++;
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
let systemNote = null;
|
|
4214
|
+
if (trimmed > 0) {
|
|
4215
|
+
const trimmedServers = /* @__PURE__ */ new Set();
|
|
4216
|
+
const keptNames = new Set(kept.map((d) => d.name));
|
|
4217
|
+
for (const def of mcpDefs) {
|
|
4218
|
+
if (!keptNames.has(def.name)) {
|
|
4219
|
+
const parts = def.name.split("__");
|
|
4220
|
+
if (parts.length >= 2) trimmedServers.add(parts[1]);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
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.`;
|
|
4224
|
+
}
|
|
4225
|
+
return { definitions: kept, trimmedCount: trimmed, systemNote };
|
|
4226
|
+
}
|
|
4113
4227
|
listAll() {
|
|
4114
4228
|
return [...this.tools.values()];
|
|
4115
4229
|
}
|
|
@@ -4229,5 +4343,6 @@ export {
|
|
|
4229
4343
|
askUserContext,
|
|
4230
4344
|
googleSearchContext,
|
|
4231
4345
|
spawnAgentContext,
|
|
4346
|
+
estimateTokens,
|
|
4232
4347
|
ToolRegistry
|
|
4233
4348
|
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
RateLimitError,
|
|
9
9
|
schemaToJsonSchema,
|
|
10
10
|
truncateForPersist
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-E45EGVSY.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-MLEM56CR.js";
|
|
25
25
|
|
|
26
26
|
// src/config/config-manager.ts
|
|
27
27
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -182,6 +182,10 @@ var ConfigSchema = z.object({
|
|
|
182
182
|
// 实际上限还会受模型 contextWindow 动态约束(取 contextWindow/4 作为下限)。
|
|
183
183
|
// 设置为 0 或未配置时使用默认值;不建议设为小于 12_000 或大于模型 contextWindow/2。
|
|
184
184
|
maxToolOutputChars: z.number().int().min(0).default(5e5),
|
|
185
|
+
// 月度成本预算(USD)。
|
|
186
|
+
// 设置后,每次 AI 回复后会跟踪成本,接近或超过预算时在 /status 和 /cost 中显示警告。
|
|
187
|
+
// 默认 0 = 不限制。例:50 表示每月最多花 $50。
|
|
188
|
+
monthlyBudget: z.number().min(0).default(0),
|
|
185
189
|
// 插件加载开关(安全控制)
|
|
186
190
|
// 默认 false:不自动加载 ~/.aicli/plugins/ 中的插件文件。
|
|
187
191
|
// 插件以完整 Node.js 权限在主进程中执行(可读写文件、访问网络、执行命令),
|
|
@@ -2462,14 +2466,25 @@ var Session = class _Session {
|
|
|
2462
2466
|
/**
|
|
2463
2467
|
* 上下文压缩:用摘要消息替换旧消息,保留最近 keepLast 条。
|
|
2464
2468
|
*
|
|
2469
|
+
* Tool-history-aware: if the cut point lands inside a tool round
|
|
2470
|
+
* (assistant+toolCalls followed by tool results), expand to keep the
|
|
2471
|
+
* entire round intact. This prevents orphaned tool results.
|
|
2472
|
+
*
|
|
2465
2473
|
* 压缩后消息结构:
|
|
2466
|
-
* [summaryMsg(user), ackMsg(assistant), ...最近
|
|
2474
|
+
* [summaryMsg(user), ackMsg(assistant), ...最近 N 条原始消息]
|
|
2467
2475
|
*
|
|
2468
2476
|
* @returns 被删除的消息条数
|
|
2469
2477
|
*/
|
|
2470
2478
|
compact(summaryMsg, ackMsg, keepLast) {
|
|
2471
|
-
|
|
2472
|
-
|
|
2479
|
+
let cutIndex = this.messages.length - keepLast;
|
|
2480
|
+
if (cutIndex <= 0) {
|
|
2481
|
+
return 0;
|
|
2482
|
+
}
|
|
2483
|
+
while (cutIndex > 0 && this.messages[cutIndex]?.role === "tool") {
|
|
2484
|
+
cutIndex--;
|
|
2485
|
+
}
|
|
2486
|
+
const preserved = this.messages.slice(cutIndex);
|
|
2487
|
+
const removedCount = cutIndex;
|
|
2473
2488
|
this.messages = [summaryMsg, ackMsg, ...preserved];
|
|
2474
2489
|
this.updated = /* @__PURE__ */ new Date();
|
|
2475
2490
|
return removedCount;
|
|
@@ -3687,6 +3702,7 @@ function formatCost(amount) {
|
|
|
3687
3702
|
}
|
|
3688
3703
|
|
|
3689
3704
|
// src/session/tool-history.ts
|
|
3705
|
+
var SESSION_SIZE_LIMIT = 2 * 1024 * 1024;
|
|
3690
3706
|
function persistToolRound(session, toolCalls, toolResults, opts) {
|
|
3691
3707
|
session.addMessage({
|
|
3692
3708
|
role: "assistant",
|
|
@@ -3757,16 +3773,51 @@ function rebuildExtraMessages(provider, toolHistory) {
|
|
|
3757
3773
|
}
|
|
3758
3774
|
return result;
|
|
3759
3775
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
const
|
|
3769
|
-
|
|
3776
|
+
function trimOldToolOutput(messages, keepRecentRounds = 10) {
|
|
3777
|
+
const roundStarts = [];
|
|
3778
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3779
|
+
if (messages[i].role === "assistant" && messages[i].toolCalls?.length) {
|
|
3780
|
+
roundStarts.push(i);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
if (roundStarts.length <= keepRecentRounds) return 0;
|
|
3784
|
+
const cutoffRoundIdx = roundStarts.length - keepRecentRounds;
|
|
3785
|
+
let trimmed = 0;
|
|
3786
|
+
for (let r = 0; r < cutoffRoundIdx; r++) {
|
|
3787
|
+
const start = roundStarts[r];
|
|
3788
|
+
const end = r + 1 < roundStarts.length ? roundStarts[r + 1] : messages.length;
|
|
3789
|
+
const assistantMsg = messages[start];
|
|
3790
|
+
if (typeof assistantMsg.content === "string" && assistantMsg.content.length > 200) {
|
|
3791
|
+
assistantMsg.content = assistantMsg.content.slice(0, 100) + "\u2026 [trimmed for size]";
|
|
3792
|
+
trimmed++;
|
|
3793
|
+
}
|
|
3794
|
+
for (let j = start + 1; j < end; j++) {
|
|
3795
|
+
const msg = messages[j];
|
|
3796
|
+
if (msg.role === "tool") {
|
|
3797
|
+
const status = msg.isError ? "\u2717 error" : "\u2713 ok";
|
|
3798
|
+
const name = msg.toolName ?? "unknown";
|
|
3799
|
+
const currentContent = typeof msg.content === "string" ? msg.content : "";
|
|
3800
|
+
if (currentContent.length > 200) {
|
|
3801
|
+
msg.content = `[${name}: ${status}] (output trimmed for size \u2014 ${currentContent.length} chars)`;
|
|
3802
|
+
trimmed++;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
return trimmed;
|
|
3808
|
+
}
|
|
3809
|
+
function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
3810
|
+
const json = JSON.stringify(session.toJSON());
|
|
3811
|
+
if (json.length <= sizeLimit) return false;
|
|
3812
|
+
let keepRecent = 10;
|
|
3813
|
+
while (keepRecent >= 2) {
|
|
3814
|
+
const trimmed = trimOldToolOutput(session.messages, keepRecent);
|
|
3815
|
+
if (trimmed === 0) break;
|
|
3816
|
+
const newSize = JSON.stringify(session.toJSON()).length;
|
|
3817
|
+
if (newSize <= sizeLimit) return true;
|
|
3818
|
+
keepRecent = Math.max(2, Math.floor(keepRecent / 2));
|
|
3819
|
+
}
|
|
3820
|
+
return true;
|
|
3770
3821
|
}
|
|
3771
3822
|
|
|
3772
3823
|
// src/repl/dev-state.ts
|
|
@@ -3882,7 +3933,7 @@ export {
|
|
|
3882
3933
|
persistToolRound,
|
|
3883
3934
|
extractToolHistory,
|
|
3884
3935
|
rebuildExtraMessages,
|
|
3885
|
-
|
|
3936
|
+
autoTrimSessionIfNeeded,
|
|
3886
3937
|
SNAPSHOT_PROMPT,
|
|
3887
3938
|
sessionHasMeaningfulContent,
|
|
3888
3939
|
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-LNJWNTSJ.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,12 +8,12 @@ import {
|
|
|
8
8
|
SessionManager,
|
|
9
9
|
SkillManager,
|
|
10
10
|
TOOL_CALL_REMINDER,
|
|
11
|
+
autoTrimSessionIfNeeded,
|
|
11
12
|
buildPhantomCorrectionMessage,
|
|
12
13
|
buildWriteRoundReminder,
|
|
13
14
|
clearDevState,
|
|
14
15
|
computeCost,
|
|
15
16
|
detectsHallucinatedFileOp,
|
|
16
|
-
estimateTokens,
|
|
17
17
|
extractToolHistory,
|
|
18
18
|
extractWrittenFilePaths,
|
|
19
19
|
findPhantomClaims,
|
|
@@ -31,11 +31,12 @@ import {
|
|
|
31
31
|
saveDevState,
|
|
32
32
|
sessionHasMeaningfulContent,
|
|
33
33
|
setupProxy
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-EYRPTNVI.js";
|
|
35
35
|
import {
|
|
36
36
|
ToolExecutor,
|
|
37
37
|
ToolRegistry,
|
|
38
38
|
askUserContext,
|
|
39
|
+
estimateTokens,
|
|
39
40
|
googleSearchContext,
|
|
40
41
|
initTheme,
|
|
41
42
|
lastResponseStore,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
spawnAgentContext,
|
|
46
47
|
theme,
|
|
47
48
|
undoStack
|
|
48
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-E45EGVSY.js";
|
|
49
50
|
import {
|
|
50
51
|
fileCheckpoints
|
|
51
52
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -70,15 +71,15 @@ import {
|
|
|
70
71
|
SKILLS_DIR_NAME,
|
|
71
72
|
VERSION,
|
|
72
73
|
buildUserIdentityPrompt
|
|
73
|
-
} from "./chunk-
|
|
74
|
+
} from "./chunk-MLEM56CR.js";
|
|
74
75
|
|
|
75
76
|
// src/index.ts
|
|
76
77
|
import { program } from "commander";
|
|
77
78
|
|
|
78
79
|
// src/repl/repl.ts
|
|
79
80
|
import * as readline from "readline";
|
|
80
|
-
import { existsSync as
|
|
81
|
-
import { join as
|
|
81
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
82
|
+
import { join as join5, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
|
|
82
83
|
import chalk4 from "chalk";
|
|
83
84
|
|
|
84
85
|
// src/repl/renderer.ts
|
|
@@ -1842,7 +1843,7 @@ ${hint}` : "")
|
|
|
1842
1843
|
{
|
|
1843
1844
|
name: "cost",
|
|
1844
1845
|
description: "Show session token usage, prompt-cache hits, and USD cost",
|
|
1845
|
-
usage: "/cost [reset]",
|
|
1846
|
+
usage: "/cost [reset | history]",
|
|
1846
1847
|
execute(args, ctx) {
|
|
1847
1848
|
const sub = args[0]?.toLowerCase();
|
|
1848
1849
|
if (sub === "reset") {
|
|
@@ -1850,6 +1851,28 @@ ${hint}` : "")
|
|
|
1850
1851
|
ctx.renderer.printSuccess("Session token counters reset.");
|
|
1851
1852
|
return;
|
|
1852
1853
|
}
|
|
1854
|
+
if (sub === "history" || sub === "h") {
|
|
1855
|
+
const tracker = ctx.getCostTracker();
|
|
1856
|
+
const budget = ctx.config.get("monthlyBudget");
|
|
1857
|
+
console.log();
|
|
1858
|
+
console.log(theme.heading(" \u{1F4B0} Cross-Session Cost Dashboard"));
|
|
1859
|
+
console.log(theme.dim(" " + "\u2500".repeat(48)));
|
|
1860
|
+
const summary = tracker.formatSummary(budget);
|
|
1861
|
+
for (const line of summary.split("\n")) {
|
|
1862
|
+
console.log(theme.dim(" ") + chalk2.white(line));
|
|
1863
|
+
}
|
|
1864
|
+
console.log(theme.dim(" " + "\u2500".repeat(48)));
|
|
1865
|
+
const warning = tracker.checkBudget(budget);
|
|
1866
|
+
if (warning) {
|
|
1867
|
+
console.log(theme.warning(` ${warning}`));
|
|
1868
|
+
} else if (budget && budget > 0) {
|
|
1869
|
+
console.log(theme.success(" \u2713 Within monthly budget"));
|
|
1870
|
+
} else {
|
|
1871
|
+
console.log(theme.dim(' Tip: set "monthlyBudget" in config (e.g., 50 for $50/month)'));
|
|
1872
|
+
}
|
|
1873
|
+
console.log();
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1853
1876
|
const session = ctx.sessions.current;
|
|
1854
1877
|
const usage = session?.tokenUsage ?? {
|
|
1855
1878
|
inputTokens: 0,
|
|
@@ -2165,7 +2188,7 @@ ${hint}` : "")
|
|
|
2165
2188
|
usage: "/test [command|filter]",
|
|
2166
2189
|
async execute(args, ctx) {
|
|
2167
2190
|
try {
|
|
2168
|
-
const { executeTests } = await import("./run-tests-
|
|
2191
|
+
const { executeTests } = await import("./run-tests-KRGPFYPF.js");
|
|
2169
2192
|
const argStr = args.join(" ").trim();
|
|
2170
2193
|
let testArgs = {};
|
|
2171
2194
|
if (argStr) {
|
|
@@ -3229,6 +3252,125 @@ var CustomCommandManager = class {
|
|
|
3229
3252
|
}
|
|
3230
3253
|
};
|
|
3231
3254
|
|
|
3255
|
+
// src/core/cost-tracker.ts
|
|
3256
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync } from "fs";
|
|
3257
|
+
import { join as join4 } from "path";
|
|
3258
|
+
var CostTracker = class {
|
|
3259
|
+
filePath;
|
|
3260
|
+
records = [];
|
|
3261
|
+
dirty = false;
|
|
3262
|
+
constructor(configDir) {
|
|
3263
|
+
this.filePath = join4(configDir, "cost-history.json");
|
|
3264
|
+
this.load();
|
|
3265
|
+
}
|
|
3266
|
+
load() {
|
|
3267
|
+
try {
|
|
3268
|
+
if (existsSync4(this.filePath)) {
|
|
3269
|
+
const data = JSON.parse(readFileSync3(this.filePath, "utf-8"));
|
|
3270
|
+
if (data.version === 1 && Array.isArray(data.records)) {
|
|
3271
|
+
this.records = data.records;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
} catch {
|
|
3275
|
+
this.records = [];
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
/** Save to disk (atomic write). */
|
|
3279
|
+
save() {
|
|
3280
|
+
if (!this.dirty) return;
|
|
3281
|
+
const data = { version: 1, records: this.records };
|
|
3282
|
+
const tmp = this.filePath + ".tmp";
|
|
3283
|
+
writeFileSync2(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
3284
|
+
renameSync(tmp, this.filePath);
|
|
3285
|
+
this.dirty = false;
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Record cost from a completed session/interaction.
|
|
3289
|
+
*/
|
|
3290
|
+
addCost(provider, model, usage) {
|
|
3291
|
+
const cost = computeCost(provider, model, usage);
|
|
3292
|
+
if (cost === null || cost === 0) return;
|
|
3293
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3294
|
+
let record = this.records.find((r) => r.date === today);
|
|
3295
|
+
if (!record) {
|
|
3296
|
+
record = { date: today, cost: 0, sessions: 0, inputTokens: 0, outputTokens: 0 };
|
|
3297
|
+
this.records.push(record);
|
|
3298
|
+
}
|
|
3299
|
+
record.cost += cost;
|
|
3300
|
+
record.sessions += 1;
|
|
3301
|
+
record.inputTokens += usage.inputTokens;
|
|
3302
|
+
record.outputTokens += usage.outputTokens;
|
|
3303
|
+
this.dirty = true;
|
|
3304
|
+
if (this.records.length > 90) {
|
|
3305
|
+
this.records = this.records.slice(-90);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
/** Get total cost for a given month ("2026-04"). */
|
|
3309
|
+
getMonthlyCost(yearMonth) {
|
|
3310
|
+
const prefix = yearMonth ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
3311
|
+
return this.records.filter((r) => r.date.startsWith(prefix)).reduce((sum, r) => sum + r.cost, 0);
|
|
3312
|
+
}
|
|
3313
|
+
/** Get today's cost. */
|
|
3314
|
+
getTodayCost() {
|
|
3315
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3316
|
+
return this.records.find((r) => r.date === today)?.cost ?? 0;
|
|
3317
|
+
}
|
|
3318
|
+
/** Get total cost for last N days. */
|
|
3319
|
+
getRecentCost(days) {
|
|
3320
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
3321
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
3322
|
+
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
|
3323
|
+
return this.records.filter((r) => r.date >= cutoffStr).reduce((sum, r) => sum + r.cost, 0);
|
|
3324
|
+
}
|
|
3325
|
+
/** Get all records for display. */
|
|
3326
|
+
getRecords() {
|
|
3327
|
+
return [...this.records];
|
|
3328
|
+
}
|
|
3329
|
+
/**
|
|
3330
|
+
* Check if monthly cost exceeds budget, return warning message or null.
|
|
3331
|
+
*/
|
|
3332
|
+
checkBudget(monthlyBudget) {
|
|
3333
|
+
if (!monthlyBudget || monthlyBudget <= 0) return null;
|
|
3334
|
+
const monthlyCost = this.getMonthlyCost();
|
|
3335
|
+
const ratio = monthlyCost / monthlyBudget;
|
|
3336
|
+
if (ratio >= 1) {
|
|
3337
|
+
return `\u{1F6A8} Monthly budget exceeded: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
3338
|
+
}
|
|
3339
|
+
if (ratio >= 0.8) {
|
|
3340
|
+
return `\u26A0 Monthly budget warning: ${formatCost(monthlyCost)} / ${formatCost(monthlyBudget)} (${Math.round(ratio * 100)}%)`;
|
|
3341
|
+
}
|
|
3342
|
+
return null;
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Format a cost summary for display.
|
|
3346
|
+
*/
|
|
3347
|
+
formatSummary(monthlyBudget) {
|
|
3348
|
+
const today = this.getTodayCost();
|
|
3349
|
+
const monthly = this.getMonthlyCost();
|
|
3350
|
+
const yearMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
3351
|
+
const lines = [
|
|
3352
|
+
`Today: ${formatCost(today)}`,
|
|
3353
|
+
`This month: ${formatCost(monthly)} (${yearMonth})`
|
|
3354
|
+
];
|
|
3355
|
+
if (monthlyBudget && monthlyBudget > 0) {
|
|
3356
|
+
const ratio = monthly / monthlyBudget;
|
|
3357
|
+
const bar = "\u2588".repeat(Math.min(20, Math.round(ratio * 20))) + "\u2591".repeat(Math.max(0, 20 - Math.round(ratio * 20)));
|
|
3358
|
+
lines.push(`Budget: ${formatCost(monthlyBudget)} [${bar}] ${Math.round(ratio * 100)}%`);
|
|
3359
|
+
}
|
|
3360
|
+
const last7 = [];
|
|
3361
|
+
for (let i = 6; i >= 0; i--) {
|
|
3362
|
+
const d = /* @__PURE__ */ new Date();
|
|
3363
|
+
d.setDate(d.getDate() - i);
|
|
3364
|
+
const dateStr = d.toISOString().slice(0, 10);
|
|
3365
|
+
const record = this.records.find((r) => r.date === dateStr);
|
|
3366
|
+
const dayLabel = dateStr.slice(5);
|
|
3367
|
+
last7.push(`${dayLabel}: ${record ? formatCost(record.cost) : "$0.00"}`);
|
|
3368
|
+
}
|
|
3369
|
+
lines.push(`Last 7 days: ${last7.join(" ")}`);
|
|
3370
|
+
return lines.join("\n");
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
|
|
3232
3374
|
// src/repl/notify.ts
|
|
3233
3375
|
import { spawn } from "child_process";
|
|
3234
3376
|
import { platform as platform2 } from "os";
|
|
@@ -3302,7 +3444,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3302
3444
|
const absPath = resolve2(cwd, rawPath);
|
|
3303
3445
|
const ext = extname2(rawPath).toLowerCase();
|
|
3304
3446
|
const mime = IMAGE_MIME[ext];
|
|
3305
|
-
if (!
|
|
3447
|
+
if (!existsSync5(absPath)) {
|
|
3306
3448
|
refs.push({ path: rawPath, type: "notfound" });
|
|
3307
3449
|
continue;
|
|
3308
3450
|
}
|
|
@@ -3312,7 +3454,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3312
3454
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
3313
3455
|
continue;
|
|
3314
3456
|
}
|
|
3315
|
-
const data =
|
|
3457
|
+
const data = readFileSync4(absPath).toString("base64");
|
|
3316
3458
|
imageParts.push({
|
|
3317
3459
|
type: "image_url",
|
|
3318
3460
|
image_url: { url: `data:${mime};base64,${data}` }
|
|
@@ -3320,7 +3462,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3320
3462
|
refs.push({ path: rawPath, type: "image" });
|
|
3321
3463
|
textBody = textBody.replace(match[0], "").trim();
|
|
3322
3464
|
} else {
|
|
3323
|
-
const content =
|
|
3465
|
+
const content = readFileSync4(absPath, "utf-8");
|
|
3324
3466
|
const inlined = `
|
|
3325
3467
|
|
|
3326
3468
|
[File: ${rawPath}]
|
|
@@ -3389,6 +3531,7 @@ var Repl = class {
|
|
|
3389
3531
|
if (options?.blockedTools) this.blockedTools = options.blockedTools;
|
|
3390
3532
|
if (options?.resumeSessionId) this.resumeSessionId = options.resumeSessionId;
|
|
3391
3533
|
if (options?.maxToolRoundsOverride !== void 0) this.maxToolRoundsOverride = options.maxToolRoundsOverride;
|
|
3534
|
+
this.costTracker = new CostTracker(this.config.getConfigDir());
|
|
3392
3535
|
}
|
|
3393
3536
|
rl;
|
|
3394
3537
|
currentProvider;
|
|
@@ -3404,12 +3547,13 @@ var Repl = class {
|
|
|
3404
3547
|
contextLayers = [];
|
|
3405
3548
|
/** 本次会话累计 token 用量 */
|
|
3406
3549
|
sessionTokenUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
3407
|
-
/** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage. */
|
|
3550
|
+
/** Fold a single-request TokenUsage (with optional cache fields) into sessionTokenUsage + cost tracker. */
|
|
3408
3551
|
addSessionUsage(u) {
|
|
3409
3552
|
this.sessionTokenUsage.inputTokens += u.inputTokens;
|
|
3410
3553
|
this.sessionTokenUsage.outputTokens += u.outputTokens;
|
|
3411
3554
|
this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
|
|
3412
3555
|
this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
|
|
3556
|
+
this.costTracker.addCost(this.currentProvider, this.currentModel, u);
|
|
3413
3557
|
}
|
|
3414
3558
|
/** 启动时检测到的 Git 分支(无 git 仓库时为 null) */
|
|
3415
3559
|
gitBranch = null;
|
|
@@ -3451,6 +3595,8 @@ var Repl = class {
|
|
|
3451
3595
|
selecting = false;
|
|
3452
3596
|
/** CLI --max-tool-rounds 覆盖值;未指定时从 config.maxToolRounds 读取 */
|
|
3453
3597
|
maxToolRoundsOverride;
|
|
3598
|
+
/** 跨 session 成本追踪器 */
|
|
3599
|
+
costTracker;
|
|
3454
3600
|
// ── /add-dir 目录上下文支持 ────────────────────────────────────────────────
|
|
3455
3601
|
/**
|
|
3456
3602
|
* 扫描目录内容,返回格式化字符串(含目录树 + 关键文件内容)。
|
|
@@ -3530,7 +3676,7 @@ var Repl = class {
|
|
|
3530
3676
|
const filtered = entries.filter((e) => !SKIP_DIRS_SET.has(e));
|
|
3531
3677
|
for (let i = 0; i < filtered.length && entryCount < MAX_TREE_ENTRIES; i++) {
|
|
3532
3678
|
const name = filtered[i];
|
|
3533
|
-
const fullPath =
|
|
3679
|
+
const fullPath = join5(dir, name);
|
|
3534
3680
|
const isLast = i === filtered.length - 1;
|
|
3535
3681
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
3536
3682
|
let isDir;
|
|
@@ -3564,7 +3710,7 @@ ${treeLines.join("\n")}`
|
|
|
3564
3710
|
for (const name of entries) {
|
|
3565
3711
|
if (totalChars >= MAX_TOTAL_CHARS) break;
|
|
3566
3712
|
if (SKIP_DIRS_SET.has(name)) continue;
|
|
3567
|
-
const fullPath =
|
|
3713
|
+
const fullPath = join5(dir, name);
|
|
3568
3714
|
let st;
|
|
3569
3715
|
try {
|
|
3570
3716
|
st = statSync3(fullPath);
|
|
@@ -3579,7 +3725,7 @@ ${treeLines.join("\n")}`
|
|
|
3579
3725
|
if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
|
|
3580
3726
|
if (st.size > MAX_FILE_CHARS * 3) continue;
|
|
3581
3727
|
try {
|
|
3582
|
-
let content =
|
|
3728
|
+
let content = readFileSync4(fullPath, "utf-8");
|
|
3583
3729
|
if (content.length > MAX_FILE_CHARS) {
|
|
3584
3730
|
content = content.slice(0, MAX_FILE_CHARS) + `
|
|
3585
3731
|
... (truncated, ${content.length} chars total)`;
|
|
@@ -3609,7 +3755,7 @@ ${content}
|
|
|
3609
3755
|
*/
|
|
3610
3756
|
addExtraContextDir(dirPath) {
|
|
3611
3757
|
const absPath = resolve2(dirPath);
|
|
3612
|
-
if (!
|
|
3758
|
+
if (!existsSync5(absPath)) {
|
|
3613
3759
|
return { success: false, charCount: 0, added: false, error: `Directory not found: ${dirPath}` };
|
|
3614
3760
|
}
|
|
3615
3761
|
let isDir;
|
|
@@ -3643,9 +3789,9 @@ ${content}
|
|
|
3643
3789
|
*/
|
|
3644
3790
|
findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
|
|
3645
3791
|
for (const candidate of candidates) {
|
|
3646
|
-
const fullPath =
|
|
3647
|
-
if (
|
|
3648
|
-
const content =
|
|
3792
|
+
const fullPath = join5(dir, candidate);
|
|
3793
|
+
if (existsSync5(fullPath)) {
|
|
3794
|
+
const content = readFileSync4(fullPath, "utf-8").trim();
|
|
3649
3795
|
if (content) return { filePath: fullPath, content };
|
|
3650
3796
|
}
|
|
3651
3797
|
}
|
|
@@ -3673,10 +3819,10 @@ ${content}
|
|
|
3673
3819
|
const cwd = process.cwd();
|
|
3674
3820
|
const gitRoot = getGitRoot(cwd);
|
|
3675
3821
|
const projectRoot = gitRoot ?? cwd;
|
|
3676
|
-
const mcpPath =
|
|
3677
|
-
if (!
|
|
3822
|
+
const mcpPath = join5(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
3823
|
+
if (!existsSync5(mcpPath)) return null;
|
|
3678
3824
|
try {
|
|
3679
|
-
const raw = JSON.parse(
|
|
3825
|
+
const raw = JSON.parse(readFileSync4(mcpPath, "utf-8"));
|
|
3680
3826
|
const servers = raw?.mcpServers;
|
|
3681
3827
|
if (!servers || typeof servers !== "object") {
|
|
3682
3828
|
process.stderr.write(
|
|
@@ -3722,8 +3868,8 @@ ${content}
|
|
|
3722
3868
|
);
|
|
3723
3869
|
return { layers: [], mergedContent: "" };
|
|
3724
3870
|
}
|
|
3725
|
-
if (
|
|
3726
|
-
const content =
|
|
3871
|
+
if (existsSync5(fullPath)) {
|
|
3872
|
+
const content = readFileSync4(fullPath, "utf-8").trim();
|
|
3727
3873
|
if (content) {
|
|
3728
3874
|
const layer = {
|
|
3729
3875
|
level: "project",
|
|
@@ -3780,9 +3926,9 @@ ${content}
|
|
|
3780
3926
|
* 超过 MEMORY_MAX_CHARS 时只取末尾最新部分。
|
|
3781
3927
|
*/
|
|
3782
3928
|
loadMemoryContent() {
|
|
3783
|
-
const memoryPath =
|
|
3784
|
-
if (!
|
|
3785
|
-
let content =
|
|
3929
|
+
const memoryPath = join5(this.config.getConfigDir(), MEMORY_FILE_NAME);
|
|
3930
|
+
if (!existsSync5(memoryPath)) return null;
|
|
3931
|
+
let content = readFileSync4(memoryPath, "utf-8").trim();
|
|
3786
3932
|
if (!content) return null;
|
|
3787
3933
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
3788
3934
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -3911,6 +4057,11 @@ ${skillContent}`);
|
|
|
3911
4057
|
"4. Current status and tasks not yet completed",
|
|
3912
4058
|
"5. Key details to remember (file paths, config values, error messages, variable names, etc.)",
|
|
3913
4059
|
"",
|
|
4060
|
+
"If the conversation contains tool call rounds (assistant calling tools like read_file, write_file, bash, etc.), ",
|
|
4061
|
+
'summarize each round as a single line: "- [tool_name] target \u2192 result/status". ',
|
|
4062
|
+
'Do NOT include raw tool output. Group consecutive similar operations (e.g., "read 5 files in src/") ',
|
|
4063
|
+
"instead of listing each one individually.",
|
|
4064
|
+
"",
|
|
3914
4065
|
"Requirements: Be well-organized so that subsequent conversation can seamlessly continue from this summary."
|
|
3915
4066
|
];
|
|
3916
4067
|
if (instruction) summaryPromptLines.push("", `Additional requirement: ${instruction}`);
|
|
@@ -4088,6 +4239,16 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4088
4239
|
process.stdout.write(
|
|
4089
4240
|
theme.dim(` \u{1F4C2} Resumed session: ${session.id.slice(0, 8)} `) + theme.dim(`(${session.messages.length} messages`) + (session.title ? theme.dim(`, "${session.title}"`) : "") + theme.dim(")\n")
|
|
4090
4241
|
);
|
|
4242
|
+
const msgs = session.messages;
|
|
4243
|
+
if (msgs.length > 0) {
|
|
4244
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
4245
|
+
const isIncomplete = lastMsg.role === "tool" || lastMsg.role === "assistant" && lastMsg.toolCalls && lastMsg.toolCalls.length > 0;
|
|
4246
|
+
if (isIncomplete) {
|
|
4247
|
+
process.stdout.write(
|
|
4248
|
+
theme.warning(" \u26A0 Session appears to have been interrupted mid-task (last message is tool output).\n") + theme.dim(" The AI will see the tool history and can continue where it left off.\n") + theme.dim(' Tip: type "continue where you left off" or describe what to do next.\n')
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4091
4252
|
}
|
|
4092
4253
|
if (layers.length > 0) {
|
|
4093
4254
|
if (layers.length === 1) {
|
|
@@ -4128,14 +4289,14 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4128
4289
|
process.stdout.write(theme.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
|
|
4129
4290
|
`));
|
|
4130
4291
|
}
|
|
4131
|
-
const skillsDir =
|
|
4292
|
+
const skillsDir = join5(this.config.getConfigDir(), SKILLS_DIR_NAME);
|
|
4132
4293
|
this.skillManager = new SkillManager(skillsDir);
|
|
4133
4294
|
const skillCount = this.skillManager.loadSkills();
|
|
4134
4295
|
if (skillCount > 0) {
|
|
4135
4296
|
process.stdout.write(theme.dim(` \u{1F3AF} Skills: ${skillCount} available (use /skill to manage)
|
|
4136
4297
|
`));
|
|
4137
4298
|
}
|
|
4138
|
-
const commandsDir =
|
|
4299
|
+
const commandsDir = join5(this.config.getConfigDir(), CUSTOM_COMMANDS_DIR_NAME);
|
|
4139
4300
|
this.customCommandManager = new CustomCommandManager(commandsDir);
|
|
4140
4301
|
const customCmdCount = this.customCommandManager.loadCommands();
|
|
4141
4302
|
if (customCmdCount > 0) {
|
|
@@ -4318,8 +4479,17 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4318
4479
|
await this.handleChatSimple(provider, session.messages);
|
|
4319
4480
|
}
|
|
4320
4481
|
if (this.config.get("session").autoSave) {
|
|
4482
|
+
if (autoTrimSessionIfNeeded(session)) {
|
|
4483
|
+
process.stderr.write(theme.dim(" [session] Trimmed old tool output to reduce file size\n"));
|
|
4484
|
+
}
|
|
4321
4485
|
await this.sessions.save();
|
|
4322
4486
|
}
|
|
4487
|
+
this.costTracker.save();
|
|
4488
|
+
const budgetWarning = this.costTracker.checkBudget(this.config.get("monthlyBudget"));
|
|
4489
|
+
if (budgetWarning) {
|
|
4490
|
+
process.stdout.write(theme.warning(` ${budgetWarning}
|
|
4491
|
+
`));
|
|
4492
|
+
}
|
|
4323
4493
|
const elapsed = Date.now() - t0;
|
|
4324
4494
|
const threshold = this.config.get("ui").notificationThreshold;
|
|
4325
4495
|
if (threshold > 0 && elapsed >= threshold) {
|
|
@@ -4583,14 +4753,14 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4583
4753
|
const dir = normalized.includes("/") ? dirname3(normalized) : ".";
|
|
4584
4754
|
const prefix = normalized.includes("/") ? basename2(normalized) : normalized;
|
|
4585
4755
|
const absDir = resolve2(process.cwd(), dir);
|
|
4586
|
-
if (!
|
|
4756
|
+
if (!existsSync5(absDir)) return [];
|
|
4587
4757
|
const entries = readdirSync3(absDir);
|
|
4588
4758
|
const results = [];
|
|
4589
4759
|
for (const entry of entries) {
|
|
4590
4760
|
if (entry.startsWith(".")) continue;
|
|
4591
4761
|
if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
|
|
4592
4762
|
try {
|
|
4593
|
-
const fullPath =
|
|
4763
|
+
const fullPath = join5(absDir, entry);
|
|
4594
4764
|
const stat = statSync3(fullPath);
|
|
4595
4765
|
const rel = dir === "." ? entry : `${dir}/${entry}`;
|
|
4596
4766
|
results.push(stat.isDirectory() ? `${rel}/` : rel);
|
|
@@ -4862,6 +5032,8 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4862
5032
|
async handleChatWithTools(provider, messages) {
|
|
4863
5033
|
const session = this.sessions.current;
|
|
4864
5034
|
let toolDefs;
|
|
5035
|
+
let mcpBudgetNote = null;
|
|
5036
|
+
const usedMcpToolNames = /* @__PURE__ */ new Set();
|
|
4865
5037
|
if (this.planMode) {
|
|
4866
5038
|
toolDefs = this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name));
|
|
4867
5039
|
} else {
|
|
@@ -4869,7 +5041,19 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4869
5041
|
if (skillFilter) {
|
|
4870
5042
|
toolDefs = this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name));
|
|
4871
5043
|
} else {
|
|
4872
|
-
|
|
5044
|
+
const contextWindow = this.getContextWindowSize();
|
|
5045
|
+
if (contextWindow > 0) {
|
|
5046
|
+
const toolBudget = Math.floor(contextWindow * 0.2);
|
|
5047
|
+
const { definitions, trimmedCount, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget, usedMcpToolNames);
|
|
5048
|
+
toolDefs = definitions;
|
|
5049
|
+
mcpBudgetNote = systemNote;
|
|
5050
|
+
if (trimmedCount > 0) {
|
|
5051
|
+
process.stderr.write(theme.dim(` [MCP budget] ${trimmedCount} MCP tool(s) trimmed to fit ${toolBudget.toLocaleString()} token budget
|
|
5052
|
+
`));
|
|
5053
|
+
}
|
|
5054
|
+
} else {
|
|
5055
|
+
toolDefs = this.toolRegistry.getDefinitions();
|
|
5056
|
+
}
|
|
4873
5057
|
}
|
|
4874
5058
|
}
|
|
4875
5059
|
if (this.allowedTools) {
|
|
@@ -4904,7 +5088,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
4904
5088
|
- Do NOT read the same file more than once \u2014 use the content from previous reads.
|
|
4905
5089
|
- Prioritize the most critical tasks first in case rounds run out.
|
|
4906
5090
|
- When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
|
|
4907
|
-
const systemPrompt = baseSystemPrompt + roundBudgetHint
|
|
5091
|
+
const systemPrompt = baseSystemPrompt + roundBudgetHint + (mcpBudgetNote ? `
|
|
5092
|
+
|
|
5093
|
+
${mcpBudgetNote}` : "");
|
|
4908
5094
|
const modelParams = this.getModelParams();
|
|
4909
5095
|
const useStreaming = this.config.get("ui").streaming;
|
|
4910
5096
|
const spinner = this.renderer.showSpinner("Thinking...");
|
|
@@ -5270,6 +5456,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
5270
5456
|
const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
|
|
5271
5457
|
const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
|
|
5272
5458
|
extraMessages.push(...newMsgs);
|
|
5459
|
+
for (const tc of result.toolCalls) {
|
|
5460
|
+
if (tc.name.startsWith("mcp__")) usedMcpToolNames.add(tc.name);
|
|
5461
|
+
}
|
|
5273
5462
|
const streamedContent = "content" in result ? result.content : void 0;
|
|
5274
5463
|
persistToolRound(session, result.toolCalls, toolResults, {
|
|
5275
5464
|
assistantContent: streamedContent,
|
|
@@ -5606,6 +5795,7 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5606
5795
|
listContextDirs: () => [...this.extraContextDirs],
|
|
5607
5796
|
forkSession: (messageCount, title) => this.sessions.forkSession(messageCount, title),
|
|
5608
5797
|
getToolExecutor: () => this.toolExecutor,
|
|
5798
|
+
getCostTracker: () => this.costTracker,
|
|
5609
5799
|
exit: () => this.handleExit()
|
|
5610
5800
|
};
|
|
5611
5801
|
await cmd.execute(args, ctx);
|
|
@@ -5710,7 +5900,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5710
5900
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5711
5901
|
process.exit(1);
|
|
5712
5902
|
}
|
|
5713
|
-
const { startWebServer } = await import("./server-
|
|
5903
|
+
const { startWebServer } = await import("./server-BGMUORHT.js");
|
|
5714
5904
|
await startWebServer({ port, host: options.host });
|
|
5715
5905
|
});
|
|
5716
5906
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5943,7 +6133,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5943
6133
|
}),
|
|
5944
6134
|
config.get("customProviders")
|
|
5945
6135
|
);
|
|
5946
|
-
const { startHub } = await import("./hub-
|
|
6136
|
+
const { startHub } = await import("./hub-R6ID4F6J.js");
|
|
5947
6137
|
await startHub(
|
|
5948
6138
|
{
|
|
5949
6139
|
topic: topic ?? "",
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
SessionManager,
|
|
8
8
|
SkillManager,
|
|
9
9
|
TOOL_CALL_REMINDER,
|
|
10
|
+
autoTrimSessionIfNeeded,
|
|
10
11
|
computeCost,
|
|
11
12
|
detectsHallucinatedFileOp,
|
|
12
|
-
estimateTokens,
|
|
13
13
|
extractToolHistory,
|
|
14
14
|
formatCost,
|
|
15
15
|
formatGitContextForPrompt,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
persistToolRound,
|
|
22
22
|
rebuildExtraMessages,
|
|
23
23
|
setupProxy
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-EYRPTNVI.js";
|
|
25
25
|
import {
|
|
26
26
|
AuthManager
|
|
27
27
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
ToolRegistry,
|
|
31
31
|
askUserContext,
|
|
32
32
|
checkPermission,
|
|
33
|
+
estimateTokens,
|
|
33
34
|
getDangerLevel,
|
|
34
35
|
googleSearchContext,
|
|
35
36
|
isFileWriteTool,
|
|
@@ -40,7 +41,7 @@ import {
|
|
|
40
41
|
spawnAgentContext,
|
|
41
42
|
truncateOutput,
|
|
42
43
|
undoStack
|
|
43
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-E45EGVSY.js";
|
|
44
45
|
import "./chunk-4BKXL7SM.js";
|
|
45
46
|
import {
|
|
46
47
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
SKILLS_DIR_NAME,
|
|
61
62
|
VERSION,
|
|
62
63
|
buildUserIdentityPrompt
|
|
63
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-MLEM56CR.js";
|
|
64
65
|
|
|
65
66
|
// src/web/server.ts
|
|
66
67
|
import express from "express";
|
|
@@ -324,14 +325,19 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
async executeAll(calls) {
|
|
327
|
-
const
|
|
328
|
+
const safeParallel = [];
|
|
329
|
+
const safeBash = [];
|
|
328
330
|
const fileWriteCalls = [];
|
|
329
331
|
const otherCalls = [];
|
|
330
332
|
for (let i = 0; i < calls.length; i++) {
|
|
331
333
|
const call = calls[i];
|
|
332
334
|
const level = getDangerLevel(call.name, call.arguments);
|
|
333
335
|
if (level === "safe") {
|
|
334
|
-
|
|
336
|
+
if (call.name === "bash") {
|
|
337
|
+
safeBash.push({ idx: i, call });
|
|
338
|
+
} else {
|
|
339
|
+
safeParallel.push({ idx: i, call });
|
|
340
|
+
}
|
|
335
341
|
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
336
342
|
fileWriteCalls.push({ idx: i, call });
|
|
337
343
|
} else {
|
|
@@ -339,11 +345,15 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
339
345
|
}
|
|
340
346
|
}
|
|
341
347
|
const results = new Array(calls.length);
|
|
342
|
-
|
|
343
|
-
|
|
348
|
+
const parallelPhase = safeParallel.length > 0 ? Promise.all(safeParallel.map(async ({ idx, call }) => {
|
|
349
|
+
results[idx] = await this.execute(call);
|
|
350
|
+
})) : Promise.resolve();
|
|
351
|
+
const bashPhase = (async () => {
|
|
352
|
+
for (const { idx, call } of safeBash) {
|
|
344
353
|
results[idx] = await this.execute(call);
|
|
345
|
-
}
|
|
346
|
-
);
|
|
354
|
+
}
|
|
355
|
+
})();
|
|
356
|
+
await Promise.all([parallelPhase, bashPhase]);
|
|
347
357
|
if (fileWriteCalls.length === 1) {
|
|
348
358
|
const { idx, call } = fileWriteCalls[0];
|
|
349
359
|
results[idx] = await this.execute(call);
|
|
@@ -361,33 +371,39 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
361
371
|
async executeBatchFileWrites(items) {
|
|
362
372
|
const calls = items.map((i) => i.call);
|
|
363
373
|
const decision = this.sessionAutoApprove ? "all" : await this.batchConfirm(calls);
|
|
364
|
-
const results =
|
|
374
|
+
const results = new Array(calls.length);
|
|
375
|
+
const approvedIndices = [];
|
|
365
376
|
for (let i = 0; i < calls.length; i++) {
|
|
366
|
-
const call = calls[i];
|
|
367
377
|
if (decision === "all" || decision instanceof Set && decision.has(i + 1)) {
|
|
378
|
+
approvedIndices.push(i);
|
|
379
|
+
} else {
|
|
380
|
+
results[i] = {
|
|
381
|
+
callId: calls[i].id,
|
|
382
|
+
content: `[User rejected] File write rejected by user. Do not retry without asking.`,
|
|
383
|
+
isError: true
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
await Promise.all(
|
|
388
|
+
approvedIndices.map(async (i) => {
|
|
389
|
+
const call = calls[i];
|
|
368
390
|
const tool = this.registry.get(call.name);
|
|
369
391
|
if (!tool) {
|
|
370
|
-
results
|
|
371
|
-
|
|
392
|
+
results[i] = { callId: call.id, content: `Unknown tool: ${call.name}`, isError: true };
|
|
393
|
+
return;
|
|
372
394
|
}
|
|
373
395
|
try {
|
|
374
396
|
const rawContent = await tool.execute(call.arguments);
|
|
375
397
|
const content = truncateOutput(rawContent, call.name);
|
|
376
398
|
this.sendToolCallResult(call, rawContent, false);
|
|
377
|
-
results
|
|
399
|
+
results[i] = { callId: call.id, content, isError: false };
|
|
378
400
|
} catch (err) {
|
|
379
401
|
const message = err instanceof Error ? err.message : String(err);
|
|
380
402
|
this.sendToolCallResult(call, message, true);
|
|
381
|
-
results
|
|
403
|
+
results[i] = { callId: call.id, content: message, isError: true };
|
|
382
404
|
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
callId: call.id,
|
|
386
|
-
content: `[User rejected] File write rejected by user. Do not retry without asking.`,
|
|
387
|
-
isError: true
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
}
|
|
405
|
+
})
|
|
406
|
+
);
|
|
391
407
|
return results;
|
|
392
408
|
}
|
|
393
409
|
};
|
|
@@ -697,6 +713,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
697
713
|
/** Save session only if it exists and has messages (never persist empty "Untitled" sessions). */
|
|
698
714
|
saveIfNeeded() {
|
|
699
715
|
if (this.sessions.current && this.sessions.current.messages.length > 0) {
|
|
716
|
+
autoTrimSessionIfNeeded(this.sessions.current);
|
|
700
717
|
const id = this.sessions.current.id;
|
|
701
718
|
this.sessions.save();
|
|
702
719
|
this.unsavedSessions.delete(id);
|
|
@@ -754,9 +771,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
754
771
|
return;
|
|
755
772
|
}
|
|
756
773
|
const hasToolSupport = typeof provider.chatWithTools === "function";
|
|
757
|
-
const toolDefs = hasToolSupport ? this.getFilteredToolDefs() : [];
|
|
774
|
+
const { toolDefs, mcpBudgetNote } = hasToolSupport ? this.getFilteredToolDefs() : { toolDefs: [], mcpBudgetNote: null };
|
|
758
775
|
if (hasToolSupport && toolDefs.length > 0) {
|
|
759
|
-
await this.handleChatWithTools(provider, session.messages, toolDefs);
|
|
776
|
+
await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote);
|
|
760
777
|
} else {
|
|
761
778
|
await this.handleChatSimple(provider, session.messages);
|
|
762
779
|
}
|
|
@@ -821,7 +838,7 @@ var SessionHandler = class _SessionHandler {
|
|
|
821
838
|
this.abortController = null;
|
|
822
839
|
}
|
|
823
840
|
}
|
|
824
|
-
async handleChatWithTools(provider, messages, toolDefs) {
|
|
841
|
+
async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
|
|
825
842
|
const session = this.sessions.current;
|
|
826
843
|
const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
|
|
827
844
|
const apiMessages = [...cleanMessages];
|
|
@@ -839,7 +856,9 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
839
856
|
- Prefer batch operations (e.g. global find-and-replace) over repetitive single edits.
|
|
840
857
|
- Prioritize the most critical tasks first in case rounds run out.
|
|
841
858
|
- When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
|
|
842
|
-
const systemPrompt = baseSystemPrompt + roundBudgetHint
|
|
859
|
+
const systemPrompt = baseSystemPrompt + roundBudgetHint + (mcpBudgetNote ? `
|
|
860
|
+
|
|
861
|
+
${mcpBudgetNote}` : "");
|
|
843
862
|
const modelParams = this.getModelParams();
|
|
844
863
|
const roundUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
|
|
845
864
|
const supportsStreamingTools = typeof provider.chatWithToolsStream === "function";
|
|
@@ -1323,6 +1342,17 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
1323
1342
|
this.sessions.loadSession(found.id);
|
|
1324
1343
|
this.resetWebSessionUsage();
|
|
1325
1344
|
this.send({ type: "info", message: `Loaded session: ${found.id.slice(0, 8)} "${found.title ?? ""}" (${found.messageCount} messages)` });
|
|
1345
|
+
const loadedSession = this.sessions.current;
|
|
1346
|
+
if (loadedSession && loadedSession.messages.length > 0) {
|
|
1347
|
+
const lastMsg = loadedSession.messages[loadedSession.messages.length - 1];
|
|
1348
|
+
const isIncomplete = lastMsg.role === "tool" || lastMsg.role === "assistant" && lastMsg.toolCalls && lastMsg.toolCalls.length > 0;
|
|
1349
|
+
if (isIncomplete) {
|
|
1350
|
+
this.send({
|
|
1351
|
+
type: "info",
|
|
1352
|
+
message: '\u26A0 This session appears to have been interrupted mid-task. The AI will see the tool history and can continue where it left off. Type "continue where you left off" to resume.'
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1326
1356
|
this.sendSessionMessages();
|
|
1327
1357
|
this.sendStatus();
|
|
1328
1358
|
this.sendSessionList();
|
|
@@ -1929,7 +1959,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1929
1959
|
case "test": {
|
|
1930
1960
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1931
1961
|
try {
|
|
1932
|
-
const { executeTests } = await import("./run-tests-
|
|
1962
|
+
const { executeTests } = await import("./run-tests-KRGPFYPF.js");
|
|
1933
1963
|
const argStr = args.join(" ").trim();
|
|
1934
1964
|
let testArgs = {};
|
|
1935
1965
|
if (argStr) {
|
|
@@ -2517,16 +2547,26 @@ Add .md files to create commands.` });
|
|
|
2517
2547
|
};
|
|
2518
2548
|
}
|
|
2519
2549
|
getFilteredToolDefs() {
|
|
2520
|
-
let defs = this.toolRegistry.getDefinitions();
|
|
2521
2550
|
if (this.planMode) {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2551
|
+
return {
|
|
2552
|
+
toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name)),
|
|
2553
|
+
mcpBudgetNote: null
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
const skillFilter = this.skillManager?.getActiveToolFilter();
|
|
2557
|
+
if (skillFilter) {
|
|
2558
|
+
return {
|
|
2559
|
+
toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name)),
|
|
2560
|
+
mcpBudgetNote: null
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
const contextWindow = this.getContextWindowSize();
|
|
2564
|
+
if (contextWindow > 0) {
|
|
2565
|
+
const toolBudget = Math.floor(contextWindow * 0.2);
|
|
2566
|
+
const { definitions, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget);
|
|
2567
|
+
return { toolDefs: definitions, mcpBudgetNote: systemNote };
|
|
2528
2568
|
}
|
|
2529
|
-
return
|
|
2569
|
+
return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
|
|
2530
2570
|
}
|
|
2531
2571
|
/**
|
|
2532
2572
|
* 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-E45EGVSY.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import {
|
|
10
10
|
SUBAGENT_ALLOWED_TOOLS
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MLEM56CR.js";
|
|
12
12
|
|
|
13
13
|
// src/hub/task-orchestrator.ts
|
|
14
14
|
import { createInterface } from "readline";
|