@bolloon/bolloon-agent 0.2.9 → 0.2.10
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 +467 -467
- package/bin/bolloon-cli.cjs +183 -183
- package/bin/bolloon.cjs +0 -0
- package/bin/bolloon.js +157 -0
- package/dist/agents/agent-manifest-protocol.js +0 -52
- package/dist/agents/constraint-layer.js +19 -19
- package/dist/agents/pi-sdk.js +287 -1213
- package/dist/agents/shell-guard.js +2 -2
- package/dist/agents/workflow-pivot-loop.js +2 -81
- 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 -26
- package/dist/bollharness-integration/integration.js +0 -71
- package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
- package/dist/bollharness-integration/skill-adapter.js +127 -52
- package/dist/bootstrap/context-collector.js +3 -6
- package/dist/bootstrap/lifecycle-hooks.js +1 -15
- package/dist/cli-entry.js +35 -39
- package/dist/constraint-runtime/src/_archive_helper.js +9 -0
- package/dist/constraint-runtime/src/agent/coordinator.js +48 -0
- package/dist/constraint-runtime/src/agent/index.js +1 -0
- package/dist/constraint-runtime/src/assistant/index.js +12 -0
- package/dist/constraint-runtime/src/bootstrap/index.js +12 -0
- package/dist/constraint-runtime/src/bootstrap_graph.js +13 -0
- package/dist/constraint-runtime/src/bridge/index.js +12 -0
- package/dist/constraint-runtime/src/buddy/index.js +12 -0
- package/dist/constraint-runtime/src/cli/index.js +12 -0
- package/dist/constraint-runtime/src/command_graph.js +10 -0
- package/dist/constraint-runtime/src/commands.js +60 -0
- package/dist/constraint-runtime/src/components/index.js +12 -0
- package/dist/constraint-runtime/src/constants/index.js +12 -0
- package/dist/constraint-runtime/src/constraint/budget.js +22 -0
- package/dist/constraint-runtime/src/constraint/index.js +2 -0
- package/dist/constraint-runtime/src/constraint/permission.js +20 -0
- package/dist/constraint-runtime/src/context.js +30 -0
- package/dist/constraint-runtime/src/coordinator/index.js +12 -0
- package/dist/constraint-runtime/src/cost_hook.js +4 -0
- package/dist/constraint-runtime/src/cost_tracker.js +8 -0
- package/dist/constraint-runtime/src/deferred_init.js +10 -0
- package/dist/constraint-runtime/src/direct_modes.js +6 -0
- package/dist/constraint-runtime/src/dynamic-tool-loader.js +85 -0
- package/dist/constraint-runtime/src/entrypoints/index.js +12 -0
- package/dist/constraint-runtime/src/execution_registry.js +44 -0
- package/dist/constraint-runtime/src/history.js +9 -0
- package/dist/constraint-runtime/src/hooks/index.js +12 -0
- package/dist/constraint-runtime/src/index.js +26 -0
- package/dist/constraint-runtime/src/ink.js +4 -0
- package/dist/constraint-runtime/src/keybindings/index.js +12 -0
- package/dist/constraint-runtime/src/memdir/index.js +12 -0
- package/dist/constraint-runtime/src/migrations/index.js +12 -0
- package/dist/constraint-runtime/src/models.js +1 -0
- package/dist/constraint-runtime/src/moreright/index.js +12 -0
- package/dist/constraint-runtime/src/native_ts/index.js +12 -0
- package/dist/constraint-runtime/src/output_styles/index.js +12 -0
- package/dist/constraint-runtime/src/parity_audit.js +12 -0
- package/dist/constraint-runtime/src/plugins/index.js +12 -0
- package/dist/constraint-runtime/src/port_manifest.js +11 -0
- package/dist/constraint-runtime/src/prefetch.js +9 -0
- package/dist/constraint-runtime/src/query.js +1 -0
- package/dist/constraint-runtime/src/remote/index.js +12 -0
- package/dist/constraint-runtime/src/remote_runtime.js +9 -0
- package/dist/constraint-runtime/src/runtime/index.js +1 -0
- package/dist/constraint-runtime/src/runtime/session.js +35 -0
- package/dist/constraint-runtime/src/schemas/index.js +12 -0
- package/dist/constraint-runtime/src/screens/index.js +12 -0
- package/dist/constraint-runtime/src/server/index.js +12 -0
- package/dist/constraint-runtime/src/services/index.js +12 -0
- package/dist/constraint-runtime/src/session_store.js +22 -0
- package/dist/constraint-runtime/src/setup.js +30 -0
- package/dist/constraint-runtime/src/skills/index.js +1 -0
- package/dist/constraint-runtime/src/skills/skill-registry.js +28 -0
- package/dist/constraint-runtime/src/state/index.js +12 -0
- package/dist/constraint-runtime/src/system_init.js +20 -0
- package/dist/constraint-runtime/src/thinking/engine.js +42 -0
- package/dist/constraint-runtime/src/thinking/index.js +1 -0
- package/dist/constraint-runtime/src/tool_pool.js +8 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/execAdapter.js +7 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/listAdapters.js +7 -0
- package/dist/constraint-runtime/src/tools/OpenCLI/runCommand.js +7 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/confirmTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/createTransaction.js +8 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/executeTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/getBalance.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/getPendingTransactions.js +6 -0
- package/dist/constraint-runtime/src/tools/SafeSDK/proposeTransaction.js +6 -0
- package/dist/constraint-runtime/src/tools.js +80 -0
- package/dist/constraint-runtime/src/transcript.js +19 -0
- package/dist/constraint-runtime/src/types/index.js +12 -0
- package/dist/constraint-runtime/src/upstream_proxy/index.js +12 -0
- package/dist/constraint-runtime/src/utils/index.js +12 -0
- package/dist/constraint-runtime/src/vim/index.js +12 -0
- package/dist/constraint-runtime/src/voice/index.js +12 -0
- package/dist/constraint-runtime/tests/agent.test.js +16 -0
- package/dist/constraint-runtime/tests/constraint.test.js +41 -0
- package/dist/constraint-runtime/tests/skill.test.js +19 -0
- package/dist/constraint-runtime/tests/thinking.test.js +22 -0
- package/dist/context-compaction/auto-compact.js +7 -7
- package/dist/electron-preload.js +1 -18
- package/dist/electron-preload.js.map +1 -1
- package/dist/electron.js +168 -3
- package/dist/electron.js.map +1 -1
- package/dist/index.js +116 -136
- package/dist/llm/config-store.js +8 -24
- package/dist/llm/llm-judgment-client.js +102 -102
- package/dist/llm/pi-ai.js +85 -126
- 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-visitor.md +19 -19
- 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/hibs_reminders.md +15 -15
- package/dist/llm/system-prompt/layers/core/identity.md +37 -37
- 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/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/web_search.md +83 -83
- 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/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/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/security/tool-gate.js +0 -11
- 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/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 +520 -520
- package/dist/web/client.js +2917 -3791
- package/dist/web/components/p2p/index.js +234 -276
- package/dist/web/components/wallet-viem.mjs +118 -118
- package/dist/web/index.html +374 -373
- package/dist/web/manifest.json +20 -20
- package/dist/web/server.js +23 -775
- package/dist/web/style.css +4737 -4737
- package/dist/web/ui/message-renderer.js +373 -451
- package/dist/web/ui/step-timeline.js +255 -351
- package/package.json +160 -160
- package/scripts/build-cli.js +215 -215
- package/scripts/build-web.ts +125 -125
- package/scripts/postinstall.js +152 -152
- package/bin/bolloon-daemon.sh +0 -207
- package/bin/ipfs +0 -0
- package/dist/agents/chat-segmenter.js +0 -298
- package/dist/agents/judgment-protocol.js +0 -479
- package/dist/agents/parse-tool-call.js +0 -304
- package/dist/agents/peer-manifest-loader.js +0 -210
- package/dist/agents/react-loop.js +0 -120
- package/dist/agents/session-store.js +0 -171
- package/dist/agents/tool-registry.js +0 -136
- package/dist/bootstrap/chat-archiver.js +0 -276
- package/dist/bootstrap/memory-compressor.js +0 -170
- package/dist/bootstrap/persona-loader.js +0 -94
- package/dist/electron/config.js +0 -16
- package/dist/electron/config.js.map +0 -1
- package/dist/electron/dialogs.js +0 -71
- package/dist/electron/dialogs.js.map +0 -1
- package/dist/electron/first-run.js +0 -129
- package/dist/electron/first-run.js.map +0 -1
- package/dist/electron/ipc.js +0 -16
- package/dist/electron/ipc.js.map +0 -1
- package/dist/electron/logger.js +0 -77
- package/dist/electron/logger.js.map +0 -1
- package/dist/electron/main.js +0 -60
- package/dist/electron/main.js.map +0 -1
- package/dist/electron/menu.js +0 -140
- package/dist/electron/menu.js.map +0 -1
- package/dist/electron/paths.js +0 -33
- package/dist/electron/paths.js.map +0 -1
- package/dist/electron/server.js +0 -74
- package/dist/electron/server.js.map +0 -1
- package/dist/electron/tray.js +0 -73
- package/dist/electron/tray.js.map +0 -1
- package/dist/electron/window.js +0 -69
- package/dist/electron/window.js.map +0 -1
- package/dist/network/local-inbox-bus.js +0 -73
- package/dist/network/p2p-outbox.js +0 -161
- package/dist/network/peer-fs.js +0 -420
- package/dist/network/peer-resource-bridge.js +0 -216
- package/dist/web/components/p2p/P2PModal.js +0 -188
- package/dist/web/components/p2p/p2p-modal.js +0 -664
- package/dist/web/components/p2p/p2p-tools.js +0 -248
- package/dist/web/input-validator.js +0 -103
- package/dist/web/util/safe-name.js +0 -32
package/dist/network/peer-fs.js
DELETED
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* peer-fs.ts — peer 目录读写 + agents 索引 + manifest 落盘
|
|
3
|
-
*
|
|
4
|
-
* 设计目的 (2026-07-05):
|
|
5
|
-
* 把"远程 P2P 节点"在本地物化成一份文件夹, 让本地所有 agent 都能"知道对方有什么":
|
|
6
|
-
* ~/.bolloon/peers/<publicKey>/
|
|
7
|
-
* peer.json ← 对方基础信息 (name / publicKey / lastSeen / lastManifestTs)
|
|
8
|
-
* user.md ← 对方用户画像 (本地维护)
|
|
9
|
-
* _index.json ← 对方 agent 列表 (轻量, 懒加载时优先读)
|
|
10
|
-
* capability-index.md ← ≤500 字摘要, 拼进 prompt
|
|
11
|
-
* agents/<agentId>.md ← 每个 agent 详细描述
|
|
12
|
-
* groups/<groupId>.md ← 群组
|
|
13
|
-
* function/<cap>.md ← 视频/音乐/图片
|
|
14
|
-
* exportment/<game>.md ← 游戏
|
|
15
|
-
* science/<exp>.md ← 实验记录
|
|
16
|
-
* chat-<YYYY-MM>.md ← 月度聊天记录归档
|
|
17
|
-
* outbox.jsonl ← 离线发送队列 (对方不在线时缓存)
|
|
18
|
-
*
|
|
19
|
-
* 写盘策略:
|
|
20
|
-
* - 纯文本 md 文件, 人类可读, 便于调试
|
|
21
|
-
* - JSON 仅用于 peer.json / _index.json / outbox.jsonl
|
|
22
|
-
* - 全部操作 atomic (write tmp + rename), 避免半截文件
|
|
23
|
-
*/
|
|
24
|
-
import * as fs from 'fs/promises';
|
|
25
|
-
import * as path from 'path';
|
|
26
|
-
import * as os from 'os';
|
|
27
|
-
import * as crypto from 'crypto';
|
|
28
|
-
// ============== 路径常量 ==============
|
|
29
|
-
const HOME = process.env.BOLLOON_HOME || path.join(os.homedir(), '.bolloon');
|
|
30
|
-
const PEERS_ROOT = path.join(HOME, 'peers');
|
|
31
|
-
/** publicKey hex → 短哈希, 用于目录名防超长 */
|
|
32
|
-
export function peerDirName(publicKey) {
|
|
33
|
-
// 用 sha256 前 16 位 hex, 防 publicKey 改名冲突 + 文件名长度可控
|
|
34
|
-
const h = crypto.createHash('sha256').update(publicKey).digest('hex').slice(0, 16);
|
|
35
|
-
return `${h}__${publicKey.slice(0, 8)}`;
|
|
36
|
-
}
|
|
37
|
-
export function getPeerDir(publicKey) {
|
|
38
|
-
return path.join(PEERS_ROOT, peerDirName(publicKey));
|
|
39
|
-
}
|
|
40
|
-
export function getPeerIndexPath(publicKey) {
|
|
41
|
-
return path.join(getPeerDir(publicKey), '_index.json');
|
|
42
|
-
}
|
|
43
|
-
export function getPeerJsonPath(publicKey) {
|
|
44
|
-
return path.join(getPeerDir(publicKey), 'peer.json');
|
|
45
|
-
}
|
|
46
|
-
export function getPeerCapabilityIndexPath(publicKey) {
|
|
47
|
-
return path.join(getPeerDir(publicKey), 'capability-index.md');
|
|
48
|
-
}
|
|
49
|
-
export function getPeerChatPath(publicKey, yearMonth) {
|
|
50
|
-
const ym = yearMonth || currentYearMonth();
|
|
51
|
-
return path.join(getPeerDir(publicKey), `chat-${ym}.md`);
|
|
52
|
-
}
|
|
53
|
-
export function getPeerOutboxPath(publicKey) {
|
|
54
|
-
return path.join(getPeerDir(publicKey), 'outbox.jsonl');
|
|
55
|
-
}
|
|
56
|
-
export function getPeerUserMdPath(publicKey) {
|
|
57
|
-
return path.join(getPeerDir(publicKey), 'user.md');
|
|
58
|
-
}
|
|
59
|
-
export function getPeerAgentMdPath(publicKey, agentId) {
|
|
60
|
-
return path.join(getPeerDir(publicKey), 'agents', `${safeName(agentId)}.md`);
|
|
61
|
-
}
|
|
62
|
-
export function getPeerGroupMdPath(publicKey, groupId) {
|
|
63
|
-
return path.join(getPeerDir(publicKey), 'groups', `${safeName(groupId)}.md`);
|
|
64
|
-
}
|
|
65
|
-
export function getPeerFunctionMdPath(publicKey, capability) {
|
|
66
|
-
return path.join(getPeerDir(publicKey), 'function', `${safeName(capability)}.md`);
|
|
67
|
-
}
|
|
68
|
-
export function getPeerExportmentMdPath(publicKey, game) {
|
|
69
|
-
return path.join(getPeerDir(publicKey), 'exportment', `${safeName(game)}.md`);
|
|
70
|
-
}
|
|
71
|
-
export function getPeerScienceMdPath(publicKey, expId) {
|
|
72
|
-
return path.join(getPeerDir(publicKey), 'science', `${safeName(expId)}.md`);
|
|
73
|
-
}
|
|
74
|
-
export function currentYearMonth() {
|
|
75
|
-
const d = new Date();
|
|
76
|
-
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
77
|
-
}
|
|
78
|
-
/** 文件名安全化: 去掉 / \ : * ? " < > | 控制符 */
|
|
79
|
-
function safeName(s) {
|
|
80
|
-
return s.replace(/[\/\\:\*\?"<>\|\x00-\x1f]/g, '_').slice(0, 64);
|
|
81
|
-
}
|
|
82
|
-
// ============== Atomic write helper ==============
|
|
83
|
-
async function atomicWrite(filePath, content) {
|
|
84
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
85
|
-
const tmp = filePath + '.tmp.' + Math.random().toString(36).slice(2, 8);
|
|
86
|
-
await fs.writeFile(tmp, content);
|
|
87
|
-
await fs.rename(tmp, filePath);
|
|
88
|
-
}
|
|
89
|
-
async function atomicWriteJson(filePath, obj) {
|
|
90
|
-
await atomicWrite(filePath, JSON.stringify(obj, null, 2) + '\n');
|
|
91
|
-
}
|
|
92
|
-
async function readJsonSafe(filePath) {
|
|
93
|
-
try {
|
|
94
|
-
const raw = await fs.readFile(filePath, 'utf-8');
|
|
95
|
-
return JSON.parse(raw);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// ============== peer.json ==============
|
|
102
|
-
export async function upsertPeer(rec) {
|
|
103
|
-
const file = getPeerJsonPath(rec.publicKey);
|
|
104
|
-
const existing = await readJsonSafe(file);
|
|
105
|
-
const merged = {
|
|
106
|
-
publicKey: rec.publicKey,
|
|
107
|
-
name: rec.name ?? existing?.name,
|
|
108
|
-
addedAt: existing?.addedAt || rec.addedAt || new Date().toISOString(),
|
|
109
|
-
lastSeenAt: rec.lastSeenAt ?? existing?.lastSeenAt,
|
|
110
|
-
lastManifestTs: rec.lastManifestTs ?? existing?.lastManifestTs,
|
|
111
|
-
manifestCount: rec.manifestCount ?? existing?.manifestCount,
|
|
112
|
-
notes: rec.notes ?? existing?.notes,
|
|
113
|
-
};
|
|
114
|
-
await atomicWriteJson(file, merged);
|
|
115
|
-
return merged;
|
|
116
|
-
}
|
|
117
|
-
export async function getPeer(publicKey) {
|
|
118
|
-
return readJsonSafe(getPeerJsonPath(publicKey));
|
|
119
|
-
}
|
|
120
|
-
export async function listPeersFromDisk() {
|
|
121
|
-
try {
|
|
122
|
-
const entries = await fs.readdir(PEERS_ROOT);
|
|
123
|
-
const out = [];
|
|
124
|
-
for (const e of entries) {
|
|
125
|
-
const peerFile = path.join(PEERS_ROOT, e, 'peer.json');
|
|
126
|
-
const rec = await readJsonSafe(peerFile);
|
|
127
|
-
if (rec)
|
|
128
|
-
out.push(rec);
|
|
129
|
-
}
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return [];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// ============== _index.json (agent 清单) ==============
|
|
137
|
-
export async function writePeerIndex(publicKey, idx) {
|
|
138
|
-
await atomicWriteJson(getPeerIndexPath(publicKey), idx);
|
|
139
|
-
}
|
|
140
|
-
export async function readPeerIndex(publicKey) {
|
|
141
|
-
return readJsonSafe(getPeerIndexPath(publicKey));
|
|
142
|
-
}
|
|
143
|
-
// ============== 单个 agent 详细描述 (markdown) ==============
|
|
144
|
-
export async function writeAgentDescription(publicKey, agent, ownerDescription) {
|
|
145
|
-
const lines = [];
|
|
146
|
-
lines.push(`# ${agent.name || agent.id}`);
|
|
147
|
-
lines.push('');
|
|
148
|
-
lines.push(`- **ID**: ${agent.id}`);
|
|
149
|
-
lines.push(`- **状态**: ${agent.status}`);
|
|
150
|
-
if (agent.capabilities.length) {
|
|
151
|
-
lines.push(`- **能力**: ${agent.capabilities.join(', ')}`);
|
|
152
|
-
}
|
|
153
|
-
if (agent.description) {
|
|
154
|
-
lines.push('');
|
|
155
|
-
lines.push('## 描述');
|
|
156
|
-
lines.push('');
|
|
157
|
-
lines.push(agent.description);
|
|
158
|
-
}
|
|
159
|
-
if (ownerDescription) {
|
|
160
|
-
lines.push('');
|
|
161
|
-
lines.push('## Owner 备注');
|
|
162
|
-
lines.push('');
|
|
163
|
-
lines.push(ownerDescription);
|
|
164
|
-
}
|
|
165
|
-
if (agent.peerId || agent.irohNodeId || agent.sessionId) {
|
|
166
|
-
lines.push('');
|
|
167
|
-
lines.push('## 寻址');
|
|
168
|
-
if (agent.peerId)
|
|
169
|
-
lines.push(`- peerId: ${agent.peerId}`);
|
|
170
|
-
if (agent.irohNodeId)
|
|
171
|
-
lines.push(`- irohNodeId: ${agent.irohNodeId}`);
|
|
172
|
-
if (agent.sessionId)
|
|
173
|
-
lines.push(`- sessionId: ${agent.sessionId}`);
|
|
174
|
-
}
|
|
175
|
-
lines.push('');
|
|
176
|
-
lines.push(`> 自动生成于 ${new Date().toISOString()} (peer=${publicKey.slice(0, 12)}...)`);
|
|
177
|
-
lines.push('');
|
|
178
|
-
await atomicWrite(getPeerAgentMdPath(publicKey, agent.id), lines.join('\n'));
|
|
179
|
-
}
|
|
180
|
-
// ============== capability-index.md (≤500 字摘要, 进 prompt) ==============
|
|
181
|
-
export function buildCapabilityIndex(idx) {
|
|
182
|
-
const lines = [];
|
|
183
|
-
lines.push(`# 远端节点能力索引 — ${idx.ownerName || idx.publicKey.slice(0, 12)}`);
|
|
184
|
-
lines.push('');
|
|
185
|
-
if (idx.ownerDescription) {
|
|
186
|
-
lines.push(idx.ownerDescription.slice(0, 200));
|
|
187
|
-
lines.push('');
|
|
188
|
-
}
|
|
189
|
-
lines.push(`## Agent (${idx.agents.length} 个)`);
|
|
190
|
-
for (const a of idx.agents.slice(0, 20)) {
|
|
191
|
-
const caps = a.capabilities.length ? ` [${a.capabilities.join('/')}]` : '';
|
|
192
|
-
const desc = a.description ? ` — ${a.description.slice(0, 60)}` : '';
|
|
193
|
-
lines.push(`- ${a.name}${caps}${desc}`);
|
|
194
|
-
}
|
|
195
|
-
if (idx.agents.length > 20)
|
|
196
|
-
lines.push(`- … 还有 ${idx.agents.length - 20} 个`);
|
|
197
|
-
if (idx.groups?.length) {
|
|
198
|
-
lines.push('');
|
|
199
|
-
lines.push(`## 群组 (${idx.groups.length})`);
|
|
200
|
-
for (const g of idx.groups.slice(0, 10))
|
|
201
|
-
lines.push(`- ${g.name}${g.description ? ' — ' + g.description.slice(0, 50) : ''}`);
|
|
202
|
-
}
|
|
203
|
-
if (idx.functions?.length) {
|
|
204
|
-
lines.push('');
|
|
205
|
-
lines.push(`## 可用功能 (${idx.functions.length})`);
|
|
206
|
-
for (const f of idx.functions.slice(0, 10))
|
|
207
|
-
lines.push(`- ${f.capability}${f.description ? ' — ' + f.description.slice(0, 50) : ''}`);
|
|
208
|
-
}
|
|
209
|
-
if (idx.exportments?.length) {
|
|
210
|
-
lines.push('');
|
|
211
|
-
lines.push(`## 娱乐 (${idx.exportments.length})`);
|
|
212
|
-
for (const e of idx.exportments.slice(0, 10))
|
|
213
|
-
lines.push(`- ${e.name}${e.description ? ' — ' + e.description.slice(0, 50) : ''}`);
|
|
214
|
-
}
|
|
215
|
-
const text = lines.join('\n');
|
|
216
|
-
// 截到 500 字
|
|
217
|
-
return text.length > 500 ? text.slice(0, 497) + '…' : text;
|
|
218
|
-
}
|
|
219
|
-
export async function writeCapabilityIndex(publicKey, idx) {
|
|
220
|
-
const text = buildCapabilityIndex(idx);
|
|
221
|
-
await atomicWrite(getPeerCapabilityIndexPath(publicKey), text);
|
|
222
|
-
}
|
|
223
|
-
export async function readCapabilityIndex(publicKey) {
|
|
224
|
-
try {
|
|
225
|
-
return await fs.readFile(getPeerCapabilityIndexPath(publicKey), 'utf-8');
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// ============== chat-<YYYY-MM>.md 月度归档 ==============
|
|
232
|
-
function formatChatEntry(e) {
|
|
233
|
-
const ts = e.ts.replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
|
|
234
|
-
const src = e.source;
|
|
235
|
-
const chan = e.channelName ? ` [${e.channelName}]` : '';
|
|
236
|
-
const from = e.fromPublicKey ? ` (${e.fromPublicKey.slice(0, 12)}…)` : '';
|
|
237
|
-
return `### ${ts}${chan} — ${src}${from}\n\n${e.text}\n`;
|
|
238
|
-
}
|
|
239
|
-
export async function appendChat(publicKey, entry) {
|
|
240
|
-
const file = getPeerChatPath(publicKey);
|
|
241
|
-
// 如果文件不存在, 先写头
|
|
242
|
-
try {
|
|
243
|
-
await fs.access(file);
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
await atomicWrite(file, `# 与 ${publicKey.slice(0, 12)}… 的聊天记录\n\n> 自动归档; 详细消息存于 sessions/cache/, 这里按月滚动.\n\n`);
|
|
247
|
-
}
|
|
248
|
-
// append
|
|
249
|
-
await fs.appendFile(file, '\n---\n\n' + formatChatEntry(entry), 'utf-8');
|
|
250
|
-
}
|
|
251
|
-
export async function readChatArchive(publicKey, yearMonth) {
|
|
252
|
-
try {
|
|
253
|
-
return await fs.readFile(getPeerChatPath(publicKey, yearMonth), 'utf-8');
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
export async function listChatMonths(publicKey) {
|
|
260
|
-
try {
|
|
261
|
-
const dir = getPeerDir(publicKey);
|
|
262
|
-
const entries = await fs.readdir(dir);
|
|
263
|
-
return entries
|
|
264
|
-
.filter(e => /^chat-\d{4}-\d{2}\.md$/.test(e))
|
|
265
|
-
.map(e => e.replace(/^chat-/, '').replace(/\.md$/, ''))
|
|
266
|
-
.sort();
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
// ============== outbox.jsonl 离线队列 ==============
|
|
273
|
-
export async function enqueueOutbox(publicKey, entry) {
|
|
274
|
-
const file = getPeerOutboxPath(publicKey);
|
|
275
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
276
|
-
await fs.appendFile(file, JSON.stringify(entry) + '\n', 'utf-8');
|
|
277
|
-
}
|
|
278
|
-
export async function readOutbox(publicKey) {
|
|
279
|
-
try {
|
|
280
|
-
const raw = await fs.readFile(getPeerOutboxPath(publicKey), 'utf-8');
|
|
281
|
-
return raw.split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return [];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
export async function clearOutbox(publicKey) {
|
|
288
|
-
try {
|
|
289
|
-
await fs.unlink(getPeerOutboxPath(publicKey));
|
|
290
|
-
}
|
|
291
|
-
catch { }
|
|
292
|
-
}
|
|
293
|
-
export async function countOutbox(publicKey) {
|
|
294
|
-
return (await readOutbox(publicKey)).length;
|
|
295
|
-
}
|
|
296
|
-
// ============== user.md 本地维护的对方用户画像 ==============
|
|
297
|
-
export async function readUserPortrait(publicKey) {
|
|
298
|
-
try {
|
|
299
|
-
return await fs.readFile(getPeerUserMdPath(publicKey), 'utf-8');
|
|
300
|
-
}
|
|
301
|
-
catch {
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
export async function writeUserPortrait(publicKey, markdown) {
|
|
306
|
-
await atomicWrite(getPeerUserMdPath(publicKey), markdown);
|
|
307
|
-
}
|
|
308
|
-
// ============== 单个 group/function/exportment/science writer ==============
|
|
309
|
-
// (peer-fs.ts 之前只有 reader — 现在补齐 4 类资源的 writer, 让 manifest.exchange.reply
|
|
310
|
-
// 能落盘对方的群组/功能/娱乐/实验清单, 跟 agents/*.md 一样被本地 agent 看见.)
|
|
311
|
-
function formatFrontmatter(fields) {
|
|
312
|
-
const lines = ['---'];
|
|
313
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
314
|
-
if (v === undefined || v === null)
|
|
315
|
-
continue;
|
|
316
|
-
if (Array.isArray(v)) {
|
|
317
|
-
lines.push(`${k}: [${v.map((x) => JSON.stringify(x)).join(', ')}]`);
|
|
318
|
-
}
|
|
319
|
-
else if (typeof v === 'string') {
|
|
320
|
-
lines.push(`${k}: ${JSON.stringify(v)}`);
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
lines.push(`${k}: ${JSON.stringify(v)}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
lines.push('---');
|
|
327
|
-
return lines.join('\n');
|
|
328
|
-
}
|
|
329
|
-
function mdBody(title, fields, description, extra) {
|
|
330
|
-
const parts = [];
|
|
331
|
-
parts.push(`# ${title}`);
|
|
332
|
-
parts.push('');
|
|
333
|
-
parts.push(formatFrontmatter(fields));
|
|
334
|
-
parts.push('');
|
|
335
|
-
if (description) {
|
|
336
|
-
parts.push(description.trim());
|
|
337
|
-
parts.push('');
|
|
338
|
-
}
|
|
339
|
-
if (extra?.length) {
|
|
340
|
-
parts.push(...extra);
|
|
341
|
-
parts.push('');
|
|
342
|
-
}
|
|
343
|
-
parts.push(`> 自动写入于 ${new Date().toISOString()} (peer 来源)`);
|
|
344
|
-
parts.push('');
|
|
345
|
-
return parts.join('\n');
|
|
346
|
-
}
|
|
347
|
-
export async function writeGroup(publicKey, g) {
|
|
348
|
-
const body = mdBody(g.name, {
|
|
349
|
-
id: g.id,
|
|
350
|
-
name: g.name,
|
|
351
|
-
visibility: g.visibility || 'invite',
|
|
352
|
-
memberCount: g.memberCount,
|
|
353
|
-
}, g.description);
|
|
354
|
-
await atomicWrite(getPeerGroupMdPath(publicKey, g.id), body);
|
|
355
|
-
}
|
|
356
|
-
export async function writeFunction(publicKey, f) {
|
|
357
|
-
const body = mdBody(f.capability, {
|
|
358
|
-
capability: f.capability,
|
|
359
|
-
mediaType: f.mediaType || 'text',
|
|
360
|
-
endpoint: f.endpoint,
|
|
361
|
-
}, f.description, f.examples?.length ? ['## 示例', '', ...f.examples.map((e) => `- ${e}`)] : undefined);
|
|
362
|
-
await atomicWrite(getPeerFunctionMdPath(publicKey, f.capability), body);
|
|
363
|
-
}
|
|
364
|
-
export async function writeExportment(publicKey, e) {
|
|
365
|
-
const body = mdBody(e.name, {
|
|
366
|
-
name: e.name,
|
|
367
|
-
genre: e.genre,
|
|
368
|
-
minPlayers: e.minPlayers,
|
|
369
|
-
maxPlayers: e.maxPlayers,
|
|
370
|
-
}, e.description);
|
|
371
|
-
await atomicWrite(getPeerExportmentMdPath(publicKey, e.name), body);
|
|
372
|
-
}
|
|
373
|
-
export async function writeScience(publicKey, s) {
|
|
374
|
-
const recordsBlock = s.records?.length
|
|
375
|
-
? ['## 记录', '', ...s.records.map((r) => `- **${r.ts}**: ${r.note}`)]
|
|
376
|
-
: undefined;
|
|
377
|
-
const body = mdBody(s.title, {
|
|
378
|
-
id: s.id,
|
|
379
|
-
title: s.title,
|
|
380
|
-
status: s.status || 'planned',
|
|
381
|
-
tags: s.tags,
|
|
382
|
-
}, s.description, recordsBlock);
|
|
383
|
-
await atomicWrite(getPeerScienceMdPath(publicKey, s.id), body);
|
|
384
|
-
}
|
|
385
|
-
export async function listPeerResources(publicKey) {
|
|
386
|
-
const dir = getPeerDir(publicKey);
|
|
387
|
-
const out = { groups: [], functions: [], exportments: [], sciences: [] };
|
|
388
|
-
for (const [subdir, key] of [
|
|
389
|
-
['groups', 'groups'],
|
|
390
|
-
['function', 'functions'],
|
|
391
|
-
['exportment', 'exportments'],
|
|
392
|
-
['science', 'sciences'],
|
|
393
|
-
]) {
|
|
394
|
-
try {
|
|
395
|
-
const entries = await fs.readdir(path.join(dir, subdir));
|
|
396
|
-
for (const e of entries) {
|
|
397
|
-
if (!e.endsWith('.md'))
|
|
398
|
-
continue;
|
|
399
|
-
const content = await fs.readFile(path.join(dir, subdir, e), 'utf-8');
|
|
400
|
-
const id = e.replace(/\.md$/, '');
|
|
401
|
-
if (key === 'groups')
|
|
402
|
-
out.groups.push({ id, content });
|
|
403
|
-
else if (key === 'functions')
|
|
404
|
-
out.functions.push({ capability: id, content });
|
|
405
|
-
else if (key === 'exportments')
|
|
406
|
-
out.exportments.push({ name: id, content });
|
|
407
|
-
else
|
|
408
|
-
out.sciences.push({ id, content });
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
catch { }
|
|
412
|
-
}
|
|
413
|
-
return out;
|
|
414
|
-
}
|
|
415
|
-
// ============== 测试 / 调试 helper ==============
|
|
416
|
-
export const _debug = {
|
|
417
|
-
PEERS_ROOT,
|
|
418
|
-
peerDirName,
|
|
419
|
-
getPeerDir,
|
|
420
|
-
};
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* peer-resource-bridge.ts — 4 类资源 (groups/function/exportment/science) 的
|
|
3
|
-
* 本地读 + 远端落盘的统一封装.
|
|
4
|
-
*
|
|
5
|
-
* 设计目的 (2026-07-05):
|
|
6
|
-
* peer-fs.ts 已经有完整路径 helpers (writeGroup/writeFunction/...) + reader (listPeerResources).
|
|
7
|
-
* server.ts 三处要调 (两个 manifest.exchange.reply 落盘 + 一个 manifest.exchange 发送),
|
|
8
|
-
* 每处手写 4 个 await 又啰嗦又容易漏. 这里抽出来.
|
|
9
|
-
*
|
|
10
|
-
* 本地读: ~/.bolloon/local-resources/<category>/<id>.md frontmatter
|
|
11
|
-
* 远端落: peerFs.writeGroup/Function/Exportment/Science (atomic, 人类可读 md)
|
|
12
|
-
*
|
|
13
|
-
* frontmatter 字段就是类型本身的字段 (id/name/description/...),
|
|
14
|
-
* description 从 frontmatter 后面的第一个段落拿.
|
|
15
|
-
*/
|
|
16
|
-
import * as fs from 'fs/promises';
|
|
17
|
-
import * as path from 'path';
|
|
18
|
-
import * as os from 'os';
|
|
19
|
-
import * as peerFs from '../network/peer-fs.js';
|
|
20
|
-
const HOME = process.env.BOLLOON_HOME || path.join(os.homedir(), '.bolloon');
|
|
21
|
-
export const LOCAL_RESOURCES_ROOT = path.join(HOME, 'local-resources');
|
|
22
|
-
const EMPTY = { groups: [], functions: [], exportments: [], sciences: [] };
|
|
23
|
-
/**
|
|
24
|
-
* 简单 frontmatter 解析: 只支持 key: value / key: [..] (本文件自产自销, 不引第三方).
|
|
25
|
-
*/
|
|
26
|
-
function parseFrontmatter(text) {
|
|
27
|
-
const m = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
28
|
-
if (!m)
|
|
29
|
-
return { fields: {}, body: text };
|
|
30
|
-
const fields = {};
|
|
31
|
-
for (const line of m[1].split('\n')) {
|
|
32
|
-
const kv = line.match(/^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/);
|
|
33
|
-
if (!kv)
|
|
34
|
-
continue;
|
|
35
|
-
const key = kv[1];
|
|
36
|
-
let raw = kv[2].trim();
|
|
37
|
-
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
38
|
-
try {
|
|
39
|
-
fields[key] = JSON.parse(raw);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
catch { }
|
|
43
|
-
}
|
|
44
|
-
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
45
|
-
try {
|
|
46
|
-
fields[key] = JSON.parse(raw);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
}
|
|
51
|
-
fields[key] = raw;
|
|
52
|
-
}
|
|
53
|
-
return { fields, body: m[2].trim() };
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 从 ~/.bolloon/local-resources/<category>/<id>.md 读所有本地资源.
|
|
57
|
-
* 文件不存在 / 目录不存在 → 返回空数组 (不报错, 跟 peer-fs 风格一致).
|
|
58
|
-
*/
|
|
59
|
-
async function readCategoryDir(category) {
|
|
60
|
-
try {
|
|
61
|
-
const dir = path.join(LOCAL_RESOURCES_ROOT, category);
|
|
62
|
-
const entries = await fs.readdir(dir);
|
|
63
|
-
return entries.filter((e) => e.endsWith('.md')).map((e) => path.join(dir, e));
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async function readGroupFile(p) {
|
|
70
|
-
try {
|
|
71
|
-
const raw = await fs.readFile(p, 'utf-8');
|
|
72
|
-
const { fields, body } = parseFrontmatter(raw);
|
|
73
|
-
if (!fields.id)
|
|
74
|
-
return null;
|
|
75
|
-
return {
|
|
76
|
-
id: String(fields.id),
|
|
77
|
-
name: String(fields.name || fields.id),
|
|
78
|
-
description: body || undefined,
|
|
79
|
-
visibility: fields.visibility,
|
|
80
|
-
memberCount: typeof fields.memberCount === 'number' ? fields.memberCount : undefined,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function readFunctionFile(p) {
|
|
88
|
-
try {
|
|
89
|
-
const raw = await fs.readFile(p, 'utf-8');
|
|
90
|
-
const { fields, body } = parseFrontmatter(raw);
|
|
91
|
-
if (!fields.capability)
|
|
92
|
-
return null;
|
|
93
|
-
return {
|
|
94
|
-
capability: String(fields.capability),
|
|
95
|
-
description: body || undefined,
|
|
96
|
-
mediaType: fields.mediaType,
|
|
97
|
-
endpoint: fields.endpoint ? String(fields.endpoint) : undefined,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async function readExportmentFile(p) {
|
|
105
|
-
try {
|
|
106
|
-
const raw = await fs.readFile(p, 'utf-8');
|
|
107
|
-
const { fields, body } = parseFrontmatter(raw);
|
|
108
|
-
if (!fields.name)
|
|
109
|
-
return null;
|
|
110
|
-
return {
|
|
111
|
-
name: String(fields.name),
|
|
112
|
-
description: body || undefined,
|
|
113
|
-
genre: fields.genre ? String(fields.genre) : undefined,
|
|
114
|
-
minPlayers: typeof fields.minPlayers === 'number' ? fields.minPlayers : undefined,
|
|
115
|
-
maxPlayers: typeof fields.maxPlayers === 'number' ? fields.maxPlayers : undefined,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async function readScienceFile(p) {
|
|
123
|
-
try {
|
|
124
|
-
const raw = await fs.readFile(p, 'utf-8');
|
|
125
|
-
const { fields, body } = parseFrontmatter(raw);
|
|
126
|
-
if (!fields.id)
|
|
127
|
-
return null;
|
|
128
|
-
return {
|
|
129
|
-
id: String(fields.id),
|
|
130
|
-
title: String(fields.title || fields.id),
|
|
131
|
-
description: body || undefined,
|
|
132
|
-
status: fields.status,
|
|
133
|
-
tags: Array.isArray(fields.tags) ? fields.tags.map(String) : undefined,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 读本机所有 4 类资源. 任何失败 → 该类别返回空 (不阻塞其他类别).
|
|
142
|
-
*/
|
|
143
|
-
export async function loadLocalResources() {
|
|
144
|
-
const out = { ...EMPTY };
|
|
145
|
-
const [gFiles, fFiles, eFiles, sFiles] = await Promise.all([
|
|
146
|
-
readCategoryDir('groups'),
|
|
147
|
-
readCategoryDir('functions'),
|
|
148
|
-
readCategoryDir('exportments'),
|
|
149
|
-
readCategoryDir('sciences'),
|
|
150
|
-
]);
|
|
151
|
-
for (const f of gFiles) {
|
|
152
|
-
const v = await readGroupFile(f);
|
|
153
|
-
if (v)
|
|
154
|
-
out.groups.push(v);
|
|
155
|
-
}
|
|
156
|
-
for (const f of fFiles) {
|
|
157
|
-
const v = await readFunctionFile(f);
|
|
158
|
-
if (v)
|
|
159
|
-
out.functions.push(v);
|
|
160
|
-
}
|
|
161
|
-
for (const f of eFiles) {
|
|
162
|
-
const v = await readExportmentFile(f);
|
|
163
|
-
if (v)
|
|
164
|
-
out.exportments.push(v);
|
|
165
|
-
}
|
|
166
|
-
for (const f of sFiles) {
|
|
167
|
-
const v = await readScienceFile(f);
|
|
168
|
-
if (v)
|
|
169
|
-
out.sciences.push(v);
|
|
170
|
-
}
|
|
171
|
-
return out;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* 把 manifest 里的 4 类资源落盘到 peerFs 对应目录.
|
|
175
|
-
* 静默容错: 单条失败不影响其他.
|
|
176
|
-
*/
|
|
177
|
-
export async function writeRemoteResources(publicKey, m) {
|
|
178
|
-
let groups = 0, functions = 0, exportments = 0, sciences = 0;
|
|
179
|
-
if (Array.isArray(m.groups)) {
|
|
180
|
-
for (const g of m.groups) {
|
|
181
|
-
try {
|
|
182
|
-
await peerFs.writeGroup(publicKey, g);
|
|
183
|
-
groups++;
|
|
184
|
-
}
|
|
185
|
-
catch { }
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (Array.isArray(m.functions)) {
|
|
189
|
-
for (const f of m.functions) {
|
|
190
|
-
try {
|
|
191
|
-
await peerFs.writeFunction(publicKey, f);
|
|
192
|
-
functions++;
|
|
193
|
-
}
|
|
194
|
-
catch { }
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (Array.isArray(m.exportments)) {
|
|
198
|
-
for (const e of m.exportments) {
|
|
199
|
-
try {
|
|
200
|
-
await peerFs.writeExportment(publicKey, e);
|
|
201
|
-
exportments++;
|
|
202
|
-
}
|
|
203
|
-
catch { }
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (Array.isArray(m.sciences)) {
|
|
207
|
-
for (const s of m.sciences) {
|
|
208
|
-
try {
|
|
209
|
-
await peerFs.writeScience(publicKey, s);
|
|
210
|
-
sciences++;
|
|
211
|
-
}
|
|
212
|
-
catch { }
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return { groups, functions, exportments, sciences };
|
|
216
|
-
}
|