@bolloon/bolloon-agent 0.5.1 → 0.5.3

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.
Files changed (77) hide show
  1. package/README.md +39 -0
  2. package/dist/agents/agent-lang.js +101 -0
  3. package/dist/agents/agent-manifest-protocol.js +4 -0
  4. package/dist/agents/chain/transfer-classify.js +99 -0
  5. package/dist/agents/chain/transfer-index.js +354 -0
  6. package/dist/agents/gateway-group.js +44 -3
  7. package/dist/agents/identity/address-binding.js +613 -0
  8. package/dist/agents/network-pulse.js +706 -27
  9. package/dist/agents/pi-sdk.js +101 -8
  10. package/dist/agents/workflow-pivot-loop.js +3 -1
  11. package/dist/agents/x402/direct-payment.js +689 -0
  12. package/dist/agents/x402/order-identity.js +408 -0
  13. package/dist/agents/x402/paid-info-store.js +17 -0
  14. package/dist/agents/x402/seller-signing.js +614 -0
  15. package/dist/agents/x402/seller-summary.js +398 -0
  16. package/dist/cli/identity-command.js +327 -2
  17. package/dist/cli/protocol-envelope.js +4 -0
  18. package/dist/cli/update-commands.js +41 -25
  19. package/dist/cli/x402-buyer-command.js +463 -0
  20. package/dist/cli/x402-seller-command.js +289 -0
  21. package/dist/cli-entry.js +21 -5
  22. package/dist/efficode/compress.js +78 -0
  23. package/dist/efficode/crc32.js +38 -0
  24. package/dist/efficode/did.js +66 -0
  25. package/dist/efficode/index.js +24 -0
  26. package/dist/efficode/lz77.js +170 -0
  27. package/dist/efficode/negotiate.js +187 -0
  28. package/dist/efficode/ops.js +87 -0
  29. package/dist/efficode/packet.js +271 -0
  30. package/dist/efficode/types.js +45 -0
  31. package/dist/electron-build/agents/agent-manifest-protocol.js +4 -0
  32. package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
  33. package/dist/electron-build/agents/chain/transfer-classify.js +106 -0
  34. package/dist/electron-build/agents/chain/transfer-classify.js.map +1 -0
  35. package/dist/electron-build/agents/chain/transfer-index.js +400 -0
  36. package/dist/electron-build/agents/chain/transfer-index.js.map +1 -0
  37. package/dist/electron-build/agents/identity/address-binding.js +672 -0
  38. package/dist/electron-build/agents/identity/address-binding.js.map +1 -0
  39. package/dist/electron-build/agents/network-pulse.js +714 -28
  40. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  41. package/dist/electron-build/agents/pi-sdk.js +102 -8
  42. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  43. package/dist/electron-build/agents/workflow-pivot-loop.js +3 -1
  44. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  45. package/dist/electron-build/agents/x402/direct-payment.js +744 -0
  46. package/dist/electron-build/agents/x402/direct-payment.js.map +1 -0
  47. package/dist/electron-build/agents/x402/order-identity.js +458 -0
  48. package/dist/electron-build/agents/x402/order-identity.js.map +1 -0
  49. package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -1
  50. package/dist/electron-build/agents/x402/paid-info-store.js +18 -0
  51. package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
  52. package/dist/electron-build/agents/x402/seller-signing.js +682 -0
  53. package/dist/electron-build/agents/x402/seller-signing.js.map +1 -0
  54. package/dist/electron-build/llm/pi-ai.js +685 -48
  55. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  56. package/dist/electron-build/utils/auto-update.js +2 -2
  57. package/dist/electron-build/utils/auto-update.js.map +1 -1
  58. package/dist/electron-build/utils/runtime-bootstrap.js +1 -1
  59. package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -1
  60. package/dist/electron-build/utils/update-manager.js +3 -3
  61. package/dist/electron-build/utils/update-manager.js.map +1 -1
  62. package/dist/electron-build/utils/version-identity.js +1 -1
  63. package/dist/electron-build/utils/version-identity.js.map +1 -1
  64. package/dist/electron-build/web/routes-x402-info.js +28 -1
  65. package/dist/electron-build/web/routes-x402-info.js.map +1 -1
  66. package/dist/index.js +3 -3
  67. package/dist/llm/pi-ai.js +666 -47
  68. package/dist/utils/auto-update.js +2 -2
  69. package/dist/utils/runtime-bootstrap.js +1 -1
  70. package/dist/utils/update-health.js +2 -2
  71. package/dist/utils/update-manager.js +3 -3
  72. package/dist/utils/version-identity.js +1 -1
  73. package/dist/web/bolloon-web.json +3 -3
  74. package/dist/web/mobile.js +64 -0
  75. package/dist/web/routes-x402-info.js +28 -1
  76. package/dist/web/sw.js +1 -1
  77. package/package.json +2 -1
