@bolloon/bolloon-agent 0.4.5 → 0.4.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.
Files changed (206) hide show
  1. package/README.md +135 -135
  2. package/bin/bolloon-cli.cjs +183 -183
  3. package/bin/bolloon.cjs +0 -0
  4. package/bin/bolloon.js +157 -0
  5. package/dist/agents/constraint-layer.js +19 -19
  6. package/dist/agents/judgment-protocol.js +14 -14
  7. package/dist/agents/pi-sdk-tools.js +62 -45
  8. package/dist/agents/pi-sdk-types.js +12 -12
  9. package/dist/agents/pi-sdk.js +103 -90
  10. package/dist/agents/skill-organizer.js +19 -19
  11. package/dist/agents/skill-writer.js +15 -3
  12. package/dist/agents/workflow-pivot-loop.js +13 -3
  13. package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
  14. package/dist/bollharness-integration/context-router-judgment.js +4 -4
  15. package/dist/bollharness-integration/context-router.js +292 -292
  16. package/dist/bollharness-integration/gate-state-machine.js +13 -13
  17. package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
  18. package/dist/bollharness-integration/skill-adapter.js +21 -21
  19. package/dist/bootstrap/chat-archiver.js +14 -14
  20. package/dist/bootstrap/context-os.js +24 -24
  21. package/dist/bootstrap/memory-compressor.js +14 -14
  22. package/dist/cli/ink-app.js +18 -0
  23. package/dist/cli-entry.js +36 -36
  24. package/dist/context-compaction/auto-compact.js +7 -7
  25. package/dist/electron/first-run.js +54 -54
  26. package/dist/electron-build/electron/first-run.js +54 -54
  27. package/dist/electron-preload.js.map +1 -1
  28. package/dist/electron.js.map +1 -1
  29. package/dist/index.js +222 -138
  30. package/dist/llm/llm-judgment-client.js +102 -102
  31. package/dist/llm/pi-ai.js +48 -48
  32. package/dist/llm/system-prompt/layers/channel/human-async.md +41 -41
  33. package/dist/llm/system-prompt/layers/channel/local.md +14 -14
  34. package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
  35. package/dist/llm/system-prompt/layers/channel/p2p-peer-sync.md +51 -51
  36. package/dist/llm/system-prompt/layers/channel/p2p-proactive.md +43 -43
  37. package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
  38. package/dist/llm/system-prompt/layers/channel/session-handoff.md +61 -61
  39. package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
  40. package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
  41. package/dist/llm/system-prompt/layers/core/external-engagement.md +73 -73
  42. package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
  43. package/dist/llm/system-prompt/layers/core/identity.md +48 -48
  44. package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
  45. package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
  46. package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
  47. package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
  48. package/dist/llm/system-prompt/layers/core/tone.md +31 -31
  49. package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
  50. package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
  51. package/dist/llm/system-prompt/layers/role/architect.md +20 -20
  52. package/dist/llm/system-prompt/layers/role/expert.md +19 -19
  53. package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
  54. package/dist/llm/system-prompt/layers/role/security.md +15 -15
  55. package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
  56. package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
  57. package/dist/llm/system-prompt/layers/tool/goal_handoff.md +77 -77
  58. package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
  59. package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
  60. package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
  61. package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
  62. package/dist/llm/system-prompt/layers/tool/p2p_request.md +61 -61
  63. package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
  64. package/dist/llm/tool-manifest/ask_user_input.js +1 -1
  65. package/dist/llm/tool-manifest/bash.js +3 -3
  66. package/dist/llm/tool-manifest/create_file.js +4 -4
  67. package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
  68. package/dist/llm/tool-manifest/image_search.js +2 -2
  69. package/dist/llm/tool-manifest/index.js +0 -59
  70. package/dist/llm/tool-manifest/mcp.js +2 -2
  71. package/dist/llm/tool-manifest/message_compose.js +3 -3
  72. package/dist/llm/tool-manifest/places.js +2 -2
  73. package/dist/llm/tool-manifest/present_files.js +1 -1
  74. package/dist/llm/tool-manifest/recipe.js +2 -2
  75. package/dist/llm/tool-manifest/str_replace.js +5 -5
  76. package/dist/llm/tool-manifest/view.js +3 -3
  77. package/dist/llm/tool-manifest/weather.js +4 -4
  78. package/dist/llm/tool-manifest/web.js +4 -4
  79. package/dist/orbitdb/agent-tools.js +139 -0
  80. package/dist/orbitdb/kanban-store.js +259 -0
  81. package/dist/orbitdb/task-store.js +112 -0
  82. package/dist/pi-ecosystem-colony/index.js +365 -0
  83. package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
  84. package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
  85. package/dist/pi-ecosystem-judgment/distillation.js +14 -14
  86. package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
  87. package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
  88. package/dist/security/context-router-tool.js +15 -15
  89. package/dist/security/tool-gate.js +2 -0
  90. package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
  91. package/dist/social/ant-colony/PheromoneEngine.js +227 -0
  92. package/dist/social/ant-colony/index.js +6 -0
  93. package/dist/social/ant-colony/types.js +24 -0
  94. package/dist/storage/trajectory.js +101 -0
  95. package/dist/test/ai-judgment-test.js +80 -0
  96. package/dist/test/bollharness-integration.test.js +318 -0
  97. package/dist/test/channel-agent-multi-dialogue.js +205 -0
  98. package/dist/test/channel-heartbeat-agent-test.js +201 -0
  99. package/dist/test/constraint-layer.test.js +164 -0
  100. package/dist/test/diap-identity-test.js +172 -0
  101. package/dist/test/diap-quick-test.js +62 -0
  102. package/dist/test/global-shared-context.test.js +315 -0
  103. package/dist/test/harness-judgment-injection.test.js +246 -0
  104. package/dist/test/harness-workflow-integrator-test.js +228 -0
  105. package/dist/test/human-value-store.test.js +243 -0
  106. package/dist/test/hybrid-integration-test.js +118 -0
  107. package/dist/test/hybrid-messenger-verify.js +55 -0
  108. package/dist/test/iroh-bistream-debug.js +38 -0
  109. package/dist/test/iroh-communication.test.js +66 -0
  110. package/dist/test/iroh-debug-test.js +57 -0
  111. package/dist/test/iroh-diap-test.js +71 -0
  112. package/dist/test/iroh-direct-connect.js +55 -0
  113. package/dist/test/iroh-e2e-fixed.js +89 -0
  114. package/dist/test/iroh-e2e-same-process.js +63 -0
  115. package/dist/test/iroh-e2e.js +66 -0
  116. package/dist/test/iroh-final-e2e.js +72 -0
  117. package/dist/test/iroh-relay-test.js +37 -0
  118. package/dist/test/iroh-simple-test.js +41 -0
  119. package/dist/test/iroh-transport-verify.js +54 -0
  120. package/dist/test/iroh-transport.test.js +37 -0
  121. package/dist/test/iroh-two-nodes.js +70 -0
  122. package/dist/test/iroh-verify.js +44 -0
  123. package/dist/test/judgment-decision.test.js +219 -0
  124. package/dist/test/llm-judgment-integration.test.js +220 -0
  125. package/dist/test/p2p-agent-complex-dialogue.js +385 -0
  126. package/dist/test/p2p-agent-dialogue.js +341 -0
  127. package/dist/test/p2p-agent-full-bidirectional.js +510 -0
  128. package/dist/test/p2p-agent-harness-flow.js +437 -0
  129. package/dist/test/p2p-agent-harness-single.js +143 -0
  130. package/dist/test/p2p-ai-dialogue-test.js +318 -0
  131. package/dist/test/p2p-cid-connect-test.js +195 -0
  132. package/dist/test/p2p-connect-receiver.js +69 -0
  133. package/dist/test/p2p-doc-transfer.js +110 -0
  134. package/dist/test/p2p-identity-page-test.js +77 -0
  135. package/dist/test/p2p-iroh-test.js +171 -0
  136. package/dist/test/p2p-minimal-test.js +241 -0
  137. package/dist/test/p2p-node-1.js +148 -0
  138. package/dist/test/p2p-node-2.js +148 -0
  139. package/dist/test/p2p-server.js +281 -0
  140. package/dist/test/p2p-two-nodes-test.js +438 -0
  141. package/dist/test/pi-sdk.test.js +44 -0
  142. package/dist/test/set-persona.js +40 -0
  143. package/dist/test/simple.test.js +9 -0
  144. package/dist/test/storage-integration.test.js +150 -0
  145. package/dist/test/subagent-manager.test.js +276 -0
  146. package/dist/test/test-gate-flow.test.js +81 -0
  147. package/dist/test/workflow-engine.test.js +87 -0
  148. package/dist/test/workflow-pivot-loop.test.js +246 -0
  149. package/dist/web/api-config.html +779 -779
  150. package/dist/web/client-hearth.js +10 -10
  151. package/dist/web/components/wallet-viem.mjs +118 -118
  152. package/dist/web/index.html +472 -472
  153. package/dist/web/manifest.json +20 -20
  154. package/dist/web/server-storage.js +29 -0
  155. package/dist/web/server.js +30 -14
  156. package/dist/web/style.css +4946 -4946
  157. package/package.json +195 -195
  158. package/scripts/build-cli.js +215 -215
  159. package/scripts/build-web.ts +130 -130
  160. package/scripts/postinstall.js +152 -152
  161. package/bin/bolloon-daemon.sh +0 -207
  162. package/dist/constraint-runtime/reference_data/archive_surface_snapshot.json +0 -63
  163. package/dist/constraint-runtime/reference_data/commands_snapshot.json +0 -1037
  164. package/dist/constraint-runtime/reference_data/subsystems/assistant.json +0 -8
  165. package/dist/constraint-runtime/reference_data/subsystems/bootstrap.json +0 -8
  166. package/dist/constraint-runtime/reference_data/subsystems/bridge.json +0 -32
  167. package/dist/constraint-runtime/reference_data/subsystems/buddy.json +0 -13
  168. package/dist/constraint-runtime/reference_data/subsystems/cli.json +0 -26
  169. package/dist/constraint-runtime/reference_data/subsystems/components.json +0 -32
  170. package/dist/constraint-runtime/reference_data/subsystems/constants.json +0 -28
  171. package/dist/constraint-runtime/reference_data/subsystems/coordinator.json +0 -8
  172. package/dist/constraint-runtime/reference_data/subsystems/entrypoints.json +0 -15
  173. package/dist/constraint-runtime/reference_data/subsystems/hooks.json +0 -32
  174. package/dist/constraint-runtime/reference_data/subsystems/keybindings.json +0 -21
  175. package/dist/constraint-runtime/reference_data/subsystems/memdir.json +0 -15
  176. package/dist/constraint-runtime/reference_data/subsystems/migrations.json +0 -18
  177. package/dist/constraint-runtime/reference_data/subsystems/moreright.json +0 -8
  178. package/dist/constraint-runtime/reference_data/subsystems/native_ts.json +0 -11
  179. package/dist/constraint-runtime/reference_data/subsystems/outputStyles.json +0 -8
  180. package/dist/constraint-runtime/reference_data/subsystems/plugins.json +0 -9
  181. package/dist/constraint-runtime/reference_data/subsystems/remote.json +0 -11
  182. package/dist/constraint-runtime/reference_data/subsystems/schemas.json +0 -8
  183. package/dist/constraint-runtime/reference_data/subsystems/screens.json +0 -10
  184. package/dist/constraint-runtime/reference_data/subsystems/server.json +0 -10
  185. package/dist/constraint-runtime/reference_data/subsystems/services.json +0 -32
  186. package/dist/constraint-runtime/reference_data/subsystems/skills.json +0 -27
  187. package/dist/constraint-runtime/reference_data/subsystems/state.json +0 -13
  188. package/dist/constraint-runtime/reference_data/subsystems/types.json +0 -18
  189. package/dist/constraint-runtime/reference_data/subsystems/upstreamproxy.json +0 -9
  190. package/dist/constraint-runtime/reference_data/subsystems/utils.json +0 -32
  191. package/dist/constraint-runtime/reference_data/subsystems/vim.json +0 -12
  192. package/dist/constraint-runtime/reference_data/subsystems/voice.json +0 -8
  193. package/dist/constraint-runtime/reference_data/tools_snapshot.json +0 -922
  194. package/dist/electron/config.js.map +0 -1
  195. package/dist/electron/dialogs.js.map +0 -1
  196. package/dist/electron/first-run.js.map +0 -1
  197. package/dist/electron/ipc.js.map +0 -1
  198. package/dist/electron/logger.js.map +0 -1
  199. package/dist/electron/main.js.map +0 -1
  200. package/dist/electron/menu.js.map +0 -1
  201. package/dist/electron/paths.js.map +0 -1
  202. package/dist/electron/server.js.map +0 -1
  203. package/dist/electron/tray.js.map +0 -1
  204. package/dist/electron/window.js.map +0 -1
  205. package/dist/utils/auto-update.js.map +0 -1
  206. package/dist/web/client.js.map +0 -7
