@bolloon/bolloon-agent 0.6.4 → 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-inbox.js +147 -0
- package/dist/agents/opportunity-match.js +48 -7
- package/dist/agents/opportunity-web-search.js +222 -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/agents/world-watcher.js +30 -1
- 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 +3 -3
- package/dist/ios/client.js +150 -113
- 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 +58 -1
- package/dist/ios/server.js +3 -1
- package/dist/ios/style.css +49 -2
- 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 +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 +150 -113
- package/dist/web/icons/icon-1400x1400.png +0 -0
- package/dist/web/icons/icon-1400x1400.webp +0 -0
- package/dist/web/index.html +52 -59
- 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 +58 -1
- package/dist/web/server.js +4 -2
- package/dist/web/style.css +49 -2
- 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
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) {
|
|
@@ -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' });
|