remnote-bridge 0.1.12 → 0.1.14
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 +141 -28
- package/README.zh-CN.md +368 -0
- package/dist/cli/commands/edit-rem.js +5 -5
- package/dist/cli/commands/health.js +231 -112
- package/dist/cli/commands/read-rem-in-tree.js +84 -0
- package/dist/cli/commands/read-rem.js +3 -1
- package/dist/cli/config.js +2 -0
- package/dist/cli/daemon/registry.js +8 -0
- package/dist/cli/handlers/edit-handler.js +49 -140
- package/dist/cli/handlers/patch-engine.js +347 -0
- package/dist/cli/handlers/read-handler.js +5 -57
- package/dist/cli/handlers/rem-cache.js +10 -5
- package/dist/cli/handlers/rem-field-filter.js +102 -0
- package/dist/cli/handlers/tree-edit-handler.js +67 -7
- package/dist/cli/handlers/tree-read-handler.js +4 -1
- package/dist/cli/handlers/tree-rem-read-handler.js +73 -0
- package/dist/cli/main.js +71 -12
- package/dist/cli/server/ws-server.js +9 -1
- package/dist/mcp/daemon-client.js +22 -2
- package/dist/mcp/format.js +43 -0
- package/dist/mcp/index.js +0 -55
- package/dist/mcp/instructions.js +447 -284
- package/dist/mcp/resources/edit-rem-guide.js +37 -157
- package/dist/mcp/resources/edit-tree-guide.js +1 -1
- package/dist/mcp/resources/error-reference.js +9 -13
- package/dist/mcp/resources/rem-object-fields.js +3 -3
- package/dist/mcp/tools/edit-tools.js +76 -10
- package/dist/mcp/tools/infra-tools.js +30 -33
- package/dist/mcp/tools/read-tools.js +221 -26
- package/package.json +1 -1
- package/remnote-plugin/dist/index-sandbox.js +24 -24
- package/remnote-plugin/dist/index.js +24 -24
- package/remnote-plugin/src/bridge/message-router.ts +3 -0
- package/remnote-plugin/src/services/read-rem-in-tree.ts +43 -0
- package/remnote-plugin/src/services/read-rem.ts +15 -0
- package/remnote-plugin/src/services/read-tree.ts +5 -0
- package/skills/remnote-bridge/SKILL.md +71 -38
- package/skills/remnote-bridge/instructions/connect.md +12 -1
- package/skills/remnote-bridge/instructions/disconnect.md +5 -0
- package/skills/remnote-bridge/instructions/edit-rem.md +105 -347
- package/skills/remnote-bridge/instructions/edit-tree.md +71 -2
- package/skills/remnote-bridge/instructions/health.md +81 -53
- package/skills/remnote-bridge/instructions/overall.md +55 -21
- package/skills/remnote-bridge/instructions/read-rem-in-tree.md +100 -0
- package/skills/remnote-bridge/instructions/read-rem.md +35 -16
- package/skills/remnote-bridge/instructions/search.md +4 -4
- package/skills/remnote-bridge/instructions/setup.md +5 -6
- package/skills/remnote-bridge-test/SKILL.md +847 -0
- package/skills/remnote-bridge-test/references/regression-suite.md +960 -0
- package/skills/remnote-bridge-test/references/verification-guide.md +161 -0
|
@@ -174,6 +174,66 @@ oldStr 必须在缓存大纲中恰好匹配 1 次
|
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
177
|
+
## 行引用模板 `{{remId}}`
|
|
178
|
+
|
|
179
|
+
在 oldStr/newStr 中使用 `{{remId}}` 引用缓存大纲中已有行的完整内容(不含缩进)。系统在 str_replace 前自动展开。不含 `{{}}` 的传统写法完全兼容。
|
|
180
|
+
|
|
181
|
+
### 动机
|
|
182
|
+
|
|
183
|
+
AI 构造 oldStr/newStr 时需精确复制已有行(含 17+ 字符 Rem ID 和元数据标记),导致 token 浪费和复制错误。`{{remId}}` 让 AI 只写 ID,系统自动替换为完整行内容。
|
|
184
|
+
|
|
185
|
+
### 展开规则
|
|
186
|
+
|
|
187
|
+
| 输入 | 展开为 |
|
|
188
|
+
|------|--------|
|
|
189
|
+
| `{{remId}}` | 该 remId 对应行的去缩进完整内容(含 `<!--remId 元数据-->`) |
|
|
190
|
+
| ` {{remId}}` | AI 写的缩进 + 展开后的完整内容 |
|
|
191
|
+
| 不含 `{{}}` 的文本 | 原样不变 |
|
|
192
|
+
|
|
193
|
+
### 示例
|
|
194
|
+
|
|
195
|
+
**重排(对比传统写法)**
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
# 传统写法(~250 tokens)
|
|
199
|
+
oldStr: " 动态数组 <!--id1_1 type:concept-->\n 静态数组 <!--id1_2 type:concept-->"
|
|
200
|
+
newStr: " 静态数组 <!--id1_2 type:concept-->\n 动态数组 <!--id1_1 type:concept-->"
|
|
201
|
+
|
|
202
|
+
# 模板写法(~50 tokens)
|
|
203
|
+
oldStr: " {{id1_1}}\n {{id1_2}}"
|
|
204
|
+
newStr: " {{id1_2}}\n {{id1_1}}"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**移动(改变缩进)**
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
oldStr: " {{idA}}\n {{idT}}\n {{idB}}"
|
|
211
|
+
newStr: " {{idA}}\n {{idB}}\n {{idT}}"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**删除(模板用于上下文定位)**
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
oldStr: " {{idA}}\n {{idA1}}\n {{idB}}"
|
|
218
|
+
newStr: " {{idB}}"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**新增 + 模板混用**
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
oldStr: " {{idZ}}"
|
|
225
|
+
newStr: " 新增行\n {{idZ}}"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 限制
|
|
229
|
+
|
|
230
|
+
- 只匹配纯字母数字(`[a-zA-Z0-9]+`),与 RemNote cloze 语法 `{{text}}` 不冲突(cloze 含中文/空格/标点不会被匹配)
|
|
231
|
+
- 匹配到但不在缓存大纲中的 `{{xxx}}` 原样保留(可能是 cloze),并输出 templateWarnings
|
|
232
|
+
- `{{remId}}` 不含缩进,缩进由 AI 控制(move 操作会改变缩进)
|
|
233
|
+
- 新增行没有 remId,不能用模板表示
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
177
237
|
## 支持的操作
|
|
178
238
|
|
|
179
239
|
### 新增行
|
|
@@ -458,8 +518,9 @@ RemNote SDK 存在已知 bug:
|
|
|
458
518
|
4. daemon TreeEditHandler:
|
|
459
519
|
├─ 防线 1: cache.get('tree:' + remId) 存在?
|
|
460
520
|
├─ 防线 2: 用缓存的 depth/maxNodes/maxSiblings 重新 read-tree → 对比
|
|
461
|
-
├─
|
|
462
|
-
├─
|
|
521
|
+
├─ 模板展开: {{remId}} → 缓存中对应行的完整内容(不含缩进)
|
|
522
|
+
├─ 防线 3: countOccurrences(cachedOutline, expandedOldStr) === 1?
|
|
523
|
+
├─ modifiedOutline = cachedOutline.replace(expandedOldStr, expandedNewStr)
|
|
463
524
|
├─ 解析新旧大纲为树(parseOutline)
|
|
464
525
|
├─ 对比差异(diffTrees)
|
|
465
526
|
│ ├─ 根节点校验
|
|
@@ -499,11 +560,19 @@ remnote-bridge edit-tree kLr --old-str ' 叶子节点 <!--leaf-->\n' --new-st
|
|
|
499
560
|
### 调换两个兄弟的顺序
|
|
500
561
|
|
|
501
562
|
```bash
|
|
563
|
+
# 传统写法
|
|
502
564
|
remnote-bridge edit-tree kLr --old-str ' 节点 A <!--idA-->\n 节点 B <!--idB-->' --new-str ' 节点 B <!--idB-->\n 节点 A <!--idA-->'
|
|
565
|
+
|
|
566
|
+
# 模板写法(JSON 模式)
|
|
567
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{idA}}\n {{idB}}","newStr":" {{idB}}\n {{idA}}"}'
|
|
503
568
|
```
|
|
504
569
|
|
|
505
570
|
### 将节点移到另一个父节点下
|
|
506
571
|
|
|
507
572
|
```bash
|
|
573
|
+
# 传统写法
|
|
508
574
|
remnote-bridge edit-tree kLr --old-str ' 旧父 <!--oldP-->\n 目标 <!--target-->\n 新父 <!--newP-->' --new-str ' 旧父 <!--oldP-->\n 新父 <!--newP-->\n 目标 <!--target-->'
|
|
575
|
+
|
|
576
|
+
# 模板写法(JSON 模式)
|
|
577
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{oldP}}\n {{target}}\n {{newP}}","newStr":" {{oldP}}\n {{newP}}\n {{target}}"}'
|
|
509
578
|
```
|
|
@@ -6,65 +6,64 @@
|
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
|
-
`health`
|
|
9
|
+
`health` 检查系统状态,支持两种模式:
|
|
10
10
|
|
|
11
|
+
1. **全量模式**(默认):遍历注册表所有活跃实例,逐个查询三层状态
|
|
12
|
+
2. **单实例模式**(`--instance` / `--headless`):只查询指定实例
|
|
13
|
+
|
|
14
|
+
每个实例的检查分两步:
|
|
11
15
|
1. **本地检查**:通过注册表查找实例,确认 daemon 进程是否存活
|
|
12
16
|
2. **远程检查**:通过 WS 连接 daemon,获取 Plugin 连接状态和 SDK 就绪状态
|
|
13
17
|
|
|
14
|
-
###
|
|
18
|
+
### 孪生连接
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
remnote-bridge health --instance work
|
|
20
|
-
```
|
|
20
|
+
每个实例的 Plugin 连接会标记是否为**孪生连接**(`plugin.isTwin`)。孪生连接表示 Plugin 的 `twinSlotIndex` 与 daemon 的槽位索引匹配,优先级更高——孪生连接可以抢占非孪生连接。
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
24
|
## 用法
|
|
25
25
|
|
|
26
|
-
###
|
|
26
|
+
### 全量模式(默认)
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
remnote-bridge health
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
输出所有活跃实例的状态:
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
|
|
36
|
-
✅
|
|
35
|
+
=== 实例: default(槽位 0)===
|
|
36
|
+
✅ 守护进程 运行中(PID: 12345,已运行 5 分钟)
|
|
37
|
+
✅ Plugin 已连接(孪生)
|
|
37
38
|
✅ SDK 就绪
|
|
38
|
-
|
|
39
39
|
超时: 25 分钟后自动关闭
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
输出示例(部分不健康):
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
✅ 守护进程 运行中(PID: 12345,实例: work,槽位: 1,已运行 2 分钟)
|
|
46
|
-
❌ Plugin 未连接
|
|
47
|
-
❌ SDK 未就绪
|
|
48
40
|
|
|
41
|
+
=== 实例: headless(槽位 1)===
|
|
42
|
+
✅ 守护进程 运行中(PID: 12346,已运行 2 分钟)
|
|
43
|
+
✅ Plugin 已连接(非孪生)
|
|
44
|
+
✅ SDK 就绪
|
|
45
|
+
✅ Chrome running
|
|
49
46
|
超时: 28 分钟后自动关闭
|
|
50
47
|
```
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
无活跃实例时:
|
|
53
50
|
|
|
54
51
|
```
|
|
55
|
-
|
|
56
|
-
❌ Plugin 未连接
|
|
57
|
-
❌ SDK 不可用
|
|
58
|
-
|
|
59
|
-
提示: 执行 `remnote-bridge connect` 启动守护进程
|
|
52
|
+
没有活跃的实例。执行 `remnote-bridge connect` 启动守护进程。
|
|
60
53
|
```
|
|
61
54
|
|
|
62
|
-
###
|
|
55
|
+
### 单实例模式
|
|
63
56
|
|
|
64
57
|
```bash
|
|
65
|
-
|
|
58
|
+
# 指定实例
|
|
59
|
+
remnote-bridge --instance work health
|
|
60
|
+
|
|
61
|
+
# 检查 headless 实例
|
|
62
|
+
remnote-bridge --headless health
|
|
66
63
|
```
|
|
67
64
|
|
|
65
|
+
输出格式与之前相同,但只显示一个实例。
|
|
66
|
+
|
|
68
67
|
### Headless 诊断模式
|
|
69
68
|
|
|
70
69
|
```bash
|
|
@@ -81,7 +80,43 @@ remnote-bridge health --reload
|
|
|
81
80
|
|
|
82
81
|
## JSON 输出
|
|
83
82
|
|
|
84
|
-
###
|
|
83
|
+
### 全量模式
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"ok": true,
|
|
88
|
+
"command": "health",
|
|
89
|
+
"exitCode": 0,
|
|
90
|
+
"instances": [
|
|
91
|
+
{
|
|
92
|
+
"instance": "default",
|
|
93
|
+
"slotIndex": 0,
|
|
94
|
+
"daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 300 },
|
|
95
|
+
"plugin": { "connected": true, "isTwin": true },
|
|
96
|
+
"sdk": { "ready": true },
|
|
97
|
+
"timeoutRemaining": 1500
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"instance": "headless",
|
|
101
|
+
"slotIndex": 1,
|
|
102
|
+
"daemon": { "running": true, "pid": 12346, "reachable": true, "uptime": 120 },
|
|
103
|
+
"plugin": { "connected": true, "isTwin": true },
|
|
104
|
+
"sdk": { "ready": true },
|
|
105
|
+
"timeoutRemaining": 1680,
|
|
106
|
+
"headless": {
|
|
107
|
+
"status": "running",
|
|
108
|
+
"chromeConnected": true,
|
|
109
|
+
"pageUrl": "http://localhost:29111",
|
|
110
|
+
"reloadCount": 0,
|
|
111
|
+
"lastError": null,
|
|
112
|
+
"recentConsoleErrors": []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 单实例模式 — 全部健康
|
|
85
120
|
|
|
86
121
|
```json
|
|
87
122
|
{
|
|
@@ -91,39 +126,36 @@ remnote-bridge health --reload
|
|
|
91
126
|
"instance": "default",
|
|
92
127
|
"slotIndex": 0,
|
|
93
128
|
"daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 300 },
|
|
94
|
-
"plugin": { "connected": true },
|
|
129
|
+
"plugin": { "connected": true, "isTwin": true },
|
|
95
130
|
"sdk": { "ready": true },
|
|
96
131
|
"timeoutRemaining": 1500
|
|
97
132
|
}
|
|
98
133
|
```
|
|
99
134
|
|
|
100
|
-
###
|
|
135
|
+
### 单实例模式 — daemon 未运行
|
|
101
136
|
|
|
102
137
|
```json
|
|
103
138
|
{
|
|
104
139
|
"ok": false,
|
|
105
140
|
"command": "health",
|
|
106
|
-
"exitCode":
|
|
107
|
-
"instance": "
|
|
108
|
-
"
|
|
109
|
-
"daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 120 },
|
|
141
|
+
"exitCode": 2,
|
|
142
|
+
"instance": "default",
|
|
143
|
+
"daemon": { "running": false },
|
|
110
144
|
"plugin": { "connected": false },
|
|
111
145
|
"sdk": { "ready": false },
|
|
112
|
-
"
|
|
146
|
+
"error": "守护进程未运行(实例: default),请先执行 remnote-bridge connect"
|
|
113
147
|
}
|
|
114
148
|
```
|
|
115
149
|
|
|
116
|
-
###
|
|
150
|
+
### 全量模式 — 无活跃实例
|
|
117
151
|
|
|
118
152
|
```json
|
|
119
153
|
{
|
|
120
154
|
"ok": false,
|
|
121
155
|
"command": "health",
|
|
122
156
|
"exitCode": 2,
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"plugin": { "connected": false },
|
|
126
|
-
"sdk": { "ready": false }
|
|
157
|
+
"instances": [],
|
|
158
|
+
"error": "没有活跃的实例,请执行 remnote-bridge connect 启动守护进程"
|
|
127
159
|
}
|
|
128
160
|
```
|
|
129
161
|
|
|
@@ -135,6 +167,7 @@ remnote-bridge health --reload
|
|
|
135
167
|
|--------|----------|------|
|
|
136
168
|
| **daemon** | 注册表查找 + `kill(pid, 0)` 探活 | 守护进程是否在运行且可达 |
|
|
137
169
|
| **plugin** | daemon 内部的 `pluginConnected` 状态 | RemNote Plugin 是否已通过 WS 连接到 daemon |
|
|
170
|
+
| **plugin.isTwin** | Plugin hello 握手中的 `twinSlotIndex` | 是否为孪生连接(匹配 daemon 槽位索引) |
|
|
138
171
|
| **sdk** | Plugin 的 hello 握手中的 `sdkReady` 字段 | RemNote SDK 是否就绪(知识库已加载,可调用 API) |
|
|
139
172
|
|
|
140
173
|
### 三层关系
|
|
@@ -151,9 +184,9 @@ daemon 运行 → Plugin 连接 → SDK 就绪
|
|
|
151
184
|
|
|
152
185
|
| 退出码 | 含义 | 触发条件 |
|
|
153
186
|
|--------|------|----------|
|
|
154
|
-
| 0 | 全部健康 |
|
|
187
|
+
| 0 | 全部健康 | 所有实例三层均通过 |
|
|
155
188
|
| 1 | 部分不健康 | daemon 运行但 Plugin 未连接或 SDK 未就绪 |
|
|
156
|
-
| 2 | 不可达 | daemon
|
|
189
|
+
| 2 | 不可达 | 无活跃实例,或 daemon 不可达 |
|
|
157
190
|
|
|
158
191
|
---
|
|
159
192
|
|
|
@@ -161,11 +194,13 @@ daemon 运行 → Plugin 连接 → SDK 就绪
|
|
|
161
194
|
|
|
162
195
|
| 字段 | 类型 | 说明 |
|
|
163
196
|
|------|------|------|
|
|
197
|
+
| `instances` | array | 全量模式下所有实例的状态数组 |
|
|
164
198
|
| `daemon.running` | boolean | 进程是否存活 |
|
|
165
199
|
| `daemon.pid` | number | 进程 ID(仅运行时) |
|
|
166
200
|
| `daemon.reachable` | boolean | WS 连接是否成功(仅运行时) |
|
|
167
201
|
| `daemon.uptime` | number | 运行秒数(仅可达时) |
|
|
168
202
|
| `plugin.connected` | boolean | Plugin WS 连接是否建立 |
|
|
203
|
+
| `plugin.isTwin` | boolean | 是否为孪生连接 |
|
|
169
204
|
| `sdk.ready` | boolean | RemNote SDK 是否就绪 |
|
|
170
205
|
| `timeoutRemaining` | number | 距自动关闭的剩余秒数(仅可达时) |
|
|
171
206
|
|
|
@@ -173,19 +208,12 @@ daemon 运行 → Plugin 连接 → SDK 就绪
|
|
|
173
208
|
|
|
174
209
|
## Headless 模式附加输出
|
|
175
210
|
|
|
176
|
-
### health 基础输出(headless
|
|
211
|
+
### health 基础输出(headless 实例额外字段)
|
|
177
212
|
|
|
178
|
-
headless
|
|
213
|
+
headless 实例额外包含 `headless` 对象:
|
|
179
214
|
|
|
180
215
|
```json
|
|
181
216
|
{
|
|
182
|
-
"ok": true,
|
|
183
|
-
"command": "health",
|
|
184
|
-
"exitCode": 0,
|
|
185
|
-
"daemon": { "running": true, "pid": 12345, "reachable": true, "uptime": 300 },
|
|
186
|
-
"plugin": { "connected": true },
|
|
187
|
-
"sdk": { "ready": true },
|
|
188
|
-
"timeoutRemaining": 1500,
|
|
189
217
|
"headless": {
|
|
190
218
|
"status": "running",
|
|
191
219
|
"chromeConnected": true,
|
|
@@ -227,7 +255,7 @@ headless 模式下 `health` 基础输出额外包含 `headless` 对象:
|
|
|
227
255
|
|
|
228
256
|
| 症状 | 可能原因 | 解决方案 |
|
|
229
257
|
|------|----------|----------|
|
|
230
|
-
|
|
|
258
|
+
| 无活跃实例 | 未执行 connect / 已超时关闭 | 执行 `connect` |
|
|
231
259
|
| daemon 运行但不可达 | WS 端口被占用或配置不匹配 | 检查 `~/.remnote-bridge/slots.json` 中的端口配置 |
|
|
232
260
|
| Plugin 未连接(标准模式) | RemNote 未打开 / Plugin 未安装 / URL 不匹配 | 打开 RemNote,确认 Plugin 中的 WS URL 设置 |
|
|
233
261
|
| Plugin 未连接(headless 模式) | Chrome 页面加载异常 | `health --diagnose` 查看截图和状态,`health --reload` 重载页面 |
|
|
@@ -44,6 +44,7 @@ Agent 的核心任务是将用户的自然语言请求翻译为 CLI 命令。以
|
|
|
44
44
|
| "我现在在看什么"、"当前页面" | 用户当前焦点/页面 | `read-context` |
|
|
45
45
|
| "展开这个主题"、"看看下面有什么" | Rem 子树 | `read-tree <remId>` |
|
|
46
46
|
| "这个笔记的详细信息" | Rem 的完整属性 | `read-rem <remId>` |
|
|
47
|
+
| "展开子树并获取所有属性" | 子树大纲 + 节点属性 | `read-rem-in-tree <remId>` |
|
|
47
48
|
| "搜索 X"、"查找关于 X 的内容" | 全文搜索 | `search <query>` |
|
|
48
49
|
|
|
49
50
|
#### 修改 / 写入
|
|
@@ -128,7 +129,7 @@ Rem 有两个**独立维度**的类型:
|
|
|
128
129
|
|:-----|:-----|:-----|
|
|
129
130
|
| 创建 Portal | `edit-tree` | 新增行 `<!--portal refs:id1,id2-->` |
|
|
130
131
|
| 删除 Portal | `edit-tree` | 从大纲中移除 Portal 行(与删除普通行相同) |
|
|
131
|
-
| 修改引用列表(增删引用的 Rem) | `edit-rem` |
|
|
132
|
+
| 修改引用列表(增删引用的 Rem) | `edit-rem` | 直接修改 changes 中的 `portalDirectlyIncludedRem` 数组 |
|
|
132
133
|
| 移动 Portal(换父节点/位置) | `edit-tree` | 与移动普通行相同 |
|
|
133
134
|
| 读取 Portal | `read-rem` | 自动输出 8 字段简化 JSON |
|
|
134
135
|
|
|
@@ -239,7 +240,7 @@ remnote-bridge disconnect --instance work
|
|
|
239
240
|
| 2 | 29120 | 29121 | 29122 |
|
|
240
241
|
| 3 | 29130 | 29131 | 29132 |
|
|
241
242
|
|
|
242
|
-
**实例名解析优先级**:CLI `--instance` > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default
|
|
243
|
+
**实例名解析优先级**:CLI `--instance` > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。`headless` 是保留实例名,不可用于 `--instance`(会报错),必须使用 `--headless` 全局选项。
|
|
243
244
|
|
|
244
245
|
**Plugin 自动发现**:Plugin 启动后通过 `/api/discovery` 获取其孪生 daemon 的连接信息(WS 端口、槽位索引等),自动建立连接。一个 Plugin 可同时连接最多 4 个 daemon。
|
|
245
246
|
|
|
@@ -282,13 +283,14 @@ remnote-bridge disconnect --instance work
|
|
|
282
283
|
| `read-context` | 当前上下文视图 | mode + 参数 | 否 | `read-context.md` |
|
|
283
284
|
| `read-tree` | 读取子树为 Markdown 大纲 | remId + 展开参数 | 是(`tree:`) | `read-tree.md` |
|
|
284
285
|
| `read-rem` | 读取单个 Rem 的 JSON 属性 | remId | 是(`rem:`) | `read-rem.md` |
|
|
286
|
+
| `read-rem-in-tree` | 子树大纲 + 节点属性一次获取 | remId + 展开参数 + 过滤参数 | 是(`tree:` + `rem:`) | `read-rem-in-tree.md` |
|
|
285
287
|
| `search` | 全文搜索 | query | 否 | `search.md` |
|
|
286
288
|
|
|
287
289
|
#### 写入命令
|
|
288
290
|
|
|
289
291
|
| 命令 | 功能 | 前置条件 | 安全机制 | 详细文档 |
|
|
290
292
|
|:-----|:-----|:---------|:---------|:---------|
|
|
291
|
-
| `edit-rem` |
|
|
293
|
+
| `edit-rem` | 直接修改 Rem 属性字段 | 先 `read-rem` | 两道防线 + 字段白名单 | `edit-rem.md` |
|
|
292
294
|
| `edit-tree` | str_replace 编辑树结构 | 先 `read-tree` | 三道防线 + diff | `edit-tree.md` |
|
|
293
295
|
|
|
294
296
|
### 4.2 探索决策指南
|
|
@@ -309,10 +311,17 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
309
311
|
├─ 某个具体 Rem 的子树 → read-tree <remId>
|
|
310
312
|
│ 完整展开子树(支持深度/节点预算控制)
|
|
311
313
|
│ 结果缓存,供 edit-tree 使用
|
|
314
|
+
│ ⚠️ 如果需要读取子树后对其中多个节点执行 edit-rem,请改用 read-rem-in-tree
|
|
312
315
|
│
|
|
313
316
|
├─ 某个 Rem 的详细属性 → read-rem <remId>
|
|
314
317
|
│ 返回 51 字段的 RemObject JSON
|
|
315
318
|
│ 结果缓存,供 edit-rem 使用
|
|
319
|
+
│ ⚠️ 如果需要读取多个 Rem 属性(≥3 个)且在同一子树下,请改用 read-rem-in-tree
|
|
320
|
+
│
|
|
321
|
+
├─ 子树结构 + 每个节点的详细属性 → read-rem-in-tree <remId>
|
|
322
|
+
│ read-tree + read-rem 的合体,一次调用同时获取大纲和 RemObject
|
|
323
|
+
│ 同时建立 tree 和 rem 双重缓存,供 edit-tree 和 edit-rem 使用
|
|
324
|
+
│ 默认 maxNodes=50(比 read-tree 的 200 低,因每节点开销大)
|
|
316
325
|
│
|
|
317
326
|
└─ 按关键词搜索 → search <query>
|
|
318
327
|
全文搜索,返回匹配的 Rem 列表
|
|
@@ -327,6 +336,7 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
327
336
|
| "我现在在编辑什么" | `read-context --mode focus` | 鱼眼视图,焦点处详细 |
|
|
328
337
|
| "当前页面的内容" | `read-context --mode page` | 以页面为根展开 |
|
|
329
338
|
| "展开某个主题的细节" | `read-tree <id>` | 完整子树,可缓存供编辑 |
|
|
339
|
+
| "展开子树并查看每个节点属性" | `read-rem-in-tree <id>` | 大纲 + RemObject,双重缓存 |
|
|
330
340
|
|
|
331
341
|
#### read-globe 特性
|
|
332
342
|
|
|
@@ -390,14 +400,28 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
390
400
|
4. read-globe ← 了解知识库结构(首次探索)
|
|
391
401
|
或 read-context ← 了解用户当前上下文
|
|
392
402
|
5. search "关键词" ← 定位目标 Rem(中文搜索可能需单字策略,详见 search.md)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
403
|
+
6a. [单节点] read-tree <id> + read-rem <id> ← 各自建立缓存
|
|
404
|
+
6b. [多节点] read-rem-in-tree <id> ← 一次建立双重缓存(推荐 ≥3 个节点需修改时)
|
|
405
|
+
7. edit-rem <id> ... ← 修改 Rem 属性
|
|
396
406
|
或 edit-tree <id> ...← 修改树结构
|
|
397
407
|
9. disconnect ← 结束会话
|
|
398
408
|
```
|
|
399
409
|
|
|
400
|
-
**注意**:步骤
|
|
410
|
+
**注意**:步骤 6a/6b 是 edit-rem / edit-tree 的强制前置条件。跳过会触发防线 1 错误。步骤 2 是必须的——connect 后不引导用户加载插件就直接调用业务命令,会报"Plugin 未连接"错误。
|
|
411
|
+
|
|
412
|
+
### 4.5 批量标注工作流(课本划重点场景)
|
|
413
|
+
|
|
414
|
+
当需要对一棵子树中的多个节点进行富文本标注(行级高亮、行内荧光、粗体等)时:
|
|
415
|
+
|
|
416
|
+
1. read-rem-in-tree <id> --maxNodes 50 -- 一次获取大纲 + 所有 RemObject
|
|
417
|
+
2. 从 remObjects 中定位需要标注的节点
|
|
418
|
+
3. 对每个目标节点 edit-rem 设置格式:
|
|
419
|
+
- 行级高亮:changes.highlightColor = "Yellow"/"Red"/...
|
|
420
|
+
- 行内荧光:changes.text = [..., {"h": 3, "i": "m", "text": "关键词"}, ...]
|
|
421
|
+
- 粗体:changes.text = [..., {"b": true, "i": "m", "text": "核心概念"}, ...]
|
|
422
|
+
4. 如需结构变更(如新增/移动节点),直接 edit-tree(tree 缓存已就绪)
|
|
423
|
+
|
|
424
|
+
关键:read-rem-in-tree 同时建立了 tree 和 rem 两种缓存,后续 edit-tree 和 edit-rem 都无需再单独 read。
|
|
401
425
|
|
|
402
426
|
---
|
|
403
427
|
|
|
@@ -410,7 +434,7 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
410
434
|
```bash
|
|
411
435
|
read-rem kLrIOHJLyMd8Y2lyA --fields text,type
|
|
412
436
|
read-tree kLrIOHJLyMd8Y2lyA --depth 2
|
|
413
|
-
edit-rem kLrIOHJLyMd8Y2lyA --
|
|
437
|
+
edit-rem kLrIOHJLyMd8Y2lyA --changes '{"type":"descriptor"}'
|
|
414
438
|
```
|
|
415
439
|
|
|
416
440
|
- 位置参数 = remId 或关键词
|
|
@@ -422,7 +446,7 @@ edit-rem kLrIOHJLyMd8Y2lyA --old-str '"concept"' --new-str '"descriptor"'
|
|
|
422
446
|
```bash
|
|
423
447
|
read-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","fields":["text","type"]}'
|
|
424
448
|
read-tree --json '{"remId":"kLrIOHJLyMd8Y2lyA","depth":2}'
|
|
425
|
-
edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","
|
|
449
|
+
edit-rem --json '{"remId":"kLrIOHJLyMd8Y2lyA","changes":{"type":"descriptor"}}'
|
|
426
450
|
```
|
|
427
451
|
|
|
428
452
|
- **位置参数 = JSON 字符串**,所有参数打包在 JSON 对象中
|
|
@@ -465,6 +489,7 @@ daemon → CLI 响应:
|
|
|
465
489
|
| `read_rem` | read-rem | readRem() | 直接转发 |
|
|
466
490
|
| `edit_rem` | edit-rem | — | daemon handler 编排 |
|
|
467
491
|
| `read_tree` | read-tree | readTree() | 直接转发 |
|
|
492
|
+
| `read_rem_in_tree` | read-rem-in-tree | readRemInTree() | daemon handler 编排 + Plugin 转发 |
|
|
468
493
|
| `edit_tree` | edit-tree | — | daemon handler 编排 |
|
|
469
494
|
| `read_globe` | read-globe | readGlobe() | 直接转发 |
|
|
470
495
|
| `read_context` | read-context | readContext() | 直接转发 |
|
|
@@ -571,7 +596,7 @@ Portal:portalType [R], portalDirectlyIncludedRem [Portal-W]
|
|
|
571
596
|
{ "i": "a", "onlyAudio": true, "url": "..." } // 音频
|
|
572
597
|
```
|
|
573
598
|
|
|
574
|
-
> 在 RemObject 格式化 JSON 中,数组内对象展开为多行。构造 edit-
|
|
599
|
+
> 在 RemObject 格式化 JSON 中,数组内对象展开为多行。构造 edit-tree 的 oldStr/newStr 或 edit-rem 的 changes 中 RichText 值时,需注意多行格式。
|
|
575
600
|
|
|
576
601
|
**⚠️ highlightColor vs h — 两种完全不同的高亮**:
|
|
577
602
|
|
|
@@ -582,7 +607,7 @@ Portal:portalType [R], portalDirectlyIncludedRem [Portal-W]
|
|
|
582
607
|
|
|
583
608
|
`h` 颜色值:0=无, 1=Red, 2=Orange, 3=Yellow, 4=Green, 5=Purple, 6=Blue, 7=Gray, 8=Brown, 9=Pink。
|
|
584
609
|
|
|
585
|
-
**序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F
|
|
610
|
+
**序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F)排在所有小写字母之前。这对乐观并发检测和 edit-tree 的 str_replace 至关重要。
|
|
586
611
|
|
|
587
612
|
---
|
|
588
613
|
|
|
@@ -690,17 +715,17 @@ read-tree / read-globe / read-context 的输出核心是 Markdown 大纲文本
|
|
|
690
715
|
|
|
691
716
|
| 前缀 | 用途 | 写入命令 |
|
|
692
717
|
|:-----|:-----|:---------|
|
|
693
|
-
| `rem:{remId}` | RemObject
|
|
694
|
-
| `tree:{remId}` | Markdown 大纲 | read-tree |
|
|
695
|
-
| `tree-depth:{remId}` 等 | read-tree 参数 | read-tree |
|
|
718
|
+
| `rem:{remId}` | RemObject 对象 | read-rem, read-rem-in-tree |
|
|
719
|
+
| `tree:{remId}` | Markdown 大纲 | read-tree, read-rem-in-tree |
|
|
720
|
+
| `tree-depth:{remId}` 等 | read-tree 参数 | read-tree, read-rem-in-tree |
|
|
696
721
|
|
|
697
722
|
- LRU 淘汰:上限 200 条目
|
|
698
723
|
- disconnect 关闭 daemon 时缓存自动消失
|
|
699
724
|
- **没有 TTL**——三道防线的并发检测已能捕获所有陈旧数据
|
|
700
725
|
|
|
701
|
-
### 9.2
|
|
726
|
+
### 9.2 安全防线
|
|
702
727
|
|
|
703
|
-
`edit-rem` 和 `edit-tree`
|
|
728
|
+
`edit-rem` 和 `edit-tree` 编辑数据时,实施多道防线防止数据损坏:
|
|
704
729
|
|
|
705
730
|
#### 防线 1:缓存存在性检查
|
|
706
731
|
|
|
@@ -718,7 +743,16 @@ edit 时重新从 SDK 读取最新数据 → 与缓存严格比较
|
|
|
718
743
|
|
|
719
744
|
如果 Rem 在 read 之后被外部修改(用户在 RemNote UI 中编辑、其他 Agent 修改等),数据不一致时拒绝编辑,**且不更新缓存**——迫使 Agent 重新 read。
|
|
720
745
|
|
|
721
|
-
#### 防线 3
|
|
746
|
+
#### edit-rem 防线 3:字段白名单校验
|
|
747
|
+
|
|
748
|
+
```
|
|
749
|
+
changes 中的字段必须在 RW 白名单内,枚举值必须合法
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
- 只读字段(R / R-F)写入时**警告跳过**,不阻断其他字段
|
|
753
|
+
- 枚举值非法时**报错拒绝**(如 `type: "invalid"`)
|
|
754
|
+
|
|
755
|
+
#### edit-tree 防线 3:str_replace 精确匹配
|
|
722
756
|
|
|
723
757
|
```
|
|
724
758
|
oldStr 必须在目标文本中恰好匹配 1 次
|
|
@@ -733,7 +767,8 @@ oldStr 必须在目标文本中恰好匹配 1 次
|
|
|
733
767
|
| 场景 | 缓存行为 |
|
|
734
768
|
|:-----|:---------|
|
|
735
769
|
| 写入全部成功 | 从 SDK 重新读取最新状态 → **更新缓存** |
|
|
736
|
-
|
|
|
770
|
+
| 防线拒绝(缓存缺失 / 并发冲突) | **不更新缓存**(迫使 Agent 重新 read) |
|
|
771
|
+
| 枚举值非法 | **报错拒绝**,不更新缓存 |
|
|
737
772
|
| 部分写入失败 | **不更新缓存** |
|
|
738
773
|
|
|
739
774
|
---
|
|
@@ -854,13 +889,12 @@ Agent 遇到错误时的诊断和恢复指南:
|
|
|
854
889
|
| has not been read yet | 未先执行 read-rem / read-tree | 执行对应 read 命令后重试 |
|
|
855
890
|
| has been modified since last read | Rem 在 read 和 edit 之间被外部修改 | 重新执行 read 获取最新状态后重试 |
|
|
856
891
|
|
|
857
|
-
### str_replace 错误
|
|
892
|
+
### edit-tree str_replace 错误
|
|
858
893
|
|
|
859
894
|
| 错误 | 原因 | 恢复 |
|
|
860
895
|
|:-----|:-----|:-----|
|
|
861
|
-
| old_str not found | oldStr 在目标文本中不存在 | 检查 oldStr
|
|
896
|
+
| old_str not found | oldStr 在目标文本中不存在 | 检查 oldStr 是否精确匹配(含空格、换行、缩进) |
|
|
862
897
|
| old_str matches N locations | oldStr 匹配到多个位置 | 扩大 oldStr 范围,包含更多上下文以唯一定位 |
|
|
863
|
-
| invalid JSON | 替换后的文本不是合法 JSON | 检查 newStr 的引号、逗号、括号完整性 |
|
|
864
898
|
|
|
865
899
|
### edit-tree 专用错误
|
|
866
900
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# read-rem-in-tree — 子树大纲 + 节点属性一次获取
|
|
2
|
+
|
|
3
|
+
> `read_tree` + `read_rem` 的完美结合体。一次调用同时获取 Markdown 大纲和每个节点的完整 RemObject JSON,建立双重缓存。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 适用场景
|
|
8
|
+
|
|
9
|
+
- 需要同时查看子树结构和节点详细属性(如批量编辑前的全量读取)
|
|
10
|
+
- 一次调用建立 tree 缓存(供 `edit_tree`)和 rem 缓存(供 `edit_rem`)
|
|
11
|
+
- 替代连续调用 `read_tree` + 多次 `read_rem` 的场景
|
|
12
|
+
|
|
13
|
+
## 不适用场景
|
|
14
|
+
|
|
15
|
+
- 只需大纲不需属性 → 用 `read_tree`(更轻量)
|
|
16
|
+
- 只需单个 Rem 属性 → 用 `read_rem`
|
|
17
|
+
- 大规模子树(>50 节点) → 每节点 40+ SDK 调用,性能开销大
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 命令格式
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# 人类模式
|
|
25
|
+
remnote-bridge read-rem-in-tree <remId> [options]
|
|
26
|
+
|
|
27
|
+
# JSON 模式
|
|
28
|
+
remnote-bridge --json read-rem-in-tree '{"remId":"...","depth":3,"maxNodes":50}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 参数
|
|
32
|
+
|
|
33
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
34
|
+
|:-----|:-----|:-------|:-----|
|
|
35
|
+
| `remId` | string | **必需** | 子树根节点的 Rem ID |
|
|
36
|
+
| `depth` | number | 3 | 递归展开深度(-1 = 无限) |
|
|
37
|
+
| `maxNodes` | number | **50** | 全局节点总预算(注意比 read_tree 的 200 低) |
|
|
38
|
+
| `maxSiblings` | number | 20 | 单个父节点下最大可见子节点数 |
|
|
39
|
+
| `ancestorLevels` | number | 0 | 向上追溯祖先层数(上限 10) |
|
|
40
|
+
| `fields` | string[] | - | RemObject 字段过滤(只返回指定子集) |
|
|
41
|
+
| `full` | boolean | false | 返回全部 51 个 RemObject 字段 |
|
|
42
|
+
| `includePowerup` | boolean | false | 包含 Powerup 系统数据 |
|
|
43
|
+
|
|
44
|
+
## 输出
|
|
45
|
+
|
|
46
|
+
### JSON 模式
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{
|
|
50
|
+
"ok": true,
|
|
51
|
+
"command": "read-rem-in-tree",
|
|
52
|
+
"data": {
|
|
53
|
+
"rootId": "kLr...",
|
|
54
|
+
"depth": 3,
|
|
55
|
+
"nodeCount": 15,
|
|
56
|
+
"outline": "# 数据结构 <!--kLr type:concept doc-->\n ...",
|
|
57
|
+
"remObjects": {
|
|
58
|
+
"kLr": { "id": "kLr", "text": [...], "type": "concept", ... },
|
|
59
|
+
"ABC": { "id": "ABC", "text": [...], ... }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"ancestors": [...], // 可选
|
|
63
|
+
"cacheOverridden": {...}, // 可选
|
|
64
|
+
"powerupFiltered": {...} // 可选
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 核心字段
|
|
69
|
+
|
|
70
|
+
- `outline`:Markdown 大纲文本,与 `read_tree` 输出格式完全一致
|
|
71
|
+
- `remObjects`:扁平 map `{ remId → RemObject }`,每个 RemObject 与 `read_rem` 输出一致
|
|
72
|
+
- 默认启用 Token Slimming(省略默认值字段)
|
|
73
|
+
- `fields` / `full` 参数控制过滤行为
|
|
74
|
+
|
|
75
|
+
## 缓存行为
|
|
76
|
+
|
|
77
|
+
| 缓存 Key | 内容 | 用途 |
|
|
78
|
+
|:---------|:-----|:-----|
|
|
79
|
+
| `tree:{remId}` | outline 大纲文本 | `edit_tree` 前置缓存 |
|
|
80
|
+
| `tree-depth:{remId}` 等 | 读取参数 | `edit_tree` 乐观并发检测 |
|
|
81
|
+
| `rem:{nodeRemId}` | 完整 RemObject(N 个) | `edit_rem` 前置缓存 |
|
|
82
|
+
|
|
83
|
+
总缓存条目:1(tree) + 3(参数) + N(rem) ≈ N+4。注意 LRU 上限 200。
|
|
84
|
+
|
|
85
|
+
## 典型工作流
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
read_rem_in_tree → 一次获取全部信息
|
|
89
|
+
↓
|
|
90
|
+
edit_tree 结构编辑(tree 缓存已就绪)
|
|
91
|
+
+
|
|
92
|
+
edit_rem 属性编辑(rem 缓存已就绪)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 关联工具
|
|
96
|
+
|
|
97
|
+
- `read_tree`:只需大纲,更轻量
|
|
98
|
+
- `read_rem`:只需单个 Rem 属性
|
|
99
|
+
- `edit_tree`:结构编辑(需先 read_tree 或 read_rem_in_tree)
|
|
100
|
+
- `edit_rem`:属性编辑(需先 read_rem 或 read_rem_in_tree)
|