@@ -48,65 +48,6 @@ export function getToolsByLayer(layerId) {
48
48
  *
49
49
  * 不包含: 完整 parameters schema (那是 PiAI 客户端在调用时读)
50
50
  */
51
- /**
52
- * 把 ToolManifest 转成 OpenAI function calling 格式 (tools 数组)
53
- * 用于 native tool_choice: "auto" 模式, 让 LLM 选择调用.
54
- *
55
- * 注意: 递归处理嵌套参数 (type='object' 的 properties / type='array' 的 items)
56
- */
57
- export function formatForOpenAI(tools) {
58
- const list = tools ?? ALL;
59
- return list.map((t) => {
60
- const properties = {};
61
- const required = [];
62
- for (const p of t.parameters) {
63
- properties[p.name] = convertParameter(p);
64
- if (p.required)
65
- required.push(p.name);
66
- }
67
- return {
68
- type: 'function',
69
- function: {
70
- name: t.id,
71
- description: t.oneLine,
72
- parameters: {
73
- type: 'object',
74
- properties,
75
- required,
76
- },
77
- },
78
- };
79
- });
80
- }
81
- function convertParameter(p) {
82
- const schema = { type: p.type === 'enum' ? 'string' : p.type };
83
- if (p.description)
84
- schema.description = p.description;
85
- if (p.enumValues)
86
- schema.enum = p.enumValues;
87
- if (p.default !== undefined)
88
- schema.default = p.default;
89
- if (p.minimum !== undefined)
90
- schema.minimum = p.minimum;
91
- if (p.maximum !== undefined)
92
- schema.maximum = p.maximum;
93
- if (p.format)
94
- schema.format = p.format;
95
- if (p.type === 'object' && p.properties) {
96
- schema.properties = {};
97
- for (const sub of p.properties) {
98
- schema.properties[sub.name] = convertParameter(sub);
99
- }
100
- }
101
- if (p.type === 'array' && p.items) {
102
- schema.items = convertParameter(p.items);
103
- if (p.minItems !== undefined)
104
- schema.minItems = p.minItems;
105
- if (p.maxItems !== undefined)
106
- schema.maxItems = p.maxItems;
107
- }
108
- return schema;
109
- }
110
51
  export function formatForPrompt(tools) {
111
52
  const list = tools ?? ALL;
112
53
  const lines = [
@@ -15,7 +15,7 @@ export const search_mcp_registry = {
15
15
  parameters: [
16
16
  { name: 'keywords', type: 'array', required: true, description: '搜索关键词', minItems: 1, items: { name: 'kw', type: 'string', required: true, description: '关键词' } }
17
17
  ],
18
- callExample: `[TOOL:search_mcp_registry]
18
+ callExample: `[TOOL:search_mcp_registry]
19
19
  [P:keywords]${'${'}JSON.stringify(["calendar","schedule"])[ENDTOOL]`,
20
20
  layerId: 'tool.mcp_apps',
21
21
  };
@@ -37,7 +37,7 @@ export const suggest_connectors = {
37
37
  parameters: [
38
38
  { name: 'uuids', type: 'array', required: true, description: '从 search_mcp_registry 结果的 directoryUuid 复制', minItems: 1, items: { name: 'u', type: 'string', required: true, description: 'uuid' } }
39
39
  ],
40
- callExample: `[TOOL:suggest_connectors]
40
+ callExample: `[TOOL:suggest_connectors]
41
41
  [P:uuids]${'${'}JSON.stringify(["uuid-from-search-result"])[ENDTOOL]`,
42
42
  layerId: 'tool.mcp_apps',
43
43
  };
@@ -23,9 +23,9 @@ export const message_compose_v1 = {
23
23
  ],
24
24
  } },
25
25
  ],
