@bolloon/bolloon-agent 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +128 -111
  2. package/dist/agents/gateway-group.js +231 -6
  3. package/dist/agents/group-autopilot.js +256 -0
  4. package/dist/agents/pi-sdk-tools.js +302 -0
  5. package/dist/agents/pi-sdk.js +89 -140
  6. package/dist/agents/task-group.js +21 -4
  7. package/dist/cli/log-gate.js +68 -4
  8. package/dist/cli/mention-data.js +1 -1
  9. package/dist/cli/model-selector.js +84 -104
  10. package/dist/cli/setup-wizard.js +2 -10
  11. package/dist/cli/tui-select.js +99 -10
  12. package/dist/cli-entry.js +1 -9
  13. package/dist/electron-build/agents/gateway-group.js +814 -0
  14. package/dist/electron-build/agents/gateway-group.js.map +1 -0
  15. package/dist/electron-build/agents/group-autopilot.js +302 -0
  16. package/dist/electron-build/agents/group-autopilot.js.map +1 -0
  17. package/dist/electron-build/agents/pi-sdk-tools.js +302 -0
  18. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  19. package/dist/electron-build/agents/pi-sdk.js +88 -139
  20. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  21. package/dist/electron-build/agents/task-group.js +601 -0
  22. package/dist/electron-build/agents/task-group.js.map +1 -0
  23. package/dist/electron-build/agents/task-public-text.js +97 -0
  24. package/dist/electron-build/agents/task-public-text.js.map +1 -0
  25. package/dist/electron-build/efficode/compress.js +86 -0
  26. package/dist/electron-build/efficode/compress.js.map +1 -0
  27. package/dist/electron-build/efficode/crc32.js +44 -0
  28. package/dist/electron-build/efficode/crc32.js.map +1 -0
  29. package/dist/electron-build/efficode/did.js +74 -0
  30. package/dist/electron-build/efficode/did.js.map +1 -0
  31. package/dist/electron-build/efficode/lz77.js +175 -0
  32. package/dist/electron-build/efficode/lz77.js.map +1 -0
  33. package/dist/electron-build/efficode/negotiate.js +233 -0
  34. package/dist/electron-build/efficode/negotiate.js.map +1 -0
  35. package/dist/electron-build/efficode/ops.js +96 -0
  36. package/dist/electron-build/efficode/ops.js.map +1 -0
  37. package/dist/electron-build/efficode/packet.js +279 -0
  38. package/dist/electron-build/efficode/packet.js.map +1 -0
  39. package/dist/electron-build/efficode/types.js +50 -0
  40. package/dist/electron-build/efficode/types.js.map +1 -0
  41. package/dist/electron-build/kernel/code-write-gate.js +55 -0
  42. package/dist/electron-build/kernel/code-write-gate.js.map +1 -0
  43. package/dist/electron-build/kernel/run-lifecycle.js +23 -0
  44. package/dist/electron-build/kernel/run-lifecycle.js.map +1 -1
  45. package/dist/electron-build/kernel/run-resume.js +62 -0
  46. package/dist/electron-build/kernel/run-resume.js.map +1 -0
  47. package/dist/electron-build/kernel/session-lifecycle.js +223 -0
  48. package/dist/electron-build/kernel/session-lifecycle.js.map +1 -0
  49. package/dist/electron-build/kernel/turn-selfcheck.js +65 -0
  50. package/dist/electron-build/kernel/turn-selfcheck.js.map +1 -0
  51. package/dist/electron-build/llm/config-store.js +2 -2
  52. package/dist/electron-build/llm/config-store.js.map +1 -1
  53. package/dist/electron-build/llm/model-selection.js +16 -2
  54. package/dist/electron-build/llm/model-selection.js.map +1 -1
  55. package/dist/electron-build/llm/pi-ai.js +2 -2
  56. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  57. package/dist/electron-build/orbitdb/cid-database.js +98 -19
  58. package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
  59. package/dist/electron-build/orbitdb/group-access.js +399 -0
  60. package/dist/electron-build/orbitdb/group-access.js.map +1 -0
  61. package/dist/index.js +143 -10
  62. package/dist/ios/bolloon-web.json +3 -3
  63. package/dist/ios/sw.js +1 -1
  64. package/dist/kernel/code-write-gate.js +47 -0
  65. package/dist/kernel/gate-scan.js +5 -1
  66. package/dist/kernel/plan-harness.js +3 -2
  67. package/dist/kernel/roster.js +6 -6
  68. package/dist/kernel/run-lifecycle.js +21 -0
  69. package/dist/kernel/run-resume.js +58 -0
  70. package/dist/kernel/session-lifecycle.js +210 -0
  71. package/dist/kernel/turn-selfcheck.js +60 -0
  72. package/dist/llm/config-store.js +2 -2
  73. package/dist/llm/model-selection.js +16 -2
  74. package/dist/llm/pi-ai.js +2 -2
  75. package/dist/orbitdb/cid-database.js +95 -19
  76. package/dist/web/bolloon-web.json +3 -3
  77. package/dist/web/sw.js +1 -1
  78. package/package.json +1 -1
