remnote-bridge 0.1.14 → 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 CHANGED
@@ -292,6 +292,12 @@ remnote-bridge addon uninstall remnote-rag --purge
292
292
 
293
293
  ## Changelog
294
294
 
295
+ ### 0.1.15 (2026-03-18)
296
+
297
+ - **Defense-2 false positive fix** — Removed `.sort()` from ID arrays in RemObject serialization that caused spurious concurrency conflicts in edit-rem
298
+ - **Documentation overhaul** — Strengthened headless mode warnings, added anti-hallucination guardrails for Plugin loading instructions, restructured edit-tree template/fallback dual-mode docs
299
+ - **Skill ↔ MCP doc sync** — Aligned connect, edit-tree, overall instructions across Skill and MCP layers
300
+
295
301
  ### 0.1.14 (2026-03-18)
296
302
 
297
303
  - **read-rem-in-tree** — New command: fetch subtree outline + all Rem objects in one call (batch read for bulk edits)
package/README.zh-CN.md CHANGED
@@ -292,6 +292,12 @@ remnote-bridge addon uninstall remnote-rag --purge
292
292
 
293
293
  ## Changelog
294
294
 
295
+ ### 0.1.15 (2026-03-18)
296
+
297
+ - **防线 2 误判修复** — 移除 RemObject 序列化中 ID 数组的 `.sort()`,消除 edit-rem 并发检测的假阳性
298
+ - **文档全面优化** — 加强 headless 模式警告、新增 Plugin 加载防幻觉红线、重组 edit-tree 模板/回退双模式文档
299
+ - **Skill ↔ MCP 文档同步** — connect、edit-tree、overall 说明在 Skill 和 MCP 层全面对齐
300
+
295
301
  ### 0.1.14 (2026-03-18)
296
302
 
297
303
  - **read-rem-in-tree** — 新命令:一次调用获取子树大纲 + 所有 Rem 对象(批量读取,用于批量编辑场景)
@@ -63,6 +63,9 @@ export class EditHandler {
63
63
  const currentJson = JSON.stringify(currentRemObject, null, 2);
64
64
  const cachedJson = JSON.stringify(cachedObj, null, 2);
65
65
  if (currentJson !== cachedJson) {
66
+ // 诊断日志:打出具体哪些字段不同,帮助定位并发误判
67
+ const diff = diffFields(cachedObj, currentRemObject);
68
+ console.error(`[defense-2] Rem ${remId} conflict detected. Changed fields: ${diff.join(', ') || '(JSON differs but no top-level field diff — possible nested change)'}`);
66
69
  // 不更新缓存 — 迫使 AI re-read
67
70
  throw new Error(`Rem ${remId} has been modified since last read. Please read it again before editing.`);
68
71
  }
@@ -126,3 +129,14 @@ export class EditHandler {
126
129
  };
127
130
  }
128
131
  }
132
+ /** 比较两个 RemObject 的顶层字段,返回值不同的 key 列表 */
133
+ function diffFields(cached, current) {
134
+ const allKeys = new Set([...Object.keys(cached), ...Object.keys(current)]);
135
+ const changed = [];
136
+ for (const key of allKeys) {
137
+ if (JSON.stringify(cached[key]) !== JSON.stringify(current[key])) {
138
+ changed.push(key);
139
+ }
140
+ }
141
+ return changed;
142
+ }
@@ -119,12 +119,20 @@ RemNote 格式设置(标题、高亮、代码等)底层通过 Powerup 机制
119
119
 
120
120
  所有操作都依赖一个活跃的会话(= 守护进程的生命周期)。
121
121
 