26
- callExample: `[TOOL:message_compose_v1]
27
- [P:kind]email
28
- [P:summary_title]Follow-up on Q3 review
26
+ callExample: `[TOOL:message_compose_v1]
27
+ [P:kind]email
28
+ [P:summary_title]Follow-up on Q3 review
29
29
  [P:variants]${'${'}JSON.stringify([{"label":"direct","subject":"Quick follow-up","body":"..."}])[ENDTOOL]`,
30
30
  layerId: 'tool.manifest',
31
31
  };
@@ -23,7 +23,7 @@ export const places_search = {
23
23
  { name: 'location_bias_lng', type: 'number', required: false, description: '可选经度' },
24
24
  { name: 'location_bias_radius', type: 'number', required: false, description: '米 (默认 5000)', default: 5000 }
25
25
  ],
26
- callExample: `[TOOL:places_search]
26
+ callExample: `[TOOL:places_search]
27
27
  [P:queries]${'${'}JSON.stringify([{"query":"ramen restaurants Tokyo"}])[ENDTOOL]`,
28
28
  layerId: 'tool.manifest',
29
29
  };
@@ -77,7 +77,7 @@ export const places_map_display_v0 = {
77
77
  { name: 'title', type: 'string', required: false, description: '行程标题' },
78
78
  { name: 'narrative', type: 'string', required: false, description: '行程叙述' },
79
79
  ],