@@ -33,6 +33,8 @@ import { createGoal, attachRun, findActiveGoal } from './goal-store.js';
33
33
  import { captureRunModelConfig } from '../llm/model-selection.js';
34
34
  // 2026-09-26: 工具名出网净化 (pi-ai.ts 唯一边界) + 回程派发还原 (原名 ↔ API 名)
35
35
  import { resolveApiToolName, expandKnownToolNames } from '../llm/tool-name.js';
36
+ // 2026-09-28 (前缀 KV 可命中): CURRENT TURN 注入标记 —— "当前轮是否已注入"的唯一判据
37
+ import { CURRENT_TURN_MARKER } from '../llm/pi-ai.js';
36
38
  import { PiAgentHarness } from './pi-harness.js';
37
39
  import { DiscoveredAgentsManager, createSocialHeartbeat } from '../social/heartbeat.js';
38
40
  import { SkillRegistry } from '@bolloon/constraint-runtime';
@@ -79,6 +81,55 @@ import { DEFAULT_MAX_REVIEWS } from './loop-review.js';
79
81
  * 循环内: 注入 system 提示强制 LLM 收尾; 运行层 (M3): 熔断 → needs_human (交人)。
80
82
  */
81
83
  const MAX_SAME_TOOL_FAILURES = 3;
84
+ /**
85
+ * 2026-09-28 (前缀 KV 可命中): 把 chat() 回带的「当前轮已注入」内容写回调用方自己的 history.
86
+ *
87
+ * 谁是调用方: ReAct 循环每轮用 `buildMessages()` **重建全新对象** —— 注入只活在那一份里.
88
+ * 不写回的话下一轮那条 user 就退回注入前, 前缀从它起分叉:
89
+ * 第 1 次 `S H [D1 U1]` 第 2 次 `S H U1 U2` ← U1 丢了 D1
90
+ * 正确形态: `S H [D1 U1] U2` (D1 成了 U1 的一部分, 于是成为后续轮的稳定前缀).
91
+ *
92
+ * 为什么是纯函数 (不塞进类里): 门 `scripts/verify-kv-prefix.ts` 要能**直接驱它**断言,
93
+ * 而不是绕一整条 ReAct 循环才验到"写回"这一下.
94
+ *
95
+ * 匹配判据 (只做加法, 绝不乱改历史):
96
+ * 1. wire 最后一条 content 必须以 CURRENT TURN 标记开头 (没注入过 → 0, 不动);
97
+ * 2. 从 history **末尾往前**找第一条 `content` 是它后缀的条目 ——
98
+ * 去掉后缀剩下的那截必须仍以标记开头 (证明"注入是前部加了一段");
99
+ * 3. 命中就整段写回 (tool 条目拿到的是含 `[工具结果]\n` 前缀 + 注入的整段),
100
+ * 写回后该条 `content` 已等于 wire → 第二次调用前缀为空, 自然返回 0 (幂等).
101
+ *
102
+ * @returns 真写回的条数 (0 = 没注入过 / 匹配不上 / 已经写回过)
103
+ */
104
+ export function writeBackCurrentTurnInto(history, wire) {
105
+ try {
106
+ if (!Array.isArray(history) || history.length === 0)
107
+ return 0;
108
+ if (!Array.isArray(wire) || wire.length === 0)
109
+ return 0;
110
+ const last = wire[wire.length - 1];
111
+ const wireContent = typeof last?.content === 'string' ? last.content : '';
112
+ if (!wireContent.startsWith(CURRENT_TURN_MARKER))
113
+ return 0; // 没注入过 → 不动
114
+ for (let i = history.length - 1; i >= 0; i--) {
115
+ const entry = history[i];
116
+ const inner = typeof entry?.content === 'string' ? entry.content : '';
117
+ if (!inner)
118
+ continue;
119
+ if (!wireContent.endsWith(inner))
120
+ continue;
121
+ const prefix = wireContent.slice(0, wireContent.length - inner.length);
122
+ if (!prefix || !prefix.startsWith(CURRENT_TURN_MARKER))
123
+ continue;
124
+ entry.content = wireContent;
125
+ return 1;
126
+ }
127
+ return 0;
128
+ }
129
+ catch {
130
+ return 0; // 写回失败绝不影响对话本身
131
+ }
132
+ }
82
133
  export class PiAgentSession {
83
134
  cwd;
84
135
  peerId;
@@ -1775,12 +1826,14 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
1775
1826
  .join('\n');
1776
1827
  loopProgressSection = `\n【本轮循环进度】你已完成以下 ${loopActionLog.length} 个动作, 这是连续执行的同一轮任务:\n${actionLines}\n请基于已有结果继续推进, 不要重复执行上面已成功的动作. 全部完成后用 <final gen> 结束.\n`;
1777
1828
  }
