@bolloon/bolloon-agent 0.5.4 → 0.5.5
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/agents/agent-identity-store.js +41 -5
- package/dist/agents/background-notices.js +124 -0
- package/dist/agents/channel-identity.js +54 -0
- package/dist/agents/code-write-gate.js +47 -0
- package/dist/agents/deny-pipeline.js +11 -2
- package/dist/agents/egress-sanitize.js +69 -0
- package/dist/agents/error-classifier.js +67 -5
- package/dist/agents/experience-review.js +226 -0
- package/dist/agents/gateway-group.js +153 -18
- package/dist/agents/iteration-budget.js +64 -0
- package/dist/agents/lesson-to-skill.js +211 -0
- package/dist/agents/managed-services.js +64 -0
- package/dist/agents/parse-tool-call.js +38 -32
- package/dist/agents/pi-sdk-session-manager.js +72 -3
- package/dist/agents/pi-sdk-tools.js +201 -32
- package/dist/agents/pi-sdk.js +463 -21
- package/dist/agents/plan-store.js +36 -0
- package/dist/agents/process-runner.js +64 -2
- package/dist/agents/shell-guard.js +109 -13
- package/dist/agents/skill-health.js +90 -0
- package/dist/agents/skill-ledger.js +129 -0
- package/dist/agents/tool-loop-guard.js +303 -0
- package/dist/agents/tool-path-args.js +53 -0
- package/dist/agents/tool-result-gate.js +50 -0
- package/dist/agents/tool-subset.js +109 -0
- package/dist/agents/tool-telemetry.js +97 -0
- package/dist/bootstrap/context-collector.js +37 -7
- package/dist/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/bootstrap/persona-init.js +174 -0
- package/dist/bootstrap/persona-loader.js +5 -1
- package/dist/cli/ink-app.js +153 -6
- package/dist/cli/input-paste.js +218 -0
- package/dist/cli/loading-tui.js +3 -1
- package/dist/cli/log-gate.js +7 -0
- package/dist/cli/mention-data.js +30 -0
- package/dist/cli/reasoning-view.js +103 -0
- package/dist/cli/startup-notice.js +25 -0
- package/dist/cli/status-segments.js +87 -2
- package/dist/cli/trace-line.js +54 -0
- package/dist/context-compaction/auto-compact.js +37 -2
- package/dist/electron-build/agents/agent-identity-store.js +43 -5
- package/dist/electron-build/agents/agent-identity-store.js.map +1 -1
- package/dist/electron-build/agents/background-notices.js +138 -0
- package/dist/electron-build/agents/background-notices.js.map +1 -0
- package/dist/electron-build/agents/channel-identity.js +64 -0
- package/dist/electron-build/agents/channel-identity.js.map +1 -0
- package/dist/electron-build/agents/code-write-gate.js +55 -0
- package/dist/electron-build/agents/code-write-gate.js.map +1 -0
- package/dist/electron-build/agents/deny-pipeline.js +11 -2
- package/dist/electron-build/agents/deny-pipeline.js.map +1 -1
- package/dist/electron-build/agents/egress-sanitize.js +76 -0
- package/dist/electron-build/agents/egress-sanitize.js.map +1 -0
- package/dist/electron-build/agents/error-classifier.js +68 -5
- package/dist/electron-build/agents/error-classifier.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +243 -0
- package/dist/electron-build/agents/experience-review.js.map +1 -0
- package/dist/electron-build/agents/iteration-budget.js +73 -0
- package/dist/electron-build/agents/iteration-budget.js.map +1 -0
- package/dist/electron-build/agents/lesson-to-skill.js +228 -0
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -0
- package/dist/electron-build/agents/managed-services.js +69 -0
- package/dist/electron-build/agents/managed-services.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +38 -32
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +75 -3
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +200 -31
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +464 -21
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/plan-store.js +37 -0
- package/dist/electron-build/agents/plan-store.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +71 -2
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/shell-guard.js +110 -14
- package/dist/electron-build/agents/shell-guard.js.map +1 -1
- package/dist/electron-build/agents/skill-health.js +103 -0
- package/dist/electron-build/agents/skill-health.js.map +1 -0
- package/dist/electron-build/agents/skill-ledger.js +146 -0
- package/dist/electron-build/agents/skill-ledger.js.map +1 -0
- package/dist/electron-build/agents/tool-loop-guard.js +319 -0
- package/dist/electron-build/agents/tool-loop-guard.js.map +1 -0
- package/dist/electron-build/agents/tool-path-args.js +63 -0
- package/dist/electron-build/agents/tool-path-args.js.map +1 -0
- package/dist/electron-build/agents/tool-result-gate.js +59 -0
- package/dist/electron-build/agents/tool-result-gate.js.map +1 -0
- package/dist/electron-build/agents/tool-subset.js +116 -0
- package/dist/electron-build/agents/tool-subset.js.map +1 -0
- package/dist/electron-build/agents/tool-telemetry.js +108 -0
- package/dist/electron-build/agents/tool-telemetry.js.map +1 -0
- package/dist/electron-build/bootstrap/context-collector.js +38 -7
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/lifecycle-hooks.js +12 -3
- package/dist/electron-build/bootstrap/lifecycle-hooks.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-init.js +216 -0
- package/dist/electron-build/bootstrap/persona-init.js.map +1 -0
- package/dist/electron-build/bootstrap/persona-loader.js +5 -1
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/context-compaction/auto-compact.js +38 -2
- package/dist/electron-build/context-compaction/auto-compact.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js +22 -0
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/orbitdb/cid-database.js +55 -0
- package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
- package/dist/electron-build/orbitdb/ipfs-node.js +36 -11
- package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
- package/dist/electron-build/web/server-storage.js +26 -5
- package/dist/electron-build/web/server-storage.js.map +1 -1
- package/dist/external-engines/delegate.js +21 -0
- package/dist/index.js +503 -77
- package/dist/orbitdb/cid-database.js +55 -0
- package/dist/orbitdb/event-index.js +370 -0
- package/dist/orbitdb/event-query.js +131 -0
- package/dist/orbitdb/event-shell.js +510 -0
- package/dist/orbitdb/group-access.js +381 -0
- package/dist/orbitdb/group-shards.js +140 -0
- package/dist/orbitdb/ipfs-node.js +36 -11
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +3 -2
- package/dist/web/mobile-core.js +7762 -6289
- package/dist/web/routes-mobile-tasks.js +2 -1
- package/dist/web/server-storage.js +26 -5
- package/dist/web/server.js +2 -0
- package/dist/web/sw.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runWithWriteOrigin = runWithWriteOrigin;
|
|
7
|
+
exports.currentWriteOrigin = currentWriteOrigin;
|
|
8
|
+
exports.isReviewWrite = isReviewWrite;
|
|
9
|
+
exports.ledgerPath = ledgerPath;
|
|
10
|
+
exports.blobsDir = blobsDir;
|
|
11
|
+
exports.captureSkillBefore = captureSkillBefore;
|
|
12
|
+
exports.appendLedger = appendLedger;
|
|
13
|
+
exports.readLedger = readLedger;
|
|
14
|
+
exports.isAgentCreated = isAgentCreated;
|
|
15
|
+
exports.reviewMayTouch = reviewMayTouch;
|
|
16
|
+
exports.rollbackEntry = rollbackEntry;
|
|
17
|
+
/**
|
|
18
|
+
* 技能账本 + 单条回滚 + 写来源隔离 (2026-10-01, 按"账本而非闸门"的形态做)。
|
|
19
|
+
*
|
|
20
|
+
* 三件事:
|
|
21
|
+
* ① **账本**: 每次技能变更追加一条 JSONL(前后清单 + 内容寻址备份)⇒ 可单条回滚 ✓。
|
|
22
|
+
* 放 JSONL 不放数据库: 耐久 · 可 grep · 数据库重置也不丢 ✓。
|
|
23
|
+
* ② **单条回滚**: 按 id 还原那一次变更; **唯独回滚是 fail-closed** ✓(安全快照拿不到就拒绝回滚,
|
|
24
|
+
* 绝不"半还原"✗); 其余全部吞错只记日志(账本坏掉不该挡住干活 ✓)。
|
|
25
|
+
* ③ **写来源隔离**: 用 AsyncLocalStorage 区分"用户点名要的写"(foreground ✓)与
|
|
26
|
+
* "自审/复盘产生的写"(review ✓)⇒ 自审**只许治理自己造出来的技能** ✓, 用户要的归用户 ✓。
|
|
27
|
+
*/
|
|
28
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
29
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
30
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
31
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
32
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
33
|
+
const originStore = new node_async_hooks_1.AsyncLocalStorage();
|
|
34
|
+
/** 在"自审"上下文里跑一段(其余一律按 foreground ✓) */
|
|
35
|
+
function runWithWriteOrigin(origin, fn) {
|
|
36
|
+
return originStore.run({ origin }, fn);
|
|
37
|
+
}
|
|
38
|
+
function currentWriteOrigin() {
|
|
39
|
+
return originStore.getStore()?.origin ?? 'foreground';
|
|
40
|
+
}
|
|
41
|
+
function isReviewWrite() {
|
|
42
|
+
return currentWriteOrigin() === 'review';
|
|
43
|
+
}
|
|
44
|
+
function ledgerPath(home = node_os_1.default.homedir()) {
|
|
45
|
+
return node_path_1.default.join(home, '.bolloon', 'skills', '.skill-ledger.jsonl');
|
|
46
|
+
}
|
|
47
|
+
function blobsDir(home = node_os_1.default.homedir()) {
|
|
48
|
+
return node_path_1.default.join(home, '.bolloon', 'skills', '.ledger-blobs');
|
|
49
|
+
}
|
|
50
|
+
function sha256(text) {
|
|
51
|
+
return node_crypto_1.default.createHash('sha256').update(text, 'utf-8').digest('hex');
|
|
52
|
+
}
|
|
53
|
+
/** 把内容按哈希存进 blobs(已存在则复用 ✓ 天然去重) */
|
|
54
|
+
function putBlob(text, home) {
|
|
55
|
+
const h = sha256(text);
|
|
56
|
+
const p = node_path_1.default.join(blobsDir(home), h);
|
|
57
|
+
try {
|
|
58
|
+
if (!node_fs_1.default.existsSync(p)) {
|
|
59
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(p), { recursive: true });
|
|
60
|
+
node_fs_1.default.writeFileSync(p, text, { mode: 0o600 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch { /* 备份失败 ⇒ 由调用方决定是否允许这次变更(见 captureBefore) */ }
|
|
64
|
+
return h;
|
|
65
|
+
}
|
|
66
|
+
/** 变更前快照: 返回 beforeSha(拿不到安全快照时返回 undefined ⇒ 回滚能力缺失, 调用方须知道 ✓) */
|
|
67
|
+
function captureSkillBefore(file, home) {
|
|
68
|
+
try {
|
|
69
|
+
const text = node_fs_1.default.readFileSync(file, 'utf-8');
|
|
70
|
+
return { beforeSha: putBlob(text, home), bytesBefore: text.length };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {}; // 文件不存在(新建)= 正常; 读不到 = 快照缺失
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** 追加一条账本(吞错只记日志 ✓ —— 账本坏掉不该挡住干活) */
|
|
77
|
+
function appendLedger(entry, home) {
|
|
78
|
+
const full = { id: entry.id || node_crypto_1.default.randomBytes(8).toString('hex'), at: entry.at ?? Date.now(), ...entry };
|
|
79
|
+
try {
|
|
80
|
+
const p = ledgerPath(home);
|
|
81
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(p), { recursive: true });
|
|
82
|
+
node_fs_1.default.appendFileSync(p, JSON.stringify(full) + '\n', { mode: 0o600 });
|
|
83
|
+
}
|
|
84
|
+
catch { /* telemetry, not a gate */ }
|
|
85
|
+
return full;
|
|
86
|
+
}
|
|
87
|
+
function readLedger(home) {
|
|
88
|
+
try {
|
|
89
|
+
const raw = node_fs_1.default.readFileSync(ledgerPath(home), 'utf-8');
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const line of raw.split('\n')) {
|
|
92
|
+
if (!line.trim())
|
|
93
|
+
continue;
|
|
94
|
+
try {
|
|
95
|
+
out.push(JSON.parse(line));
|
|
96
|
+
}
|
|
97
|
+
catch { /* 坏行跳过 */ }
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** 这个技能是不是"自审造出来的"?(决定自审有没有资格改它 ✓) */
|
|
106
|
+
function isAgentCreated(skill, home) {
|
|
107
|
+
return readLedger(home).some((e) => e.skill === skill && e.tool === 'create_skill' && e.origin === 'review');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 自审写前的资格检查(只读判断, 不改任何东西 ✓):
|
|
111
|
+
* 自审只许动**自己造出来**的技能 ✓; 用户点名要的归用户 ✓。
|
|
112
|
+
*/
|
|
113
|
+
function reviewMayTouch(skill, home) {
|
|
114
|
+
if (!isReviewWrite())
|
|
115
|
+
return { ok: true, reason: 'foreground(用户点名要的写)' };
|
|
116
|
+
return isAgentCreated(skill, home)
|
|
117
|
+
? { ok: true, reason: 'review(该技能由自审创建, 可治理)' }
|
|
118
|
+
: { ok: false, reason: '该技能不是自审创建的(用户点名要的归用户)⇒ 自审只记候选, 不动它' };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 单条回滚: 把某次变更前的快照还原回去 ✓。
|
|
122
|
+
* **fail-closed**: 快照缺失/内容哈希对不上 ⇒ 拒绝(绝不半还原 ✗)。
|
|
123
|
+
*/
|
|
124
|
+
function rollbackEntry(id, home) {
|
|
125
|
+
const entry = readLedger(home).find((e) => e.id === id);
|
|
126
|
+
if (!entry)
|
|
127
|
+
return { ok: false, reason: `账本里没有 id=${id}` };
|
|
128
|
+
if (entry.tool === 'create_skill') {
|
|
129
|
+
// 创建类变更: 回滚 = 删掉那个文件(要用户明确同意 ⇒ 这里只报告, 不擅自删 ✗)
|
|
130
|
+
return { ok: false, reason: '该条是"新建技能"; 回滚等于删除文件 —— 需要你明确同意, 我不擅自删' };
|
|
131
|
+
}
|
|
132
|
+
if (!entry.beforeSha)
|
|
133
|
+
return { ok: false, reason: '该条没有变更前快照 ⇒ 拒绝回滚(fail-closed, 不做半还原)' };
|
|
134
|
+
try {
|
|
135
|
+
const blob = node_fs_1.default.readFileSync(node_path_1.default.join(blobsDir(home), entry.beforeSha), 'utf-8');
|
|
136
|
+
if (sha256(blob) !== entry.beforeSha)
|
|
137
|
+
return { ok: false, reason: '快照内容与哈希不符 ⇒ 拒绝回滚' };
|
|
138
|
+
node_fs_1.default.writeFileSync(entry.file, blob, { mode: 0o600 });
|
|
139
|
+
appendLedger({ origin: 'foreground', tool: 'rollback', skill: entry.skill, file: entry.file, beforeSha: entry.afterSha, afterSha: entry.beforeSha, note: `回到 ${entry.id}` }, home);
|
|
140
|
+
return { ok: true, reason: `已回滚到变更前(${entry.id})`, restored: entry.file };
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return { ok: false, reason: `回滚失败: ${String(e?.message || e).slice(0, 120)}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=skill-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-ledger.js","sourceRoot":"","sources":["../../../src/agents/skill-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_THRESHOLDS = exports.LoopStallState = exports.STUB_ARGS_PREVIEW_CHARS = exports.REPEAT_STUB_MIN_CHARS = exports.STUB_MIN_CHARS = exports.CYCLE_HISTORY = exports.MAX_CYCLE_PERIOD = exports.IDENTICAL_CALL_THRESHOLD = exports.FAILURE_TOLERANT_TOOLS = exports.REPEATABLE_SUFFIXES = exports.REPEATABLE_TOOLS = exports.MUTATING_TOOLS = exports.IDEMPOTENT_TOOLS = exports.CONSTANT_RESULT_TOOLS = void 0;
|
|
4
|
+
exports.canonicalToolArgs = canonicalToolArgs;
|
|
5
|
+
exports.toolCallSignature = toolCallSignature;
|
|
6
|
+
exports.resultFingerprint = resultFingerprint;
|
|
7
|
+
exports.isRepeatableTool = isRepeatableTool;
|
|
8
|
+
exports.observeToolCall = observeToolCall;
|
|
9
|
+
exports.detectCycle = detectCycle;
|
|
10
|
+
exports.toolMayHaveSideEffect = toolMayHaveSideEffect;
|
|
11
|
+
exports.defaultHardStopFor = defaultHardStopFor;
|
|
12
|
+
exports.newProgressBookkeeping = newProgressBookkeeping;
|
|
13
|
+
exports.trackProgress = trackProgress;
|
|
14
|
+
exports.batchHint = batchHint;
|
|
15
|
+
/**
|
|
16
|
+
* tool-loop-guard.ts — 工具调用的**停滞观测与温和引导** (2026-10-01)
|
|
17
|
+
*
|
|
18
|
+
* 为什么需要它 (用户原话: 「一个简单的任务居然花这么多次工具调用」):
|
|
19
|
+
* 原先的防重只有一条「同工具连续 5 次 ⇒ 注入一条提示, 然后重置计数」。实测模型无视提示后
|
|
20
|
+
* 完全没刹车 —— 同一个 get_identity 连调 10 次 ✗; 而且**只看工具名**, 连"同工具换参数的正常
|
|
21
|
+
* 迭代"(逐文件读)也会被误判。
|
|
22
|
+
* 反过来"硬停"也不行 (用户明确否决): 那是在症状上下手, 而且会堵掉正当的重复轮询。
|
|
23
|
+
*
|
|
24
|
+
* 所以这里采用**分类 + 温和合成**的口径:
|
|
25
|
+
* ① 工具先分类: 幂等读 / 有副作用 / 可重复轮询 (含命名约定 `_poll` / `_get_result`);
|
|
26
|
+
* ② 判据看**签名** (工具名 + 参数的规范 JSON 的哈希), 不只看工具名 ⇒ 换参数不误伤;
|
|
27
|
+
* ③ 命中停滞**不拒绝执行**, 而是产出一条**引导文本**追加在真结果后面 (保留模型的选择权);
|
|
28
|
+
* ④ 完全相同的**结果**从第 2 次起用**引用 stub** 替代 (省上下文; 且明确告诉模型"与上次相同");
|
|
29
|
+
* ⑤ 抓**循环** (A→B→A→B…, 周期 ≤4): 相邻重复计数会被交替调用清零, 只抓相邻的会漏;
|
|
30
|
+
* ⑥ 失败宽容类工具 (跑测试红了 / grep 空 / 页面超时) 的"失败"是正常工作输出, 不参与停滞判定。
|
|
31
|
+
*
|
|
32
|
+
* 纯逻辑, 不碰 IO / 不发请求 —— 便于单测与变异验证。
|
|
33
|
+
*/
|
|
34
|
+
const node_crypto_1 = require("node:crypto");
|
|
35
|
+
/** 幂等只读类: 同参数重复调用不会产生新信息 (但允许换参数继续用) */
|
|
36
|
+
/**
|
|
37
|
+
* **结果恒定**的工具 (2026-10-01): 同一会话内反复调, 返回**永远一样**, 且答案本身很短。
|
|
38
|
+
* 用户实测: `get_identity` 连调 3–4 次 ✗ —— 原有"引用 stub"有字符数门槛, 而它的输出才 ~90 字符
|
|
39
|
+
* ⇒ 门槛拦不住 ⇒ 模型看不出"再问也一样"。
|
|
40
|
+
* 规矩 (工具设置层, 不做硬刹车): 从**第 2 次**完全相同的调用起, 直接把结果换成一句标准答复,
|
|
41
|
+
* 明确告诉模型"答案就在上面那次返回里, 再调不会变" ⇒ 去掉它重复调的理由。
|
|
42
|
+
*/
|
|
43
|
+
exports.CONSTANT_RESULT_TOOLS = new Set([
|
|
44
|
+
'get_identity', 'bolloon_config_get', 'get_agent_info', 'whoami',
|
|
45
|
+
]);
|
|
46
|
+
exports.IDEMPOTENT_TOOLS = new Set([
|
|
47
|
+
'read_file', 'list_files', 'search_files', 'get_identity', 'get_operation_logs',
|
|
48
|
+
'list_remote_channels', 'list_context_layers', 'read_context_assets', 'get_balance', 'wallet_list',
|
|
49
|
+
'web_search', 'web_extract', 'check_inbox',
|
|
50
|
+
]);
|
|
51
|
+
/** 有副作用类: 重复调用可能是正当的 (例如再跑一条命令), 由"同签名同结果"判定停滞 */
|
|
52
|
+
exports.MUTATING_TOOLS = new Set([
|
|
53
|
+
'terminal', 'shell_exec', 'execute_code', 'write_file', 'patch', 'set_persona', 'memory',
|
|
54
|
+
'todo_list', 'send_message', 'send_to_remote_channel', 'delegate_task', 'create_group', 'set_wallet',
|
|
55
|
+
]);
|
|
56
|
+
/** 轮询类: 同参数重复**本来就正当** (等异步任务), 永不触发相同调用提示 */
|
|
57
|
+
exports.REPEATABLE_TOOLS = new Set(['process_manage', 'check_task_status']);
|
|
58
|
+
/** 生成/MCP 轮询工具的命名约定 (与上面同理) */
|
|
59
|
+
exports.REPEATABLE_SUFFIXES = ['_poll', '_get_result'];
|
|
60
|
+
/** 失败的"正常输出"类: 红了/空结果/超时属于工作产出, 不参与停滞判定 */
|
|
61
|
+
exports.FAILURE_TOLERANT_TOOLS = new Set(['terminal', 'shell_exec', 'execute_code', 'web_search', 'web_extract']);
|
|
62
|
+
/** 第几次**连续相同 (工具+参数+结果)** 触发引导 (3 = 容忍一次复核) */
|
|
63
|
+
exports.IDENTICAL_CALL_THRESHOLD = 3;
|
|
64
|
+
/** 抓循环时最长的周期 (A→B→A→B 的周期是 2) */
|
|
65
|
+
exports.MAX_CYCLE_PERIOD = 4;
|
|
66
|
+
/** 历史窗口: 够放最长的周期若干圈 + 余量 */
|
|
67
|
+
exports.CYCLE_HISTORY = 64;
|
|
68
|
+
/** 结果达到这个字符数才值得用 stub 替代 (太短本来就省不了多少) */
|
|
69
|
+
exports.STUB_MIN_CHARS = 512;
|
|
70
|
+
/**
|
|
71
|
+
* **同签名 + 同结果**的重复调用: 门槛降到这个值 (2026-10-01, 优化 #1 的收尾)。
|
|
72
|
+
* 为什么单独一条: 512 是"值不值得压缩"的门槛 ✗, 而"同一个调用又跑了一遍、结果一字不差"**本身就是浪费** ✓
|
|
73
|
+
* —— 实测 `get_identity` 输出才 ~90 字符 ⇒ 卡在 512 门外, 于是被连调 3–4 次 ✓。
|
|
74
|
+
* 现在: 同签名重复 且 结果一致 且 ≥64 字符 ⇒ 从第 2 次起换引用 stub ✓ (少于 64 字符的本来也占不了多少上下文, 放行 ✓)。
|
|
75
|
+
*/
|
|
76
|
+
exports.REPEAT_STUB_MIN_CHARS = 64;
|
|
77
|
+
/** stub 里保留的参数预览长度 (万一压缩把原结果挤掉, 还能看出当初调了什么) */
|
|
78
|
+
exports.STUB_ARGS_PREVIEW_CHARS = 120;
|
|
79
|
+
/** 参数的规范 JSON (键排序 + 紧凑分隔符) —— 让 {a,b} 与 {b,a} 同签名 */
|
|
80
|
+
function canonicalToolArgs(args) {
|
|
81
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args))
|
|
82
|
+
return '';
|
|
83
|
+
const obj = args;
|
|
84
|
+
const keys = Object.keys(obj).sort();
|
|
85
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
|
|
86
|
+
return `{${parts.join(',')}}`;
|
|
87
|
+
}
|
|
88
|
+
function stableStringify(v) {
|
|
89
|
+
if (v === null || typeof v !== 'object')
|
|
90
|
+
return JSON.stringify(v) ?? 'null';
|
|
91
|
+
if (Array.isArray(v))
|
|
92
|
+
return `[${v.map(stableStringify).join(',')}]`;
|
|
93
|
+
const o = v;
|
|
94
|
+
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(',')}}`;
|
|
95
|
+
}
|
|
96
|
+
/** 调用签名: 不可逆 (只留哈希), 不泄漏参数原文 */
|
|
97
|
+
function toolCallSignature(toolName, args) {
|
|
98
|
+
return (0, node_crypto_1.createHash)('sha256').update(`${toolName}\u0000${canonicalToolArgs(args)}`).digest('hex').slice(0, 16);
|
|
99
|
+
}
|
|
100
|
+
/** 结果指纹 (判"完全相同的返回") */
|
|
101
|
+
function resultFingerprint(resultText) {
|
|
102
|
+
return (0, node_crypto_1.createHash)('sha256').update(String(resultText ?? '')).digest('hex').slice(0, 16);
|
|
103
|
+
}
|
|
104
|
+
function isRepeatableTool(toolName) {
|
|
105
|
+
if (exports.REPEATABLE_TOOLS.has(toolName))
|
|
106
|
+
return true;
|
|
107
|
+
return exports.REPEATABLE_SUFFIXES.some((s) => toolName.endsWith(s));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 记录一次调用并给出观测。**纯函数式**: 调用方每次都要把上一次的 streak 传进来
|
|
111
|
+
* (见 `LoopStallState`), 这样状态归属清晰、也便于单测。
|
|
112
|
+
*/
|
|
113
|
+
function observeToolCall(state, input) {
|
|
114
|
+
const ok = input.ok !== false;
|
|
115
|
+
const tolerant = exports.FAILURE_TOLERANT_TOOLS.has(input.toolName);
|
|
116
|
+
const repeatable = isRepeatableTool(input.toolName);
|
|
117
|
+
const text = String(input.resultText ?? '');
|
|
118
|
+
const chars = input.resultChars ?? text.length;
|
|
119
|
+
// ① 轮询类: 同参数重复本就正当 ⇒ 只记录, 不出声
|
|
120
|
+
if (repeatable) {
|
|
121
|
+
state.push(input.toolName, input.args, text);
|
|
122
|
+
return { action: 'allow', code: 'allow', count: 0 };
|
|
123
|
+
}
|
|
124
|
+
const sig = toolCallSignature(input.toolName, input.args);
|
|
125
|
+
const fp = resultFingerprint(text);
|
|
126
|
+
const prev = state.last;
|
|
127
|
+
const sameAsPrev = !!prev && prev.sig === sig && prev.fp === fp;
|
|
128
|
+
// ② 连续相同 (工具 + 参数 + 结果): 每次 +1, 否则归 1
|
|
129
|
+
state.streak = sameAsPrev ? state.streak + 1 : 1;
|
|
130
|
+
state.push(input.toolName, input.args, text);
|
|
131
|
+
// ③ 失败宽容类工具的失败不参与停滞判定 (红测试/空 grep 是产出, 不是卡住)
|
|
132
|
+
if (!ok && tolerant) {
|
|
133
|
+
return { action: 'allow', code: 'allow', count: state.streak };
|
|
134
|
+
}
|
|
135
|
+
// ③.5 恒定结果工具 (get_identity 这类): 从第 2 次起换标准答复 —— 不管结果长短
|
|
136
|
+
if (sameAsPrev && exports.CONSTANT_RESULT_TOOLS.has(input.toolName) && ok) {
|
|
137
|
+
return {
|
|
138
|
+
action: 'allow',
|
|
139
|
+
code: 'allow',
|
|
140
|
+
count: state.streak,
|
|
141
|
+
stub: `[已答] ${input.toolName} 的结果在本次会话内**恒定不变**(它读的是固定身份/配置, 不是活数据)。` +
|
|
142
|
+
`上面那一次返回就是答案, 直接用它作答即可; 再调一次只会得到同样的内容。` +
|
|
143
|
+
`若确实需要变化, 请改用写类工具(如 set_persona / 相应 update 工具)。`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// ④ 结果引用 stub: 从**第 2 次**完全相同的返回起 (够长才替代; 失败永不替代)
|
|
147
|
+
let stub;
|
|
148
|
+
// 2026-10-01 (优化 #1 收尾): 同签名重复 + 结果一字不差 ⇒ 用更低门槛 (重复本身就是浪费, 不管长短)
|
|
149
|
+
const repeatFloor = input.sameSignatureBefore ? exports.REPEAT_STUB_MIN_CHARS : exports.STUB_MIN_CHARS;
|
|
150
|
+
if (ok && input.seenResultBefore && chars >= repeatFloor) {
|
|
151
|
+
stub = `[结果引用] 与本次会话中此前一次 ${input.toolName} 的返回**逐字相同** (${chars} 字符, 已折叠)。`
|
|
152
|
+
+ `参数预览: ${canonicalToolArgs(input.args).slice(0, exports.STUB_ARGS_PREVIEW_CHARS)}`;
|
|
153
|
+
}
|
|
154
|
+
if (state.streak >= exports.IDENTICAL_CALL_THRESHOLD) {
|
|
155
|
+
// ⑤ 连续相同达阈值 ⇒ 温和引导 (不拒绝执行)
|
|
156
|
+
return {
|
|
157
|
+
action: 'warn',
|
|
158
|
+
code: 'identical_call_streak',
|
|
159
|
+
count: state.streak,
|
|
160
|
+
stub,
|
|
161
|
+
notice: `[系统提示] 这是第 ${state.streak} 次**用完全相同的参数**调用 ${input.toolName}, 且返回与上次完全相同。`
|
|
162
|
+
+ `重复调用不会得到新信息 —— 请换参数、换工具, 或基于已有结果直接回答。`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
// ⑥ 循环检测: 最近 N 次里出现了周期 ≤4 的重复串 (相邻计数会被交替调用清零, 只抓相邻必漏)
|
|
166
|
+
const cycle = detectCycle(state.history);
|
|
167
|
+
if (cycle) {
|
|
168
|
+
return {
|
|
169
|
+
action: 'warn',
|
|
170
|
+
code: 'identical_cycle',
|
|
171
|
+
count: cycle.laps,
|
|
172
|
+
stub,
|
|
173
|
+
notice: `[系统提示] 检测到最近 ${cycle.laps} 圈重复同一组 ${cycle.period} 次调用`
|
|
174
|
+
+ `(以 ${input.toolName} 结尾), 参数与返回都逐字相同。请不要重复这一组 —— 换参数、换工具, 或基于已有结果直接回答。`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { action: 'allow', code: 'allow', count: state.streak, stub };
|
|
178
|
+
}
|
|
179
|
+
/** 每轮 ReAct 循环一份的停滞状态 */
|
|
180
|
+
class LoopStallState {
|
|
181
|
+
streak = 0;
|
|
182
|
+
last = null;
|
|
183
|
+
history = [];
|
|
184
|
+
/** 已见过的结果指纹 (用于"完全相同返回"的 stub 判定) */
|
|
185
|
+
seenResults = new Set();
|
|
186
|
+
push(toolName, args, resultText) {
|
|
187
|
+
const step = { sig: toolCallSignature(toolName, args), fp: resultFingerprint(resultText) };
|
|
188
|
+
this.last = step;
|
|
189
|
+
this.history.push(step);
|
|
190
|
+
if (this.history.length > exports.CYCLE_HISTORY)
|
|
191
|
+
this.history.shift();
|
|
192
|
+
this.seenResults.add(step.fp);
|
|
193
|
+
}
|
|
194
|
+
/** 这个结果指纹此前是否出现过 (供 observeToolCall 的 seenResultBefore) */
|
|
195
|
+
hasSeenResult(text) {
|
|
196
|
+
return this.seenResults.has(resultFingerprint(text));
|
|
197
|
+
}
|
|
198
|
+
reset() {
|
|
199
|
+
this.streak = 0;
|
|
200
|
+
this.last = null;
|
|
201
|
+
this.history.length = 0;
|
|
202
|
+
this.seenResults.clear();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.LoopStallState = LoopStallState;
|
|
206
|
+
/**
|
|
207
|
+
* 找"最近若干圈完全重复"的串: 对每个候选周期 p ∈ [1, MAX_CYCLE_PERIOD],
|
|
208
|
+
* 看末尾是否由同一个 p 长度的块**重复 ≥ THRESHOLD 次**组成 (块内每个 (签名,结果指纹) 都相同)。
|
|
209
|
+
* 返回圈数与周期, 没有则 null。
|
|
210
|
+
*/
|
|
211
|
+
function detectCycle(history) {
|
|
212
|
+
const h = history;
|
|
213
|
+
for (let p = 1; p <= exports.MAX_CYCLE_PERIOD; p++) {
|
|
214
|
+
const need = p * exports.IDENTICAL_CALL_THRESHOLD;
|
|
215
|
+
if (h.length < need)
|
|
216
|
+
continue;
|
|
217
|
+
const tail = h.slice(-need);
|
|
218
|
+
let same = true;
|
|
219
|
+
for (let i = p; i < need && same; i++) {
|
|
220
|
+
if (tail[i].sig !== tail[i % p].sig || tail[i].fp !== tail[i % p].fp)
|
|
221
|
+
same = false;
|
|
222
|
+
}
|
|
223
|
+
if (same)
|
|
224
|
+
return { period: p, laps: exports.IDENTICAL_CALL_THRESHOLD };
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// 2026-10-01 第二轮 (继续学): 三处补上
|
|
230
|
+
// ① **无进展**是独立一轴: 幂等工具即使**换了参数**, 只要结果与上次相同 ⇒ 也是无进展
|
|
231
|
+
// (原先只抓"同参数" ⇒ 换个措辞再问一遍就绕过去了)
|
|
232
|
+
// ② **进展的定义 = 有副作用的调用成功过** (纯读再多都不算) ⇒ 用它清零失败/无进展计数
|
|
233
|
+
// ③ 升级策略**按在场与否分流**: 有人在 ⇒ 只提醒(从不阻止执行); 无人值守 ⇒ 才允许拦/停
|
|
234
|
+
// (人在场时不该替用户做决定; 但 cron/supervisor 这种无人值守的场景, 该果断收)
|
|
235
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
236
|
+
/** 有副作用 = 会改变世界 (文件/进程/网络/资金/记忆). 唯一判据, 供"进展"与"无进展"用 */
|
|
237
|
+
function toolMayHaveSideEffect(toolName) {
|
|
238
|
+
if (exports.MUTATING_TOOLS.has(toolName))
|
|
239
|
+
return true;
|
|
240
|
+
if (exports.IDEMPOTENT_TOOLS.has(toolName))
|
|
241
|
+
return false;
|
|
242
|
+
if (isRepeatableTool(toolName))
|
|
243
|
+
return false;
|
|
244
|
+
// 未登记的工具按"可能有副作用"处理 (保守: 不轻易判定"它没改变世界")
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
exports.DEFAULT_THRESHOLDS = {
|
|
248
|
+
exactFailureWarnAfter: 2,
|
|
249
|
+
exactFailureBlockAfter: 5,
|
|
250
|
+
sameToolFailureWarnAfter: 3,
|
|
251
|
+
sameToolFailureHaltAfter: 8,
|
|
252
|
+
noProgressWarnAfter: 2,
|
|
253
|
+
noProgressBlockAfter: 5,
|
|
254
|
+
hardStopEnabled: false,
|
|
255
|
+
};
|
|
256
|
+
/** 无人值守场景 (cron / supervisor / 非交互) —— 那里才默认允许拦与停 */
|
|
257
|
+
function defaultHardStopFor(context = {}) {
|
|
258
|
+
if (typeof context.unattended === 'boolean')
|
|
259
|
+
return context.unattended;
|
|
260
|
+
const env = context.env || process.env;
|
|
261
|
+
return env.BOLLOON_UNATTENDED === '1' || env.BOLLOON_CRON === '1' || env.BOLLOON_SUPERVISOR === '1';
|
|
262
|
+
}
|
|
263
|
+
function newProgressBookkeeping() {
|
|
264
|
+
return { progressClearedSignatures: new Set(), noProgress: new Map() };
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 记一次调用结果, 维护"进展 / 无进展"两本账。
|
|
268
|
+
* - 有副作用的调用**成功** ⇒ 视为进展: 清掉该签名的失败标记 + 清掉**所有**无进展记录
|
|
269
|
+
* (世界变了 ⇒ 之前"读了没变化"的判定作废)
|
|
270
|
+
* - 幂等工具: 结果指纹与上次相同 ⇒ 无进展 +1 (**参数变没变不影响**), 不同 ⇒ 归 1
|
|
271
|
+
*/
|
|
272
|
+
function trackProgress(book, input, thresholds = exports.DEFAULT_THRESHOLDS) {
|
|
273
|
+
const ok = input.ok !== false;
|
|
274
|
+
const sig = toolCallSignature(input.toolName, input.args);
|
|
275
|
+
const fp = resultFingerprint(String(input.resultText ?? ''));
|
|
276
|
+
if (ok && toolMayHaveSideEffect(input.toolName)) {
|
|
277
|
+
book.progressClearedSignatures.add(sig);
|
|
278
|
+
if (book.noProgress.size > 0)
|
|
279
|
+
book.noProgress.clear();
|
|
280
|
+
return { action: 'allow', code: 'allow', count: 0 };
|
|
281
|
+
}
|
|
282
|
+
if (!toolMayHaveSideEffect(input.toolName)) {
|
|
283
|
+
const prev = book.noProgress.get(input.toolName);
|
|
284
|
+
const count = prev && prev.lastResultFp === fp ? prev.count + 1 : 1;
|
|
285
|
+
book.noProgress.set(input.toolName, { lastResultFp: fp, count });
|
|
286
|
+
if (count >= thresholds.noProgressWarnAfter) {
|
|
287
|
+
return {
|
|
288
|
+
action: 'warn',
|
|
289
|
+
code: 'idempotent_no_progress',
|
|
290
|
+
count,
|
|
291
|
+
notice: `[系统提示] 连续 ${count} 次读取类调用 (${input.toolName}) 返回的都是**同一份内容**`
|
|
292
|
+
+ `(参数不同也一样) —— 世界没有变化, 再读还是它。请基于已有内容作答, 或改去查别的来源。`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return { action: 'allow', code: 'allow', count };
|
|
296
|
+
}
|
|
297
|
+
return { action: 'allow', code: 'allow', count: 0 };
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 2026-10-01 (用户: 「为什么这么慢」): 量到的事实 —— 每次 LLM 往返平均 **3338ms**, 而
|
|
301
|
+
* `toolCalls>1`(一轮发多个工具)**只占 12/63** ✗ ⇒ 回合时长 ≈ 往返次数 × 3.3s ✓;
|
|
302
|
+
* 也就是说"慢"主要不是工具慢(工具合计仅占 4% ✗), 而是**同一轮只干一件事**、来回跑 ✓。
|
|
303
|
+
* 做法: 一轮只调了一个**只读**工具时, 在结果后附一句**带代价**的提醒(不是口号 ✓)。
|
|
304
|
+
* 只读工具才提醒(写类工具串行更安全 ✓ 不催)。
|
|
305
|
+
*/
|
|
306
|
+
const BATCHABLE_HINT_TOOLS = new Set([
|
|
307
|
+
'read_file', 'grep_files', 'glob_files', 'list_files', 'read_document',
|
|
308
|
+
'git_status', 'git_log', 'git_diff', 'get_identity', 'list_tools', 'list_skills',
|
|
309
|
+
'process', 'list_local_channels', 'get_balance',
|
|
310
|
+
]);
|
|
311
|
+
function batchHint(toolName, toolCallsThisRound) {
|
|
312
|
+
if (toolCallsThisRound !== 1)
|
|
313
|
+
return '';
|
|
314
|
+
if (!BATCHABLE_HINT_TOOLS.has(String(toolName || '')))
|
|
315
|
+
return '';
|
|
316
|
+
return '\n⚡ 若你接下来还要查别的(或还要读别的文件), **同一轮可以一次发多个工具** —— '
|
|
317
|
+
+ '每次来回约 3.3 秒, 分成多轮就是纯等待; 独立的读操作请并成一轮发。';
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=tool-loop-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-loop-guard.js","sourceRoot":"","sources":["../../../src/agents/tool-loop-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PATH_ARG_KEYS = void 0;
|
|
7
|
+
exports.expandTilde = expandTilde;
|
|
8
|
+
exports.expandKnownAliases = expandKnownAliases;
|
|
9
|
+
exports.expandHomeArgs = expandHomeArgs;
|
|
10
|
+
/**
|
|
11
|
+
* 工具参数里的 `~` 展开 (2026-10-01)。
|
|
12
|
+
*
|
|
13
|
+
* 用户实测: 智能体调 `list_files { path: "~/.bolloon" }` ⇒ `ENOENT: scandir '~/.bolloon'` ✗。
|
|
14
|
+
* 根因: 全仓**没有任何一处**做 `~` 展开 ⇒ 模型写的 `~/x` 原样进 fs, 变成字面目录名。
|
|
15
|
+
* 修法: 在**唯一分发点** (pi-sdk.ts 的 `tool.execute(...)`) 统一展开 —— 工具设置层修一次,
|
|
16
|
+
* 而不是每个工具各打一个补丁 (那样必然漏)。只碰已知的路径类参数, 其它参数一律不动。
|
|
17
|
+
*/
|
|
18
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
/** 会被当成路径的参数名 (只展开这些, 别的一律不碰) */
|
|
21
|
+
exports.PATH_ARG_KEYS = [
|
|
22
|
+
'path', 'paths', 'dir', 'directory', 'folder', 'file', 'file_path', 'filePath',
|
|
23
|
+
'from', 'to', 'target', 'source', 'src', 'dest', 'cwd', 'workdir', 'root', 'glob_dir',
|
|
24
|
+
];
|
|
25
|
+
/** 只展开**开头**的 `~` (单独的 `~` 或 `~/...`); `a~b` / `~user` 不碰 */
|
|
26
|
+
function expandTilde(p, home = node_os_1.default.homedir()) {
|
|
27
|
+
const s = String(p ?? '');
|
|
28
|
+
if (s === '~')
|
|
29
|
+
return home;
|
|
30
|
+
if (s.startsWith('~/') || s.startsWith('~' + node_path_1.default.sep))
|
|
31
|
+
return node_path_1.default.join(home, s.slice(2));
|
|
32
|
+
return s;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 已知别名: `.bolloon` 指的就是数据目录 (~/.bolloon)。
|
|
36
|
+
* 2026-10-01 用户实测: 智能体写 `list_files { path: ".bolloon" }` ⇒ `ENOENT: scandir '.bolloon'` ✗
|
|
37
|
+
* (它连 `~` 都没写, 是相对路径 ⇒ 按 cwd 解析必然不存在)。只映射**恰好的**那个别名:
|
|
38
|
+
* `.bolloon` / `.bolloon/...`; `foo/.bolloon` 之类一律不碰 (避免误伤真的同名目录)。
|
|
39
|
+
*/
|
|
40
|
+
function expandKnownAliases(p, home = node_os_1.default.homedir()) {
|
|
41
|
+
const s = String(p ?? '');
|
|
42
|
+
if (s === '.bolloon')
|
|
43
|
+
return node_path_1.default.join(home, '.bolloon');
|
|
44
|
+
if (s.startsWith('.bolloon/') || s.startsWith('.bolloon' + node_path_1.default.sep))
|
|
45
|
+
return node_path_1.default.join(home, '.bolloon', s.slice('.bolloon/'.length));
|
|
46
|
+
return s;
|
|
47
|
+
}
|
|
48
|
+
function expandHomeArgs(args, home) {
|
|
49
|
+
if (!args || typeof args !== 'object')
|
|
50
|
+
return args;
|
|
51
|
+
const out = { ...args };
|
|
52
|
+
for (const key of Object.keys(out)) {
|
|
53
|
+
if (!exports.PATH_ARG_KEYS.includes(key))
|
|
54
|
+
continue;
|
|
55
|
+
const v = out[key];
|
|
56
|
+
if (typeof v === 'string')
|
|
57
|
+
out[key] = expandKnownAliases(expandTilde(v, home), home);
|
|
58
|
+
else if (Array.isArray(v))
|
|
59
|
+
out[key] = v.map((x) => (typeof x === 'string' ? expandKnownAliases(expandTilde(x, home), home) : x));
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=tool-path-args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-path-args.js","sourceRoot":"","sources":["../../../src/agents/tool-path-args.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;;;;GAOG;AACH,sDAAyB;AACzB,0DAA6B;AAE7B,iCAAiC;AACpB,QAAA,aAAa,GAA0B;IAClD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU;IAC9E,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU;CACtF,CAAC;AAEF,6DAA6D;AAC7D,qBAA4B,CAAS,EAAE,IAAI,GAAW,iBAAE,CAAC,OAAO,EAAE;IAChE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1B,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,GAAG,mBAAI,CAAC,GAAG,CAAC;QAAE,OAAO,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;GAKG;AACH,4BAAmC,CAAS,EAAE,IAAI,GAAW,iBAAE,CAAC,OAAO,EAAE;IACvE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1B,IAAI,CAAC,KAAK,UAAU;QAAE,OAAO,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACzD,IAAI,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,GAAG,mBAAI,CAAC,GAAG,CAAC;QAAE,OAAO,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;IACtI,OAAO,CAAC,CAAC;AACX,CAAC;AAED,wBAA+B,IAAyB,EAAE,IAAa;IACrE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,GAAG,GAAwB,EAAE,GAAG,IAAI,EAAE,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,QAAA,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QAC3C,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACnB,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;aAChF,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnI,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_RESULT_MAX_CHARS = void 0;
|
|
7
|
+
exports.resultsDir = resultsDir;
|
|
8
|
+
exports.capToolResult = capToolResult;
|
|
9
|
+
/**
|
|
10
|
+
* 工具结果的"进上下文"闸 (2026-10-01, 优化方向 #1)。
|
|
11
|
+
*
|
|
12
|
+
* 问题: 工具结果**没有上限** ✗ 就直接进 history, 而 history **每一轮都重放** ⇒ 一条大输出会持续吃上下文。
|
|
13
|
+
* 规矩: 超上限 ⇒ 保留**头 + 尾**(中段信息密度最低) + 把**完整结果落到文件**并把路径给模型
|
|
14
|
+
* (要看细节就 read_file ⇒ 既省上下文, 又不丢信息 ✓)。
|
|
15
|
+
* 纯函数 + 一个落盘动作; 落盘失败就退化成"只截断"(绝不因此让工具失败 ✓)。
|
|
16
|
+
*/
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
20
|
+
/** 默认上限: 8000 字符 (够放下绝大多数结果; 超了才走落文件) */
|
|
21
|
+
exports.DEFAULT_RESULT_MAX_CHARS = 8000;
|
|
22
|
+
function resultsDir(home = node_os_1.default.homedir()) {
|
|
23
|
+
return node_path_1.default.join(home, '.bolloon', 'logs', 'tool-results');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 头尾保留: head 60% / tail 25%, 留 15% 给提示语 (与出口净化的省略口径一致, 免得两套规矩)。
|
|
27
|
+
*/
|
|
28
|
+
function capToolResult(text, opts = { tool: '?' }) {
|
|
29
|
+
const s = String(text ?? '');
|
|
30
|
+
const max = opts.maxChars ?? exports.DEFAULT_RESULT_MAX_CHARS;
|
|
31
|
+
const originalChars = s.length;
|
|
32
|
+
if (max <= 0 || s.length <= max)
|
|
33
|
+
return { text: s, capped: false, originalChars };
|
|
34
|
+
const head = Math.floor(max * 0.6);
|
|
35
|
+
const tail = Math.floor(max * 0.25);
|
|
36
|
+
let spilledTo;
|
|
37
|
+
if (opts.spill !== false) {
|
|
38
|
+
try {
|
|
39
|
+
const home = opts.home || node_os_1.default.homedir();
|
|
40
|
+
const dir = resultsDir(home);
|
|
41
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
42
|
+
const safeTool = String(opts.tool || 'tool').replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 40);
|
|
43
|
+
const file = node_path_1.default.join(dir, `${Date.now()}-${safeTool}.txt`);
|
|
44
|
+
node_fs_1.default.writeFileSync(file, s, 'utf-8');
|
|
45
|
+
spilledTo = file;
|
|
46
|
+
}
|
|
47
|
+
catch { /* 落盘失败就只截断 */ }
|
|
48
|
+
}
|
|
49
|
+
const notice = spilledTo
|
|
50
|
+
? `\n…[结果过长已截断: 原文 ${originalChars} 字符, 完整内容已存到 ${spilledTo} —— 要看细节用 read_file 读它]…\n`
|
|
51
|
+
: `\n…[结果过长已截断: 原文 ${originalChars} 字符]…\n`;
|
|
52
|
+
return {
|
|
53
|
+
text: `${s.slice(0, head)}${notice}${s.slice(-tail)}`,
|
|
54
|
+
capped: true,
|
|
55
|
+
spilledTo,
|
|
56
|
+
originalChars,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=tool-result-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-result-gate.js","sourceRoot":"","sources":["../../../src/agents/tool-result-gate.ts"],"names":[],"mappings":";;;;;;;;AAAA;;;;;;;GAOG;AACH,sDAAyB;AACzB,0DAA6B;AAC7B,sDAAyB;AAEzB,yCAAyC;AAC5B,QAAA,wBAAwB,GAAG,IAAI,CAAC;AAS7C,oBAA2B,IAAI,GAAG,iBAAE,CAAC,OAAO,EAAE;IAC5C,OAAO,mBAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AAC7D,CAAC;AAED;;GAEG;AACH,uBACE,IAAY,EACZ,IAAI,GAAwE,EAAE,IAAI,EAAE,GAAG,EAAE;IAEzF,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAA,wBAAwB,CAAC;IACtD,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAElF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IACpC,IAAI,SAA6B,CAAC;IAClC,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,iBAAE,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC7B,iBAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3F,MAAM,IAAI,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,MAAM,CAAC,CAAC;YAC7D,iBAAE,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;YACnC,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC;IACD,MAAM,MAAM,GAAG,SAAS;QACtB,CAAC,CAAC,mBAAmB,aAAa,gBAAgB,SAAS,4BAA4B;QACvF,CAAC,CAAC,mBAAmB,aAAa,SAAS,CAAC;IAC9C,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE;QACrD,MAAM,EAAE,IAAI;QACZ,SAAS;QACT,aAAa;KACd,CAAC;AACJ,CAAC"}
|