80
- callExample: `[TOOL:places_map_display_v0]
80
+ callExample: `[TOOL:places_map_display_v0]
81
81
  [P:locations]${'${'}JSON.stringify([{"name":"Senso-ji","latitude":35.7148,"longitude":139.7967,"place_id":"ChIJ..."}])[ENDTOOL]`,
82
82
  layerId: 'tool.manifest',
83
83
  };
@@ -15,7 +15,7 @@ export const present_files = {
15
15
  parameters: [
16
16
  { name: 'filepaths', type: 'array', required: true, description: '至少 1 个文件路径', minItems: 1, items: { name: 'path', type: 'string', required: true, description: '容器内路径' } },
17
17
  ],
18
- callExample: `[TOOL:present_files]
18
+ callExample: `[TOOL:present_files]
19
19
  [P:filepaths]${'${'}JSON.stringify(["/home/bolloon/report.md"])[ENDTOOL]`,
20
20
  layerId: 'tool.manifest',
21
21
  };
@@ -33,8 +33,8 @@ export const recipe_display_v0 = {
33
33
  } },
34
34
  { name: 'notes', type: 'string', required: false, description: '附加说明' },
35
35
  ],
36
- callExample: `[TOOL:recipe_display_v0]
37
- [P:title]Pasta
36
+ callExample: `[TOOL:recipe_display_v0]
37
+ [P:title]Pasta
38
38
  [P:ingredients]${'${'}JSON.stringify([{"id":"a1","amount":500,"name":"pasta"}])[ENDTOOL]`,
39
39
  layerId: 'tool.manifest',
40
40
  };
@@ -17,11 +17,11 @@ export const str_replace = {
17
17
  { name: 'new_str', type: 'string', required: true, description: '新字符串 (空字符串 = 删除)', default: '' },
18
18
  { name: 'path', type: 'string', required: true, description: '文件路径' },
19
19
  ],
20
- callExample: `[TOOL:str_replace]
21
- [P:description]修复 typo
22
- [P:old_str]recieve
23
- [P:new_str]receive
24
- [P:path]/home/bolloon/main.py
20
+ callExample: `[TOOL:str_replace]
21
+ [P:description]修复 typo
22
+ [P:old_str]recieve
23
+ [P:new_str]receive
24
+ [P:path]/home/bolloon/main.py
25
25
  [ENDTOOL]`,
26
26
  layerId: 'tool.manifest',
27
27
  };
@@ -16,9 +16,9 @@ export const view = {
16
16
  { name: 'path', type: 'string', required: true, description: '绝对路径' },
17
17
  { name: 'view_range', type: 'array', required: false, description: '[start, end] 1-indexed, end=-1 表示到末尾', minItems: 2, maxItems: 2, items: { name: 'r', type: 'integer', required: true, description: '行号' } }
18
18
  ],
19
- callExample: `[TOOL:view]
20
- [P:description]读 README
21
- [P:path]/home/bolloon/README.md
19
+ callExample: `[TOOL:view]
20
+ [P:description]读 README
21
+ [P:path]/home/bolloon/README.md
22
22
  [ENDTOOL]`,
23
23
  layerId: 'tool.manifest',
24
24
  };
@@ -18,10 +18,10 @@ export const weather_fetch = {
18
18
  { name: 'latitude', type: 'number', required: true, description: '纬度' },
19
19
  { name: 'longitude', type: 'number', required: true, description: '经度' },
20
20
  ],
21
- callExample: `[TOOL:weather_fetch]
22
- [P:location_name]San Francisco, CA
23
- [P:latitude]37.78
24
- [P:longitude]-122.41
21
+ callExample: `[TOOL:weather_fetch]
22
+ [P:location_name]San Francisco, CA
23
+ [P:latitude]37.78
24
+ [P:longitude]-122.41
25
25
  [ENDTOOL]`,
26
26
  layerId: 'tool.manifest',
27
27
  };
@@ -15,8 +15,8 @@ export const web_search = {
15
15
  parameters: [
16
16
  { name: 'query', type: 'string', required: true, description: '搜索查询 (1-6 词最佳)' },
17
17
  ],
18
- callExample: `[TOOL:web_search]
19
- [P:query]S&P 500 current price
18
+ callExample: `[TOOL:web_search]
19
+ [P:query]S&P 500 current price
20
20
  [ENDTOOL]`,
21
21
  layerId: 'tool.web_search',
22
22
  };
@@ -44,8 +44,8 @@ export const web_fetch = {
44
44
  { name: 'web_fetch_rate_limit_key', type: 'string', required: false, description: '速率限制键' },
45
45
  { name: 'web_fetch_rate_limit_dark_launch', type: 'boolean', required: false, description: '速率限制暗启动模式' },
46
46
  ],
47
- callExample: `[TOOL:web_fetch]
48
- [P:url]https://example.com/article
47
+ callExample: `[TOOL:web_fetch]
48
+ [P:url]https://example.com/article
49
49
  [ENDTOOL]`,
50
50
  layerId: 'tool.manifest',
51
51
  };
