@huicai/cli 2026.710.97949 → 2026.710.97950
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/package.json +2 -2
- package/src/agent.js +30 -10
- package/src/subagent.js +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huicai/cli",
|
|
3
|
-
"version": "2026.710.
|
|
4
|
-
"versionDisplay": "2026.07.10.
|
|
3
|
+
"version": "2026.710.97950",
|
|
4
|
+
"versionDisplay": "2026.07.10.97950",
|
|
5
5
|
"description": "在终端运行的智能体 CLI:以 HuiCai_AI 平台的 OpenAI 兼容 /v1 为模型后端,在用户本机执行文件/命令工具。",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/src/agent.js
CHANGED
|
@@ -29,7 +29,8 @@ import { makeMemoryTool } from './memory.js';
|
|
|
29
29
|
import * as ide from './ide.js';
|
|
30
30
|
import { c } from './colors.js';
|
|
31
31
|
|
|
32
|
-
const DEFAULT_MAX_ITERS =
|
|
32
|
+
const DEFAULT_MAX_ITERS = 120; // 中大型项目需更多工具轮(读多文件/多次检索/多处编辑);仍可 config.maxTurns 或 --max-turns 覆盖
|
|
33
|
+
const TOOL_CONCURRENCY = 8; // 并行只读工具的并发上限:大仓库里模型一次触发大量 grep/glob 时限并发,降争用、提稳定与总吞吐
|
|
33
34
|
const MAX_TOOL_RESULT_CHARS = 20_000;
|
|
34
35
|
const CAP_HEAD = 16_000;
|
|
35
36
|
const CAP_TAIL = 3_000;
|
|
@@ -43,6 +44,23 @@ const MARKUP_RETRY_PROMPT =
|
|
|
43
44
|
const MARKUP_FORCE_ANSWER_PROMPT =
|
|
44
45
|
'请停止调用任何工具。基于目前已获得的全部信息,直接用中文输出面向用户的完整最终答复,'
|
|
45
46
|
+ '不要输出任何工具调用或协议标记。';
|
|
47
|
+
// 预算收敛:临近工具轮数上限时注入,禁用工具并要求直接作答,保证中大型项目里到达上限也能给出干净的最终答复(而非被硬截断)。
|
|
48
|
+
const BUDGET_FINAL_PROMPT =
|
|
49
|
+
'已接近本回合的工具调用轮数上限。请不要再调用任何工具,基于目前已获得的信息,直接用中文输出面向用户的完整最终答复。';
|
|
50
|
+
|
|
51
|
+
/** 有序的有限并发 map:并发不超过 limit,结果按输入下标回填(与 Promise.all 相同的顺序语义,但并发受限)。 */
|
|
52
|
+
async function mapLimit(items, limit, fn) {
|
|
53
|
+
const results = new Array(items.length);
|
|
54
|
+
let next = 0;
|
|
55
|
+
const worker = async () => {
|
|
56
|
+
while (next < items.length) {
|
|
57
|
+
const i = next++;
|
|
58
|
+
results[i] = await fn(items[i], i);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
46
64
|
|
|
47
65
|
/** 伪模型(平台编排)判定:agent-/workflow-/datahuman- 前缀。 */
|
|
48
66
|
export function isPlatformModel(model) {
|
|
@@ -137,6 +155,9 @@ export async function runTurn({
|
|
|
137
155
|
let correction = null; // 待注入的纠正指令:仅随本轮请求发给模型,不进会话记录(与平台侧临时 convo 等价)
|
|
138
156
|
|
|
139
157
|
for (let iter = 0; iter < iterMax; iter++) {
|
|
158
|
+
// 预算收敛:最后一轮禁用工具并提示直接作答,保证中大型项目里到达上限也能给出干净的最终答复(而非被硬截断)
|
|
159
|
+
const lastIter = iter === iterMax - 1;
|
|
160
|
+
if (lastIter && !platformMode && !toolsDisabled && !correction) correction = BUDGET_FINAL_PROMPT;
|
|
140
161
|
// 非破坏式上下文压缩:仅更新 compState(摘要独立存储),messages/页面内容不动
|
|
141
162
|
const compacted = await compactIfNeeded(messages, config, { state: compState, lastPromptTokens });
|
|
142
163
|
if (compacted > 0) info(`已压缩上下文(${compacted} 条较早消息已摘要,页面与会话记录不受影响)`);
|
|
@@ -160,7 +181,7 @@ export async function runTurn({
|
|
|
160
181
|
apiKey: config.apiKey,
|
|
161
182
|
model: config.model,
|
|
162
183
|
messages: correction ? [...modelMessages, { role: 'user', content: correction }] : modelMessages,
|
|
163
|
-
tools: toolsDisabled ? undefined : specs,
|
|
184
|
+
tools: (toolsDisabled || lastIter) ? undefined : specs,
|
|
164
185
|
// 对话落库审计:来源与会话号(由 config 透传;扩展侧为 vscode)
|
|
165
186
|
client: config.client,
|
|
166
187
|
sessionId: config.sessionId,
|
|
@@ -319,14 +340,13 @@ export async function runTurn({
|
|
|
319
340
|
let j = i;
|
|
320
341
|
while (j < parsed.length && parsed[j].readOnly && !parsed[j].argsError) j++;
|
|
321
342
|
const batch = parsed.slice(i, j);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
);
|
|
343
|
+
// 只读段并行执行,但并发受限(大仓库里模型可能一次触发很多 grep/glob,无限并发会抢占磁盘/CPU 反而更慢);结果仍按原顺序回灌
|
|
344
|
+
const settled = await mapLimit(batch, TOOL_CONCURRENCY, async (item) => {
|
|
345
|
+
const perm = await permissions.request(item.name, item.args, cwd, true);
|
|
346
|
+
if (!perm.allow) return { item, denied: perm.reason || '未授权,未执行。' };
|
|
347
|
+
emitStart(item);
|
|
348
|
+
return { item, result: await execOne(item) };
|
|
349
|
+
});
|
|
330
350
|
for (const s of settled) {
|
|
331
351
|
if (s.denied != null) emitSkip(s.item, s.denied);
|
|
332
352
|
else emitResult(s.item, s.result);
|
package/src/subagent.js
CHANGED
|
@@ -61,6 +61,7 @@ export function makeSubagentTool({ config, cwd, extraTools = [], depth = 0 }) {
|
|
|
61
61
|
cwd,
|
|
62
62
|
extraTools: subExtra,
|
|
63
63
|
depth: depth + 1,
|
|
64
|
+
maxIters: 50, // 子智能体是聚焦的只读子任务:显式收敛轮数,不随顶层上限(120)放大,控成本与时延
|
|
64
65
|
io: { write: () => {} }, // 静默:子智能体过程不刷主终端
|
|
65
66
|
});
|
|
66
67
|
if (r?.error) return `子智能体执行失败:${r.error}`;
|