@bolloon/bolloon-agent 0.3.51 → 0.4.0
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/README.md +135 -135
- package/bin/bolloon-cli.cjs +183 -183
- package/bin/bolloon.cjs +0 -0
- package/bin/bolloon.js +157 -0
- package/dist/agents/constraint-layer.js +19 -19
- package/dist/agents/judgment-protocol.js +14 -14
- package/dist/agents/loop-review.js +21 -0
- package/dist/agents/pi-sdk-tools.js +86 -2
- package/dist/agents/pi-sdk-types.js +12 -12
- package/dist/agents/pi-sdk.js +90 -90
- package/dist/agents/shell-guard.js +22 -1
- package/dist/agents/skill-organizer.js +19 -19
- package/dist/agents/verify-recipe.js +84 -0
- package/dist/agents/workflow-pivot-loop.js +101 -13
- package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
- package/dist/bollharness-integration/context-router-judgment.js +4 -4
- package/dist/bollharness-integration/context-router.js +292 -292
- package/dist/bollharness-integration/gate-state-machine.js +13 -13
- package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
- package/dist/bollharness-integration/skill-adapter.js +21 -21
- package/dist/bootstrap/chat-archiver.js +14 -14
- package/dist/bootstrap/context-os.js +50 -40
- package/dist/bootstrap/memory-compressor.js +13 -13
- package/dist/cli-entry.js +36 -36
- package/dist/context-compaction/auto-compact.js +7 -7
- package/dist/electron/first-run.js +54 -54
- package/dist/electron-build/electron/first-run.js +54 -54
- package/dist/electron-preload.js.map +1 -1
- package/dist/electron.js.map +1 -1
- package/dist/external-engines/delegate-handle.js +70 -0
- package/dist/external-engines/delegate.js +24 -3
- package/dist/index.js +112 -112
- package/dist/llm/error-lessons.js +53 -0
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +61 -48
- package/dist/llm/system-prompt/layers/channel/human-async.md +41 -41
- package/dist/llm/system-prompt/layers/channel/local.md +14 -14
- package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
- package/dist/llm/system-prompt/layers/channel/p2p-peer-sync.md +51 -51
- package/dist/llm/system-prompt/layers/channel/p2p-proactive.md +43 -43
- package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
- package/dist/llm/system-prompt/layers/channel/session-handoff.md +61 -61
- package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
- package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
- package/dist/llm/system-prompt/layers/core/external-engagement.md +73 -73
- package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
- package/dist/llm/system-prompt/layers/core/identity.md +48 -48
- package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
- package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
- package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
- package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
- package/dist/llm/system-prompt/layers/core/tone.md +31 -31
- package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
- package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
- package/dist/llm/system-prompt/layers/role/architect.md +20 -20
- package/dist/llm/system-prompt/layers/role/expert.md +19 -19
- package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
- package/dist/llm/system-prompt/layers/role/security.md +15 -15
- package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
- package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
- package/dist/llm/system-prompt/layers/tool/goal_handoff.md +77 -77
- package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
- package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
- package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
- package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
- package/dist/llm/system-prompt/layers/tool/p2p_request.md +61 -61
- package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
- package/dist/llm/system-prompt/registry.js +9 -1
- package/dist/llm/tool-manifest/ask_user_input.js +1 -1
- package/dist/llm/tool-manifest/bash.js +3 -3
- package/dist/llm/tool-manifest/create_file.js +4 -4
- package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
- package/dist/llm/tool-manifest/image_search.js +2 -2
- package/dist/llm/tool-manifest/index.js +0 -59
- package/dist/llm/tool-manifest/mcp.js +2 -2
- package/dist/llm/tool-manifest/message_compose.js +3 -3
- package/dist/llm/tool-manifest/places.js +2 -2
- package/dist/llm/tool-manifest/present_files.js +1 -1
- package/dist/llm/tool-manifest/recipe.js +2 -2
- package/dist/llm/tool-manifest/str_replace.js +5 -5
- package/dist/llm/tool-manifest/view.js +3 -3
- package/dist/llm/tool-manifest/weather.js +4 -4
- package/dist/llm/tool-manifest/web.js +4 -4
- package/dist/locales/en.js +28 -0
- package/dist/locales/zh.js +26 -0
- package/dist/pi-ecosystem-colony/index.js +365 -0
- package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
- package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
- package/dist/pi-ecosystem-judgment/distillation.js +14 -14
- package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
- package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
- package/dist/security/context-router-tool.js +15 -15
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +101 -0
- package/dist/social/ant-colony/PheromoneEngine.js +227 -0
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +24 -0
- package/dist/storage/trajectory.js +101 -0
- package/dist/test/ai-judgment-test.js +80 -0
- package/dist/test/bollharness-integration.test.js +318 -0
- package/dist/test/channel-agent-multi-dialogue.js +205 -0
- package/dist/test/channel-heartbeat-agent-test.js +201 -0
- package/dist/test/constraint-layer.test.js +164 -0
- package/dist/test/diap-identity-test.js +172 -0
- package/dist/test/diap-quick-test.js +62 -0
- package/dist/test/global-shared-context.test.js +315 -0
- package/dist/test/harness-judgment-injection.test.js +246 -0
- package/dist/test/harness-workflow-integrator-test.js +228 -0
- package/dist/test/human-value-store.test.js +243 -0
- package/dist/test/hybrid-integration-test.js +118 -0
- package/dist/test/hybrid-messenger-verify.js +55 -0
- package/dist/test/iroh-bistream-debug.js +38 -0
- package/dist/test/iroh-communication.test.js +66 -0
- package/dist/test/iroh-debug-test.js +57 -0
- package/dist/test/iroh-diap-test.js +71 -0
- package/dist/test/iroh-direct-connect.js +55 -0
- package/dist/test/iroh-e2e-fixed.js +89 -0
- package/dist/test/iroh-e2e-same-process.js +63 -0
- package/dist/test/iroh-e2e.js +66 -0
- package/dist/test/iroh-final-e2e.js +72 -0
- package/dist/test/iroh-relay-test.js +37 -0
- package/dist/test/iroh-simple-test.js +41 -0
- package/dist/test/iroh-transport-verify.js +54 -0
- package/dist/test/iroh-transport.test.js +37 -0
- package/dist/test/iroh-two-nodes.js +70 -0
- package/dist/test/iroh-verify.js +44 -0
- package/dist/test/judgment-decision.test.js +219 -0
- package/dist/test/llm-judgment-integration.test.js +220 -0
- package/dist/test/p2p-agent-complex-dialogue.js +385 -0
- package/dist/test/p2p-agent-dialogue.js +341 -0
- package/dist/test/p2p-agent-full-bidirectional.js +510 -0
- package/dist/test/p2p-agent-harness-flow.js +437 -0
- package/dist/test/p2p-agent-harness-single.js +143 -0
- package/dist/test/p2p-ai-dialogue-test.js +318 -0
- package/dist/test/p2p-cid-connect-test.js +195 -0
- package/dist/test/p2p-connect-receiver.js +69 -0
- package/dist/test/p2p-doc-transfer.js +110 -0
- package/dist/test/p2p-identity-page-test.js +77 -0
- package/dist/test/p2p-iroh-test.js +171 -0
- package/dist/test/p2p-minimal-test.js +241 -0
- package/dist/test/p2p-node-1.js +148 -0
- package/dist/test/p2p-node-2.js +148 -0
- package/dist/test/p2p-server.js +281 -0
- package/dist/test/p2p-two-nodes-test.js +438 -0
- package/dist/test/pi-sdk.test.js +44 -0
- package/dist/test/set-persona.js +40 -0
- package/dist/test/simple.test.js +9 -0
- package/dist/test/storage-integration.test.js +150 -0
- package/dist/test/subagent-manager.test.js +276 -0
- package/dist/test/test-gate-flow.test.js +81 -0
- package/dist/test/workflow-engine.test.js +87 -0
- package/dist/test/workflow-pivot-loop.test.js +246 -0
- package/dist/web/api-config.html +779 -779
- package/dist/web/client-hearth.js +10 -10
- package/dist/web/client.js +54 -0
- package/dist/web/components/wallet-viem.mjs +118 -118
- package/dist/web/delivery-ledger.js +103 -0
- package/dist/web/i18n.js +104 -0
- package/dist/web/index.html +472 -472
- package/dist/web/manifest.json +20 -20
- package/dist/web/routes-tasks.js +242 -24
- package/dist/web/server-storage.js +111 -0
- package/dist/web/server.js +55 -26
- package/dist/web/style.css +4946 -4946
- package/dist/web/task-breaker.js +18 -0
- package/dist/web/task-cancel.js +35 -0
- package/dist/web/task-deps.js +36 -0
- package/dist/web/task-review.js +27 -0
- package/dist/web/task-verify.js +60 -0
- package/package.json +196 -193
- package/scripts/build-cli.js +215 -215
- package/scripts/build-web.ts +130 -130
- package/scripts/postinstall.js +152 -152
- package/bin/bolloon-daemon.sh +0 -207
- package/dist/constraint-runtime/reference_data/archive_surface_snapshot.json +0 -63
- package/dist/constraint-runtime/reference_data/commands_snapshot.json +0 -1037
- package/dist/constraint-runtime/reference_data/subsystems/assistant.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/bootstrap.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/bridge.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/buddy.json +0 -13
- package/dist/constraint-runtime/reference_data/subsystems/cli.json +0 -26
- package/dist/constraint-runtime/reference_data/subsystems/components.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/constants.json +0 -28
- package/dist/constraint-runtime/reference_data/subsystems/coordinator.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/entrypoints.json +0 -15
- package/dist/constraint-runtime/reference_data/subsystems/hooks.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/keybindings.json +0 -21
- package/dist/constraint-runtime/reference_data/subsystems/memdir.json +0 -15
- package/dist/constraint-runtime/reference_data/subsystems/migrations.json +0 -18
- package/dist/constraint-runtime/reference_data/subsystems/moreright.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/native_ts.json +0 -11
- package/dist/constraint-runtime/reference_data/subsystems/outputStyles.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/plugins.json +0 -9
- package/dist/constraint-runtime/reference_data/subsystems/remote.json +0 -11
- package/dist/constraint-runtime/reference_data/subsystems/schemas.json +0 -8
- package/dist/constraint-runtime/reference_data/subsystems/screens.json +0 -10
- package/dist/constraint-runtime/reference_data/subsystems/server.json +0 -10
- package/dist/constraint-runtime/reference_data/subsystems/services.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/skills.json +0 -27
- package/dist/constraint-runtime/reference_data/subsystems/state.json +0 -13
- package/dist/constraint-runtime/reference_data/subsystems/types.json +0 -18
- package/dist/constraint-runtime/reference_data/subsystems/upstreamproxy.json +0 -9
- package/dist/constraint-runtime/reference_data/subsystems/utils.json +0 -32
- package/dist/constraint-runtime/reference_data/subsystems/vim.json +0 -12
- package/dist/constraint-runtime/reference_data/subsystems/voice.json +0 -8
- package/dist/constraint-runtime/reference_data/tools_snapshot.json +0 -922
- package/dist/electron/config.js.map +0 -1
- package/dist/electron/dialogs.js.map +0 -1
- package/dist/electron/first-run.js.map +0 -1
- package/dist/electron/ipc.js.map +0 -1
- package/dist/electron/logger.js.map +0 -1
- package/dist/electron/main.js.map +0 -1
- package/dist/electron/menu.js.map +0 -1
- package/dist/electron/paths.js.map +0 -1
- package/dist/electron/server.js.map +0 -1
- package/dist/electron/tray.js.map +0 -1
- package/dist/electron/window.js.map +0 -1
- package/dist/utils/auto-update.js.map +0 -1
- package/dist/web/client.js.map +0 -7
|
@@ -18,9 +18,9 @@ export const fetch_sports_data = {
|
|
|
18
18
|
{ name: 'league', type: 'enum', required: true, description: 'nfl/nba/nhl/mlb/wnba/ncaafb/ncaamb/ncaawb/epl/la_liga/serie_a/bundesliga/ligue_1/mls/champions_league/tennis/golf/nascar/cricket/mma', enumValues: ['nfl', 'nba', 'nhl', 'mlb', 'wnba', 'ncaafb', 'ncaamb', 'ncaawb', 'epl', 'la_liga', 'serie_a', 'bundesliga', 'ligue_1', 'mls', 'champions_league', 'tennis', 'golf', 'nascar', 'cricket', 'mma'] },
|
|
19
19
|
{ name: 'team', type: 'string', required: false, description: '可选的球队名称' },
|
|
20
20
|
],
|
|
21
|
-
callExample: `[TOOL:fetch_sports_data]
|
|
22
|
-
[P:data_type]scores
|
|
23
|
-
[P:league]nba
|
|
21
|
+
callExample: `[TOOL:fetch_sports_data]
|
|
22
|
+
[P:data_type]scores
|
|
23
|
+
[P:league]nba
|
|
24
24
|
[ENDTOOL]`,
|
|
25
25
|
layerId: 'tool.manifest',
|
|
26
26
|
};
|
|
@@ -17,8 +17,8 @@ export const image_search = {
|
|
|
17
17
|
{ name: 'max_results', type: 'integer', required: false, description: '返回图片数 (默认 3, 最少 3, 最多 5)', minimum: 3, maximum: 5, default: 3 },
|
|
18
18
|
{ name: 'query', type: 'string', required: true, description: '搜索查询 (3-6 词具体)' },
|
|
19
19
|
],
|
|
20
|
-
callExample: `[TOOL:image_search]
|
|
21
|
-
[P:query]Eiffel Tower Paris
|
|
20
|
+
callExample: `[TOOL:image_search]
|
|
21
|
+
[P:query]Eiffel Tower Paris
|
|
22
22
|
[ENDTOOL]`,
|
|
23
23
|
layerId: 'tool.image_search',
|
|
24
24
|
};
|
|
@@ -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
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* locales/en.ts — English catalog (source of truth)
|
|
3
|
+
*
|
|
4
|
+
* 范围纪律 (Hermes i18n): 只放用户可见静态消息 (审批/护栏/任务系统广播);
|
|
5
|
+
* agent 输出/日志/报错不翻译。
|
|
6
|
+
*/
|
|
7
|
+
export const catalog = {
|
|
8
|
+
guard: {
|
|
9
|
+
deny_reason: 'Command hit high-risk guard {pattern} (core untouched: privilege escalation / format / delete root / .bolloon data). Try a safer command.',
|
|
10
|
+
self_lifecycle_reason: 'Command would restart or kill the bolloon host service (pattern {pattern}) — this creates a supervisor revival loop. Not allowed.',
|
|
11
|
+
empty: 'Empty command',
|
|
12
|
+
},
|
|
13
|
+
tasks: {
|
|
14
|
+
cancelled: 'Task cancelled: {title}',
|
|
15
|
+
review_pending: 'Task finished execution, awaiting review: {title}',
|
|
16
|
+
approved: 'Task approved: {title}',
|
|
17
|
+
rejected: 'Task rejected, back to queue: {title}',
|
|
18
|
+
failed: 'Task failed: {message}',
|
|
19
|
+
failed_retry: 'Task failed: {message} (auto-retry)',
|
|
20
|
+
parents_undone: 'Task parents not done ({parents}) — cannot execute until parents finish',
|
|
21
|
+
breaker_tripped: 'Failed {count} times consecutively, circuit broken (no auto-retry): {message}',
|
|
22
|
+
},
|
|
23
|
+
claim: {
|
|
24
|
+
busy: 'Another task is currently executing',
|
|
25
|
+
not_claimable: 'Task not claimable (status={status})',
|
|
26
|
+
released: '[task-claim] reclaimed {count} stale claim(s)',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* locales/zh.ts — 中文目录 (默认语言)
|
|
3
|
+
* 键集合与占位符必须与 en.ts 一致 — parity 测试断言 (Hermes test_i18n.py 模式).
|
|
4
|
+
*/
|
|
5
|
+
export const catalog = {
|
|
6
|
+
guard: {
|
|
7
|
+
deny_reason: '命令命中高危护栏 {pattern} (核心不碰: 提权/格式化/删根/.bolloon 数据). 换一条安全命令.',
|
|
8
|
+
self_lifecycle_reason: '命令会重启或杀死 bolloon 宿主服务 (模式 {pattern}) — 会形成 supervisor 复活循环, 不允许.',
|
|
9
|
+
empty: '命令为空',
|
|
10
|
+
},
|
|
11
|
+
tasks: {
|
|
12
|
+
cancelled: '任务已取消: {title}',
|
|
13
|
+
review_pending: '任务执行完成, 待审批: {title}',
|
|
14
|
+
approved: '任务审批通过: {title}',
|
|
15
|
+
rejected: '任务审批驳回, 退回队列: {title}',
|
|
16
|
+
failed: '任务执行失败: {message}',
|
|
17
|
+
failed_retry: '任务执行失败: {message} (自动重试)',
|
|
18
|
+
parents_undone: '任务父依赖未完成 ({parents}) — 父任务完成后才能执行',
|
|
19
|
+
breaker_tripped: '连续失败 {count} 次, 熔断 (不再自动重试): {message}',
|
|
20
|
+
},
|
|
21
|
+
claim: {
|
|
22
|
+
busy: '另一个任务正在执行',
|
|
23
|
+
not_claimable: '任务不可认领 (status={status})',
|
|
24
|
+
released: '[task-claim] 已回收 {count} 个过期认领',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Ant Colony Integration for Bolloon
|
|
3
|
+
*
|
|
4
|
+
* Multi-agent collaboration system with white-box signal protocol.
|
|
5
|
+
* Based on oh-pi's ant colony system: COLONY_SIGNAL protocol for agent coordination.
|
|
6
|
+
*
|
|
7
|
+
* Signal Protocol:
|
|
8
|
+
* COLONY_SIGNAL:LAUNCHED → SCOUTING → WORKING → REVIEWING → COMPLETE
|
|
9
|
+
*
|
|
10
|
+
* Key features:
|
|
11
|
+
* - Fully visible signal protocol (no black boxes)
|
|
12
|
+
* - Per-ant turn limits, cost tracking, concurrent scheduling
|
|
13
|
+
* - White-box design: no "what is it doing in the background" anxiety
|
|
14
|
+
*/
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
import * as fs from 'fs/promises';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
// Colony state
|
|
19
|
+
const ants = new Map();
|
|
20
|
+
const tasks = new Map();
|
|
21
|
+
const signalHistory = [];
|
|
22
|
+
const STATE_DIR = path.join(process.env.HOME || '/tmp', '.bolloon', 'colony');
|
|
23
|
+
// Event emitter
|
|
24
|
+
class ColonyEventEmitter extends EventEmitter {
|
|
25
|
+
}
|
|
26
|
+
const colonyEvents = new ColonyEventEmitter();
|
|
27
|
+
/**
|
|
28
|
+
* Generate a unique ant ID
|
|
29
|
+
*/
|
|
30
|
+
function generateAntId() {
|
|
31
|
+
return `ant-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register a new ant in the colony
|
|
35
|
+
*/
|
|
36
|
+
export function registerAnt(name, role, maxTurns = 10, tokenBudget = 50000) {
|
|
37
|
+
const id = generateAntId();
|
|
38
|
+
const ant = {
|
|
39
|
+
id,
|
|
40
|
+
name,
|
|
41
|
+
role,
|
|
42
|
+
signal: 'LAUNCHED',
|
|
43
|
+
turnCount: 0,
|
|
44
|
+
maxTurns,
|
|
45
|
+
tokenBudget,
|
|
46
|
+
tokensUsed: 0,
|
|
47
|
+
createdAt: new Date().toISOString(),
|
|
48
|
+
updatedAt: new Date().toISOString(),
|
|
49
|
+
};
|
|
50
|
+
ants.set(id, ant);
|
|
51
|
+
emitSignal(id, 'LAUNCHED');
|
|
52
|
+
console.log(`[Colony] Registered ant: ${name} (${role})`);
|
|
53
|
+
return ant;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Emit a signal transition
|
|
57
|
+
*/
|
|
58
|
+
function emitSignal(antId, newSignal, details) {
|
|
59
|
+
const ant = ants.get(antId);
|
|
60
|
+
if (!ant)
|
|
61
|
+
return;
|
|
62
|
+
const event = {
|
|
63
|
+
antId,
|
|
64
|
+
antName: ant.name,
|
|
65
|
+
fromSignal: ant.signal,
|
|
66
|
+
toSignal: newSignal,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
details,
|
|
69
|
+
};
|
|
70
|
+
signalHistory.push(event);
|
|
71
|
+
if (signalHistory.length > 100) {
|
|
72
|
+
signalHistory.shift();
|
|
73
|
+
}
|
|
74
|
+
ant.signal = newSignal;
|
|
75
|
+
ant.updatedAt = new Date().toISOString();
|
|
76
|
+
ants.set(antId, ant);
|
|
77
|
+
colonyEvents.emit('signal', event);
|
|
78
|
+
console.log(`[Colony] ${ant.name}: ${event.fromSignal} → ${event.toSignal}${details ? ` (${details})` : ''}`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Transition ant to SCOUTING
|
|
82
|
+
*/
|
|
83
|
+
export function antScouting(antId, task) {
|
|
84
|
+
const ant = ants.get(antId);
|
|
85
|
+
if (!ant)
|
|
86
|
+
return;
|
|
87
|
+
ant.task = task;
|
|
88
|
+
ants.set(antId, ant);
|
|
89
|
+
emitSignal(antId, 'SCOUTING', `Task: ${task.substring(0, 50)}...`);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Transition ant to WORKING
|
|
93
|
+
*/
|
|
94
|
+
export function antWorking(antId) {
|
|
95
|
+
const ant = ants.get(antId);
|
|
96
|
+
if (!ant)
|
|
97
|
+
return;
|
|
98
|
+
emitSignal(antId, 'WORKING');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Transition ant to REVIEWING
|
|
102
|
+
*/
|
|
103
|
+
export function antReviewing(antId) {
|
|
104
|
+
emitSignal(antId, 'REVIEWING');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Complete ant work
|
|
108
|
+
*/
|
|
109
|
+
export function antComplete(antId, result) {
|
|
110
|
+
const ant = ants.get(antId);
|
|
111
|
+
if (!ant)
|
|
112
|
+
return;
|
|
113
|
+
ant.result = result;
|
|
114
|
+
ants.set(antId, ant);
|
|
115
|
+
emitSignal(antId, 'COMPLETE', `Result: ${result.substring(0, 50)}...`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Fail ant work
|
|
119
|
+
*/
|
|
120
|
+
export function antFail(antId, error) {
|
|
121
|
+
const ant = ants.get(antId);
|
|
122
|
+
if (!ant)
|
|
123
|
+
return;
|
|
124
|
+
ant.result = error;
|
|
125
|
+
ants.set(antId, ant);
|
|
126
|
+
emitSignal(antId, 'FAILED', error);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Abort ant work
|
|
130
|
+
*/
|
|
131
|
+
export function antAbort(antId, reason) {
|
|
132
|
+
emitSignal(antId, 'ABORTED', reason);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Increment turn count
|
|
136
|
+
*/
|
|
137
|
+
export function antTick(antId, tokensUsed = 0) {
|
|
138
|
+
const ant = ants.get(antId);
|
|
139
|
+
if (!ant)
|
|
140
|
+
return;
|
|
141
|
+
ant.turnCount++;
|
|
142
|
+
ant.tokensUsed += tokensUsed;
|
|
143
|
+
ant.updatedAt = new Date().toISOString();
|
|
144
|
+
ants.set(antId, ant);
|
|
145
|
+
// Check if exceeded limits
|
|
146
|
+
if (ant.turnCount >= ant.maxTurns) {
|
|
147
|
+
antAbort(antId, 'Max turns exceeded');
|
|
148
|
+
}
|
|
149
|
+
else if (ant.tokensUsed >= ant.tokenBudget) {
|
|
150
|
+
antAbort(antId, 'Token budget exceeded');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create a colony task
|
|
155
|
+
*/
|
|
156
|
+
export function createTask(description) {
|
|
157
|
+
const id = `task-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
158
|
+
const task = {
|
|
159
|
+
id,
|
|
160
|
+
description,
|
|
161
|
+
status: 'pending',
|
|
162
|
+
assignedAnts: [],
|
|
163
|
+
results: new Map(),
|
|
164
|
+
createdAt: new Date().toISOString(),
|
|
165
|
+
};
|
|
166
|
+
tasks.set(id, task);
|
|
167
|
+
console.log(`[Colony] Created task: ${id} - ${description.substring(0, 50)}...`);
|
|
168
|
+
return task;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Dispatch task to ants
|
|
172
|
+
*/
|
|
173
|
+
export function dispatchTask(taskId, antIds) {
|
|
174
|
+
const task = tasks.get(taskId);
|
|
175
|
+
if (!task)
|
|
176
|
+
return;
|
|
177
|
+
task.status = 'dispatched';
|
|
178
|
+
task.assignedAnts = antIds;
|
|
179
|
+
tasks.set(taskId, task);
|
|
180
|
+
for (const antId of antIds) {
|
|
181
|
+
const ant = ants.get(antId);
|
|
182
|
+
if (ant) {
|
|
183
|
+
antScouting(antId, task.description);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(`[Colony] Dispatched task ${taskId} to ${antIds.length} ants`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Record ant result for a task
|
|
190
|
+
*/
|
|
191
|
+
export function recordResult(taskId, antId, result) {
|
|
192
|
+
const task = tasks.get(taskId);
|
|
193
|
+
if (!task)
|
|
194
|
+
return;
|
|
195
|
+
task.results.set(antId, result);
|
|
196
|
+
// Check if all ants completed
|
|
197
|
+
if (task.results.size === task.assignedAnts.length) {
|
|
198
|
+
task.status = 'completed';
|
|
199
|
+
task.completedAt = new Date().toISOString();
|
|
200
|
+
for (const id of task.assignedAnts) {
|
|
201
|
+
antReviewing(id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
tasks.set(taskId, task);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get ant by ID
|
|
208
|
+
*/
|
|
209
|
+
export function getAnt(antId) {
|
|
210
|
+
return ants.get(antId);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* List all ants
|
|
214
|
+
*/
|
|
215
|
+
export function listAnts() {
|
|
216
|
+
return Array.from(ants.values());
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* List ants by role
|
|
220
|
+
*/
|
|
221
|
+
export function listAntsByRole(role) {
|
|
222
|
+
return Array.from(ants.values()).filter((a) => a.role === role);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get ants by signal state
|
|
226
|
+
*/
|
|
227
|
+
export function listAntsBySignal(signal) {
|
|
228
|
+
return Array.from(ants.values()).filter((a) => a.signal === signal);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get active ants (not complete/failed/aborted)
|
|
232
|
+
*/
|
|
233
|
+
export function getActiveAnts() {
|
|
234
|
+
return Array.from(ants.values()).filter((a) => !['COMPLETE', 'FAILED', 'ABORTED'].includes(a.signal));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get task by ID
|
|
238
|
+
*/
|
|
239
|
+
export function getTask(taskId) {
|
|
240
|
+
return tasks.get(taskId);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* List all tasks
|
|
244
|
+
*/
|
|
245
|
+
export function listTasks() {
|
|
246
|
+
return Array.from(tasks.values());
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get signal history
|
|
250
|
+
*/
|
|
251
|
+
export function getSignalHistory() {
|
|
252
|
+
return [...signalHistory];
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get colony status
|
|
256
|
+
*/
|
|
257
|
+
export function getColonyStatus() {
|
|
258
|
+
const antList = Array.from(ants.values());
|
|
259
|
+
const taskList = Array.from(tasks.values());
|
|
260
|
+
const signalDistribution = {
|
|
261
|
+
LAUNCHED: 0,
|
|
262
|
+
SCOUTING: 0,
|
|
263
|
+
WORKING: 0,
|
|
264
|
+
REVIEWING: 0,
|
|
265
|
+
COMPLETE: 0,
|
|
266
|
+
FAILED: 0,
|
|
267
|
+
ABORTED: 0,
|
|
268
|
+
};
|
|
269
|
+
for (const ant of antList) {
|
|
270
|
+
signalDistribution[ant.signal]++;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
antCount: antList.length,
|
|
274
|
+
activeAnts: antList.filter((a) => !['COMPLETE', 'FAILED', 'ABORTED'].includes(a.signal)).length,
|
|
275
|
+
taskCount: taskList.length,
|
|
276
|
+
activeTasks: taskList.filter((t) => t.status === 'in_progress' || t.status === 'dispatched').length,
|
|
277
|
+
signalDistribution,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Subscribe to colony events
|
|
282
|
+
*/
|
|
283
|
+
export function onColonyEvent(event, callback) {
|
|
284
|
+
colonyEvents.on(event, callback);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Unsubscribe from colony events
|
|
288
|
+
*/
|
|
289
|
+
export function offColonyEvent(event, callback) {
|
|
290
|
+
colonyEvents.off(event, callback);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get formatted colony dump for debugging
|
|
294
|
+
*/
|
|
295
|
+
export function getColonyDump() {
|
|
296
|
+
const status = getColonyStatus();
|
|
297
|
+
const active = getActiveAnts();
|
|
298
|
+
let dump = '# Colony Status\n\n';
|
|
299
|
+
dump += `Ants: ${status.antCount} total, ${status.activeAnts} active\n`;
|
|
300
|
+
dump += `Tasks: ${status.taskCount} total, ${status.activeTasks} active\n\n`;
|
|
301
|
+
dump += '## Signal Distribution\n';
|
|
302
|
+
for (const [signal, count] of Object.entries(status.signalDistribution)) {
|
|
303
|
+
if (count > 0) {
|
|
304
|
+
dump += `- ${signal}: ${count}\n`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
dump += '\n## Active Ants\n';
|
|
308
|
+
for (const ant of active) {
|
|
309
|
+
dump += `- ${ant.name} (${ant.role}): ${ant.signal} [${ant.turnCount}/${ant.maxTurns} turns, ${ant.tokensUsed}/${ant.tokenBudget} tokens]\n`;
|
|
310
|
+
if (ant.task) {
|
|
311
|
+
dump += ` Task: ${ant.task.substring(0, 60)}...\n`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
dump += '\n## Recent Signals\n';
|
|
315
|
+
const recent = signalHistory.slice(-10);
|
|
316
|
+
for (const event of recent.reverse()) {
|
|
317
|
+
dump += `- ${event.timestamp}: ${event.antName}: ${event.fromSignal} → ${event.toSignal}\n`;
|
|
318
|
+
}
|
|
319
|
+
return dump;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Persist colony state
|
|
323
|
+
*/
|
|
324
|
+
export async function persistColony() {
|
|
325
|
+
try {
|
|
326
|
+
await fs.mkdir(STATE_DIR, { recursive: true });
|
|
327
|
+
const state = {
|
|
328
|
+
ants: Array.from(ants.values()),
|
|
329
|
+
tasks: Array.from(tasks.entries()).map(([id, t]) => ({
|
|
330
|
+
...t,
|
|
331
|
+
results: Array.from(t.results.entries()),
|
|
332
|
+
})),
|
|
333
|
+
signalHistory: signalHistory.slice(-50),
|
|
334
|
+
persistedAt: new Date().toISOString(),
|
|
335
|
+
};
|
|
336
|
+
await fs.writeFile(path.join(STATE_DIR, 'colony.json'), JSON.stringify(state, null, 2));
|
|
337
|
+
console.log('[Colony] Persisted state');
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
console.warn('[Colony] Failed to persist:', e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Load colony state
|
|
345
|
+
*/
|
|
346
|
+
export async function loadColony() {
|
|
347
|
+
try {
|
|
348
|
+
const data = await fs.readFile(path.join(STATE_DIR, 'colony.json'), 'utf-8');
|
|
349
|
+
const state = JSON.parse(data);
|
|
350
|
+
ants.clear();
|
|
351
|
+
for (const a of state.ants) {
|
|
352
|
+
ants.set(a.id, a);
|
|
353
|
+
}
|
|
354
|
+
tasks.clear();
|
|
355
|
+
for (const t of state.tasks) {
|
|
356
|
+
tasks.set(t.id, { ...t, results: new Map(t.results) });
|
|
357
|
+
}
|
|
358
|
+
signalHistory.length = 0;
|
|
359
|
+
signalHistory.push(...state.signalHistory);
|
|
360
|
+
console.log(`[Colony] Loaded ${ants.size} ants, ${tasks.size} tasks`);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// No persisted state
|
|
364
|
+
}
|
|
365
|
+
}
|