@@ -24,6 +24,138 @@ async function uiStore() {
24
24
  return getUICidStore();
25
25
  }
26
26
  const fmt = (o) => JSON.stringify(o, null, 2).slice(0, 3000);
27
+ /** 2026-08-12 (Task5): Kanban 看板工具 (Hermes kanban_db → OrbitDB). 见 kanban-store.ts. */
28
+ async function kanbanBoard(boardId) {
29
+ const { openKanbanBoard } = await import('./kanban-store.js');
30
+ return openKanbanBoard(boardId);
31
+ }
32
+ const fmtRows = (tasks) => tasks.length === 0 ? '📭 无任务' : tasks.map(t => ` [${t.status}] ${t.id} ${t.title}${t.assignee ? ` (${t.assignee})` : ''}`).join('\n');
33
+ export function registerKanbanTools(ctx) {
34
+ ctx.tools.set('kanban_create', {
35
+ name: 'kanban_create',
36
+ description: '在看板上创建任务 (OrbitDB 持久化, 跨设备同步). 状态从 triage 开始.',
37
+ parameters: { boardId: '看板 id (必填, 默认用 agentId)', title: '任务标题 (必填)', body: '任务详情 (可选)', parentIds: '父任务 id 数组 (可选, 全部 done 才可执行)', createdBy: '创建者 (可选)', dueAt: '计划时间戳 (可选)' },
38
+ execute: async (args) => {
39
+ try {
40
+ const boardId = String(args.boardId || args.agentId || '').trim() || 'default';
41
+ const title = String(args.title || '').trim();
42
+ if (!title)
43
+ return { success: false, error: 'title 必填' };
44
+ const parentIds = Array.isArray(args.parentIds) ? args.parentIds.map(String) : [];
45
+ const dueAt = typeof args.dueAt === 'number' ? args.dueAt : null;
46
+ const task = await (await kanbanBoard(boardId)).createTask({ boardId, title, body: String(args.body || '').trim(), parentIds, createdBy: String(args.createdBy || '').trim() || undefined, dueAt });
47
+ return { success: true, output: `📌 已创建任务 (${boardId}):\n id: ${task.id}\n 状态: ${task.status}\n 推进: kanban_claim(id="${task.id}") → kanban_complete(...)` };
48
+ }
49
+ catch (e) {
50
+ return { success: false, error: `kanban_create 失败: ${String(e.message || e).slice(0, 200)}` };
51
+ }
52
+ }
53
+ });
54
+ ctx.tools.set('kanban_list', {
55
+ name: 'kanban_list',
56
+ description: '列出看板任务 (可按状态过滤). 状态: triage/todo/scheduled/ready/running/blocked/review/done/archived.',
57
+ parameters: { boardId: '看板 id (必填)', status: '可选: 按状态过滤' },
58
+ execute: async (args) => {
59
+ try {
60
+ const boardId = String(args.boardId || '').trim() || 'default';
61
+ const status = String(args.status || '').trim() || undefined;
62
+ const tasks = await (await kanbanBoard(boardId)).listTasks(status);
63
+ return { success: true, output: `🗂 ${boardId} ${status ? `[${status}]` : '全部'} ${tasks.length} 个任务:\n${fmtRows(tasks)}` };
64
+ }
65
+ catch (e) {
66
+ return { success: false, error: `kanban_list 失败: ${String(e.message || e).slice(0, 200)}` };
67
+ }
68
+ }
69
+ });
70
+ ctx.tools.set('kanban_get', {
71
+ name: 'kanban_get',
72
+ description: '查看单个任务详情 (含 parentIds/认领人/失败计数/createdRefs).',
73
+ parameters: { boardId: '看板 id (必填)', id: '任务 id (必填)' },
74
+ execute: async (args) => {
75
+ try {
76
+ const boardId = String(args.boardId || '').trim() || 'default';
77
+ const id = String(args.id || '').trim();
78
+ if (!id)
79
+ return { success: false, error: 'id 必填' };
80
+ const t = await (await kanbanBoard(boardId)).getTask(id);
81
+ if (!t)
82
+ return { success: false, error: `任务不存在: ${id}` };
83
+ return { success: true, output: `📄 ${t.id} [${t.status}]\n 标题: ${t.title}\n 详情: ${t.body || '—'}\n 父任务: ${t.parentIds.length ? t.parentIds.join(', ') : '无'}\n 认领人: ${t.assignee || t.claimLock || '未认领'}\n 失败计数: ${t.consecutiveFailures}\n 引用: ${t.createdRefs.length ? t.createdRefs.join(', ') : '无'}\n 备注: ${t.reason || '—'}` };
84
+ }
85
+ catch (e) {
86
+ return { success: false, error: `kanban_get 失败: ${String(e.message || e).slice(0, 200)}` };
87
+ }
88
+ }
89
+ });
90
+ ctx.tools.set('kanban_claim', {
91
+ name: 'kanban_claim',
92
+ description: '原子认领一个 ready 任务 (CAS, 至多一个 worker 成功). 认领后状态 → running.',
93
+ parameters: { boardId: '看板 id (必填)', id: '任务 id (必填)', worker: '认领者标识 (默认当前 agentId)' },
94
+ execute: async (args) => {
95
+ try {
96
+ const boardId = String(args.boardId || '').trim() || 'default';
97
+ const id = String(args.id || '').trim();
98
+ const worker = String(args.worker || '').trim() || 'agent';
99
+ if (!id)
100
+ return { success: false, error: 'id 必填' };
101
+ const r = await (await kanbanBoard(boardId)).claimTask(id, worker);
102
+ if (!r.ok)
103
+ return { success: false, error: `认领失败: ${r.reason}` };
104
+ return { success: true, output: `✅ 已认领 ${id} (worker=${worker})\n 完成: kanban_complete(id="${id}", worker="${worker}")` };
105
+ }
106
+ catch (e) {
107
+ return { success: false, error: `kanban_claim 失败: ${String(e.message || e).slice(0, 200)}` };
108
+ }
109
+ }
110
+ });
111
+ ctx.tools.set('kanban_complete', {
112
+ name: 'kanban_complete',
113
+ description: '完成一个 running 任务 → done (自动释放认领锁, 父任务完成时自动解子任务). createdRefs 声称的引用会做防幻觉校验.',
114
+ parameters: { boardId: '看板 id (必填)', id: '任务 id (必填)', worker: '认领者 (默认当前 agentId)', createdRefs: '可选: 声称创建的任务/资源引用数组' },
115
+ execute: async (args) => {
116
+ try {
117
+ const boardId = String(args.boardId || '').trim() || 'default';
118
+ const id = String(args.id || '').trim();
119
+ const worker = String(args.worker || '').trim() || 'agent';
120
+ if (!id)
121
+ return { success: false, error: 'id 必填' };
122
+ const refs = Array.isArray(args.createdRefs) ? args.createdRefs.map(String) : [];
123
+ const t = await (await kanbanBoard(boardId)).completeTask(id, worker, refs);
124
+ if (!t)
125
+ return { success: false, error: `无法完成 ${id} (不存在 或 非本 worker 认领)` };
126
+ return { success: true, output: `🏁 已完成 ${id} [done]${t.reason ? `\n 提醒: ${t.reason}` : ''}` };
127
+ }
128
+ catch (e) {
129
+ return { success: false, error: `kanban_complete 失败: ${String(e.message || e).slice(0, 200)}` };
130
+ }
131
+ }
132
+ });
133
+ ctx.tools.set('kanban_status', {
134
+ name: 'kanban_status',
135
+ description: '手动推进任务状态 (review/done/blocked/archived/ready 等合法迁移). blocked 需带 reason; archived 为终态.',
136
+ parameters: { boardId: '看板 id (必填)', id: '任务 id (必填)', status: '目标状态 (必填)', reason: 'blocked/review 时必填备注' },
137
+ execute: async (args) => {
138
+ try {
139
+ const boardId = String(args.boardId || '').trim() || 'default';
140
+ const id = String(args.id || '').trim();
141
+ const status = String(args.status || '').trim();
142
+ if (!id || !status)
143
+ return { success: false, error: 'id 和 status 必填' };
144
+ let t = null;
145
+ if (status === 'review')
146
+ t = await (await kanbanBoard(boardId)).requestReview(id, String(args.reason || '').trim());
147
+ else
148
+ t = await (await kanbanBoard(boardId)).setStatus(id, status, String(args.reason || '').trim());
149
+ if (!t)
150
+ return { success: false, error: `状态迁移失败 (非法迁移或任务不存在): ${id} → ${status}` };
151
+ return { success: true, output: `↻ ${id} → [${t.status}]${t.reason ? ` (${t.reason})` : ''}` };
152
+ }
153
+ catch (e) {
154
+ return { success: false, error: `kanban_status 失败: ${String(e.message || e).slice(0, 200)}` };
155
+ }
156
+ }
157
+ });
158
+ }
27
159
  export function registerOrbitdbTools(ctx) {
28
160
  ctx.tools.set('cid_save', {
29
161
  name: 'cid_save',
@@ -222,4 +354,11 @@ export function registerOrbitdbTools(ctx) {
222
354
  }
223
355
  }
224
356
  });
