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
|
@@ -11,6 +11,7 @@ import type { ReactRNPlugin } from '@remnote/plugin-sdk';
|
|
|
11
11
|
import type { BridgeRequest } from './websocket-client';
|
|
12
12
|
import { readRem } from '../services/read-rem';
|
|
13
13
|
import { readTree } from '../services/read-tree';
|
|
14
|
+
import { readRemInTree } from '../services/read-rem-in-tree';
|
|
14
15
|
import { readGlobe } from '../services/read-globe';
|
|
15
16
|
import { readContext } from '../services/read-context';
|
|
16
17
|
import { writeRemFields } from '../services/write-rem-fields';
|
|
@@ -36,6 +37,8 @@ export function createMessageRouter(plugin: ReactRNPlugin): (request: BridgeRequ
|
|
|
36
37
|
return readRem(plugin, request.payload as { remId: string; includePowerup?: boolean });
|
|
37
38
|
case 'read_tree':
|
|
38
39
|
return readTree(plugin, request.payload as { remId: string; depth?: number; maxNodes?: number; maxSiblings?: number; ancestorLevels?: number; includePowerup?: boolean });
|
|
40
|
+
case 'read_rem_in_tree':
|
|
41
|
+
return readRemInTree(plugin, request.payload as { remId: string; depth?: number; maxNodes?: number; maxSiblings?: number; ancestorLevels?: number; includePowerup?: boolean });
|
|
39
42
|
case 'write_rem_fields':
|
|
40
43
|
return writeRemFields(plugin, request.payload as { remId: string; changes: Record<string, unknown> });
|
|
41
44
|
case 'create_rem':
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read-rem-in-tree service — read_tree + 对每个节点 buildRemObject
|
|
3
|
+
*
|
|
4
|
+
* 同态命名:read_rem_in_tree (action) → read-rem-in-tree.ts (文件) → readRemInTree (函数)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactRNPlugin } from '@remnote/plugin-sdk';
|
|
8
|
+
import type { RemObject } from '../types';
|
|
9
|
+
import { readTree, type ReadTreePayload, type ReadTreeResult } from './read-tree';
|
|
10
|
+
import { buildRemObject } from './read-rem';
|
|
11
|
+
|
|
12
|
+
export interface ReadRemInTreePayload extends ReadTreePayload {}
|
|
13
|
+
|
|
14
|
+
export interface ReadRemInTreeResult extends ReadTreeResult {
|
|
15
|
+
/** remId → RemObject 的扁平映射 */
|
|
16
|
+
remObjects: Record<string, RemObject & { powerupFiltered?: { tags: number; children: number } }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 读取 Rem 子树大纲 + 对树中每个节点构建完整 RemObject。
|
|
21
|
+
*
|
|
22
|
+
* @throws Error — Rem 不存在、节点数超限
|
|
23
|
+
*/
|
|
24
|
+
export async function readRemInTree(
|
|
25
|
+
plugin: ReactRNPlugin,
|
|
26
|
+
payload: ReadRemInTreePayload,
|
|
27
|
+
): Promise<ReadRemInTreeResult> {
|
|
28
|
+
// 1. 获取 outline + nodeRemIds(复用 readTree)
|
|
29
|
+
const treeResult = await readTree(plugin, payload);
|
|
30
|
+
|
|
31
|
+
// 2. 用 readTree 遍历时收集的 nodeRemIds,直接并行 buildRemObject
|
|
32
|
+
const includePowerup = payload.includePowerup ?? false;
|
|
33
|
+
const remObjects: ReadRemInTreeResult['remObjects'] = {};
|
|
34
|
+
|
|
35
|
+
await Promise.all(treeResult.nodeRemIds.map(async (id) => {
|
|
36
|
+
const rem = await plugin.rem.findOne(id);
|
|
37
|
+
if (rem) {
|
|
38
|
+
remObjects[id] = await buildRemObject(plugin, rem, { includePowerup });
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
return { ...treeResult, remObjects };
|
|
43
|
+
}
|
|
@@ -33,6 +33,21 @@ export async function readRem(
|
|
|
33
33
|
throw new Error(`Rem not found: ${payload.remId}`);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
return buildRemObject(plugin, rem, { includePowerup });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 从已有 Rem 对象组装完整 RemObject(跳过 findOne)。
|
|
41
|
+
*
|
|
42
|
+
* 供 readRem 和 readRemInTree 共享。
|
|
43
|
+
*/
|
|
44
|
+
export async function buildRemObject(
|
|
45
|
+
plugin: ReactRNPlugin,
|
|
46
|
+
rem: Rem,
|
|
47
|
+
options: { includePowerup?: boolean },
|
|
48
|
+
): Promise<RemObject & { powerupFiltered?: { tags: number; children: number } }> {
|
|
49
|
+
const { includePowerup = false } = options;
|
|
50
|
+
|
|
36
51
|
// 并行获取所有异步字段
|
|
37
52
|
const [
|
|
38
53
|
isDocument,
|
|
@@ -196,7 +211,7 @@ export async function readRem(
|
|
|
196
211
|
portalType: remTypeToString(rem.type as number) === 'portal'
|
|
197
212
|
? portalTypeToString(portalType as number)
|
|
198
213
|
: null,
|
|
199
|
-
portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id),
|
|
214
|
+
portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id).sort(),
|
|
200
215
|
|
|
201
216
|
// 属性类型
|
|
202
217
|
propertyType: (propertyType as PropertyTypeValue | undefined) ?? null,
|
|
@@ -205,27 +220,27 @@ export async function readRem(
|
|
|
205
220
|
enablePractice,
|
|
206
221
|
practiceDirection: practiceDirection as RemObject['practiceDirection'],
|
|
207
222
|
|
|
208
|
-
// 关联 —
|
|
209
|
-
tags: filteredTagRems.map(r => r._id),
|
|
210
|
-
sources: sourceRems.map(r => r._id),
|
|
211
|
-
aliases: aliasRems.map(r => r._id),
|
|
223
|
+
// 关联 — 直接关系(排序保证确定性序列化)
|
|
224
|
+
tags: filteredTagRems.map(r => r._id).sort(),
|
|
225
|
+
sources: sourceRems.map(r => r._id).sort(),
|
|
226
|
+
aliases: aliasRems.map(r => r._id).sort(),
|
|
212
227
|
|
|
213
228
|
// 关联 — 引用关系
|
|
214
|
-
remsBeingReferenced: refsBeingReferenced.map(r => r._id),
|
|
215
|
-
deepRemsBeingReferenced: deepRefsBeingReferenced.map(r => r._id),
|
|
216
|
-
remsReferencingThis: refsReferencingThis.map(r => r._id),
|
|
229
|
+
remsBeingReferenced: refsBeingReferenced.map(r => r._id).sort(),
|
|
230
|
+
deepRemsBeingReferenced: deepRefsBeingReferenced.map(r => r._id).sort(),
|
|
231
|
+
remsReferencingThis: refsReferencingThis.map(r => r._id).sort(),
|
|
217
232
|
|
|
218
233
|
// 关联 — 标签体系
|
|
219
|
-
taggedRem: taggedRems.map(r => r._id),
|
|
220
|
-
ancestorTagRem: ancestorTagRems.map(r => r._id),
|
|
221
|
-
descendantTagRem: descendantTagRems.map(r => r._id),
|
|
234
|
+
taggedRem: taggedRems.map(r => r._id).sort(),
|
|
235
|
+
ancestorTagRem: ancestorTagRems.map(r => r._id).sort(),
|
|
236
|
+
descendantTagRem: descendantTagRems.map(r => r._id).sort(),
|
|
222
237
|
|
|
223
238
|
// 关联 — 层级遍历
|
|
224
|
-
descendants: descendantRems.map(r => r._id),
|
|
225
|
-
siblingRem: siblingRems.map(r => r._id),
|
|
226
|
-
portalsAndDocumentsIn: portalsAndDocsIn.map(r => r._id),
|
|
227
|
-
allRemInDocumentOrPortal: allRemInDocOrPortal.map(r => r._id),
|
|
228
|
-
allRemInFolderQueue: allRemInFolderQ.map(r => r._id),
|
|
239
|
+
descendants: descendantRems.map(r => r._id).sort(),
|
|
240
|
+
siblingRem: siblingRems.map(r => r._id).sort(),
|
|
241
|
+
portalsAndDocumentsIn: portalsAndDocsIn.map(r => r._id).sort(),
|
|
242
|
+
allRemInDocumentOrPortal: allRemInDocOrPortal.map(r => r._id).sort(),
|
|
243
|
+
allRemInFolderQueue: allRemInFolderQ.map(r => r._id).sort(),
|
|
229
244
|
|
|
230
245
|
// 位置 / 统计
|
|
231
246
|
positionAmongstSiblings: position ?? null,
|
|
@@ -43,6 +43,8 @@ export interface ReadTreeResult {
|
|
|
43
43
|
depth: number;
|
|
44
44
|
nodeCount: number;
|
|
45
45
|
outline: string;
|
|
46
|
+
/** 树中所有节点的 remId 列表(遍历顺序) */
|
|
47
|
+
nodeRemIds: string[];
|
|
46
48
|
/** 祖先链(从直接父亲到最远祖先,由近及远) */
|
|
47
49
|
ancestors?: AncestorInfo[];
|
|
48
50
|
powerupFiltered?: { tags: number; children: number };
|
|
@@ -75,6 +77,7 @@ export async function readTree(
|
|
|
75
77
|
let totalFilteredTags = 0;
|
|
76
78
|
let totalFilteredChildren = 0;
|
|
77
79
|
const budget = { remaining: maxNodes };
|
|
80
|
+
const nodeRemIds: string[] = [];
|
|
78
81
|
|
|
79
82
|
/**
|
|
80
83
|
* 递归构建 OutlineNode 树。
|
|
@@ -86,6 +89,7 @@ export async function readTree(
|
|
|
86
89
|
async function buildNode(rem: Rem, currentDepth: number, maxDepth: number): Promise<OutlineNode> {
|
|
87
90
|
nodeCount++;
|
|
88
91
|
budget.remaining--;
|
|
92
|
+
nodeRemIds.push(rem._id);
|
|
89
93
|
|
|
90
94
|
const allChildren = await rem.getChildrenRem();
|
|
91
95
|
const children = includePowerup ? allChildren : await filterNoisyChildren(allChildren);
|
|
@@ -179,6 +183,7 @@ export async function readTree(
|
|
|
179
183
|
depth,
|
|
180
184
|
nodeCount,
|
|
181
185
|
outline,
|
|
186
|
+
nodeRemIds,
|
|
182
187
|
};
|
|
183
188
|
|
|
184
189
|
// 祖先链构建
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 多 daemon 连接:Plugin 同时连接 ALL_WS_PORTS 对应的 4 个槽位。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export const DEFAULT_PLUGIN_VERSION = '0.2.
|
|
8
|
+
export const DEFAULT_PLUGIN_VERSION = '0.2.1';
|
|
9
9
|
|
|
10
10
|
/** 4 个固定 WS 端口,对应 4 个 daemon 槽位 */
|
|
11
11
|
export const ALL_WS_PORTS = [29100, 29110, 29120, 29130] as const;
|
|
@@ -18,6 +18,7 @@ description: "RemNote 知识库操作指南。通过 remnote-bridge 命令行工
|
|
|
18
18
|
| read-rem | `instructions/read-rem.md` |
|
|
19
19
|
| edit-rem | `instructions/edit-rem.md` |
|
|
20
20
|
| read-tree | `instructions/read-tree.md` |
|
|
21
|
+
| read-rem-in-tree | `instructions/read-rem-in-tree.md` |
|
|
21
22
|
| edit-tree | `instructions/edit-tree.md` |
|
|
22
23
|
| read-globe | `instructions/read-globe.md` |
|
|
23
24
|
| read-context | `instructions/read-context.md` |
|
|
@@ -222,6 +223,7 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
|
|
|
222
223
|
用户当前在看什么("当前页面") → read-context
|
|
223
224
|
某个 Rem 的子树("展开这个主题") → read-tree <remId>
|
|
224
225
|
某个 Rem 的详细属性("详细信息") → read-rem <remId>
|
|
226
|
+
子树 + 每个节点属性("批量标注") → read-rem-in-tree <remId>
|
|
225
227
|
按关键词搜索("搜索 X") → search <query>(中文搜索有限制,见下方说明)
|
|
226
228
|
```
|
|
227
229
|
|
|
@@ -233,8 +235,9 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
|
|
|
233
235
|
| 我在编辑什么 | `read-context --mode focus` | 鱼眼视图(焦点 depth=3,siblings depth=1,叔伯 depth=0),**无缓存** |
|
|
234
236
|
| 当前页面内容 | `read-context --mode page` | 均匀展开,**无缓存** |
|
|
235
237
|
| 展开某主题细节 | `read-tree <id>` | 完整子树,**有缓存**供 edit-tree |
|
|
238
|
+
| 展开子树 + 每个节点属性 | `read-rem-in-tree <id>` | 大纲 + RemObject,**双重缓存**供 edit-tree 和 edit-rem |
|
|
236
239
|
|
|
237
|
-
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有焦点(focus 模式)或打开页面(page 模式)。
|
|
240
|
+
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有焦点(focus 模式)或打开页面(page 模式)。
|
|
238
241
|
|
|
239
242
|
### 修改:用户想改什么?
|
|
240
243
|
|
|
@@ -246,11 +249,43 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
|
|
|
246
249
|
|
|
247
250
|
**关键区分**:`edit-rem` 修改 Rem 的**属性**,`edit-tree` 修改 Rem 之间的**结构关系**。`edit-tree` **禁止修改行内容**。
|
|
248
251
|
|
|
252
|
+
### read-rem-in-tree:何时用、怎么用
|
|
253
|
+
|
|
254
|
+
`read-rem-in-tree` = `read-tree` + 批量 `read-rem` 的合体。一次调用同时获取子树大纲和每个节点的 RemObject(含完整 RichText),同时建立 `tree:` 和 `rem:` 双重缓存。
|
|
255
|
+
|
|
256
|
+
**何时用**:当你需要对一棵子树中的**多个节点**做属性修改(edit-rem)时。典型场景:
|
|
257
|
+
|
|
258
|
+
- 批量标注重点(给多个节点加高亮/粗体)
|
|
259
|
+
- 批量修改类型(把多个普通 Rem 改为 concept)
|
|
260
|
+
- 读取子树后需要检查多个节点的 RichText 再决定怎么改
|
|
261
|
+
|
|
262
|
+
**何时不用**:只需要看大纲结构(用 `read-tree`),或只改 1-2 个节点属性(用 `read-tree` + `read-rem`)。
|
|
263
|
+
|
|
264
|
+
**用法示例**(课本划重点场景):
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# 1. 一次获取大纲 + 所有节点属性(默认 maxNodes=50)
|
|
268
|
+
read-rem-in-tree --json '{"remId":"kLr...","depth":3,"maxNodes":30}'
|
|
269
|
+
|
|
270
|
+
# 返回:outline(大纲文本)+ remObjects(每个节点的 RemObject)
|
|
271
|
+
|
|
272
|
+
# 2. 从 remObjects 中找到要标注的节点,直接 edit-rem(rem 缓存已就绪)
|
|
273
|
+
edit-rem --json '{"remId":"ABC","changes":{"highlightColor":"Yellow"}}'
|
|
274
|
+
edit-rem --json '{"remId":"DEF","changes":{"text":["关键词",{"b":true,"h":1,"i":"m","text":"重点"}]}}'
|
|
275
|
+
|
|
276
|
+
# 3. 如需结构变更,直接 edit-tree(tree 缓存已就绪)
|
|
277
|
+
edit-tree --json '{"remId":"kLr...","oldStr":"...","newStr":"..."}'
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**对比效率**:修改 10 个节点时,`read-rem-in-tree` = 1 次调用;`read-tree` + 10×`read-rem` = 11 次调用。
|
|
281
|
+
|
|
249
282
|
---
|
|
250
283
|
|
|
251
284
|
## 3. 标准工作流
|
|
252
285
|
|
|
253
|
-
### ⚠️
|
|
286
|
+
### ⚠️ 标准模式(推荐):connect 后需要用户配合
|
|
287
|
+
|
|
288
|
+
**标准模式是推荐的日常使用方式**。用户在自己的浏览器中操作 RemNote,Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
|
|
254
289
|
|
|
255
290
|
`connect` 成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成操作,Plugin 才能连接到 daemon:
|
|
256
291
|
|
|
@@ -266,11 +301,18 @@ Rem 的属性(文本、类型、格式、标签) → edit-rem (前置
|
|
|
266
301
|
|
|
267
302
|
**你必须**:执行 `connect` 后,**立即告知用户需要完成上述操作**,不要直接调用业务命令。引导用户完成后,用 `health` 确认三层就绪再继续。
|
|
268
303
|
|
|
269
|
-
### Headless
|
|
304
|
+
### Headless 模式(特殊场景,不推荐日常使用)
|
|
305
|
+
|
|
306
|
+
通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
|
|
307
|
+
|
|
308
|
+
**⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。Agent 无法感知用户正在浏览和操作的页面,协作体验大打折扣。
|
|
270
309
|
|
|
271
|
-
|
|
310
|
+
**仅在以下场景使用 headless**:
|
|
311
|
+
- 用户明确要求在**服务器/无 GUI 环境**中运行
|
|
312
|
+
- 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
|
|
313
|
+
- 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
|
|
272
314
|
|
|
273
|
-
|
|
315
|
+
**默认始终使用标准模式**,除非用户主动要求 headless。
|
|
274
316
|
|
|
275
317
|
#### 首次使用(setup)
|
|
276
318
|
|
|
@@ -315,9 +357,9 @@ setup 只需执行一次。之后每次连接直接用 `connect --headless`。
|
|
|
315
357
|
4. read-globe -- 了解知识库结构(首次探索)
|
|
316
358
|
或 read-context -- 了解用户当前上下文
|
|
317
359
|
5. search "关键词" -- 定位目标 Rem(结果不进缓存!)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
360
|
+
6a. [单节点] read-tree <id> + read-rem <id> -- 各自建立缓存
|
|
361
|
+
6b. [多节点] read-rem-in-tree <id> -- 一次建立双重缓存(推荐 ≥3 个节点需修改时)
|
|
362
|
+
7. edit-rem / edit-tree -- 执行修改
|
|
321
363
|
9. disconnect -- 结束会话(缓存全部清空,幂等)
|
|
322
364
|
```
|
|
323
365
|
|
|
@@ -43,7 +43,9 @@ remnote-bridge --json connect --instance work
|
|
|
43
43
|
| 2 | 29120 | 29121 | 29122 |
|
|
44
44
|
| 3 | 29130 | 29131 | 29132 |
|
|
45
45
|
|
|
46
|
-
**实例名解析优先级**:CLI `--instance` 参数 > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。
|
|
46
|
+
**实例名解析优先级**:CLI `--instance` 参数 > 环境变量 `REMNOTE_BRIDGE_INSTANCE` > 默认值 `default`。
|
|
47
|
+
|
|
48
|
+
**⚠️ `headless` 是保留实例名**:`--instance headless` 会直接报错。headless 模式必须使用专用的 `--headless` 全局选项(见下方 Headless 模式章节)。
|
|
47
49
|
|
|
48
50
|
**首次使用多实例时**,用户需在 RemNote 中为每个实例分别配置 dev plugin URL(对应各自的 Plugin 服务端口)。
|
|
49
51
|
|
|
@@ -51,13 +53,20 @@ remnote-bridge --json connect --instance work
|
|
|
51
53
|
|
|
52
54
|
## 两种模式
|
|
53
55
|
|
|
54
|
-
###
|
|
56
|
+
### 标准模式(默认,推荐)
|
|
57
|
+
|
|
58
|
+
**标准模式是推荐的日常使用方式**。启动 daemon 后用户在自己的浏览器中加载 Plugin。优势:Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
### Headless 模式(`--headless`,特殊场景)
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
自动启动 headless Chrome 加载 Plugin,无需用户操作。
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
**⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。
|
|
65
|
+
|
|
66
|
+
**仅在以下场景使用 headless**:
|
|
67
|
+
- 用户明确要求在**服务器/无 GUI 环境**中运行
|
|
68
|
+
- 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
|
|
69
|
+
- 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
|
|
61
70
|
|
|
62
71
|
**前置条件**:必须先执行 `setup` 完成 RemNote 登录。
|
|
63
72
|
|
|
@@ -74,6 +83,15 @@ remnote-bridge --json connect --headless
|
|
|
74
83
|
|
|
75
84
|
Headless 模式下 Plugin 可能需要 10-30 秒才能连接到 daemon,使用 `health` 确认就绪。
|
|
76
85
|
|
|
86
|
+
`--headless` 是全局选项,headless 会话中**所有命令都需要带上**:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
remnote-bridge --headless connect # 启动
|
|
90
|
+
remnote-bridge --headless health # 检查
|
|
91
|
+
remnote-bridge --headless read-rem --json '{"remId":"..."}' # 业务命令
|
|
92
|
+
remnote-bridge --headless disconnect # 结束
|
|
93
|
+
```
|
|
94
|
+
|
|
77
95
|
排查工具:`health --diagnose`(截图+状态+console 错误)、`health --reload`(重载 Chrome 页面)。
|
|
78
96
|
|
|
79
97
|
---
|
|
@@ -82,12 +100,17 @@ Headless 模式下 Plugin 可能需要 10-30 秒才能连接到 daemon,使用
|
|
|
82
100
|
|
|
83
101
|
`connect`(不传 `--headless`)成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成以下操作:
|
|
84
102
|
|
|
103
|
+
> **⚠️ 防幻觉红线**:本插件是**开发者插件**,通过「开发你的插件」功能加载本地 URL。
|
|
104
|
+
> - **禁止**告诉用户"去插件市场/商店搜索安装"——本插件**不在 RemNote 插件市场中**
|
|
105
|
+
> - **禁止**告诉用户"Settings → Plugins"——这个路径不存在
|
|
106
|
+
> - **禁止**编造不存在的安装流程——严格按照下方步骤引导用户
|
|
107
|
+
|
|
85
108
|
### 首次使用(RemNote 从未加载过此插件)
|
|
86
109
|
|
|
87
110
|
1. 打开 RemNote 桌面端或网页端
|
|
88
|
-
2.
|
|
89
|
-
3.
|
|
90
|
-
4. 在输入框中填入 connect 输出的 Plugin
|
|
111
|
+
2. 点击左侧边栏底部的**插件图标**(拼图形状)
|
|
112
|
+
3. 点击「**开发你的插件**」(Develop Your Plugin)
|
|
113
|
+
4. 在输入框中填入 connect 输出的 **Plugin 服务地址**(如 `http://localhost:29101`)
|
|
91
114
|
5. 等待插件加载完成
|
|
92
115
|
|
|
93
116
|
### 非首次使用(之前已加载过此插件)
|
|
@@ -18,12 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
通过 `--instance <name>` 指定要停止的实例。不指定时停止 `default` 实例。
|
|
20
20
|
|
|
21
|
+
`--headless` 是全局选项,用于停止 headless 模式启动的实例。
|
|
22
|
+
|
|
21
23
|
```bash
|
|
22
24
|
# 停止默认实例
|
|
23
25
|
remnote-bridge disconnect
|
|
24
26
|
|
|
25
27
|
# 停止指定实例
|
|
26
28
|
remnote-bridge disconnect --instance work
|
|
29
|
+
|
|
30
|
+
# 停止 headless 实例
|
|
31
|
+
remnote-bridge --headless disconnect
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
---
|