package/README.md CHANGED
@@ -1,180 +1,197 @@
1
- # Bolloon — P2P AI 智能体平台
1
+ # Bolloon
2
2
 
3
- Bolloon 是一个本地优先、P2P 协作的 AI 智能体平台。它在你的设备上运行,通过 DHT 网络发现并连接其他节点。
3
+ **一万个智能体,为你工作。**
4
4
 
5
- > 中文 · [English](#english)
5
+ Bolloon 是一台跑在你自己设备上的智能体。它有自己的身份(DID)、自己的记忆、自己的技能库,
6
+ 并且能在点对点网络里**找到别的智能体、把自己的活委派出去、按结果结算**。
7
+
8
+ 不是「又一个聊天框」,而是**一个可以互相接手工作的智能体网络**。
9
+
10
+ > 官网 · https://bolloon.cn | 安装 · `npm i -g @bolloon/bolloon-agent`([npm 上的最新版](https://www.npmjs.com/package/@bolloon/bolloon-agent))
6
11
 
7
12
  ---
8
13
 
9
- ## 快速安装
14
+ ## 为什么
15
+
16
+ 今天你和 AI 的关系是「一个人问,一个模型答」。可真正的活儿往往需要好几个角色:
17
+ 一个人查资料、一个人写、一个人验、一个人盯着别搞坏。
18
+
19
+ Bolloon 把这件事变成网络问题:**每个智能体都有身份、技能和账本**,于是它可以被别的智能体
20
+ 发现、被委派、被结算——你只需要说清目标。
21
+
22
+ 这也是我们的方向:让 C 端用户在自己的电脑上,拥有一条**由很多智能体组成的生产线**,
23
+ 而不只是一个对话框。我自己每天用它做的事是**可控核聚变的研究**——线圈设计、仿真、验证、
24
+ 论文形式化,由不同智能体分头接手,我只看结论。
25
+
26
+ ---
10
27
 
11
- ### macOS / Linux
28
+ ## 现在就能做的四件事
29
+
30
+ 每一条都能在终端里当场验证,没有一条是愿景:
31
+
32
+ **1. 让智能体自己干活,你只看结论**
33
+ 它有一百多个工具(读写文件、跑命令、搜代码、连钱包、发消息),会自己拆步骤、自己判断卡在哪、
34
+ 失败了自己换法;每轮结束**必复盘**,把教训写进技能库和判断力库。
35
+
36
+ **2. 把它接进网络,让别的智能体接手**
37
+ DID 身份 + 点对点发现(Hyperswarm / iroh)。它可以把编码任务委派给本机的其他智能体 CLI
38
+ (codex / claude-code / opencode …),**起完就走**,结果自己回灌到下一轮。
39
+ 反过来,别的节点也可以按能力清单找到你的智能体并派活。
40
+
41
+ **3. 技能是可以积累、可以卖的资产**
42
+ 1300+ 个技能在本地注册表里,可以读、可以改、可以分享。每一次变更都进**账本**
43
+ (内容寻址备份 + 单条回滚),自审只能治理自己造出来的技能——用户点名要的归用户。
44
+
45
+ **4. 干完的活能被核对**
46
+ 任务、证据、结算都在链上有记录(Base 主网)。终端里的「链上活动」是可点开区块浏览器的**真交易**,
47
+ 不是漂亮数字。
48
+
49
+ ---
50
+
51
+ ## 快速开始
12
52
 
13
53
  ```bash
14
- # 方式一:npm(需 Node.js ≥ 18)
15
54
  npm install -g @bolloon/bolloon-agent
16
-
17
- # 方式二:一键脚本
18
- curl -fsSL https://raw.githubusercontent.com/logos-42/bolloon/master/scripts/install.sh | sh
55
+ bolloon
19
56
  ```
20
57
 
21
- ### Windows
58
+ 首次启动会引导你填一个模型供应方(OpenAI / DeepSeek / Anthropic / 自定义)。
59
+ Node.js ≥ 18。Windows 用 PowerShell 装同一个包即可。
22
60
 
23
- ```powershell
24
- # 方式一:npm(需 Node.js ≥ 18)
25
- npm install -g @bolloon/bolloon-agent
61
+ 其他入口:
26
62
 
27
- # 方式二:一键脚本
28
- # 以管理员身份运行 PowerShell:
29
- iwr -useb https://raw.githubusercontent.com/logos-42/bolloon/master/scripts/install.ps1 | iex
63
+ ```bash
64
+ bolloon --web # 浏览器界面 (http://127.0.0.1:54188)
65
+ bolloon --help # 全部命令
30
66
  ```
31
67
 
32
- > **Node.js 安装**:如果尚未安装,访问 [nodejs.org](https://nodejs.org) 下载 LTS 版本,或使用包管理器:
33
- > - macOS: `brew install node`
34
- > - Windows: `winget install -e --id OpenJS.NodeJS.LTS`
35
- > - Linux: `sudo apt install -y nodejs npm`
68
+ Docker(不想在本机装环境时):
69
+
70
+ ```bash
71
+ docker run -d --name bolloon -p 127.0.0.1:54188:54188 \
72
+ -v bolloon-data:/home/bolloon/.bolloon bolloon-agent:local --web
73
+ ```
36
74
 
37
75
  ---
38
76
 
39
- ## 快速启动
77
+ ## 它长什么样
40
78
 
41
- ```bash
42
- bolloon # CLI 交互模式(默认)
43
- bolloon --web # Web UI 模式(浏览器)
44
- bolloon --cli # 强制 CLI 模式
45
- bolloon --help # 查看所有命令
46
- ```
79
+ **终端**:正文是**一条一条的消息**,工具调用显示成「这一步在做什么」(`🔧 查看 git 状态 · git_status 327ms`),
80
+ 不刷思维流;任务结束后会看到 `📚 复盘: …`,告诉你它学到了什么。
47
81
 
48
- 首次启动会自动配置 LLM(需要 `OPENAI_API_KEY` 或 `DEEPSEEK_API_KEY` 环境变量)。
82
+ **网页**:`bolloon --web` 打开本地面板——链上活动表格、待接单任务、去中心化群聊,
83
+ 以及每个数字的**来源与口径**(见官网「来源」一节)。
49
84
 
50
85
  ---
51
86
 
52
- ## 功能概览
87
+ ## 状态与边界
88
+
89
+ **已经稳定**:CLI 交互 · 技能库与提炼 · 工具调用与类型门 · 后台进程与服务管理 · 链上任务与结算 · P2P 发现与委派。
53
90
 
54
- | 功能 | 描述 |
55
- |------|------|
56
- | 🤖 AI 对话 | 本地运行的智能体,支持多轮对话 |
57
- | 🌐 P2P 网络 | 自动发现并连接其他 Bolloon 节点 |
58
- | 📄 文档处理 | 接收和管理文档,支持 DID 签名验证 |
59
- | 🔧 工具调用 | shell 命令、文件操作、代码分析等 |
60
- | 🔄 自我改进 | 自动检查更新、质量门控 |
91
+ **还在早期**:多智能体之间的**经济闭环**(谁付钱、怎么分账、争议怎么裁)只有最基础的一层;
92
+ 「一万个智能体」是方向,不是今天打开就能看到的数量。
93
+
94
+ **我们不说的话**:不承诺「全自动无需照看」;链上记录不可撤销;模型能力是上限,
95
+ 智能体不会超过它用的模型。
61
96
 
62
97
  ---
63
98
 
64
- ## 项目结构
99
+ ## 深入
65
100
 
66
- ```
67
- bolloon/
68
- ├── src/
69
- │ ├── agents/ # 智能体核心(pi-sdk、工具、session)
70
- │ ├── cli/ # CLI 界面(Ink TUI 渲染引擎)
71
- │ ├── network/ # P2P 网络(Hyperswarm / DIAP)
72
- │ ├── security/ # 安全层(工具闸门、守卫)
73
- │ ├── llm/ # LLM 接入层
74
- │ └── web/ # Web UI
75
- ├── dist/ # 编译输出
76
- ├── docs/ # 文档
77
- └── scripts/ # 构建和安装脚本
78
- ```
101
+ | 想做什么 | 去哪里 |
102
+ |---|---|
103
+ | 看产品与截图 | https://bolloon.cn |
104
+ | 读安装/文档 | https://bolloon.cn/docs |
105
+ | 让别的 agent 接入你的智能体 | https://bolloon.cn/gateway |
106
+ | Docker 部署细节 | [docs/wiki/docker-deployment.md](./docs/wiki/docker-deployment.md) |
107
+ | 项目当前状态(内部台账) | [docs/wiki/current-status.md](./docs/wiki/current-status.md) |
79
108
 
80
109
  ---
81
110
 
82
111
  ## 从源码构建
83
112
 
84
113
  ```bash
85
- git clone https://github.com/logos-42/bolloon.git
86
- cd bolloon
114
+ git clone https://github.com/logos-42/bolloon.git && cd bolloon
87
115
  npm install
88
116
  npm run build:all
89
117
  npm start
90
118
  ```
91
119
 
120
+ 跑测试:`npx vitest run`(当前 304 个文件 / 4609 个用例)。
121
+ 改了 TypeScript 后:`npx tsc --noEmit`。
122
+
92
123
  ---
93
124
 
94
- ## Docker
125
+ ## 开源协议
95
126
 
96
- 不想在本机装 Node 与依赖, 可以直接跑容器 (多阶段镜像 · 非 root 用户 uid 1001 · 状态全部落在卷里):
127
+ MIT。见 [LICENSE](./LICENSE)。
97
128
 
98
- ```bash
99
- docker build -t bolloon-agent:local .
100
- docker run -d --name bolloon-agent \
101
- -p 127.0.0.1:54188:54188 \
102
- -v bolloon-data:/home/bolloon/.bolloon \
103
- bolloon-agent:local --web
104
- # → http://127.0.0.1:54188
105
-
106
- docker compose up -d # 同上的 compose 写法
107
- docker compose run --rm bolloon-cli --version # 一次性 CLI (不用起服务)
108
- ```
129
+ ---
109
130
 
110
- - **密钥别写进 compose**: 复制 `.env.example` → `.env.docker` (已在 `.gitignore`), 或者把本机
111
- 0600 的 `llm-config.json` **只读挂载**进容器 (更安全: 不进环境变量、不进 `docker inspect`)。
112
- - **数据卷里有身份私钥与 API key 配置** (`/home/bolloon/.bolloon`): 别导出、别提交、别 `docker commit`。
113
- - 端口只认环境变量 `PORT` (默认 `54188`); `--port` 参数无效。
131
+ <a name="english"></a>
114
132
 
115
- 完整说明 (镜像怎么搭的 · 卷与 uid 权限 · HEALTHCHECK 探什么/不探什么 · 常见问题) 见
116
- [docs/wiki/docker-deployment.md](./docs/wiki/docker-deployment.md)。
133
+ # Bolloon (English)
117
134
 
118
- ---
135
+ **Ten thousand agents, working for you.**
119
136
 
120
- ## 环境变量
137
+ Bolloon is an agent that runs on your own machine. It has its own identity (DID), its own memory and
138
+ its own library of skills, and it can **find other agents on a peer-to-peer network, hand work over,
139
+ and settle by result**.
121
140
 
122
- | 变量 | 说明 |
123
- |------|------|
124
- | `OPENAI_API_KEY` | OpenAI 提供商(可选) |
125
- | `DEEPSEEK_API_KEY` | DeepSeek 提供商(可选) |
126
- | `ANTHROPIC_API_KEY` | Anthropic 提供商(可选) |
127
- | `BOLLOON_LLM_PROVIDER` | 指定 LLM 提供商 |
141
+ Not "yet another chat box" — **a network of agents that can take over each other's work.**
128
142
 
129
- ---
143
+ > Website · https://bolloon.cn | Install · `npm i -g @bolloon/bolloon-agent`([latest on npm](https://www.npmjs.com/package/@bolloon/bolloon-agent))
130
144
 
131
- ## 开源协议
145
+ ## Why
132
146
 
133
- 本项目基于 [MIT 开源协议](./LICENSE) 发布。你可以自由使用、修改、分发本项目,但需保留版权声明和许可声明。详见 [LICENSE](./LICENSE) 文件。
147
+ Today your relationship with AI is one person asking, one model answering. Real work needs several
148
+ roles: someone researches, someone writes, someone verifies, someone makes sure nothing breaks.
134
149
 
135
- ---
150
+ Bolloon turns that into a network problem. Every agent has an identity, skills and a ledger, so it can
151
+ be discovered, delegated to and paid. You only have to state the goal.
136
152
 
137
- <a name="english"></a>
153
+ That is the direction: a **production line made of many agents**, running on a consumer machine —
154
+ not a single dialogue box. I use it every day for **controlled nuclear fusion** research.
138
155
 
139
- ## English
156
+ ## What works today
140
157
 
141
- **Bolloon** — A local-first, P2P-collaborative AI agent platform that runs on your device and discovers other nodes through a DHT network.
158
+ **1. The agent works on its own, you read the conclusion.** 125 tools (files, shell, search, wallets,
159
+ messaging), its own step planning and error recovery, and a **review after every task** that writes
160
+ lessons into its skill library.
142
161
 
143
- ### Quick Install
162
+ **2. Plug it into the network and let others take the work.** DID identity plus P2P discovery
163
+ (Hyperswarm / iroh). It can delegate coding tasks to other local agent CLIs and **return immediately**,
164
+ while results flow back into the next turn. Other nodes can find and delegate to your agent too.
144
165
 
145
- **macOS / Linux:**
146
- ```bash
147
- npm install -g @bolloon/bolloon-agent
148
- ```
166
+ **3. Skills are an accumulating, sellable asset.** 1300+ skills in a local registry; every change goes
167
+ into a ledger with content-addressed backups and single-entry rollback.
149
168
 
150
- **Windows (PowerShell):**
151
- ```powershell
152
- npm install -g @bolloon/bolloon-agent
153
- ```
169
+ **4. Finished work can be verified.** Tasks, evidence and settlement are recorded on-chain (Base).
170
+ Transactions in the terminal open in a block explorer — real, not decorative.
154
171
 
155
- ### Quick Start
172
+ ## Quick start
156
173
 
157
174
  ```bash
158
- bolloon # CLI interactive mode
159
- bolloon --web # Web UI mode
160
- bolloon --help # All commands
175
+ npm install -g @bolloon/bolloon-agent
176
+ bolloon
161
177
  ```
162
178
 
163
- Requires Node.js ≥ 18 and an LLM API key (`OPENAI_API_KEY` or `DEEPSEEK_API_KEY`).
179
+ Node.js ≥ 18. First run walks you through picking a model provider.
164
180
 
165
- ### Docker
181
+ ## Status and limits
166
182
 
167
- Run it in a container instead (multi-stage image, non-root user, state in a volume):
183
+ **Stable**: CLI · skills · tool calling · background services · on-chain tasks · P2P discovery and
184
+ delegation. **Early**: the economics between agents (who pays, how it splits, how disputes resolve).
185
+ "Ten thousand agents" is a direction, not a number you will see on day one.
168
186
 
169
- ```bash
170
- docker build -t bolloon-agent:local .
171
- docker run -d --name bolloon-agent -p 127.0.0.1:54188:54188 \
172
- -v bolloon-data:/home/bolloon/.bolloon bolloon-agent:local --web
173
- ```
187
+ **What we will not claim**: fully unattended operation; reversible on-chain records; capability beyond
188
+ the model you plug in.
189
+
190
+ ## Links
174
191
 
175
- Copy `.env.example` to `.env.docker` (gitignored) for keys, or mount your `llm-config.json` read-only
176
- (safer). Full guide: [docs/wiki/docker-deployment.md](./docs/wiki/docker-deployment.md).
192
+ Website https://bolloon.cn · Docs https://bolloon.cn/docs · Gateway https://bolloon.cn/gateway ·
193
+ Docker [docs/wiki/docker-deployment.md](./docs/wiki/docker-deployment.md)
177
194
 
178
- ### License
195
+ ## License
179
196
 
180
- This project is released under the [MIT License](./LICENSE). You are free to use, modify, and distribute this project, provided that you retain the copyright and permission notices. See the [LICENSE](./LICENSE) file for details.
197
+ MIT — see [LICENSE](./LICENSE).
@@ -33,7 +33,7 @@ import { decodeFromPeer, encodeForPeer, recordLangDecision } from '../efficode/n
33
33
  // 白名单里放的是 **OrbitDB 写身份 id** (66 位压缩 secp256k1 公钥 hex), 不是 DID 字符串
34
34
  // (canAppend 只比 identity.id; 放 DID 会永远匹配不上 → 谁都写不了)。
35
35
  // 成员变更 = 一条**双侧 Ed25519 验签**的群事件 (成员自签 + 群主签), 细节见 group-access.ts。
36
- import { applyMembershipEvents, groupAccessOptions, membershipEntryOf, normalizeWriteList, verifyMembershipEvent, GROUP_MEMBERSHIP_KIND, } from '../orbitdb/group-access.js';
36
+ import { aclChangePlan, applyMembershipEvents, groupAccessOptions, membershipEntryOf, normalizeWriteList, verifyMembershipEvent, GROUP_MEMBERSHIP_KIND, } from '../orbitdb/group-access.js';
37
37
  // ============ 依赖注入 (测试用, 避免单测起真实 OrbitDB 节点) ============
38
38
  let _dbOverride = null;
39
39
  /** 测试用: 注入 fake CIDDatabase */
@@ -45,6 +45,20 @@ export function resetGroupState() {
45
45
  onChangeCallbacks.clear();
46
46
  openFailures.clear();
47
47
  }
48
+ /**
49
+ * 关闭群聊使用的 OrbitDB/Helia 资源。
50
+ * 一次性 CLI、验收探针和测试必须在读回后调用它,否则底层 libp2p/Helia 句柄
51
+ * 会继续保持事件循环活跃,表现为“消息已读完但进程不退出”。
52
+ * 测试注入的 fake DB 不关闭,避免破坏测试夹具生命周期。
53
+ */
54
+ export async function closeGroupResources() {
55
+ storeCache.clear();
56
+ onChangeCallbacks.clear();
57
+ openFailures.clear();
58
+ if (_dbOverride)
59
+ return;
60
+ await getCIDDatabase().close();
61
+ }
48
62
  /**
49
63
  * 群 store 打不开 (区块不在本机 / 地址不可解析)。
50
64
  *
@@ -211,7 +225,7 @@ export async function createGroup(name, opts) {
211
225
  ...(gate.members ?? []).map((m) => m.orbitdbId),
212
226
  ];
213
227
  writeList = normalizeWriteList(ids);
214
- gateInfo = { gated: true, ownerDid: gate.owner.did, aclWrite: writeList };
228
+ gateInfo = { gated: true, ownerDid: gate.owner.did, ownerPublicKeyHex: gate.owner.publicKeyHex, aclWrite: writeList };
215
229
  membershipEntries = applied.accepted.map(membershipEntryOf);
216
230
  }
217
231
  else if (opts?.acl === 'open') {
@@ -233,6 +247,8 @@ export async function createGroup(name, opts) {
233
247
  id, name: groupName, address, link,
234
248
  createdAt: new Date().toISOString(),
235
249
  lastSyncAt: new Date().toISOString(),
250
+ // 2026-10-05: 隐私群标记 —— 建群默认隐私 (白名单/独占写); acl=open 才是公开
251
+ privacy: opts?.acl !== 'open',
236
252
  ...gateInfo,
237
253
  };
238
254
  storeCache.set(id, store);
@@ -294,8 +310,7 @@ export async function listGroups() {
294
310
  * 不会因为本机退出而改 (本模块没有"踢人/解散"的权限, 也不假装有)。
295
311
  * 返回 `removed` = 被摘掉的 groupId (按 id 或群名匹配)。
296
312
  *
297
- * 注意: 底层 OrbitDB store 已在进程内打开的**不会**在这里关 (适配层没暴露 close);
298
- * 对 `bolloon task group leave` 这种一次性进程无影响, 长驻进程里它活到进程结束。
313
+ * 注意: leave 只摘本地群列表;需要结束进程资源时调用 `closeGroupResources()`。
299
314
  */
300
315
  export async function leaveGroup(idOrName) {
301
316
  const raw = String(idOrName || '').trim();
@@ -337,6 +352,33 @@ export async function groupMessages(groupId, limit = 50) {
337
352
  // 只透传字符串形态的语言声明; 别的形状一律当"没声明"(不让脏值进到解码分派)
338
353
  if (typeof v.lang === 'string' && v.lang.trim())
339
354
  m.lang = v.lang.trim();
355
+ // 2026-10-02: 新字段一律**先验形状再透传** (脏值当没声明, 不做修补)
356
+ const entryId = String(entry?.hash ?? entry?.id ?? '').trim();
357
+ if (entryId)
358
+ m.id = entryId;
359
+ if (Array.isArray(v.mentions)) {
360
+ const ms = v.mentions.map((x) => String(x ?? '').trim()).filter(Boolean);
361
+ if (ms.length)
362
+ m.mentions = ms;
363
+ }
364
+ if (typeof v.replyTo === 'string' && v.replyTo.trim())
365
+ m.replyTo = v.replyTo.trim();
366
+ if (typeof v.branch === 'string' && v.branch.trim())
367
+ m.branch = v.branch.trim();
368
+ if (typeof v.kind === 'string' && v.kind.trim())
369
+ m.kind = v.kind.trim();
370
+ if (Array.isArray(v.attachments)) {
371
+ const at = v.attachments
372
+ .filter((a) => a && typeof a.cid === 'string' && /^(image|audio|file)$/.test(String(a.kind)))
373
+ .map((a) => ({
374
+ kind: a.kind,
375
+ cid: String(a.cid).trim(),
376
+ ...(typeof a.name === 'string' && a.name.trim() ? { name: a.name.trim().slice(0, 120) } : {}),
377
+ ...(typeof a.bytes === 'number' && Number.isFinite(a.bytes) ? { bytes: a.bytes } : {}),
378
+ }));
379
+ if (at.length)
380
+ m.attachments = at;
381
+ }
340
382
  msgs.push(m);
341
383
  }
342
384
  }
@@ -455,8 +497,38 @@ export async function groupSend(groupId, text, from, opts) {
455
497
  const lang = typeof opts?.lang === 'string' ? opts.lang.trim() : '';
456
498
  if (lang)
457
499
  record.lang = lang;
458
- await store.add(record);
459
- return { ok: true };
500
+ // 2026-10-02: 与 lang 同口径 —— **只有显式给了合法形状才写字段** (不给就不写, 老读者读到的一字不变)
501
+ const mentions = Array.isArray(opts?.mentions)
502
+ ? opts.mentions.map((x) => String(x ?? '').trim()).filter(Boolean).slice(0, 20)
503
+ : [];
504
+ if (mentions.length)
505
+ record.mentions = mentions;
506
+ const replyTo = typeof opts?.replyTo === 'string' ? opts.replyTo.trim() : '';
507
+ if (replyTo)
508
+ record.replyTo = replyTo.slice(0, 200);
509
+ const branch = typeof opts?.branch === 'string' ? opts.branch.trim() : '';
510
+ if (branch)
511
+ record.branch = branch.slice(0, 120);
512
+ const attachments = Array.isArray(opts?.attachments)
513
+ ? opts.attachments
514
+ .filter((a) => a && typeof a.cid === 'string' && a.cid.trim() && /^(image|audio|file)$/.test(String(a.kind)))
515
+ .slice(0, 8)
516
+ .map((a) => ({
517
+ kind: a.kind,
518
+ cid: String(a.cid).trim(),
519
+ ...(a.name ? { name: String(a.name).slice(0, 120) } : {}),
520
+ ...(typeof a.bytes === 'number' && Number.isFinite(a.bytes) ? { bytes: a.bytes } : {}),
521
+ }))
522
+ : [];
523
+ if (attachments.length)
524
+ record.attachments = attachments;
525
+ const kind = typeof opts?.kind === 'string' ? opts.kind.trim() : '';
526
+ if (kind)
527
+ record.kind = kind.slice(0, 40);
528
+ // 2026-10-02: 回传本条消息的 id —— 回复/建分支都要锚点 (拿不到就不给字段, 不假造)
529
+ const entry = (await store.add(record));
530
+ const id = String(entry?.hash ?? entry?.id ?? '').trim();
531
+ return id ? { ok: true, id } : { ok: true };
460
532
  }
461
533
  catch (e) {
462
534
  return { ok: false, error: `发送失败: ${String(e?.message || e).slice(0, 160)}` };
@@ -523,3 +595,156 @@ export async function restoreGroups() {
523
595
  }
524
596
  return { restored, failed, total: groups.length };
525
597
  }
598
+ // ─────────────────────────────────────────────────────────── 群运营能力 (2026-10-05, leo: 邀请/踢出/隐私/链接/状态)
599
+ /**
600
+ * 重建群 store (新白名单) —— 这是"改成员"唯一诚实的路径。
601
+ *
602
+ * IPFS 型 AC 的白名单烧在内容寻址的 ACL manifest 里, 改它必然换 store 地址
603
+ * (见 `ACL_INPLACE_UPDATE.ipfsType` / `aclChangePlan`)。所以:
604
+ * · 邀请 = 新白名单 + 被邀请者 → 重建;
605
+ * · 踢出 = 新白名单 − 被踢者 → 重建。
606
+ * 重建 = 新地址、新链接、**历史消息不回放** (要保留必须显式回放)。
607
+ *
608
+ * 本函数做"换地址重建 + 历史回放 (逐条 add) + 本机链接更新", 返回新 GroupInfo。
609
+ * 迁移代价 (老链接作废/其他成员需重新 join) 由调用方 (CLI) 明示, 这里不假装无痛。
610
+ */
611
+ export async function rebuildGroupWithWriteList(groupId, mode, opts) {
612
+ const groups = await loadGroups();
613
+ const g = groups.find((x) => x.id === groupId);
614
+ if (!g)
615
+ return { ok: false, error: '群组不存在 (先 joinGroup/createGroup)' };
616
+ if (!g.gated) {
617
+ return {
618
+ ok: false,
619
+ error: `「${g.name}」不是门控群 (write:[*]), 没有白名单可改 —— 邀请=分享链接, 踢出=不适用 (人人可写)`,
620
+ };
621
+ }
622
+ const owner = {
623
+ did: g.ownerDid || '',
624
+ publicKeyHex: g.ownerPublicKeyHex || '',
625
+ orbitdbId: g.aclWrite?.[0] || '',
626
+ };
627
+ if (!owner.did || !owner.publicKeyHex || !owner.orbitdbId) {
628
+ return { ok: false, error: '缺少群主身份信息 (ownerDid/ownerPublicKeyHex/aclWrite 为空), 无法派生新白名单' };
629
+ }
630
+ // 当前白名单 = manifest 快照 (aclWrite, 上次重建/建群时烧进去的) 优先;
631
+ // 事件派生只在没有快照的老门控群兜底 (两套语义: 事件=成员自签加入, rebuild=群主单方改名单)
632
+ const derived = await groupWriteList(groupId, owner).catch(() => null);
633
+ const before = g.aclWrite && g.aclWrite.length > 0 ? g.aclWrite : (derived?.write ?? []);
634
+ const after = normalizeWriteList(mode.kind === 'add'
635
+ ? [...before, mode.ref.orbitdbId]
636
+ : before.filter((id) => id !== mode.orbitdbId));
637
+ const changed = before.join(',') !== after.join(',');
638
+ if (!changed) {
639
+ return { ok: false, error: mode.kind === 'add' ? '该成员已在白名单里' : '该成员不在白名单里' };
640
+ }
641
+ if (mode.kind === 'remove' && !before.includes(mode.orbitdbId)) {
642
+ return { ok: false, error: `白名单里没有 ${mode.orbitdbId.slice(0, 16)}…, 无需踢出` };
643
+ }
644
+ const plan = aclChangePlan({ before, after, messageCount: opts?.replayHistory ? undefined : 0 });
645
+ const db = getDb();
646
+ // 1) 读历史消息 (回放前) —— 只在显式要求时
647
+ const history = opts?.replayHistory ? await groupMessages(groupId, 100000) : [];
648
+ // 2) 建新 store (新白名单)
649
+ let store = null;
650
+ try {
651
+ store = await db.openStore(`bolloon-gw-group-${g.name}-r${Date.now().toString(36)}`, 'events', groupAccessOptions(after));
652
+ }
653
+ catch (e) {
654
+ return { ok: false, error: `重建 store 失败: ${String(e?.message || e).slice(0, 160)}` };
655
+ }
656
+ if (!store)
657
+ return { ok: false, error: '重建 store 返回空 (openStore 失败, 未改任何东西)' };
658
+ // 3) 迁移成员事件 (原样重放, 双侧签名保留) —— 否则新 store 派生白名单只剩群主, 成员全丢
659
+ try {
660
+ const rawAll = await allRawEntries(groupId);
661
+ for (const { value } of rawAll) {
662
+ const v = value;
663
+ if (v && typeof v === 'object' && v.kind === GROUP_MEMBERSHIP_KIND) {
664
+ try {
665
+ await store.add(v);
666
+ }
667
+ catch (e) { /* 单条失败不阻断 */ }
668
+ }
669
+ }
670
+ }
671
+ catch (e) {
672
+ return { ok: false, error: `迁移成员事件失败: ${String(e?.message || e).slice(0, 120)}` };
673
+ }
674
+ // 4) 回放历史消息 (逐条 add, 保持 ts)
675
+ if (opts?.replayHistory) {
676
+ for (const m of history) {
677
+ try {
678
+ await store.add({ ...m });
679
+ }
680
+ catch (e) {
681
+ // 逐条静默跳过, 能回放多少算多少; 回放是尽力而为, 不阻断重建
682
+ }
683
+ }
684
+ }
685
+ // 5) 更新本机记录: 换地址/换链接; 老 store 不删 (占用不回收, 见 plan.costs)
686
+ const address = store.address;
687
+ const link = `orbitdb://${address}?type=group&name=${encodeURIComponent(g.name)}`;
688
+ const info = {
689
+ ...g,
690
+ address,
691
+ link,
692
+ lastSyncAt: new Date().toISOString(),
693
+ gated: true,
694
+ ownerDid: g.ownerDid,
695
+ aclWrite: after,
696
+ privacy: g.privacy ?? true,
697
+ };
698
+ await saveGroups(groups.map((x) => (x.id === groupId ? info : x)));
699
+ storeCache.set(groupId, store);
700
+ return { ok: true, group: info, before, after, costs: plan.costs };
701
+ }
702
+ /** 邀请: 把 orbitdbId 加进白名单 (重建路径) */
703
+ export async function inviteMember(groupId, ref) {
704
+ const r = await rebuildGroupWithWriteList(groupId, { kind: 'add', ref });
705
+ return { ok: r.ok, error: r.error, group: r.group, costs: r.costs };
706
+ }
707
+ /** 踢出: 把 orbitdbId 移出白名单 (重建路径; 协议上无法记"被踢者自签的 remove 事件", 不假装) */
708
+ export async function kickMember(groupId, orbitdbId) {
709
+ const r = await rebuildGroupWithWriteList(groupId, { kind: 'remove', orbitdbId });
710
+ return { ok: r.ok, error: r.error, group: r.group, costs: r.costs };
711
+ }
712
+ /** 设置隐私群标记 (只影响本机展示/默认 ACL 说明, 不改变 OrbitDB 权限语义) */
713
+ export async function setGroupPrivacy(groupId, privacy) {
714
+ const groups = await loadGroups();
715
+ const g = groups.find((x) => x.id === groupId);
716
+ if (!g)
717
+ return { ok: false, error: '群组不存在' };
718
+ const info = { ...g, privacy, lastSyncAt: new Date().toISOString() };
719
+ await saveGroups(groups.map((x) => (x.id === groupId ? info : x)));
720
+ return { ok: true, group: info };
721
+ }
722
+ /** 群状态快照: 成员/消息/白名单/gated/privacy/最后同步 (状态管理用) */
723
+ export async function groupStatus(groupId) {
724
+ const g = await groupInfo(groupId);
725
+ if (!g)
726
+ return { ok: false, error: '群组不存在' };
727
+ const members = await groupMembers(groupId);
728
+ const wl = await (async () => {
729
+ if (!g.gated || !g.ownerDid)
730
+ return g.aclWrite ?? [];
731
+ const owner = { did: g.ownerDid, publicKeyHex: g.ownerPublicKeyHex || '', orbitdbId: g.aclWrite?.[0] || '' };
732
+ if (!owner.orbitdbId || !owner.publicKeyHex)
733
+ return g.aclWrite ?? [];
734
+ const d = await groupWriteList(groupId, owner).catch(() => null);
735
+ return d ? d.write : (g.aclWrite ?? []);
736
+ })();
737
+ return {
738
+ ok: true,
739
+ status: {
740
+ group: g,
741
+ memberCount: members.length,
742
+ messageCount: g.messageCount ?? 0,
743
+ writeList: wl,
744
+ gated: !!g.gated,
745
+ privacy: g.privacy ?? !!g.gated,
746
+ ownerDid: g.ownerDid,
747
+ lastSyncAt: g.lastSyncAt,
748
+ },
749
+ };
750
+ }