357
+ // 2026-08-12 (Task5): Kanban 看板工具 (Hermes kanban_db → OrbitDB)
358
+ try {
359
+ registerKanbanTools(ctx);
360
+ }
361
+ catch (odbErr) {
362
+ console.warn('[registerTools] Kanban 工具注册失败 (非致命):', odbErr);
363
+ }
225
364
  }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * kanban-store.ts — Hermes kanban 的 OrbitDB 落地 (2026-08-12, Task5)
3
+ *
4
+ * 借鉴 hermes_cli/kanban_db.py 的 9 态任务看板 + 原子认领 (CAS), 存到 OrbitDB
5
+ * keyvalue store (去中心化, DID 复制可跨设备同步). 每个 board 一个 store:
6
+ * bolloon-kanban-<boardId>
7
+ *
8
+ * 任务 9 态 (VALID_STATUSES):
9
+ * triage → todo → (父全部 done) → ready → running → done / archived
10
+ * - scheduled: 定时任务 (本次简化为状态 + dueAt, 到时可 promote)
11
+ * - blocked: 主动 block (sticky, 需显式 unblock) 或父依赖未完成 (自动解)
12
+ * - review: request_review 挂起等人工审批, approve → done
13
+ * - archived: 终态归档
14
+ *
15
+ * 原子认领 (CAS, 至多一个 worker 认领成功): claim_task 只在
16
+ * status === 'ready' && claimLock 为空 时置 running + claimLock + claimExpires,
17
+ * 否则返回输家 (不重试, 无分布式锁).
18
+ *
19
+ * 安全:
20
+ * - 依赖的 KV store (put/get/all) 可注入 → 单测用内存 map, 生产用 OrbitDB store.
21
+ * - 任务字段全部有默认值 (dag-cbor 不支持 undefined).
22
+ */
23
+ export const KANBAN_STATUSES = ['triage', 'todo', 'scheduled', 'ready', 'running', 'blocked', 'review', 'done', 'archived'];
24
+ const now = () => Date.now();
25
+ function fresh(id, boardId, title, base = {}) {
26
+ return {
27
+ id, boardId, title,
28
+ body: base.body ?? '',
29
+ status: base.status ?? 'triage',
30
+ parentIds: base.parentIds ?? [],
31
+ createdBy: base.createdBy ?? '',
32
+ assignee: base.assignee ?? '',
33
+ dueAt: base.dueAt ?? null,
34
+ claimLock: base.claimLock ?? '',
35
+ claimExpires: base.claimExpires ?? 0,
36
+ consecutiveFailures: base.consecutiveFailures ?? 0,
37
+ createdRefs: base.createdRefs ?? [],
38
+ reason: base.reason ?? '',
39
+ review: base.review ?? '',
40
+ createdAt: base.createdAt ?? now(),
41
+ updatedAt: base.updatedAt ?? now(),
42
+ completedAt: base.completedAt ?? null,
43
+ archivedAt: base.archivedAt ?? null,
44
+ };
45
+ }
46
+ export function taskKey(id) {
47
+ return `task:${id}`;
48
+ }
49
+ /**
50
+ * KanbanBoard — 绑定一个 KV store 的看板.
51
+ * 所有写都经 readTask→改→put 的 CAS 语义; claim 在内核判赢家.
52
+ */
53
+ export class KanbanBoard {
54
+ kv;
55
+ constructor(kv) {
56
+ this.kv = kv;
57
+ }
58
+ st(s) {
59
+ return JSON.parse(JSON.stringify(s));
60
+ }
61
+ async createTask(input) {
62
+ const id = `t_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
63
+ const task = fresh(id, input.boardId, input.title, {
64
+ body: input.body ?? '',
65
+ parentIds: input.parentIds ?? [],
66
+ createdBy: input.createdBy ?? '',
67
+ dueAt: input.dueAt ?? null,
68
+ status: 'triage',
69
+ });
70
+ await this.kv.put(taskKey(id), JSON.parse(JSON.stringify(task)));
71
+ return this.st(task);
72
+ }
73
+ async getTask(id) {
74
+ const v = await this.kv.get(taskKey(id));
75
+ return v || null;
76
+ }
77
+ async listTasks(status) {
78
+ const all = await this.kv.all();
79
+ const tasks = [];
80
+ for (const e of all) {
81
+ const t = e.value;
82
+ if (t && typeof t === 'object' && t.title !== undefined) {
83
+ if (!status || t.status === status)
84
+ tasks.push(this.st(t));
85
+ }
86
+ }
87
+ return tasks.sort((a, b) => a.createdAt - b.createdAt);
88
+ }
89
+ /** 保存变更 (校验合法) */
90
+ async persist(task) {
91
+ task.updatedAt = now();
92
+ await this.kv.put(taskKey(task.id), JSON.parse(JSON.stringify(task)));
93
+ return this.st(task);
94
+ }
95
+ /**
96
+ * 状态推进 gate: 只允许合法迁移 (防乱跳). 返回 null 表示禁止.
97
+ */
98
+ trans(from, to) {
99
+ if (from === to)
100
+ return true;
101
+ if (from === 'done' || from === 'archived')
102
+ return false; // 终态不可再动
103
+ const allowed = {
104
+ triage: ['todo', 'scheduled', 'blocked', 'archived'],
105
+ todo: ['scheduled', 'ready', 'blocked', 'archived'],
106
+ scheduled: ['todo', 'ready', 'blocked', 'archived'],
107
+ ready: ['running', 'blocked', 'archived'],
108
+ running: ['review', 'done', 'blocked', 'ready'],
109
+ blocked: ['todo', 'ready', 'archived'],
110
+ review: ['done', 'blocked', 'todo'],
111
+ };
112
+ const ok = (allowed[from] || []).includes(to);
113
+ return ok;
114
+ }
115
+ /** checking parents status from KV */
116
+ async allParentsDone(task) {
117
+ if (!task.parentIds || task.parentIds.length === 0)
118
+ return true;
119
+ for (const pid of task.parentIds) {
120
+ const p = await this.getTask(pid);
121
+ if (p && p.status !== 'done' && p.status !== 'archived')
122
+ return false;
123
+ }
124
+ return true;
125
+ }
126
+ /** 把 todo/scheduled/blocked(父依赖未完成) 任务晋升为 ready (父依赖满足时) — recompute Ready */
127
+ async promoteReady(taskId) {
128
+ const t = await this.getTask(taskId);
129
+ if (!t)
130
+ return null;
131
+ // 自动可清 blocked = 因父依赖未完成; sticky blocked (用户主动) 不自动解
132
+ const autoClearable = t.status === 'blocked' && t.reason === 'parents_not_done';
133
+ if (!autoClearable && t.status !== 'todo' && t.status !== 'scheduled')
134
+ return null;
135
+ if (!(await this.allParentsDone(t))) {
136
+ if (t.status === 'blocked')
137
+ return this.st(t);
138
+ return { ...this.st(t), status: 'blocked', reason: 'parents_not_done' };
139
+ }
140
+ return this.persist({ ...t, status: 'ready', reason: '' });
141
+ }
142
+ /** 认领 (CAS): 只有 ready 且未被认领的才能成功 → running + claimLock */
143
+ async claimTask(taskId, worker, ttlMs = 15 * 60 * 1000) {
144
+ const t = await this.getTask(taskId);
145
+ if (!t)
146
+ return { ok: false, reason: `任务不存在: ${taskId}` };
147
+ if (t.claimLock)
148
+ return { ok: false, reason: '已被其他 worker 认领' };
149
+ if (t.status !== 'ready') {
150
+ // 父依赖不变式: 认领点再强校验一次 (任何写路径把任务置 ready 都可能被这里纠正)
151
+ if (!(await this.allParentsDone(t))) {
152
+ await this.persist({ ...t, status: 'blocked', reason: 'parents_not_done' });
153
+ return { ok: false, reason: '父任务未完成 (降级 blocked)' };
154
+ }
155
+ return { ok: false, reason: `状态为 ${t.status}, 只有 ready 可认领` };
156
+ }
157
+ const run = fresh(t.id, t.boardId, t.title, { ...t, status: 'running', claimLock: worker, claimExpires: now() + ttlMs, consecutiveFailures: 0 });
158
+ await this.kv.put(taskKey(taskId), JSON.parse(JSON.stringify(run)));
159
+ return { ok: true, task: this.st(run) };
160
+ }
161
+ /** 认领 TTL 过期释放 — 防崩溃锁泄漏 (重启后旧 claimLock 残留) */
162
+ async releaseStaleClaims(nowTs = now()) {
163
+ const tasks = await this.listTasks('running');
164
+ let freed = 0;
165
+ for (const t of tasks) {
166
+ if (t.claimLock && t.claimExpires < nowTs) {
167
+ await this.persist({ ...t, status: 'ready', claimLock: '', claimExpires: 0 });
168
+ freed++;
169
+ }
170
+ }
171
+ return freed;
172
+ }
173
+ /** 记一次失败 (熔断): 连续失败 > limit → blocked */
174
+ async registerFailure(taskId, failureLimit = 3) {
175
+ const t = await this.getTask(taskId);
176
+ if (!t)
177
+ return null;
178
+ let next = t.claimLock ? { ...t, status: 'ready', claimLock: '', claimExpires: 0 } : { ...t };
179
+ next.consecutiveFailures = (next.consecutiveFailures || 0) + 1;
180
+ if (next.consecutiveFailures >= failureLimit) {
181
+ next.status = 'blocked';
182
+ next.reason = `circuit_break (连续失败 ${next.consecutiveFailures})`;
183
+ }
184
+ return this.persist(next);
185
+ }
186
+ /** 完成: 校验声称创建的引用 (防幻觉 — 幽灵引用 advisory), 父任务完成则解子依赖 */
187
+ async completeTask(taskId, worker, createdRefs = []) {
188
+ const t = await this.getTask(taskId);
189
+ if (!t)
190
+ return null;
191
+ if (t.claimLock && t.claimLock !== worker)
192
+ return null;
193
+ // 防幻觉 (advisory): 声称创建的引用逐一验证存在 → 不存在记为 advisory (不阻塞, 但可审计)
194
+ const missing = [];
195
+ for (const ref of createdRefs) {
196
+ const existing = await this.getTask(ref.replace(/^task:/, ''));
197
+ if (!existing)
198
+ missing.push(ref);
199
+ }
200
+ const task = fresh(t.id, t.boardId, t.title, {
201
+ ...t, status: 'done', claimLock: '', claimExpires: 0,
202
+ createdRefs, reason: missing.length ? `advisory_missing_refs: ${missing.join(',')}` : t.reason,
203
+ completedAt: now(),
204
+ });
205
+ await this.kv.put(taskKey(taskId), JSON.parse(JSON.stringify(task)));
206
+ // 子任务受父完成影响 → 尝试晋升 ready
207
+ const all = await this.listTasks();
208
+ for (const c of all) {
209
+ if (c.parentIds.includes(taskId) && (c.status === 'todo' || c.status === 'blocked')) {
210
+ await this.promoteReady(c.id);
211
+ }
212
+ }
213
+ return this.st(task);
214
+ }
215
+ async requestReview(taskId, note) {
216
+ const t = await this.getTask(taskId);
217
+ if (!t || t.status !== 'running')
218
+ return null;
219
+ return this.persist({ ...t, status: 'review', claimLock: '', claimExpires: 0, review: note });
220
+ }
221
+ async approveReview(taskId) {
222
+ const t = await this.getTask(taskId);
223
+ if (!t || t.status !== 'review')
224
+ return null;
225
+ return this.persist({ ...t, status: 'done', reason: 'review_approved', completedAt: now() });
226
+ }
227
+ async rejectReview(taskId, reason) {
228
+ const t = await this.getTask(taskId);
229
+ if (!t || t.status !== 'review')
230
+ return null;
231
+ return this.persist({ ...t, status: 'blocked', review: '', reason: `review_rejected: ${reason}` });
232
+ }
233
+ async setStatus(taskId, to, reason = '') {
234
+ const t = await this.getTask(taskId);
235
+ if (!t || !this.trans(t.status, to))
236
+ return null;
237
+ if (to === 'blocked' && !reason && !t.reason)
238
+ return null; // blocked 需带 reason
239
+ const next = { ...t, status: to, reason: to === 'blocked' ? (reason || t.reason) : reason || t.reason };
240
+ if (to === 'archived')
241
+ next.archivedAt = now();
242
+ if (to === 'ready') {
243
+ if (!(await this.allParentsDone(next)))
244
+ return this.persist({ ...next, status: 'blocked', reason: 'parents_not_done' });
245
+ }
246
+ return this.persist(next);
247
+ }
248
+ }
249
+ /** 打开/缓存指定 board 的 OrbitDB KV store, 返回绑定的 KanbanBoard. 失败抛错. */
250
+ const _boardCache = new Map();
251
+ export async function openKanbanBoard(boardId) {
252
+ if (_boardCache.has(boardId))
253
+ return _boardCache.get(boardId);
254
+ const { getCIDDatabase } = await import('./cid-database.js');
255
+ const store = await (await getCIDDatabase()).openStore(`bolloon-kanban-${boardId}`, 'keyvalue');
256
+ const board = new KanbanBoard(store);
257
+ _boardCache.set(boardId, board);
258
+ return board;
259
+ }