@bolloon/bolloon-agent 0.3.26 → 0.3.28

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.
@@ -394,6 +394,81 @@ export function registerBuiltinTools(ctx) {
394
394
  }
395
395
  }
396
396
  });
397
+ // 2026-08-02: 远端 channel 工具 — 让本地智能体能获取远端 channel 列表 + 发送消息到远端
398
+ // (之前本地智能体看不到远端 channel, 无法 @ 远程智能体交流 — "工具没有给到位")
399
+ ctx.tools.set('list_remote_channels', {
400
+ name: 'list_remote_channels',
401
+ description: '列出 P2P 好友节点分享给你的远端 channel (远程智能体会话). 每个远端 channel 属于某个 peer, 你可以在回复中写 "@渠道名 消息内容" 或调用 send_to_remote_channel 给它们发消息.',
402
+ parameters: {},
403
+ execute: async () => {
404
+ try {
405
+ const port = process.env.PORT || '54188';
406
+ const res = await fetch(`http://127.0.0.1:${port}/api/remote-channels`);
407
+ if (!res.ok)
408
+ return { success: false, error: `HTTP ${res.status}` };
409
+ const data = await res.json();
410
+ const peers = data?.peers || [];
411
+ const lines = [];
412
+ let total = 0;
413
+ for (const p of peers) {
414
+ const chs = p.channels || [];
415
+ total += chs.length;
416
+ if (chs.length === 0)
417
+ continue;
418
+ lines.push(`👤 ${p.peerName || ('peer-' + String(p.peerId).substring(0, 8))} (${String(p.peerId).substring(0, 16)}…):`);
419
+ for (const c of chs) {
420
+ lines.push(` - @${c.name} (id=${c.id})`);
421
+ }
422
+ }
423
+ if (total === 0) {
424
+ return { success: true, output: '📭 当前没有远端 channel (没有好友分享 channel 给你, 或对方不在线). 可先用 add_friend_by_id 添加好友.' };
425
+ }
426
+ return { success: true, output: `🌐 ${total} 个远端 channel:\n${lines.join('\n')}\n\n在回复中写 "@渠道名 消息内容" 即可发送 (系统自动转发到对方节点).` };
427
+ }
428
+ catch (e) {
429
+ return { success: false, error: `获取远端 channel 失败: ${String(e.message || e)}` };
430
+ }
431
+ }
432
+ });
433
+ ctx.tools.set('send_to_remote_channel', {
434
+ name: 'send_to_remote_channel',
435
+ description: '发送消息到远端 channel (远程智能体会话, 属于某个 P2P 好友节点). 对方节点会在该 channel 上跑 LLM 处理你的消息并回复. 用 list_remote_channels 查看可用 channel 和 owner.',
436
+ parameters: {
437
+ targetPublicKey: '远端节点 publicKey (64 hex, 用 list_remote_channels 看 owner)',
438
+ channelId: '远端 channel id (用 list_remote_channels 查看)',
439
+ text: '消息内容 (必填)',
440
+ autoInvokeTools: '可选, 是否允许对方调用工具 (true/false, 默认 true)'
441
+ },
442
+ execute: async (args) => {
443
+ const targetPublicKey = String(args.targetPublicKey || '').trim();
444
+ const channelId = String(args.channelId || '').trim();
445
+ const text = String(args.text || '').trim();
446
+ if (!targetPublicKey || !channelId || !text) {
447
+ return { success: false, error: 'targetPublicKey, channelId, text 必填 (用 list_remote_channels 查)' };
448
+ }
449
+ try {
450
+ const port = process.env.PORT || '54188';
451
+ const res = await fetch(`http://127.0.0.1:${port}/api/remote-channels/chat-send`, {
452
+ method: 'POST',
453
+ headers: { 'Content-Type': 'application/json' },
454
+ body: JSON.stringify({
455
+ targetPublicKey,
456
+ channelId,
457
+ text,
458
+ ...(typeof args.autoInvokeTools === 'boolean' ? { autoInvokeTools: args.autoInvokeTools } : {}),
459
+ })
460
+ });
461
+ const data = await res.json();
462
+ if (!res.ok) {
463
+ return { success: false, error: `发送失败: ${data.error || `HTTP ${res.status}`}` };
464
+ }
465
+ return { success: true, output: `📨 消息已发送到远端 channel ${channelId} (${data.sent ? '已送达' : data.queued ? '对方不在线, 已入队, 上线后自动送达' : '未知状态'}). 对方智能体会回复, 可用 check_inbox 或稍后查看.` };
466
+ }
467
+ catch (e) {
468
+ return { success: false, error: `发送失败: ${String(e.message || e)}` };
469
+ }
470
+ }
471
+ });
397
472
  // delegate_to_engine — 把编码任务委派给本机已安装的其他 AI 编码智能体 CLI
398
473
  // (codex / claude-code / opencode / openclaw / hermes). 它们必须已安装且可达 PATH.
399
474
  // 实验 API 引擎 (experiment:xxx) 是供应商不是 CLI, 不支持委派, 工具会提示改用 import.
