@bolloon/bolloon-agent 0.6.5 → 0.6.7
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/chain/chain-settlement.js +0 -1
- package/dist/agents/chat-segmenter.js +4 -16
- package/dist/agents/contacts/human-directory.js +155 -0
- package/dist/agents/contacts/tools.js +79 -0
- package/dist/agents/experience-review.js +0 -3
- package/dist/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/agents/goal-resume.js +0 -4
- package/dist/agents/ha-daemon.js +85 -0
- package/dist/agents/ha-supervisor.js +146 -0
- package/dist/agents/intent-classifier.js +1 -1
- package/dist/agents/iteration-budget.js +12 -0
- package/dist/agents/knowledge-organizer.js +0 -2
- package/dist/agents/lesson-to-skill.js +0 -1
- package/dist/agents/loop-review.js +2 -2
- package/dist/agents/memory-store.js +384 -0
- package/dist/agents/opportunity-match.js +15 -3
- package/dist/agents/opportunity-web-search.js +39 -0
- package/dist/agents/p2p-chat-tools.js +1 -2
- package/dist/agents/parse-tool-call.js +9 -22
- package/dist/agents/pi-harness.js +1 -2
- package/dist/agents/pi-sdk-session-manager.js +8 -0
- package/dist/agents/pi-sdk-tools.js +180 -5
- package/dist/agents/pi-sdk.js +210 -503
- package/dist/agents/process-runner.js +28 -9
- package/dist/agents/react-loop.js +9 -17
- package/dist/agents/runner-resolver.js +4 -3
- package/dist/agents/skill-loader.js +22 -15
- package/dist/agents/skills-manager.js +1 -25
- package/dist/agents/workflow-pivot-loop.js +152 -61
- package/dist/bootstrap/context-collector.js +67 -7
- package/dist/bootstrap/context-hierarchy.js +13 -1
- package/dist/bootstrap/persona-loader.js +26 -0
- package/dist/cli/ink-app.js +44 -17
- package/dist/cli/intent-command.js +22 -12
- package/dist/cli/loading-tui.js +8 -2
- package/dist/cli/log-gate.js +40 -8
- package/dist/cli/mention-data.js +1 -0
- package/dist/cli/reply-hygiene.js +22 -0
- package/dist/cli/trace-line.js +61 -2
- package/dist/electron-build/agents/agent-gateway.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-indexer.js.map +1 -1
- package/dist/electron-build/agents/chain/chain-settlement.js +0 -1
- package/dist/electron-build/agents/chain/chain-settlement.js.map +1 -1
- package/dist/electron-build/agents/contacts/chain.js.map +1 -1
- package/dist/electron-build/agents/contacts/human-directory.js +204 -0
- package/dist/electron-build/agents/contacts/human-directory.js.map +1 -0
- package/dist/electron-build/agents/contacts/policy.js.map +1 -1
- package/dist/electron-build/agents/contacts/tools.js +112 -0
- package/dist/electron-build/agents/contacts/tools.js.map +1 -1
- package/dist/electron-build/agents/experience-review.js +0 -3
- package/dist/electron-build/agents/experience-review.js.map +1 -1
- package/dist/electron-build/agents/external-events.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel/goal-change.js +3 -4
- package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -1
- package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -1
- package/dist/electron-build/agents/goal-resume.js +0 -4
- package/dist/electron-build/agents/goal-resume.js.map +1 -1
- package/dist/electron-build/agents/intent-classifier.js +1 -1
- package/dist/electron-build/agents/lesson-to-skill.js +0 -1
- package/dist/electron-build/agents/lesson-to-skill.js.map +1 -1
- package/dist/electron-build/agents/loop-review.js +2 -2
- package/dist/electron-build/agents/loop-review.js.map +1 -1
- package/dist/electron-build/agents/memory-recall.js.map +1 -1
- package/dist/electron-build/agents/memory-store.js +430 -0
- package/dist/electron-build/agents/memory-store.js.map +1 -0
- package/dist/electron-build/agents/parse-tool-call.js +9 -22
- package/dist/electron-build/agents/parse-tool-call.js.map +1 -1
- package/dist/electron-build/agents/pi-harness.js +1 -2
- package/dist/electron-build/agents/pi-harness.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-session-manager.js +8 -0
- package/dist/electron-build/agents/pi-sdk-session-manager.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +208 -501
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/pre-tool-validator.js.map +1 -1
- package/dist/electron-build/agents/process-runner.js +28 -9
- package/dist/electron-build/agents/process-runner.js.map +1 -1
- package/dist/electron-build/agents/runner-resolver.js +4 -3
- package/dist/electron-build/agents/runner-resolver.js.map +1 -1
- package/dist/electron-build/agents/skill-loader.js +22 -16
- package/dist/electron-build/agents/skill-loader.js.map +1 -1
- package/dist/electron-build/agents/skill-supervisor-link.js.map +1 -1
- package/dist/electron-build/agents/skills-manager.js +0 -24
- package/dist/electron-build/agents/skills-manager.js.map +1 -1
- package/dist/electron-build/agents/task/task-runner.js.map +1 -1
- package/dist/electron-build/agents/workflow-engine.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +155 -62
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/goal-run-bridge.js.map +1 -1
- package/dist/electron-build/bollharness-integration/gate-state-machine.js.map +1 -1
- package/dist/electron-build/bootstrap/context-collector.js +68 -7
- package/dist/electron-build/bootstrap/context-collector.js.map +1 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js +13 -1
- package/dist/electron-build/bootstrap/context-hierarchy.js.map +1 -1
- package/dist/electron-build/bootstrap/persona-loader.js +27 -0
- package/dist/electron-build/bootstrap/persona-loader.js.map +1 -1
- package/dist/electron-build/electron-preload.js.map +1 -1
- package/dist/electron-build/external-engines/delegate.js.map +1 -1
- package/dist/electron-build/kernel/channel-actor.js +31 -2
- package/dist/electron-build/kernel/channel-actor.js.map +1 -1
- package/dist/electron-build/kernel/model-runtime.js +8 -1
- package/dist/electron-build/kernel/model-runtime.js.map +1 -1
- package/dist/electron-build/llm/config-store.js +20 -0
- package/dist/electron-build/llm/config-store.js.map +1 -1
- package/dist/electron-build/llm/embedding-adapter.js +75 -0
- package/dist/electron-build/llm/embedding-adapter.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +112 -38
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/llm/system-prompt/registry.js +8 -6
- package/dist/electron-build/llm/system-prompt/registry.js.map +1 -1
- package/dist/electron-build/network/agent-network.js.map +1 -1
- package/dist/electron-build/orbitdb/memory-sync.js +76 -0
- package/dist/electron-build/orbitdb/memory-sync.js.map +1 -0
- package/dist/electron-build/pi-ecosystem-judgment/adaptive-scan.js.map +1 -1
- package/dist/electron-build/security/tool-gate.js +2 -0
- package/dist/electron-build/security/tool-gate.js.map +1 -1
- package/dist/electron-build/setup/setup-store.js +34 -6
- package/dist/electron-build/setup/setup-store.js.map +1 -1
- package/dist/electron-build/social/heartbeat.js +18 -5
- package/dist/electron-build/social/heartbeat.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/index.js +250 -12
- package/dist/ios/bolloon-web.json +3 -3
- package/dist/ios/client.js +74 -35
- package/dist/ios/icons/icon-1400x1400.png +0 -0
- package/dist/ios/icons/icon-1400x1400.webp +0 -0
- package/dist/ios/index.html +122 -108
- package/dist/ios/mobile-agent.js +572 -45
- package/dist/ios/mobile-contacts.js +0 -4
- package/dist/ios/mobile-core.js +8079 -4590
- package/dist/ios/mobile-harness.js +1004 -0
- package/dist/ios/mobile-memory.js +317 -0
- package/dist/ios/mobile-orbitdb.js +194 -0
- package/dist/ios/mobile-persona.js +80 -0
- package/dist/ios/mobile-privacy.js +2 -3
- package/dist/ios/mobile.css +286 -21
- package/dist/ios/mobile.html +122 -108
- package/dist/ios/mobile.js +434 -152
- package/dist/ios/routes-contacts.js +94 -1
- package/dist/ios/routes-world.js +13 -2
- package/dist/ios/server.js +54 -41
- package/dist/ios/style.css +13 -5
- package/dist/ios/sw.js +1 -1
- package/dist/ios/ui/message-renderer.js +4 -18
- package/dist/ios/ui/step-timeline.js +14 -1
- package/dist/kernel/channel-actor.js +30 -2
- package/dist/kernel/gate-scan.js +2 -2
- package/dist/kernel/ha/ha-arbiter.js +177 -0
- package/dist/kernel/ha/ha-guard.js +151 -0
- package/dist/kernel/index.js +36 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +5 -5
- package/dist/kernel/roster.js +3 -3
- package/dist/llm/config-store.js +20 -0
- package/dist/llm/embedding-adapter.js +69 -0
- package/dist/llm/pi-ai.js +110 -37
- package/dist/llm/system-prompt/registry.js +8 -6
- package/dist/orbitdb/memory-sync.js +70 -0
- package/dist/pi-ecosystem-judgment/adaptive-scan.js +1 -1
- package/dist/security/tool-gate.js +2 -0
- package/dist/setup/setup-store.js +34 -6
- package/dist/social/heartbeat.js +18 -5
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/client.js +74 -35
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +37 -25
- package/dist/web/mobile-agent.js +572 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +8079 -4590
- package/dist/web/mobile-harness.js +1004 -0
- package/dist/web/mobile-memory.js +317 -0
- package/dist/web/mobile-orbitdb.js +194 -0
- package/dist/web/mobile-persona.js +80 -0
- package/dist/web/mobile-privacy.js +2 -3
- package/dist/web/mobile.css +286 -21
- package/dist/web/mobile.html +122 -108
- package/dist/web/mobile.js +434 -152
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +13 -2
- package/dist/web/server.js +54 -41
- package/dist/web/style.css +13 -5
- package/dist/web/sw.js +1 -1
- package/dist/web/ui/message-renderer.js +4 -18
- package/dist/web/ui/step-timeline.js +14 -1
- package/package.json +3 -2
- package/scripts/build-web.ts +16 -1
|
@@ -30,7 +30,15 @@ export function spawnBackground(raw, cwd = process.cwd()) {
|
|
|
30
30
|
const shell = process.platform === 'win32' ? 'cmd' : '/bin/sh';
|
|
31
31
|
const shellArgs = process.platform === 'win32' ? ['/c', raw] : ['-c', raw];
|
|
32
32
|
try {
|
|
33
|
-
const proc = spawn(shell, shellArgs, {
|
|
33
|
+
const proc = spawn(shell, shellArgs, {
|
|
34
|
+
cwd,
|
|
35
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
// 2026-10-13 (leo: 「有没有错误回收机制」): detached:true = 每条命令独立进程组,
|
|
38
|
+
// killSession 才能按组杀 (负 pid) 回收整棵进程树, 不留 shell 的孤儿子进程。
|
|
39
|
+
// 副作用 (进程随父退出不自动回收) 由既有落盘恢复机制兜底 (persistSessions/restoreSessions)。
|
|
40
|
+
detached: process.platform !== 'win32',
|
|
41
|
+
});
|
|
34
42
|
session.proc = proc;
|
|
35
43
|
session.pid = proc.pid;
|
|
36
44
|
persistSessions();
|
|
@@ -59,8 +67,11 @@ export function pollSession(id) {
|
|
|
59
67
|
},
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
|
-
/** 等待后台进程结束 (最多 timeoutMs). 返回最终状态.
|
|
63
|
-
|
|
70
|
+
/** 等待后台进程结束 (最多 timeoutMs). 返回最终状态.
|
|
71
|
+
* 2026-10-13 (leo: 「有没有错误回收机制, 超时回收机制?」): 超时后**回收** —— 默认 kill 挂起进程
|
|
72
|
+
* (wait 语义 = "等它跑完取结果", 超时说明它不会在预算内跑完, 留着占资源没意义);
|
|
73
|
+
* 不想杀传 killOnTimeout=false (纯等待/轮询场景). */
|
|
74
|
+
export async function waitSession(id, timeoutMs = 30000, killOnTimeout = true) {
|
|
64
75
|
const s = sessions.get(id);
|
|
65
76
|
if (!s)
|
|
66
77
|
return { ok: false, error: `未知 session ${id}` };
|
|
@@ -71,9 +82,13 @@ export async function waitSession(id, timeoutMs = 30000) {
|
|
|
71
82
|
while (Date.now() < deadline && s.status === 'running') {
|
|
72
83
|
await new Promise((r) => setTimeout(r, 250));
|
|
73
84
|
}
|
|
85
|
+
const timedOut = s.status === 'running';
|
|
86
|
+
if (timedOut && killOnTimeout) {
|
|
87
|
+
killSession(id); // 超时回收: 终止挂起进程, 不占资源
|
|
88
|
+
}
|
|
74
89
|
return {
|
|
75
|
-
ok:
|
|
76
|
-
session: { id: s.id, status: s.status, exitCode: s.exitCode, output: s.output.slice(-4000), timedOut
|
|
90
|
+
ok: !timedOut,
|
|
91
|
+
session: { id: s.id, status: s.status, exitCode: s.exitCode, output: s.output.slice(-4000), timedOut },
|
|
77
92
|
};
|
|
78
93
|
}
|
|
79
94
|
/** 杀死后台进程. 返回是否已终止. */
|
|
@@ -85,10 +100,14 @@ export function killSession(id) {
|
|
|
85
100
|
return { ok: true, reason: `进程已 ${s.status}` };
|
|
86
101
|
}
|
|
87
102
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
// 2026-10-13 (leo: 「有没有错误回收机制」): kill 只杀 shell 不杀子进程 = 进程树泄漏;
|
|
104
|
+
// spawn 时 detached:true 让每条命令独立进程组 ⇒ 这里 kill 整个进程组 (负 pid = 组信号)。
|
|
105
|
+
// spawnBackground 已带 detached:true (见该函数)。
|
|
106
|
+
try {
|
|
107
|
+
process.kill(-(s.proc.pid), 'SIGTERM');
|
|
108
|
+
}
|
|
109
|
+
catch { /* 组信号可能 EPERM (非组长) */ }
|
|
110
|
+
s.proc.kill('SIGTERM');
|
|
92
111
|
s.status = 'killed';
|
|
93
112
|
return { ok: true };
|
|
94
113
|
}
|
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
* 决策表 (apply 到 LLM 回复 + 当前 state):
|
|
9
9
|
* 1. reply 是 AI failure sentinel → continue (push error 进 history, 让 LLM 反思)
|
|
10
10
|
* 2. reply 可 parse 出 tool call → execute-tool (调 tool, push result 进 history)
|
|
11
|
-
* 3. reply
|
|
12
|
-
* 4. reply 无 <final gen>, 无 tool_call → continue (LLM 可能再想想, 让下一轮重新生成)
|
|
11
|
+
* 3. reply 有 tool_call → execute-tool (工具优先)
|
|
13
12
|
*
|
|
14
13
|
* 这个模块不调 LLM, 只接受 reply/state 返回下一步决策. LLM 调用 + 真正状态变更
|
|
15
14
|
* 仍由 PiAgentSession 协调. 这样 claude code 可以独立 unit-test 决策边界.
|
|
16
15
|
*/
|
|
17
|
-
import { parseToolCall
|
|
16
|
+
import { parseToolCall } from './parse-tool-call.js';
|
|
18
17
|
/** 把 LLM 回复归到一种 next action — 纯函数. */
|
|
19
18
|
export function decideNext(ctx) {
|
|
20
19
|
if (ctx.signalAborted) {
|
|
@@ -42,12 +41,10 @@ export function decideNext(ctx) {
|
|
|
42
41
|
reason: 'parse-tool-call',
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
|
-
// final gen
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
// 默认 — 普通 text, 让 LLM 继续想
|
|
50
|
-
return { kind: 'continue', reason: 'no-tool-no-final' };
|
|
44
|
+
// 2026-10-12 (leo 拍板: 全项目去掉 <final gen> 截止符): 旧实现这里用
|
|
45
|
+
// isFinalResponse (找 `<final gen>` 标记) 判结束 — 已移除。
|
|
46
|
+
// 无工具调用 = 正常 text, 让 LLM 继续想 (由外层质量/无进展门收尾)。
|
|
47
|
+
return { kind: 'continue', reason: 'no-tool' };
|
|
51
48
|
}
|
|
52
49
|
/**
|
|
53
50
|
* isAiFailureSentinel — 检测 LLM 上游失败哨兵.
|
|
@@ -62,14 +59,9 @@ export function isAiFailureSentinel(reply) {
|
|
|
62
59
|
trimmed.startsWith('[AI 调用失败]') ||
|
|
63
60
|
trimmed.startsWith('[错误:');
|
|
64
61
|
}
|
|
65
|
-
/** extractFinalText — 抽 LLM
|
|
66
|
-
export function extractFinalText(reply
|
|
67
|
-
|
|
68
|
-
if (idx !== -1) {
|
|
69
|
-
const after = reply.substring(idx + marker.length).trim();
|
|
70
|
-
return after || reply.substring(0, idx).trim();
|
|
71
|
-
}
|
|
72
|
-
return reply;
|
|
62
|
+
/** extractFinalText — 抽 LLM 输出里能呈现给用户的最终文字. (2026-10-12: 不再按 <final gen> 切) */
|
|
63
|
+
export function extractFinalText(reply) {
|
|
64
|
+
return reply.trim();
|
|
73
65
|
}
|
|
74
66
|
/**
|
|
75
67
|
* 错误累计判定 — 当 totalErrors 达阈值时, 给 final 答案 + 汇总.
|
|
@@ -44,20 +44,21 @@ function classify(err, stage) {
|
|
|
44
44
|
/** 读本机 LLM 配置摘要 (只看结构与有无, 不打印任何密钥) */
|
|
45
45
|
export async function probeLlmConfig(home = os.homedir()) {
|
|
46
46
|
try {
|
|
47
|
-
|
|
47
|
+
// 2026-10-06 技术债清理: 只读 canonical bolloon-config.json (旧 llm-config.json 已废弃, 迁移逻辑在 config-store)
|
|
48
|
+
const raw = JSON.parse(await fsp.readFile(path.join(home, '.bolloon', 'bolloon-config.json'), 'utf8'));
|
|
48
49
|
const active = raw.activeProvider || raw.provider;
|
|
49
50
|
const providers = raw.providers || {};
|
|
50
51
|
const p = providers[active] || {};
|
|
51
52
|
const needsKey = p.requiresApiKey !== false;
|
|
52
53
|
const hasKey = !!(p.apiKey || p.api_key || process.env[`${String(active).toUpperCase()}_API_KEY`]);
|
|
53
54
|
if (!active)
|
|
54
|
-
return { ok: false, reason: '
|
|
55
|
+
return { ok: false, reason: 'bolloon-config.json 没有 activeProvider' };
|
|
55
56
|
if (needsKey && !hasKey)
|
|
56
57
|
return { ok: false, provider: active, model: p.model, hasKey: false, reason: `provider ${active} 需要 apiKey 但没配` };
|
|
57
58
|
return { ok: true, provider: active, model: p.model, hasKey };
|
|
58
59
|
}
|
|
59
60
|
catch (err) {
|
|
60
|
-
return { ok: false, reason: `
|
|
61
|
+
return { ok: false, reason: `bolloon-config.json 不可读: ${String(err?.message || err).slice(0, 100)}` };
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
async function stage(stages, name, required, timeoutMs, fn, log, noteOf) {
|
|
@@ -108,33 +108,44 @@ export async function parseSkillFile(filePath) {
|
|
|
108
108
|
const dirName = path.basename(path.dirname(filePath));
|
|
109
109
|
return normalize(dirName, filePath, raw);
|
|
110
110
|
}
|
|
111
|
-
/** 扫描一个目录, 找所有 {name}/SKILL.md (
|
|
111
|
+
/** 扫描一个目录, 找所有 {name}/SKILL.md — 支持多级嵌套 (2026-10-12, Hermes 式分类技能库) */
|
|
112
112
|
export async function loadSkillsDir(dir) {
|
|
113
113
|
const out = [];
|
|
114
|
+
await walkSkillsDir(dir, out);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/** 递归扫: 一层 {name}/SKILL.md + 深层子目录 (Hermes skills/<category>/<skill>/SKILL.md 形态) */
|
|
118
|
+
async function walkSkillsDir(dir, out, depth = 0) {
|
|
114
119
|
let entries;
|
|
115
120
|
try {
|
|
116
121
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
117
122
|
}
|
|
118
123
|
catch {
|
|
119
|
-
return
|
|
124
|
+
return;
|
|
120
125
|
}
|
|
121
126
|
for (const entry of entries) {
|
|
122
|
-
if (!entry.isDirectory())
|
|
123
|
-
continue;
|
|
124
127
|
if (entry.name.startsWith('.'))
|
|
125
128
|
continue;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
const full = path.join(dir, entry.name);
|
|
130
|
+
if (entry.isDirectory()) {
|
|
131
|
+
// ① 这层有 SKILL.md → 即一个技能目录
|
|
132
|
+
const meta = await parseSkillFile(path.join(full, 'SKILL.md'));
|
|
133
|
+
if (meta) {
|
|
134
|
+
out.push(meta);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// ② 没有 → 递归下钻 (分类层), 防深到失控
|
|
138
|
+
if (depth < 6)
|
|
139
|
+
await walkSkillsDir(full, out, depth + 1);
|
|
140
|
+
}
|
|
130
141
|
}
|
|
131
|
-
return out;
|
|
132
142
|
}
|
|
133
|
-
/** 默认 skill 路径优先级 (后者覆盖前者同名 skill) */
|
|
143
|
+
/** 默认 skill 路径优先级 (后者覆盖前者同名 skill); 2026-10-12 加 `<cwd>/skills` (Hermes 式仓库技能库) */
|
|
134
144
|
export function defaultSkillPaths(home = os.homedir(), cwd = process.cwd()) {
|
|
135
145
|
return [
|
|
136
146
|
path.join(home, '.bolloon', 'skills'), // 全局用户级
|
|
137
|
-
path.join(cwd, '.bolloon', 'skills'), // 项目级
|
|
147
|
+
path.join(cwd, '.bolloon', 'skills'), // 项目级 (传统 Bolloon)
|
|
148
|
+
path.join(cwd, 'skills'), // 项目级 (Hermes 式: 仓库根 skills/ 分类技能库)
|
|
138
149
|
path.join(home, '.boll', 'skills'), // 全局 (兼容 bollharness 旧用户)
|
|
139
150
|
];
|
|
140
151
|
}
|
|
@@ -168,7 +179,3 @@ export async function loadSkillsFromPaths(paths) {
|
|
|
168
179
|
}
|
|
169
180
|
return Array.from(seen.values());
|
|
170
181
|
}
|
|
171
|
-
/** 列出已加载的 skills (调试/UI 用) */
|
|
172
|
-
export function describeSkill(s) {
|
|
173
|
-
return `${s.name}: ${s.description}`;
|
|
174
|
-
}
|
|
@@ -25,7 +25,7 @@ import { sanitizeSkillName as sanitizeNameLoose, getUserSkillsDir as userSkillsD
|
|
|
25
25
|
import { parseSkillFile as parseSkillFileLoose } from './skill-loader.js';
|
|
26
26
|
import { parseSkillFile, defaultSkillPaths } from './skill-loader.js';
|
|
27
27
|
import { getUserSkillsDir } from './skill-writer.js';
|
|
28
|
-
import { collectSkillBundle, parseSkillBundle,
|
|
28
|
+
import { collectSkillBundle, parseSkillBundle, installSkillBundle, } from './skill-share.js';
|
|
29
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
30
|
export function registryPath(home = os.homedir()) {
|
|
31
31
|
return path.join(home, '.bolloon', 'skills-registry.json');
|
|
@@ -400,30 +400,6 @@ export class SkillsManager {
|
|
|
400
400
|
await this.onRegistryChanged({ home: opts.home ?? this.home, action: 'import', name: tx.name }).catch(() => null);
|
|
401
401
|
return { ok: tx.ok, name: tx.name, version: tx.version, error: tx.error, skill: tx.skill };
|
|
402
402
|
}
|
|
403
|
-
/** @deprecated 保留旧签名以兼容; 内部已走事务版 */
|
|
404
|
-
async importLegacy(ref, opts = {}) {
|
|
405
|
-
const cid = parseSkillRef(ref);
|
|
406
|
-
if (!cid)
|
|
407
|
-
return { ok: false, error: `无法识别的技能引用 (要 bolloon://skill/<cid> / ipfs://<cid> / 裸 CID): ${ref.slice(0, 60)}` };
|
|
408
|
-
const fetched = await fetchSkillBundle(cid);
|
|
409
|
-
if (!fetched.ok || !fetched.bundle)
|
|
410
|
-
return { ok: false, error: fetched.error || '取包失败' };
|
|
411
|
-
const inst = await installSkillBundle(fetched.bundle, { home: opts.home ?? this.home, cwd: opts.cwd ?? this.cwd, force: opts.force, scope: opts.scope });
|
|
412
|
-
if (!inst.ok)
|
|
413
|
-
return { ok: false, error: inst.error };
|
|
414
|
-
const h = opts.home ?? this.home;
|
|
415
|
-
const name = fetched.bundle.name;
|
|
416
|
-
this.cache = null;
|
|
417
|
-
const after = await this.inspect(name, { home: h });
|
|
418
|
-
if (after) {
|
|
419
|
-
await this.patchRegistry(name, {
|
|
420
|
-
status: 'installed', source: opts.source || 'shared', sourceRef: cid, trust: 'unverified',
|
|
421
|
-
contentHash: after.contentHash, version: after.version,
|
|
422
|
-
}, h);
|
|
423
|
-
}
|
|
424
|
-
return { ok: true, name, version: fetched.bundle.version, skill: (await this.inspect(name, { home: h })) || undefined };
|
|
425
|
-
}
|
|
426
|
-
/** 从已解析的技能包装入 (本地文件/已取到的包) */
|
|
427
403
|
async install(bundleJson, opts = {}) {
|
|
428
404
|
// 2026-09-16 (2-G.3): 同样走事务 (install 的默认来源保持 'imported')
|
|
429
405
|
const tx = await this.importTransactional({ bundleJson }, { ...opts, source: opts.source || 'imported' });
|
|
@@ -56,6 +56,8 @@ export function canonicalizeToolCallArguments(argStr) {
|
|
|
56
56
|
}
|
|
57
57
|
/** 2026-08-11 (Hermes build_worker_context 全限幅): 工具输出进 LLM 上下文的上限 */
|
|
58
58
|
export const TOOL_RESULT_MAX_CHARS = 12_000;
|
|
59
|
+
/** 2026-10-12: 单工具执行超时 (慢工具不再拖死整轮; 超时 → 失败结果 + 续跑提示给 LLM 下轮决策) */
|
|
60
|
+
export const TOOL_EXEC_TIMEOUT_MS = (typeof process !== 'undefined' && Number(process.env.BOLLOON_TOOL_TIMEOUT_MS)) || 120_000;
|
|
59
61
|
/** 截断工具输出 (纯函数, 可单测): 超限 → 保留前 max + 可见省略标记 */
|
|
60
62
|
export function truncateToolOutput(output, max = TOOL_RESULT_MAX_CHARS) {
|
|
61
63
|
const s = output ?? '';
|
|
@@ -63,6 +65,34 @@ export function truncateToolOutput(output, max = TOOL_RESULT_MAX_CHARS) {
|
|
|
63
65
|
return s;
|
|
64
66
|
return s.slice(0, max) + `\n… [truncated, ${s.length - max} chars omitted]`;
|
|
65
67
|
}
|
|
68
|
+
/** 2026-10-12 (leo: 「每个命令后面放一个命令执行时间的读秒」): 毫秒 → 人读耗时串 (纯函数, 可单测) */
|
|
69
|
+
export function formatDuration(ms) {
|
|
70
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
71
|
+
return '(耗时未知)';
|
|
72
|
+
if (ms < 1000)
|
|
73
|
+
return `${ms}ms`;
|
|
74
|
+
const s = ms / 1000;
|
|
75
|
+
if (s < 60)
|
|
76
|
+
return `${s.toFixed(1)}s`;
|
|
77
|
+
const m = Math.floor(s / 60);
|
|
78
|
+
const rem = Math.round(s % 60);
|
|
79
|
+
return `${m}m${rem}s`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 2026-10-12 (leo: 「对于 terminal 执行的命令, 你可以提供一个压缩的摘要」):
|
|
83
|
+
* 工具输出进 UI 前的摘要 (纯函数, 可单测) —— 只压 `terminal`/`shell` 类长输出:
|
|
84
|
+
* 折叠空白、截断到 ~300 字符、保留头部 (命令结果的开头最有信息量)。
|
|
85
|
+
* 其它工具原样返回 (read_file 等短输出没必要压)。
|
|
86
|
+
*/
|
|
87
|
+
export function summarizeToolOutput(toolName, output) {
|
|
88
|
+
const s = output ?? '';
|
|
89
|
+
if (!/terminal|shell|bash|exec/i.test(toolName))
|
|
90
|
+
return s;
|
|
91
|
+
const collapsed = s.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
|
|
92
|
+
if (collapsed.length <= 300)
|
|
93
|
+
return collapsed;
|
|
94
|
+
return collapsed.slice(0, 300) + `\n… [摘要, 原输出 ${s.length} 字符]`;
|
|
95
|
+
}
|
|
66
96
|
/**
|
|
67
97
|
* Analyze input to determine task complexity
|
|
68
98
|
*/
|
|
@@ -386,24 +416,14 @@ export class WorkflowPivotLoop {
|
|
|
386
416
|
const nativeTools = this.nativeToolCallsToDefinitions(llmResponse.toolCalls || []);
|
|
387
417
|
const pendingTools = nativeTools.length > 0 ? nativeTools : this.extractPendingToolUses(reply);
|
|
388
418
|
if (pendingTools.length === 0) {
|
|
389
|
-
// 2026-
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
response = cleaned || reply;
|
|
395
|
-
this.emit({
|
|
396
|
-
type: 'status', internal: true, // K4-B: 循环遥测 = 内部运行日志 (口径同老 loop)
|
|
397
|
-
content: `✅ 检测到 <final gen> 结束标记, 立即退出 (iter=${this.state.iteration})`,
|
|
398
|
-
tool: 'system',
|
|
399
|
-
});
|
|
400
|
-
return this.createResult(true, response, 'final_gen_marker');
|
|
401
|
-
}
|
|
419
|
+
// 2026-10-12 (leo 拍板: 全项目去掉 <final gen> 截止符):
|
|
420
|
+
// 旧实现这里检测 `<final gen>` 标记 → 立即 break 退出 —— 模型在中间轮次
|
|
421
|
+
// echo 出 "> <final gen>" 类文本 (思考引用/复述契约) 就被正则命中, 循环提前截止,
|
|
422
|
+
// 正是「中间生成了内容, 没办法衔接上就停止了」的根因之一。
|
|
423
|
+
// 现在循环停止只剩: 质量达标 / 连续无进展 / 用户中止 (leo 确认的三条路)。
|
|
402
424
|
// Check if the reply contains tool call intent but couldn't be parsed
|
|
403
|
-
// 2026-07-06: 排除合法的 思考/<final gen> 这些 tag (它们是 sentinel 不是 tool call)
|
|
404
425
|
const cleanedForIntentCheck = reply
|
|
405
|
-
.replace(
|
|
406
|
-
.replace(/<\/?think(?:ing)?>/gi, ''); // 移除
|
|
426
|
+
.replace(/<\/?think(?:ing)?>/gi, ''); // 移除 think 块
|
|
407
427
|
const containsToolCallIntent = cleanedForIntentCheck.includes('调用工具') || cleanedForIntentCheck.includes('tool(') ||
|
|
408
428
|
cleanedForIntentCheck.includes('使用工具') || cleanedForIntentCheck.includes('需要获取') || cleanedForIntentCheck.includes('需要查看') ||
|
|
409
429
|
cleanedForIntentCheck.includes('tool =>') || cleanedForIntentCheck.includes('[TOOL_CALL]') ||
|
|
@@ -421,6 +441,12 @@ export class WorkflowPivotLoop {
|
|
|
421
441
|
}
|
|
422
442
|
// No pending tool uses - this is a normal completion
|
|
423
443
|
this.state.pendingToolUses = [];
|
|
444
|
+
// 2026-10-12 (leo: 「任务进行到一半就卡住了」): **0 工具轮也要把回复写进历史**。
|
|
445
|
+
// 旧代码只在工具执行分支 push assistant —— 纯回复轮 (模型给最终回答) 不进 history,
|
|
446
|
+
// 于是: ① 质量不达标被打回重跑时, 模型下一轮**看不到自己上轮说过什么**;
|
|
447
|
+
// ② 循环因无进展退出时, 兜底取 `lastAssistant?.content` 是**最后一次工具轮**的
|
|
448
|
+
// 空 content ⇒ 屏上 `│ │` 空框。现在: 每轮回复都进历史, 兜底能拿到真正的最终答复。
|
|
449
|
+
this.messageHistory.push({ role: 'assistant', content: reply });
|
|
424
450
|
// Evaluate quality before accepting
|
|
425
451
|
const quality = this.evaluateQuality(reply);
|
|
426
452
|
this.state.qualityScores.push(quality);
|
|
@@ -429,32 +455,38 @@ export class WorkflowPivotLoop {
|
|
|
429
455
|
content: `✅ 检测到最终回复 (质量: ${(quality * 10).toFixed(1)}/10)`,
|
|
430
456
|
tool: 'system'
|
|
431
457
|
});
|
|
432
|
-
//
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
458
|
+
// 2026-10-12 (leo: 「循环跑 1 次就截止, 要能多轮」): 质量未达标时旧代码在
|
|
459
|
+
// iter >= minIterations (默认 2) 就**直接收下** (no_pending_tools) —— 长任务
|
|
460
|
+
// 经常第 2 轮就给出不完整回答被收了, 正是「中间生成内容但衔接不上就停止」。
|
|
461
|
+
// 现在: 质量未达标一律**注入续跑提示 + 继续循环**, 由 maxConsecutiveNoProgress 兜底
|
|
462
|
+
// (真有进展的会持续往下, 空转的到阈值才停) —— 该停才停, 不是早停。
|
|
463
|
+
if (quality < effectiveConfig.qualityThreshold) {
|
|
464
|
+
this.pushContinuationHint(`上一轮回答质量评测 ${(quality * 10).toFixed(1)}/${(effectiveConfig.qualityThreshold * 10).toFixed(1)}, 未达完成标准 — 请对照用户需求检查是否还有未完成的子目标, 需要工具就继续调用, 全部完成再给出完整最终回答.`);
|
|
465
|
+
this.state.consecutiveNoProgress++;
|
|
466
|
+
this.emit({
|
|
467
|
+
type: 'status', internal: true, // K4-B: 循环遥测 = 内部运行日志 (口径同老 loop)
|
|
468
|
+
content: `📊 质量未达标 (${(quality * 10).toFixed(1)}/${(effectiveConfig.qualityThreshold * 10).toFixed(1)}),注入续跑提示,继续循环`,
|
|
469
|
+
tool: 'system'
|
|
470
|
+
});
|
|
471
|
+
continue;
|
|
442
472
|
}
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
this.
|
|
446
|
-
type: 'status', internal: true, // K4-B: 循环遥测 = 内部运行日志 (口径同老 loop)
|
|
447
|
-
content: `📊 质量未达标 (${(quality * 10).toFixed(1)}/${(effectiveConfig.qualityThreshold * 10).toFixed(1)}),继续循环`,
|
|
448
|
-
tool: 'system'
|
|
449
|
-
});
|
|
450
|
-
continue;
|
|
473
|
+
// Quality met — accept
|
|
474
|
+
response = reply;
|
|
475
|
+
return this.createResult(true, reply, 'quality_threshold_met');
|
|
451
476
|
}
|
|
452
477
|
// We have pending tool uses - execute them
|
|
453
478
|
this.state.pendingToolUses = pendingTools;
|
|
454
479
|
this.state.lastMeaningfulWork = this.state.iteration;
|
|
455
480
|
this.state.consecutiveNoProgress = 0;
|
|
456
|
-
|
|
481
|
+
// 2026-10-12 (leo: 「运行工具的时候没有在后台运行, 花了太久, 把时间给 pass 了」):
|
|
482
|
+
// 旧实现 for...of 串行 await + **无超时** —— 一个慢工具 (网络/重 IO) 拖住整轮,
|
|
483
|
+
// 后面的工具全被阻塞, 轮次预算被耗在等待上。
|
|
484
|
+
// 现在: ① 同一轮的多个工具**并行**执行 (老 loop 早已 Promise.all, pivot 是行为退化);
|
|
485
|
+
// ② 每个工具加超时包装 (默认 120s, BOLLOON_TOOL_TIMEOUT_MS 可调) —— 超时返回
|
|
486
|
+
// 失败结果 + 续跑提示, 让 LLM 下轮决定换法/重试, 而不是无限挂起。
|
|
487
|
+
const execTool = async (toolCall) => {
|
|
457
488
|
this.state.toolCallsCount++;
|
|
489
|
+
const toolT0 = Date.now(); // 2026-10-12: 单工具耗时 (step_done 读秒用)
|
|
458
490
|
const tool = this.tools.get(toolCall.name);
|
|
459
491
|
if (!tool) {
|
|
460
492
|
this.emit({
|
|
@@ -478,7 +510,7 @@ export class WorkflowPivotLoop {
|
|
|
478
510
|
role: 'tool',
|
|
479
511
|
content: JSON.stringify({ success: false, error: `Unknown tool: ${toolCall.name}` })
|
|
480
512
|
});
|
|
481
|
-
|
|
513
|
+
return;
|
|
482
514
|
}
|
|
483
515
|
this.emit({
|
|
484
516
|
type: 'tool',
|
|
@@ -515,18 +547,38 @@ export class WorkflowPivotLoop {
|
|
|
515
547
|
// **fail-closed**: 端口抛错 ⇒ 记拒执行文案且**绝不回落** `tool.execute` ——
|
|
516
548
|
// 门坏掉若静默变成"没门"(旧实现那两条 fail-open 路径), 比没有门更危险。
|
|
517
549
|
let result;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
550
|
+
const executeFn = this.config.guardedExecute
|
|
551
|
+
? () => this.config.guardedExecute(tool, toolCall.args ?? {})
|
|
552
|
+
: () => tool.execute(toolCall.args ?? {});
|
|
553
|
+
// 2026-10-12: 超时包装 — 慢工具不再拖死整轮 (失败结果给 LLM 下轮决策)
|
|
554
|
+
const toolTimeoutMs = TOOL_EXEC_TIMEOUT_MS;
|
|
555
|
+
// 2026-10-13 (leo: 「发送消息过程中的打断和衔接整个流程」): 工具执行阶段也要响应 abort —
|
|
556
|
+
// 之前 Promise.race 只有 executeFn + 超时两路, Ctrl+C 打断要等工具跑完/超时 (最长 120s) 才生效。
|
|
557
|
+
// 现在加第三路 abort: signal 触发立即返回"已中断"结果 → while 顶部 signal.aborted 检查 break。
|
|
558
|
+
const abortWait = new Promise((resolve) => {
|
|
559
|
+
const onAbort = () => resolve({ success: false, error: `已中断: ${toolCall.name} 被打断 (Ctrl+C)` });
|
|
560
|
+
if (signal?.aborted) {
|
|
561
|
+
onAbort();
|
|
562
|
+
return;
|
|
521
563
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
564
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
565
|
+
});
|
|
566
|
+
result = await Promise.race([
|
|
567
|
+
(async () => {
|
|
568
|
+
try {
|
|
569
|
+
return await executeFn();
|
|
570
|
+
}
|
|
571
|
+
catch (guardErr) {
|
|
572
|
+
const msg = String(guardErr?.message ?? guardErr);
|
|
573
|
+
return { success: false, error: `拒绝: [harness-error] ${msg}` };
|
|
574
|
+
}
|
|
575
|
+
})(),
|
|
576
|
+
new Promise((resolve) => setTimeout(() => {
|
|
577
|
+
resolve({ success: false, error: `工具超时 (${toolTimeoutMs}ms): ${toolCall.name} 未在时限内返回` });
|
|
578
|
+
}, toolTimeoutMs).unref?.()),
|
|
579
|
+
abortWait,
|
|
580
|
+
]);
|
|
581
|
+
const toolCostMs = Date.now() - toolT0; // 2026-10-12: 实际耗时 (含等待)
|
|
530
582
|
// 2026-08-11: 输出过大 → 续跑提示 (Hermes dropped-tools continuation 模式),
|
|
531
583
|
// 上下文后续可能被压缩, 提醒 LLM 需要精确数据时缩小范围重查
|
|
532
584
|
const outLen = (result.output || '').length;
|
|
@@ -537,19 +589,25 @@ export class WorkflowPivotLoop {
|
|
|
537
589
|
this.emit({
|
|
538
590
|
type: result.success ? 'step_done' : 'step_error',
|
|
539
591
|
content: result.success
|
|
540
|
-
? `${toolCall.name}
|
|
541
|
-
: `${toolCall.name} 失败: ${result.error}`,
|
|
592
|
+
? `${toolCall.name} 成功 · ${formatDuration(toolCostMs)}`
|
|
593
|
+
: `${toolCall.name} 失败: ${result.error}`, // 失败文案带原因即可, 耗时由 UI 从 durationMs 读
|
|
542
594
|
tool: toolCall.name,
|
|
543
595
|
success: result.success,
|
|
544
|
-
output: result.output,
|
|
596
|
+
output: summarizeToolOutput(toolCall.name, result.output),
|
|
545
597
|
error: result.error,
|
|
598
|
+
durationMs: toolCostMs,
|
|
546
599
|
});
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
600
|
+
// 2026-10-12 (leo: 「✅ read_file 成功 不用发出来」):
|
|
601
|
+
// 成功不再发 `✅ X 成功` 状态行 —— step_done 已带 工具名+耗时+摘要, 重复的
|
|
602
|
+
// 成功汇报纯属噪音 (用户原话: 「这种执行工具成功的汇报不用发出来这个 log,
|
|
603
|
+
// 只需要把工具执行的记录写清楚就行了」)。**失败保留** (`❌` error 事件,
|
|
604
|
+
// 需人介入的信号不许吞)。
|
|
605
|
+
if (!result.success) {
|
|
606
|
+
this.emit({
|
|
607
|
+
type: 'error',
|
|
608
|
+
content: `❌ ${toolCall.name} 失败: ${result.error}`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
553
611
|
this.messageHistory.push({
|
|
554
612
|
role: 'assistant',
|
|
555
613
|
content: reply,
|
|
@@ -574,9 +632,29 @@ export class WorkflowPivotLoop {
|
|
|
574
632
|
content: JSON.stringify({ success: false, error: String(execError) })
|
|
575
633
|
});
|
|
576
634
|
}
|
|
577
|
-
}
|
|
635
|
+
};
|
|
636
|
+
await Promise.all(pendingTools.map(execTool));
|
|
578
637
|
}
|
|
579
638
|
catch (error) {
|
|
639
|
+
// 2026-10-13 (leo: 「多步执行的任务会返回空, 直接回复的不会」): 根因是**模型调用超时杀死循环**。
|
|
640
|
+
// 多步任务工具多轮后上下文膨胀 (40KB+), kernel 模型调用 30s 硬超时抛 ModelTimeoutError,
|
|
641
|
+
// 旧代码一律当「循环崩溃」处理 (return createResult(false, response, 'error')) ⇒
|
|
642
|
+
// 工具全干完、response 还是空 → 屏上 `│ │` 空框。
|
|
643
|
+
// 修复: 超时 = 供应商慢, 不是循环崩。emit 状态 + continue 重试下一轮;
|
|
644
|
+
// 真卡死由 maxConsecutiveNoProgress 兜底 (每条路都有终点, 唯独不该有「白干+空框」)。
|
|
645
|
+
const isTimeout = error instanceof Error && (error.name === 'ModelTimeoutError' || /模型调用超时/.test(error.message));
|
|
646
|
+
if (isTimeout) {
|
|
647
|
+
this.state.consecutiveNoProgress++;
|
|
648
|
+
this.emit({
|
|
649
|
+
type: 'status', internal: true, // K4-B: 循环遥测 = 内部运行日志 (口径同老 loop)
|
|
650
|
+
content: `⏳ 模型调用超时 (${String(error.message).replace(/^模型调用超时\s*\(/, '').replace(/\)$/, '')}), 重试下一轮 (连续 ${this.state.consecutiveNoProgress}/${effectiveConfig.maxConsecutiveNoProgress} 次则停)`,
|
|
651
|
+
tool: 'system',
|
|
652
|
+
});
|
|
653
|
+
// 2026-10-13 (leo: 「可以不用超时截止来确定时间周期, 有时候任务需要时间会比较久」):
|
|
654
|
+
// 超时只是这一轮没来得及, 任务该做多久做多久 —— **不**注入「快点给个回答」的收敛提示
|
|
655
|
+
// (那会让模型在任务没做完时草草收尾)。继续推进, 由质量门/无进展兜底决定什么时候算完。
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
580
658
|
this.emit({
|
|
581
659
|
type: 'error',
|
|
582
660
|
content: `❌ 循环异常: ${error}`
|
|
@@ -618,12 +696,18 @@ export class WorkflowPivotLoop {
|
|
|
618
696
|
}
|
|
619
697
|
/**
|
|
620
698
|
* Adapt configuration based on task profile
|
|
699
|
+
*
|
|
700
|
+
* 2026-10-12 (leo: 「工具轮次到了 10 次就截止了」): 旧实现用
|
|
701
|
+
* `Math.min(config.maxIterations, profile.suggestedMaxIterations)` 压迭代上限、
|
|
702
|
+
* `Math.min(config.maxTokenBudget, profile.tokenBudget)` 压 token 预算 ——
|
|
703
|
+
* simple 任务被压到 **10 iter + 800~2400 token**, 而 systemPrompt 本身就 40KB+,
|
|
704
|
+
* 第一次迭代 totalTokens 就超支 → 循环立刻 break, 长任务根本跑不完。
|
|
705
|
+
* 上限保留 createDefaultPivotConfig 的 200/1000/2000 (本来就注释「跑到 N 次也不报超时」),
|
|
706
|
+
* 由质量门 / maxConsecutiveNoProgress / user abort 兜底 —— 循环持久, 该停才停。
|
|
621
707
|
*/
|
|
622
708
|
adaptConfigForTask(profile) {
|
|
623
709
|
return {
|
|
624
710
|
...this.config,
|
|
625
|
-
maxIterations: Math.min(this.config.maxIterations, profile.suggestedMaxIterations),
|
|
626
|
-
maxTokenBudget: Math.min(this.config.maxTokenBudget, profile.tokenBudget),
|
|
627
711
|
complexity: profile.complexity
|
|
628
712
|
};
|
|
629
713
|
}
|
|
@@ -847,6 +931,13 @@ export class WorkflowPivotLoop {
|
|
|
847
931
|
* Evaluate response quality
|
|
848
932
|
*/
|
|
849
933
|
evaluateQuality(response) {
|
|
934
|
+
// 2026-10-13 (leo: 「我发了一个命令, 返回空」): **空回复必须判不合格** —— 旧实现起点 0.7,
|
|
935
|
+
// 空串 '' 也有 0.7 分, 恰好等于默认阈值 ⇒ 空 reply 直接 quality_threshold_met 收尾 ⇒ CLI 空框
|
|
936
|
+
// (「你好」正常因为模型给文本; 「能看到我的仓库吗?」模型某轮返回空 content/tool-only 回复 ⇒ 空串过关)。
|
|
937
|
+
// 现在: 空/纯空白一律 0.3 (低于任何阈值), 打回让模型重给; 非空保持旧评分。
|
|
938
|
+
const t = String(response || '').trim();
|
|
939
|
+
if (!t)
|
|
940
|
+
return 0.3;
|
|
850
941
|
let score = 0.7; // 2026-07-06: 起点 0.7 — 短回复不应被当成低质量打回去重跑
|
|
851
942
|
// Length bonus
|
|
852
943
|
if (response.length > 100)
|
|
@@ -908,7 +999,7 @@ export class WorkflowPivotLoop {
|
|
|
908
999
|
const identityLine = lines.find((l) => l.includes('你是') && !l.includes('(续)')) ?? '';
|
|
909
1000
|
return `[continuation] 你的 persona + tools + judgments 已在首轮 messageHistory 中.
|
|
910
1001
|
${identityLine ? identityLine + '\n' : ''}当前 step 别再读 persona 全文, 继续推进任务即可.
|
|
911
|
-
>
|
|
1002
|
+
> 任务完成前持续调用工具推进; 全部完成后给出完整最终回答.`;
|
|
912
1003
|
}
|
|
913
1004
|
/**
|
|
914
1005
|
* Create result object
|
|
@@ -972,7 +1063,7 @@ ${identityLine ? identityLine + '\n' : ''}当前 step 别再读 persona 全文,
|
|
|
972
1063
|
*/
|
|
973
1064
|
export function createDefaultPivotConfig(complexity) {
|
|
974
1065
|
// 2026-07-06: maxIterations 默认大幅上调 — LLM 自己约束结束时机, 持久循环.
|
|
975
|
-
//
|
|
1066
|
+
// 2026-10-12: 真实停止由 质量达标 / 连续无进展 / user abort 触发 (leo 拍板去掉 <final gen>).
|
|
976
1067
|
// 旧 30/60 iter 在长任务 (eg. 大文档改写) 太短, 现在跑到 N 次也不报超时.
|
|
977
1068
|
const profiles = {
|
|
978
1069
|
simple: {
|