@bolloon/bolloon-agent 0.2.8 → 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 -157
- package/scripts/build-cli.js +216 -0
- package/scripts/build-web.ts +125 -0
- package/scripts/postinstall.js +153 -0
- 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
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* session-store — 持久化 Message[] (LLM 历史) 到 ~/.bolloon/sessions/cache/<key>.json
|
|
3
|
-
*
|
|
4
|
-
* 2026-06-30 抽出:
|
|
5
|
-
* - 之前 PiAgentSession 用 constraint-runtime 的 saveSession (存 string[]) + 独立的 hydrateMessageHistory
|
|
6
|
-
* 读路径, 写路径不存在 — claude code / 外部 harness 想"接续 prompt"完全无入口.
|
|
7
|
-
* - 现在抽出 SessionStore 模块, 暴露纯 IO 接口, 完全可消融测试:
|
|
8
|
-
* saveMessages(key, msgs) — 写
|
|
9
|
-
* loadMessages(key) — 读
|
|
10
|
-
* listKeys() — 列全部 key
|
|
11
|
-
* deleteKey(key) — 清
|
|
12
|
-
* - 任何目录位置都可注入 (默认 ~/.bolloon/sessions/cache/), 测试里指向临时 dir.
|
|
13
|
-
*
|
|
14
|
-
* 使用:
|
|
15
|
-
* import { sessionStore } from './session-store';
|
|
16
|
-
* sessionStore.saveMessages('cli:session-A', messageHistory);
|
|
17
|
-
* const restored = sessionStore.loadMessages('cli:session-A');
|
|
18
|
-
*/
|
|
19
|
-
import * as fs from 'fs/promises';
|
|
20
|
-
import * as path from 'path';
|
|
21
|
-
import * as os from 'os';
|
|
22
|
-
export class SessionStore {
|
|
23
|
-
cacheDir;
|
|
24
|
-
constructor(config = {}) {
|
|
25
|
-
this.cacheDir = config.cacheDir ?? path.join(os.homedir(), '.bolloon', 'sessions', 'cache');
|
|
26
|
-
}
|
|
27
|
-
/** 当前缓存目录 (只读). */
|
|
28
|
-
get dir() {
|
|
29
|
-
return this.cacheDir;
|
|
30
|
-
}
|
|
31
|
-
/** 单文件路径 — 暴露出来方便测试和外部读取.
|
|
32
|
-
*
|
|
33
|
-
* 2026-07-04 fix: Windows 文件名禁止 `:` (NTFS). web server 用 `channelId:currentSessionId`
|
|
34
|
-
* 拼 sessionKey (server.ts:1759 之类), 会含 `:`. 在 Linux/macOS 上能用, Windows 上
|
|
35
|
-
* fs.writeFile 抛 EINVAL. 修法: filename 层 escape `:` → `__`, key 保持不变
|
|
36
|
-
* (load/save/listKeys/deleteKey 全部透明).
|
|
37
|
-
*/
|
|
38
|
-
pathFor(key) {
|
|
39
|
-
if (!key || key.includes('/') || key.includes('..')) {
|
|
40
|
-
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
41
|
-
}
|
|
42
|
-
return path.join(this.cacheDir, `${SessionStore.filenameEscape(key)}.json`);
|
|
43
|
-
}
|
|
44
|
-
/** 把 session key 转换成跨平台安全的 filename (escape Windows 非法字符). */
|
|
45
|
-
static filenameEscape(key) {
|
|
46
|
-
return key.replace(/:/g, '__');
|
|
47
|
-
}
|
|
48
|
-
/** listKeys 反向: 把 filename 还原成 session key. */
|
|
49
|
-
static filenameUnescape(filenameKey) {
|
|
50
|
-
return filenameKey.replace(/__/g, ':');
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* 写 history to disk.
|
|
54
|
-
* - 自动 ensure 缓存目录
|
|
55
|
-
* - 失败抛错 (写失败不应静默 — 用户可能依赖持久化作为审计)
|
|
56
|
-
*/
|
|
57
|
-
async saveMessages(key, messages) {
|
|
58
|
-
if (!key || key.includes('/') || key.includes('..')) {
|
|
59
|
-
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
60
|
-
}
|
|
61
|
-
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
62
|
-
const payload = {
|
|
63
|
-
key,
|
|
64
|
-
messages,
|
|
65
|
-
metadata: {
|
|
66
|
-
savedAt: Date.now(),
|
|
67
|
-
cwd: process.cwd(),
|
|
68
|
-
totalCount: messages.length,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
const filePath = this.pathFor(key);
|
|
72
|
-
// 写临时文件 + rename — 防止半写损坏
|
|
73
|
-
const tmpPath = `${filePath}.tmp`;
|
|
74
|
-
await fs.writeFile(tmpPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
75
|
-
await fs.rename(tmpPath, filePath);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* 同步版 (PiAgentSession 里多条路径用同步 — 避免 async 链)
|
|
79
|
-
* 失败抛错
|
|
80
|
-
*/
|
|
81
|
-
saveMessagesSync(key, messages) {
|
|
82
|
-
if (!key || key.includes('/') || key.includes('..')) {
|
|
83
|
-
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
84
|
-
}
|
|
85
|
-
fsSyncMkdir(this.cacheDir);
|
|
86
|
-
const payload = {
|
|
87
|
-
key,
|
|
88
|
-
messages,
|
|
89
|
-
metadata: {
|
|
90
|
-
savedAt: Date.now(),
|
|
91
|
-
cwd: process.cwd(),
|
|
92
|
-
totalCount: messages.length,
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
const filePath = this.pathFor(key);
|
|
96
|
-
const tmpPath = `${filePath}.tmp`;
|
|
97
|
-
fsSyncWrite(tmpPath, JSON.stringify(payload, null, 2));
|
|
98
|
-
fsSyncRename(tmpPath, filePath);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* 读 history from disk.
|
|
102
|
-
* - key 不存在 → 返回 null (不抛错)
|
|
103
|
-
* - 文件存在但 JSON 损坏 → 抛错 (claude code 应该感知坏数据)
|
|
104
|
-
* - 文件 schema 不匹配 (新版格式) → 抛错含 details
|
|
105
|
-
*/
|
|
106
|
-
async loadMessages(key) {
|
|
107
|
-
if (!key || key.includes('/') || key.includes('..')) {
|
|
108
|
-
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
109
|
-
}
|
|
110
|
-
const filePath = this.pathFor(key);
|
|
111
|
-
let raw;
|
|
112
|
-
try {
|
|
113
|
-
raw = await fs.readFile(filePath, 'utf-8');
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
if (e?.code === 'ENOENT')
|
|
117
|
-
return null;
|
|
118
|
-
throw e;
|
|
119
|
-
}
|
|
120
|
-
const parsed = JSON.parse(raw);
|
|
121
|
-
if (!parsed || !Array.isArray(parsed.messages)) {
|
|
122
|
-
throw new Error(`SessionStore: ${key} schema invalid (missing messages[])`);
|
|
123
|
-
}
|
|
124
|
-
return parsed.messages;
|
|
125
|
-
}
|
|
126
|
-
/** 列所有 keys — 用于调试 / claude code "看有哪些 session 可接续". */
|
|
127
|
-
async listKeys() {
|
|
128
|
-
try {
|
|
129
|
-
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
130
|
-
const files = await fs.readdir(this.cacheDir);
|
|
131
|
-
return files
|
|
132
|
-
.filter(f => f.endsWith('.json') && !f.endsWith('.tmp'))
|
|
133
|
-
.map(f => SessionStore.filenameUnescape(f.slice(0, -'.json'.length)))
|
|
134
|
-
.sort();
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/** 删一条 session — 失败抛错. */
|
|
141
|
-
async deleteKey(key) {
|
|
142
|
-
if (!key || key.includes('/') || key.includes('..')) {
|
|
143
|
-
throw new Error(`SessionStore: invalid key ${JSON.stringify(key)}`);
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
await fs.unlink(this.pathFor(key));
|
|
147
|
-
}
|
|
148
|
-
catch (e) {
|
|
149
|
-
if (e?.code !== 'ENOENT')
|
|
150
|
-
throw e;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// --- sync fs helpers (避免 import 'fs' 二次) ---
|
|
155
|
-
function fsSyncMkdir(dir) {
|
|
156
|
-
try {
|
|
157
|
-
require('fs').mkdirSync(dir, { recursive: true });
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
if (e?.code !== 'EEXIST')
|
|
161
|
-
throw e;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function fsSyncWrite(filePath, content) {
|
|
165
|
-
require('fs').writeFileSync(filePath, content, 'utf-8');
|
|
166
|
-
}
|
|
167
|
-
function fsSyncRename(from, to) {
|
|
168
|
-
require('fs').renameSync(from, to);
|
|
169
|
-
}
|
|
170
|
-
/** 默认 store 实例 (用 ~/.bolloon/sessions/cache/). 业务代码用这个. */
|
|
171
|
-
export const sessionStore = new SessionStore();
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ToolRegistry — 工具注册表 + 别名解析器
|
|
3
|
-
*
|
|
4
|
-
* 2026-06-30 抽出:
|
|
5
|
-
* - 原 PiAgentSession.tools Map + resolveToolName 是 private 方法, 测试无法直接验证 alias 映射
|
|
6
|
-
* - 现在抽到独立模块, 完全可消融测试.
|
|
7
|
-
*
|
|
8
|
-
* 设计目标:
|
|
9
|
-
* 1. claude code / 外部 harness 直接 import, 用同一份 alias 表
|
|
10
|
-
* 2. 注册/反注册/查表都是纯方法, 无副作用
|
|
11
|
-
* 3. 默认 alias 表跟 parse-tool-call.ts 保持一致 (read→read_file, bash→shell_exec 等)
|
|
12
|
-
* 4. 自定义 alias 可注入 (业务可扩)
|
|
13
|
-
*
|
|
14
|
-
* 用法:
|
|
15
|
-
* import { ToolRegistry, DEFAULT_ALIASES } from './tool-registry';
|
|
16
|
-
* const reg = new ToolRegistry();
|
|
17
|
-
* reg.register({ name: 'shell_exec', description: '...', parameters: {}, execute });
|
|
18
|
-
* reg.has('bash'); // true (alias 命中)
|
|
19
|
-
* reg.resolve('bash'); // 'shell_exec'
|
|
20
|
-
* reg.get('shell_exec')?.execute({ command: 'ls' });
|
|
21
|
-
*/
|
|
22
|
-
/** 默认 alias 表 — Claude Code 风格 → bolloon 工具名 */
|
|
23
|
-
export const DEFAULT_ALIASES = Object.freeze({
|
|
24
|
-
// file ops
|
|
25
|
-
read: 'read_file',
|
|
26
|
-
edit: 'edit_file',
|
|
27
|
-
write: 'write_file',
|
|
28
|
-
rm: 'delete_file',
|
|
29
|
-
mv: 'move_file',
|
|
30
|
-
cat: 'read_file',
|
|
31
|
-
// shell
|
|
32
|
-
bash: 'shell_exec',
|
|
33
|
-
shell: 'shell_exec',
|
|
34
|
-
sh: 'shell_exec',
|
|
35
|
-
// test/build
|
|
36
|
-
test: 'vitest_run',
|
|
37
|
-
vitest: 'vitest_run',
|
|
38
|
-
typecheck: 'tsc_check',
|
|
39
|
-
tsc: 'tsc_check',
|
|
40
|
-
// git
|
|
41
|
-
log: 'git_log',
|
|
42
|
-
show: 'git_show',
|
|
43
|
-
diff: 'git_diff',
|
|
44
|
-
commit: 'git_commit',
|
|
45
|
-
push: 'git_push',
|
|
46
|
-
branch: 'git_branch',
|
|
47
|
-
checkout: 'git_branch',
|
|
48
|
-
stash: 'git_stash',
|
|
49
|
-
// task
|
|
50
|
-
todo_write: 'create_task',
|
|
51
|
-
todowrite: 'create_task',
|
|
52
|
-
task: 'create_task',
|
|
53
|
-
});
|
|
54
|
-
export class ToolRegistry {
|
|
55
|
-
tools = new Map();
|
|
56
|
-
aliases;
|
|
57
|
-
constructor(aliases = DEFAULT_ALIASES) {
|
|
58
|
-
this.aliases = new Map();
|
|
59
|
-
for (const [k, v] of Object.entries(aliases)) {
|
|
60
|
-
this.aliases.set(k.toLowerCase(), v);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
/** 注册一个 tool (alias 字典不动) */
|
|
64
|
-
register(tool) {
|
|
65
|
-
if (!tool.name)
|
|
66
|
-
throw new Error('ToolRegistry.register: tool.name 必填');
|
|
67
|
-
this.tools.set(tool.name, tool);
|
|
68
|
-
}
|
|
69
|
-
/** 批量注册 */
|
|
70
|
-
registerAll(tools) {
|
|
71
|
-
for (const t of tools)
|
|
72
|
-
this.register(t);
|
|
73
|
-
}
|
|
74
|
-
/** 反注册 — 名字 + 对应 alias (如果有指向它) 全部清掉. */
|
|
75
|
-
unregister(name) {
|
|
76
|
-
const existed = this.tools.delete(name);
|
|
77
|
-
// 反向清 alias
|
|
78
|
-
for (const [alias, target] of this.aliases.entries()) {
|
|
79
|
-
if (target === name)
|
|
80
|
-
this.aliases.delete(alias);
|
|
81
|
-
}
|
|
82
|
-
return existed;
|
|
83
|
-
}
|
|
84
|
-
/** 查 tool (只查主名, alias 走 resolve). */
|
|
85
|
-
get(name) {
|
|
86
|
-
return this.tools.get(name);
|
|
87
|
-
}
|
|
88
|
-
/** 是否注册 (主名 + alias 命中都算). */
|
|
89
|
-
has(name) {
|
|
90
|
-
if (this.tools.has(name))
|
|
91
|
-
return true;
|
|
92
|
-
const aliased = this.aliases.get(name.toLowerCase());
|
|
93
|
-
return aliased !== undefined && this.tools.has(aliased);
|
|
94
|
-
}
|
|
95
|
-
/** 把 LLM 输出的名字 (可能是 alias / 大小写不一致) 解析为标准主名. */
|
|
96
|
-
resolve(name) {
|
|
97
|
-
if (this.tools.has(name))
|
|
98
|
-
return name;
|
|
99
|
-
const lower = name.toLowerCase();
|
|
100
|
-
const aliased = this.aliases.get(lower);
|
|
101
|
-
if (aliased && this.tools.has(aliased))
|
|
102
|
-
return aliased;
|
|
103
|
-
if (this.tools.has(lower))
|
|
104
|
-
return lower;
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
/** 列出所有主名. 顺序按注册时间. */
|
|
108
|
-
list() {
|
|
109
|
-
return Array.from(this.tools.keys());
|
|
110
|
-
}
|
|
111
|
-
/** 当前 alias 表快照 (调试用). */
|
|
112
|
-
get aliasMap() {
|
|
113
|
-
const out = {};
|
|
114
|
-
for (const [k, v] of this.aliases.entries())
|
|
115
|
-
out[k] = v;
|
|
116
|
-
return out;
|
|
117
|
-
}
|
|
118
|
-
/** 个数 */
|
|
119
|
-
get size() {
|
|
120
|
-
return this.tools.size;
|
|
121
|
-
}
|
|
122
|
-
/** 清空 */
|
|
123
|
-
clear() {
|
|
124
|
-
this.tools.clear();
|
|
125
|
-
// alias 表保留 (除非调用方显式 clear, alias 表是稳定的)
|
|
126
|
-
}
|
|
127
|
-
/** 调一个 tool, 名字可能是 alias. throw if 未知. */
|
|
128
|
-
async invoke(name, args) {
|
|
129
|
-
const canonical = this.resolve(name);
|
|
130
|
-
if (!canonical) {
|
|
131
|
-
throw new Error(`ToolRegistry.invoke: 未知工具名 ${JSON.stringify(name)}`);
|
|
132
|
-
}
|
|
133
|
-
const tool = this.tools.get(canonical);
|
|
134
|
-
return tool.execute(args);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* chat-archiver.ts — 按 peer 月度归档聊天记录 + 接 memory 摘要
|
|
3
|
-
*
|
|
4
|
-
* 设计目的 (2026-07-05):
|
|
5
|
-
* 解决"对方不在线也能看到历史"的核心问题.
|
|
6
|
-
* sessions/cache/<channelId>.json 单文件无限增长, 超过 50MB 直接拒绝加载.
|
|
7
|
-
*
|
|
8
|
-
* 新方案:
|
|
9
|
-
* - 每次 /message 处理后, 同时写到 3 个地方:
|
|
10
|
-
* ① sessions/cache/<channelId>.json (原有, 最近窗口用)
|
|
11
|
-
* ② ~/.bolloon/peers/<pk>/chat-<YYYY-MM>.md (按 peer 月度归档)
|
|
12
|
-
* ③ ~/.bolloon/memory/<agentId>/peers/<pk>/<YYYY-MM>.summary.md (月度摘要)
|
|
13
|
-
*
|
|
14
|
-
* 触发时机:
|
|
15
|
-
* - appendChatArchive(): 每次保存远端/本地 user + ai 消息后立即调用 (高频)
|
|
16
|
-
* - compressMonthlyArchive(): 月底 / 90 天滚动 / 显式调用 (低频, 调 LLM 摘要)
|
|
17
|
-
*
|
|
18
|
-
* peer 维度 而不是 channel 维度: 同一个远端节点可能跨多个 channel 协作,
|
|
19
|
-
* 按 peer 归档才能完整还原"与对方的对话历史".
|
|
20
|
-
*/
|
|
21
|
-
import * as fs from 'fs/promises';
|
|
22
|
-
import * as fsSync from 'fs';
|
|
23
|
-
import * as path from 'path';
|
|
24
|
-
import * as os from 'os';
|
|
25
|
-
import * as peerFs from '../network/peer-fs.js';
|
|
26
|
-
// ============== 路径 ==============
|
|
27
|
-
const HOME = process.env.BOLLOON_HOME || path.join(os.homedir(), '.bolloon');
|
|
28
|
-
export function getPeerSummaryDir(agentId, publicKey) {
|
|
29
|
-
const safeAgent = sanitizeId(agentId);
|
|
30
|
-
const safePeer = sanitizeId(publicKey);
|
|
31
|
-
return path.join(HOME, 'memory', safeAgent, 'peers', safePeer);
|
|
32
|
-
}
|
|
33
|
-
export function getPeerSummaryPath(agentId, publicKey, yearMonth) {
|
|
34
|
-
return path.join(getPeerSummaryDir(agentId, publicKey), `${yearMonth}.summary.md`);
|
|
35
|
-
}
|
|
36
|
-
export function getPeerSummaryCursorPath(agentId, publicKey, yearMonth) {
|
|
37
|
-
return path.join(getPeerSummaryDir(agentId, publicKey), `${yearMonth}.cursor`);
|
|
38
|
-
}
|
|
39
|
-
function sanitizeId(s) {
|
|
40
|
-
return s.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
41
|
-
}
|
|
42
|
-
// ============== appendChatArchive — 高频, 每次 message 都调 ==============
|
|
43
|
-
/**
|
|
44
|
-
* 追加一条 chat entry 到 peer 的月度归档.
|
|
45
|
-
* 不依赖 LLM, 纯文件 IO; 失败静默不阻塞主对话.
|
|
46
|
-
*/
|
|
47
|
-
export async function appendChatArchive(opts) {
|
|
48
|
-
try {
|
|
49
|
-
await peerFs.appendChat(opts.publicKey, opts.entry);
|
|
50
|
-
return { ok: true, path: peerFs.getPeerChatPath(opts.publicKey) };
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
return { ok: false, error: err?.message || String(err) };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// ============== compressMonthlyArchive — 低频, 月底/显式调 ==============
|
|
57
|
-
/**
|
|
58
|
-
* 读取月度 markdown 归档, 调 LLM 摘要, append 写到 memory.
|
|
59
|
-
* 用 cursor 跟踪已摘要到第几行, 增量压缩.
|
|
60
|
-
*/
|
|
61
|
-
async function readArchiveLines(publicKey, yearMonth) {
|
|
62
|
-
const file = peerFs.getPeerChatPath(publicKey, yearMonth);
|
|
63
|
-
try {
|
|
64
|
-
const raw = await fs.readFile(file, 'utf-8');
|
|
65
|
-
return raw.split('\n');
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async function readSummaryCursor(cursorPath) {
|
|
72
|
-
try {
|
|
73
|
-
const raw = await fs.readFile(cursorPath, 'utf-8');
|
|
74
|
-
const n = parseInt(raw.trim(), 10);
|
|
75
|
-
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return 0;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function writeSummaryCursor(cursorPath, value) {
|
|
82
|
-
await fs.mkdir(path.dirname(cursorPath), { recursive: true });
|
|
83
|
-
await fs.writeFile(cursorPath, String(value), 'utf-8');
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* 尝试调 LLM 生成月度摘要. 失败 → 抛错, 由 caller fallback 到模板.
|
|
87
|
-
*/
|
|
88
|
-
async function tryMonthlyLlm(systemPrompt, userPrompt) {
|
|
89
|
-
try {
|
|
90
|
-
const piAi = await import('../llm/pi-ai.js');
|
|
91
|
-
const generateText = piAi.generateText;
|
|
92
|
-
if (typeof generateText !== 'function')
|
|
93
|
-
throw new Error('generateText not exported');
|
|
94
|
-
const result = await generateText({
|
|
95
|
-
messages: [
|
|
96
|
-
{ role: 'system', content: systemPrompt },
|
|
97
|
-
{ role: 'user', content: userPrompt },
|
|
98
|
-
],
|
|
99
|
-
temperature: 0.2,
|
|
100
|
-
maxTokens: 1000,
|
|
101
|
-
});
|
|
102
|
-
const text = result?.reply || result?.text || '';
|
|
103
|
-
if (typeof text !== 'string' || text.length < 20)
|
|
104
|
-
throw new Error('LLM returned too-short text');
|
|
105
|
-
return text;
|
|
106
|
-
}
|
|
107
|
-
catch (e) {
|
|
108
|
-
throw new Error(`LLM call failed: ${e?.message || String(e)}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* 模板 fallback: 简单统计 user/ai 数量, 列前 5 条关键内容.
|
|
113
|
-
*/
|
|
114
|
-
function templateMonthlySummary(opts) {
|
|
115
|
-
const user = opts.entries.filter(e => e.msgType === 'user' || e.source === 'local' || e.source === 'remote');
|
|
116
|
-
const ai = opts.entries.filter(e => e.msgType === 'ai' || e.source?.startsWith('ai-mention'));
|
|
117
|
-
const channelSet = new Set(opts.entries.map(e => e.channelName).filter(Boolean));
|
|
118
|
-
const sampleUser = user.slice(0, 5).map(e => ` - [${e.ts}] ${e.text.slice(0, 80)}`).join('\n');
|
|
119
|
-
const sampleAi = ai.slice(0, 5).map(e => ` - [${e.ts}] ${e.text.slice(0, 80)}`).join('\n');
|
|
120
|
-
return `# 月度对话摘要 — ${opts.yearMonth} (peer ${opts.publicKey.slice(0, 12)}…)
|
|
121
|
-
|
|
122
|
-
时间: ${opts.timestamp}
|
|
123
|
-
对话轮次: ${opts.entries.length} (user=${user.length}, ai=${ai.length})
|
|
124
|
-
涉及 channel: ${Array.from(channelSet).slice(0, 10).join(', ') || '(无)'}
|
|
125
|
-
|
|
126
|
-
## 用户关键提问
|
|
127
|
-
${sampleUser || ' (无)'}
|
|
128
|
-
|
|
129
|
-
## AI 关键回答
|
|
130
|
-
${sampleAi || ' (无)'}
|
|
131
|
-
|
|
132
|
-
## 备注
|
|
133
|
-
- 此摘要由模板生成 (LLM 调用失败 fallback)
|
|
134
|
-
`;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* 解析 markdown 归档为 entries (用 "### " 切分)
|
|
138
|
-
*/
|
|
139
|
-
function parseArchiveEntries(lines) {
|
|
140
|
-
const out = [];
|
|
141
|
-
let i = 0;
|
|
142
|
-
while (i < lines.length) {
|
|
143
|
-
if (lines[i].startsWith('### ')) {
|
|
144
|
-
const header = lines[i];
|
|
145
|
-
// 解析 "### 2026-07-05 10:30:45 UTC [channelName] — source"
|
|
146
|
-
const m = header.match(/^### (.+?)\s*(?:\[(.+?)\])?\s*—\s*(\S+?)(?:\s+\(.+?\))?$/);
|
|
147
|
-
if (m) {
|
|
148
|
-
const ts = m[1].replace(/\s+UTC$/, '').trim();
|
|
149
|
-
const channelName = m[2];
|
|
150
|
-
const source = m[3];
|
|
151
|
-
// 找下一段 "### " 或 "---" 之间的内容
|
|
152
|
-
let j = i + 1;
|
|
153
|
-
const bodyLines = [];
|
|
154
|
-
while (j < lines.length && !lines[j].startsWith('### ') && !lines[j].startsWith('---')) {
|
|
155
|
-
bodyLines.push(lines[j]);
|
|
156
|
-
j++;
|
|
157
|
-
}
|
|
158
|
-
const text = bodyLines.join('\n').trim();
|
|
159
|
-
out.push({
|
|
160
|
-
startLine: i,
|
|
161
|
-
entry: {
|
|
162
|
-
ts: ts.includes('T') ? ts : ts.replace(' ', 'T') + 'Z',
|
|
163
|
-
source,
|
|
164
|
-
channelName,
|
|
165
|
-
text,
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
i = j;
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
i++;
|
|
173
|
-
}
|
|
174
|
-
return out;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* 主入口: 压缩月度归档到 memory.
|
|
178
|
-
*/
|
|
179
|
-
export async function compressMonthlyArchive(opts) {
|
|
180
|
-
const agentId = sanitizeId(opts.agentId);
|
|
181
|
-
const publicKey = opts.publicKey;
|
|
182
|
-
const yearMonth = opts.yearMonth || peerFs.currentYearMonth();
|
|
183
|
-
const home = opts.home || HOME;
|
|
184
|
-
const summaryPath = getPeerSummaryPath(agentId, publicKey, yearMonth);
|
|
185
|
-
const cursorPath = getPeerSummaryCursorPath(agentId, publicKey, yearMonth);
|
|
186
|
-
const archivePath = peerFs.getPeerChatPath(publicKey, yearMonth);
|
|
187
|
-
// 读归档
|
|
188
|
-
const lines = await readArchiveLines(publicKey, yearMonth);
|
|
189
|
-
if (lines.length === 0) {
|
|
190
|
-
return { summaryPath, cursorPath, entriesCount: 0, bytesWritten: 0, skipped: 'no-archive' };
|
|
191
|
-
}
|
|
192
|
-
// 解析 entries
|
|
193
|
-
const allEntries = parseArchiveEntries(lines);
|
|
194
|
-
const cursor = await readSummaryCursor(cursorPath);
|
|
195
|
-
const newEntries = allEntries.slice(cursor);
|
|
196
|
-
const minNew = opts.minNewEntries ?? 20;
|
|
197
|
-
if (newEntries.length < minNew) {
|
|
198
|
-
return { summaryPath, cursorPath, entriesCount: newEntries.length, bytesWritten: 0, skipped: 'too-few-entries' };
|
|
199
|
-
}
|
|
200
|
-
const timestamp = new Date().toISOString();
|
|
201
|
-
let summaryBody;
|
|
202
|
-
try {
|
|
203
|
-
const sysPrompt = '你是 bolloon 月度记忆压缩助手. 输入是按 peer 月度归档的聊天记录 markdown, 输出 400-800 字中文摘要, 包含: 1) 关键合作主题; 2) 共同完成的成果; 3) 待跟进事项; 4) 对方能力偏好. 不要寒暄, 不复述已知.';
|
|
204
|
-
const recentSnippet = newEntries.slice(-30).map(e => {
|
|
205
|
-
const ts = e.entry.ts;
|
|
206
|
-
const chan = e.entry.channelName ? `[${e.entry.channelName}]` : '';
|
|
207
|
-
return `[${ts}]${chan} ${e.entry.source}: ${e.entry.text}`;
|
|
208
|
-
}).join('\n').slice(0, 8000);
|
|
209
|
-
const userPrompt = `Peer: ${publicKey}\n月份: ${yearMonth}\n时间: ${timestamp}\n新增条数: ${newEntries.length}\n\n最近对话:\n${recentSnippet}`;
|
|
210
|
-
summaryBody = await tryMonthlyLlm(sysPrompt, userPrompt);
|
|
211
|
-
}
|
|
212
|
-
catch (e) {
|
|
213
|
-
summaryBody = templateMonthlySummary({
|
|
214
|
-
publicKey,
|
|
215
|
-
yearMonth,
|
|
216
|
-
entries: newEntries.map(e => e.entry),
|
|
217
|
-
timestamp,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
const block = `\n\n---\n\n## 增量摘要 @ ${timestamp} (${newEntries.length} entries)\n\n${summaryBody.trim()}\n`;
|
|
221
|
-
await fs.mkdir(path.dirname(summaryPath), { recursive: true });
|
|
222
|
-
await fs.appendFile(summaryPath, block, 'utf-8');
|
|
223
|
-
await writeSummaryCursor(cursorPath, allEntries.length);
|
|
224
|
-
return {
|
|
225
|
-
summaryPath,
|
|
226
|
-
cursorPath,
|
|
227
|
-
entriesCount: newEntries.length,
|
|
228
|
-
bytesWritten: Buffer.byteLength(block, 'utf-8'),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* 把 session.json 中的所有消息按 peer 切分, 一次性 archive 到各 peer 月度文件.
|
|
233
|
-
* 用于: ① 启动时迁移已有 session 历史到 peer archive; ② 兜底补档.
|
|
234
|
-
*/
|
|
235
|
-
export async function archiveSessionMessagesForPeer(opts) {
|
|
236
|
-
let written = 0;
|
|
237
|
-
let errors = 0;
|
|
238
|
-
for (const m of opts.messages) {
|
|
239
|
-
const ts = m.timestamp || new Date().toISOString();
|
|
240
|
-
const entry = {
|
|
241
|
-
ts,
|
|
242
|
-
source: m.source || (m.type === 'user' ? 'remote' : 'ai-mention'),
|
|
243
|
-
channelId: m.channelId,
|
|
244
|
-
channelName: m.channelName,
|
|
245
|
-
text: m.content,
|
|
246
|
-
fromPublicKey: m.fromPublicKey || opts.publicKey,
|
|
247
|
-
msgType: m.type,
|
|
248
|
-
};
|
|
249
|
-
const r = await appendChatArchive({ publicKey: opts.publicKey, entry });
|
|
250
|
-
if (r.ok)
|
|
251
|
-
written++;
|
|
252
|
-
else
|
|
253
|
-
errors++;
|
|
254
|
-
}
|
|
255
|
-
return { written, errors };
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* 列出指定 peer 所有可用的月度摘要 (按月份倒序)
|
|
259
|
-
*/
|
|
260
|
-
export async function listPeerSummaries(agentId, publicKey) {
|
|
261
|
-
const dir = getPeerSummaryDir(agentId, publicKey);
|
|
262
|
-
try {
|
|
263
|
-
const entries = await fs.readdir(dir);
|
|
264
|
-
return entries
|
|
265
|
-
.filter(e => /^\d{4}-\d{2}\.summary\.md$/.test(e))
|
|
266
|
-
.map(e => ({
|
|
267
|
-
yearMonth: e.replace(/\.summary\.md$/, ''),
|
|
268
|
-
path: path.join(dir, e),
|
|
269
|
-
size: fsSync.statSync(path.join(dir, e)).size,
|
|
270
|
-
}))
|
|
271
|
-
.sort((a, b) => b.yearMonth.localeCompare(a.yearMonth));
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
return [];
|
|
275
|
-
}
|
|
276
|
-
}
|