@@ -1433,6 +1508,350 @@ export function registerBuiltinTools(ctx) {
1433
1508
  }
1434
1509
  }
1435
1510
  });
1511
+ // ============================================================
1512
+ // 决策协议工具 (2026-08-03, Context OS §7) — 可回滚的推理链
1513
+ // create_decision / decide_decision / rollback_decision / list_decisions
1514
+ // 实现: decision-store.ts (~/.bolloon/decisions/<id>.json)
1515
+ // 9 要素: 问题/选项(含不做)/成本/收益/风险/信息缺口/推荐/时机/回滚
1516
+ // 决策确认 (decide_decision) 自动 reflect 到 judgeness (HumanJudgment + JudgenessDescription)
1517
+ // ============================================================
1518
+ ctx.tools.set('create_decision', {
1519
+ name: 'create_decision',
1520
+ description: '重大决策前先写推理链 (Context OS 9 要素): problem 问题是什么, options 选项数组 (含"不做"), info_gaps 信息缺口, recommendation 推荐方案, timing 为什么是现在, rollback 失败时回滚条件. 之后用 decide_decision 确认.',
1521
+ parameters: {
1522
+ problem: '问题到底是什么 (必填)',
1523
+ options: '选项数组 JSON (必填, e.g. [{"label":"方案A","costs":"成本","benefits":"收益","risks":"风险"},{"label":"什么都不做","includeDoNothing":true}])',
1524
+ info_gaps: '当前信息缺口 (可选)',
1525
+ recommendation: '推荐方案 (可选, 确认时必填)',
1526
+ timing: '为什么是现在 (可选)',
1527
+ rollback: '失败时的回滚条件 (可选)',
1528
+ stakes: '风险等级: low / medium / high / critical (可选)',
1529
+ domain: '领域 (可选)',
1530
+ },
1531
+ execute: async (args) => {
1532
+ try {
1533
+ const { createDecision, decisionToContext } = await import('./decision-store.js');
1534
+ const problem = String(args.problem || '').trim();
1535
+ if (!problem)
1536
+ return { success: false, error: 'problem 必填' };
1537
+ let options = [];
1538
+ try {
1539
+ const s = JSON.parse(String(args.options || '[]'));
1540
+ if (Array.isArray(s))
1541
+ options = s;
1542
+ }
1543
+ catch { /* options 解析失败 */ }
1544
+ const rawStakes = String(args.stakes || 'medium');
1545
+ const stakes = rawStakes === 'low' || rawStakes === 'high' || rawStakes === 'critical' ? rawStakes : 'medium';
1546
+ const r = await createDecision({
1547
+ problem,
1548
+ options,
1549
+ infoGaps: args.info_gaps ? String(args.info_gaps) : undefined,
1550
+ recommendation: args.recommendation ? String(args.recommendation) : undefined,
1551
+ timing: args.timing ? String(args.timing) : undefined,
1552
+ rollback: args.rollback ? String(args.rollback) : undefined,
1553
+ stakes,
1554
+ domain: args.domain ? String(args.domain) : undefined,
1555
+ by: 'agent',
1556
+ originChannel: ctx.channelId || '',
1557
+ });
1558
+ if (!r.ok || !r.decision)
1559
+ return { success: false, error: r.error };
1560
+ return { success: true, output: `✅ 决策推理链已创建 ${r.decision.decisionId}\n\n${decisionToContext(r.decision)}` };
1561
+ }
1562
+ catch (e) {
1563
+ return { success: false, error: `create_decision 失败: ${String(e).slice(0, 200)}` };
1564
+ }
1565
+ }
1566
+ });
1567
+ ctx.tools.set('decide_decision', {
1568
+ name: 'decide_decision',
1569
+ description: '确认一个决策 (必须已有 recommendation). 确认后自动把该决策入库 judgeness (HumanJudgment + 5 维描述, 阶段0 临时价值点). 决策确认后状态 → decided.',
1570
+ parameters: {
1571
+ decision_id: '决策 ID (必填, create_decision 返回)',
1572
+ recommendation: '最终推荐方案 (必填, 若创建时未填)',
1573
+ },
1574
+ execute: async (args) => {
1575
+ try {
1576
+ const { updateDecisionStatus } = await import('./decision-store.js');
1577
+ const decisionId = String(args.decision_id || '').trim();
1578
+ if (!decisionId)
1579
+ return { success: false, error: 'decision_id 必填' };
1580
+ const r = await updateDecisionStatus(decisionId, { decide: true, recommendation: args.recommendation ? String(args.recommendation) : undefined }, { byAgentId: ctx.agentId || '' });
1581
+ if (!r.ok || !r.decision)
1582
+ return { success: false, error: r.error };
1583
+ const refl = r.decision.reflection ? ` (已入库 judgeness: hv=${r.decision.reflection.hvId})` : '';
1584
+ return { success: true, output: `✅ 决策已确认: ${r.decision.problem} → ${r.decision.recommendation}${refl}` };
1585
+ }
1586
+ catch (e) {
1587
+ return { success: false, error: `decide_decision 失败: ${String(e).slice(0, 200)}` };
1588
+ }
1589
+ }
1590
+ });
1591
+ ctx.tools.set('rollback_decision', {
1592
+ name: 'rollback_decision',
1593
+ description: '决策失败触发回滚条件时调用: 标记 rolled-back + 记录教训 (reject 语义入库 judgeness, 防止重复踩坑).',
1594
+ parameters: {
1595
+ decision_id: '决策 ID (必填)',
1596
+ reason: '失败/回滚原因 (必填, 将作为教训入库)',
1597
+ },
1598
+ execute: async (args) => {
1599
+ try {
1600
+ const { updateDecisionStatus } = await import('./decision-store.js');
1601
+ const decisionId = String(args.decision_id || '').trim();
1602
+ if (!decisionId)
1603
+ return { success: false, error: 'decision_id 必填' };
1604
+ const reason = String(args.reason || '').trim();
1605
+ if (!reason)
1606
+ return { success: false, error: 'reason 必填 (回滚原因)' };
1607
+ const r = await updateDecisionStatus(decisionId, { rollback: true, reason }, { byAgentId: ctx.agentId || '' });
1608
+ if (!r.ok || !r.decision)
1609
+ return { success: false, error: r.error };
1610
+ return { success: true, output: `↩️ 决策已回滚: ${r.decision.problem}\n教训已入库 judgeness (reject 语义): ${reason.slice(0, 120)}` };
1611
+ }
1612
+ catch (e) {
1613
+ return { success: false, error: `rollback_decision 失败: ${String(e).slice(0, 200)}` };
1614
+ }
1615
+ }
1616
+ });
1617
+ ctx.tools.set('list_decisions', {
1618
+ name: 'list_decisions',
1619
+ description: '列出全部决策 (按创建时间倒序). 可选 status 过滤: draft / decided / implemented / abandoned / rolled-back. 用于恢复决策上下文.',
1620
+ parameters: {
1621
+ status: '可选过滤: draft / decided / implemented / abandoned / rolled-back',
1622
+ },
1623
+ execute: async (args) => {
1624
+ try {
1625
+ const { listDecisions, decisionToContext } = await import('./decision-store.js');
1626
+ const status = ['draft', 'decided', 'implemented', 'abandoned', 'rolled-back'].includes(args.status) ? args.status : undefined;
1627
+ const decisions = await listDecisions(status);
1628
+ if (decisions.length === 0)
1629
+ return { success: true, output: '暂无决策记录.' };
1630
+ const text = decisions.slice(0, 8).map(d => decisionToContext(d)).join('\n\n');
1631
+ return { success: true, output: `🧭 ${decisions.length} 条决策 (9 要素推理链可追溯):\n\n${text}` };
1632
+ }
1633
+ catch (e) {
1634
+ return { success: false, error: `list_decisions 失败: ${String(e).slice(0, 200)}` };
1635
+ }
1636
+ }
1637
+ });
1638
+ // ============================================================
1639
+ // Context OS 资产层工具 (2026-08-03, P5) — 12+3 层文件夹体系
1640
+ // list_context_layers / write_context_asset / read_context_assets
1641
+ // 实现: src/bootstrap/context-os.ts (~/.bolloon/context-os/)
1642
+ // 价值判断: 写入前回答"未来哪个具体场景会用到它?" — 回答不出进 tmp/
1643
+ // ============================================================
1644
+ ctx.tools.set('list_context_layers', {
1645
+ name: 'list_context_layers',
1646
+ description: '列出 Context OS 资产层 (12+3 层: 01-Me 我是谁 / 02-Network 我认识谁 / 03-Current 我在做什么 / 04-Projects 项目 / 05-Prompts 提示词 / 06-Protocols 协议 / 07-Knowledge 知识 / 08-Insights 洞察 / 09-Tools 工具 / 10-Skills 技能 / 11-Write 写作 / 12-Analysis 决策复盘 / output / research / tmp) + 每层资产数. 任务前先看目录, 再按任务路由读取对应层.',
1647
+ parameters: {},
1648
+ execute: async () => {
1649
+ try {
1650
+ const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
1651
+ const listings = await readContextAssets();
1652
+ const total = listings.reduce((s, l) => s + l.fileCount, 0);
1653
+ if (total === 0)
1654
+ return { success: true, output: '📂 Context OS 资产层已就绪 (12+3 层), 当前暂无资产. 有价值的内容用 write_context_asset 写入对应层.' };
1655
+ return { success: true, output: `📂 Context OS 资产层共 ${total} 篇资产:\n\n${formatLayerListing(listings)}` };
1656
+ }
1657
+ catch (e) {
1658
+ return { success: false, error: `list_context_layers 失败: ${String(e).slice(0, 200)}` };
1659
+ }
1660
+ }
1661
+ });
1662
+ ctx.tools.set('write_context_asset', {
1663
+ name: 'write_context_asset',
1664
+ description: '把已验证的价值写入 Context OS 资产层 (唯一落点, 不制造重复文件). 写入前先自检: 未来哪个具体场景会用到它? 回答不出 → 写 tmp/ 或放弃. layer 可选: 01-Me 原则边界 / 02-Network 人脉 / 03-Current 当前状态 / 04-Projects 项目 / 05-Prompts 已验证提示词 / 06-Protocols 规则 / 07-Knowledge 跨项目知识 / 08-Insights 已验证洞察/教训 / 09-Tools 工具经验 / 10-Skills 可验证能力 / 11-Write 成熟表达 / 12-Analysis 决策复盘 / output 对外交付 / research 中间成果 / tmp 一次性草稿.',
1665
+ parameters: {
1666
+ layer: '层 key (必填, 见 description 列表)',
1667
+ title: '资产标题 (必填, 一句话)',
1668
+ content: '资产正文 markdown (必填)',
1669
+ tags: '可选 tags 数组 JSON',
1670
+ domain: '可选领域',
1671
+ },
1672
+ execute: async (args) => {
1673
+ try {
1674
+ const { writeContextAsset } = await import('../bootstrap/context-os.js');
1675
+ const layer = String(args.layer || '').trim();
1676
+ const title = String(args.title || '').trim();
1677
+ const content = String(args.content || '').trim();
1678
+ if (!layer)
1679
+ return { success: false, error: 'layer 必填 (如 07-Knowledge)' };
1680
+ if (!title)
1681
+ return { success: false, error: 'title 必填' };
1682
+ if (!content)
1683
+ return { success: false, error: 'content 必填' };
1684
+ let tags = [];
1685
+ try {
1686
+ const t = JSON.parse(String(args.tags || '[]'));
1687
+ if (Array.isArray(t))
1688
+ tags = t.map(String);
1689
+ }
1690
+ catch { /* tags 解析失败 */ }
1691
+ const r = await writeContextAsset({ layer, title, content, tags, domain: args.domain ? String(args.domain) : undefined });
1692
+ if (!r.ok)
1693
+ return { success: false, error: r.error };
1694
+ if (r.skipped)
1695
+ return { success: true, output: `⏭️ ${r.error}` };
1696
+ return { success: true, output: `📥 已写入资产层 ${r.asset.layer}: ${r.asset.title} (stage0 临时价值点, 待验证后固化)\n路径: ${r.asset.path}` };
1697
+ }
1698
+ catch (e) {
1699
+ return { success: false, error: `write_context_asset 失败: ${String(e).slice(0, 200)}` };
1700
+ }
1701
+ }
1702
+ });
1703
+ ctx.tools.set('read_context_assets', {
1704
+ name: 'read_context_assets',
1705
+ description: '读取 Context OS 资产层内容. layer 可选 (空 = 全层汇总); keyword 可选 (标题/内容过滤). 做项目前先读 04-Projects 对应项目, 重大决策前读 08-Insights + 12-Analysis, 学技术读 07-Knowledge + 09-Tools.',
1706
+ parameters: {
1707
+ layer: '可选层 key (如 07-Knowledge), 空 = 全部',
1708
+ keyword: '可选关键词过滤',
1709
+ },
1710
+ execute: async (args) => {
1711
+ try {
1712
+ const { readContextAssets, formatLayerListing } = await import('../bootstrap/context-os.js');
1713
+ const layer = args.layer ? String(args.layer) : undefined;
1714
+ const kw = args.keyword ? String(args.keyword) : undefined;
1715
+ const listings = await readContextAssets(layer, kw);
1716
+ if (listings.every((l) => l.fileCount === 0)) {
1717
+ return { success: true, output: layer ? `📂 资产层 ${layer} 暂无资产` : '📂 资产层暂无资产' };
1718
+ }
1719
+ // 单层且有 keyword → 输出完整正文
1720
+ if (layer && kw) {
1721
+ const found = listings[0]?.files || [];
1722
+ const { readAssetBody } = await import('../bootstrap/context-os.js');
1723
+ const bodies = [];
1724
+ for (const f of found.slice(0, 5)) {
1725
+ try {
1726
+ const r = await readAssetBody(layer, f.file);
1727
+ if (r.ok && r.body)
1728
+ bodies.push(`--- ${f.title} ---\n${r.body.slice(0, 2000)}\n--- 结束 ---`);
1729
+ }
1730
+ catch { /* 跳过 */ }
1731
+ }
1732
+ return { success: true, output: bodies.length > 0 ? bodies.join('\n\n') : '未找到匹配资产' };
1733
+ }
1734
+ return { success: true, output: formatLayerListing(listings) };
1735
+ }
1736
+ catch (e) {
1737
+ return { success: false, error: `read_context_assets 失败: ${String(e).slice(0, 200)}` };
1738
+ }
1739
+ }
1740
+ });
1741
+ // ============================================================
1742
+ // MCP 工具 (2026-08-03) — 外部 MCP server 接入 agent 工具系统
1743
+ // 配置: ~/.mcp.json (mcpServers), 启动时 initializeMcpAdapter 自动握手发现工具
1744
+ // mcp_list_tools: 列出已发现的 MCP 工具
1745
+ // mcp_tool: 调用任意 MCP 工具 (真实 stdio JSON-RPC)
1746
+ // ============================================================
1747
+ ctx.tools.set('mcp_list_tools', {
1748
+ name: 'mcp_list_tools',
1749
+ description: '列出通过 MCP 协议连接的可用外部工具 (来自 ~/.mcp.json 配置的 MCP servers). 调用 MCP 工具前先列一次, 拿准确工具名和参数.',
1750
+ parameters: {},
1751
+ execute: async () => {
1752
+ try {
1753
+ const mcp = await import('../pi-ecosystem-mcp/index.js');
1754
+ await mcp.initializeMcpAdapter().catch(() => { });
1755
+ const tools = mcp.listTools();
1756
+ if (tools.length === 0) {
1757
+ return { success: true, output: '未发现 MCP 工具. 配置 ~/.mcp.json (mcpServers: {name: {command, args}}), 重启后自动连接.' };
1758
+ }
1759
+ const lines = tools.map((t) => ` - ${t.name} (${t.serverName}): ${t.description?.slice(0, 80) || '无描述'}`);
1760
+ return { success: true, output: `🔌 ${tools.length} 个 MCP 工具可用:\n${lines.join('\n')}\n\n调用用 mcp_tool (tool=工具名, arguments=参数 JSON)` };
1761
+ }
1762
+ catch (e) {
1763
+ return { success: false, error: `mcp_list_tools 失败: ${String(e).slice(0, 200)}` };
1764
+ }
1765
+ }
1766
+ });
1767
+ ctx.tools.set('mcp_tool', {
1768
+ name: 'mcp_tool',
1769
+ description: '调用外部 MCP 工具 (真实 stdio JSON-RPC 通信). tool = 工具名 (先用 mcp_list_tools 查看), arguments = 参数 JSON 对象.',
1770
+ parameters: {
1771
+ tool: 'MCP 工具名 (必填)',
1772
+ arguments: '参数 JSON 对象 (必填, e.g. {"text":"hello"})',
1773
+ },
1774
+ execute: async (args) => {
1775
+ try {
1776
+ const mcp = await import('../pi-ecosystem-mcp/index.js');
1777
+ const tool = String(args.tool || '').trim();
1778
+ if (!tool)
1779
+ return { success: false, error: 'tool 必填' };
1780
+ let argumentsObj = {};
1781
+ try {
1782
+ const a = JSON.parse(String(args.arguments || '{}'));
1783
+ if (a && typeof a === 'object')
1784
+ argumentsObj = a;
1785
+ }
1786
+ catch {
1787
+ return { success: false, error: 'arguments 必须是 JSON 对象' };
1788
+ }
1789
+ const r = await mcp.executeTool(tool, argumentsObj);
1790
+ if (!r.success)
1791
+ return { success: false, error: r.error || 'MCP 调用失败' };
1792
+ const text = Array.isArray(r.content)
1793
+ ? r.content.map((c) => c?.text ?? '').filter(Boolean).join('\n')
1794
+ : JSON.stringify(r.content);
1795
+ return { success: true, output: `🔧 MCP ${tool}:\n${text.slice(0, 4000)}` };
1796
+ }
1797
+ catch (e) {
1798
+ return { success: false, error: `mcp_tool 失败: ${String(e).slice(0, 200)}` };
1799
+ }
1800
+ }
1801
+ });
1802
+ // ============================================================
1803
+ // publish_did (2026-08-03) — 把当前 agent 的 DID 发布到 IPFS + IPNS
1804
+ // 全自动: 自动安装/启动本地 Kubo → 上传 DID 文档 → 发布 IPNS name
1805
+ // 实现: @diap/sdk (AgentAuthManager + publishAfterUpload)
1806
+ // ============================================================
1807
+ ctx.tools.set('publish_did', {
1808
+ name: 'publish_did',
1809
+ description: '把当前 agent 的 DID 身份发布到本地 IPFS + IPNS (自动安装启动 Kubo). 返回 DID + CID (IPFS 内容地址) + IPNS name (稳定可解析标识). 跨节点发现和身份解析依赖它.',
1810
+ parameters: {
1811
+ name: '可选: 发布显示名 (默认 agentId)',
1812
+ },
1813
+ execute: async (args) => {
1814
+ try {
1815
+ const agentId = String(ctx.agentId || '').trim();
1816
+ const { loadOrCreateAgentIdentity } = await import('./agent-identity.js');
1817
+ const identity = loadOrCreateAgentIdentity(agentId || 'default-agent');
1818
+ const { KeyManager } = await import('@diap/sdk');
1819
+ const kp = KeyManager.fromPrivateKey(Buffer.from(identity.privateKey, 'hex'));
1820
+ const displayName = args.name ? String(args.name) : agentId || 'bolloon-agent';
1821
+ // 1. 确保本地 Kubo (自动安装 + 启动)
1822
+ const sdk = await import('@diap/sdk');
1823
+ const checkKuboSetup = sdk.checkKuboSetup;
1824
+ if (typeof checkKuboSetup === 'function') {
1825
+ const setup = await checkKuboSetup(true, true);
1826
+ if (!setup?.ready || !setup?.daemonRunning) {
1827
+ return { success: false, error: '本地 Kubo 不可用 (自动安装失败), 无法发布到 IPFS' };
1828
+ }
1829
+ }
1830
+ // 2. 注册 agent → 上传 DID 文档 → CID
1831
+ const { AgentAuthManager } = await import('@diap/sdk');
1832
+ const auth = await AgentAuthManager.newWithRemoteIpfs('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
1833
+ const result = await auth.registerAgent({ name: displayName, services: [] }, kp, '');
1834
+ const cid = result.cid || result.didDocCid;
1835
+ if (!cid)
1836
+ return { success: false, error: 'DID 上传成功但未拿到 CID' };
1837
+ // 3. 发布 IPNS name (稳定标识)
1838
+ let ipnsName = '';
1839
+ try {
1840
+ const ipfs = await sdk.IpfsClient.newWithRemoteNode('http://127.0.0.1:5001', 'http://127.0.0.1:8080');
1841
+ const pub = await ipfs.publishAfterUpload?.(cid, kp);
1842
+ ipnsName = pub?.name || pub?.ipnsName || '';
1843
+ }
1844
+ catch { /* IPNS 失败不致命, CID 仍可用 */ }
1845
+ return {
1846
+ success: true,
1847
+ output: `✅ DID 已发布到 IPFS:\n DID: ${identity.did}\n CID: ${cid}\n IPNS: ${ipnsName || '(发布失败, CID 仍可用)'}\n 读回验证: curl -X POST "http://127.0.0.1:5001/api/v0/cat?arg=${cid}"`,
1848
+ };
1849
+ }
1850
+ catch (e) {
1851
+ return { success: false, error: `publish_did 失败: ${String(e).slice(0, 200)}` };
1852
+ }
1853
+ }
1854
+ });
1436
1855
  }