1829
+ // 2026-09-28 (前缀 KV 可命中): `refineContext` / `loopProgressSection` 是**每轮都在变**的段
1830
+ // (质量分 / 连续错误数 / 本轮已完成动作). 留在 system 里 = 每轮把 IMMUTABLE PREFIX 打碎 =
1831
+ // 服务端前缀 KV 永远 miss. 移到 CURRENT TURN 区 (注入到最后一条 user 前部) —— 文本一字未改, 只换位置.
1832
+ const currentTurnContext = `${refineContext}${loopProgressSection}`;
1778
1833
  const systemPrompt = `${this.bootstrapAddition}你是 ${this.identity.name},基于ReAct (Reasoning + Acting)模式工作。${personaSection}
1779
1834
  当前工作目录: ${this.cwd}
1780
1835
  当前身份: ${this.identity.name} (${this.identity.did})
1781
- ${refineContext}
1782
1836
  ${this.currentIntentHint}
1783
- ${loopProgressSection}
1784
1837
 
1785
1838
  ${toolDefs}
1786
1839
 
@@ -1830,7 +1883,11 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
1830
1883
  // 2026-09-16 (Milestone 1-B): 模型调用前后走唯一门面 (扩展点 + 计数留痕; 默认不加新 hook 事件, 避免改变现有触发次数)
1831
1884
  this.piHarness().beforeModelCall(this.harnessCtx());
1832
1885
  const _modelCallT0 = Date.now();
1833
- const response = await this.callLlmWithRecovery(llm, messages, systemPrompt, signal, onStream, openaiFormattedTools);
1886
+ const response = await this.callLlmWithRecovery(llm, messages, systemPrompt, signal, onStream, openaiFormattedTools, currentTurnContext);
1887
+ // 2026-09-28 (前缀 KV 可命中): 把注入了 CURRENT TURN 的当前轮**原样写回自己的 messageHistory** ——
1888
+ // buildMessages() 每轮重建全新对象, 不写回的话下一轮那条 user 就退回注入前, 前缀从那里分叉.
1889
+ // 用 chat() 回带的 wire (真正发出去的那一份), 而不是把 messages 再拼一遍.
1890
+ this.writeBackCurrentTurn(response.messages);
1834
1891
  this.piHarness().afterModelCall(this.harnessCtx(), { ms: Date.now() - _modelCallT0 });
1835
1892
  const reply = (response.reply || '').trim();
1836
1893
  // 2026-06-30: OpenAI 协议 native tool_calls (LLM 真产了 tool_call 时, minimax/M3 会返回 id)
@@ -2571,6 +2628,11 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2571
2628
  const source = this.projectedHistory ?? this.messageHistory;
2572
2629
  const WINDOW = 15;
2573
2630
  const out = [];
2631
+ // 2026-09-28 (前缀 KV 可命中): 带 CURRENT TURN 注入的条目要**原样回带**.
2632
+ // 两个理由: (1) 注入块是"当前轮的前缀", 重渲染 (重加 `[工具结果]` 前缀 / 再 slice 截断)
2633
+ // 会改字节 → 下一轮前缀从这条起分叉; (2) 已注入的 tool 条目在 wire 上已经是 user 形状,
2634
+ // 再套一次前缀就是双前缀, 连内容都变了.
2635
+ const isInjectedEntry = (m) => typeof m.content === 'string' && m.content.includes(CURRENT_TURN_MARKER);
2574
2636
  // 早期历史压缩: 超过窗口时, 不直接丢弃 — 提取前段用户意图摘要注入 (同步, 无 LLM).
2575
2637
  // 结构对齐 Context OS: System Prompt(persona) + 压缩摘要 + 最近消息.
2576
2638
  if (source.length > WINDOW) {
@@ -2586,6 +2648,10 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2586
2648
  });
2587
2649
  for (const m of slice) {
2588
2650
  const r = m.role;
2651
+ if (isInjectedEntry(m)) {
2652
+ out.push({ role: 'user', content: String(m.content) });
2653
+ continue;
2654
+ }
2589
2655
  if (r === 'tool') {
2590
2656
  out.push({ role: 'user', content: `[工具结果]\n${(m.content || '').slice(0, 2000)}` });
2591
2657
  continue;
@@ -2608,6 +2674,10 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2608
2674
  const slice = source.slice(-WINDOW);
2609
2675
  for (const m of slice) {
2610
2676
  const r = m.role;
2677
+ if (isInjectedEntry(m)) {
2678
+ out.push({ role: 'user', content: String(m.content) });
2679
+ continue;
2680
+ }
2611
2681
  if (r === 'tool') {
2612
2682
  out.push({ role: 'user', content: `[工具结果]\n${m.content || ''}` });
2613
2683
  continue;
@@ -2631,6 +2701,20 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2631
2701
  return [{ role: 'user', content: this.buildContext() }];
2632
2702
  }
2633
2703
  }
2704
+ /**
2705
+ * 2026-09-28 (前缀 KV 可命中): 把 chat() 回带的当前轮写回自己的 messageHistory.
2706
+ * 逻辑在模块级 `writeBackCurrentTurnInto` (纯函数, 门可以直接驱它断言);
2707
+ * 这里只负责接到 `this.messageHistory` 上, 并保证任何异常都静默 (写回失败不影响对话).
2708
+ */
2709
+ writeBackCurrentTurn(wire) {
2710
+ try {
2711
+ return writeBackCurrentTurnInto(this.messageHistory, wire);
2712
+ }
2713
+ catch (err) {
2714
+ console.warn('[PiAgent] writeBackCurrentTurn failed (silent):', err);
2715
+ return 0;
2716
+ }
2717
+ }
2634
2718
  /**
2635
2719
  * 估算 messageHistory 的 token 数 (4 字符 ≈ 1 token, 与 context-compaction 同步).
2636
2720
  * 失败静默: 任何异常 → 0 (不阻塞)
@@ -2652,7 +2736,9 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2652
2736
  *
2653
2737
  * 失败静默: 全部失败 → 返回空 reply, 让上层 no-tool_use 终止
2654
2738
  */
2655
- async callLlmWithRecovery(llm, contextOrMessages, systemPrompt, signal, onStream, tools) {
2739
+ async callLlmWithRecovery(llm, contextOrMessages, systemPrompt, signal, onStream, tools,
2740
+ /** 2026-09-28: 当前轮易变段 (循环进度/改进提示) —— 进 CURRENT TURN 区, 不进 system */
2741
+ currentTurnContext) {
2656
2742
  // Reactive compaction 预检: 估算 token 超 80% 阈值, 跑一次
2657
2743
  const estimated = this.estimateHistoryTokens();
2658
2744
  if (estimated > this.maxContextTokens() * 0.8) {
@@ -2706,9 +2792,13 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2706
2792
  // M3.5 (2026-06-17): 传 messages 数组 (如果 contextOrMessages 是数组) 或字符串
2707
2793
  // 数组版让 LLM 看到结构化的 user/assistant/tool role, 而不是把 history 拼成单字符串
2708
2794
  // Bug 5: pass tool IDs for native OpenAI tool calling
2709
- const response = await llm.chat(contextOrMessages, systemPrompt, signal, tools);
2795
+ // 2026-09-28 (前缀 KV 可命中): 显式 purpose/source —— pi-sdk 的 ReAct 循环**就是主对话**
2796
+ // (完整 IMMUTABLE PREFIX + 工具全集 + CURRENT TURN, 也只有它带 cache_prompt);
2797
+ // currentTurnContext 是调用方自己的当前轮易变段 (循环进度/改进提示), 走 CURRENT TURN 区.
2798
+ // 2026-09-28: 回带最终 wire messages (含注入后的当前轮) → 上层据此写回 messageHistory.
2799
+ const response = await llm.chat(contextOrMessages, systemPrompt, signal, tools, 'main-agent', 'react-loop', undefined, currentTurnContext);
2710
2800
  // 2026-06-30: 透传 toolCalls (OpenAI 协议 native) 给上层, 让 assistant message 能 emit 真 id
2711
- return { reply: response.reply || '', toolCalls: response.toolCalls };
2801
+ return { reply: response.reply || '', toolCalls: response.toolCalls, messages: response.messages };
2712
2802
  }
2713
2803
  catch (err) {
2714
2804
  // 用户主动 abort: 不重试, 立即抛
@@ -2816,7 +2906,9 @@ ${PiAgentSession.TOOL_SELECTION_GUIDE}
2816
2906
  // 给 Context Collapse (虚拟投影) 和 Auto-Compact (摘要) 共用
2817
2907
  const llm = getMinimax();
2818
2908
  const llmChat = async (systemPrompt, userPrompt) => {
2819
- const r = await llm.chat(userPrompt, systemPrompt, signal);
2909
+ // 2026-09-28 (前缀 KV 可命中): 压缩摘要是**非主对话的一次性调用** → 轻量前缀
2910
+ // (它不该拖满 IMMUTABLE PREFIX, 更不该把主对话的 KV slot 顶掉).
2911
+ const r = await llm.chat(userPrompt, systemPrompt, signal, undefined, 'auto-compact', 'pi-sdk-compact');
2820
2912
  return r.reply;
2821
2913
  };
2822
2914
  // 2026-08-06: 预算 = ContextManager 配置 (1M * 55% ≈ 550K), 不再写死 8000.
@@ -3082,7 +3174,8 @@ ${this.extractOperationsFromRef(operationsRef)}
3082
3174
  const conversation = messages.map(m => `${m.type === 'user' ? '用户' : '助手'}: ${m.content}`).join('\n');
3083
3175
  const llm = getMinimax();
3084
3176
  try {
3085
- const response = await llm.chat(`根据以下对话内容,为这个对话生成一个简短的名称(不超过20个字):\n\n${conversation}\n\n直接输出名称,不要其他解释。`, '命名建议');
3177
+ const response = await llm.chat(`根据以下对话内容,为这个对话生成一个简短的名称(不超过20个字):\n\n${conversation}\n\n直接输出名称,不要其他解释。`, '命名建议', undefined, undefined, 'chat', 'session-namer' // 非主对话: 轻量前缀, 不抢主对话的 KV slot
3178
+ );
3086
3179
  const name = response.reply.trim();
3087
3180
  // 拒绝错误回退串 (LLM 不可用时返回的占位文本)
3088
3181
  if (!name)
@@ -329,7 +329,9 @@ export class WorkflowPivotLoop {
329
329
  // 2026-08-02: 传原生 OpenAI tools — deepseek 返回结构化 tool_calls,
330
330
  // UI 才能显示真实的工具执行 step (之前靠文本 JSON 猜格式, LLM 编造 result)
331
331
  const openAITools = this.buildOpenAITools();
332
- const llmResponse = await llm.chat(context, headerForThisIter, signal, openAITools);
332
+ // 2026-09-28: pivot loop 就是主对话循环 → purpose='main-agent'
333
+ // (完整 IMMUTABLE PREFIX + 工具全集 + CURRENT TURN; 也只有它带 cache_prompt)
334
+ const llmResponse = await llm.chat(context, headerForThisIter, signal, openAITools, 'main-agent', 'pivot-loop');
333
335
  const reply = (llmResponse.reply || '').trim();
334
336
  this.vlog(`[pivot] iter=${this.state.iteration} LLM took=${Date.now() - t0}ms reply=${reply.length} nativeToolCalls=${llmResponse.toolCalls?.length ?? 0} head=${reply.substring(0, 80).replace(/\n/g, ' ')}`);
335
337
  this.emit({ type: 'token', content: reply });