@bolloon/bolloon-agent 0.6.5 → 0.6.6
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/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 +359 -0
- 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-tools.js +180 -5
- package/dist/agents/pi-sdk.js +143 -9
- 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 +2 -4
- package/dist/bootstrap/context-hierarchy.js +13 -1
- 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 +404 -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-tools.js +180 -5
- package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +143 -9
- 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 +2 -4
- 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/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 +96 -35
- package/dist/electron-build/llm/pi-ai.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 +2 -2
- 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 +106 -107
- package/dist/ios/mobile-agent.js +346 -45
- package/dist/ios/mobile-core.js +7303 -4590
- package/dist/ios/mobile-harness.js +1004 -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 +106 -107
- package/dist/ios/mobile.js +300 -132
- package/dist/ios/routes-world.js +4 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +13 -5
- 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 +1 -1
- package/dist/kernel/index.js +33 -0
- package/dist/kernel/model-runtime.js +8 -1
- package/dist/kernel/plan.js +3 -3
- 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 +94 -34
- 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 +351 -45
- package/dist/web/mobile-contacts.js +0 -4
- package/dist/web/mobile-core.js +7430 -4590
- package/dist/web/mobile-harness.js +1004 -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 +121 -107
- package/dist/web/mobile.js +409 -136
- package/dist/web/routes-contacts.js +94 -1
- package/dist/web/routes-world.js +4 -1
- package/dist/web/server.js +4 -2
- 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
|
@@ -13,6 +13,7 @@ import { checkWritePath } from './shell-guard.js';
|
|
|
13
13
|
import { p2pDocumentTools } from './p2p-document-tools.js';
|
|
14
14
|
import { runSelfImproveLoop } from './pi-sdk-session-factory.js';
|
|
15
15
|
import { getMinimax } from '../constraints/index.js';
|
|
16
|
+
import { llmConfigStore } from '../llm/config-store.js';
|
|
16
17
|
import { delegateToEngine, buildDelegateCommand } from '../external-engines/delegate.js';
|
|
17
18
|
import { spawnBackground } from './process-runner.js';
|
|
18
19
|
import { DELEGATE_ENV_MARK } from './background-notices.js';
|
|
@@ -858,11 +859,9 @@ export function registerBuiltinTools(ctx) {
|
|
|
858
859
|
// 与 shell_exec 的区别: 直接接受完整 shell 命令字符串, 更适合模型自主写命令.
|
|
859
860
|
// 2026-10-01 (优化 #2 v1): **工具发现** —— 125 个工具不可能都记住, 给一个"按关键词找工具"的入口。
|
|
860
861
|
// 空关键词只回**分类索引**(名称清单), 有关键词才回"名称+用途摘要" ⇒ 不刷屏。
|
|
861
|
-
// 2026-10-01 (用户: 「skills 教训学习, bolloon
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
// 她那边的做法: 启动自动装载 + 按**描述**在提示里露面 + 需要时用 `skill_view` 读全文 ✓。
|
|
865
|
-
// bolloon 有 1300 个(她百来号) ⇒ 全量注入会撑爆提示 ✗ ⇒ 改成**两个发现工具**(和 `list_tools` 同一思路 ✓)。
|
|
862
|
+
// 2026-10-01 (用户: 「skills 教训学习, bolloon 有吗」): 技能发现 = 两个工具而非全量注入 —
|
|
863
|
+
// 技能库上千个, 全量注入会撑爆提示 (历史: 19 个 skill 曾把 systemPrompt 顶到 22K 导致模型不输出)。
|
|
864
|
+
// 做法同 `list_tools`: 按关键词找 (`list_skills`) → 读全文 (`read_skill`)。
|
|
866
865
|
ctx.tools.set('list_skills', {
|
|
867
866
|
name: 'list_skills',
|
|
868
867
|
description: '用于**找技能**: 给关键词(中文/英文都行)⇒ 返回已装载技能的名字+说明(≤20 条); 不给关键词 ⇒ 先给总数与前若干条. 找到后用 read_skill 读全文再照做.',
|
|
@@ -4445,6 +4444,182 @@ export function registerWalletTools(ctx) {
|
|
|
4445
4444
|
}
|
|
4446
4445
|
}
|
|
4447
4446
|
});
|
|
4447
|
+
// 2026-10-14 (leo: 「需要向量记忆和去中心化记忆」, P0): 结构化记忆工具
|
|
4448
|
+
// —— MemoryEntry 本地 JSONL 持久化; embedding 向量 (P1) / OrbitDB 复制 (P3) 后续接
|
|
4449
|
+
const memoryAgentId = () => ctx.identity?.did || String(ctx.cwd || '');
|
|
4450
|
+
/**
|
|
4451
|
+
* P1: 记忆 embedding 配置。读 llm-config 的 `embedding` 段:
|
|
4452
|
+
* { embedding: { provider, model, baseUrl?, apiKey?, groupId? } }
|
|
4453
|
+
* 未配置 / provider 不可用 → { provider: 'none' } (记忆层回退关键词, 行为不变)。
|
|
4454
|
+
* 诚实边界: 本仓现有 provider (glm 中转 / minimax 订阅 key) 实测无可用 embedding,
|
|
4455
|
+
* 故默认 none; 将来任一供应商 key 就绪, 在此配置段填上即可, 记忆层零改动。
|
|
4456
|
+
*/
|
|
4457
|
+
const getMemoryEmbeddingConfig = () => {
|
|
4458
|
+
try {
|
|
4459
|
+
const cfg = llmConfigStore?.config;
|
|
4460
|
+
const emb = cfg?.embedding;
|
|
4461
|
+
if (emb && typeof emb === 'object' && emb.provider && emb.provider !== 'none') {
|
|
4462
|
+
return {
|
|
4463
|
+
provider: String(emb.provider),
|
|
4464
|
+
model: emb.model ? String(emb.model) : undefined,
|
|
4465
|
+
baseUrl: emb.baseUrl ? String(emb.baseUrl) : undefined,
|
|
4466
|
+
apiKey: emb.apiKey ? String(emb.apiKey) : undefined,
|
|
4467
|
+
groupId: emb.groupId ? String(emb.groupId) : undefined,
|
|
4468
|
+
};
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
catch { /* 配置读取失败 → none */ }
|
|
4472
|
+
return { provider: 'none' };
|
|
4473
|
+
};
|
|
4474
|
+
ctx.tools.set('memory_save', {
|
|
4475
|
+
name: 'memory_save',
|
|
4476
|
+
description: '保存一条**结构化长期记忆** (事实/偏好/洞察/事件/关系/技能反馈)。用于: 用户明确说"记住 X"、反复出现的偏好、值得跨会话保留的事实。自动去重 (同内容不重复), 自动打分重要性。',
|
|
4477
|
+
parameters: {
|
|
4478
|
+
content: '记忆内容 (必填, ≥2 字符)',
|
|
4479
|
+
kind: '类型: fact | insight | preference | event | relationship | skill-feedback (默认 fact)',
|
|
4480
|
+
importance: '手动重要性 0-1 (可选, 缺省自动启发式打分)',
|
|
4481
|
+
refs: '关联证据 JSON 数组 (可选, 如 goalId/runId)',
|
|
4482
|
+
},
|
|
4483
|
+
execute: async (args) => {
|
|
4484
|
+
try {
|
|
4485
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4486
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4487
|
+
// P1: 有 embedding adapter 时 save 顺便向量化 (存 vec, 供语义召回)
|
|
4488
|
+
let adapter;
|
|
4489
|
+
try {
|
|
4490
|
+
const { createEmbeddingAdapter } = await import('../llm/embedding-adapter.js');
|
|
4491
|
+
const a = createEmbeddingAdapter(getMemoryEmbeddingConfig());
|
|
4492
|
+
if (a.provider !== 'none')
|
|
4493
|
+
adapter = a;
|
|
4494
|
+
}
|
|
4495
|
+
catch { /* 无向量能力 → 只存文本 */ }
|
|
4496
|
+
const entry = await store.save({
|
|
4497
|
+
content: String(args.content || '').trim(),
|
|
4498
|
+
kind: args.kind || 'fact',
|
|
4499
|
+
importance: args.importance !== undefined ? Number(args.importance) : undefined,
|
|
4500
|
+
refs: Array.isArray(args.refs) ? args.refs.map(String) : undefined,
|
|
4501
|
+
source: 'tool',
|
|
4502
|
+
}, adapter);
|
|
4503
|
+
// P3: 条目级 OrbitDB 同步 (失败静默 — 本地记忆仍权威)
|
|
4504
|
+
try {
|
|
4505
|
+
const { syncMemoryEntryToOrbit } = await import('../orbitdb/memory-sync.js');
|
|
4506
|
+
await syncMemoryEntryToOrbit(entry);
|
|
4507
|
+
}
|
|
4508
|
+
catch { /* 同步失败不阻塞保存 */ }
|
|
4509
|
+
return { success: true, output: `✅ 已保存记忆 #${entry.id.slice(0, 8)} (${entry.kind}, 重要性 ${entry.importance.toFixed(2)})` };
|
|
4510
|
+
}
|
|
4511
|
+
catch (e) {
|
|
4512
|
+
return { success: false, error: `memory_save 失败: ${String(e.message || e)}` };
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
});
|
|
4516
|
+
ctx.tools.set('memory_recall', {
|
|
4517
|
+
name: 'memory_recall',
|
|
4518
|
+
description: '按查询关键词**召回长期记忆** (事实/偏好/洞察)。每轮对话已自动做记忆召回, 这里用于显式深挖: 用户说"你还记得我之前说的 X"、或当前任务需要历史上下文时调用。',
|
|
4519
|
+
parameters: {
|
|
4520
|
+
query: '查询内容 (必填, 用关键词)',
|
|
4521
|
+
limit: '返回条数 (默认 3)',
|
|
4522
|
+
},
|
|
4523
|
+
execute: async (args) => {
|
|
4524
|
+
try {
|
|
4525
|
+
const { getMemoryStore, formatRecallBlock } = await import('./memory-store.js');
|
|
4526
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4527
|
+
await store.load();
|
|
4528
|
+
// P3: 召回前先拉远端条目合并 (LWW, updatedAt 大者胜; 网络不可用静默)
|
|
4529
|
+
try {
|
|
4530
|
+
const { fetchRemoteEntries } = await import('../orbitdb/memory-sync.js');
|
|
4531
|
+
const remote = await fetchRemoteEntries(memoryAgentId());
|
|
4532
|
+
if (remote.length > 0)
|
|
4533
|
+
await store.mergeRemote(remote);
|
|
4534
|
+
}
|
|
4535
|
+
catch { /* 远端拉取失败 → 只用本地 */ }
|
|
4536
|
+
const query = String(args.query || '');
|
|
4537
|
+
// P1: 有 embedding adapter 时走向量召回; 无 → 关键词回退 (P0 行为不变)
|
|
4538
|
+
let queryVec;
|
|
4539
|
+
try {
|
|
4540
|
+
const { createEmbeddingAdapter } = await import('../llm/embedding-adapter.js');
|
|
4541
|
+
const adapter = createEmbeddingAdapter(getMemoryEmbeddingConfig());
|
|
4542
|
+
if (adapter.provider !== 'none' && query.trim()) {
|
|
4543
|
+
queryVec = await adapter.embed(query);
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
catch { /* vector 不可用 → 关键词回退 */ }
|
|
4547
|
+
const hits = await store.recall({
|
|
4548
|
+
query,
|
|
4549
|
+
limit: args.limit ? Number(args.limit) : 3,
|
|
4550
|
+
queryVec,
|
|
4551
|
+
});
|
|
4552
|
+
if (!hits.length)
|
|
4553
|
+
return { success: true, output: '📭 没有找到相关记忆 (可能是新话题或记忆未保存)' };
|
|
4554
|
+
return { success: true, output: formatRecallBlock(hits) };
|
|
4555
|
+
}
|
|
4556
|
+
catch (e) {
|
|
4557
|
+
return { success: false, error: `memory_recall 失败: ${String(e.message || e)}` };
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
});
|
|
4561
|
+
// P2: 价值分层工具 — 固化 / 遗忘 / 统计 (2026-10-11, decentralized-memory-design §2.7)
|
|
4562
|
+
ctx.tools.set('memory_crystallize', {
|
|
4563
|
+
name: 'memory_crystallize',
|
|
4564
|
+
description: '固化一条记忆 (crystallize): 标记为「重要, 不淡忘」+ importance 加成。用于: 用户明确说"这个要记住"、反复确认的重要约定、多次命中的核心事实。返回固化后的条目。',
|
|
4565
|
+
parameters: {
|
|
4566
|
+
entryId: '记忆 ID (从 memory_recall 的返回里拿, 形如 #xxxxxxxx)',
|
|
4567
|
+
},
|
|
4568
|
+
execute: async (args) => {
|
|
4569
|
+
try {
|
|
4570
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4571
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4572
|
+
await store.load();
|
|
4573
|
+
const id = String(args.entryId || '').replace(/^#/, '');
|
|
4574
|
+
const e = await store.crystallize(id);
|
|
4575
|
+
if (!e)
|
|
4576
|
+
return { success: false, error: `未找到记忆 #${id}` };
|
|
4577
|
+
return { success: true, output: `✅ 已固化记忆 #${e.id.slice(0, 8)} (${e.kind}, importance ${e.importance.toFixed(2)}, 不再淡忘)` };
|
|
4578
|
+
}
|
|
4579
|
+
catch (e) {
|
|
4580
|
+
return { success: false, error: `memory_crystallize 失败: ${String(e.message || e)}` };
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
});
|
|
4584
|
+
ctx.tools.set('memory_forget', {
|
|
4585
|
+
name: 'memory_forget',
|
|
4586
|
+
description: '删除一条记忆 (entryId 从 memory_recall 返回拿)。用于: 用户说"忘掉这个/删掉这条记忆"、记忆过时/错误。删除不可恢复。',
|
|
4587
|
+
parameters: {
|
|
4588
|
+
entryId: '记忆 ID (形如 #xxxxxxxx)',
|
|
4589
|
+
},
|
|
4590
|
+
execute: async (args) => {
|
|
4591
|
+
try {
|
|
4592
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4593
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4594
|
+
await store.load();
|
|
4595
|
+
const id = String(args.entryId || '').replace(/^#/, '');
|
|
4596
|
+
const ok = await store.remove(id);
|
|
4597
|
+
if (!ok)
|
|
4598
|
+
return { success: false, error: `未找到记忆 #${id}` };
|
|
4599
|
+
return { success: true, output: `🗑️ 已删除记忆 #${id.slice(0, 8)}` };
|
|
4600
|
+
}
|
|
4601
|
+
catch (e) {
|
|
4602
|
+
return { success: false, error: `memory_forget 失败: ${String(e.message || e)}` };
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
});
|
|
4606
|
+
ctx.tools.set('memory_stats', {
|
|
4607
|
+
name: 'memory_stats',
|
|
4608
|
+
description: '查看记忆库统计: 总条数 / 已固化 / 已淡化 (decay)。用于: 用户问"我存了多少记忆"、诊断记忆衰减是否生效。',
|
|
4609
|
+
parameters: {},
|
|
4610
|
+
execute: async () => {
|
|
4611
|
+
try {
|
|
4612
|
+
const { getMemoryStore } = await import('./memory-store.js');
|
|
4613
|
+
const store = getMemoryStore(memoryAgentId());
|
|
4614
|
+
await store.load();
|
|
4615
|
+
const s = store.stats();
|
|
4616
|
+
return { success: true, output: `📊 记忆统计: 共 ${s.total} 条 · 已固化 ${s.crystallized} · 已淡化 ${s.decayed}` };
|
|
4617
|
+
}
|
|
4618
|
+
catch (e) {
|
|
4619
|
+
return { success: false, error: `memory_stats 失败: ${String(e.message || e)}` };
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
});
|
|
4448
4623
|
// 2026-07-28: 注册 LSP 工具 (代码智能)
|
|
4449
4624
|
// registerBuiltinTools 不是 async, 用同步 import 兜底
|
|
4450
4625
|
try {
|
package/dist/agents/pi-sdk.js
CHANGED
|
@@ -63,6 +63,7 @@ import { initDocumentReceiver } from './p2p-document-tools.js';
|
|
|
63
63
|
import { startRun, recordStep, finishRun, readRun, recordDegradation, recordHarnessEvent, recordRecovery, setRunStatus, prepareResume, buildResumeInstruction, argsDigestOf, repeatedFailureCount, classifyError as classifyRunError } from './run-store.js';
|
|
64
64
|
import { createGoal, attachRun } from './goal-store.js';
|
|
65
65
|
import { captureRunModelConfig } from '../llm/model-selection.js';
|
|
66
|
+
// 2026-09-26: 工具名出网净化 (pi-ai.ts 唯一边界) + 回程派发还原 (原名 ↔ API 名)
|
|
66
67
|
// 2026-09-28 (前缀 KV 可命中): CURRENT TURN 注入标记 —— "当前轮是否已注入"的唯一判据
|
|
67
68
|
import { CURRENT_TURN_MARKER } from '../llm/pi-ai.js';
|
|
68
69
|
import { PiAgentHarness } from './pi-harness.js';
|
|
@@ -200,6 +201,10 @@ export class PiAgentSession {
|
|
|
200
201
|
identity;
|
|
201
202
|
persona = null;
|
|
202
203
|
minimaxAvailable = false;
|
|
204
|
+
/** 2026-10-13: pivot 路径会话累计真实消耗 (每轮 loop 的 totalTokens 累加, 刻度随会话单调增长) */
|
|
205
|
+
pivotCumulativeTokens = 0;
|
|
206
|
+
/** 2026-10-13: 上下文管理周期压缩在飞标志 (防一轮里重复触发) */
|
|
207
|
+
_ctxCycleCompacting = false;
|
|
203
208
|
workflows = new Map();
|
|
204
209
|
constraintLayer;
|
|
205
210
|
workflowEngine;
|
|
@@ -294,6 +299,8 @@ export class PiAgentSession {
|
|
|
294
299
|
/** 2026-06-30: tool registry 模块 — 独立 alias resolve, 测试可消融. */
|
|
295
300
|
_toolRegistry = new ToolRegistry();
|
|
296
301
|
skillRegistry = new SkillRegistry();
|
|
302
|
+
/** 2026-10-13: 技能扫描 promise (loadSkills 保存, buildSkillsIndex await) — 确保首次对话前技能已就绪 */
|
|
303
|
+
_skillsReady = Promise.resolve();
|
|
297
304
|
/** M2.4: 缓存 tool 列表, registerTools() 之后不变, runReActLoop 多次循环复用 */
|
|
298
305
|
cachedToolDefinitions = '';
|
|
299
306
|
/** M2.4: 缓存 persona section */
|
|
@@ -327,6 +334,41 @@ export class PiAgentSession {
|
|
|
327
334
|
return bg ? `${bg}\n${plans}` : plans;
|
|
328
335
|
}
|
|
329
336
|
cachedPersonaSection = '';
|
|
337
|
+
/**
|
|
338
|
+
* 2026-10-13 (leo: 「重新阅读理解,整体开发整个技能的实现方式… 成整个加载循环」):
|
|
339
|
+
* 技能库索引摘要 —— 注入 systemPrompt, 让模型**知道技能存在**(名字+一句话描述),
|
|
340
|
+
* 需要时用 `list_skills`(按关键词找) → `read_skill`(读全文, 结果进上下文) 按需加载。
|
|
341
|
+
* 为什么必须有: 技能描述**从不进提示** ⇒ 模型不知道 `read_skill` 该读什么 ⇒ 技能加载循环
|
|
342
|
+
* 断在起点 (此前 `describeSkill` 只 import 从不调用, 见 pi-sdk-tools.ts:915 注释)。
|
|
343
|
+
* 预算控制: 只列前 40 个 + 总数, 完整清单由 `list_skills` 按需取 —— 不撑爆 systemPrompt。
|
|
344
|
+
* 失败静默(返回 ''): 技能库读不到时循环退化成 "无技能", 不阻塞主流程。
|
|
345
|
+
*/
|
|
346
|
+
async buildSkillsIndex() {
|
|
347
|
+
// 2026-10-13 (leo: 「成整个加载循环」): 先等技能扫描 promise 落地再读 registry ——
|
|
348
|
+
// loadSkills 是构造期 fire-and-forget, 不 await 的话首次对话时 registry 还是空的。
|
|
349
|
+
try {
|
|
350
|
+
await this._skillsReady;
|
|
351
|
+
}
|
|
352
|
+
catch { /* 扫描失败不影响 (registry 为空时返回 '') */ }
|
|
353
|
+
try {
|
|
354
|
+
const all = this.skillRegistry.list();
|
|
355
|
+
if (!all.length)
|
|
356
|
+
return '';
|
|
357
|
+
const budget = 40;
|
|
358
|
+
const lines = all.slice(0, budget).map((sk) => {
|
|
359
|
+
const name = String(sk?.name ?? '').trim();
|
|
360
|
+
const desc = String(sk?.description ?? '').trim().replace(/\s+/g, ' ').slice(0, 100);
|
|
361
|
+
return name ? `- ${name}: ${desc}` : null;
|
|
362
|
+
}).filter(Boolean);
|
|
363
|
+
if (!lines.length)
|
|
364
|
+
return '';
|
|
365
|
+
const more = all.length > budget ? `\n…还有 ${all.length - budget} 个 (用 list_skills 按关键词找)` : '';
|
|
366
|
+
return `## 技能库 (${all.length} 个技能 — 需要时用 list_skills 找, read_skill 读全文执行; 内容进上下文, 按需加载不占首屏)\n${lines.join('\n')}${more}`;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return '';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
330
372
|
/** 本轮迭代预算 (每轮重置; 批处理工具会退还 —— 见 iteration-budget.ts) */
|
|
331
373
|
/** 上一个工具调用的参数指纹 (遥测判"是否重复调用") */
|
|
332
374
|
lastToolSig = null;
|
|
@@ -808,14 +850,16 @@ export class PiAgentSession {
|
|
|
808
850
|
* 2026-07-04: 移除 18 个 bollharness builtin skill (findBolloonBuiltinSkillsPath).
|
|
809
851
|
* 历史遗留: 写 pi-sdk 时为方便演示, 把 bolloon 项目里的 19 个 skill 强制注入到 system prompt.
|
|
810
852
|
* 问题: system prompt 涨到 22K chars, LLM (minimax M3) 在 pivot loop 里反复 think 不输出
|
|
811
|
-
*
|
|
853
|
+
* 最终回答, session 落盘拿不到最终回答.
|
|
812
854
|
* 现在: 只让用户放 .bolloon/skills/SKILL.md 才生效, 干净且 project-owned.
|
|
813
855
|
*
|
|
814
856
|
* 静默忽略不存在的目录.
|
|
815
857
|
*/
|
|
816
858
|
loadSkills(paths) {
|
|
817
859
|
const resolved = (paths && paths.length > 0) ? paths : defaultSkillPaths(os.homedir(), this.cwd);
|
|
818
|
-
|
|
860
|
+
// 2026-10-13 (leo: 「成整个加载循环」): 保存 promise 供 buildSkillsIndex await ——
|
|
861
|
+
// 原来 fire-and-forget, 构造后立即读 registry = 0 ⇒ 技能索引空 ⇒ 模型不知道技能 ⇒ 循环断在起点。
|
|
862
|
+
this._skillsReady = loadSkillsFromPaths(resolved)
|
|
819
863
|
.then((skills) => {
|
|
820
864
|
for (const s of skills) {
|
|
821
865
|
if (this.skillRegistry.has(s.name)) {
|
|
@@ -912,7 +956,7 @@ export class PiAgentSession {
|
|
|
912
956
|
_inboxMessages: this._inboxMessages,
|
|
913
957
|
getChannelWallet: async () => {
|
|
914
958
|
try {
|
|
915
|
-
const {
|
|
959
|
+
const {} = await import('../web/server-types.js');
|
|
916
960
|
const { loadChannels } = await import('../web/server-storage.js');
|
|
917
961
|
const channels = await loadChannels();
|
|
918
962
|
const ch = channels.find((c) => c.id === this.actor.state.channelId);
|
|
@@ -1035,10 +1079,50 @@ export class PiAgentSession {
|
|
|
1035
1079
|
* 之前只有 runReActLoop 迭代内上报, chitchat/fallback/pivot 路径状态栏恒 0 */
|
|
1036
1080
|
reportUsageToContextManager() {
|
|
1037
1081
|
try {
|
|
1038
|
-
getContextManager()
|
|
1082
|
+
const cm = getContextManager();
|
|
1083
|
+
const estimated = this.estimateHistoryTokens();
|
|
1084
|
+
// 2026-10-13 (leo: 刻度被覆盖回 0): pivot 路径在本轮内已上报真实消耗
|
|
1085
|
+
// (promptWithPivotLoop 内 updateUsage(pivotCumulative)); 这里若再用 actor 历史估算**覆盖**,
|
|
1086
|
+
// 刻度会被拉回 ~0 (pivot 路径 actor 历史每轮只有 1-2 条)。⇒ 取 max, 只升不降。
|
|
1087
|
+
cm.updateUsage(Math.max(estimated, cm.getUsage().usedTokens));
|
|
1039
1088
|
}
|
|
1040
1089
|
catch { /* 非致命 */ }
|
|
1041
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* 2026-10-13: 上下文管理周期 — 阈值触发压缩 (pivot 路径)。
|
|
1093
|
+
* ContextManager 的 markCompressStart/Complete + snapshot 原先只在 promptStream 的
|
|
1094
|
+
* maybeAutoCompact 里被调用; CLI 主路径 (prompt → promptWithPivotLoop) 从没触发过 ⇒
|
|
1095
|
+
* 刻度显示到 'compressing' 但永远不会真压缩。这里补上最小周期:
|
|
1096
|
+
* 累计上报 → stage 达压缩阈值 → 同步前 3 层压缩 (compressHistorySync, 零 LLM 成本) → snapshot。
|
|
1097
|
+
* 失败静默, 不阻塞主流程。
|
|
1098
|
+
*/
|
|
1099
|
+
async runContextCycle(usage) {
|
|
1100
|
+
try {
|
|
1101
|
+
const cm = getContextManager();
|
|
1102
|
+
const history = this.actor.state.messageHistory;
|
|
1103
|
+
if (history.length < 10) {
|
|
1104
|
+
console.log(`[PiAgent] 上下文管理周期: 刻度达压缩阈值 (${Math.round(usage.pct * 100)}%), 历史仅 ${history.length} 条, 跳过压缩 (stage=compressing 留作警示)`);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const beforeTokens = this.estimateHistoryTokens();
|
|
1108
|
+
cm.markCompressStart(beforeTokens);
|
|
1109
|
+
const compacted = this.compressHistorySync(history);
|
|
1110
|
+
this.replaceHistory(compacted);
|
|
1111
|
+
const afterTokens = this.estimateHistoryTokens();
|
|
1112
|
+
const snap = cm.makeSnapshot({
|
|
1113
|
+
beforeTokens,
|
|
1114
|
+
afterTokens,
|
|
1115
|
+
summary: `pivot 阈值压缩 (同步前3层): ${beforeTokens.toLocaleString()} → ${afterTokens.toLocaleString()} tokens`,
|
|
1116
|
+
agentId: this.actor.state.agentId,
|
|
1117
|
+
channelId: this.actor.state.channelId,
|
|
1118
|
+
});
|
|
1119
|
+
cm.markCompressComplete(snap);
|
|
1120
|
+
console.log(`[PiAgent] 上下文管理周期: 压缩完成 ${beforeTokens.toLocaleString()}→${afterTokens.toLocaleString()} tokens, stage=${cm.getUsage().stage}, saved=${cm.getUsage().lastSavedTokens.toLocaleString()}`);
|
|
1121
|
+
}
|
|
1122
|
+
catch (err) {
|
|
1123
|
+
console.warn('[PiAgent] 上下文管理周期: 压缩失败 (不阻塞主流程):', String(err?.message ?? err).slice(0, 160));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1042
1126
|
// ============================================================
|
|
1043
1127
|
// 2026-08-08: 运行轨迹采集 (trajectory) — 每轮运行 → 落盘 + OrbitDB, 失败静默
|
|
1044
1128
|
// ============================================================
|
|
@@ -1119,6 +1203,32 @@ export class PiAgentSession {
|
|
|
1119
1203
|
role: 'user',
|
|
1120
1204
|
content: input
|
|
1121
1205
|
});
|
|
1206
|
+
// 2026-10-13 (leo: 「记忆加载和管理要变成一种执行的规范, 我刚开始就被复利性质地使用过, 所以这个没有实现」):
|
|
1207
|
+
// 记忆加载原来只在 promptStream 调用 (1514), CLI 主路径 prompt() 从不召回 ⇒ 命令行 Agent 根本没有"记忆"
|
|
1208
|
+
// (用户贴屏: 「没有在工具的调用初始化过程当中看到记忆加载和记忆管理」)。这里与 promptStream 同款召回,
|
|
1209
|
+
// 注入 systemPrompt 的 contextHintAddition —— 每轮执行都过这道闸, 就是"执行的规范"。
|
|
1210
|
+
try {
|
|
1211
|
+
const { recallMemory } = await import('./memory-recall.js');
|
|
1212
|
+
const recalled = await recallMemory({ query: input, agentId: this.actor.state.agentId || this.peerId || '' });
|
|
1213
|
+
if (recalled) {
|
|
1214
|
+
this.contextHintAddition = [this.contextHintAddition, recalled].filter(Boolean).join('\n\n');
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
catch { /* 记忆召回失败静默 (增强层) */ }
|
|
1218
|
+
// 2026-10-14 (P0, decentralized-memory-design §2.7): 结构化记忆 (MemoryEntry) 每轮同步召回 —
|
|
1219
|
+
// 与上面会话摘要召回互补: 摘要是「整段会话的压缩」, 这里是「单条结构化记忆」 (带 kind/importance)。
|
|
1220
|
+
// 借鉴 Hermes 冻结快照: 写入落盘不改 prompt, 召回走每轮注入 (progressive disclosure: 只带摘要+权重)。
|
|
1221
|
+
try {
|
|
1222
|
+
const { getMemoryStore, formatRecallBlock } = await import('./memory-store.js');
|
|
1223
|
+
const store = getMemoryStore(this.actor.state.agentId || this.peerId || '');
|
|
1224
|
+
await store.load();
|
|
1225
|
+
const hits = await store.recall({ query: input, limit: 3 });
|
|
1226
|
+
const block = formatRecallBlock(hits);
|
|
1227
|
+
if (block) {
|
|
1228
|
+
this.contextHintAddition = [this.contextHintAddition, block].filter(Boolean).join('\n\n');
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
catch { /* 结构化记忆召回失败静默 (增强层, 不阻塞对话) */ }
|
|
1122
1232
|
// K9 (2026-10-02): 同 `promptWithPivotLoop` —— **回合入口的可用性判据必须问当前适配器**。
|
|
1123
1233
|
// 这类 `!minimaxAvailable` 闸一共 6 处: 1223(prompt)/1466(promptStream)/1714(pivot, 已修)
|
|
1124
1234
|
// 是**回合入口** ⇒ 全部改用 `inferenceAvailable()`; 其余三处是 Pi 自己的文档类功能
|
|
@@ -1526,7 +1636,11 @@ export class PiAgentSession {
|
|
|
1526
1636
|
if (attempt > MAX_LOOP_RETRIES) {
|
|
1527
1637
|
console.warn(`[PiAgent] loop 自动重试 ${MAX_LOOP_RETRIES} 次后仍失败, 终止`);
|
|
1528
1638
|
if (onStream) {
|
|
1529
|
-
|
|
1639
|
+
// 2026-10-14 (leo: 「loop 自动重试提示漏进内容栏」): 老代码这条**漏标 internal** ——
|
|
1640
|
+
// `onStream({type:'status'})` 事件在 CLI 渲染层 (index.ts) 只认发送点的显式 internal 声明,
|
|
1641
|
+
// 没标 → 走 appendLine 上内容栏。与下面 1717 行的「重试中」事件对齐, 补标 internal:true
|
|
1642
|
+
// ⇒ 同一判据 (appendInternalRunLog: 落盘 + --verbose 上屏), 不进对话流。
|
|
1643
|
+
onStream({ type: 'status', internal: true, content: `⛔ loop 自动重试 ${MAX_LOOP_RETRIES} 次后仍失败: ${lastAiFailureReason}`, tool: 'system' });
|
|
1530
1644
|
}
|
|
1531
1645
|
result = lastAiFailureReason || 'AI 服务调用失败, 自动重试后仍不可用';
|
|
1532
1646
|
break;
|
|
@@ -1850,6 +1964,8 @@ ${PROACTIVE_WORK_DISCIPLINE}
|
|
|
1850
1964
|
|
|
1851
1965
|
${await this.renderActivePlansSection()}
|
|
1852
1966
|
|
|
1967
|
+
${await this.buildSkillsIndex()}
|
|
1968
|
+
|
|
1853
1969
|
工作模式:
|
|
1854
1970
|
1. 理解用户自然语言请求
|
|
1855
1971
|
2. 分析需要哪些工具来完成
|
|
@@ -1860,7 +1976,7 @@ ${await this.renderActivePlansSection()}
|
|
|
1860
1976
|
重要 (一次命中要求):
|
|
1861
1977
|
- 每次只调用一个工具
|
|
1862
1978
|
- 仔细分析工具返回结果
|
|
1863
|
-
-
|
|
1979
|
+
- 任务未完成就继续调用工具推进, 直到全部完成
|
|
1864
1980
|
- 如果需要更多信息,继续调用工具
|
|
1865
1981
|
|
|
1866
1982
|
【工具调用格式 (严格遵守, 否则系统无法解析)】
|
|
@@ -1868,8 +1984,7 @@ ${await this.renderActivePlansSection()}
|
|
|
1868
1984
|
- 工具调用格式: {"name":"<tool_name>","input":{"arg1":"value1"}}
|
|
1869
1985
|
- 用 markdown json code block 包裹: \`\`\`json\n{"name":"X","input":{...}}\n\`\`\`
|
|
1870
1986
|
- 工具调用前可以简短思考 (1-2 句话), 但**不要写长篇 thinking** (会撞 max_tokens)
|
|
1871
|
-
- 工具调用后必须等结果,
|
|
1872
|
-
- <final gen> 只在**真完成所有任务**时输出, 不要在工具调用前/中输出${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1987
|
+
- 工具调用后必须等结果, 不要在同一个回复里继续输出${this.judgmentGateAddition}${this.contextHintAddition}`;
|
|
1873
1988
|
// 2026-06-15: 把 runCtx.eventSink 传给 loop, 让 step-timeline 在 pivot 循环里也能 emit step_start/done
|
|
1874
1989
|
// 之前 loop.execute() 不接 streamCallback, 导致 step-timeline 只能看到老 runReActLoop 路径
|
|
1875
1990
|
// promptWithPivotLoop 路径 0 step events — UI 显示 timeline 但永远是空
|
|
@@ -1903,6 +2018,25 @@ ${await this.renderActivePlansSection()}
|
|
|
1903
2018
|
let result;
|
|
1904
2019
|
try {
|
|
1905
2020
|
result = await loop.execute(input, llmWithHarness, systemPrompt + historyBlock, this.runCtx.eventSink ?? undefined, this.runCtx.abortSignal ?? undefined, onCompact);
|
|
2021
|
+
// 2026-10-12 (leo: 「没有根据上下文加载刻度」): pivot 的 messageHistory 是 process-local,
|
|
2022
|
+
// `estimateHistoryTokens()` 读 actor 的旧历史 ⇒ 状态栏上下文刻度恒 0.00% 的根因。
|
|
2023
|
+
// pivot 自己累计的 `state.totalTokens` 才是这一轮**真实**消耗 —— 直接用它上报 ContextManager,
|
|
2024
|
+
// 刻度立刻有数 (旧: 只在上面的 reportUsageToContextManager 里用 actor 历史估, 恒 0)。
|
|
2025
|
+
try {
|
|
2026
|
+
const pivotTokens = Number(result?.state?.totalTokens);
|
|
2027
|
+
if (Number.isFinite(pivotTokens) && pivotTokens > 0) {
|
|
2028
|
+
// 2026-10-13: 会话累计 (多轮对话刻度单调增长; 单轮 totalTokens 只是本轮)
|
|
2029
|
+
this.pivotCumulativeTokens += pivotTokens;
|
|
2030
|
+
const cm = getContextManager();
|
|
2031
|
+
const usage = cm.updateUsage(this.pivotCumulativeTokens);
|
|
2032
|
+
// 上下文管理周期: 上报 → 阈值判断 → 触发压缩 (刻度有数之外, 到阈值真的会动)
|
|
2033
|
+
if (usage.stage === 'compressing' && !this._ctxCycleCompacting) {
|
|
2034
|
+
this._ctxCycleCompacting = true;
|
|
2035
|
+
void this.runContextCycle(usage).finally(() => { this._ctxCycleCompacting = false; });
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
catch { /* 上报失败不阻塞主流程 */ }
|
|
1906
2040
|
}
|
|
1907
2041
|
finally {
|
|
1908
2042
|
try {
|
|
@@ -1923,7 +2057,7 @@ ${await this.renderActivePlansSection()}
|
|
|
1923
2057
|
runId: this.actor.state.activeRun || undefined,
|
|
1924
2058
|
goalId: this.actor.state.goalBinding || undefined,
|
|
1925
2059
|
});
|
|
1926
|
-
console.log(`[PiAgent] reviewFinal (K4-B 恢复调用, 判定不参与流程):
|
|
2060
|
+
console.log(`[PiAgent] reviewFinal (K4-B 恢复调用, 判定不参与流程): kind=${reviewDecision?.kind} reason=${String(reviewDecision?.reason ?? '').slice(0, 60)}`);
|
|
1927
2061
|
}
|
|
1928
2062
|
catch { /* 审查异常不改变本次结论 (门面内部对"审查失效"自有留痕策略) */ }
|
|
1929
2063
|
if (result.response) {
|
|
@@ -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) {
|