1437
1856
  /**
1438
1857
  * 注册 Wallet + Polymarket + Safe 工具 (基于 constraint-runtime/src/tools/).
@@ -0,0 +1,213 @@
1
+ /**
2
+ * context-os.ts — Context OS 资产层 (2026-08-03, P5)
3
+ *
4
+ * 把 Ziye-Context-OS 的 12+3 层文件夹体系落地到 Bolloon:
5
+ * ~/.bolloon/context-os/
6
+ * 01-Me ~ 12-Analysis + output / research / tmp
7
+ *
8
+ * 每层回答一种问题 (Context OS §3):
9
+ * 01-Me 我是谁 / 02-Network 我认识谁 / 03-Current 我现在在做什么
10
+ * 04-Projects 我正在推进什么 / 05-Prompts 哪些提示词已验证
11
+ * 06-Protocols AI 和系统该如何工作 / 07-Knowledge 哪些知识跨项目复用
12
+ * 08-Insights 哪些判断改变决策 / 09-Tools 哪些工具省时间
13
+ * 10-Skills 哪些能力可验证 / 11-Write 哪些表达可复用
14
+ * 12-Analysis 决策过程与复盘 / output 对外交付 / research 中间成果 / tmp 一次性草稿
15
+ *
16
+ * 价值判断标准 (Context OS §5): 每个资产进入前回答"未来哪个具体场景会用到它?".
17
+ * 回答不出 = 噪音, 不该进正式层.
18
+ *
19
+ * 设计 (减法):
20
+ * - 每层一个 README.md 声明职责边界 (存什么/不该存什么/典型用途)
21
+ * - 资产文件: <ts>-<slug>.md, frontmatter v2 (stage0 = 临时价值点, 与 judgeness 生命周期对应)
22
+ * - 写操作失败静默, 不阻塞主对话
23
+ */
24
+ import * as fs from 'fs/promises';
25
+ import * as os from 'os';
26
+ import * as path from 'path';
27
+ export const CONTEXT_OS_LAYERS = [
28
+ { key: '01-Me', name: '我是谁', store: '经过验证的原则、不可碰的边界、稳定偏好', notStore: '临时情绪、未经验证的念头', usage: '防止 AI 用错误的方式帮助你' },
29
+ { key: '02-Network', name: '我认识谁', store: '有真实关系、能力可定位、能在具体问题上调用的人', notStore: '只看过主页的陌生人', usage: '需要咨询、合作、求证时找到正确的人' },
30
+ { key: '03-Current', name: '我现在在做什么', store: '今天/本周的现实状态、工作现场、阻塞项', notStore: '长期知识、项目历史全文', usage: '防止 AI 按过期状态给建议' },
31
+ { key: '04-Projects', name: '我正在推进什么', store: '有明确交付、真实进度、可验证证据的项目', notStore: '只有想法的脑暴', usage: '让 AI 按项目真实边界推进' },
32
+ { key: '05-Prompts', name: '已验证可复用的提示词', store: '至少复用过、效果稳定的 Prompt', notStore: '一次性调试 Prompt', usage: '跨项目、跨工具复用工作方法' },
33
+ { key: '06-Protocols', name: 'AI 和系统该如何工作', store: '为防止真实错误而产生、被重复调用的规则', notStore: '纯理论流程', usage: '把"知道"变成"每次都会做"' },
34
+ { key: '07-Knowledge', name: '哪些领域知识未来复用', store: '未来至少三个项目可能复用的领域理解', notStore: '随手可搜的常识', usage: '技术/行业问题的长期积累' },
35
+ { key: '08-Insights', name: '哪些已验证的判断改变决策', store: '能改变决策、产品方向或自我认知的已验证判断', notStore: '情绪碎片、未经验证的直觉', usage: '防止重复踩同一种坑' },
36
+ { key: '09-Tools', name: '哪些工具和脚本省时间', store: '实际用过、能节约时间、包含回退方案的工具经验', notStore: '只安装未使用的软件', usage: '提升执行效率,避免重复试错' },
37
+ { key: '10-Skills', name: '哪些能力可验证交付', store: '可外部验证、能交付、有作品支撑的能力', notStore: '"我想学"的愿望', usage: '简历、分工、能力缺口识别' },
38
+ { key: '11-Write', name: '哪些写作可复用', store: '可以引用、改写、发布的成熟表达', notStore: '未整理草稿', usage: '保持跨场景表达一致' },
39
+ { key: '12-Analysis', name: '决策过程与复盘', store: '有推理链、可事后复盘的研究与决策', notStore: '只有结论的事后合理化', usage: '重要决策可追溯、可修正' },
40
+ { key: 'output', name: '对外交付物', store: '给外部的人看的最终交付物', notStore: '内部草稿', usage: '可直接分享给他人' },
41
+ { key: 'research', name: '研究中间成果', store: '研究中的中间成果', notStore: '结论已定型的资产', usage: '未完成研究的暂存' },
42
+ { key: 'tmp', name: '一次性草稿', store: '一次性草稿与临时文件', notStore: '任何未来要复用的东西', usage: '定期清理' },
43
+ ];
44
+ const LAYER_KEYS = new Set(CONTEXT_OS_LAYERS.map((l) => l.key));
45
+ // ============================================================
46
+ // 路径
47
+ // ============================================================
48
+ export function getContextOsRoot(home = os.homedir()) {
49
+ return path.join(home, '.bolloon', 'context-os');
50
+ }
51
+ export function getLayerDir(layer, home = os.homedir()) {
52
+ return path.join(getContextOsRoot(home), layer);
53
+ }
54
+ /** 校验 layer 合法; 非法返回 null */
55
+ export function resolveLayer(layer) {
56
+ const key = String(layer || '').trim();
57
+ return LAYER_KEYS.has(key) ? CONTEXT_OS_LAYERS.find((l) => l.key === key) : null;
58
+ }
59
+ function slugify(s) {
60
+ return s.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_').slice(0, 40) || 'untitled';
61
+ }
62
+ // ============================================================
63
+ // 初始化: 建目录 + 每层 README (幂等)
64
+ // ============================================================
65
+ function layerReadme(l) {
66
+ return `# ${l.key} — ${l.name}
67
+
68
+ ## 这一层回答的问题
69
+ ${l.name}
70
+
71
+ ## 存什么
72
+ ${l.store}
73
+
74
+ ## 不该存什么
75
+ ${l.notStore}
76
+
77
+ ## 典型用途
78
+ ${l.usage}
79
+
80
+ ## 价值判断标准 (Context OS §5)
81
+ 写入前先回答: **未来哪个具体场景会用到它?**
82
+ 回答不出 = 噪音, 留在 tmp/, 不进正式层.
83
+
84
+ ## 价值生命周期
85
+ 阶段0 临时价值点 (对话中刚出现, 未验证) → 阶段1 验证 (被使用/确认)
86
+ → 阶段2 固化 (本层唯一位置) → 阶段3 索引化 (高频引用) → 阶段4 归档/删除.
87
+ `;
88
+ }
89
+ export async function ensureContextOsDirs(home) {
90
+ const root = getContextOsRoot(home);
91
+ await fs.mkdir(root, { recursive: true });
92
+ for (const l of CONTEXT_OS_LAYERS) {
93
+ const dir = getLayerDir(l.key, home);
94
+ await fs.mkdir(dir, { recursive: true });
95
+ const readmePath = path.join(dir, 'README.md');
96
+ try {
97
+ await fs.access(readmePath);
98
+ }
99
+ catch {
100
+ await fs.writeFile(readmePath, layerReadme(l), 'utf-8');
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * 写入资产到指定层.
106
+ * 文件名: <ts>-<slug>.md; frontmatter v2 (stage0 = 临时价值点, 待验证).
107
+ * 幂等: 同层同 slug 已存在 → 跳过 (不重复造文件, Context OS §6 Step3).
108
+ */
109
+ export async function writeContextAsset(input, home) {
110
+ const layer = resolveLayer(input.layer);
111
+ if (!layer) {
112
+ return { ok: false, error: `layer 非法: '${input.layer}'. 合法: ${CONTEXT_OS_LAYERS.map((l) => l.key).join(' / ')}` };
113
+ }
114
+ const title = String(input.title || '').trim();
115
+ if (!title)
116
+ return { ok: false, error: 'title 必填' };
117
+ const content = String(input.content || '').trim();
118
+ if (!content)
119
+ return { ok: false, error: 'content 必填' };
120
+ await ensureContextOsDirs(home);
121
+ const now = new Date().toISOString();
122
+ const ts = Date.now();
123
+ const slug = slugify(title);
124
+ const fileName = `${ts}-${slug}.md`;
125
+ const filePath = path.join(getLayerDir(layer.key, home), fileName);
126
+ // 幂等: 同 slug 已存在 → 跳过
127
+ try {
128
+ const files = await fs.readdir(getLayerDir(layer.key, home));
129
+ if (files.some((f) => f.endsWith(`-${slug}.md`))) {
130
+ return { ok: true, skipped: true, error: `同标题资产已存在 (${slug}.md), 未重复写入` };
131
+ }
132
+ }
133
+ catch { /* 目录不存在, 继续 */ }
134
+ const fm = [
135
+ '---',
136
+ `title: ${title.replace(/[\n\r]/g, ' ').slice(0, 80)}`,
137
+ `source: session`,
138
+ `created: ${now}`,
139
+ `layer: ${layer.key}`,
140
+ `stage: stage0`,
141
+ `tags: [${(input.tags || []).map((t) => t.replace(/[^\w\u4e00-\u9fa5-]/g, '')).filter(Boolean).join(', ')}]`,
142
+ input.domain ? `domain: ${input.domain.replace(/[\n\r]/g, ' ').slice(0, 40)}` : '',
143
+ 'schema_version: 2',
144
+ '---',
145
+ '',
146
+ content,
147
+ ].filter(Boolean).join('\n');
148
+ try {
149
+ await fs.writeFile(filePath, fm, 'utf-8');
150
+ return {
151
+ ok: true,
152
+ asset: { layer: layer.key, file: fileName, title, path: filePath, createdAt: now, stage: 'stage0' },
153
+ };
154
+ }
155
+ catch (e) {
156
+ return { ok: false, error: `写入失败: ${e?.message || String(e)}` };
157
+ }
158
+ }
159
+ /** 列出层资产; layer 为空 → 全层汇总 */
160
+ export async function readContextAssets(layer, keyword, home) {
161
+ const root = getContextOsRoot(home);
162
+ const kw = String(keyword || '').trim().toLowerCase();
163
+ const wanted = layer ? [resolveLayer(layer)].filter(Boolean).map((l) => l.key) : CONTEXT_OS_LAYERS.map((l) => l.key);
164
+ const out = [];
165
+ for (const key of wanted) {
166
+ const l = resolveLayer(key);
167
+ try {
168
+ const files = (await fs.readdir(getLayerDir(key, home))).filter((f) => f.endsWith('.md') && f !== 'README.md');
169
+ const entries = [];
170
+ for (const f of files) {
171
+ try {
172
+ const raw = await fs.readFile(path.join(getLayerDir(key, home), f), 'utf-8');
173
+ const titleM = raw.match(/^title:\s*(.+)$/m);
174
+ const createdM = raw.match(/^created:\s*(.+)$/m);
175
+ const title = titleM ? titleM[1].trim() : f.replace(/\.md$/, '');
176
+ if (kw && !(title.toLowerCase().includes(kw) || raw.toLowerCase().includes(kw)))
177
+ continue;
178
+ entries.push({ file: f, title, createdAt: createdM ? createdM[1].trim() : '' });
179
+ }
180
+ catch { /* 单文件损坏跳过 */ }
181
+ }
182
+ entries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
183
+ out.push({ layer: key, name: l.name, fileCount: entries.length, files: entries.slice(0, 20) });
184
+ }
185
+ catch {
186
+ out.push({ layer: key, name: l.name, fileCount: 0, files: [] });
187
+ }
188
+ }
189
+ return out;
190
+ }
191
+ /** 读取单篇资产正文 (供工具输出完整内容) */
192
+ export async function readAssetBody(layer, file, home) {
193
+ const l = resolveLayer(layer);
194
+ if (!l)
195
+ return { ok: false, error: `layer 非法: '${layer}'` };
196
+ const safeFile = path.basename(String(file || '').replace(/[^\w\u4e00-\u9fa5.-]/g, '_'));
197
+ if (!safeFile.endsWith('.md'))
198
+ return { ok: false, error: 'file 必须是 .md' };
199
+ try {
200
+ const raw = await fs.readFile(path.join(getLayerDir(l.key, home), safeFile), 'utf-8');
201
+ return { ok: true, body: raw };
202
+ }
203
+ catch (e) {
204
+ return { ok: false, error: `读取失败: ${e?.message || String(e)}` };
205
+ }
206
+ }
207
+ /** 层 → 上下文注入摘要 (给 LLM 的资产层目录) */
208
+ export function formatLayerListing(listings) {
209
+ if (listings.length === 0)
210
+ return '';
211
+ const lines = listings.map((l) => ` - ${l.layer} (${l.name}): ${l.fileCount} 篇` + (l.files.length > 0 ? ` — ${l.files.slice(0, 3).map((f) => f.title).join(' / ')}` : ''));
212
+ return `[系统上下文] 资产层 (Context OS 12+3 层, 先看 03-Current 再按任务路由):\n${lines.join('\n')}\n\n`;
213
+ }