remnote-bridge 0.1.13 → 0.1.15
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 +147 -28
- package/README.zh-CN.md +374 -0
- package/dist/cli/commands/health.js +231 -112
- package/dist/cli/commands/read-rem-in-tree.js +84 -0
- package/dist/cli/config.js +2 -0
- package/dist/cli/daemon/registry.js +8 -0
- package/dist/cli/handlers/edit-handler.js +14 -0
- package/dist/cli/handlers/patch-engine.js +347 -0
- package/dist/cli/handlers/read-handler.js +2 -53
- 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 +53 -2
- package/dist/cli/server/ws-server.js +9 -1
- package/dist/mcp/daemon-client.js +22 -2
- package/dist/mcp/instructions.js +99 -58
- package/dist/mcp/tools/edit-tools.js +7 -2
- package/dist/mcp/tools/infra-tools.js +20 -11
- package/dist/mcp/tools/read-tools.js +88 -2
- 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/dist/manifest.json +1 -1
- package/remnote-plugin/package.json +1 -1
- package/remnote-plugin/public/manifest.json +1 -1
- 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 +31 -16
- package/remnote-plugin/src/services/read-tree.ts +5 -0
- package/remnote-plugin/src/settings.ts +1 -1
- package/skills/remnote-bridge/SKILL.md +50 -8
- package/skills/remnote-bridge/instructions/connect.md +31 -8
- package/skills/remnote-bridge/instructions/disconnect.md +5 -0
- package/skills/remnote-bridge/instructions/edit-tree.md +117 -51
- package/skills/remnote-bridge/instructions/health.md +81 -53
- package/skills/remnote-bridge/instructions/overall.md +39 -8
- package/skills/remnote-bridge/instructions/read-rem-in-tree.md +100 -0
- package/skills/remnote-bridge/instructions/read-rem.md +30 -11
- 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,58 @@ oldStr 必须在缓存大纲中恰好匹配 1 次
|
|
|
174
174
|
|
|
175
175
|
---
|
|
176
176
|
|
|
177
|
+
## 两种写法:模板模式与完整匹配模式
|
|
178
|
+
|
|
179
|
+
已有行(带 `<!--remId-->` 注释的行)在 oldStr/newStr 中支持两种写法:
|
|
180
|
+
|
|
181
|
+
### 模板模式(优先使用)
|
|
182
|
+
|
|
183
|
+
用 `{{remId}}` 引用已有行,系统在 str_replace 前自动展开为完整行内容(不含缩进)。节省 token、减少复制错误。
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
# 重排
|
|
187
|
+
oldStr: " {{id1_1}}\n {{id1_2}}"
|
|
188
|
+
newStr: " {{id1_2}}\n {{id1_1}}"
|
|
189
|
+
|
|
190
|
+
# 移动(改变缩进 = 改变父节点)
|
|
191
|
+
oldStr: " {{idA}}\n {{idT}}\n {{idB}}"
|
|
192
|
+
newStr: " {{idA}}\n {{idB}}\n {{idT}}"
|
|
193
|
+
|
|
194
|
+
# 删除(必须同时删子行)
|
|
195
|
+
oldStr: " {{idA}}\n {{idA1}}\n {{idB}}"
|
|
196
|
+
newStr: " {{idB}}"
|
|
197
|
+
|
|
198
|
+
# 新增(新增行手动写,已有行用模板)
|
|
199
|
+
oldStr: " {{idZ}}"
|
|
200
|
+
newStr: " 新增行\n {{idZ}}"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**模板规则**:
|
|
204
|
+
- `{{remId}}` 展开为**不含缩进**的完整行内容,缩进由你控制
|
|
205
|
+
- 只匹配纯字母数字(`[a-zA-Z0-9]+`),与 RemNote cloze 语法 `{{text}}` 不冲突
|
|
206
|
+
- 匹配到但不在缓存大纲中的 `{{xxx}}` 原样保留(可能是 cloze),并输出 templateWarnings
|
|
207
|
+
- 新增行没有 remId,不能用模板表示
|
|
208
|
+
|
|
209
|
+
### 完整匹配模式(回退)
|
|
210
|
+
|
|
211
|
+
直接从大纲复制已有行的完整内容(含 `<!--remId 元数据-->`)。
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
# 重排
|
|
215
|
+
oldStr: " 动态数组 <!--id1_1 type:concept-->\n 静态数组 <!--id1_2 type:concept-->"
|
|
216
|
+
newStr: " 静态数组 <!--id1_2 type:concept-->\n 动态数组 <!--id1_1 type:concept-->"
|
|
217
|
+
|
|
218
|
+
# 移动
|
|
219
|
+
oldStr: " 子节点 A <!--idA-->\n 目标行 <!--idT-->\n 子节点 B <!--idB-->"
|
|
220
|
+
newStr: " 子节点 A <!--idA-->\n 子节点 B <!--idB-->\n 目标行 <!--idT-->"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### ⚠️ 回退策略
|
|
224
|
+
|
|
225
|
+
**优先使用模板模式**。但如果模板模式连续 2+ 次因 ID 错误导致 `old_str not found`,说明当前上下文不足以准确引用 ID——**立即切换到完整匹配模式**(重新 read_tree,从最新大纲复制完整行内容),不要反复重试模板。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
177
229
|
## 支持的操作
|
|
178
230
|
|
|
179
231
|
### 新增行
|
|
@@ -181,12 +233,13 @@ oldStr 必须在缓存大纲中恰好匹配 1 次
|
|
|
181
233
|
在 newStr 中添加**无 remId 注释**的新行。新行可以使用 Markdown 前缀和箭头分隔符来设置属性。
|
|
182
234
|
|
|
183
235
|
```
|
|
184
|
-
|
|
185
|
-
|
|
236
|
+
# 模板模式
|
|
237
|
+
oldStr: " {{idA}}"
|
|
238
|
+
newStr: " 新增节点\n {{idA}}"
|
|
186
239
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
子节点 A <!--idA-->
|
|
240
|
+
# 完整匹配模式
|
|
241
|
+
oldStr: " 子节点 A <!--idA-->"
|
|
242
|
+
newStr: " 新增节点\n 子节点 A <!--idA-->"
|
|
190
243
|
```
|
|
191
244
|
|
|
192
245
|
#### 新增行的 Markdown 前缀
|
|
@@ -266,12 +319,13 @@ newStr:
|
|
|
266
319
|
示例:
|
|
267
320
|
|
|
268
321
|
```
|
|
269
|
-
|
|
270
|
-
|
|
322
|
+
# 模板模式
|
|
323
|
+
oldStr: " {{idA}}"
|
|
324
|
+
newStr: " <!--portal refs:refId1,refId2-->\n {{idA}}"
|
|
271
325
|
|
|
272
|
-
|
|
273
|
-
<!--
|
|
274
|
-
子节点 A <!--idA-->
|
|
326
|
+
# 完整匹配模式
|
|
327
|
+
oldStr: " 子节点 A <!--idA-->"
|
|
328
|
+
newStr: " <!--portal refs:refId1,refId2-->\n 子节点 A <!--idA-->"
|
|
275
329
|
```
|
|
276
330
|
|
|
277
331
|
#### 嵌套新增
|
|
@@ -279,11 +333,13 @@ newStr:
|
|
|
279
333
|
新增行下面可以再嵌套新增行,通过缩进表示父子关系:
|
|
280
334
|
|
|
281
335
|
```
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
答案行 1
|
|
285
|
-
|
|
286
|
-
|
|
336
|
+
# 模板模式
|
|
337
|
+
oldStr: " {{idA}}"
|
|
338
|
+
newStr: " 父节点 ↓\n 答案行 1\n 答案行 2\n {{idA}}"
|
|
339
|
+
|
|
340
|
+
# 完整匹配模式
|
|
341
|
+
oldStr: " 子节点 A <!--idA-->"
|
|
342
|
+
newStr: " 父节点 ↓\n 答案行 1\n 答案行 2\n 子节点 A <!--idA-->"
|
|
287
343
|
```
|
|
288
344
|
|
|
289
345
|
嵌套新增行的父 ID 通过内部占位标记 `__new_N__` 管理,创建顺序保证从浅到深。
|
|
@@ -293,14 +349,13 @@ newStr:
|
|
|
293
349
|
新行**不能**插在一个有子节点的 Rem 和它的 children 之间,否则 children 会被新行"劫持",触发 `children_captured` 错误。
|
|
294
350
|
|
|
295
351
|
```
|
|
296
|
-
❌
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
352
|
+
❌ 错误(模板):
|
|
353
|
+
oldStr: " {{idA}}" newStr: " {{idA}}\n 新行" ← idA 有子节点,新行劫持 children!
|
|
354
|
+
❌ 错误(完整匹配):
|
|
355
|
+
oldStr: " 水分子 ↓ <!--idA-->" newStr: " 水分子 ↓ <!--idA-->\n 新行" ← 同理
|
|
300
356
|
|
|
301
|
-
✅
|
|
302
|
-
|
|
303
|
-
新行 ← 不影响任何已有节点
|
|
357
|
+
✅ 正确:插在末尾
|
|
358
|
+
oldStr: " {{idZ}}" newStr: " {{idZ}}\n 新行"
|
|
304
359
|
```
|
|
305
360
|
|
|
306
361
|
#### 两步操作:创建新节点并移入已有 children
|
|
@@ -317,13 +372,13 @@ newStr:
|
|
|
317
372
|
从 newStr 中移除带 remId 的行。**必须同时删除该行的所有可见子行**,否则报 orphan_detected 错误。
|
|
318
373
|
|
|
319
374
|
```
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
子节点 B <!--idB-->
|
|
375
|
+
# 模板模式
|
|
376
|
+
oldStr: " {{idA}}\n {{idA1}}\n {{idB}}"
|
|
377
|
+
newStr: " {{idB}}"
|
|
324
378
|
|
|
325
|
-
|
|
326
|
-
子节点 B <!--idB-->
|
|
379
|
+
# 完整匹配模式
|
|
380
|
+
oldStr: " 子节点 A <!--idA-->\n 孙节点 A1 <!--idA1-->\n 子节点 B <!--idB-->"
|
|
381
|
+
newStr: " 子节点 B <!--idB-->"
|
|
327
382
|
```
|
|
328
383
|
|
|
329
384
|
删除操作按深度**从深到浅**执行(先删子后删父),确保 RemNote SDK 不会拒绝操作。
|
|
@@ -333,15 +388,13 @@ newStr:
|
|
|
333
388
|
改变行的缩进级别或位置,使其移动到新的父节点下:
|
|
334
389
|
|
|
335
390
|
```
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
子节点 B <!--idB-->
|
|
391
|
+
# 模板模式
|
|
392
|
+
oldStr: " {{idA}}\n {{idT}}\n {{idB}}"
|
|
393
|
+
newStr: " {{idA}}\n {{idB}}\n {{idT}}"
|
|
340
394
|
|
|
341
|
-
|
|
342
|
-
子节点 A <!--idA-->
|
|
343
|
-
子节点 B <!--idB-->
|
|
344
|
-
目标行 <!--idT-->
|
|
395
|
+
# 完整匹配模式
|
|
396
|
+
oldStr: " 子节点 A <!--idA-->\n 目标行 <!--idT-->\n 子节点 B <!--idB-->"
|
|
397
|
+
newStr: " 子节点 A <!--idA-->\n 子节点 B <!--idB-->\n 目标行 <!--idT-->"
|
|
345
398
|
```
|
|
346
399
|
|
|
347
400
|
### 重排行
|
|
@@ -349,15 +402,13 @@ newStr:
|
|
|
349
402
|
调换同级行的顺序:
|
|
350
403
|
|
|
351
404
|
```
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
子节点 C <!--idC-->
|
|
405
|
+
# 模板模式
|
|
406
|
+
oldStr: " {{idA}}\n {{idB}}\n {{idC}}"
|
|
407
|
+
newStr: " {{idC}}\n {{idA}}\n {{idB}}"
|
|
356
408
|
|
|
357
|
-
|
|
358
|
-
子节点 C <!--idC-->
|
|
359
|
-
子节点 A <!--idA-->
|
|
360
|
-
子节点 B <!--idB-->
|
|
409
|
+
# 完整匹配模式
|
|
410
|
+
oldStr: " 子节点 A <!--idA-->\n 子节点 B <!--idB-->\n 子节点 C <!--idC-->"
|
|
411
|
+
newStr: " 子节点 C <!--idC-->\n 子节点 A <!--idA-->\n 子节点 B <!--idB-->"
|
|
361
412
|
```
|
|
362
413
|
|
|
363
414
|
---
|
|
@@ -458,8 +509,9 @@ RemNote SDK 存在已知 bug:
|
|
|
458
509
|
4. daemon TreeEditHandler:
|
|
459
510
|
├─ 防线 1: cache.get('tree:' + remId) 存在?
|
|
460
511
|
├─ 防线 2: 用缓存的 depth/maxNodes/maxSiblings 重新 read-tree → 对比
|
|
461
|
-
├─
|
|
462
|
-
├─
|
|
512
|
+
├─ 模板展开: {{remId}} → 缓存中对应行的完整内容(不含缩进)
|
|
513
|
+
├─ 防线 3: countOccurrences(cachedOutline, expandedOldStr) === 1?
|
|
514
|
+
├─ modifiedOutline = cachedOutline.replace(expandedOldStr, expandedNewStr)
|
|
463
515
|
├─ 解析新旧大纲为树(parseOutline)
|
|
464
516
|
├─ 对比差异(diffTrees)
|
|
465
517
|
│ ├─ 根节点校验
|
|
@@ -482,28 +534,42 @@ RemNote SDK 存在已知 bug:
|
|
|
482
534
|
|
|
483
535
|
---
|
|
484
536
|
|
|
485
|
-
##
|
|
537
|
+
## 常见使用模式(JSON 模式)
|
|
538
|
+
|
|
539
|
+
> 优先使用模板模式;连续 2+ 次 `old_str not found` 则回退到完整匹配模式。
|
|
486
540
|
|
|
487
541
|
### 在指定位置插入新行
|
|
488
542
|
|
|
489
543
|
```bash
|
|
490
|
-
|
|
544
|
+
# 模板模式
|
|
545
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{idA}}","newStr":" 新增行\n {{idA}}"}'
|
|
546
|
+
|
|
547
|
+
# 完整匹配模式
|
|
548
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" 子节点 A <!--idA-->","newStr":" 新增行\n 子节点 A <!--idA-->"}'
|
|
491
549
|
```
|
|
492
550
|
|
|
493
551
|
### 删除一个叶子节点
|
|
494
552
|
|
|
495
553
|
```bash
|
|
496
|
-
remnote-bridge edit-tree
|
|
554
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{leaf}}\n","newStr":""}'
|
|
497
555
|
```
|
|
498
556
|
|
|
499
557
|
### 调换两个兄弟的顺序
|
|
500
558
|
|
|
501
559
|
```bash
|
|
502
|
-
|
|
560
|
+
# 模板模式
|
|
561
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{idA}}\n {{idB}}","newStr":" {{idB}}\n {{idA}}"}'
|
|
562
|
+
|
|
563
|
+
# 完整匹配模式
|
|
564
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" 节点 A <!--idA-->\n 节点 B <!--idB-->","newStr":" 节点 B <!--idB-->\n 节点 A <!--idA-->"}'
|
|
503
565
|
```
|
|
504
566
|
|
|
505
567
|
### 将节点移到另一个父节点下
|
|
506
568
|
|
|
507
569
|
```bash
|
|
508
|
-
|
|
570
|
+
# 模板模式
|
|
571
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{oldP}}\n {{target}}\n {{newP}}","newStr":" {{oldP}}\n {{newP}}\n {{target}}"}'
|
|
572
|
+
|
|
573
|
+
# 完整匹配模式
|
|
574
|
+
remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" 旧父 <!--oldP-->\n 目标 <!--target-->\n 新父 <!--newP-->","newStr":" 旧父 <!--oldP-->\n 新父 <!--newP-->\n 目标 <!--target-->"}'
|
|
509
575
|
```
|
|
@@ -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
|
#### 修改 / 写入
|
|
@@ -189,6 +190,8 @@ RemNote SDK → 知识库
|
|
|
189
190
|
|
|
190
191
|
一次**会话(Session)= 守护进程的生命周期**。
|
|
191
192
|
|
|
193
|
+
**标准模式(推荐)**——用户在自己的浏览器中操作 RemNote,Agent 可感知用户上下文:
|
|
194
|
+
|
|
192
195
|
```
|
|
193
196
|
connect → daemon 启动
|
|
194
197
|
↓
|
|
@@ -200,6 +203,10 @@ disconnect → daemon 关闭 → 会话结束,缓存清空
|
|
|
200
203
|
```
|
|
201
204
|
|
|
202
205
|
> **重要**:`connect` 成功只意味着 daemon 已启动,Plugin 并未自动连接。首次使用需用户在 RemNote「开发你的插件」中填入对应的 Plugin 服务地址;非首次只需刷新 RemNote 页面。必须引导用户完成此步后再用 `health` 确认就绪。
|
|
206
|
+
>
|
|
207
|
+
> **⚠️ 防幻觉红线**:本插件是**开发者插件**,通过「开发你的插件」加载本地 URL。**禁止**告诉用户去插件市场/商店搜索安装(插件不在市场中);**禁止**编造"Settings → Plugins"等不存在的路径。
|
|
208
|
+
|
|
209
|
+
**Headless 模式(不推荐日常使用)**——通过后台 Chrome 自动连接,但**会丢失用户上下文**(`read-context` 返回 headless 实例的上下文,不是用户浏览器的)。仅在以下场景使用:用户明确要求在服务器/无 GUI 环境运行、用户明确不想参与操作(全自动化)、用户不在 RemNote 前面。详见 `connect.md`。
|
|
203
210
|
|
|
204
211
|
`connect` 启动三个服务,端口由槽位自动分配:
|
|
205
212
|
|
|
@@ -239,7 +246,7 @@ remnote-bridge disconnect --instance work
|
|
|
239
246
|
| 2 | 29120 | 29121 | 29122 |
|
|
240
247
|
| 3 | 29130 | 29131 | 29132 |
|
|
241
248
|
|
|
242
|
-
**实例名解析优先级**:CLI `--instance` > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default
|
|
249
|
+
**实例名解析优先级**:CLI `--instance` > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。`headless` 是保留实例名,不可用于 `--instance`(会报错),必须使用 `--headless` 全局选项。
|
|
243
250
|
|
|
244
251
|
**Plugin 自动发现**:Plugin 启动后通过 `/api/discovery` 获取其孪生 daemon 的连接信息(WS 端口、槽位索引等),自动建立连接。一个 Plugin 可同时连接最多 4 个 daemon。
|
|
245
252
|
|
|
@@ -282,6 +289,7 @@ remnote-bridge disconnect --instance work
|
|
|
282
289
|
| `read-context` | 当前上下文视图 | mode + 参数 | 否 | `read-context.md` |
|
|
283
290
|
| `read-tree` | 读取子树为 Markdown 大纲 | remId + 展开参数 | 是(`tree:`) | `read-tree.md` |
|
|
284
291
|
| `read-rem` | 读取单个 Rem 的 JSON 属性 | remId | 是(`rem:`) | `read-rem.md` |
|
|
292
|
+
| `read-rem-in-tree` | 子树大纲 + 节点属性一次获取 | remId + 展开参数 + 过滤参数 | 是(`tree:` + `rem:`) | `read-rem-in-tree.md` |
|
|
285
293
|
| `search` | 全文搜索 | query | 否 | `search.md` |
|
|
286
294
|
|
|
287
295
|
#### 写入命令
|
|
@@ -309,10 +317,17 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
309
317
|
├─ 某个具体 Rem 的子树 → read-tree <remId>
|
|
310
318
|
│ 完整展开子树(支持深度/节点预算控制)
|
|
311
319
|
│ 结果缓存,供 edit-tree 使用
|
|
320
|
+
│ ⚠️ 如果需要读取子树后对其中多个节点执行 edit-rem,请改用 read-rem-in-tree
|
|
312
321
|
│
|
|
313
322
|
├─ 某个 Rem 的详细属性 → read-rem <remId>
|
|
314
323
|
│ 返回 51 字段的 RemObject JSON
|
|
315
324
|
│ 结果缓存,供 edit-rem 使用
|
|
325
|
+
│ ⚠️ 如果需要读取多个 Rem 属性(≥3 个)且在同一子树下,请改用 read-rem-in-tree
|
|
326
|
+
│
|
|
327
|
+
├─ 子树结构 + 每个节点的详细属性 → read-rem-in-tree <remId>
|
|
328
|
+
│ read-tree + read-rem 的合体,一次调用同时获取大纲和 RemObject
|
|
329
|
+
│ 同时建立 tree 和 rem 双重缓存,供 edit-tree 和 edit-rem 使用
|
|
330
|
+
│ 默认 maxNodes=50(比 read-tree 的 200 低,因每节点开销大)
|
|
316
331
|
│
|
|
317
332
|
└─ 按关键词搜索 → search <query>
|
|
318
333
|
全文搜索,返回匹配的 Rem 列表
|
|
@@ -327,6 +342,7 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
327
342
|
| "我现在在编辑什么" | `read-context --mode focus` | 鱼眼视图,焦点处详细 |
|
|
328
343
|
| "当前页面的内容" | `read-context --mode page` | 以页面为根展开 |
|
|
329
344
|
| "展开某个主题的细节" | `read-tree <id>` | 完整子树,可缓存供编辑 |
|
|
345
|
+
| "展开子树并查看每个节点属性" | `read-rem-in-tree <id>` | 大纲 + RemObject,双重缓存 |
|
|
330
346
|
|
|
331
347
|
#### read-globe 特性
|
|
332
348
|
|
|
@@ -390,14 +406,28 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
390
406
|
4. read-globe ← 了解知识库结构(首次探索)
|
|
391
407
|
或 read-context ← 了解用户当前上下文
|
|
392
408
|
5. search "关键词" ← 定位目标 Rem(中文搜索可能需单字策略,详见 search.md)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
409
|
+
6a. [单节点] read-tree <id> + read-rem <id> ← 各自建立缓存
|
|
410
|
+
6b. [多节点] read-rem-in-tree <id> ← 一次建立双重缓存(推荐 ≥3 个节点需修改时)
|
|
411
|
+
7. edit-rem <id> ... ← 修改 Rem 属性
|
|
396
412
|
或 edit-tree <id> ...← 修改树结构
|
|
397
413
|
9. disconnect ← 结束会话
|
|
398
414
|
```
|
|
399
415
|
|
|
400
|
-
**注意**:步骤
|
|
416
|
+
**注意**:步骤 6a/6b 是 edit-rem / edit-tree 的强制前置条件。跳过会触发防线 1 错误。步骤 2 是必须的——connect 后不引导用户加载插件就直接调用业务命令,会报"Plugin 未连接"错误。
|
|
417
|
+
|
|
418
|
+
### 4.5 批量标注工作流(课本划重点场景)
|
|
419
|
+
|
|
420
|
+
当需要对一棵子树中的多个节点进行富文本标注(行级高亮、行内荧光、粗体等)时:
|
|
421
|
+
|
|
422
|
+
1. read-rem-in-tree <id> --maxNodes 50 -- 一次获取大纲 + 所有 RemObject
|
|
423
|
+
2. 从 remObjects 中定位需要标注的节点
|
|
424
|
+
3. 对每个目标节点 edit-rem 设置格式:
|
|
425
|
+
- 行级高亮:changes.highlightColor = "Yellow"/"Red"/...
|
|
426
|
+
- 行内荧光:changes.text = [..., {"h": 3, "i": "m", "text": "关键词"}, ...]
|
|
427
|
+
- 粗体:changes.text = [..., {"b": true, "i": "m", "text": "核心概念"}, ...]
|
|
428
|
+
4. 如需结构变更(如新增/移动节点),直接 edit-tree(tree 缓存已就绪)
|
|
429
|
+
|
|
430
|
+
关键:read-rem-in-tree 同时建立了 tree 和 rem 两种缓存,后续 edit-tree 和 edit-rem 都无需再单独 read。
|
|
401
431
|
|
|
402
432
|
---
|
|
403
433
|
|
|
@@ -465,6 +495,7 @@ daemon → CLI 响应:
|
|
|
465
495
|
| `read_rem` | read-rem | readRem() | 直接转发 |
|
|
466
496
|
| `edit_rem` | edit-rem | — | daemon handler 编排 |
|
|
467
497
|
| `read_tree` | read-tree | readTree() | 直接转发 |
|
|
498
|
+
| `read_rem_in_tree` | read-rem-in-tree | readRemInTree() | daemon handler 编排 + Plugin 转发 |
|
|
468
499
|
| `edit_tree` | edit-tree | — | daemon handler 编排 |
|
|
469
500
|
| `read_globe` | read-globe | readGlobe() | 直接转发 |
|
|
470
501
|
| `read_context` | read-context | readContext() | 直接转发 |
|
|
@@ -690,9 +721,9 @@ read-tree / read-globe / read-context 的输出核心是 Markdown 大纲文本
|
|
|
690
721
|
|
|
691
722
|
| 前缀 | 用途 | 写入命令 |
|
|
692
723
|
|:-----|:-----|:---------|
|
|
693
|
-
| `rem:{remId}` | RemObject 对象 | read-rem |
|
|
694
|
-
| `tree:{remId}` | Markdown 大纲 | read-tree |
|
|
695
|
-
| `tree-depth:{remId}` 等 | read-tree 参数 | read-tree |
|
|
724
|
+
| `rem:{remId}` | RemObject 对象 | read-rem, read-rem-in-tree |
|
|
725
|
+
| `tree:{remId}` | Markdown 大纲 | read-tree, read-rem-in-tree |
|
|
726
|
+
| `tree-depth:{remId}` 等 | read-tree 参数 | read-tree, read-rem-in-tree |
|
|
696
727
|
|
|
697
728
|
- LRU 淘汰:上限 200 条目
|
|
698
729
|
- disconnect 关闭 daemon 时缓存自动消失
|