122
- ### 标准模式(需要用户配合)
122
+ > **⚠️ 关于 Plugin 加载方式(防幻觉红线)**:
123
+ > - 本插件是**开发者插件**,通过 RemNote「开发你的插件」功能加载本地 URL
124
+ > - **禁止**告诉用户"去插件市场/商店搜索安装"——本插件**不在 RemNote 插件市场中**
125
+ > - **禁止**告诉用户"Settings → Plugins"——这个路径不存在
126
+ > - **禁止**编造不存在的安装流程——严格按照下方步骤引导用户
127
+
128
+ ### 标准模式(默认,推荐)
129
+
130
+ **标准模式是日常使用的推荐方式**。用户在自己的浏览器中打开 RemNote 并加载 Plugin,Agent 可以通过 \`read_context\` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
123
131
 
124
132
  \`\`\`
125
133
  connect → 启动 daemon(幂等)
126
134
 
127
- ⚠️ 用户操作:确保 RemNote 中已加载插件(见下方说明)
135
+ ⚠️ 引导用户加载 Plugin(见下方 ★ 标记的步骤)
128
136
 
129
137
  health → 确认三层就绪(daemon / Plugin / SDK)
130
138
 
@@ -133,11 +141,34 @@ health → 确认三层就绪(daemon / Plugin / SDK)
133
141
  disconnect → 关闭 daemon,清空所有缓存
134
142
  \`\`\`
135
143
 
136
- ### Headless 模式(自动连接)
144
+ ### 标准模式:connect 后引导用户加载 Plugin(核心步骤)
145
+
146
+ \`connect\`(不传 headless)成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。你必须引导用户完成以下操作:
147
+
148
+ **首次使用**(RemNote 从未加载过此插件):
149
+ 1. 打开 RemNote 桌面端或网页端
150
+ 2. 点击左侧边栏底部的**插件图标**(拼图形状)
151
+ 3. 点击「**开发你的插件**」(Develop Your Plugin)
152
+ 4. 在输入框中填入 connect 输出的 **Plugin 服务地址**(如 \`http://localhost:29101\`)
153
+ 5. 等待插件加载完成
154
+
155
+ **非首次使用**(之前已加载过此插件):
156
+ - 只需**刷新 RemNote 页面**即可(浏览器 F5 或 Cmd+R)
157
+
158
+ **你必须**:执行 \`connect\` 后,**立即**将上述步骤告知用户,**禁止**跳过此步直接调用业务命令。引导用户完成后,用 \`health\` 确认三层就绪再继续。
159
+
160
+ ### Headless 模式(特殊场景,不推荐日常使用)
137
161
 
138
162
  通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
139
163
 
140
- **⚠️ 模式选择建议**:日常使用推荐**标准模式**。Headless 模式下 Chrome 在后台运行,**无法感知用户正在 RemNote 中浏览和操作的界面**(\`read_context\` 返回的是 headless Chrome 的上下文,而非用户的浏览器)。只有在全自动化场景才建议使用 Headless 模式。
164
+ **⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**:\`read_context\` 返回的是 headless Chrome 的上下文,不是用户浏览器的。Agent 无法感知用户正在浏览和操作的页面,协作体验大打折扣。
165
+
166
+ **仅在以下场景使用 headless**:
167
+ - 用户明确要求在**服务器/无 GUI 环境**中运行
168
+ - 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
169
+ - 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
170
+
171
+ **默认始终使用标准模式**,除非用户主动要求 headless。
141
172
 
142
173
  #### 首次使用(setup)
143
174
 
@@ -169,22 +200,6 @@ disconnect → 关闭 daemon + headless Chrome,清空所有缓存,清除 hea
169
200
  - \`health(reload=true)\`:重载 headless Chrome 页面
170
201
  - Plugin 始终不连接 → 可能登录 session 过期,需重新 setup
171
202
 
172
- ### ⚠️ 标准模式:connect 后需要用户配合(重要)
173
-
174
- \`connect\`(不传 headless)成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。
175
-
176
- **首次使用**(RemNote 从未加载过此插件):
177
- 1. 打开 RemNote 桌面端或网页端
178
- 2. 点击左侧边栏底部的插件图标(拼图形状)
179
- 3. 点击「开发你的插件」(Develop Your Plugin)
180
- 4. 在输入框中填入 connect 输出的 Plugin 服务地址(如 \`http://localhost:29101\`)
181
- 5. 等待插件加载完成
182
-
183
- **非首次使用**(之前已加载过此插件):
184
- - 只需**刷新 RemNote 页面**即可(浏览器 F5 或 Cmd+R)
185
-
186
- **你必须**:执行 \`connect\` 后,**立即告知用户需要完成上述操作**,不要直接调用业务命令。引导用户完成后,用 \`health\` 确认三层就绪再继续。
187
-
188
203
  ### Windows 注意事项
189
204
 
190
205
  - 默认模式秒级启动(预构建 plugin)
@@ -326,59 +341,39 @@ changes: { "type": "concept", "highlightColor": "Yellow", "fontSize": "H1" }
326
341
  - \`<!--portal refs:id1,id2-->\`(创建并引用指定 Rem)
327
342
  - \`<!--portal-->\`(创建空 Portal)
328
343
 
329
- #### str_replace 构造示例
344
+ #### 行引用模板 \`{{remId}}\`
330
345
 
331
- **示例 1:在末尾新增行**
332
- \`\`\`
333
- oldStr:
334
- 最后一个兄弟 <!--idZ-->
335
- newStr:
336
- 最后一个兄弟 <!--idZ-->
337
- 新增行
338
- \`\`\`
346
+ 已有行支持两种写法:**模板模式**(推荐)和**完整匹配模式**(回退)。
339
347
 
340
- **示例 2:删除一个带子节点的行(必须一起删)**
341
- \`\`\`
342
- oldStr:
343
- 子节点 A <!--idA-->
344
- 孙节点 A1 <!--idA1-->
345
- 子节点 B <!--idB-->
346
- newStr:
347
- 子节点 B <!--idB-->
348
- \`\`\`
348
+ **模板模式**(优先使用):用 \`{{remId}}\` 引用已有行,系统自动展开为完整内容(不含缩进)。节省 token、减少复制错误。
349
+ **完整匹配模式**(回退):直接从大纲复制完整行内容(含 \`<!--remId 元数据-->\`)。
349
350
 
350
- **示例 3:创建多行闪卡**
351
- \`\`\`
352
- oldStr:
353
- 子节点 A <!--idA-->
354
- newStr:
355
- 什么是线性回归? ↓
356
- 一种基本的回归分析方法
357
- 假设因变量与自变量呈线性关系
358
- 子节点 A <!--idA-->
359
- \`\`\`
351
+ **策略:优先模板,连续失败则回退**。如果模板模式连续 2+ 次因 ID 错误导致 \`old_str not found\`,说明当前上下文不足以准确引用 ID——立即切换到完整匹配模式(从最新大纲复制完整行内容),不要反复重试模板。
360
352
 
361
- #### 行引用模板 \`{{remId}}\`
353
+ **模板模式示例**:
354
+ \`\`\`
355
+ # 重排两个节点
356
+ oldStr: " {{id1_1}}\\n {{id1_2}}"
357
+ newStr: " {{id1_2}}\\n {{id1_1}}"
362
358
 
363
- oldStr/newStr 中使用 \`{{remId}}\` 引用缓存大纲中已有行的完整内容(不含缩进)。系统在 str_replace 前自动展开。
359
+ # 删除带子节点的行
360
+ oldStr: " {{idA}}\\n {{idA1}}\\n {{idB}}"
361
+ newStr: " {{idB}}"
364
362
 
365
- **优势**:避免抄写完整行内容(remId、元数据标记),减少 token 浪费和复制错误。
363
+ # 末尾新增行(新增行手动写,已有行用模板)
364
+ oldStr: " {{idZ}}"
365
+ newStr: " 新增行\\n {{idZ}}"
366
+ \`\`\`
366
367
 
367
- **示例:重排两个节点**
368
+ **完整匹配模式示例**(回退时使用):
368
369
  \`\`\`
369
- // 不用模板:
370
370
  oldStr: " 动态数组 <!--id1_1 type:concept-->\\n 静态数组 <!--id1_2 type:concept-->"
371
371
  newStr: " 静态数组 <!--id1_2 type:concept-->\\n 动态数组 <!--id1_1 type:concept-->"
372
-
373
- // 用模板:
374
- oldStr: " {{id1_1}}\\n {{id1_2}}"
375
- newStr: " {{id1_2}}\\n {{id1_1}}"
376
372
  \`\`\`
377
373
 
378
- **规则**:
374
+ **模板规则**:
379
375
  - \`{{remId}}\` 展开为**不含缩进**的完整行内容,缩进由你控制
380
- - 只匹配纯字母数字(\`[a-zA-Z0-9]+\`),与 RemNote cloze 语法 \`{{text}}\` 不冲突(cloze 含中文/空格/标点,不会被匹配)
381
- - 匹配到但不在缓存大纲中的 \`{{xxx}}\` 原样保留(可能是 cloze),并输出 warning
376
+ - 只匹配纯字母数字(\`[a-zA-Z0-9]+\`),与 RemNote cloze 语法 \`{{text}}\` 不冲突
382
377
  - 新增行不能用 \`{{}}\`(新增行没有 remId)
383
378
  - 可以混用:部分行用 \`{{id}}\`,部分行手动写
384
379
 
@@ -387,15 +382,13 @@ newStr: " {{id1_2}}\\n {{id1_1}}"
387
382
  在有子节点的 Rem 和其 children 之间插入新行,会导致新行"劫持"已有 children:
388
383
 
389
384
  \`\`\`
390
- 错误:插在父 Rem 和 children 之间
391
- oldStr: 水分子 <!--idA-->
392
- newStr: 水分子 ↓ <!--idA-->
393
- 新行 children 会变成新行的子节点!
385
+ 错误(模板):
386
+ oldStr: " {{idA}}" newStr: " {{idA}}\\n 新行" ← idA 有子节点,新行劫持 children!
387
+ 错误(完整匹配):
388
+ oldStr: " 水分子 ↓ <!--idA-->" newStr: " 水分子 ↓ <!--idA-->\\n 新行" 同理
394
389
 
395
390
  ✅ 正确:插在末尾
396
- oldStr: 最后一个兄弟 <!--idZ-->
397
- newStr: 最后一个兄弟 <!--idZ-->
398
- 新行
391
+ oldStr: " {{idZ}}" newStr: " {{idZ}}\\n 新行"
399
392
  \`\`\`
400
393
 
401
394
  #### 创建新节点并移动已有 children
@@ -427,7 +420,7 @@ newStr: 最后一个兄弟 <!--idZ-->
427
420
 
428
421
  \`health\` 默认查询所有活跃实例的三层状态(daemon / Plugin / SDK),返回 \`instances\` 数组。有 \`--instance\` 或 \`--headless\` 时只查询指定实例。每个实例的 \`plugin.isTwin\` 标记是否为孪生连接。
429
422
 
430
- 故障定位:无活跃实例 → \`connect\`;Plugin 未连接 → 引导用户操作 RemNote(或使用 headless 模式);SDK 未就绪 → 等待重试。
423
+ 故障定位:无活跃实例 → \`connect\`;Plugin 未连接 → 引导用户操作 RemNote(标准模式下刷新页面或首次加载插件);SDK 未就绪 → 等待重试。
431
424
 
432
425
  ### 场景 H:管理增强项目
433
426
 
@@ -643,6 +636,7 @@ tags, sources, positionAmongstSiblings, portalDirectlyIncludedRem
643
636
 
644
637
  ### 诊断决策树
645
638
 
639
+
646
640
  \`\`\`
647
641
  命令报错
648
642
  ├─ "守护进程未运行" → connect 未执行或 daemon 已超时 → 执行 connect
@@ -22,7 +22,7 @@ export function registerInfraTools(server) {
22
22
  // -------------------------------------------------------------------------
23
23
  server.addTool({
24
24
  name: 'connect',
25
- description: '启动守护进程(daemon),建立 CLI 与 RemNote Plugin 之间的通信通道。这是所有业务命令(read_rem、edit_rem、search 等)的前置步骤。\n\n适用场景:\n- 开始一次 RemNote 操作会话前,必须先调用此工具\n- 不确定 daemon 是否在运行时,也可安全调用(幂等)\n\n两种模式:\n- 标准模式(默认):启动 daemon 后需要用户在 RemNote 中手动加载 Plugin\n- Headless 模式(headless=true):自动启动 headless Chrome 加载 Plugin,无需用户操作。需先完成 setup(保存登录凭证)\n\nHeadless 模式会话持久化:connect(headless=true) 后,MCP Server 自动记住 headless 状态,后续所有工具调用(health、read_rem、edit_rem 等)自动路由到 headless 实例,无需额外参数。disconnect 或 clean 后自动清除。\n\n输出:返回 JSON,关键字段 ok、alreadyRunning、instance、slotIndex、pid、wsPort、headless。\n幂等:重复调用不会启动多个 daemon。daemon 默认 30 分钟无活动自动关闭。\n关联工具:setup(headless 前置)、disconnect(结束会话)、health(检查状态)',
25
+ description: '启动守护进程(daemon),建立 CLI 与 RemNote Plugin 之间的通信通道。这是所有业务命令(read_rem、edit_rem、search 等)的前置步骤。\n\n适用场景:\n- 开始一次 RemNote 操作会话前,必须先调用此工具\n- 不确定 daemon 是否在运行时,也可安全调用(幂等)\n\n两种模式:\n- 标准模式(默认,推荐):启动 daemon 后用户在自己的浏览器中加载 Plugin。优势:Agent 可通过 read_context 感知用户正在浏览的页面,实现协作\n- Headless 模式(headless=true,不推荐日常使用):自动启动 headless Chrome,无需用户操作。⚠️ 会丢失用户上下文(read_context 返回 headless 实例的上下文,不是用户浏览器的)。仅在以下场景使用:用户明确要求在服务器/无 GUI 环境运行、用户不想参与操作(全自动化)、用户不在 RemNote 前面。需先完成 setup\n\n⚠️ 标准模式 Plugin 加载(connect 后必须引导用户完成):\n本插件是开发者插件,不在 RemNote 插件市场中,禁止告诉用户去市场搜索安装。正确步骤:\n- 首次:RemNote → 左下角插件图标 → 「开发你的插件」→ 填入 Plugin 服务地址(如 http://localhost:29101)\n- 非首次:刷新 RemNote 页面即可\n引导用户完成后用 health 确认三层就绪再执行业务命令。\n\nHeadless 模式会话持久化:connect(headless=true) 后,MCP Server 自动记住 headless 状态,后续所有工具调用自动路由到 headless 实例。disconnect 或 clean 后自动清除。\n\n输出:返回 JSON,关键字段 ok、alreadyRunning、instance、slotIndex、pid、wsPort、headless。\n幂等:重复调用不会启动多个 daemon。daemon 默认 30 分钟无活动自动关闭。\n关联工具:setup(headless 前置)、disconnect(结束会话)、health(检查状态)',
26
26
  parameters: z.object({
27
27
  headless: z.boolean().optional().describe('启用 headless 模式:自动启动 Chrome 加载 Plugin(需先 setup)'),
28
28
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remnote-bridge",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "RemNote 自动化桥接工具集:CLI + MCP Server + Plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,7 +55,7 @@ Add a <Suspense fallback=...> component higher in the tree to provide a loading
55
55
  *
56
56
  * This source code is licensed under the MIT license found in the
57
57
  * LICENSE file in the root directory of this source tree.
58
- */var Xe,Ot,Ae,it;if(typeof performance=="object"&&typeof performance.now=="function"){var B=performance;K.unstable_now=function(){return B.now()}}else{var wn=Date,Et=wn.now();K.unstable_now=function(){return wn.now()-Et}}if(typeof window>"u"||typeof MessageChannel!="function"){var Tt=null,b=null,Y=function(){if(Tt!==null)try{var F=K.unstable_now();Tt(!0,F),Tt=null}catch(A){throw setTimeout(Y,0),A}};Xe=function(F){Tt!==null?setTimeout(Xe,0,F):(Tt=F,setTimeout(Y,0))},Ot=function(F,A){b=setTimeout(F,A)},Ae=function(){clearTimeout(b)},K.unstable_shouldYield=function(){return!1},it=K.unstable_forceFrameRate=function(){}}else{var c=window.setTimeout,Mt=window.clearTimeout;if(typeof console<"u"){var ht=window.cancelAnimationFrame;typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof ht!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")}var Ve=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(F){0>F||125<F?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<F?Math.floor(1e3/F):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var F=K.unstable_now();ot=F+tt;try{at(!0,F)?ae.postMessage(null):(Ve=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else Ve=!1},Xe=function(F){at=F,Ve||(Ve=!0,ae.postMessage(null))},Ot=function(F,A){Pt=c(function(){F(K.unstable_now())},A)},Ae=function(){Mt(Pt),Pt=-1}}function _e(F,A){var $=F.length;F.push(A);e:for(;;){var H=$-1>>>1,ne=F[H];if(ne!==void 0&&0<st(ne,A))F[H]=A,F[$]=ne,$=H;else break e}}function Ne(F){return F=F[0],F===void 0?null:F}function He(F){var A=F[0];if(A!==void 0){var $=F.pop();if($!==A){F[0]=$;e:for(var H=0,ne=F.length;H<ne;){var U=2*(H+1)-1,ye=F[U],M=U+1,sn=F[M];if(ye!==void 0&&0>st(ye,$))sn!==void 0&&0>st(sn,ye)?(F[H]=sn,F[M]=$,H=M):(F[H]=ye,F[U]=$,H=U);else if(sn!==void 0&&0>st(sn,$))F[H]=sn,F[M]=$,H=M;else break e}}return A}return null}function st(F,A){var $=F.sortIndex-A.sortIndex;return $!==0?$:F.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(F){for(var A=Ne(Ze);A!==null;){if(A.callback===null)He(Ze);else if(A.startTime<=F)He(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Ne(Ze)}}function ge(F){if(me=!1,q(F),!ie)if(Ne(Z)!==null)ie=!0,Xe(C);else{var A=Ne(Ze);A!==null&&Ot(ge,A.startTime-F)}}function C(F,A){ie=!1,me&&(me=!1,Ae()),ee=!0;var $=fe;try{for(q(A),ue=Ne(Z);ue!==null&&(!(ue.expirationTime>A)||F&&!K.unstable_shouldYield());){var H=ue.callback;if(typeof H=="function"){ue.callback=null,fe=ue.priorityLevel;var ne=H(ue.expirationTime<=A);A=K.unstable_now(),typeof ne=="function"?ue.callback=ne:ue===Ne(Z)&&He(Z),q(A)}else He(Z);ue=Ne(Z)}if(ue!==null)var U=!0;else{var ye=Ne(Ze);ye!==null&&Ot(ge,ye.startTime-A),U=!1}return U}finally{ue=null,fe=$,ee=!1}}var V=it;K.unstable_IdlePriority=5,K.unstable_ImmediatePriority=1,K.unstable_LowPriority=4,K.unstable_NormalPriority=3,K.unstable_Profiling=null,K.unstable_UserBlockingPriority=2,K.unstable_cancelCallback=function(F){F.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,Xe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Ne(Z)},K.unstable_next=function(F){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return F()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(F,A){switch(F){case 1:case 2:case 3:case 4:case 5:break;default:F=3}var $=fe;fe=F;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(F,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,F){case 1:var ne=-1;break;case 2:ne=250;break;case 5:ne=1073741823;break;case 4:ne=1e4;break;default:ne=5e3}return ne=$+ne,F={id:Ye++,callback:A,priorityLevel:F,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(F.sortIndex=$,_e(Ze,F),Ne(Z)===null&&F===Ne(Ze)&&(me?Ae():me=!0,Ot(ge,$-H))):(F.sortIndex=ne,_e(Z,F),ie||ee||(ie=!0,Xe(C))),F},K.unstable_wrapCallback=function(F){var A=fe;return function(){var $=fe;fe=A;try{return F.apply(this,arguments)}finally{fe=$}}}},825(en,K,Xe){"use strict";en.exports=Xe(742)}},vd={};function Ac(en){var K=vd[en];if(K!==void 0)return K.exports;var Xe=vd[en]={exports:{}};return Pf[en](Xe,Xe.exports,Ac),Xe.exports}Ac.g=(function(){if(typeof globalThis=="object")return globalThis;try{return this||new Function("return this")()}catch{if(typeof window=="object")return window}})();var wp={};(()=>{"use strict";var en=Ac(216);const K="0.2.0",Xe=[29100,29110,29120,29130],Ot=18e3;var Ae=Object.defineProperty,it=(w,y,E)=>y in w?Ae(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,B=(w,y,E)=>it(w,typeof y!="symbol"?y+"":y,E);const wn=4e3,Et=4003;class Tt{constructor(y){B(this,"ws",null),B(this,"reconnectAttempts",0),B(this,"reconnectTimeout",null),B(this,"messageHandler",null),B(this,"status","disconnected"),B(this,"isShuttingDown",!1),B(this,"isPreempted",!1),B(this,"_sdkReady"),B(this,"config"),this._sdkReady=y.sdkReady,this.config={url:y.url,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:y.isTwinConnection??!1,maxReconnectAttempts:y.maxReconnectAttempts??10,initialReconnectDelay:y.initialReconnectDelay??1e3,maxReconnectDelay:y.maxReconnectDelay??3e4,onStatusChange:y.onStatusChange,onLog:y.onLog,onPreempted:y.onPreempted,onTwinOccupied:y.onTwinOccupied,onOtherOccupied:y.onOtherOccupied}}log(y,E="info"){this.config.onLog?.(y,E)}setStatus(y){this.status!==y&&(this.status=y,this.config.onStatusChange?.(y))}sendHello(){const y={type:"hello",version:this.config.pluginVersion,sdkReady:this._sdkReady,twinSlotIndex:this.config.twinSlotIndex};try{this.ws?.send(JSON.stringify(y)),this.log(`\u53D1\u9001 hello\uFF08v${this.config.pluginVersion}, sdkReady=${this._sdkReady}, twinSlot=${this.config.twinSlotIndex}\uFF09`)}catch(E){this.log(`\u53D1\u9001 hello \u5931\u8D25: ${E}`,"warn")}}connect(){if(!(this.ws?.readyState===WebSocket.OPEN||this.ws?.readyState===WebSocket.CONNECTING)){this.isShuttingDown=!1,this.isPreempted=!1,this.setStatus("connecting");try{this.ws=new WebSocket(this.config.url),this.ws.onopen=()=>{this.log("\u5DF2\u8FDE\u63A5\u5230\u5B88\u62A4\u8FDB\u7A0B"),this.reconnectAttempts=0,this.setStatus("connected"),this.sendHello()},this.ws.onmessage=async y=>{await this.handleMessage(typeof y.data=="string"?y.data:String(y.data))},this.ws.onclose=y=>{y.code!==1006&&this.log(`\u8FDE\u63A5\u65AD\u5F00: ${y.code} ${y.reason}`,"warn"),this.setStatus("disconnected"),y.code===Et?this.config.onTwinOccupied?.():y.code===wn&&this.config.onOtherOccupied?.(),this.isShuttingDown||this.scheduleReconnect()},this.ws.onerror=()=>{}}catch(y){this.log(`\u8FDE\u63A5\u5931\u8D25: ${y}`,"error"),this.setStatus("disconnected"),this.scheduleReconnect()}}}async handleMessage(y){try{const E=JSON.parse(y);if(E.type==="preempted"){this.isPreempted=!0,this.log(`\u88AB\u5B6A\u751F Plugin \u62A2\u5360: ${E.reason}`,"warn"),this.config.onPreempted?.();return}if(E.type==="ping"){this.ws?.send(JSON.stringify({type:"pong"}));return}if(E.id&&E.action&&this.messageHandler){const R=E;this.log(`\u6536\u5230\u8BF7\u6C42: ${R.action}`);try{const N=await this.messageHandler(R),j={id:R.id,result:N};this.ws?.send(JSON.stringify(j)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const j=N instanceof Error?N.message:String(N),Q={id:R.id,error:j};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${j}`,"error")}}}catch(E){this.log(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${E}`,"error")}}scheduleReconnect(){if(this.isShuttingDown||this.isPreempted||!this.config.isTwinConnection)return;if(this.reconnectAttempts>=this.config.maxReconnectAttempts){this.log("\u5DF2\u8FBE\u6700\u5927\u91CD\u8FDE\u6B21\u6570","error");return}const y=Math.min(this.config.initialReconnectDelay*Math.pow(2,this.reconnectAttempts),this.config.maxReconnectDelay),E=Math.random()*.3*y,R=y+E;this.reconnectAttempts++,this.log(`${Math.round(R)}ms \u540E\u91CD\u8FDE\uFF08\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21\uFF09`),this.reconnectTimeout=setTimeout(()=>{this.connect()},R)}setMessageHandler(y){this.messageHandler=y}disconnect(){this.isShuttingDown=!0,this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(1e3,"Plugin disconnect"),this.ws=null),this.setStatus("disconnected")}getStatus(){return this.status}}var b=Object.defineProperty,Y=(w,y,E)=>y in w?b(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,c=(w,y,E)=>Y(w,typeof y!="symbol"?y+"":y,E);class Mt{constructor(y){c(this,"clients",[]),c(this,"slotStates",[]),c(this,"scanTimer",null),c(this,"config"),this.config=y;for(let E=0;E<Xe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:Xe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${Xe[E]}`,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:R,maxReconnectAttempts:R?10:0,initialReconnectDelay:1e3,maxReconnectDelay:3e4,onStatusChange:N=>this.handleStatusChange(E,N),onLog:(N,j)=>y.onLog(E,N,j),onPreempted:()=>this.handleDisconnectReason(E,"preempted"),onTwinOccupied:()=>this.handleDisconnectReason(E,"twin_occupied"),onOtherOccupied:()=>this.handleDisconnectReason(E,"other_occupied")}))}}setMessageHandler(y){for(const E of this.clients)E.setMessageHandler(y)}start(){const y=this.config.twinSlotIndex;this.clients[y].connect(),setTimeout(()=>{for(let E=0;E<this.clients.length;E++)E!==y&&this.clients[E].connect()},2e3),this.scanTimer=setInterval(()=>this.scanAndReconnect(),Ot)}stop(){this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null);for(const y of this.clients)y.disconnect()}getSlots(){return this.slotStates.slice()}handleStatusChange(y,E){const R=this.slotStates[y];R.status=E,E==="connected"&&(R.disconnectReason=null),this.notifySlotsChange()}handleDisconnectReason(y,E){this.slotStates[y].disconnectReason=E,this.notifySlotsChange()}notifySlotsChange(){this.config.onSlotsChange(this.slotStates.slice())}scanAndReconnect(){for(let y=0;y<this.clients.length;y++){const E=this.slotStates[y];E.isTwin||E.status==="connected"||E.status==="connecting"||this.clients[y].connect()}}}async function ht(w){const[y,E,R,N]=await Promise.all([w.isPowerupProperty(),w.isPowerupSlot(),w.isPowerupPropertyListItem(),w.isPowerupEnum()]);return y||E||R||N}async function Ve(w){const y=await Promise.all(w.map(ht));return w.filter((E,R)=>!y[R])}async function at(w){const y=await Promise.all(w.map(E=>E.isPowerup()));return w.filter((E,R)=>!y[R])}async function Pt(w,y){try{return await w.richText.toMarkdown(y)}catch{return tt(y)}}function tt(w){return w.map(y=>{if(typeof y=="string")return y;if(typeof y!="object"||y===null)return"";const E=y;switch(E.i){case"m":return String(E.text??"");case"q":return`[[${String(E._id??"")}]]`;case"u":return E.title?`[${String(E.title)}](${String(E.url)})`:String(E.url??"");case"x":return`$${String(E.text??"")}$`;case"i":return`![image](${String(E.url??"")})`;case"a":return`[audio](${String(E.url??"")})`;case"p":return String(E.text??"");case"g":return String(E.text??"");case"n":return String(E.text??"");case"o":return String(E.text??"");case"s":case"fi":case"ai":return"";default:return String(E.text??E.url??"")}}).join("")}async function ot(w,y,E,R){const N=R?.includePowerup??!1,[j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be]=await Promise.all([Pt(w,y.text??[]),y.backText?Pt(w,y.backText):Promise.resolve(null),y.getType(),y.isCardItem(),y.isDocument(),y.getTagRems().then(qe=>N?qe:at(qe).then(Re=>{const xt=qe.length-Re.length;return xt>0&&R?.onFilteredTags?.(xt),Re})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let Ge=!1;E.length>0&&(Ge=(await Promise.all(E.map(Re=>Re.isCardItem()))).some(Boolean));const nt=Pe&&(y.text??[]).length===0,Qe=await Promise.all(De.map(async qe=>({id:qe._id,name:te(await Pt(w,qe.text??[]))})));return{id:y._id,markdownText:te(j),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:Ge,practiceDirection:he??"none",isCardItem:ve,isDocument:Fe,isPortal:y.type===6,portalRefs:Be.map(qe=>qe._id),childrenCount:E.length,tags:Qe,fontSize:mt??null,isTodo:Me,todoStatus:$e??null,isCode:wt,isDivider:nt,isTopLevel:y.parent===null}}function te(w){return w.replace(/\n/g," ")}function ae(w){switch(w){case 1:return"concept";case 2:return"descriptor";case 6:return"portal";default:return"default"}}async function _e(w,y){const{includePowerup:E=!1}=y,R=await w.rem.findOne(y.remId);if(!R)throw new Error(`Rem not found: ${y.remId}`);return Ne(w,R,{includePowerup:E})}async function Ne(w,y,E){const{includePowerup:R=!1}=E,[N,j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be,Ge,nt,Qe,qe,Re,xt,Je,an,Ke,Qt,un,pn,Zt,ut,ct,Nt,Bt,hn,Dt,Jn,ur,So,er,Tr,_n,Sn,Dn,cr,xr]=await Promise.all([y.isDocument(),y.getFontSize(),y.getHighlightColor(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.isCardItem(),y.isTable(),y.isSlot(),y.isProperty(),y.getEnablePractice(),y.getPracticeDirection(),y.getTagRems(),y.getSources(),y.getAliases(),y.positionAmongstSiblings(),y.isPowerup(),y.isPowerupEnum(),y.isPowerupProperty(),y.isPowerupPropertyListItem(),y.isPowerupSlot(),y.getPortalType(),y.getPortalDirectlyIncludedRem(),y.getPropertyType(),y.remsBeingReferenced(),y.deepRemsBeingReferenced(),y.remsReferencingThis(),y.taggedRem(),y.ancestorTagRem(),y.descendantTagRem(),y.getDescendants(),y.siblingRem(),y.portalsAndDocumentsIn(),y.allRemInDocumentOrPortal(),y.allRemInFolderQueue(),y.getChildrenRem(),y.timesSelectedInSearch(),y.getLastTimeMovedTo(),y.getSchemaVersion(),y.embeddedQueueViewMode(),y.getLastPracticed()]);let dr=Ge,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Ge),mi=Ge.length-dr.length;const ze=await Ve(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:He(y.text??[]),backText:y.backText?He(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:j??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Fe,isQuote:De,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:$e,isProperty:wt,isPowerup:Re,isPowerupEnum:xt,isPowerupProperty:Je,isPowerupPropertyListItem:an,isPowerupSlot:Ke,portalType:ae(y.type)==="portal"?st(Qt):null,portalDirectlyIncludedRem:un.map(ze=>ze._id),propertyType:pn??null,enablePractice:Pe,practiceDirection:Be,tags:dr.map(ze=>ze._id),sources:nt.map(ze=>ze._id),aliases:Qe.map(ze=>ze._id),remsBeingReferenced:Zt.map(ze=>ze._id),deepRemsBeingReferenced:ut.map(ze=>ze._id),remsReferencingThis:ct.map(ze=>ze._id),taggedRem:Nt.map(ze=>ze._id),ancestorTagRem:Bt.map(ze=>ze._id),descendantTagRem:hn.map(ze=>ze._id),descendants:Dt.map(ze=>ze._id),siblingRem:Jn.map(ze=>ze._id),portalsAndDocumentsIn:ur.map(ze=>ze._id),allRemInDocumentOrPortal:So.map(ze=>ze._id),allRemInFolderQueue:er.map(ze=>ze._id),positionAmongstSiblings:qe??null,timesSelectedInSearch:_n,lastTimeMovedTo:Sn,schemaVersion:Dn,embeddedQueueViewMode:cr,createdAt:y.createdAt,updatedAt:y.updatedAt,localUpdatedAt:y.localUpdatedAt,lastPracticed:xr};return!R&&(mi>0||Mr>0)?{...fr,powerupFiltered:{tags:mi,children:Mr}}:fr}function He(w){return w.map(y=>{if(typeof y=="string")return y;const E={};for(const R of Object.keys(y).sort())E[R]=y[R];return E})}function st(w){switch(w){case 2:return"embedded_queue";case 3:return"scaffold";case 4:return"search_portal";default:return"portal"}}function Z(w){return"type"in w&&w.type==="elided"}function Ze(w){if(w.isDivider)return"---";const{markdownText:y,markdownBackText:E,hasMultilineChildren:R,practiceDirection:N}=w;let j;return E!==null?j=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?j=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):j=y,w.isCode&&(j="`"+j+"`"),w.isTodo&&(j=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+j),w.fontSize&&(j=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+j),j}function Ye(w,y){const E=[];w.type==="concept"?E.push("type:concept"):w.type==="descriptor"?E.push("type:descriptor"):w.type==="portal"&&(E.push("type:portal"),w.portalRefs.length>0&&E.push("refs:"+w.portalRefs.join(","))),w.isDocument&&E.push("doc"),w.isCardItem&&E.push("role:card-item"),y&&w.childrenCount>0&&E.push("children:"+w.childrenCount);for(const R of w.tags)E.push("tag:"+R.name+"("+R.id+")");return w.isTopLevel&&E.push("top"),E}function ue(w){return{markdownBackText:null,type:"default",hasMultilineChildren:!1,practiceDirection:"none",isCardItem:!1,isDocument:!1,isPortal:!1,portalRefs:[],tags:[],fontSize:null,isTodo:!1,todoStatus:null,isCode:!1,isDivider:!1,...w}}function fe(w,y=!1){const E=Ze(w),R=Ye(w,y),N=R.length>0?" "+R.join(" "):"";return`${E} <!--${w.id}${N}-->`}function ee(w){const y=w.isExact?"siblings":"nodes";return`<!--...elided ${w.isExact?"":">="}${w.count} ${y} (parent:${w.parentId} range:${w.rangeFrom}-${w.rangeTo} total:${w.totalSiblings})-->`}function ie(w){const y=[];function E(R,N){const j=" ".repeat(N);if(Z(R)){y.push(j+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(j+Q),!R.folded)for(const G of R.children)E(G,N+1)}return E(w,0),y.join(`
58
+ */var Xe,Ot,Ae,it;if(typeof performance=="object"&&typeof performance.now=="function"){var B=performance;K.unstable_now=function(){return B.now()}}else{var wn=Date,Et=wn.now();K.unstable_now=function(){return wn.now()-Et}}if(typeof window>"u"||typeof MessageChannel!="function"){var Tt=null,b=null,Y=function(){if(Tt!==null)try{var F=K.unstable_now();Tt(!0,F),Tt=null}catch(A){throw setTimeout(Y,0),A}};Xe=function(F){Tt!==null?setTimeout(Xe,0,F):(Tt=F,setTimeout(Y,0))},Ot=function(F,A){b=setTimeout(F,A)},Ae=function(){clearTimeout(b)},K.unstable_shouldYield=function(){return!1},it=K.unstable_forceFrameRate=function(){}}else{var c=window.setTimeout,Mt=window.clearTimeout;if(typeof console<"u"){var ht=window.cancelAnimationFrame;typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof ht!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")}var Ve=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(F){0>F||125<F?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<F?Math.floor(1e3/F):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var F=K.unstable_now();ot=F+tt;try{at(!0,F)?ae.postMessage(null):(Ve=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else Ve=!1},Xe=function(F){at=F,Ve||(Ve=!0,ae.postMessage(null))},Ot=function(F,A){Pt=c(function(){F(K.unstable_now())},A)},Ae=function(){Mt(Pt),Pt=-1}}function _e(F,A){var $=F.length;F.push(A);e:for(;;){var H=$-1>>>1,ne=F[H];if(ne!==void 0&&0<st(ne,A))F[H]=A,F[$]=ne,$=H;else break e}}function Ne(F){return F=F[0],F===void 0?null:F}function He(F){var A=F[0];if(A!==void 0){var $=F.pop();if($!==A){F[0]=$;e:for(var H=0,ne=F.length;H<ne;){var U=2*(H+1)-1,ye=F[U],M=U+1,sn=F[M];if(ye!==void 0&&0>st(ye,$))sn!==void 0&&0>st(sn,ye)?(F[H]=sn,F[M]=$,H=M):(F[H]=ye,F[U]=$,H=U);else if(sn!==void 0&&0>st(sn,$))F[H]=sn,F[M]=$,H=M;else break e}}return A}return null}function st(F,A){var $=F.sortIndex-A.sortIndex;return $!==0?$:F.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(F){for(var A=Ne(Ze);A!==null;){if(A.callback===null)He(Ze);else if(A.startTime<=F)He(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Ne(Ze)}}function ge(F){if(me=!1,q(F),!ie)if(Ne(Z)!==null)ie=!0,Xe(C);else{var A=Ne(Ze);A!==null&&Ot(ge,A.startTime-F)}}function C(F,A){ie=!1,me&&(me=!1,Ae()),ee=!0;var $=fe;try{for(q(A),ue=Ne(Z);ue!==null&&(!(ue.expirationTime>A)||F&&!K.unstable_shouldYield());){var H=ue.callback;if(typeof H=="function"){ue.callback=null,fe=ue.priorityLevel;var ne=H(ue.expirationTime<=A);A=K.unstable_now(),typeof ne=="function"?ue.callback=ne:ue===Ne(Z)&&He(Z),q(A)}else He(Z);ue=Ne(Z)}if(ue!==null)var U=!0;else{var ye=Ne(Ze);ye!==null&&Ot(ge,ye.startTime-A),U=!1}return U}finally{ue=null,fe=$,ee=!1}}var V=it;K.unstable_IdlePriority=5,K.unstable_ImmediatePriority=1,K.unstable_LowPriority=4,K.unstable_NormalPriority=3,K.unstable_Profiling=null,K.unstable_UserBlockingPriority=2,K.unstable_cancelCallback=function(F){F.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,Xe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Ne(Z)},K.unstable_next=function(F){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return F()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(F,A){switch(F){case 1:case 2:case 3:case 4:case 5:break;default:F=3}var $=fe;fe=F;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(F,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,F){case 1:var ne=-1;break;case 2:ne=250;break;case 5:ne=1073741823;break;case 4:ne=1e4;break;default:ne=5e3}return ne=$+ne,F={id:Ye++,callback:A,priorityLevel:F,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(F.sortIndex=$,_e(Ze,F),Ne(Z)===null&&F===Ne(Ze)&&(me?Ae():me=!0,Ot(ge,$-H))):(F.sortIndex=ne,_e(Z,F),ie||ee||(ie=!0,Xe(C))),F},K.unstable_wrapCallback=function(F){var A=fe;return function(){var $=fe;fe=A;try{return F.apply(this,arguments)}finally{fe=$}}}},825(en,K,Xe){"use strict";en.exports=Xe(742)}},vd={};function Ac(en){var K=vd[en];if(K!==void 0)return K.exports;var Xe=vd[en]={exports:{}};return Pf[en](Xe,Xe.exports,Ac),Xe.exports}Ac.g=(function(){if(typeof globalThis=="object")return globalThis;try{return this||new Function("return this")()}catch{if(typeof window=="object")return window}})();var wp={};(()=>{"use strict";var en=Ac(216);const K="0.2.1",Xe=[29100,29110,29120,29130],Ot=18e3;var Ae=Object.defineProperty,it=(w,y,E)=>y in w?Ae(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,B=(w,y,E)=>it(w,typeof y!="symbol"?y+"":y,E);const wn=4e3,Et=4003;class Tt{constructor(y){B(this,"ws",null),B(this,"reconnectAttempts",0),B(this,"reconnectTimeout",null),B(this,"messageHandler",null),B(this,"status","disconnected"),B(this,"isShuttingDown",!1),B(this,"isPreempted",!1),B(this,"_sdkReady"),B(this,"config"),this._sdkReady=y.sdkReady,this.config={url:y.url,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:y.isTwinConnection??!1,maxReconnectAttempts:y.maxReconnectAttempts??10,initialReconnectDelay:y.initialReconnectDelay??1e3,maxReconnectDelay:y.maxReconnectDelay??3e4,onStatusChange:y.onStatusChange,onLog:y.onLog,onPreempted:y.onPreempted,onTwinOccupied:y.onTwinOccupied,onOtherOccupied:y.onOtherOccupied}}log(y,E="info"){this.config.onLog?.(y,E)}setStatus(y){this.status!==y&&(this.status=y,this.config.onStatusChange?.(y))}sendHello(){const y={type:"hello",version:this.config.pluginVersion,sdkReady:this._sdkReady,twinSlotIndex:this.config.twinSlotIndex};try{this.ws?.send(JSON.stringify(y)),this.log(`\u53D1\u9001 hello\uFF08v${this.config.pluginVersion}, sdkReady=${this._sdkReady}, twinSlot=${this.config.twinSlotIndex}\uFF09`)}catch(E){this.log(`\u53D1\u9001 hello \u5931\u8D25: ${E}`,"warn")}}connect(){if(!(this.ws?.readyState===WebSocket.OPEN||this.ws?.readyState===WebSocket.CONNECTING)){this.isShuttingDown=!1,this.isPreempted=!1,this.setStatus("connecting");try{this.ws=new WebSocket(this.config.url),this.ws.onopen=()=>{this.log("\u5DF2\u8FDE\u63A5\u5230\u5B88\u62A4\u8FDB\u7A0B"),this.reconnectAttempts=0,this.setStatus("connected"),this.sendHello()},this.ws.onmessage=async y=>{await this.handleMessage(typeof y.data=="string"?y.data:String(y.data))},this.ws.onclose=y=>{y.code!==1006&&this.log(`\u8FDE\u63A5\u65AD\u5F00: ${y.code} ${y.reason}`,"warn"),this.setStatus("disconnected"),y.code===Et?this.config.onTwinOccupied?.():y.code===wn&&this.config.onOtherOccupied?.(),this.isShuttingDown||this.scheduleReconnect()},this.ws.onerror=()=>{}}catch(y){this.log(`\u8FDE\u63A5\u5931\u8D25: ${y}`,"error"),this.setStatus("disconnected"),this.scheduleReconnect()}}}async handleMessage(y){try{const E=JSON.parse(y);if(E.type==="preempted"){this.isPreempted=!0,this.log(`\u88AB\u5B6A\u751F Plugin \u62A2\u5360: ${E.reason}`,"warn"),this.config.onPreempted?.();return}if(E.type==="ping"){this.ws?.send(JSON.stringify({type:"pong"}));return}if(E.id&&E.action&&this.messageHandler){const R=E;this.log(`\u6536\u5230\u8BF7\u6C42: ${R.action}`);try{const N=await this.messageHandler(R),j={id:R.id,result:N};this.ws?.send(JSON.stringify(j)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const j=N instanceof Error?N.message:String(N),Q={id:R.id,error:j};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${j}`,"error")}}}catch(E){this.log(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${E}`,"error")}}scheduleReconnect(){if(this.isShuttingDown||this.isPreempted||!this.config.isTwinConnection)return;if(this.reconnectAttempts>=this.config.maxReconnectAttempts){this.log("\u5DF2\u8FBE\u6700\u5927\u91CD\u8FDE\u6B21\u6570","error");return}const y=Math.min(this.config.initialReconnectDelay*Math.pow(2,this.reconnectAttempts),this.config.maxReconnectDelay),E=Math.random()*.3*y,R=y+E;this.reconnectAttempts++,this.log(`${Math.round(R)}ms \u540E\u91CD\u8FDE\uFF08\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21\uFF09`),this.reconnectTimeout=setTimeout(()=>{this.connect()},R)}setMessageHandler(y){this.messageHandler=y}disconnect(){this.isShuttingDown=!0,this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(1e3,"Plugin disconnect"),this.ws=null),this.setStatus("disconnected")}getStatus(){return this.status}}var b=Object.defineProperty,Y=(w,y,E)=>y in w?b(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,c=(w,y,E)=>Y(w,typeof y!="symbol"?y+"":y,E);class Mt{constructor(y){c(this,"clients",[]),c(this,"slotStates",[]),c(this,"scanTimer",null),c(this,"config"),this.config=y;for(let E=0;E<Xe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:Xe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${Xe[E]}`,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:R,maxReconnectAttempts:R?10:0,initialReconnectDelay:1e3,maxReconnectDelay:3e4,onStatusChange:N=>this.handleStatusChange(E,N),onLog:(N,j)=>y.onLog(E,N,j),onPreempted:()=>this.handleDisconnectReason(E,"preempted"),onTwinOccupied:()=>this.handleDisconnectReason(E,"twin_occupied"),onOtherOccupied:()=>this.handleDisconnectReason(E,"other_occupied")}))}}setMessageHandler(y){for(const E of this.clients)E.setMessageHandler(y)}start(){const y=this.config.twinSlotIndex;this.clients[y].connect(),setTimeout(()=>{for(let E=0;E<this.clients.length;E++)E!==y&&this.clients[E].connect()},2e3),this.scanTimer=setInterval(()=>this.scanAndReconnect(),Ot)}stop(){this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null);for(const y of this.clients)y.disconnect()}getSlots(){return this.slotStates.slice()}handleStatusChange(y,E){const R=this.slotStates[y];R.status=E,E==="connected"&&(R.disconnectReason=null),this.notifySlotsChange()}handleDisconnectReason(y,E){this.slotStates[y].disconnectReason=E,this.notifySlotsChange()}notifySlotsChange(){this.config.onSlotsChange(this.slotStates.slice())}scanAndReconnect(){for(let y=0;y<this.clients.length;y++){const E=this.slotStates[y];E.isTwin||E.status==="connected"||E.status==="connecting"||this.clients[y].connect()}}}async function ht(w){const[y,E,R,N]=await Promise.all([w.isPowerupProperty(),w.isPowerupSlot(),w.isPowerupPropertyListItem(),w.isPowerupEnum()]);return y||E||R||N}async function Ve(w){const y=await Promise.all(w.map(ht));return w.filter((E,R)=>!y[R])}async function at(w){const y=await Promise.all(w.map(E=>E.isPowerup()));return w.filter((E,R)=>!y[R])}async function Pt(w,y){try{return await w.richText.toMarkdown(y)}catch{return tt(y)}}function tt(w){return w.map(y=>{if(typeof y=="string")return y;if(typeof y!="object"||y===null)return"";const E=y;switch(E.i){case"m":return String(E.text??"");case"q":return`[[${String(E._id??"")}]]`;case"u":return E.title?`[${String(E.title)}](${String(E.url)})`:String(E.url??"");case"x":return`$${String(E.text??"")}$`;case"i":return`![image](${String(E.url??"")})`;case"a":return`[audio](${String(E.url??"")})`;case"p":return String(E.text??"");case"g":return String(E.text??"");case"n":return String(E.text??"");case"o":return String(E.text??"");case"s":case"fi":case"ai":return"";default:return String(E.text??E.url??"")}}).join("")}async function ot(w,y,E,R){const N=R?.includePowerup??!1,[j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be]=await Promise.all([Pt(w,y.text??[]),y.backText?Pt(w,y.backText):Promise.resolve(null),y.getType(),y.isCardItem(),y.isDocument(),y.getTagRems().then(qe=>N?qe:at(qe).then(Re=>{const xt=qe.length-Re.length;return xt>0&&R?.onFilteredTags?.(xt),Re})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let Ge=!1;E.length>0&&(Ge=(await Promise.all(E.map(Re=>Re.isCardItem()))).some(Boolean));const nt=Pe&&(y.text??[]).length===0,Qe=await Promise.all(De.map(async qe=>({id:qe._id,name:te(await Pt(w,qe.text??[]))})));return{id:y._id,markdownText:te(j),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:Ge,practiceDirection:he??"none",isCardItem:ve,isDocument:Fe,isPortal:y.type===6,portalRefs:Be.map(qe=>qe._id),childrenCount:E.length,tags:Qe,fontSize:mt??null,isTodo:Me,todoStatus:$e??null,isCode:wt,isDivider:nt,isTopLevel:y.parent===null}}function te(w){return w.replace(/\n/g," ")}function ae(w){switch(w){case 1:return"concept";case 2:return"descriptor";case 6:return"portal";default:return"default"}}async function _e(w,y){const{includePowerup:E=!1}=y,R=await w.rem.findOne(y.remId);if(!R)throw new Error(`Rem not found: ${y.remId}`);return Ne(w,R,{includePowerup:E})}async function Ne(w,y,E){const{includePowerup:R=!1}=E,[N,j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be,Ge,nt,Qe,qe,Re,xt,Je,an,Ke,Qt,un,pn,Zt,ut,ct,Nt,Bt,hn,Dt,Jn,ur,So,er,Tr,_n,Sn,Dn,cr,xr]=await Promise.all([y.isDocument(),y.getFontSize(),y.getHighlightColor(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.isCardItem(),y.isTable(),y.isSlot(),y.isProperty(),y.getEnablePractice(),y.getPracticeDirection(),y.getTagRems(),y.getSources(),y.getAliases(),y.positionAmongstSiblings(),y.isPowerup(),y.isPowerupEnum(),y.isPowerupProperty(),y.isPowerupPropertyListItem(),y.isPowerupSlot(),y.getPortalType(),y.getPortalDirectlyIncludedRem(),y.getPropertyType(),y.remsBeingReferenced(),y.deepRemsBeingReferenced(),y.remsReferencingThis(),y.taggedRem(),y.ancestorTagRem(),y.descendantTagRem(),y.getDescendants(),y.siblingRem(),y.portalsAndDocumentsIn(),y.allRemInDocumentOrPortal(),y.allRemInFolderQueue(),y.getChildrenRem(),y.timesSelectedInSearch(),y.getLastTimeMovedTo(),y.getSchemaVersion(),y.embeddedQueueViewMode(),y.getLastPracticed()]);let dr=Ge,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Ge),mi=Ge.length-dr.length;const ze=await Ve(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:He(y.text??[]),backText:y.backText?He(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:j??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Fe,isQuote:De,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:$e,isProperty:wt,isPowerup:Re,isPowerupEnum:xt,isPowerupProperty:Je,isPowerupPropertyListItem:an,isPowerupSlot:Ke,portalType:ae(y.type)==="portal"?st(Qt):null,portalDirectlyIncludedRem:un.map(ze=>ze._id).sort(),propertyType:pn??null,enablePractice:Pe,practiceDirection:Be,tags:dr.map(ze=>ze._id).sort(),sources:nt.map(ze=>ze._id).sort(),aliases:Qe.map(ze=>ze._id).sort(),remsBeingReferenced:Zt.map(ze=>ze._id).sort(),deepRemsBeingReferenced:ut.map(ze=>ze._id).sort(),remsReferencingThis:ct.map(ze=>ze._id).sort(),taggedRem:Nt.map(ze=>ze._id).sort(),ancestorTagRem:Bt.map(ze=>ze._id).sort(),descendantTagRem:hn.map(ze=>ze._id).sort(),descendants:Dt.map(ze=>ze._id).sort(),siblingRem:Jn.map(ze=>ze._id).sort(),portalsAndDocumentsIn:ur.map(ze=>ze._id).sort(),allRemInDocumentOrPortal:So.map(ze=>ze._id).sort(),allRemInFolderQueue:er.map(ze=>ze._id).sort(),positionAmongstSiblings:qe??null,timesSelectedInSearch:_n,lastTimeMovedTo:Sn,schemaVersion:Dn,embeddedQueueViewMode:cr,createdAt:y.createdAt,updatedAt:y.updatedAt,localUpdatedAt:y.localUpdatedAt,lastPracticed:xr};return!R&&(mi>0||Mr>0)?{...fr,powerupFiltered:{tags:mi,children:Mr}}:fr}function He(w){return w.map(y=>{if(typeof y=="string")return y;const E={};for(const R of Object.keys(y).sort())E[R]=y[R];return E})}function st(w){switch(w){case 2:return"embedded_queue";case 3:return"scaffold";case 4:return"search_portal";default:return"portal"}}function Z(w){return"type"in w&&w.type==="elided"}function Ze(w){if(w.isDivider)return"---";const{markdownText:y,markdownBackText:E,hasMultilineChildren:R,practiceDirection:N}=w;let j;return E!==null?j=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?j=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):j=y,w.isCode&&(j="`"+j+"`"),w.isTodo&&(j=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+j),w.fontSize&&(j=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+j),j}function Ye(w,y){const E=[];w.type==="concept"?E.push("type:concept"):w.type==="descriptor"?E.push("type:descriptor"):w.type==="portal"&&(E.push("type:portal"),w.portalRefs.length>0&&E.push("refs:"+w.portalRefs.join(","))),w.isDocument&&E.push("doc"),w.isCardItem&&E.push("role:card-item"),y&&w.childrenCount>0&&E.push("children:"+w.childrenCount);for(const R of w.tags)E.push("tag:"+R.name+"("+R.id+")");return w.isTopLevel&&E.push("top"),E}function ue(w){return{markdownBackText:null,type:"default",hasMultilineChildren:!1,practiceDirection:"none",isCardItem:!1,isDocument:!1,isPortal:!1,portalRefs:[],tags:[],fontSize:null,isTodo:!1,todoStatus:null,isCode:!1,isDivider:!1,...w}}function fe(w,y=!1){const E=Ze(w),R=Ye(w,y),N=R.length>0?" "+R.join(" "):"";return`${E} <!--${w.id}${N}-->`}function ee(w){const y=w.isExact?"siblings":"nodes";return`<!--...elided ${w.isExact?"":">="}${w.count} ${y} (parent:${w.parentId} range:${w.rangeFrom}-${w.rangeTo} total:${w.totalSiblings})-->`}function ie(w){const y=[];function E(R,N){const j=" ".repeat(N);if(Z(R)){y.push(j+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(j+Q),!R.folded)for(const G of R.children)E(G,N+1)}return E(w,0),y.join(`
59
59
  `)}function me(w,y,E){if(w<=y)return{visibleIndices:null,elided:null};const R=Math.ceil(y*.7),N=Math.floor(y*.3);return{visibleIndices:{head:R,tail:N},elided:{count:w-R-N,parentId:E,rangeFrom:R,rangeTo:w-N-1,totalSiblings:w}}}async function q(w,y){const{remId:E,depth:R=3,maxNodes:N=200,maxSiblings:j=20,ancestorLevels:Q=0,includePowerup:G=!1}=y,ve=await w.rem.findOne(E);if(!ve)throw new Error(`Rem not found: ${E}`);let Fe=0,De=0,he=0;const mt={remaining:N},Me=[];async function $e(Qe,qe,Re){Fe++,mt.remaining--,Me.push(Qe._id);const xt=await Qe.getChildrenRem(),Je=G?xt:await Ve(xt);G||(he+=xt.length-Je.length);const Ke=Re!==-1&&qe>=Re&&Je.length>0,Qt=await ot(w,Qe,Je,{includePowerup:G,onFilteredTags:pn=>{De+=pn}});Ke&&(Qt.hasMultilineChildren=!1);const un=[];if(!Ke){const{visibleIndices:pn,elided:Zt}=me(Je.length,j,Qe._id);if(pn){const{head:ut,tail:ct}=pn;for(let Bt=0;Bt<ut&&mt.remaining>0;Bt++)un.push(await $e(Je[Bt],qe+1,Re));if(Zt){const Bt=Re!==-1&&qe+1>=Re;un.push({type:"elided",count:Zt.count,isExact:Bt,parentId:Zt.parentId,rangeFrom:Zt.rangeFrom,rangeTo:Zt.rangeTo,totalSiblings:Zt.totalSiblings})}const Nt=Je.length-ct;for(let Bt=Nt;Bt<Je.length&&mt.remaining>0;Bt++)un.push(await $e(Je[Bt],qe+1,Re))}else for(let ut=0;ut<Je.length;ut++){if(mt.remaining<=0){const ct=Je.length-ut;un.push({type:"elided",count:ct,isExact:!1,parentId:Qe._id,rangeFrom:ut,rangeTo:Je.length-1,totalSiblings:Je.length});break}un.push(await $e(Je[ut],qe+1,Re))}}return{rem:Qt,children:un,folded:Ke}}const wt=await $e(ve,0,R);await ve.getParentRem()||(wt.rem.isTopLevel=!0);const Be=ie(wt),Ge={rootId:E,depth:R,nodeCount:Fe,outline:Be,nodeRemIds:Me},nt=Math.min(Math.max(Q,0),10);if(nt>0){const Qe=[];let qe=ve;for(let Re=0;Re<nt;Re++){const xt=await qe.getParentRem();if(!xt)break;const[Je,an,Ke]=await Promise.all([Pt(w,xt.text??[]),xt.getChildrenRem(),xt.isDocument()]);Qe.push({id:xt._id,name:te(Je),childrenCount:an.length,isDocument:Ke}),qe=xt}if(Qe.length>0){const Re=Qe[Qe.length-1];await qe.getParentRem()||(Re.isTopLevel=!0),Ge.ancestors=Qe}}return!G&&(De>0||he>0)&&(Ge.powerupFiltered={tags:De,children:he}),Ge}async function ge(w,y){const E=await q(w,y),R=y.includePowerup??!1,N={};return await Promise.all(E.nodeRemIds.map(async j=>{const Q=await w.rem.findOne(j);Q&&(N[j]=await Ne(w,Q,{includePowerup:R}))})),{...E,remObjects:N}}async function C(w,y){const{depth:E=-1,maxNodes:R=200,maxSiblings:N=20}=y,Q=(await w.rem.getAll()).filter(Pe=>Pe.parent===null),G=await Promise.all(Q.map(Pe=>Pe.isDocument())),ve=Q.filter((Pe,Be)=>G[Be]);let Fe=0;const De={remaining:R};async function he(Pe,Be,Ge){Fe++,De.remaining--;const nt=await Pe.getChildrenRem(),Qe=await Ve(nt),qe=await Promise.all(Qe.map(ut=>ut.isDocument())),Re=Qe.filter((ut,ct)=>qe[ct]),xt=Qe.length-Re.length,an=Ge!==-1&&Be>=Ge&&Re.length>0,Ke=Pe.type===6,[Qt,un]=await Promise.all([Pt(w,Pe.text??[]),Ke?Pe.getPortalDirectlyIncludedRem():Promise.resolve([])]),pn=ue({id:Pe._id,markdownText:Qt.replace(/\n/g," "),childrenCount:Qe.length,isDocument:!0,isTopLevel:Pe.parent===null,isPortal:Ke,...Ke?{type:"portal",portalRefs:un.map(ut=>ut._id)}:{}}),Zt=[];if(!an&&Re.length>0){const{visibleIndices:ut,elided:ct}=me(Re.length,N,Pe._id);if(ut){const{head:Nt,tail:Bt}=ut;for(let Dt=0;Dt<Nt&&De.remaining>0;Dt++)Zt.push(await he(Re[Dt],Be+1,Ge));if(ct){const Dt=Ge!==-1&&Be+1>=Ge;Zt.push({type:"elided",count:ct.count,isExact:Dt,parentId:ct.parentId,rangeFrom:ct.rangeFrom,rangeTo:ct.rangeTo,totalSiblings:ct.totalSiblings})}const hn=Re.length-Bt;for(let Dt=hn;Dt<Re.length&&De.remaining>0;Dt++)Zt.push(await he(Re[Dt],Be+1,Ge))}else for(let Nt=0;Nt<Re.length;Nt++){if(De.remaining<=0){const Bt=Re.length-Nt;Zt.push({type:"elided",count:Bt,isExact:!1,parentId:Pe._id,rangeFrom:Nt,rangeTo:Re.length-1,totalSiblings:Re.length});break}Zt.push(await he(Re[Nt],Be+1,Ge))}}return{rem:pn,children:Zt,folded:an}}const mt=[],{visibleIndices:Me,elided:$e}=me(ve.length,N,"root");if(Me){const{head:Pe,tail:Be}=Me;for(let nt=0;nt<Pe&&De.remaining>0;nt++)mt.push(await he(ve[nt],0,E));$e&&mt.push({type:"elided",count:$e.count,isExact:!1,parentId:"root",rangeFrom:$e.rangeFrom,rangeTo:$e.rangeTo,totalSiblings:$e.totalSiblings});const Ge=ve.length-Be;for(let nt=Ge;nt<ve.length&&De.remaining>0;nt++)mt.push(await he(ve[nt],0,E))}else for(let Pe=0;Pe<ve.length;Pe++){if(De.remaining<=0){const Be=ve.length-Pe;mt.push({type:"elided",count:Be,isExact:!1,parentId:"root",rangeFrom:Pe,rangeTo:ve.length-1,totalSiblings:ve.length});break}mt.push(await he(ve[Pe],0,E))}const wt=["<!-- globe: \u77E5\u8BC6\u5E93\u6982\u89C8 -->"];for(const Pe of mt)Z(Pe)?wt.push(ee(Pe)):wt.push(ie(Pe));return{nodeCount:Fe,outline:wt.join(`
60
60
  `)}}async function V(w,y){const E=[];let R=y;for(;R;){const N=await Pt(w,R.text??[]);E.unshift(N.replace(/\n/g," ").trim()||R._id),R=await R.getParentRem()}return E}async function F(w,y){const{mode:E="focus",ancestorLevels:R=2,maxNodes:N=200,maxSiblings:j=20,depth:Q=3,focusRemId:G}=y;if(E==="page"){if(G)throw new Error("focusRemId \u4EC5\u5728 focus \u6A21\u5F0F\u4E0B\u6709\u6548\uFF0Cpage \u6A21\u5F0F\u4E0B\u8BF7\u52FF\u6307\u5B9A");return A(w,{maxNodes:N,maxSiblings:j,depth:Q})}return $(w,{ancestorLevels:R,maxNodes:N,maxSiblings:j,focusRemId:G})}async function A(w,y){const E=await w.window.getFocusedPaneId();if(!E)throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u9762\u677F ID\uFF0C\u8BF7\u786E\u4FDD\u6709\u6253\u5F00\u7684\u9875\u9762");const R=await w.window.getOpenPaneRemId(E);if(!R)throw new Error("\u5F53\u524D\u9762\u677F\u6CA1\u6709\u6253\u5F00\u4EFB\u4F55 Rem");const N=await w.rem.findOne(R);if(!N)throw new Error(`Page Rem not found: ${R}`);const j=await V(w,N);let Q=0;const G={remaining:y.maxNodes},ve=await U(w,N,0,y.depth,y.maxSiblings,G);Q=y.maxNodes-G.remaining;const De=`<!-- page: ${j[j.length-1]||R} -->
61
61
  <!-- path: ${j.join(" > ")} -->`+`
@@ -55,7 +55,7 @@ Add a <Suspense fallback=...> component higher in the tree to provide a loading
55
55
  *
56
56
  * This source code is licensed under the MIT license found in the
57
57
  * LICENSE file in the root directory of this source tree.
58
- */var Xe,Ot,Ae,it;if(typeof performance=="object"&&typeof performance.now=="function"){var B=performance;K.unstable_now=function(){return B.now()}}else{var wn=Date,Et=wn.now();K.unstable_now=function(){return wn.now()-Et}}if(typeof window>"u"||typeof MessageChannel!="function"){var Tt=null,b=null,Y=function(){if(Tt!==null)try{var F=K.unstable_now();Tt(!0,F),Tt=null}catch(A){throw setTimeout(Y,0),A}};Xe=function(F){Tt!==null?setTimeout(Xe,0,F):(Tt=F,setTimeout(Y,0))},Ot=function(F,A){b=setTimeout(F,A)},Ae=function(){clearTimeout(b)},K.unstable_shouldYield=function(){return!1},it=K.unstable_forceFrameRate=function(){}}else{var c=window.setTimeout,Mt=window.clearTimeout;if(typeof console<"u"){var ht=window.cancelAnimationFrame;typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof ht!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")}var Ve=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(F){0>F||125<F?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<F?Math.floor(1e3/F):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var F=K.unstable_now();ot=F+tt;try{at(!0,F)?ae.postMessage(null):(Ve=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else Ve=!1},Xe=function(F){at=F,Ve||(Ve=!0,ae.postMessage(null))},Ot=function(F,A){Pt=c(function(){F(K.unstable_now())},A)},Ae=function(){Mt(Pt),Pt=-1}}function _e(F,A){var $=F.length;F.push(A);e:for(;;){var H=$-1>>>1,ne=F[H];if(ne!==void 0&&0<st(ne,A))F[H]=A,F[$]=ne,$=H;else break e}}function Ne(F){return F=F[0],F===void 0?null:F}function He(F){var A=F[0];if(A!==void 0){var $=F.pop();if($!==A){F[0]=$;e:for(var H=0,ne=F.length;H<ne;){var U=2*(H+1)-1,ye=F[U],M=U+1,sn=F[M];if(ye!==void 0&&0>st(ye,$))sn!==void 0&&0>st(sn,ye)?(F[H]=sn,F[M]=$,H=M):(F[H]=ye,F[U]=$,H=U);else if(sn!==void 0&&0>st(sn,$))F[H]=sn,F[M]=$,H=M;else break e}}return A}return null}function st(F,A){var $=F.sortIndex-A.sortIndex;return $!==0?$:F.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(F){for(var A=Ne(Ze);A!==null;){if(A.callback===null)He(Ze);else if(A.startTime<=F)He(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Ne(Ze)}}function ge(F){if(me=!1,q(F),!ie)if(Ne(Z)!==null)ie=!0,Xe(C);else{var A=Ne(Ze);A!==null&&Ot(ge,A.startTime-F)}}function C(F,A){ie=!1,me&&(me=!1,Ae()),ee=!0;var $=fe;try{for(q(A),ue=Ne(Z);ue!==null&&(!(ue.expirationTime>A)||F&&!K.unstable_shouldYield());){var H=ue.callback;if(typeof H=="function"){ue.callback=null,fe=ue.priorityLevel;var ne=H(ue.expirationTime<=A);A=K.unstable_now(),typeof ne=="function"?ue.callback=ne:ue===Ne(Z)&&He(Z),q(A)}else He(Z);ue=Ne(Z)}if(ue!==null)var U=!0;else{var ye=Ne(Ze);ye!==null&&Ot(ge,ye.startTime-A),U=!1}return U}finally{ue=null,fe=$,ee=!1}}var V=it;K.unstable_IdlePriority=5,K.unstable_ImmediatePriority=1,K.unstable_LowPriority=4,K.unstable_NormalPriority=3,K.unstable_Profiling=null,K.unstable_UserBlockingPriority=2,K.unstable_cancelCallback=function(F){F.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,Xe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Ne(Z)},K.unstable_next=function(F){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return F()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(F,A){switch(F){case 1:case 2:case 3:case 4:case 5:break;default:F=3}var $=fe;fe=F;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(F,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,F){case 1:var ne=-1;break;case 2:ne=250;break;case 5:ne=1073741823;break;case 4:ne=1e4;break;default:ne=5e3}return ne=$+ne,F={id:Ye++,callback:A,priorityLevel:F,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(F.sortIndex=$,_e(Ze,F),Ne(Z)===null&&F===Ne(Ze)&&(me?Ae():me=!0,Ot(ge,$-H))):(F.sortIndex=ne,_e(Z,F),ie||ee||(ie=!0,Xe(C))),F},K.unstable_wrapCallback=function(F){var A=fe;return function(){var $=fe;fe=A;try{return F.apply(this,arguments)}finally{fe=$}}}},825(en,K,Xe){"use strict";en.exports=Xe(742)}},vd={};function Ac(en){var K=vd[en];if(K!==void 0)return K.exports;var Xe=vd[en]={exports:{}};return Pf[en](Xe,Xe.exports,Ac),Xe.exports}Ac.g=(function(){if(typeof globalThis=="object")return globalThis;try{return this||new Function("return this")()}catch{if(typeof window=="object")return window}})();var wp={};(()=>{"use strict";var en=Ac(216);const K="0.2.0",Xe=[29100,29110,29120,29130],Ot=18e3;var Ae=Object.defineProperty,it=(w,y,E)=>y in w?Ae(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,B=(w,y,E)=>it(w,typeof y!="symbol"?y+"":y,E);const wn=4e3,Et=4003;class Tt{constructor(y){B(this,"ws",null),B(this,"reconnectAttempts",0),B(this,"reconnectTimeout",null),B(this,"messageHandler",null),B(this,"status","disconnected"),B(this,"isShuttingDown",!1),B(this,"isPreempted",!1),B(this,"_sdkReady"),B(this,"config"),this._sdkReady=y.sdkReady,this.config={url:y.url,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:y.isTwinConnection??!1,maxReconnectAttempts:y.maxReconnectAttempts??10,initialReconnectDelay:y.initialReconnectDelay??1e3,maxReconnectDelay:y.maxReconnectDelay??3e4,onStatusChange:y.onStatusChange,onLog:y.onLog,onPreempted:y.onPreempted,onTwinOccupied:y.onTwinOccupied,onOtherOccupied:y.onOtherOccupied}}log(y,E="info"){this.config.onLog?.(y,E)}setStatus(y){this.status!==y&&(this.status=y,this.config.onStatusChange?.(y))}sendHello(){const y={type:"hello",version:this.config.pluginVersion,sdkReady:this._sdkReady,twinSlotIndex:this.config.twinSlotIndex};try{this.ws?.send(JSON.stringify(y)),this.log(`\u53D1\u9001 hello\uFF08v${this.config.pluginVersion}, sdkReady=${this._sdkReady}, twinSlot=${this.config.twinSlotIndex}\uFF09`)}catch(E){this.log(`\u53D1\u9001 hello \u5931\u8D25: ${E}`,"warn")}}connect(){if(!(this.ws?.readyState===WebSocket.OPEN||this.ws?.readyState===WebSocket.CONNECTING)){this.isShuttingDown=!1,this.isPreempted=!1,this.setStatus("connecting");try{this.ws=new WebSocket(this.config.url),this.ws.onopen=()=>{this.log("\u5DF2\u8FDE\u63A5\u5230\u5B88\u62A4\u8FDB\u7A0B"),this.reconnectAttempts=0,this.setStatus("connected"),this.sendHello()},this.ws.onmessage=async y=>{await this.handleMessage(typeof y.data=="string"?y.data:String(y.data))},this.ws.onclose=y=>{y.code!==1006&&this.log(`\u8FDE\u63A5\u65AD\u5F00: ${y.code} ${y.reason}`,"warn"),this.setStatus("disconnected"),y.code===Et?this.config.onTwinOccupied?.():y.code===wn&&this.config.onOtherOccupied?.(),this.isShuttingDown||this.scheduleReconnect()},this.ws.onerror=()=>{}}catch(y){this.log(`\u8FDE\u63A5\u5931\u8D25: ${y}`,"error"),this.setStatus("disconnected"),this.scheduleReconnect()}}}async handleMessage(y){try{const E=JSON.parse(y);if(E.type==="preempted"){this.isPreempted=!0,this.log(`\u88AB\u5B6A\u751F Plugin \u62A2\u5360: ${E.reason}`,"warn"),this.config.onPreempted?.();return}if(E.type==="ping"){this.ws?.send(JSON.stringify({type:"pong"}));return}if(E.id&&E.action&&this.messageHandler){const R=E;this.log(`\u6536\u5230\u8BF7\u6C42: ${R.action}`);try{const N=await this.messageHandler(R),j={id:R.id,result:N};this.ws?.send(JSON.stringify(j)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const j=N instanceof Error?N.message:String(N),Q={id:R.id,error:j};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${j}`,"error")}}}catch(E){this.log(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${E}`,"error")}}scheduleReconnect(){if(this.isShuttingDown||this.isPreempted||!this.config.isTwinConnection)return;if(this.reconnectAttempts>=this.config.maxReconnectAttempts){this.log("\u5DF2\u8FBE\u6700\u5927\u91CD\u8FDE\u6B21\u6570","error");return}const y=Math.min(this.config.initialReconnectDelay*Math.pow(2,this.reconnectAttempts),this.config.maxReconnectDelay),E=Math.random()*.3*y,R=y+E;this.reconnectAttempts++,this.log(`${Math.round(R)}ms \u540E\u91CD\u8FDE\uFF08\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21\uFF09`),this.reconnectTimeout=setTimeout(()=>{this.connect()},R)}setMessageHandler(y){this.messageHandler=y}disconnect(){this.isShuttingDown=!0,this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(1e3,"Plugin disconnect"),this.ws=null),this.setStatus("disconnected")}getStatus(){return this.status}}var b=Object.defineProperty,Y=(w,y,E)=>y in w?b(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,c=(w,y,E)=>Y(w,typeof y!="symbol"?y+"":y,E);class Mt{constructor(y){c(this,"clients",[]),c(this,"slotStates",[]),c(this,"scanTimer",null),c(this,"config"),this.config=y;for(let E=0;E<Xe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:Xe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${Xe[E]}`,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:R,maxReconnectAttempts:R?10:0,initialReconnectDelay:1e3,maxReconnectDelay:3e4,onStatusChange:N=>this.handleStatusChange(E,N),onLog:(N,j)=>y.onLog(E,N,j),onPreempted:()=>this.handleDisconnectReason(E,"preempted"),onTwinOccupied:()=>this.handleDisconnectReason(E,"twin_occupied"),onOtherOccupied:()=>this.handleDisconnectReason(E,"other_occupied")}))}}setMessageHandler(y){for(const E of this.clients)E.setMessageHandler(y)}start(){const y=this.config.twinSlotIndex;this.clients[y].connect(),setTimeout(()=>{for(let E=0;E<this.clients.length;E++)E!==y&&this.clients[E].connect()},2e3),this.scanTimer=setInterval(()=>this.scanAndReconnect(),Ot)}stop(){this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null);for(const y of this.clients)y.disconnect()}getSlots(){return this.slotStates.slice()}handleStatusChange(y,E){const R=this.slotStates[y];R.status=E,E==="connected"&&(R.disconnectReason=null),this.notifySlotsChange()}handleDisconnectReason(y,E){this.slotStates[y].disconnectReason=E,this.notifySlotsChange()}notifySlotsChange(){this.config.onSlotsChange(this.slotStates.slice())}scanAndReconnect(){for(let y=0;y<this.clients.length;y++){const E=this.slotStates[y];E.isTwin||E.status==="connected"||E.status==="connecting"||this.clients[y].connect()}}}async function ht(w){const[y,E,R,N]=await Promise.all([w.isPowerupProperty(),w.isPowerupSlot(),w.isPowerupPropertyListItem(),w.isPowerupEnum()]);return y||E||R||N}async function Ve(w){const y=await Promise.all(w.map(ht));return w.filter((E,R)=>!y[R])}async function at(w){const y=await Promise.all(w.map(E=>E.isPowerup()));return w.filter((E,R)=>!y[R])}async function Pt(w,y){try{return await w.richText.toMarkdown(y)}catch{return tt(y)}}function tt(w){return w.map(y=>{if(typeof y=="string")return y;if(typeof y!="object"||y===null)return"";const E=y;switch(E.i){case"m":return String(E.text??"");case"q":return`[[${String(E._id??"")}]]`;case"u":return E.title?`[${String(E.title)}](${String(E.url)})`:String(E.url??"");case"x":return`$${String(E.text??"")}$`;case"i":return`![image](${String(E.url??"")})`;case"a":return`[audio](${String(E.url??"")})`;case"p":return String(E.text??"");case"g":return String(E.text??"");case"n":return String(E.text??"");case"o":return String(E.text??"");case"s":case"fi":case"ai":return"";default:return String(E.text??E.url??"")}}).join("")}async function ot(w,y,E,R){const N=R?.includePowerup??!1,[j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be]=await Promise.all([Pt(w,y.text??[]),y.backText?Pt(w,y.backText):Promise.resolve(null),y.getType(),y.isCardItem(),y.isDocument(),y.getTagRems().then(qe=>N?qe:at(qe).then(Re=>{const xt=qe.length-Re.length;return xt>0&&R?.onFilteredTags?.(xt),Re})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let Ge=!1;E.length>0&&(Ge=(await Promise.all(E.map(Re=>Re.isCardItem()))).some(Boolean));const nt=Pe&&(y.text??[]).length===0,Qe=await Promise.all(De.map(async qe=>({id:qe._id,name:te(await Pt(w,qe.text??[]))})));return{id:y._id,markdownText:te(j),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:Ge,practiceDirection:he??"none",isCardItem:ve,isDocument:Fe,isPortal:y.type===6,portalRefs:Be.map(qe=>qe._id),childrenCount:E.length,tags:Qe,fontSize:mt??null,isTodo:Me,todoStatus:$e??null,isCode:wt,isDivider:nt,isTopLevel:y.parent===null}}function te(w){return w.replace(/\n/g," ")}function ae(w){switch(w){case 1:return"concept";case 2:return"descriptor";case 6:return"portal";default:return"default"}}async function _e(w,y){const{includePowerup:E=!1}=y,R=await w.rem.findOne(y.remId);if(!R)throw new Error(`Rem not found: ${y.remId}`);return Ne(w,R,{includePowerup:E})}async function Ne(w,y,E){const{includePowerup:R=!1}=E,[N,j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be,Ge,nt,Qe,qe,Re,xt,Je,an,Ke,Qt,un,pn,Zt,ut,ct,Nt,Bt,hn,Dt,Jn,ur,So,er,Tr,_n,Sn,Dn,cr,xr]=await Promise.all([y.isDocument(),y.getFontSize(),y.getHighlightColor(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.isCardItem(),y.isTable(),y.isSlot(),y.isProperty(),y.getEnablePractice(),y.getPracticeDirection(),y.getTagRems(),y.getSources(),y.getAliases(),y.positionAmongstSiblings(),y.isPowerup(),y.isPowerupEnum(),y.isPowerupProperty(),y.isPowerupPropertyListItem(),y.isPowerupSlot(),y.getPortalType(),y.getPortalDirectlyIncludedRem(),y.getPropertyType(),y.remsBeingReferenced(),y.deepRemsBeingReferenced(),y.remsReferencingThis(),y.taggedRem(),y.ancestorTagRem(),y.descendantTagRem(),y.getDescendants(),y.siblingRem(),y.portalsAndDocumentsIn(),y.allRemInDocumentOrPortal(),y.allRemInFolderQueue(),y.getChildrenRem(),y.timesSelectedInSearch(),y.getLastTimeMovedTo(),y.getSchemaVersion(),y.embeddedQueueViewMode(),y.getLastPracticed()]);let dr=Ge,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Ge),mi=Ge.length-dr.length;const ze=await Ve(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:He(y.text??[]),backText:y.backText?He(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:j??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Fe,isQuote:De,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:$e,isProperty:wt,isPowerup:Re,isPowerupEnum:xt,isPowerupProperty:Je,isPowerupPropertyListItem:an,isPowerupSlot:Ke,portalType:ae(y.type)==="portal"?st(Qt):null,portalDirectlyIncludedRem:un.map(ze=>ze._id),propertyType:pn??null,enablePractice:Pe,practiceDirection:Be,tags:dr.map(ze=>ze._id),sources:nt.map(ze=>ze._id),aliases:Qe.map(ze=>ze._id),remsBeingReferenced:Zt.map(ze=>ze._id),deepRemsBeingReferenced:ut.map(ze=>ze._id),remsReferencingThis:ct.map(ze=>ze._id),taggedRem:Nt.map(ze=>ze._id),ancestorTagRem:Bt.map(ze=>ze._id),descendantTagRem:hn.map(ze=>ze._id),descendants:Dt.map(ze=>ze._id),siblingRem:Jn.map(ze=>ze._id),portalsAndDocumentsIn:ur.map(ze=>ze._id),allRemInDocumentOrPortal:So.map(ze=>ze._id),allRemInFolderQueue:er.map(ze=>ze._id),positionAmongstSiblings:qe??null,timesSelectedInSearch:_n,lastTimeMovedTo:Sn,schemaVersion:Dn,embeddedQueueViewMode:cr,createdAt:y.createdAt,updatedAt:y.updatedAt,localUpdatedAt:y.localUpdatedAt,lastPracticed:xr};return!R&&(mi>0||Mr>0)?{...fr,powerupFiltered:{tags:mi,children:Mr}}:fr}function He(w){return w.map(y=>{if(typeof y=="string")return y;const E={};for(const R of Object.keys(y).sort())E[R]=y[R];return E})}function st(w){switch(w){case 2:return"embedded_queue";case 3:return"scaffold";case 4:return"search_portal";default:return"portal"}}function Z(w){return"type"in w&&w.type==="elided"}function Ze(w){if(w.isDivider)return"---";const{markdownText:y,markdownBackText:E,hasMultilineChildren:R,practiceDirection:N}=w;let j;return E!==null?j=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?j=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):j=y,w.isCode&&(j="`"+j+"`"),w.isTodo&&(j=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+j),w.fontSize&&(j=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+j),j}function Ye(w,y){const E=[];w.type==="concept"?E.push("type:concept"):w.type==="descriptor"?E.push("type:descriptor"):w.type==="portal"&&(E.push("type:portal"),w.portalRefs.length>0&&E.push("refs:"+w.portalRefs.join(","))),w.isDocument&&E.push("doc"),w.isCardItem&&E.push("role:card-item"),y&&w.childrenCount>0&&E.push("children:"+w.childrenCount);for(const R of w.tags)E.push("tag:"+R.name+"("+R.id+")");return w.isTopLevel&&E.push("top"),E}function ue(w){return{markdownBackText:null,type:"default",hasMultilineChildren:!1,practiceDirection:"none",isCardItem:!1,isDocument:!1,isPortal:!1,portalRefs:[],tags:[],fontSize:null,isTodo:!1,todoStatus:null,isCode:!1,isDivider:!1,...w}}function fe(w,y=!1){const E=Ze(w),R=Ye(w,y),N=R.length>0?" "+R.join(" "):"";return`${E} <!--${w.id}${N}-->`}function ee(w){const y=w.isExact?"siblings":"nodes";return`<!--...elided ${w.isExact?"":">="}${w.count} ${y} (parent:${w.parentId} range:${w.rangeFrom}-${w.rangeTo} total:${w.totalSiblings})-->`}function ie(w){const y=[];function E(R,N){const j=" ".repeat(N);if(Z(R)){y.push(j+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(j+Q),!R.folded)for(const G of R.children)E(G,N+1)}return E(w,0),y.join(`
58
+ */var Xe,Ot,Ae,it;if(typeof performance=="object"&&typeof performance.now=="function"){var B=performance;K.unstable_now=function(){return B.now()}}else{var wn=Date,Et=wn.now();K.unstable_now=function(){return wn.now()-Et}}if(typeof window>"u"||typeof MessageChannel!="function"){var Tt=null,b=null,Y=function(){if(Tt!==null)try{var F=K.unstable_now();Tt(!0,F),Tt=null}catch(A){throw setTimeout(Y,0),A}};Xe=function(F){Tt!==null?setTimeout(Xe,0,F):(Tt=F,setTimeout(Y,0))},Ot=function(F,A){b=setTimeout(F,A)},Ae=function(){clearTimeout(b)},K.unstable_shouldYield=function(){return!1},it=K.unstable_forceFrameRate=function(){}}else{var c=window.setTimeout,Mt=window.clearTimeout;if(typeof console<"u"){var ht=window.cancelAnimationFrame;typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof ht!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")}var Ve=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(F){0>F||125<F?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<F?Math.floor(1e3/F):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var F=K.unstable_now();ot=F+tt;try{at(!0,F)?ae.postMessage(null):(Ve=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else Ve=!1},Xe=function(F){at=F,Ve||(Ve=!0,ae.postMessage(null))},Ot=function(F,A){Pt=c(function(){F(K.unstable_now())},A)},Ae=function(){Mt(Pt),Pt=-1}}function _e(F,A){var $=F.length;F.push(A);e:for(;;){var H=$-1>>>1,ne=F[H];if(ne!==void 0&&0<st(ne,A))F[H]=A,F[$]=ne,$=H;else break e}}function Ne(F){return F=F[0],F===void 0?null:F}function He(F){var A=F[0];if(A!==void 0){var $=F.pop();if($!==A){F[0]=$;e:for(var H=0,ne=F.length;H<ne;){var U=2*(H+1)-1,ye=F[U],M=U+1,sn=F[M];if(ye!==void 0&&0>st(ye,$))sn!==void 0&&0>st(sn,ye)?(F[H]=sn,F[M]=$,H=M):(F[H]=ye,F[U]=$,H=U);else if(sn!==void 0&&0>st(sn,$))F[H]=sn,F[M]=$,H=M;else break e}}return A}return null}function st(F,A){var $=F.sortIndex-A.sortIndex;return $!==0?$:F.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(F){for(var A=Ne(Ze);A!==null;){if(A.callback===null)He(Ze);else if(A.startTime<=F)He(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Ne(Ze)}}function ge(F){if(me=!1,q(F),!ie)if(Ne(Z)!==null)ie=!0,Xe(C);else{var A=Ne(Ze);A!==null&&Ot(ge,A.startTime-F)}}function C(F,A){ie=!1,me&&(me=!1,Ae()),ee=!0;var $=fe;try{for(q(A),ue=Ne(Z);ue!==null&&(!(ue.expirationTime>A)||F&&!K.unstable_shouldYield());){var H=ue.callback;if(typeof H=="function"){ue.callback=null,fe=ue.priorityLevel;var ne=H(ue.expirationTime<=A);A=K.unstable_now(),typeof ne=="function"?ue.callback=ne:ue===Ne(Z)&&He(Z),q(A)}else He(Z);ue=Ne(Z)}if(ue!==null)var U=!0;else{var ye=Ne(Ze);ye!==null&&Ot(ge,ye.startTime-A),U=!1}return U}finally{ue=null,fe=$,ee=!1}}var V=it;K.unstable_IdlePriority=5,K.unstable_ImmediatePriority=1,K.unstable_LowPriority=4,K.unstable_NormalPriority=3,K.unstable_Profiling=null,K.unstable_UserBlockingPriority=2,K.unstable_cancelCallback=function(F){F.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,Xe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Ne(Z)},K.unstable_next=function(F){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return F()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(F,A){switch(F){case 1:case 2:case 3:case 4:case 5:break;default:F=3}var $=fe;fe=F;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(F,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,F){case 1:var ne=-1;break;case 2:ne=250;break;case 5:ne=1073741823;break;case 4:ne=1e4;break;default:ne=5e3}return ne=$+ne,F={id:Ye++,callback:A,priorityLevel:F,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(F.sortIndex=$,_e(Ze,F),Ne(Z)===null&&F===Ne(Ze)&&(me?Ae():me=!0,Ot(ge,$-H))):(F.sortIndex=ne,_e(Z,F),ie||ee||(ie=!0,Xe(C))),F},K.unstable_wrapCallback=function(F){var A=fe;return function(){var $=fe;fe=A;try{return F.apply(this,arguments)}finally{fe=$}}}},825(en,K,Xe){"use strict";en.exports=Xe(742)}},vd={};function Ac(en){var K=vd[en];if(K!==void 0)return K.exports;var Xe=vd[en]={exports:{}};return Pf[en](Xe,Xe.exports,Ac),Xe.exports}Ac.g=(function(){if(typeof globalThis=="object")return globalThis;try{return this||new Function("return this")()}catch{if(typeof window=="object")return window}})();var wp={};(()=>{"use strict";var en=Ac(216);const K="0.2.1",Xe=[29100,29110,29120,29130],Ot=18e3;var Ae=Object.defineProperty,it=(w,y,E)=>y in w?Ae(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,B=(w,y,E)=>it(w,typeof y!="symbol"?y+"":y,E);const wn=4e3,Et=4003;class Tt{constructor(y){B(this,"ws",null),B(this,"reconnectAttempts",0),B(this,"reconnectTimeout",null),B(this,"messageHandler",null),B(this,"status","disconnected"),B(this,"isShuttingDown",!1),B(this,"isPreempted",!1),B(this,"_sdkReady"),B(this,"config"),this._sdkReady=y.sdkReady,this.config={url:y.url,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:y.isTwinConnection??!1,maxReconnectAttempts:y.maxReconnectAttempts??10,initialReconnectDelay:y.initialReconnectDelay??1e3,maxReconnectDelay:y.maxReconnectDelay??3e4,onStatusChange:y.onStatusChange,onLog:y.onLog,onPreempted:y.onPreempted,onTwinOccupied:y.onTwinOccupied,onOtherOccupied:y.onOtherOccupied}}log(y,E="info"){this.config.onLog?.(y,E)}setStatus(y){this.status!==y&&(this.status=y,this.config.onStatusChange?.(y))}sendHello(){const y={type:"hello",version:this.config.pluginVersion,sdkReady:this._sdkReady,twinSlotIndex:this.config.twinSlotIndex};try{this.ws?.send(JSON.stringify(y)),this.log(`\u53D1\u9001 hello\uFF08v${this.config.pluginVersion}, sdkReady=${this._sdkReady}, twinSlot=${this.config.twinSlotIndex}\uFF09`)}catch(E){this.log(`\u53D1\u9001 hello \u5931\u8D25: ${E}`,"warn")}}connect(){if(!(this.ws?.readyState===WebSocket.OPEN||this.ws?.readyState===WebSocket.CONNECTING)){this.isShuttingDown=!1,this.isPreempted=!1,this.setStatus("connecting");try{this.ws=new WebSocket(this.config.url),this.ws.onopen=()=>{this.log("\u5DF2\u8FDE\u63A5\u5230\u5B88\u62A4\u8FDB\u7A0B"),this.reconnectAttempts=0,this.setStatus("connected"),this.sendHello()},this.ws.onmessage=async y=>{await this.handleMessage(typeof y.data=="string"?y.data:String(y.data))},this.ws.onclose=y=>{y.code!==1006&&this.log(`\u8FDE\u63A5\u65AD\u5F00: ${y.code} ${y.reason}`,"warn"),this.setStatus("disconnected"),y.code===Et?this.config.onTwinOccupied?.():y.code===wn&&this.config.onOtherOccupied?.(),this.isShuttingDown||this.scheduleReconnect()},this.ws.onerror=()=>{}}catch(y){this.log(`\u8FDE\u63A5\u5931\u8D25: ${y}`,"error"),this.setStatus("disconnected"),this.scheduleReconnect()}}}async handleMessage(y){try{const E=JSON.parse(y);if(E.type==="preempted"){this.isPreempted=!0,this.log(`\u88AB\u5B6A\u751F Plugin \u62A2\u5360: ${E.reason}`,"warn"),this.config.onPreempted?.();return}if(E.type==="ping"){this.ws?.send(JSON.stringify({type:"pong"}));return}if(E.id&&E.action&&this.messageHandler){const R=E;this.log(`\u6536\u5230\u8BF7\u6C42: ${R.action}`);try{const N=await this.messageHandler(R),j={id:R.id,result:N};this.ws?.send(JSON.stringify(j)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const j=N instanceof Error?N.message:String(N),Q={id:R.id,error:j};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${j}`,"error")}}}catch(E){this.log(`\u5904\u7406\u6D88\u606F\u5931\u8D25: ${E}`,"error")}}scheduleReconnect(){if(this.isShuttingDown||this.isPreempted||!this.config.isTwinConnection)return;if(this.reconnectAttempts>=this.config.maxReconnectAttempts){this.log("\u5DF2\u8FBE\u6700\u5927\u91CD\u8FDE\u6B21\u6570","error");return}const y=Math.min(this.config.initialReconnectDelay*Math.pow(2,this.reconnectAttempts),this.config.maxReconnectDelay),E=Math.random()*.3*y,R=y+E;this.reconnectAttempts++,this.log(`${Math.round(R)}ms \u540E\u91CD\u8FDE\uFF08\u7B2C ${this.reconnectAttempts}/${this.config.maxReconnectAttempts} \u6B21\uFF09`),this.reconnectTimeout=setTimeout(()=>{this.connect()},R)}setMessageHandler(y){this.messageHandler=y}disconnect(){this.isShuttingDown=!0,this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(1e3,"Plugin disconnect"),this.ws=null),this.setStatus("disconnected")}getStatus(){return this.status}}var b=Object.defineProperty,Y=(w,y,E)=>y in w?b(w,y,{enumerable:!0,configurable:!0,writable:!0,value:E}):w[y]=E,c=(w,y,E)=>Y(w,typeof y!="symbol"?y+"":y,E);class Mt{constructor(y){c(this,"clients",[]),c(this,"slotStates",[]),c(this,"scanTimer",null),c(this,"config"),this.config=y;for(let E=0;E<Xe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:Xe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${Xe[E]}`,pluginVersion:y.pluginVersion,sdkReady:y.sdkReady,twinSlotIndex:y.twinSlotIndex,isTwinConnection:R,maxReconnectAttempts:R?10:0,initialReconnectDelay:1e3,maxReconnectDelay:3e4,onStatusChange:N=>this.handleStatusChange(E,N),onLog:(N,j)=>y.onLog(E,N,j),onPreempted:()=>this.handleDisconnectReason(E,"preempted"),onTwinOccupied:()=>this.handleDisconnectReason(E,"twin_occupied"),onOtherOccupied:()=>this.handleDisconnectReason(E,"other_occupied")}))}}setMessageHandler(y){for(const E of this.clients)E.setMessageHandler(y)}start(){const y=this.config.twinSlotIndex;this.clients[y].connect(),setTimeout(()=>{for(let E=0;E<this.clients.length;E++)E!==y&&this.clients[E].connect()},2e3),this.scanTimer=setInterval(()=>this.scanAndReconnect(),Ot)}stop(){this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null);for(const y of this.clients)y.disconnect()}getSlots(){return this.slotStates.slice()}handleStatusChange(y,E){const R=this.slotStates[y];R.status=E,E==="connected"&&(R.disconnectReason=null),this.notifySlotsChange()}handleDisconnectReason(y,E){this.slotStates[y].disconnectReason=E,this.notifySlotsChange()}notifySlotsChange(){this.config.onSlotsChange(this.slotStates.slice())}scanAndReconnect(){for(let y=0;y<this.clients.length;y++){const E=this.slotStates[y];E.isTwin||E.status==="connected"||E.status==="connecting"||this.clients[y].connect()}}}async function ht(w){const[y,E,R,N]=await Promise.all([w.isPowerupProperty(),w.isPowerupSlot(),w.isPowerupPropertyListItem(),w.isPowerupEnum()]);return y||E||R||N}async function Ve(w){const y=await Promise.all(w.map(ht));return w.filter((E,R)=>!y[R])}async function at(w){const y=await Promise.all(w.map(E=>E.isPowerup()));return w.filter((E,R)=>!y[R])}async function Pt(w,y){try{return await w.richText.toMarkdown(y)}catch{return tt(y)}}function tt(w){return w.map(y=>{if(typeof y=="string")return y;if(typeof y!="object"||y===null)return"";const E=y;switch(E.i){case"m":return String(E.text??"");case"q":return`[[${String(E._id??"")}]]`;case"u":return E.title?`[${String(E.title)}](${String(E.url)})`:String(E.url??"");case"x":return`$${String(E.text??"")}$`;case"i":return`![image](${String(E.url??"")})`;case"a":return`[audio](${String(E.url??"")})`;case"p":return String(E.text??"");case"g":return String(E.text??"");case"n":return String(E.text??"");case"o":return String(E.text??"");case"s":case"fi":case"ai":return"";default:return String(E.text??E.url??"")}}).join("")}async function ot(w,y,E,R){const N=R?.includePowerup??!1,[j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be]=await Promise.all([Pt(w,y.text??[]),y.backText?Pt(w,y.backText):Promise.resolve(null),y.getType(),y.isCardItem(),y.isDocument(),y.getTagRems().then(qe=>N?qe:at(qe).then(Re=>{const xt=qe.length-Re.length;return xt>0&&R?.onFilteredTags?.(xt),Re})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let Ge=!1;E.length>0&&(Ge=(await Promise.all(E.map(Re=>Re.isCardItem()))).some(Boolean));const nt=Pe&&(y.text??[]).length===0,Qe=await Promise.all(De.map(async qe=>({id:qe._id,name:te(await Pt(w,qe.text??[]))})));return{id:y._id,markdownText:te(j),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:Ge,practiceDirection:he??"none",isCardItem:ve,isDocument:Fe,isPortal:y.type===6,portalRefs:Be.map(qe=>qe._id),childrenCount:E.length,tags:Qe,fontSize:mt??null,isTodo:Me,todoStatus:$e??null,isCode:wt,isDivider:nt,isTopLevel:y.parent===null}}function te(w){return w.replace(/\n/g," ")}function ae(w){switch(w){case 1:return"concept";case 2:return"descriptor";case 6:return"portal";default:return"default"}}async function _e(w,y){const{includePowerup:E=!1}=y,R=await w.rem.findOne(y.remId);if(!R)throw new Error(`Rem not found: ${y.remId}`);return Ne(w,R,{includePowerup:E})}async function Ne(w,y,E){const{includePowerup:R=!1}=E,[N,j,Q,G,ve,Fe,De,he,mt,Me,$e,wt,Pe,Be,Ge,nt,Qe,qe,Re,xt,Je,an,Ke,Qt,un,pn,Zt,ut,ct,Nt,Bt,hn,Dt,Jn,ur,So,er,Tr,_n,Sn,Dn,cr,xr]=await Promise.all([y.isDocument(),y.getFontSize(),y.getHighlightColor(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.isCardItem(),y.isTable(),y.isSlot(),y.isProperty(),y.getEnablePractice(),y.getPracticeDirection(),y.getTagRems(),y.getSources(),y.getAliases(),y.positionAmongstSiblings(),y.isPowerup(),y.isPowerupEnum(),y.isPowerupProperty(),y.isPowerupPropertyListItem(),y.isPowerupSlot(),y.getPortalType(),y.getPortalDirectlyIncludedRem(),y.getPropertyType(),y.remsBeingReferenced(),y.deepRemsBeingReferenced(),y.remsReferencingThis(),y.taggedRem(),y.ancestorTagRem(),y.descendantTagRem(),y.getDescendants(),y.siblingRem(),y.portalsAndDocumentsIn(),y.allRemInDocumentOrPortal(),y.allRemInFolderQueue(),y.getChildrenRem(),y.timesSelectedInSearch(),y.getLastTimeMovedTo(),y.getSchemaVersion(),y.embeddedQueueViewMode(),y.getLastPracticed()]);let dr=Ge,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Ge),mi=Ge.length-dr.length;const ze=await Ve(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:He(y.text??[]),backText:y.backText?He(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:j??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Fe,isQuote:De,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:$e,isProperty:wt,isPowerup:Re,isPowerupEnum:xt,isPowerupProperty:Je,isPowerupPropertyListItem:an,isPowerupSlot:Ke,portalType:ae(y.type)==="portal"?st(Qt):null,portalDirectlyIncludedRem:un.map(ze=>ze._id).sort(),propertyType:pn??null,enablePractice:Pe,practiceDirection:Be,tags:dr.map(ze=>ze._id).sort(),sources:nt.map(ze=>ze._id).sort(),aliases:Qe.map(ze=>ze._id).sort(),remsBeingReferenced:Zt.map(ze=>ze._id).sort(),deepRemsBeingReferenced:ut.map(ze=>ze._id).sort(),remsReferencingThis:ct.map(ze=>ze._id).sort(),taggedRem:Nt.map(ze=>ze._id).sort(),ancestorTagRem:Bt.map(ze=>ze._id).sort(),descendantTagRem:hn.map(ze=>ze._id).sort(),descendants:Dt.map(ze=>ze._id).sort(),siblingRem:Jn.map(ze=>ze._id).sort(),portalsAndDocumentsIn:ur.map(ze=>ze._id).sort(),allRemInDocumentOrPortal:So.map(ze=>ze._id).sort(),allRemInFolderQueue:er.map(ze=>ze._id).sort(),positionAmongstSiblings:qe??null,timesSelectedInSearch:_n,lastTimeMovedTo:Sn,schemaVersion:Dn,embeddedQueueViewMode:cr,createdAt:y.createdAt,updatedAt:y.updatedAt,localUpdatedAt:y.localUpdatedAt,lastPracticed:xr};return!R&&(mi>0||Mr>0)?{...fr,powerupFiltered:{tags:mi,children:Mr}}:fr}function He(w){return w.map(y=>{if(typeof y=="string")return y;const E={};for(const R of Object.keys(y).sort())E[R]=y[R];return E})}function st(w){switch(w){case 2:return"embedded_queue";case 3:return"scaffold";case 4:return"search_portal";default:return"portal"}}function Z(w){return"type"in w&&w.type==="elided"}function Ze(w){if(w.isDivider)return"---";const{markdownText:y,markdownBackText:E,hasMultilineChildren:R,practiceDirection:N}=w;let j;return E!==null?j=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?j=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):j=y,w.isCode&&(j="`"+j+"`"),w.isTodo&&(j=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+j),w.fontSize&&(j=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+j),j}function Ye(w,y){const E=[];w.type==="concept"?E.push("type:concept"):w.type==="descriptor"?E.push("type:descriptor"):w.type==="portal"&&(E.push("type:portal"),w.portalRefs.length>0&&E.push("refs:"+w.portalRefs.join(","))),w.isDocument&&E.push("doc"),w.isCardItem&&E.push("role:card-item"),y&&w.childrenCount>0&&E.push("children:"+w.childrenCount);for(const R of w.tags)E.push("tag:"+R.name+"("+R.id+")");return w.isTopLevel&&E.push("top"),E}function ue(w){return{markdownBackText:null,type:"default",hasMultilineChildren:!1,practiceDirection:"none",isCardItem:!1,isDocument:!1,isPortal:!1,portalRefs:[],tags:[],fontSize:null,isTodo:!1,todoStatus:null,isCode:!1,isDivider:!1,...w}}function fe(w,y=!1){const E=Ze(w),R=Ye(w,y),N=R.length>0?" "+R.join(" "):"";return`${E} <!--${w.id}${N}-->`}function ee(w){const y=w.isExact?"siblings":"nodes";return`<!--...elided ${w.isExact?"":">="}${w.count} ${y} (parent:${w.parentId} range:${w.rangeFrom}-${w.rangeTo} total:${w.totalSiblings})-->`}function ie(w){const y=[];function E(R,N){const j=" ".repeat(N);if(Z(R)){y.push(j+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(j+Q),!R.folded)for(const G of R.children)E(G,N+1)}return E(w,0),y.join(`
59
59
  `)}function me(w,y,E){if(w<=y)return{visibleIndices:null,elided:null};const R=Math.ceil(y*.7),N=Math.floor(y*.3);return{visibleIndices:{head:R,tail:N},elided:{count:w-R-N,parentId:E,rangeFrom:R,rangeTo:w-N-1,totalSiblings:w}}}async function q(w,y){const{remId:E,depth:R=3,maxNodes:N=200,maxSiblings:j=20,ancestorLevels:Q=0,includePowerup:G=!1}=y,ve=await w.rem.findOne(E);if(!ve)throw new Error(`Rem not found: ${E}`);let Fe=0,De=0,he=0;const mt={remaining:N},Me=[];async function $e(Qe,qe,Re){Fe++,mt.remaining--,Me.push(Qe._id);const xt=await Qe.getChildrenRem(),Je=G?xt:await Ve(xt);G||(he+=xt.length-Je.length);const Ke=Re!==-1&&qe>=Re&&Je.length>0,Qt=await ot(w,Qe,Je,{includePowerup:G,onFilteredTags:pn=>{De+=pn}});Ke&&(Qt.hasMultilineChildren=!1);const un=[];if(!Ke){const{visibleIndices:pn,elided:Zt}=me(Je.length,j,Qe._id);if(pn){const{head:ut,tail:ct}=pn;for(let Bt=0;Bt<ut&&mt.remaining>0;Bt++)un.push(await $e(Je[Bt],qe+1,Re));if(Zt){const Bt=Re!==-1&&qe+1>=Re;un.push({type:"elided",count:Zt.count,isExact:Bt,parentId:Zt.parentId,rangeFrom:Zt.rangeFrom,rangeTo:Zt.rangeTo,totalSiblings:Zt.totalSiblings})}const Nt=Je.length-ct;for(let Bt=Nt;Bt<Je.length&&mt.remaining>0;Bt++)un.push(await $e(Je[Bt],qe+1,Re))}else for(let ut=0;ut<Je.length;ut++){if(mt.remaining<=0){const ct=Je.length-ut;un.push({type:"elided",count:ct,isExact:!1,parentId:Qe._id,rangeFrom:ut,rangeTo:Je.length-1,totalSiblings:Je.length});break}un.push(await $e(Je[ut],qe+1,Re))}}return{rem:Qt,children:un,folded:Ke}}const wt=await $e(ve,0,R);await ve.getParentRem()||(wt.rem.isTopLevel=!0);const Be=ie(wt),Ge={rootId:E,depth:R,nodeCount:Fe,outline:Be,nodeRemIds:Me},nt=Math.min(Math.max(Q,0),10);if(nt>0){const Qe=[];let qe=ve;for(let Re=0;Re<nt;Re++){const xt=await qe.getParentRem();if(!xt)break;const[Je,an,Ke]=await Promise.all([Pt(w,xt.text??[]),xt.getChildrenRem(),xt.isDocument()]);Qe.push({id:xt._id,name:te(Je),childrenCount:an.length,isDocument:Ke}),qe=xt}if(Qe.length>0){const Re=Qe[Qe.length-1];await qe.getParentRem()||(Re.isTopLevel=!0),Ge.ancestors=Qe}}return!G&&(De>0||he>0)&&(Ge.powerupFiltered={tags:De,children:he}),Ge}async function ge(w,y){const E=await q(w,y),R=y.includePowerup??!1,N={};return await Promise.all(E.nodeRemIds.map(async j=>{const Q=await w.rem.findOne(j);Q&&(N[j]=await Ne(w,Q,{includePowerup:R}))})),{...E,remObjects:N}}async function C(w,y){const{depth:E=-1,maxNodes:R=200,maxSiblings:N=20}=y,Q=(await w.rem.getAll()).filter(Pe=>Pe.parent===null),G=await Promise.all(Q.map(Pe=>Pe.isDocument())),ve=Q.filter((Pe,Be)=>G[Be]);let Fe=0;const De={remaining:R};async function he(Pe,Be,Ge){Fe++,De.remaining--;const nt=await Pe.getChildrenRem(),Qe=await Ve(nt),qe=await Promise.all(Qe.map(ut=>ut.isDocument())),Re=Qe.filter((ut,ct)=>qe[ct]),xt=Qe.length-Re.length,an=Ge!==-1&&Be>=Ge&&Re.length>0,Ke=Pe.type===6,[Qt,un]=await Promise.all([Pt(w,Pe.text??[]),Ke?Pe.getPortalDirectlyIncludedRem():Promise.resolve([])]),pn=ue({id:Pe._id,markdownText:Qt.replace(/\n/g," "),childrenCount:Qe.length,isDocument:!0,isTopLevel:Pe.parent===null,isPortal:Ke,...Ke?{type:"portal",portalRefs:un.map(ut=>ut._id)}:{}}),Zt=[];if(!an&&Re.length>0){const{visibleIndices:ut,elided:ct}=me(Re.length,N,Pe._id);if(ut){const{head:Nt,tail:Bt}=ut;for(let Dt=0;Dt<Nt&&De.remaining>0;Dt++)Zt.push(await he(Re[Dt],Be+1,Ge));if(ct){const Dt=Ge!==-1&&Be+1>=Ge;Zt.push({type:"elided",count:ct.count,isExact:Dt,parentId:ct.parentId,rangeFrom:ct.rangeFrom,rangeTo:ct.rangeTo,totalSiblings:ct.totalSiblings})}const hn=Re.length-Bt;for(let Dt=hn;Dt<Re.length&&De.remaining>0;Dt++)Zt.push(await he(Re[Dt],Be+1,Ge))}else for(let Nt=0;Nt<Re.length;Nt++){if(De.remaining<=0){const Bt=Re.length-Nt;Zt.push({type:"elided",count:Bt,isExact:!1,parentId:Pe._id,rangeFrom:Nt,rangeTo:Re.length-1,totalSiblings:Re.length});break}Zt.push(await he(Re[Nt],Be+1,Ge))}}return{rem:pn,children:Zt,folded:an}}const mt=[],{visibleIndices:Me,elided:$e}=me(ve.length,N,"root");if(Me){const{head:Pe,tail:Be}=Me;for(let nt=0;nt<Pe&&De.remaining>0;nt++)mt.push(await he(ve[nt],0,E));$e&&mt.push({type:"elided",count:$e.count,isExact:!1,parentId:"root",rangeFrom:$e.rangeFrom,rangeTo:$e.rangeTo,totalSiblings:$e.totalSiblings});const Ge=ve.length-Be;for(let nt=Ge;nt<ve.length&&De.remaining>0;nt++)mt.push(await he(ve[nt],0,E))}else for(let Pe=0;Pe<ve.length;Pe++){if(De.remaining<=0){const Be=ve.length-Pe;mt.push({type:"elided",count:Be,isExact:!1,parentId:"root",rangeFrom:Pe,rangeTo:ve.length-1,totalSiblings:ve.length});break}mt.push(await he(ve[Pe],0,E))}const wt=["<!-- globe: \u77E5\u8BC6\u5E93\u6982\u89C8 -->"];for(const Pe of mt)Z(Pe)?wt.push(ee(Pe)):wt.push(ie(Pe));return{nodeCount:Fe,outline:wt.join(`
60
60
  `)}}async function V(w,y){const E=[];let R=y;for(;R;){const N=await Pt(w,R.text??[]);E.unshift(N.replace(/\n/g," ").trim()||R._id),R=await R.getParentRem()}return E}async function F(w,y){const{mode:E="focus",ancestorLevels:R=2,maxNodes:N=200,maxSiblings:j=20,depth:Q=3,focusRemId:G}=y;if(E==="page"){if(G)throw new Error("focusRemId \u4EC5\u5728 focus \u6A21\u5F0F\u4E0B\u6709\u6548\uFF0Cpage \u6A21\u5F0F\u4E0B\u8BF7\u52FF\u6307\u5B9A");return A(w,{maxNodes:N,maxSiblings:j,depth:Q})}return $(w,{ancestorLevels:R,maxNodes:N,maxSiblings:j,focusRemId:G})}async function A(w,y){const E=await w.window.getFocusedPaneId();if(!E)throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u9762\u677F ID\uFF0C\u8BF7\u786E\u4FDD\u6709\u6253\u5F00\u7684\u9875\u9762");const R=await w.window.getOpenPaneRemId(E);if(!R)throw new Error("\u5F53\u524D\u9762\u677F\u6CA1\u6709\u6253\u5F00\u4EFB\u4F55 Rem");const N=await w.rem.findOne(R);if(!N)throw new Error(`Page Rem not found: ${R}`);const j=await V(w,N);let Q=0;const G={remaining:y.maxNodes},ve=await U(w,N,0,y.depth,y.maxSiblings,G);Q=y.maxNodes-G.remaining;const De=`<!-- page: ${j[j.length-1]||R} -->
61
61
  <!-- path: ${j.join(" > ")} -->`+`
@@ -7,7 +7,7 @@
7
7
  "version": {
8
8
  "major": 0,
9
9
  "minor": 2,
10
- "patch": 0
10
+ "patch": 1
11
11
  },
12
12
  "theme": [],
13
13
  "enableOnMobile": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": true,
3
3
  "name": "unofficial-remnote-bridge-plugin",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "license": "MIT",
6
6
  "description": "RemNote 桥接层:嵌入 RemNote 的 WebSocket 桥接插件",
7
7
  "scripts": {
@@ -7,7 +7,7 @@
7
7
  "version": {
8
8
  "major": 0,
9
9
  "minor": 2,
10
- "patch": 0
10
+ "patch": 1
11
11
  },
12
12
  "theme": [],
13
13
  "enableOnMobile": false,
@@ -211,7 +211,7 @@ export async function buildRemObject(
211
211
  portalType: remTypeToString(rem.type as number) === 'portal'
212
212
  ? portalTypeToString(portalType as number)
213
213
  : null,
214
- portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id),
214
+ portalDirectlyIncludedRem: portalDirectlyIncludedRems.map(r => r._id).sort(),
215
215
 
216
216
  // 属性类型
217
217
  propertyType: (propertyType as PropertyTypeValue | undefined) ?? null,
@@ -220,27 +220,27 @@ export async function buildRemObject(
220
220
  enablePractice,
221
221
  practiceDirection: practiceDirection as RemObject['practiceDirection'],
222
222
 
223
- // 关联 — 直接关系
224
- tags: filteredTagRems.map(r => r._id),
225
- sources: sourceRems.map(r => r._id),
226
- 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(),
227
227
 
228
228
  // 关联 — 引用关系
229
- remsBeingReferenced: refsBeingReferenced.map(r => r._id),
230
- deepRemsBeingReferenced: deepRefsBeingReferenced.map(r => r._id),
231
- 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(),
232
232
 
233
233
  // 关联 — 标签体系
234
- taggedRem: taggedRems.map(r => r._id),
235
- ancestorTagRem: ancestorTagRems.map(r => r._id),
236
- 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(),
237
237
 
238
238
  // 关联 — 层级遍历
239
- descendants: descendantRems.map(r => r._id),
240
- siblingRem: siblingRems.map(r => r._id),
241
- portalsAndDocumentsIn: portalsAndDocsIn.map(r => r._id),
242
- allRemInDocumentOrPortal: allRemInDocOrPortal.map(r => r._id),
243
- 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(),
244
244
 
245
245
  // 位置 / 统计
246
246
  positionAmongstSiblings: position ?? null,
@@ -5,7 +5,7 @@
5
5
  * 多 daemon 连接:Plugin 同时连接 ALL_WS_PORTS 对应的 4 个槽位。
6
6
  */
7
7
 
8
- export const DEFAULT_PLUGIN_VERSION = '0.2.0';
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;
@@ -283,7 +283,9 @@ edit-tree --json '{"remId":"kLr...","oldStr":"...","newStr":"..."}'
283
283
 
284
284
  ## 3. 标准工作流
285
285
 
286
- ### ⚠️ 标准模式:connect 后需要用户配合
286
+ ### ⚠️ 标准模式(推荐):connect 后需要用户配合
287
+
288
+ **标准模式是推荐的日常使用方式**。用户在自己的浏览器中操作 RemNote,Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
287
289
 
288
290
  `connect` 成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成操作,Plugin 才能连接到 daemon:
289
291
 
@@ -299,11 +301,18 @@ edit-tree --json '{"remId":"kLr...","oldStr":"...","newStr":"..."}'
299
301
 
300
302
  **你必须**:执行 `connect` 后,**立即告知用户需要完成上述操作**,不要直接调用业务命令。引导用户完成后,用 `health` 确认三层就绪再继续。
301
303
 
302
- ### Headless 模式:自动连接
304
+ ### Headless 模式(特殊场景,不推荐日常使用)
305
+
306
+ 通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
307
+
308
+ **⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。Agent 无法感知用户正在浏览和操作的页面,协作体验大打折扣。
303
309
 
304
- 标准模式每次 connect 后都需要用户手动操作 RemNote。Headless 模式通过 setup(一次性)+ headless Chrome 实现自动连接,后续 connect 无需用户介入。
310
+ **仅在以下场景使用 headless**:
311
+ - 用户明确要求在**服务器/无 GUI 环境**中运行
312
+ - 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
313
+ - 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
305
314
 
306
- **⚠️ 模式选择建议**:日常使用推荐**标准模式**。Headless 模式下 Chrome 在后台运行,**无法感知用户正在 RemNote 中浏览和操作的界面**(`read-context` 返回的是 headless Chrome 的上下文,而非用户的浏览器)。只有在全自动化场景(CI/CD、定时任务、批量操作等无需与用户界面交互的场景)才建议使用 Headless 模式。
315
+ **默认始终使用标准模式**,除非用户主动要求 headless
307
316
 
308
317
  #### 首次使用(setup)
309
318
 
@@ -53,13 +53,20 @@ remnote-bridge --json connect --instance work
53
53
 
54
54
  ## 两种模式
55
55
 
56
- ### 标准模式(默认)
56
+ ### 标准模式(默认,推荐)
57
57
 
58
- 启动 daemon 后需要用户手动在 RemNote 中加载 Plugin。适用于用户已打开 RemNote 的场景。
58
+ **标准模式是推荐的日常使用方式**。启动 daemon 后用户在自己的浏览器中加载 Plugin。优势:Agent 可以通过 `read-context` 感知用户正在浏览的页面和焦点位置,实现真正的协作。
59
59
 
60
- ### Headless 模式(`--headless`)
60
+ ### Headless 模式(`--headless`,特殊场景)
61
61
 
62
- 自动启动 headless Chrome 加载 Plugin,无需用户操作。适用于无 GUI 环境或全自动连接场景。
62
+ 自动启动 headless Chrome 加载 Plugin,无需用户操作。
63
+
64
+ **⚠️ 不推荐日常使用**。Headless Chrome 是后台独立实例,**会丢失用户上下文**——`read-context` 返回的是 headless Chrome 的上下文,不是用户浏览器的。
65
+
66
+ **仅在以下场景使用 headless**:
67
+ - 用户明确要求在**服务器/无 GUI 环境**中运行
68
+ - 用户明确表示**不想参与操作**,希望全自动化(CI/CD、定时任务、批量处理等)
69
+ - 用户自己不在 RemNote 前面,不需要与 Agent 协作浏览
63
70
 
64
71
  **前置条件**:必须先执行 `setup` 完成 RemNote 登录。
65
72
 
@@ -93,12 +100,17 @@ remnote-bridge --headless disconnect # 结束
93
100
 
94
101
  `connect`(不传 `--headless`)成功只意味着 daemon 和 Plugin 服务已启动,**Plugin 并未自动连接**。用户必须在 RemNote 中完成以下操作:
95
102
 
103
+ > **⚠️ 防幻觉红线**:本插件是**开发者插件**,通过「开发你的插件」功能加载本地 URL。
104
+ > - **禁止**告诉用户"去插件市场/商店搜索安装"——本插件**不在 RemNote 插件市场中**
105
+ > - **禁止**告诉用户"Settings → Plugins"——这个路径不存在
106
+ > - **禁止**编造不存在的安装流程——严格按照下方步骤引导用户
107
+
96
108
  ### 首次使用(RemNote 从未加载过此插件)
97
109
 
98
110
  1. 打开 RemNote 桌面端或网页端
99
- 2. 点击左侧边栏底部的插件图标(拼图形状)
100
- 3. 点击「开发你的插件」(Develop Your Plugin)
101
- 4. 在输入框中填入 connect 输出的 Plugin 服务地址(如 `http://localhost:29101`)
111
+ 2. 点击左侧边栏底部的**插件图标**(拼图形状)
112
+ 3. 点击「**开发你的插件**」(Develop Your Plugin)
113
+ 4. 在输入框中填入 connect 输出的 **Plugin 服务地址**(如 `http://localhost:29101`)
102
114
  5. 等待插件加载完成
103
115
 
104
116
  ### 非首次使用(之前已加载过此插件)
@@ -174,64 +174,56 @@ oldStr 必须在缓存大纲中恰好匹配 1 次
174
174
 
175
175
  ---
176
176
 
177
- ## 行引用模板 `{{remId}}`
177
+ ## 两种写法:模板模式与完整匹配模式
178
178
 
179
- oldStr/newStr 中使用 `{{remId}}` 引用缓存大纲中已有行的完整内容(不含缩进)。系统在 str_replace 前自动展开。不含 `{{}}` 的传统写法完全兼容。
179
+ 已有行(带 `<!--remId-->` 注释的行)在 oldStr/newStr 中支持两种写法:
180
180
 
181
- ### 动机
181
+ ### 模板模式(优先使用)
182
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
- **重排(对比传统写法)**
183
+ `{{remId}}` 引用已有行,系统在 str_replace 前自动展开为完整行内容(不含缩进)。节省 token、减少复制错误。
196
184
 
197
185
  ```
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)
186
+ # 重排
203
187
  oldStr: " {{id1_1}}\n {{id1_2}}"
204
188
  newStr: " {{id1_2}}\n {{id1_1}}"
205
- ```
206
-
207
- **移动(改变缩进)**
208
189
 
209
- ```
190
+ # 移动(改变缩进 = 改变父节点)
210
191
  oldStr: " {{idA}}\n {{idT}}\n {{idB}}"
211
192
  newStr: " {{idA}}\n {{idB}}\n {{idT}}"
212
- ```
213
-
214
- **删除(模板用于上下文定位)**
215
193
 
216
- ```
194
+ # 删除(必须同时删子行)
217
195
  oldStr: " {{idA}}\n {{idA1}}\n {{idB}}"
218
196
  newStr: " {{idB}}"
219
- ```
220
197
 
221
- **新增 + 模板混用**
222
-
223
- ```
198
+ # 新增(新增行手动写,已有行用模板)
224
199
  oldStr: " {{idZ}}"
225
200
  newStr: " 新增行\n {{idZ}}"
226
201
  ```
227
202
 
228
- ### 限制
229
-
230
- - 只匹配纯字母数字(`[a-zA-Z0-9]+`),与 RemNote cloze 语法 `{{text}}` 不冲突(cloze 含中文/空格/标点不会被匹配)
203
+ **模板规则**:
204
+ - `{{remId}}` 展开为**不含缩进**的完整行内容,缩进由你控制
205
+ - 只匹配纯字母数字(`[a-zA-Z0-9]+`),与 RemNote cloze 语法 `{{text}}` 不冲突
231
206
  - 匹配到但不在缓存大纲中的 `{{xxx}}` 原样保留(可能是 cloze),并输出 templateWarnings
232
- - `{{remId}}` 不含缩进,缩进由 AI 控制(move 操作会改变缩进)
233
207
  - 新增行没有 remId,不能用模板表示
234
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
+
235
227
  ---
236
228
 
237
229
  ## 支持的操作
@@ -241,12 +233,13 @@ newStr: " 新增行\n {{idZ}}"
241
233
  在 newStr 中添加**无 remId 注释**的新行。新行可以使用 Markdown 前缀和箭头分隔符来设置属性。
242
234
 
243
235
  ```
244
- oldStr:
245
- 子节点 A <!--idA-->
236
+ # 模板模式
237
+ oldStr: " {{idA}}"
238
+ newStr: " 新增节点\n {{idA}}"
246
239
 
247
- newStr:
248
- 新增节点
249
- 子节点 A <!--idA-->
240
+ # 完整匹配模式
241
+ oldStr: " 子节点 A <!--idA-->"
242
+ newStr: " 新增节点\n 子节点 A <!--idA-->"
250
243
  ```
251
244
 
252
245
  #### 新增行的 Markdown 前缀
@@ -326,12 +319,13 @@ newStr:
326
319
  示例:
327
320
 
328
321
  ```
329
- oldStr:
330
- 子节点 A <!--idA-->
322
+ # 模板模式
323
+ oldStr: " {{idA}}"
324
+ newStr: " <!--portal refs:refId1,refId2-->\n {{idA}}"
331
325
 
332
- newStr:
333
- <!--portal refs:refId1,refId2-->
334
- 子节点 A <!--idA-->
326
+ # 完整匹配模式
327
+ oldStr: " 子节点 A <!--idA-->"
328
+ newStr: " <!--portal refs:refId1,refId2-->\n 子节点 A <!--idA-->"
335
329
  ```
336
330
 
337
331
  #### 嵌套新增
@@ -339,11 +333,13 @@ newStr:
339
333
  新增行下面可以再嵌套新增行,通过缩进表示父子关系:
340
334
 
341
335
  ```
342
- newStr:
343
- 父节点 ↓
344
- 答案行 1
345
- 答案行 2
346
- 子节点 A <!--idA-->
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-->"
347
343
  ```
348
344
 
349
345
  嵌套新增行的父 ID 通过内部占位标记 `__new_N__` 管理,创建顺序保证从浅到深。
@@ -353,14 +349,13 @@ newStr:
353
349
  新行**不能**插在一个有子节点的 Rem 和它的 children 之间,否则 children 会被新行"劫持",触发 `children_captured` 错误。
354
350
 
355
351
  ```
356
- 错误:插在父 Rem 和 children 之间
357
- 水分子 <!--idA-->
358
- 新行 ← children 被解析为新行的子节点!
359
- 化学式 H₂O <!--idB role:card-item-->
352
+ 错误(模板):
353
+ oldStr: " {{idA}}" newStr: " {{idA}}\n 新行" ← idA 有子节点,新行劫持 children!
354
+ 错误(完整匹配):
355
+ oldStr: " 水分子 <!--idA-->" newStr: " 水分子 ↓ <!--idA-->\n 新行" ← 同理
360
356
 
361
- 正确:插在所有兄弟末尾
362
- 极性 ... <!--idZ role:card-item-->
363
- 新行 ← 不影响任何已有节点
357
+ 正确:插在末尾
358
+ oldStr: " {{idZ}}" newStr: " {{idZ}}\n 新行"
364
359
  ```
365
360
 
366
361
  #### 两步操作:创建新节点并移入已有 children
@@ -377,13 +372,13 @@ newStr:
377
372
  从 newStr 中移除带 remId 的行。**必须同时删除该行的所有可见子行**,否则报 orphan_detected 错误。
378
373
 
379
374
  ```
380
- oldStr:
381
- 子节点 A <!--idA-->
382
- 孙节点 A1 <!--idA1-->
383
- 子节点 B <!--idB-->
375
+ # 模板模式
376
+ oldStr: " {{idA}}\n {{idA1}}\n {{idB}}"
377
+ newStr: " {{idB}}"
384
378
 
385
- newStr:
386
- 子节点 B <!--idB-->
379
+ # 完整匹配模式
380
+ oldStr: " 子节点 A <!--idA-->\n 孙节点 A1 <!--idA1-->\n 子节点 B <!--idB-->"
381
+ newStr: " 子节点 B <!--idB-->"
387
382
  ```
388
383
 
389
384
  删除操作按深度**从深到浅**执行(先删子后删父),确保 RemNote SDK 不会拒绝操作。
@@ -393,15 +388,13 @@ newStr:
393
388
  改变行的缩进级别或位置,使其移动到新的父节点下:
394
389
 
395
390
  ```
396
- oldStr:
397
- 子节点 A <!--idA-->
398
- 目标行 <!--idT-->
399
- 子节点 B <!--idB-->
391
+ # 模板模式
392
+ oldStr: " {{idA}}\n {{idT}}\n {{idB}}"
393
+ newStr: " {{idA}}\n {{idB}}\n {{idT}}"
400
394
 
401
- newStr:
402
- 子节点 A <!--idA-->
403
- 子节点 B <!--idB-->
404
- 目标行 <!--idT-->
395
+ # 完整匹配模式
396
+ oldStr: " 子节点 A <!--idA-->\n 目标行 <!--idT-->\n 子节点 B <!--idB-->"
397
+ newStr: " 子节点 A <!--idA-->\n 子节点 B <!--idB-->\n 目标行 <!--idT-->"
405
398
  ```
406
399
 
407
400
  ### 重排行
@@ -409,15 +402,13 @@ newStr:
409
402
  调换同级行的顺序:
410
403
 
411
404
  ```
412
- oldStr:
413
- 子节点 A <!--idA-->
414
- 子节点 B <!--idB-->
415
- 子节点 C <!--idC-->
405
+ # 模板模式
406
+ oldStr: " {{idA}}\n {{idB}}\n {{idC}}"
407
+ newStr: " {{idC}}\n {{idA}}\n {{idB}}"
416
408
 
417
- newStr:
418
- 子节点 C <!--idC-->
419
- 子节点 A <!--idA-->
420
- 子节点 B <!--idB-->
409
+ # 完整匹配模式
410
+ oldStr: " 子节点 A <!--idA-->\n 子节点 B <!--idB-->\n 子节点 C <!--idC-->"
411
+ newStr: " 子节点 C <!--idC-->\n 子节点 A <!--idA-->\n 子节点 B <!--idB-->"
421
412
  ```
422
413
 
423
414
  ---
@@ -543,36 +534,42 @@ RemNote SDK 存在已知 bug:
543
534
 
544
535
  ---
545
536
 
546
- ## 常见使用模式
537
+ ## 常见使用模式(JSON 模式)
538
+
539
+ > 优先使用模板模式;连续 2+ 次 `old_str not found` 则回退到完整匹配模式。
547
540
 
548
541
  ### 在指定位置插入新行
549
542
 
550
543
  ```bash
551
- remnote-bridge edit-tree kLr --old-str ' 子节点 A <!--idA-->' --new-str ' 新增行\n 子节点 A <!--idA-->'
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-->"}'
552
549
  ```
553
550
 
554
551
  ### 删除一个叶子节点
555
552
 
556
553
  ```bash
557
- remnote-bridge edit-tree kLr --old-str ' 叶子节点 <!--leaf-->\n' --new-str ''
554
+ remnote-bridge edit-tree --json '{"remId":"kLr","oldStr":" {{leaf}}\n","newStr":""}'
558
555
  ```
559
556
 
560
557
  ### 调换两个兄弟的顺序
561
558
 
562
559
  ```bash
563
- # 传统写法
564
- remnote-bridge edit-tree kLr --old-str ' 节点 A <!--idA-->\n 节点 B <!--idB-->' --new-str ' 节点 B <!--idB-->\n 节点 A <!--idA-->'
565
-
566
- # 模板写法(JSON 模式)
560
+ # 模板模式
567
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-->"}'
568
565
  ```
569
566
 
570
567
  ### 将节点移到另一个父节点下
571
568
 
572
569
  ```bash
573
- # 传统写法
574
- remnote-bridge edit-tree kLr --old-str ' 旧父 <!--oldP-->\n 目标 <!--target-->\n 新父 <!--newP-->' --new-str ' 旧父 <!--oldP-->\n 新父 <!--newP-->\n 目标 <!--target-->'
575
-
576
- # 模板写法(JSON 模式)
570
+ # 模板模式
577
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-->"}'
578
575
  ```
@@ -190,6 +190,8 @@ RemNote SDK → 知识库
190
190
 
191
191
  一次**会话(Session)= 守护进程的生命周期**。
192
192
 
193
+ **标准模式(推荐)**——用户在自己的浏览器中操作 RemNote,Agent 可感知用户上下文:
194
+
193
195
  ```
194
196
  connect → daemon 启动
195
197
 
@@ -201,6 +203,10 @@ disconnect → daemon 关闭 → 会话结束,缓存清空
201
203
  ```
202
204
 
203
205
  > **重要**:`connect` 成功只意味着 daemon 已启动,Plugin 并未自动连接。首次使用需用户在 RemNote「开发你的插件」中填入对应的 Plugin 服务地址;非首次只需刷新 RemNote 页面。必须引导用户完成此步后再用 `health` 确认就绪。
206
+ >
207
+ > **⚠️ 防幻觉红线**:本插件是**开发者插件**,通过「开发你的插件」加载本地 URL。**禁止**告诉用户去插件市场/商店搜索安装(插件不在市场中);**禁止**编造"Settings → Plugins"等不存在的路径。
208
+
209
+ **Headless 模式(不推荐日常使用)**——通过后台 Chrome 自动连接,但**会丢失用户上下文**(`read-context` 返回 headless 实例的上下文,不是用户浏览器的)。仅在以下场景使用:用户明确要求在服务器/无 GUI 环境运行、用户明确不想参与操作(全自动化)、用户不在 RemNote 前面。详见 `connect.md`。
204
210
 
205
211
  `connect` 启动三个服务,端口由槽位自动分配:
206
212