remnote-bridge 0.1.16 → 0.1.17
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 +13 -2
- package/README.zh-CN.md +13 -2
- package/dist/cli/commands/read-context.js +6 -4
- package/dist/cli/config.js +1 -1
- package/dist/cli/handlers/edit-handler.js +63 -16
- package/dist/cli/main.js +1 -1
- package/dist/cli/server/config-server.js +1 -1
- package/dist/mcp/instructions.js +7 -5
- package/dist/mcp/tools/edit-tools.js +7 -3
- package/dist/mcp/tools/read-tools.js +8 -4
- package/package.json +1 -1
- package/remnote-plugin/dist/index-sandbox.js +1 -1
- package/remnote-plugin/dist/index.js +1 -1
- package/remnote-plugin/src/services/read-context.ts +1 -1
- package/remnote-plugin/src/types.ts +59 -51
- package/skills/remnote-bridge/SKILL.md +15 -8
- package/skills/remnote-bridge/instructions/edit-rem.md +44 -15
- package/skills/remnote-bridge/instructions/overall.md +33 -19
- package/skills/remnote-bridge/instructions/read-context.md +10 -5
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ remnote-bridge health
|
|
|
60
60
|
|
|
61
61
|
# 4. Explore your knowledge base
|
|
62
62
|
remnote-bridge read-globe # Global document overview
|
|
63
|
-
remnote-bridge read-context # Current
|
|
63
|
+
remnote-bridge read-context # Current page context in RemNote
|
|
64
64
|
remnote-bridge search "machine learning" # Full-text search
|
|
65
65
|
remnote-bridge read-tree <remId> # Expand a subtree
|
|
66
66
|
remnote-bridge read-rem <remId> # Read Rem properties
|
|
@@ -108,7 +108,7 @@ remnote-bridge search "machine learning"
|
|
|
108
108
|
| Command | Description | Caches |
|
|
109
109
|
|:--------|:------------|:-------|
|
|
110
110
|
| `read-globe` | Global document-level overview | No |
|
|
111
|
-
| `read-context` | Current focus
|
|
111
|
+
| `read-context` | Current page/focus context view | No |
|
|
112
112
|
| `read-tree <remId>` | Subtree as Markdown outline | Yes |
|
|
113
113
|
| `read-rem <remId>` | Single Rem's full JSON properties | Yes |
|
|
114
114
|
| `read-rem-in-tree <remId>` | Subtree outline + all Rem objects in one call | Yes |
|
|
@@ -292,6 +292,17 @@ remnote-bridge addon uninstall remnote-rag --purge
|
|
|
292
292
|
|
|
293
293
|
## Changelog
|
|
294
294
|
|
|
295
|
+
### 0.1.17 (2026-03-19)
|
|
296
|
+
|
|
297
|
+
- **Defense-2 three-layer field classification** — `edit_rem` concurrency detection now classifies field differences into three tiers: semantic fields (text/type/tags etc.) → hard reject; parent → pass with `⚠️ parent has changed` warning; metadata (position/timestamps etc.) → pass with `ℹ️ Metadata fields changed` warning. This eliminates false positives after `edit_tree` move/reorder operations
|
|
298
|
+
- **Documentation sync** — Updated defense-2 descriptions across all 5 doc surfaces (SKILL.md, edit-rem.md, overall.md, MCP tool description, SERVER_INSTRUCTIONS) with three-layer classification, warning text examples, and revised judgment tree
|
|
299
|
+
|
|
300
|
+
### 0.1.16 (2026-03-19)
|
|
301
|
+
|
|
302
|
+
- **Ordered list prefix tolerance** — `edit-tree` now accepts `2.`~`9.` prefixes for ordered lists, auto-normalizes to `isListItem=true`, and returns `templateWarnings` to remind agents to use `1.` (Lazy Numbering). `10.` and above are not matched (kept as plain text)
|
|
303
|
+
- **templateWarnings passthrough fix** — CLI `edit-tree` command now correctly includes `templateWarnings` in JSON output (was silently dropped)
|
|
304
|
+
- **Lazy Numbering documentation** — Added prominent warnings in MCP tool description, SERVER_INSTRUCTIONS, and Skill docs explaining RemNote's auto-numbering convention
|
|
305
|
+
|
|
295
306
|
### 0.1.15 (2026-03-18)
|
|
296
307
|
|
|
297
308
|
- **Defense-2 false positive fix** — Removed `.sort()` from ID arrays in RemObject serialization that caused spurious concurrency conflicts in edit-rem
|
package/README.zh-CN.md
CHANGED
|
@@ -60,7 +60,7 @@ remnote-bridge health
|
|
|
60
60
|
|
|
61
61
|
# 4. 浏览知识库
|
|
62
62
|
remnote-bridge read-globe # 全局文档概览
|
|
63
|
-
remnote-bridge read-context #
|
|
63
|
+
remnote-bridge read-context # 当前页面/焦点上下文
|
|
64
64
|
remnote-bridge search "machine learning" # 全文搜索
|
|
65
65
|
remnote-bridge read-tree <remId> # 展开子树
|
|
66
66
|
remnote-bridge read-rem <remId> # 读取 Rem 属性
|
|
@@ -108,7 +108,7 @@ remnote-bridge search "machine learning"
|
|
|
108
108
|
| 命令 | 说明 | 缓存 |
|
|
109
109
|
|:-----|:-----|:-----|
|
|
110
110
|
| `read-globe` | 全局文档级概览 | 否 |
|
|
111
|
-
| `read-context` |
|
|
111
|
+
| `read-context` | 当前页面/焦点上下文视图 | 否 |
|
|
112
112
|
| `read-tree <remId>` | 子树序列化为 Markdown 大纲 | 是 |
|
|
113
113
|
| `read-rem <remId>` | 单个 Rem 的完整 JSON 属性 | 是 |
|
|
114
114
|
| `read-rem-in-tree <remId>` | 子树大纲 + 所有 Rem 对象,一次调用 | 是 |
|
|
@@ -292,6 +292,17 @@ remnote-bridge addon uninstall remnote-rag --purge
|
|
|
292
292
|
|
|
293
293
|
## Changelog
|
|
294
294
|
|
|
295
|
+
### 0.1.17 (2026-03-19)
|
|
296
|
+
|
|
297
|
+
- **防线 2 三层字段分类** — `edit_rem` 并发检测现在将字段差异分为三层:语义字段(text/type/tags 等)→ 硬拒绝;parent → 放行并返回 `⚠️ parent has changed` 警告;元数据(位置/时间戳等)→ 放行并返回 `ℹ️ Metadata fields changed` 警告。消除 `edit_tree` 移动/重排后的误报
|
|
298
|
+
- **文档同步** — 在全部 5 个文档面(SKILL.md、edit-rem.md、overall.md、MCP 工具描述、SERVER_INSTRUCTIONS)更新防线 2 描述:三层分类机制、警告文本示例、修订判断树
|
|
299
|
+
|
|
300
|
+
### 0.1.16 (2026-03-19)
|
|
301
|
+
|
|
302
|
+
- **有序列表前缀容错** — `edit-tree` 新增行现在接受 `2.`~`9.` 前缀,自动归一化为 `isListItem=true`,并返回 `templateWarnings` 提醒使用 `1.`(Lazy Numbering 风格)。`10.` 及以上不匹配(保留为纯文本)
|
|
303
|
+
- **templateWarnings 透传修复** — CLI `edit-tree` 命令现在正确包含 `templateWarnings` 到 JSON 输出中(之前被静默丢弃)
|
|
304
|
+
- **Lazy Numbering 文档** — 在 MCP 工具描述、SERVER_INSTRUCTIONS 和 Skill 文档中添加醒目的有序列表使用规范说明
|
|
305
|
+
|
|
295
306
|
### 0.1.15 (2026-03-18)
|
|
296
307
|
|
|
297
308
|
- **防线 2 误判修复** — 移除 RemObject 序列化中 ID 数组的 `.sort()`,消除 edit-rem 并发检测的假阳性
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* read-context 命令
|
|
3
3
|
*
|
|
4
4
|
* 读取当前上下文视图。
|
|
5
|
-
* - --mode focus|page(默认
|
|
5
|
+
* - --mode focus|page(默认 page)
|
|
6
6
|
* - --ancestor-levels N 向上追溯几层祖先(默认 2,仅 focus 模式)
|
|
7
7
|
* - --depth N 展开深度(默认 3,仅 page 模式)
|
|
8
8
|
* - --max-nodes N 全局节点上限(默认 200)
|
|
@@ -14,8 +14,8 @@ import { sendDaemonRequest } from '../daemon/send-request.js';
|
|
|
14
14
|
import { jsonOutput, handleCommandError } from '../utils/output.js';
|
|
15
15
|
export async function readContextCommand(options = {}) {
|
|
16
16
|
const { json } = options;
|
|
17
|
-
const mode = options.mode
|
|
18
|
-
if (mode !== 'focus' && mode !== 'page') {
|
|
17
|
+
const mode = options.mode;
|
|
18
|
+
if (mode !== undefined && mode !== 'focus' && mode !== 'page') {
|
|
19
19
|
const errMsg = '--mode must be "focus" or "page"';
|
|
20
20
|
if (json) {
|
|
21
21
|
jsonOutput({ ok: false, command: 'read-context', error: errMsg });
|
|
@@ -43,7 +43,9 @@ export async function readContextCommand(options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
let result;
|
|
45
45
|
try {
|
|
46
|
-
const reqPayload = {
|
|
46
|
+
const reqPayload = { ancestorLevels, depth, maxNodes, maxSiblings };
|
|
47
|
+
if (mode)
|
|
48
|
+
reqPayload.mode = mode;
|
|
47
49
|
if (options.focusRemId)
|
|
48
50
|
reqPayload.focusRemId = options.focusRemId;
|
|
49
51
|
result = await sendDaemonRequest('read_context', reqPayload);
|
package/dist/cli/config.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Plugin 只负责原子写入(write_rem_fields)。
|
|
6
6
|
*
|
|
7
7
|
* 防线 1:缓存存在性检查(必须先 read 再 edit)
|
|
8
|
-
* 防线 2
|
|
8
|
+
* 防线 2:语义并发检测(三层字段比较:语义字段硬拒绝,元数据放行+警告)
|
|
9
9
|
*/
|
|
10
10
|
/** 只读字段集合 — 变更这些字段只产生警告,不执行写入 */
|
|
11
11
|
const READ_ONLY_FIELDS = new Set([
|
|
@@ -25,6 +25,28 @@ const READ_ONLY_FIELDS = new Set([
|
|
|
25
25
|
'isPowerup', 'isPowerupEnum', 'isPowerupProperty',
|
|
26
26
|
'isPowerupPropertyListItem', 'isPowerupSlot',
|
|
27
27
|
]);
|
|
28
|
+
// ── 防线 2 三层字段分类 ──────────────────────────────────────
|
|
29
|
+
// 不在以下两个集合中的字段 = 第1层语义字段(硬拒绝)
|
|
30
|
+
/** 第2层:敏感元数据字段——放行但输出专门警告。扩展时须同步更新 handleEditRem 中的 warned 警告生成逻辑 */
|
|
31
|
+
const DEFENSE2_WARN_FIELDS = new Set([
|
|
32
|
+
'parent',
|
|
33
|
+
]);
|
|
34
|
+
/** 第3层:普通元数据字段——放行,有变化时输出统一警告 */
|
|
35
|
+
const DEFENSE2_IGNORE_FIELDS = new Set([
|
|
36
|
+
'id',
|
|
37
|
+
// 位置/时间
|
|
38
|
+
'positionAmongstSiblings', 'updatedAt', 'localUpdatedAt', 'createdAt',
|
|
39
|
+
'lastPracticed', 'lastTimeMovedTo', 'timesSelectedInSearch',
|
|
40
|
+
// 级联关联
|
|
41
|
+
'children', 'siblingRem', 'descendants',
|
|
42
|
+
'deepRemsBeingReferenced', 'remsReferencingThis',
|
|
43
|
+
'taggedRem', 'ancestorTagRem', 'descendantTagRem',
|
|
44
|
+
'portalsAndDocumentsIn', 'allRemInDocumentOrPortal', 'allRemInFolderQueue',
|
|
45
|
+
// 系统
|
|
46
|
+
'schemaVersion', 'embeddedQueueViewMode',
|
|
47
|
+
'isPowerup', 'isPowerupEnum', 'isPowerupProperty',
|
|
48
|
+
'isPowerupPropertyListItem', 'isPowerupSlot',
|
|
49
|
+
]);
|
|
28
50
|
/** 可写字段白名单 — 21 个可写字段 */
|
|
29
51
|
const WRITABLE_FIELDS = new Set([
|
|
30
52
|
'text', 'backText', 'type', 'isDocument', 'parent',
|
|
@@ -58,19 +80,34 @@ export class EditHandler {
|
|
|
58
80
|
if (!cachedObj) {
|
|
59
81
|
throw new Error(`Rem ${remId} has not been read yet. Read it first before editing.`);
|
|
60
82
|
}
|
|
61
|
-
// ── 防线 2:
|
|
83
|
+
// ── 防线 2: 乐观并发检测(三层字段比较) ──
|
|
62
84
|
const currentRemObject = await this.forwardToPlugin('read_rem', { remId });
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (
|
|
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)'}`);
|
|
69
|
-
// 不更新缓存 — 迫使 AI re-read
|
|
85
|
+
const { semantic, warned, ignored } = classifyDiff(cachedObj, currentRemObject);
|
|
86
|
+
// 第1层:语义字段冲突 → 硬拒绝
|
|
87
|
+
if (semantic.length > 0) {
|
|
88
|
+
console.error(`[defense-2] Rem ${remId} conflict. Changed semantic fields: ${semantic.join(', ')}`);
|
|
70
89
|
throw new Error(`Rem ${remId} has been modified since last read. Please read it again before editing.`);
|
|
71
90
|
}
|
|
91
|
+
// 第2层+第3层:元数据变化 → 放行,收集警告
|
|
92
|
+
const defense2Warnings = [];
|
|
93
|
+
if (warned.length > 0) {
|
|
94
|
+
const cachedParent = cachedObj.parent;
|
|
95
|
+
const currentParent = currentRemObject.parent;
|
|
96
|
+
defense2Warnings.push(`⚠️ parent has changed (was: ${cachedParent}, now: ${currentParent}). ` +
|
|
97
|
+
`The Rem has been moved to a different parent since last read. Proceeding with edit.`);
|
|
98
|
+
console.error(`[defense-2] Rem ${remId} parent changed: ${cachedParent} → ${currentParent}`);
|
|
99
|
+
}
|
|
100
|
+
if (ignored.length > 0) {
|
|
101
|
+
defense2Warnings.push(`ℹ️ Metadata fields changed since last read: ${ignored.join(', ')}. ` +
|
|
102
|
+
`This is expected after structural operations. Proceeding with edit.`);
|
|
103
|
+
console.error(`[defense-2] Rem ${remId} metadata drift: ${ignored.join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
// 静默刷新缓存(保持新鲜度)
|
|
106
|
+
if (warned.length > 0 || ignored.length > 0) {
|
|
107
|
+
this.cache.set('rem:' + remId, currentRemObject);
|
|
108
|
+
}
|
|
72
109
|
// ── 遍历 changes keys:分类过滤 ──
|
|
73
|
-
const warnings = [];
|
|
110
|
+
const warnings = [...defense2Warnings];
|
|
74
111
|
const writableChanges = {};
|
|
75
112
|
for (const key of Object.keys(changes)) {
|
|
76
113
|
if (READ_ONLY_FIELDS.has(key)) {
|
|
@@ -129,14 +166,24 @@ export class EditHandler {
|
|
|
129
166
|
};
|
|
130
167
|
}
|
|
131
168
|
}
|
|
132
|
-
/**
|
|
133
|
-
function
|
|
169
|
+
/** 三层字段分类比较 — 防线2核心逻辑 */
|
|
170
|
+
function classifyDiff(cached, current) {
|
|
134
171
|
const allKeys = new Set([...Object.keys(cached), ...Object.keys(current)]);
|
|
135
|
-
const
|
|
172
|
+
const semantic = [];
|
|
173
|
+
const warned = [];
|
|
174
|
+
const ignored = [];
|
|
136
175
|
for (const key of allKeys) {
|
|
137
|
-
if (JSON.stringify(cached[key])
|
|
138
|
-
|
|
176
|
+
if (JSON.stringify(cached[key]) === JSON.stringify(current[key]))
|
|
177
|
+
continue;
|
|
178
|
+
if (DEFENSE2_WARN_FIELDS.has(key)) {
|
|
179
|
+
warned.push(key);
|
|
180
|
+
}
|
|
181
|
+
else if (DEFENSE2_IGNORE_FIELDS.has(key)) {
|
|
182
|
+
ignored.push(key);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
semantic.push(key);
|
|
139
186
|
}
|
|
140
187
|
}
|
|
141
|
-
return
|
|
188
|
+
return { semantic, warned, ignored };
|
|
142
189
|
}
|
package/dist/cli/main.js
CHANGED
|
@@ -249,7 +249,7 @@ program
|
|
|
249
249
|
program
|
|
250
250
|
.command('read-context [jsonStr]')
|
|
251
251
|
.description('读取当前上下文视图(focus 鱼眼 / page 页面)')
|
|
252
|
-
.option('--mode <mode>', '模式:
|
|
252
|
+
.option('--mode <mode>', '模式:page(默认)或 focus')
|
|
253
253
|
.option('--ancestor-levels <levels>', '向上追溯几层祖先(默认 2,仅 focus 模式)')
|
|
254
254
|
.option('--depth <depth>', '展开深度(默认 3,仅 page 模式)')
|
|
255
255
|
.option('--max-nodes <maxNodes>', '全局节点上限(默认 200)')
|
|
@@ -281,8 +281,8 @@ export class ConfigServer {
|
|
|
281
281
|
<div class="field">
|
|
282
282
|
<label>模式 (readContextMode)</label>
|
|
283
283
|
<select id="readContextMode">
|
|
284
|
-
<option value="focus">focus</option>
|
|
285
284
|
<option value="page">page</option>
|
|
285
|
+
<option value="focus">focus</option>
|
|
286
286
|
</select>
|
|
287
287
|
</div>
|
|
288
288
|
<div class="field">
|
package/dist/mcp/instructions.js
CHANGED
|
@@ -238,7 +238,7 @@ disconnect → 关闭 daemon + headless Chrome,清空所有缓存,清除 hea
|
|
|
238
238
|
- 用户的描述与你已知信息对不上
|
|
239
239
|
- 搜索不到用户提到的内容
|
|
240
240
|
|
|
241
|
-
\`read_context
|
|
241
|
+
\`read_context\`:默认使用 **page 模式**——只需有打开的页面即可,几乎总能成功。仅当需要知道用户光标具体在哪个 Rem 上时,才显式传 \`mode="focus"\`(focus 模式要求用户光标停在某个 Rem 上,否则报错"当前没有聚焦的 Rem")。两者都返回面包屑路径。
|
|
242
242
|
|
|
243
243
|
### 场景 D:修改文本或属性
|
|
244
244
|
|
|
@@ -448,7 +448,7 @@ oldStr: " {{idZ}}" newStr: " {{idZ}}\\n 新行"
|
|
|
448
448
|
### 两道防线
|
|
449
449
|
|
|
450
450
|
1. **缓存存在**:必须有对应的 read 缓存
|
|
451
|
-
2.
|
|
451
|
+
2. **语义并发检测**(三层字段分类):edit 时重新读取最新数据并逐字段比较——语义字段(text/type/tags 等)变化 → 硬拒绝;parent 变化 → 放行 + warnings 返回 \`"⚠️ parent has changed (was: X, now: Y)..."\`;普通元数据(positionAmongstSiblings/updatedAt 等)变化 → 放行 + warnings 返回 \`"ℹ️ Metadata fields changed since last read: ..."\`。这意味着 \`edit_tree\` 移动/重排 Rem 后,可以直接 \`edit_rem\` 修改受影响节点,无需重新 read
|
|
452
452
|
|
|
453
453
|
### edit_tree 禁止事项
|
|
454
454
|
|
|
@@ -465,7 +465,9 @@ oldStr: " {{idZ}}" newStr: " {{idZ}}\\n 新行"
|
|
|
465
465
|
| 场景 | 缓存行为 | 重试策略 |
|
|
466
466
|
|:-----|:---------|:---------|
|
|
467
467
|
| edit_rem 写入成功 | 从 Plugin 重新读取 → 更新缓存 | 可继续编辑 |
|
|
468
|
-
| edit_rem
|
|
468
|
+
| edit_rem 仅元数据变化 | 静默刷新缓存并放行 | 可继续编辑(返回警告) |
|
|
469
|
+
| edit_rem 语义字段冲突 | 不更新缓存 | 必须重新 read_rem |
|
|
470
|
+
| edit_rem 部分写入失败 | 不更新缓存 | 必须重新 read_rem |
|
|
469
471
|
| edit_tree 成功 | 自动 re-read → 更新缓存 | 可连续 edit |
|
|
470
472
|
| edit_tree 防线 3 拒绝(str_replace 不匹配等) | 缓存保持不变 | 调整 oldStr/newStr 后直接重试 |
|
|
471
473
|
| edit_tree 执行中异常 | 已执行操作保留(**无回滚**),不更新缓存 | 必须重新 read_tree |
|
|
@@ -630,7 +632,7 @@ tags, sources, positionAmongstSiblings, portalDirectlyIncludedRem
|
|
|
630
632
|
- 超链接必须用 \`iUrl\`,\`url\` 字段已废弃无效
|
|
631
633
|
- RichText 对象内部按 **key 字母序排列**(\`_id\` < \`b\` < \`cId\` < \`h\` < \`i\` < \`iUrl\` < \`text\`),确保序列化一致性
|
|
632
634
|
- \`highlightColor\`(RemObject 顶层,字符串 \`"Red"\`)与 \`h\`(RichText 内部,数字 \`1\`)完全独立——前者是整行背景色,后者是文字片段荧光底色
|
|
633
|
-
- 防线 2
|
|
635
|
+
- 防线 2(语义并发检测)依赖 key 字母序的确定性序列化来比较语义字段
|
|
634
636
|
|
|
635
637
|
---
|
|
636
638
|
|
|
@@ -645,7 +647,7 @@ tags, sources, positionAmongstSiblings, portalDirectlyIncludedRem
|
|
|
645
647
|
├─ "Plugin 未连接" → RemNote 未打开或插件未加载 → 引导用户操作 RemNote
|
|
646
648
|
├─ "SDK 未就绪" → 知识库尚未加载 → 等待并重试 health
|
|
647
649
|
├─ "has not been read yet" → 未先 read → 执行对应 read 后重试
|
|
648
|
-
├─ "has been modified since last read" →
|
|
650
|
+
├─ "has been modified since last read" → 语义字段被外部修改 → 必须重新 read(不可直接重试)
|
|
649
651
|
├─ "Invalid value" → 枚举字段值不合法 → 检查允许的值范围
|
|
650
652
|
├─ "old_str not found" → oldStr 不精确 → 检查缩进、空格、换行
|
|
651
653
|
├─ "old_str matches N locations" → oldStr 不够具体 → 扩大范围包含更多上下文
|
|
@@ -28,13 +28,17 @@ export function registerEditTools(server) {
|
|
|
28
28
|
'\\n- fontSize: H1 / H2 / H3 / null(恢复普通)' +
|
|
29
29
|
'\\n- todoStatus: Finished / Unfinished / null(需先 isTodo=true)' +
|
|
30
30
|
'\\n\\nhighlightColor vs RichText h 字段:两者完全独立。highlightColor 是整行背景色(字符串如 "Red");h 是 RichText 元素内部的行内荧光底色(数字:1=Red, 2=Orange, 3=Yellow, 4=Green, 5=Purple, 6=Blue, 7=Gray, 8=Brown, 9=Pink)。' +
|
|
31
|
-
'\\n\\n输出格式:JSON 对象,包含 changes(已写入的字段名数组)和 warnings
|
|
31
|
+
'\\n\\n输出格式:JSON 对象,包含 changes(已写入的字段名数组)和 warnings(警告数组,可能包含防线2元数据警告和字段校验警告)。' +
|
|
32
32
|
'\\n\\n两道防线:' +
|
|
33
33
|
'\\n1. 缓存存在检查——未先 read_rem 则报 "has not been read yet"' +
|
|
34
|
-
'\\n2.
|
|
34
|
+
'\\n2. 语义并发检测(三层字段分类)——edit 时重新读取并逐字段比较:' +
|
|
35
|
+
'\\n - 语义字段(text/type/tags 等)变化 → 硬拒绝 "has been modified since last read"' +
|
|
36
|
+
'\\n - parent 变化 → 放行 + warnings 返回 "⚠️ parent has changed (was: X, now: Y)..."' +
|
|
37
|
+
'\\n - 普通元数据(positionAmongstSiblings/updatedAt 等)变化 → 放行 + warnings 返回 "ℹ️ Metadata fields changed since last read: ..."' +
|
|
38
|
+
'\\n 这意味着 edit_tree 移动/重排后可直接 edit_rem,无需重新 read' +
|
|
35
39
|
'\\n\\n常见错误:' +
|
|
36
40
|
'\\n- "has not been read yet" → 先 read_rem' +
|
|
37
|
-
'\\n- "has been modified since last read" →
|
|
41
|
+
'\\n- "has been modified since last read" → 语义字段被外部修改,重新 read_rem' +
|
|
38
42
|
'\\n- "Invalid value for \'field\'" → 检查枚举合法值' +
|
|
39
43
|
'\\n- "Field \'...\' is read-only/unknown and was ignored" → 警告不阻断' +
|
|
40
44
|
'\\n\\n关联工具:read_rem(前置读取)、edit_tree(子树结构编辑)',
|
|
@@ -329,11 +329,15 @@ export function registerReadTools(server) {
|
|
|
329
329
|
'\\n\\n重要:用户正在看的页面对 AI 不可见。当用户说"这个"、"当前页面"、"这里",或描述与已知信息对不上时,必须主动调用 read_context 对齐信息。' +
|
|
330
330
|
'\\n\\n适用场景:了解用户当前焦点位置或打开的页面;用户说"我现在在看什么"、"当前页面是什么"时使用;需要上下文才能理解用户指代时使用。' +
|
|
331
331
|
'\\n不适用场景:查看特定 Rem(已知 remId 用 read_tree);搜索内容(用 search)。' +
|
|
332
|
-
'\\n\\n前置条件:daemon 已连接。focus
|
|
332
|
+
'\\n\\n前置条件:daemon 已连接。page 模式只需有打开的页面(几乎总满足);focus 模式需用户光标停在某个 Rem 上或指定 focusRemId(否则报错"当前没有聚焦的 Rem")。' +
|
|
333
|
+
'\\n\\n模式选择指引:' +
|
|
334
|
+
'\\n- 绝大多数场景用 page(默认)——用户通常只是打开页面浏览,不会特意点击某个 Rem,page 更可靠' +
|
|
335
|
+
'\\n- 仅当需要知道用户光标具体在哪个 Rem 上时才用 focus——如用户说"我正在编辑的这个"、"光标所在的 Rem"' +
|
|
336
|
+
'\\n- 不确定时用 page——最坏情况只是展开整个页面;focus 的最坏情况是报错' +
|
|
333
337
|
'\\n\\n参数说明:' +
|
|
334
|
-
'\\n- mode(可选,默认 "
|
|
338
|
+
'\\n- mode(可选,默认 "page"):视图模式' +
|
|
339
|
+
'\\n - page:以当前打开的页面为根,均匀展开子树(推荐,可靠性高)' +
|
|
335
340
|
'\\n - focus:以焦点 Rem 为中心的鱼眼视图。焦点完全展开(depth=3),siblings 浅层预览(depth=1,前3个children可见),叔伯不展开。焦点行以 * 前缀标记' +
|
|
336
|
-
'\\n - page:以当前打开的页面为根,均匀展开子树' +
|
|
337
341
|
'\\n- focusRemId(可选,仅 focus 模式):指定任意 Rem 作为鱼眼中心,此时不依赖用户实际焦点。page 模式下传入会报错' +
|
|
338
342
|
'\\n- ancestorLevels(可选,默认 2,仅 focus 模式生效):从焦点向上追溯几层祖先作为上下文起点' +
|
|
339
343
|
'\\n- depth(可选,默认 3,仅 page 模式生效):向下展开深度(-1 无限)' +
|
|
@@ -352,7 +356,7 @@ export function registerReadTools(server) {
|
|
|
352
356
|
mode: z
|
|
353
357
|
.enum(['focus', 'page'])
|
|
354
358
|
.optional()
|
|
355
|
-
.describe('视图模式:focus
|
|
359
|
+
.describe('视图模式:page(页面)或 focus(聚焦),默认 page'),
|
|
356
360
|
ancestorLevels: z
|
|
357
361
|
.number()
|
|
358
362
|
.optional()
|
package/package.json
CHANGED
|
@@ -57,7 +57,7 @@ Add a <Suspense fallback=...> component higher in the tree to provide a loading
|
|
|
57
57
|
* LICENSE file in the root directory of this source tree.
|
|
58
58
|
*/var qe,Ot,De,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 L=K.unstable_now();Tt(!0,L),Tt=null}catch(A){throw setTimeout(Y,0),A}};qe=function(L){Tt!==null?setTimeout(qe,0,L):(Tt=L,setTimeout(Y,0))},Ot=function(L,A){b=setTimeout(L,A)},De=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 He=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(L){0>L||125<L?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<L?Math.floor(1e3/L):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var L=K.unstable_now();ot=L+tt;try{at(!0,L)?ae.postMessage(null):(He=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else He=!1},qe=function(L){at=L,He||(He=!0,ae.postMessage(null))},Ot=function(L,A){Pt=c(function(){L(K.unstable_now())},A)},De=function(){Mt(Pt),Pt=-1}}function _e(L,A){var $=L.length;L.push(A);e:for(;;){var H=$-1>>>1,ne=L[H];if(ne!==void 0&&0<st(ne,A))L[H]=A,L[$]=ne,$=H;else break e}}function Oe(L){return L=L[0],L===void 0?null:L}function $e(L){var A=L[0];if(A!==void 0){var $=L.pop();if($!==A){L[0]=$;e:for(var H=0,ne=L.length;H<ne;){var U=2*(H+1)-1,ye=L[U],M=U+1,ln=L[M];if(ye!==void 0&&0>st(ye,$))ln!==void 0&&0>st(ln,ye)?(L[H]=ln,L[M]=$,H=M):(L[H]=ye,L[U]=$,H=U);else if(ln!==void 0&&0>st(ln,$))L[H]=ln,L[M]=$,H=M;else break e}}return A}return null}function st(L,A){var $=L.sortIndex-A.sortIndex;return $!==0?$:L.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(L){for(var A=Oe(Ze);A!==null;){if(A.callback===null)$e(Ze);else if(A.startTime<=L)$e(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Oe(Ze)}}function ge(L){if(me=!1,q(L),!ie)if(Oe(Z)!==null)ie=!0,qe(C);else{var A=Oe(Ze);A!==null&&Ot(ge,A.startTime-L)}}function C(L,A){ie=!1,me&&(me=!1,De()),ee=!0;var $=fe;try{for(q(A),ue=Oe(Z);ue!==null&&(!(ue.expirationTime>A)||L&&!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===Oe(Z)&&$e(Z),q(A)}else $e(Z);ue=Oe(Z)}if(ue!==null)var U=!0;else{var ye=Oe(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(L){L.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,qe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Oe(Z)},K.unstable_next=function(L){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return L()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(L,A){switch(L){case 1:case 2:case 3:case 4:case 5:break;default:L=3}var $=fe;fe=L;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(L,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,L){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,L={id:Ye++,callback:A,priorityLevel:L,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(L.sortIndex=$,_e(Ze,L),Oe(Z)===null&&L===Oe(Ze)&&(me?De():me=!0,Ot(ge,$-H))):(L.sortIndex=ne,_e(Z,L),ie||ee||(ie=!0,qe(C))),L},K.unstable_wrapCallback=function(L){var A=fe;return function(){var $=fe;fe=A;try{return L.apply(this,arguments)}finally{fe=$}}}},825(tn,K,qe){"use strict";tn.exports=qe(742)}},vd={};function Ac(tn){var K=vd[tn];if(K!==void 0)return K.exports;var qe=vd[tn]={exports:{}};return Pf[tn](qe,qe.exports,Ac),qe.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 tn=Ac(216);const K="0.2.1",qe=[29100,29110,29120,29130],Ot=18e3;var De=Object.defineProperty,it=(w,y,E)=>y in w?De(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),F={id:R.id,result:N};this.ws?.send(JSON.stringify(F)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const F=N instanceof Error?N.message:String(N),Q={id:R.id,error:F};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${F}`,"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<qe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:qe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${qe[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,F)=>y.onLog(E,N,F),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 He(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`})`;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,[F,Q,G,ve,Ae,Ne,he,mt,Me,Qe,wt,Pe,Be,Xe,nt]=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(Je=>N?Je:at(Je).then(Ke=>{const Zt=Je.length-Ke.length;return Zt>0&&R?.onFilteredTags?.(Zt),Ke})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let We=!1;E.length>0&&(We=(await Promise.all(E.map(Ke=>Ke.isCardItem()))).some(Boolean));const xt=Xe&&(y.text??[]).length===0,Fe=await Promise.all(Ne.map(async Je=>({id:Je._id,name:te(await Pt(w,Je.text??[]))})));return{id:y._id,markdownText:te(F),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:We,practiceDirection:he??"none",isCardItem:ve,isDocument:Ae,isPortal:y.type===6,portalRefs:nt.map(Je=>Je._id),childrenCount:E.length,tags:Fe,fontSize:mt??null,isTodo:Me,todoStatus:Qe??null,isCode:wt,isQuote:Pe,isListItem:Be,isDivider:xt,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 Oe(w,R,{includePowerup:E})}async function Oe(w,y,E){const{includePowerup:R=!1}=E,[N,F,Q,G,ve,Ae,Ne,he,mt,Me,Qe,wt,Pe,Be,Xe,nt,We,xt,Fe,Je,Ke,Zt,Ge,Kt,un,pn,Wt,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=Xe,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Xe),mi=Xe.length-dr.length;const ze=await He(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:$e(y.text??[]),backText:y.backText?$e(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:F??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Ae,isQuote:Ne,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:Qe,isProperty:wt,isPowerup:Fe,isPowerupEnum:Je,isPowerupProperty:Ke,isPowerupPropertyListItem:Zt,isPowerupSlot:Ge,portalType:ae(y.type)==="portal"?st(Kt):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:We.map(ze=>ze._id).sort(),remsBeingReferenced:Wt.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:xt??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 $e(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 F;return E!==null?F=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?F=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):F=y,w.isCode&&(F="`"+F+"`"),w.isListItem&&(F="1. "+F),w.isQuote&&(F="> "+F),w.isTodo&&(F=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+F),w.fontSize&&(F=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+F),F}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,isQuote:!1,isListItem:!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 F=" ".repeat(N);if(Z(R)){y.push(F+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(F+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:F=20,ancestorLevels:Q=0,includePowerup:G=!1}=y,ve=await w.rem.findOne(E);if(!ve)throw new Error(`Rem not found: ${E}`);let Ae=0,Ne=0,he=0;const mt={remaining:N},Me=[];async function Qe(We,xt,Fe){Ae++,mt.remaining--,Me.push(We._id);const Je=await We.getChildrenRem(),Ke=G?Je:await He(Je);G||(he+=Je.length-Ke.length);const Ge=Fe!==-1&&xt>=Fe&&Ke.length>0,Kt=await ot(w,We,Ke,{includePowerup:G,onFilteredTags:pn=>{Ne+=pn}});Ge&&(Kt.hasMultilineChildren=!1);const un=[];if(!Ge){const{visibleIndices:pn,elided:Wt}=me(Ke.length,F,We._id);if(pn){const{head:ut,tail:ct}=pn;for(let Bt=0;Bt<ut&&mt.remaining>0;Bt++)un.push(await Qe(Ke[Bt],xt+1,Fe));if(Wt){const Bt=Fe!==-1&&xt+1>=Fe;un.push({type:"elided",count:Wt.count,isExact:Bt,parentId:Wt.parentId,rangeFrom:Wt.rangeFrom,rangeTo:Wt.rangeTo,totalSiblings:Wt.totalSiblings})}const Nt=Ke.length-ct;for(let Bt=Nt;Bt<Ke.length&&mt.remaining>0;Bt++)un.push(await Qe(Ke[Bt],xt+1,Fe))}else for(let ut=0;ut<Ke.length;ut++){if(mt.remaining<=0){const ct=Ke.length-ut;un.push({type:"elided",count:ct,isExact:!1,parentId:We._id,rangeFrom:ut,rangeTo:Ke.length-1,totalSiblings:Ke.length});break}un.push(await Qe(Ke[ut],xt+1,Fe))}}return{rem:Kt,children:un,folded:Ge}}const wt=await Qe(ve,0,R);await ve.getParentRem()||(wt.rem.isTopLevel=!0);const Be=ie(wt),Xe={rootId:E,depth:R,nodeCount:Ae,outline:Be,nodeRemIds:Me},nt=Math.min(Math.max(Q,0),10);if(nt>0){const We=[];let xt=ve;for(let Fe=0;Fe<nt;Fe++){const Je=await xt.getParentRem();if(!Je)break;const[Ke,Zt,Ge]=await Promise.all([Pt(w,Je.text??[]),Je.getChildrenRem(),Je.isDocument()]);We.push({id:Je._id,name:te(Ke),childrenCount:Zt.length,isDocument:Ge}),xt=Je}if(We.length>0){const Fe=We[We.length-1];await xt.getParentRem()||(Fe.isTopLevel=!0),Xe.ancestors=We}}return!G&&(Ne>0||he>0)&&(Xe.powerupFiltered={tags:Ne,children:he}),Xe}async function ge(w,y){const E=await q(w,y),R=y.includePowerup??!1,N={};return await Promise.all(E.nodeRemIds.map(async F=>{const Q=await w.rem.findOne(F);Q&&(N[F]=await Oe(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 Ae=0;const Ne={remaining:R};async function he(Pe,Be,Xe){Ae++,Ne.remaining--;const nt=await Pe.getChildrenRem(),We=await He(nt),xt=await Promise.all(We.map(ut=>ut.isDocument())),Fe=We.filter((ut,ct)=>xt[ct]),Je=We.length-Fe.length,Zt=Xe!==-1&&Be>=Xe&&Fe.length>0,Ge=Pe.type===6,[Kt,un]=await Promise.all([Pt(w,Pe.text??[]),Ge?Pe.getPortalDirectlyIncludedRem():Promise.resolve([])]),pn=ue({id:Pe._id,markdownText:Kt.replace(/\n/g," "),childrenCount:We.length,isDocument:!0,isTopLevel:Pe.parent===null,isPortal:Ge,...Ge?{type:"portal",portalRefs:un.map(ut=>ut._id)}:{}}),Wt=[];if(!Zt&&Fe.length>0){const{visibleIndices:ut,elided:ct}=me(Fe.length,N,Pe._id);if(ut){const{head:Nt,tail:Bt}=ut;for(let Dt=0;Dt<Nt&&Ne.remaining>0;Dt++)Wt.push(await he(Fe[Dt],Be+1,Xe));if(ct){const Dt=Xe!==-1&&Be+1>=Xe;Wt.push({type:"elided",count:ct.count,isExact:Dt,parentId:ct.parentId,rangeFrom:ct.rangeFrom,rangeTo:ct.rangeTo,totalSiblings:ct.totalSiblings})}const hn=Fe.length-Bt;for(let Dt=hn;Dt<Fe.length&&Ne.remaining>0;Dt++)Wt.push(await he(Fe[Dt],Be+1,Xe))}else for(let Nt=0;Nt<Fe.length;Nt++){if(Ne.remaining<=0){const Bt=Fe.length-Nt;Wt.push({type:"elided",count:Bt,isExact:!1,parentId:Pe._id,rangeFrom:Nt,rangeTo:Fe.length-1,totalSiblings:Fe.length});break}Wt.push(await he(Fe[Nt],Be+1,Xe))}}return{rem:pn,children:Wt,folded:Zt}}const mt=[],{visibleIndices:Me,elided:Qe}=me(ve.length,N,"root");if(Me){const{head:Pe,tail:Be}=Me;for(let nt=0;nt<Pe&&Ne.remaining>0;nt++)mt.push(await he(ve[nt],0,E));Qe&&mt.push({type:"elided",count:Qe.count,isExact:!1,parentId:"root",rangeFrom:Qe.rangeFrom,rangeTo:Qe.rangeTo,totalSiblings:Qe.totalSiblings});const Xe=ve.length-Be;for(let nt=Xe;nt<ve.length&&Ne.remaining>0;nt++)mt.push(await he(ve[nt],0,E))}else for(let Pe=0;Pe<ve.length;Pe++){if(Ne.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:Ae,outline:wt.join(`
|
|
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 L(w,y){const{mode:E="
|
|
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 L(w,y){const{mode:E="page",ancestorLevels:R=2,maxNodes:N=200,maxSiblings:F=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:F,depth:Q})}return $(w,{ancestorLevels:R,maxNodes:N,maxSiblings:F,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 F=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 Ne=`<!-- page: ${F[F.length-1]||R} -->
|
|
61
61
|
<!-- path: ${F.join(" > ")} -->`+`
|
|
62
62
|
`+ie(ve);return{nodeCount:Q,outline:Ne,breadcrumb:F,mode:"page"}}async function $(w,y){let E;if(y.focusRemId){if(E=await w.rem.findOne(y.focusRemId),!E)throw new Error(`\u6307\u5B9A\u7684 Rem \u4E0D\u5B58\u5728: ${y.focusRemId}`)}else if(E=await w.focus.getFocusedRem(),!E)throw new Error("\u5F53\u524D\u6CA1\u6709\u805A\u7126\u7684 Rem\uFF0C\u8BF7\u5148\u5728 RemNote \u4E2D\u70B9\u51FB\u4E00\u4E2A Rem");const R=await V(w,E),N=[E];let F=E;for(let Me=0;Me<y.ancestorLevels;Me++){const Qe=await F.getParentRem();if(!Qe)break;N.unshift(Qe),F=Qe}const Q={remaining:y.maxNodes};let G=0;const ve=N[0],Ae=await H(w,ve,N,0,E._id,y.maxSiblings,Q);G=y.maxNodes-Q.remaining;const Ne=R[R.length-1]||E._id,mt=`<!-- path: ${R.join(" > ")} -->
|
|
63
63
|
<!-- focus: ${Ne} (${E._id}) -->`+`
|
|
@@ -57,7 +57,7 @@ Add a <Suspense fallback=...> component higher in the tree to provide a loading
|
|
|
57
57
|
* LICENSE file in the root directory of this source tree.
|
|
58
58
|
*/var qe,Ot,De,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 L=K.unstable_now();Tt(!0,L),Tt=null}catch(A){throw setTimeout(Y,0),A}};qe=function(L){Tt!==null?setTimeout(qe,0,L):(Tt=L,setTimeout(Y,0))},Ot=function(L,A){b=setTimeout(L,A)},De=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 He=!1,at=null,Pt=-1,tt=5,ot=0;K.unstable_shouldYield=function(){return K.unstable_now()>=ot},it=function(){},K.unstable_forceFrameRate=function(L){0>L||125<L?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):tt=0<L?Math.floor(1e3/L):5};var te=new MessageChannel,ae=te.port2;te.port1.onmessage=function(){if(at!==null){var L=K.unstable_now();ot=L+tt;try{at(!0,L)?ae.postMessage(null):(He=!1,at=null)}catch(A){throw ae.postMessage(null),A}}else He=!1},qe=function(L){at=L,He||(He=!0,ae.postMessage(null))},Ot=function(L,A){Pt=c(function(){L(K.unstable_now())},A)},De=function(){Mt(Pt),Pt=-1}}function _e(L,A){var $=L.length;L.push(A);e:for(;;){var H=$-1>>>1,ne=L[H];if(ne!==void 0&&0<st(ne,A))L[H]=A,L[$]=ne,$=H;else break e}}function Oe(L){return L=L[0],L===void 0?null:L}function $e(L){var A=L[0];if(A!==void 0){var $=L.pop();if($!==A){L[0]=$;e:for(var H=0,ne=L.length;H<ne;){var U=2*(H+1)-1,ye=L[U],M=U+1,ln=L[M];if(ye!==void 0&&0>st(ye,$))ln!==void 0&&0>st(ln,ye)?(L[H]=ln,L[M]=$,H=M):(L[H]=ye,L[U]=$,H=U);else if(ln!==void 0&&0>st(ln,$))L[H]=ln,L[M]=$,H=M;else break e}}return A}return null}function st(L,A){var $=L.sortIndex-A.sortIndex;return $!==0?$:L.id-A.id}var Z=[],Ze=[],Ye=1,ue=null,fe=3,ee=!1,ie=!1,me=!1;function q(L){for(var A=Oe(Ze);A!==null;){if(A.callback===null)$e(Ze);else if(A.startTime<=L)$e(Ze),A.sortIndex=A.expirationTime,_e(Z,A);else break;A=Oe(Ze)}}function ge(L){if(me=!1,q(L),!ie)if(Oe(Z)!==null)ie=!0,qe(C);else{var A=Oe(Ze);A!==null&&Ot(ge,A.startTime-L)}}function C(L,A){ie=!1,me&&(me=!1,De()),ee=!0;var $=fe;try{for(q(A),ue=Oe(Z);ue!==null&&(!(ue.expirationTime>A)||L&&!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===Oe(Z)&&$e(Z),q(A)}else $e(Z);ue=Oe(Z)}if(ue!==null)var U=!0;else{var ye=Oe(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(L){L.callback=null},K.unstable_continueExecution=function(){ie||ee||(ie=!0,qe(C))},K.unstable_getCurrentPriorityLevel=function(){return fe},K.unstable_getFirstCallbackNode=function(){return Oe(Z)},K.unstable_next=function(L){switch(fe){case 1:case 2:case 3:var A=3;break;default:A=fe}var $=fe;fe=A;try{return L()}finally{fe=$}},K.unstable_pauseExecution=function(){},K.unstable_requestPaint=V,K.unstable_runWithPriority=function(L,A){switch(L){case 1:case 2:case 3:case 4:case 5:break;default:L=3}var $=fe;fe=L;try{return A()}finally{fe=$}},K.unstable_scheduleCallback=function(L,A,$){var H=K.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?H+$:H):$=H,L){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,L={id:Ye++,callback:A,priorityLevel:L,startTime:$,expirationTime:ne,sortIndex:-1},$>H?(L.sortIndex=$,_e(Ze,L),Oe(Z)===null&&L===Oe(Ze)&&(me?De():me=!0,Ot(ge,$-H))):(L.sortIndex=ne,_e(Z,L),ie||ee||(ie=!0,qe(C))),L},K.unstable_wrapCallback=function(L){var A=fe;return function(){var $=fe;fe=A;try{return L.apply(this,arguments)}finally{fe=$}}}},825(tn,K,qe){"use strict";tn.exports=qe(742)}},vd={};function Ac(tn){var K=vd[tn];if(K!==void 0)return K.exports;var qe=vd[tn]={exports:{}};return Pf[tn](qe,qe.exports,Ac),qe.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 tn=Ac(216);const K="0.2.1",qe=[29100,29110,29120,29130],Ot=18e3;var De=Object.defineProperty,it=(w,y,E)=>y in w?De(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),F={id:R.id,result:N};this.ws?.send(JSON.stringify(F)),this.log(`\u5B8C\u6210: ${R.action}`)}catch(N){const F=N instanceof Error?N.message:String(N),Q={id:R.id,error:F};this.ws?.send(JSON.stringify(Q)),this.log(`\u5931\u8D25: ${R.action} - ${F}`,"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<qe.length;E++){const R=E===y.twinSlotIndex;this.slotStates.push({slotIndex:E,wsPort:qe[E],status:"disconnected",isTwin:R,disconnectReason:"not_started"}),this.clients.push(new Tt({url:`ws://127.0.0.1:${qe[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,F)=>y.onLog(E,N,F),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 He(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`})`;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,[F,Q,G,ve,Ae,Ne,he,mt,Me,Qe,wt,Pe,Be,Xe,nt]=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(Je=>N?Je:at(Je).then(Ke=>{const Zt=Je.length-Ke.length;return Zt>0&&R?.onFilteredTags?.(Zt),Ke})),y.getPracticeDirection(),y.getFontSize(),y.isTodo(),y.getTodoStatus(),y.isCode(),y.isQuote(),y.isListItem(),y.hasPowerup("dv"),y.type===6?y.getPortalDirectlyIncludedRem():Promise.resolve([])]);let We=!1;E.length>0&&(We=(await Promise.all(E.map(Ke=>Ke.isCardItem()))).some(Boolean));const xt=Xe&&(y.text??[]).length===0,Fe=await Promise.all(Ne.map(async Je=>({id:Je._id,name:te(await Pt(w,Je.text??[]))})));return{id:y._id,markdownText:te(F),markdownBackText:Q!==null?te(Q):null,type:ae(G),hasMultilineChildren:We,practiceDirection:he??"none",isCardItem:ve,isDocument:Ae,isPortal:y.type===6,portalRefs:nt.map(Je=>Je._id),childrenCount:E.length,tags:Fe,fontSize:mt??null,isTodo:Me,todoStatus:Qe??null,isCode:wt,isQuote:Pe,isListItem:Be,isDivider:xt,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 Oe(w,R,{includePowerup:E})}async function Oe(w,y,E){const{includePowerup:R=!1}=E,[N,F,Q,G,ve,Ae,Ne,he,mt,Me,Qe,wt,Pe,Be,Xe,nt,We,xt,Fe,Je,Ke,Zt,Ge,Kt,un,pn,Wt,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=Xe,Cr=y.children??[],mi=0,Mr=0;if(!R){dr=await at(Xe),mi=Xe.length-dr.length;const ze=await He(Tr);Mr=Tr.length-ze.length,Cr=ze.map(An=>An._id)}const fr={id:y._id,text:$e(y.text??[]),backText:y.backText?$e(y.backText):null,type:ae(y.type),isDocument:N,parent:y.parent,children:Cr,fontSize:F??null,highlightColor:Q??null,isTodo:G,todoStatus:ve??null,isCode:Ae,isQuote:Ne,isListItem:he,isCardItem:mt,isTable:!!Me,isSlot:Qe,isProperty:wt,isPowerup:Fe,isPowerupEnum:Je,isPowerupProperty:Ke,isPowerupPropertyListItem:Zt,isPowerupSlot:Ge,portalType:ae(y.type)==="portal"?st(Kt):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:We.map(ze=>ze._id).sort(),remsBeingReferenced:Wt.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:xt??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 $e(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 F;return E!==null?F=y+(R?N==="backward"?" \u2191 ":N==="both"?" \u2195 ":" \u2193 ":N==="backward"?" \u2190 ":N==="both"?" \u2194 ":" \u2192 ")+E:R?F=y+(N==="backward"?" \u2191":N==="both"?" \u2195":" \u2193"):F=y,w.isCode&&(F="`"+F+"`"),w.isListItem&&(F="1. "+F),w.isQuote&&(F="> "+F),w.isTodo&&(F=(w.todoStatus==="Finished"?"- [x] ":"- [ ] ")+F),w.fontSize&&(F=(w.fontSize==="H1"?"# ":w.fontSize==="H2"?"## ":"### ")+F),F}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,isQuote:!1,isListItem:!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 F=" ".repeat(N);if(Z(R)){y.push(F+ee(R));return}const Q=fe(R.rem,R.folded);if(y.push(F+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:F=20,ancestorLevels:Q=0,includePowerup:G=!1}=y,ve=await w.rem.findOne(E);if(!ve)throw new Error(`Rem not found: ${E}`);let Ae=0,Ne=0,he=0;const mt={remaining:N},Me=[];async function Qe(We,xt,Fe){Ae++,mt.remaining--,Me.push(We._id);const Je=await We.getChildrenRem(),Ke=G?Je:await He(Je);G||(he+=Je.length-Ke.length);const Ge=Fe!==-1&&xt>=Fe&&Ke.length>0,Kt=await ot(w,We,Ke,{includePowerup:G,onFilteredTags:pn=>{Ne+=pn}});Ge&&(Kt.hasMultilineChildren=!1);const un=[];if(!Ge){const{visibleIndices:pn,elided:Wt}=me(Ke.length,F,We._id);if(pn){const{head:ut,tail:ct}=pn;for(let Bt=0;Bt<ut&&mt.remaining>0;Bt++)un.push(await Qe(Ke[Bt],xt+1,Fe));if(Wt){const Bt=Fe!==-1&&xt+1>=Fe;un.push({type:"elided",count:Wt.count,isExact:Bt,parentId:Wt.parentId,rangeFrom:Wt.rangeFrom,rangeTo:Wt.rangeTo,totalSiblings:Wt.totalSiblings})}const Nt=Ke.length-ct;for(let Bt=Nt;Bt<Ke.length&&mt.remaining>0;Bt++)un.push(await Qe(Ke[Bt],xt+1,Fe))}else for(let ut=0;ut<Ke.length;ut++){if(mt.remaining<=0){const ct=Ke.length-ut;un.push({type:"elided",count:ct,isExact:!1,parentId:We._id,rangeFrom:ut,rangeTo:Ke.length-1,totalSiblings:Ke.length});break}un.push(await Qe(Ke[ut],xt+1,Fe))}}return{rem:Kt,children:un,folded:Ge}}const wt=await Qe(ve,0,R);await ve.getParentRem()||(wt.rem.isTopLevel=!0);const Be=ie(wt),Xe={rootId:E,depth:R,nodeCount:Ae,outline:Be,nodeRemIds:Me},nt=Math.min(Math.max(Q,0),10);if(nt>0){const We=[];let xt=ve;for(let Fe=0;Fe<nt;Fe++){const Je=await xt.getParentRem();if(!Je)break;const[Ke,Zt,Ge]=await Promise.all([Pt(w,Je.text??[]),Je.getChildrenRem(),Je.isDocument()]);We.push({id:Je._id,name:te(Ke),childrenCount:Zt.length,isDocument:Ge}),xt=Je}if(We.length>0){const Fe=We[We.length-1];await xt.getParentRem()||(Fe.isTopLevel=!0),Xe.ancestors=We}}return!G&&(Ne>0||he>0)&&(Xe.powerupFiltered={tags:Ne,children:he}),Xe}async function ge(w,y){const E=await q(w,y),R=y.includePowerup??!1,N={};return await Promise.all(E.nodeRemIds.map(async F=>{const Q=await w.rem.findOne(F);Q&&(N[F]=await Oe(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 Ae=0;const Ne={remaining:R};async function he(Pe,Be,Xe){Ae++,Ne.remaining--;const nt=await Pe.getChildrenRem(),We=await He(nt),xt=await Promise.all(We.map(ut=>ut.isDocument())),Fe=We.filter((ut,ct)=>xt[ct]),Je=We.length-Fe.length,Zt=Xe!==-1&&Be>=Xe&&Fe.length>0,Ge=Pe.type===6,[Kt,un]=await Promise.all([Pt(w,Pe.text??[]),Ge?Pe.getPortalDirectlyIncludedRem():Promise.resolve([])]),pn=ue({id:Pe._id,markdownText:Kt.replace(/\n/g," "),childrenCount:We.length,isDocument:!0,isTopLevel:Pe.parent===null,isPortal:Ge,...Ge?{type:"portal",portalRefs:un.map(ut=>ut._id)}:{}}),Wt=[];if(!Zt&&Fe.length>0){const{visibleIndices:ut,elided:ct}=me(Fe.length,N,Pe._id);if(ut){const{head:Nt,tail:Bt}=ut;for(let Dt=0;Dt<Nt&&Ne.remaining>0;Dt++)Wt.push(await he(Fe[Dt],Be+1,Xe));if(ct){const Dt=Xe!==-1&&Be+1>=Xe;Wt.push({type:"elided",count:ct.count,isExact:Dt,parentId:ct.parentId,rangeFrom:ct.rangeFrom,rangeTo:ct.rangeTo,totalSiblings:ct.totalSiblings})}const hn=Fe.length-Bt;for(let Dt=hn;Dt<Fe.length&&Ne.remaining>0;Dt++)Wt.push(await he(Fe[Dt],Be+1,Xe))}else for(let Nt=0;Nt<Fe.length;Nt++){if(Ne.remaining<=0){const Bt=Fe.length-Nt;Wt.push({type:"elided",count:Bt,isExact:!1,parentId:Pe._id,rangeFrom:Nt,rangeTo:Fe.length-1,totalSiblings:Fe.length});break}Wt.push(await he(Fe[Nt],Be+1,Xe))}}return{rem:pn,children:Wt,folded:Zt}}const mt=[],{visibleIndices:Me,elided:Qe}=me(ve.length,N,"root");if(Me){const{head:Pe,tail:Be}=Me;for(let nt=0;nt<Pe&&Ne.remaining>0;nt++)mt.push(await he(ve[nt],0,E));Qe&&mt.push({type:"elided",count:Qe.count,isExact:!1,parentId:"root",rangeFrom:Qe.rangeFrom,rangeTo:Qe.rangeTo,totalSiblings:Qe.totalSiblings});const Xe=ve.length-Be;for(let nt=Xe;nt<ve.length&&Ne.remaining>0;nt++)mt.push(await he(ve[nt],0,E))}else for(let Pe=0;Pe<ve.length;Pe++){if(Ne.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:Ae,outline:wt.join(`
|
|
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 L(w,y){const{mode:E="
|
|
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 L(w,y){const{mode:E="page",ancestorLevels:R=2,maxNodes:N=200,maxSiblings:F=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:F,depth:Q})}return $(w,{ancestorLevels:R,maxNodes:N,maxSiblings:F,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 F=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 Ne=`<!-- page: ${F[F.length-1]||R} -->
|
|
61
61
|
<!-- path: ${F.join(" > ")} -->`+`
|
|
62
62
|
`+ie(ve);return{nodeCount:Q,outline:Ne,breadcrumb:F,mode:"page"}}async function $(w,y){let E;if(y.focusRemId){if(E=await w.rem.findOne(y.focusRemId),!E)throw new Error(`\u6307\u5B9A\u7684 Rem \u4E0D\u5B58\u5728: ${y.focusRemId}`)}else if(E=await w.focus.getFocusedRem(),!E)throw new Error("\u5F53\u524D\u6CA1\u6709\u805A\u7126\u7684 Rem\uFF0C\u8BF7\u5148\u5728 RemNote \u4E2D\u70B9\u51FB\u4E00\u4E2A Rem");const R=await V(w,E),N=[E];let F=E;for(let Me=0;Me<y.ancestorLevels;Me++){const Qe=await F.getParentRem();if(!Qe)break;N.unshift(Qe),F=Qe}const Q={remaining:y.maxNodes};let G=0;const ve=N[0],Ae=await H(w,ve,N,0,E._id,y.maxSiblings,Q);G=y.maxNodes-Q.remaining;const Ne=R[R.length-1]||E._id,mt=`<!-- path: ${R.join(" > ")} -->
|
|
63
63
|
<!-- focus: ${Ne} (${E._id}) -->`+`
|
|
@@ -97,13 +97,20 @@ export type PropertyTypeValue =
|
|
|
97
97
|
|
|
98
98
|
// ─── RemObject 主体 ─────────────────────────────────────────
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* RemObject 字段注释标记说明:
|
|
102
|
+
* - [R] 只读 / [RW] 可读写 / [R-F] 只读+默认过滤
|
|
103
|
+
* - 🛡️D2 = 参与防线2语义比较(变化 → 硬拒绝)
|
|
104
|
+
* - ⚠️D2 = 敏感元数据(变化 → 放行 + 专门警告)
|
|
105
|
+
* - ⊘D2 = 普通元数据(变化 → 放行 + 统一警告)
|
|
106
|
+
*/
|
|
100
107
|
export interface RemObject {
|
|
101
108
|
|
|
102
109
|
// ══════════════════════════════════════════════════════════
|
|
103
110
|
// 核心标识
|
|
104
111
|
// ══════════════════════════════════════════════════════════
|
|
105
112
|
|
|
106
|
-
/** [R] Rem 唯一 ID。SDK 直接属性 _id */
|
|
113
|
+
/** [R] ⊘D2 Rem 唯一 ID。SDK 直接属性 _id */
|
|
107
114
|
id: string;
|
|
108
115
|
|
|
109
116
|
// ══════════════════════════════════════════════════════════
|
|
@@ -111,12 +118,12 @@ export interface RemObject {
|
|
|
111
118
|
// ══════════════════════════════════════════════════════════
|
|
112
119
|
|
|
113
120
|
/**
|
|
114
|
-
* [RW] ✅ 正面文本(RichText 数组)。SDK: text / setText()
|
|
121
|
+
* [RW] 🛡️D2 ✅ 正面文本(RichText 数组)。SDK: text / setText()
|
|
115
122
|
* UI 行为:文本内容立即更新显示,无格式副作用
|
|
116
123
|
*/
|
|
117
124
|
text: RichText;
|
|
118
125
|
/**
|
|
119
|
-
* [RW] ✅ 背面文本。SDK: backText / setBackText()
|
|
126
|
+
* [RW] 🛡️D2 ✅ 背面文本。SDK: backText / setBackText()
|
|
120
127
|
* UI 行为:设值后 Rem 显示为 "正面文本 → 背面文本" 格式(箭头分隔符)
|
|
121
128
|
* 默认 null(无背面);设值即产生闪卡正反面结构
|
|
122
129
|
* 写入语义:null → setBackText([])(SDK 接受 undefined | RichTextInterface,空数组清除背面)
|
|
@@ -129,14 +136,14 @@ export interface RemObject {
|
|
|
129
136
|
// ══════════════════════════════════════════════════════════
|
|
130
137
|
|
|
131
138
|
/**
|
|
132
|
-
* [RW] ✅ Rem 类型。SDK: type, getType() / setType(SetRemType)
|
|
139
|
+
* [RW] 🛡️D2 ✅ Rem 类型。SDK: type, getType() / setType(SetRemType)
|
|
133
140
|
* UI 行为:CONCEPT → 文字变粗体;DESCRIPTOR → 保持正常字重(与默认无视觉差异)
|
|
134
141
|
* SetRemType 不含 PORTAL(6),Portal 只能通过 createPortal() 创建
|
|
135
142
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
136
143
|
*/
|
|
137
144
|
type: RemTypeValue;
|
|
138
145
|
/**
|
|
139
|
-
* [RW] ✅ 是否作为独立文档页面打开。SDK: isDocument() / setIsDocument()
|
|
146
|
+
* [RW] 🛡️D2 ✅ 是否作为独立文档页面打开。SDK: isDocument() / setIsDocument()
|
|
140
147
|
* UI 行为:bullet (•) 变为文档页面图标(小方块),Rem 可作为独立页面打开
|
|
141
148
|
* 独立于 type,CONCEPT Rem 可以同时是 Document
|
|
142
149
|
* 底层机制:Powerup — 注入"文档" Tag + 自动创建 [Status];;[Draft] descriptor 子 Rem
|
|
@@ -148,11 +155,12 @@ export interface RemObject {
|
|
|
148
155
|
// ══════════════════════════════════════════════════════════
|
|
149
156
|
|
|
150
157
|
/**
|
|
151
|
-
* [RW] ✅ 父 Rem ID。null 表示顶级。SDK: parent / setParent(parent, position?)
|
|
158
|
+
* [RW] ⚠️D2 ✅ 父 Rem ID。null 表示顶级。SDK: parent / setParent(parent, position?)
|
|
152
159
|
* UI 行为:Rem 从原位置消失,出现在新父级的子列表中
|
|
160
|
+
* 防线2:结构操作后常变化,放行但输出专门警告
|
|
153
161
|
*/
|
|
154
162
|
parent: string | null;
|
|
155
|
-
/** [R] 子 Rem ID 有序数组。SDK 直接属性 children(修改子的 parent 间接改变) */
|
|
163
|
+
/** [R] ⊘D2 子 Rem ID 有序数组。SDK 直接属性 children(修改子的 parent 间接改变) */
|
|
156
164
|
children: string[];
|
|
157
165
|
|
|
158
166
|
// ══════════════════════════════════════════════════════════
|
|
@@ -160,14 +168,14 @@ export interface RemObject {
|
|
|
160
168
|
// ══════════════════════════════════════════════════════════
|
|
161
169
|
|
|
162
170
|
/**
|
|
163
|
-
* [RW] ✅ 标题大小。SDK: getFontSize() / setFontSize()
|
|
171
|
+
* [RW] 🛡️D2 ✅ 标题大小。SDK: getFontSize() / setFontSize()
|
|
164
172
|
* UI 行为:H1 → 超大粗体;H2 → 大粗体(略小于 H1);H3 → 中粗体
|
|
165
173
|
* 默认 null(普通大小);setFontSize(undefined) 恢复
|
|
166
174
|
* 底层机制:Powerup — 注入"标题" Tag + 创建 [Size];;[H1/H2/H3] descriptor 子 Rem
|
|
167
175
|
*/
|
|
168
176
|
fontSize: FontSize | null;
|
|
169
177
|
/**
|
|
170
|
-
* [RW] ✅ 高亮颜色。SDK: getHighlightColor() / setHighlightColor()
|
|
178
|
+
* [RW] 🛡️D2 ✅ 高亮颜色。SDK: getHighlightColor() / setHighlightColor()
|
|
171
179
|
* UI 行为:整行背景变为对应颜色(Red→粉红、Blue→浅蓝),bullet 也着色
|
|
172
180
|
* 默认 null(无高亮)
|
|
173
181
|
* SDK 注意:setHighlightColor() 只能设置颜色,不能清除(null/undefined 均被拒绝)
|
|
@@ -181,47 +189,47 @@ export interface RemObject {
|
|
|
181
189
|
// ══════════════════════════════════════════════════════════
|
|
182
190
|
|
|
183
191
|
/**
|
|
184
|
-
* [RW] ✅ 是否待办。SDK: isTodo() / setIsTodo()
|
|
192
|
+
* [RW] 🛡️D2 ✅ 是否待办。SDK: isTodo() / setIsTodo()
|
|
185
193
|
* UI 行为:文本前出现空心 checkbox(☐);副作用:todoStatus 自动初始化为 "Unfinished"
|
|
186
194
|
* 底层机制:Powerup — 注入"待办" Tag + 自动创建 [Status];;[Unfinished] descriptor 子 Rem
|
|
187
195
|
*/
|
|
188
196
|
isTodo: boolean;
|
|
189
197
|
/**
|
|
190
|
-
* [RW] ✅ 待办完成状态。SDK: getTodoStatus() / setTodoStatus()
|
|
198
|
+
* [RW] 🛡️D2 ✅ 待办完成状态。SDK: getTodoStatus() / setTodoStatus()
|
|
191
199
|
* UI 行为:Finished → checkbox 变蓝色已勾选(☑)+ 文本加删除线
|
|
192
200
|
* 前提:需先 setIsTodo(true),否则无意义
|
|
193
201
|
* 写入语义:null → 跳过(清除 todo 状态应通过 isTodo=false 实现,SDK 不接受 null)
|
|
194
202
|
*/
|
|
195
203
|
todoStatus: TodoStatus | null;
|
|
196
204
|
/**
|
|
197
|
-
* [RW] ✅ 是否代码块。SDK: isCode() / setIsCode()
|
|
205
|
+
* [RW] 🛡️D2 ✅ 是否代码块。SDK: isCode() / setIsCode()
|
|
198
206
|
* UI 行为:Rem 变为代码块容器——等宽字体、灰色背景、块级缩进
|
|
199
207
|
* 底层机制:Powerup — 注入"代码" Tag,无参数子 Rem
|
|
200
208
|
*/
|
|
201
209
|
isCode: boolean;
|
|
202
210
|
/**
|
|
203
|
-
* [RW] ✅ 是否引用块。SDK: isQuote() / setIsQuote()
|
|
211
|
+
* [RW] 🛡️D2 ✅ 是否引用块。SDK: isQuote() / setIsQuote()
|
|
204
212
|
* UI 行为:左侧出现灰色竖线 + 行背景变浅灰(经典 blockquote 样式)
|
|
205
213
|
* 底层机制:Powerup — 注入"引用" Tag,无参数子 Rem
|
|
206
214
|
*/
|
|
207
215
|
isQuote: boolean;
|
|
208
216
|
/**
|
|
209
|
-
* [RW] ✅ 是否列表项。SDK: isListItem() / setIsListItem()
|
|
217
|
+
* [RW] 🛡️D2 ✅ 是否列表项。SDK: isListItem() / setIsListItem()
|
|
210
218
|
* UI 行为:bullet (•) 变为数字编号 "1."(有序列表样式)
|
|
211
219
|
* 底层机制:Powerup — 注入"列表项" Tag,无参数子 Rem
|
|
212
220
|
*/
|
|
213
221
|
isListItem: boolean;
|
|
214
222
|
/**
|
|
215
|
-
* [RW] ✅ 是否卡片项。SDK: isCardItem() / setIsCardItem()
|
|
223
|
+
* [RW] 🛡️D2 ✅ 是否卡片项。SDK: isCardItem() / setIsCardItem()
|
|
216
224
|
* UI:无明显变化。功能:标记 Rem 以卡片样式显示(类似看板布局),
|
|
217
225
|
* 而非默认项目符号列表,在 RemNote 的 Card View 中生效
|
|
218
226
|
* 底层机制:Powerup — 注入"卡片条目" Tag (MultiLineCard),无参数子 Rem
|
|
219
227
|
*/
|
|
220
228
|
isCardItem: boolean;
|
|
221
|
-
/** [R] 是否表格。SDK: isTable()(无 setIsTable,只有 setTableFilter) */
|
|
229
|
+
/** [R] 🛡️D2 是否表格。SDK: isTable()(无 setIsTable,只有 setTableFilter) */
|
|
222
230
|
isTable: boolean;
|
|
223
231
|
/**
|
|
224
|
-
* [RW] ✅ 是否 Powerup 插槽。SDK: isSlot() / setIsSlot()
|
|
232
|
+
* [RW] 🛡️D2 ✅ 是否 Powerup 插槽。SDK: isSlot() / setIsSlot()
|
|
225
233
|
* UI:bullet 变为方形图标(☐)。功能:标记 Rem 为 Powerup 的数据插槽(slot),
|
|
226
234
|
* Powerup 注册时通过 slots 配置定义,用于存储键值对数据(值为 RichText)。
|
|
227
235
|
* 通过 getPowerupProperty(code, slot) / setPowerupProperty() 读写
|
|
@@ -232,7 +240,7 @@ export interface RemObject {
|
|
|
232
240
|
*/
|
|
233
241
|
isSlot: boolean;
|
|
234
242
|
/**
|
|
235
|
-
* [RW] ✅ 是否 Tag 属性(表格列)。SDK: isProperty() / setIsProperty()
|
|
243
|
+
* [RW] 🛡️D2 ✅ 是否 Tag 属性(表格列)。SDK: isProperty() / setIsProperty()
|
|
236
244
|
* UI:bullet 变为方形图标(☐,与 isSlot 相同)。功能:标记 Rem 为父级 Tag 的
|
|
237
245
|
* 结构化属性列,可通过 getPropertyType() 指定数据类型(text/number/date/checkbox/
|
|
238
246
|
* single_select/multi_select/url/image 等),通过 getTagPropertyValue(propertyId) /
|
|
@@ -240,31 +248,31 @@ export interface RemObject {
|
|
|
240
248
|
* 底层机制:与 isSlot 完全相同 — 注入同一个"模板插槽" Tag (vD8KGEg5dkj9bzkRn)
|
|
241
249
|
*/
|
|
242
250
|
isProperty: boolean;
|
|
243
|
-
/** [R-F] 是否 Powerup。SDK: isPowerup()(写入用 addPowerup/removePowerup,参数化)。Powerup 系统标识 */
|
|
251
|
+
/** [R-F] ⊘D2 是否 Powerup。SDK: isPowerup()(写入用 addPowerup/removePowerup,参数化)。Powerup 系统标识 */
|
|
244
252
|
isPowerup: boolean;
|
|
245
|
-
/** [R-F] 是否 Powerup 枚举。SDK: isPowerupEnum()。Powerup 细分类型 */
|
|
253
|
+
/** [R-F] ⊘D2 是否 Powerup 枚举。SDK: isPowerupEnum()。Powerup 细分类型 */
|
|
246
254
|
isPowerupEnum: boolean;
|
|
247
|
-
/** [R-F] 是否 Powerup 属性。SDK: isPowerupProperty()。Powerup 细分类型 */
|
|
255
|
+
/** [R-F] ⊘D2 是否 Powerup 属性。SDK: isPowerupProperty()。Powerup 细分类型 */
|
|
248
256
|
isPowerupProperty: boolean;
|
|
249
|
-
/** [R-F] 是否 Powerup 属性列表项。SDK: isPowerupPropertyListItem()。Powerup 细分类型 */
|
|
257
|
+
/** [R-F] ⊘D2 是否 Powerup 属性列表项。SDK: isPowerupPropertyListItem()。Powerup 细分类型 */
|
|
250
258
|
isPowerupPropertyListItem: boolean;
|
|
251
|
-
/** [R-F] 是否 Powerup 插槽。SDK: isPowerupSlot()。Powerup 细分类型 */
|
|
259
|
+
/** [R-F] ⊘D2 是否 Powerup 插槽。SDK: isPowerupSlot()。Powerup 细分类型 */
|
|
252
260
|
isPowerupSlot: boolean;
|
|
253
261
|
|
|
254
262
|
// ══════════════════════════════════════════════════════════
|
|
255
263
|
// Portal 专用
|
|
256
264
|
// ══════════════════════════════════════════════════════════
|
|
257
265
|
|
|
258
|
-
/** [R] Portal 子类型。仅 type === 'portal' 时有值。SDK: getPortalType() */
|
|
266
|
+
/** [R] 🛡️D2 Portal 子类型。仅 type === 'portal' 时有值。SDK: getPortalType() */
|
|
259
267
|
portalType: PortalType | null;
|
|
260
|
-
/** [Portal-W] Portal 直接包含的 Rem ID 数组。SDK: getPortalDirectlyIncludedRem() / addToPortal() / removeFromPortal()。写入时使用 diff 机制。仅 type === 'portal' 时可修改 */
|
|
268
|
+
/** [Portal-W] 🛡️D2 Portal 直接包含的 Rem ID 数组。SDK: getPortalDirectlyIncludedRem() / addToPortal() / removeFromPortal()。写入时使用 diff 机制。仅 type === 'portal' 时可修改 */
|
|
261
269
|
portalDirectlyIncludedRem: string[];
|
|
262
270
|
|
|
263
271
|
// ══════════════════════════════════════════════════════════
|
|
264
272
|
// 属性类型(当此 Rem 是 tag/powerup 的属性时)
|
|
265
273
|
// ══════════════════════════════════════════════════════════
|
|
266
274
|
|
|
267
|
-
/** [R] 属性数据类型。SDK: getPropertyType() */
|
|
275
|
+
/** [R] 🛡️D2 属性数据类型。SDK: getPropertyType() */
|
|
268
276
|
propertyType: PropertyTypeValue | null;
|
|
269
277
|
|
|
270
278
|
// ══════════════════════════════════════════════════════════
|
|
@@ -272,14 +280,14 @@ export interface RemObject {
|
|
|
272
280
|
// ══════════════════════════════════════════════════════════
|
|
273
281
|
|
|
274
282
|
/**
|
|
275
|
-
* [RW] ✅ 是否启用间隔重复练习。SDK: getEnablePractice() / setEnablePractice()
|
|
283
|
+
* [RW] 🛡️D2 ✅ 是否启用间隔重复练习。SDK: getEnablePractice() / setEnablePractice()
|
|
276
284
|
* UI:无明显变化。功能:为 true 时,RemNote 根据 Rem 的 text/backText 结构
|
|
277
285
|
* 自动生成闪卡并纳入间隔重复调度。setType(CONCEPT) 可能自动置为 true
|
|
278
286
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
279
287
|
*/
|
|
280
288
|
enablePractice: boolean;
|
|
281
289
|
/**
|
|
282
|
-
* [RW] ✅ 闪卡练习方向。SDK: getPracticeDirection() / setPracticeDirection()
|
|
290
|
+
* [RW] 🛡️D2 ✅ 闪卡练习方向。SDK: getPracticeDirection() / setPracticeDirection()
|
|
283
291
|
* UI:无明显变化。功能:控制闪卡生成方向——forward=正面→背面,
|
|
284
292
|
* backward=背面→正面,both=双向生成,none=不生成闪卡
|
|
285
293
|
* 底层机制:纯字段修改,不涉及 Powerup Tag 注入或隐藏子 Rem
|
|
@@ -291,7 +299,7 @@ export interface RemObject {
|
|
|
291
299
|
// ══════════════════════════════════════════════════════════
|
|
292
300
|
|
|
293
301
|
/**
|
|
294
|
-
* [RW] ✅ 标签 Rem ID 数组。SDK: getTagRems() / addTag() / removeTag()
|
|
302
|
+
* [RW] 🛡️D2 ✅ 标签 Rem ID 数组。SDK: getTagRems() / addTag() / removeTag()
|
|
295
303
|
* UI 行为:行右侧出现标签徽章(圆角矩形,显示标签名 + × 删除按钮)
|
|
296
304
|
* setType(CONCEPT) 等操作可能自动添加系统标签
|
|
297
305
|
* 注意:系统 Powerup Tag 会混入此数组(如 setIsCode 注入的"代码" Tag),
|
|
@@ -299,12 +307,12 @@ export interface RemObject {
|
|
|
299
307
|
*/
|
|
300
308
|
tags: string[];
|
|
301
309
|
/**
|
|
302
|
-
* [RW] ✅ 来源 Rem ID 数组。SDK: getSources() / addSource() / removeSource()
|
|
310
|
+
* [RW] 🛡️D2 ✅ 来源 Rem ID 数组。SDK: getSources() / addSource() / removeSource()
|
|
303
311
|
* UI 行为:Rem 下方出现来源引用子元素(灰色圆角框,显示来源 Rem 名 + ↗ 图标)
|
|
304
312
|
*/
|
|
305
313
|
sources: string[];
|
|
306
314
|
/**
|
|
307
|
-
* [R] 别名 Rem ID 数组。SDK: getAliases()
|
|
315
|
+
* [R] 🛡️D2 别名 Rem ID 数组。SDK: getAliases()
|
|
308
316
|
* 写入接口 getOrCreateAliasWithText(text) 需要文本参数(非 ID),与 RemObject 的 ID 数组形式不匹配。
|
|
309
317
|
* v1 标记为只读,后续可提供独立命令 `add-alias <remId> <text>`
|
|
310
318
|
*/
|
|
@@ -314,37 +322,37 @@ export interface RemObject {
|
|
|
314
322
|
// 关联 — 引用关系(ID 数组)
|
|
315
323
|
// ══════════════════════════════════════════════════════════
|
|
316
324
|
|
|
317
|
-
/** [R] 本 Rem 引用的其他 Rem ID 数组。SDK: remsBeingReferenced() */
|
|
325
|
+
/** [R] 🛡️D2 本 Rem 引用的其他 Rem ID 数组。SDK: remsBeingReferenced() */
|
|
318
326
|
remsBeingReferenced: string[];
|
|
319
|
-
/** [R-F] 本 Rem 深层引用的 Rem ID 数组。SDK: deepRemsBeingReferenced()。可由 remsBeingReferenced 递归获取 */
|
|
327
|
+
/** [R-F] ⊘D2 本 Rem 深层引用的 Rem ID 数组。SDK: deepRemsBeingReferenced()。可由 remsBeingReferenced 递归获取 */
|
|
320
328
|
deepRemsBeingReferenced: string[];
|
|
321
|
-
/** [R] 引用本 Rem 的 Rem ID 数组(反向链接)。SDK: remsReferencingThis() */
|
|
329
|
+
/** [R] ⊘D2 引用本 Rem 的 Rem ID 数组(反向链接)。SDK: remsReferencingThis() */
|
|
322
330
|
remsReferencingThis: string[];
|
|
323
331
|
|
|
324
332
|
// ══════════════════════════════════════════════════════════
|
|
325
333
|
// 关联 — 标签体系(ID 数组)
|
|
326
334
|
// ══════════════════════════════════════════════════════════
|
|
327
335
|
|
|
328
|
-
/** [R] 被本 Rem 标记的 Rem ID 数组(本 Rem 作为 tag 时)。SDK: taggedRem() */
|
|
336
|
+
/** [R] ⊘D2 被本 Rem 标记的 Rem ID 数组(本 Rem 作为 tag 时)。SDK: taggedRem() */
|
|
329
337
|
taggedRem: string[];
|
|
330
|
-
/** [R-F] 祖先标签 Rem ID 数组。SDK: ancestorTagRem()。标签继承链,低频 */
|
|
338
|
+
/** [R-F] ⊘D2 祖先标签 Rem ID 数组。SDK: ancestorTagRem()。标签继承链,低频 */
|
|
331
339
|
ancestorTagRem: string[];
|
|
332
|
-
/** [R-F] 后代标签 Rem ID 数组。SDK: descendantTagRem()。标签继承链,低频 */
|
|
340
|
+
/** [R-F] ⊘D2 后代标签 Rem ID 数组。SDK: descendantTagRem()。标签继承链,低频 */
|
|
333
341
|
descendantTagRem: string[];
|
|
334
342
|
|
|
335
343
|
// ══════════════════════════════════════════════════════════
|
|
336
344
|
// 关联 — 层级遍历(ID 数组)
|
|
337
345
|
// ══════════════════════════════════════════════════════════
|
|
338
346
|
|
|
339
|
-
/** [R] 所有后代 Rem ID 数组。SDK: getDescendants() */
|
|
347
|
+
/** [R] ⊘D2 所有后代 Rem ID 数组。SDK: getDescendants() */
|
|
340
348
|
descendants: string[];
|
|
341
|
-
/** [R] 兄弟 Rem ID 数组。SDK: siblingRem() */
|
|
349
|
+
/** [R] ⊘D2 兄弟 Rem ID 数组。SDK: siblingRem() */
|
|
342
350
|
siblingRem: string[];
|
|
343
|
-
/** [R-F] 包含的 Portal 和文档 Rem ID 数组。SDK: portalsAndDocumentsIn()。使用场景有限 */
|
|
351
|
+
/** [R-F] ⊘D2 包含的 Portal 和文档 Rem ID 数组。SDK: portalsAndDocumentsIn()。使用场景有限 */
|
|
344
352
|
portalsAndDocumentsIn: string[];
|
|
345
|
-
/** [R-F] 文档/Portal 中所有 Rem ID 数组。SDK: allRemInDocumentOrPortal()。可能数据量大 */
|
|
353
|
+
/** [R-F] ⊘D2 文档/Portal 中所有 Rem ID 数组。SDK: allRemInDocumentOrPortal()。可能数据量大 */
|
|
346
354
|
allRemInDocumentOrPortal: string[];
|
|
347
|
-
/** [R-F] 文件夹队列中的 Rem ID 数组。SDK: allRemInFolderQueue()。场景有限 */
|
|
355
|
+
/** [R-F] ⊘D2 文件夹队列中的 Rem ID 数组。SDK: allRemInFolderQueue()。场景有限 */
|
|
348
356
|
allRemInFolderQueue: string[];
|
|
349
357
|
|
|
350
358
|
// ══════════════════════════════════════════════════════════
|
|
@@ -352,36 +360,36 @@ export interface RemObject {
|
|
|
352
360
|
// ══════════════════════════════════════════════════════════
|
|
353
361
|
|
|
354
362
|
/**
|
|
355
|
-
* [RW] ✅ 在兄弟间的位置(0 起始)。SDK: positionAmongstSiblings() / setParent(parent, position)
|
|
363
|
+
* [RW] ⊘D2 ✅ 在兄弟间的位置(0 起始)。SDK: positionAmongstSiblings() / setParent(parent, position)
|
|
356
364
|
* UI 行为:Rem 在父级子列表中的显示位置改变(测试:A→B→C 变为 B→C→A)
|
|
357
365
|
* position 超过实际数量会被钳位到末尾;位置相对于父 Rem 的全部 children
|
|
358
366
|
*/
|
|
359
367
|
positionAmongstSiblings: number | null;
|
|
360
|
-
/** [R-F] 搜索中被选次数。SDK: timesSelectedInSearch()。统计数据,低频 */
|
|
368
|
+
/** [R-F] ⊘D2 搜索中被选次数。SDK: timesSelectedInSearch()。统计数据,低频 */
|
|
361
369
|
timesSelectedInSearch: number;
|
|
362
|
-
/** [R-F] 上次移动时间(毫秒时间戳)。SDK: getLastTimeMovedTo()。过于细粒度 */
|
|
370
|
+
/** [R-F] ⊘D2 上次移动时间(毫秒时间戳)。SDK: getLastTimeMovedTo()。过于细粒度 */
|
|
363
371
|
lastTimeMovedTo: number;
|
|
364
|
-
/** [R-F] Schema 版本号。SDK: getSchemaVersion()。内部版本号 */
|
|
372
|
+
/** [R-F] ⊘D2 Schema 版本号。SDK: getSchemaVersion()。内部版本号 */
|
|
365
373
|
schemaVersion: number;
|
|
366
374
|
|
|
367
375
|
// ══════════════════════════════════════════════════════════
|
|
368
376
|
// 队列视图
|
|
369
377
|
// ══════════════════════════════════════════════════════════
|
|
370
378
|
|
|
371
|
-
/** [R-F] 嵌入式队列视图模式。SDK: embeddedQueueViewMode()。场景有限 */
|
|
379
|
+
/** [R-F] ⊘D2 嵌入式队列视图模式。SDK: embeddedQueueViewMode()。场景有限 */
|
|
372
380
|
embeddedQueueViewMode: boolean;
|
|
373
381
|
|
|
374
382
|
// ══════════════════════════════════════════════════════════
|
|
375
383
|
// 元数据 / 时间戳
|
|
376
384
|
// ══════════════════════════════════════════════════════════
|
|
377
385
|
|
|
378
|
-
/** [R] 创建时间(毫秒时间戳)。SDK 直接属性 createdAt */
|
|
386
|
+
/** [R] ⊘D2 创建时间(毫秒时间戳)。SDK 直接属性 createdAt */
|
|
379
387
|
createdAt: number;
|
|
380
|
-
/** [R] 最后更新时间(毫秒时间戳)。SDK 直接属性 updatedAt */
|
|
388
|
+
/** [R] ⊘D2 最后更新时间(毫秒时间戳)。SDK 直接属性 updatedAt */
|
|
381
389
|
updatedAt: number;
|
|
382
|
-
/** [R-F] 本地最后更新时间(毫秒时间戳)。SDK 直接属性 localUpdatedAt。与 updatedAt 重叠 */
|
|
390
|
+
/** [R-F] ⊘D2 本地最后更新时间(毫秒时间戳)。SDK 直接属性 localUpdatedAt。与 updatedAt 重叠 */
|
|
383
391
|
localUpdatedAt: number;
|
|
384
|
-
/** [R-F] 上次练习时间(毫秒时间戳)。SDK: getLastPracticed()。间隔重复内部数据 */
|
|
392
|
+
/** [R-F] ⊘D2 上次练习时间(毫秒时间戳)。SDK: getLastPracticed()。间隔重复内部数据 */
|
|
385
393
|
lastPracticed: number;
|
|
386
394
|
}
|
|
387
395
|
|
|
@@ -196,7 +196,7 @@ Portal 的编辑同步意味着修改一处会影响另一处。Portal 引用的
|
|
|
196
196
|
|
|
197
197
|
#### 序列化确定性
|
|
198
198
|
|
|
199
|
-
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061)之前。防线 2
|
|
199
|
+
RichText 对象内部按 **key 字母序排列**(`sortRichTextKeys()`),确保 JSON 始终一致。`_id` 中的 `_`(U+005F)排在所有小写字母(`a`=U+0061)之前。防线 2(语义并发检测)依赖此确定性序列化来逐字段比较缓存与最新数据。
|
|
200
200
|
|
|
201
201
|
### Powerup 机制与噪音过滤
|
|
202
202
|
|
|
@@ -232,12 +232,12 @@ RemNote 格式设置(fontSize、highlightColor 等)底层通过 Powerup 机
|
|
|
232
232
|
| 场景 | 命令 | 特点 |
|
|
233
233
|
|:-----|:-----|:-----|
|
|
234
234
|
| 知识库有什么 | `read-globe` | 仅 Document 层级,**无缓存**,最小序列化(无 backText/箭头) |
|
|
235
|
-
|
|
|
236
|
-
|
|
|
235
|
+
| 当前页面内容 | `read-context` | 均匀展开(默认,推荐——只需有打开页面即可),**无缓存** |
|
|
236
|
+
| 我光标在哪 / 正在编辑的 Rem | `read-context --mode focus` | 鱼眼视图(需光标在某 Rem 上,否则报错),**无缓存** |
|
|
237
237
|
| 展开某主题细节 | `read-tree <id>` | 完整子树,**有缓存**供 edit-tree |
|
|
238
238
|
| 展开子树 + 每个节点属性 | `read-rem-in-tree <id>` | 大纲 + RemObject,**双重缓存**供 edit-tree 和 edit-rem |
|
|
239
239
|
|
|
240
|
-
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote
|
|
240
|
+
**重要**:read-globe、read-context、search 都**不写入缓存**,不能替代 read-tree/read-rem/read-rem-in-tree 作为 edit 的前置条件。read-context 需要用户在 RemNote 中有打开页面(page 模式,默认)或有焦点(focus 模式)。
|
|
241
241
|
|
|
242
242
|
### 修改:用户想改什么?
|
|
243
243
|
|
|
@@ -409,9 +409,14 @@ search 调用 RemNote SDK 官方搜索方法,其分词基于空格分割。**
|
|
|
409
409
|
|
|
410
410
|
必须先 read 再 edit。未缓存的 Rem 不允许编辑。
|
|
411
411
|
|
|
412
|
-
### 防线 2
|
|
412
|
+
### 防线 2:语义并发检测(三层字段分类)
|
|
413
413
|
|
|
414
|
-
edit 时从 SDK
|
|
414
|
+
edit 时从 SDK 重新读取最新数据,逐字段与缓存比较并按三层分类处理:
|
|
415
|
+
- **语义字段**(text/type/tags/highlightColor 等)变化 → **硬拒绝**,不更新缓存,迫使 Agent 重新 read
|
|
416
|
+
- **敏感元数据**(parent)变化 → **放行** + warnings 返回 `"⚠️ parent has changed (was: X, now: Y)..."`,静默刷新缓存
|
|
417
|
+
- **普通元数据**(positionAmongstSiblings/updatedAt/siblingRem 等)变化 → **放行** + warnings 返回 `"ℹ️ Metadata fields changed since last read: ..."`,静默刷新缓存
|
|
418
|
+
|
|
419
|
+
这意味着 edit-tree 移动/重排 Rem 后,可直接 edit-rem 修改受影响节点,无需重新 read。
|
|
415
420
|
|
|
416
421
|
### 防线 3:str_replace 精确匹配(仅 edit-tree)
|
|
417
422
|
|
|
@@ -424,7 +429,9 @@ edit-rem 使用字段直接修改(changes 对象),不经过 str_replace,
|
|
|
424
429
|
| 场景 | 缓存行为 | Agent 操作 |
|
|
425
430
|
|:-----|:---------|:-----------|
|
|
426
431
|
| 写入成功 | 从 SDK 重新读取 → 更新缓存 | 可继续编辑 |
|
|
427
|
-
|
|
|
432
|
+
| 仅元数据变化(位置/时间戳等) | 静默刷新缓存并放行 | 可继续编辑(返回 warnings) |
|
|
433
|
+
| 语义字段冲突(text/type 等) | **不更新缓存** | 必须重新 read |
|
|
434
|
+
| 部分写入失败 | **不更新缓存** | 必须重新 read |
|
|
428
435
|
| 枚举值非法(edit-rem) | 缓存保持不变 | 检查允许的值范围后重试 |
|
|
429
436
|
| 防线 3 拒绝 / JSON 语法错误(edit-tree) | 缓存保持不变 | 调整 oldStr/newStr 后**直接重试** |
|
|
430
437
|
| 操作执行中异常(edit-tree) | 已执行的操作保留(**无回滚**),不更新缓存 | 必须重新 read-tree |
|
|
@@ -654,7 +661,7 @@ aliases, descendants, siblingRem, isTable, portalType, propertyType
|
|
|
654
661
|
| 守护进程未运行 | 未 connect 或已超时 | `connect` |
|
|
655
662
|
| Plugin 未连接 | RemNote 未打开 | 打开 RemNote(health 三层:daemon→Plugin→SDK 链式依赖) |
|
|
656
663
|
| has not been read yet | 未先 read | 执行对应 read 命令(search 结果不算 read!) |
|
|
657
|
-
| has been modified since last read |
|
|
664
|
+
| has been modified since last read | 语义字段被外部修改(元数据变化不触发此错误,只返回 warnings) | 重新 read(必须,不可直接重试) |
|
|
658
665
|
| Invalid value for 'field' | 枚举字段值不合法(edit-rem) | 检查该字段允许的值范围 |
|
|
659
666
|
| old_str not found | oldStr 不精确(edit-tree) | 检查引号、空格、换行 |
|
|
660
667
|
| old_str matches N locations | oldStr 不够具体(edit-tree) | 扩大 oldStr 范围,包含更多上下文 |
|
|
@@ -225,28 +225,55 @@ if cache.get('rem:' + remId) === null:
|
|
|
225
225
|
|
|
226
226
|
**恢复方式**:执行 `read-rem <remId>` 后重试。
|
|
227
227
|
|
|
228
|
-
### 防线 2
|
|
228
|
+
### 防线 2:语义并发检测(三层字段分类)
|
|
229
229
|
|
|
230
230
|
**目的**:检测自上次 read 以来,Rem 是否被外部修改(如用户在 RemNote UI 中编辑、其他 Agent 修改等)。
|
|
231
231
|
|
|
232
|
+
**核心机制**:将 RemObject 所有字段逐个比较,按差异字段所属层级决定处理方式:
|
|
233
|
+
|
|
232
234
|
```
|
|
233
235
|
currentRemObject = forwardToPlugin('read_rem', { remId })
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
{ semantic, warned, ignored } = classifyDiff(cachedObj, currentRemObject)
|
|
237
|
+
|
|
238
|
+
if semantic.length > 0:
|
|
239
|
+
// 第1层:语义字段冲突 → 硬拒绝,不更新缓存
|
|
240
|
+
throw "Rem {remId} has been modified since last read."
|
|
241
|
+
|
|
242
|
+
if warned.length > 0:
|
|
243
|
+
// 第2层:parent 变化 → 放行 + 专门警告
|
|
244
|
+
warnings.push("⚠️ parent has changed (was: {old}, now: {new}). ...")
|
|
236
245
|
|
|
237
|
-
if
|
|
238
|
-
//
|
|
239
|
-
|
|
246
|
+
if ignored.length > 0:
|
|
247
|
+
// 第3层:普通元数据变化 → 放行 + 统一警告
|
|
248
|
+
warnings.push("ℹ️ Metadata fields changed since last read: {fields}. ...")
|
|
249
|
+
|
|
250
|
+
if warned.length > 0 || ignored.length > 0:
|
|
251
|
+
// 静默刷新缓存(保持新鲜度)
|
|
252
|
+
cache.set('rem:' + remId, currentRemObject)
|
|
240
253
|
```
|
|
241
254
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
255
|
+
**三层字段分类**:
|
|
256
|
+
|
|
257
|
+
| 层级 | 包含字段 | 变化时行为 |
|
|
258
|
+
|:-----|:---------|:----------|
|
|
259
|
+
| 第1层:语义字段 | text, backText, type, tags, highlightColor, fontSize 等(不在下面两层的所有字段) | **硬拒绝**,不更新缓存 |
|
|
260
|
+
| 第2层:敏感元数据 | parent | **放行** + `⚠️ parent has changed (was: X, now: Y)...` 警告 + 刷新缓存 |
|
|
261
|
+
| 第3层:普通元数据 | positionAmongstSiblings, updatedAt, siblingRem, children, descendants 等 23 个字段 | **放行** + `ℹ️ Metadata fields changed since last read: ...` 警告 + 刷新缓存 |
|
|
246
262
|
|
|
247
|
-
|
|
263
|
+
**设计意义**:`edit_tree` 移动/重排 Rem 后,可以直接 `edit_rem` 修改受影响节点的文本格式,无需逐个重新 `read_rem`。只有真正的内容/属性并发冲突才会被拦截。
|
|
248
264
|
|
|
249
|
-
|
|
265
|
+
**warnings 输出示例**:
|
|
266
|
+
|
|
267
|
+
| 场景 | warnings |
|
|
268
|
+
|:-----|:---------|
|
|
269
|
+
| 无外部变化 | `[]` |
|
|
270
|
+
| edit_tree 重排后 | `["ℹ️ Metadata fields changed since last read: positionAmongstSiblings. This is expected after structural operations. Proceeding with edit."]` |
|
|
271
|
+
| edit_tree 移动后 | `["⚠️ parent has changed (was: oldId, now: newId). The Rem has been moved to a different parent since last read. Proceeding with edit.", "ℹ️ Metadata fields changed since last read: siblingRem, positionAmongstSiblings, updatedAt. ..."]` |
|
|
272
|
+
| 语义字段被外部修改 | 不返回(抛异常 `"has been modified since last read"`) |
|
|
273
|
+
|
|
274
|
+
**恢复方式**(仅语义冲突需要):执行 `read-rem <remId>` 获取最新状态后重试。
|
|
275
|
+
|
|
276
|
+
### 防线判断树
|
|
250
277
|
|
|
251
278
|
```
|
|
252
279
|
edit-rem(remId, changes)
|
|
@@ -255,9 +282,11 @@ edit-rem(remId, changes)
|
|
|
255
282
|
│ ├─ 否 → ERROR: "has not been read yet"
|
|
256
283
|
│ └─ 是 → 继续
|
|
257
284
|
│
|
|
258
|
-
├─ 防线 2:
|
|
259
|
-
│ ├─
|
|
260
|
-
│
|
|
285
|
+
├─ 防线 2: 三层字段分类比较
|
|
286
|
+
│ ├─ 语义字段变化 → ERROR: "has been modified since last read"
|
|
287
|
+
│ ├─ 仅 parent 变化 → 放行(warnings += ⚠️ parent 警告,刷新缓存)
|
|
288
|
+
│ ├─ 仅元数据变化 → 放行(warnings += ℹ️ 元数据警告,刷新缓存)
|
|
289
|
+
│ └─ 无变化 → 继续
|
|
261
290
|
│
|
|
262
291
|
├─ 字段分类
|
|
263
292
|
│ ├─ 只读字段 → 警告
|
|
@@ -310,9 +310,10 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
310
310
|
│ 适合:首次探索、了解知识库组织
|
|
311
311
|
│
|
|
312
312
|
├─ 用户当前在看什么 → read-context
|
|
313
|
-
│ ├─
|
|
314
|
-
│ │
|
|
315
|
-
│ └─
|
|
313
|
+
│ ├─ page 模式(默认,推荐):以当前打开页面为根展开子树
|
|
314
|
+
│ │ 只需有打开的页面即可,几乎总能成功
|
|
315
|
+
│ └─ focus 模式(仅特定场景):以用户光标所在 Rem 为中心的鱼眼视图
|
|
316
|
+
│ 需用户光标停在某个 Rem 上,否则报错;仅当需要定位光标位置时使用
|
|
316
317
|
│
|
|
317
318
|
├─ 某个具体 Rem 的子树 → read-tree <remId>
|
|
318
319
|
│ 完整展开子树(支持深度/节点预算控制)
|
|
@@ -339,8 +340,8 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
339
340
|
| 场景 | 命令 | 输出特点 |
|
|
340
341
|
|:-----|:-----|:---------|
|
|
341
342
|
| "看看知识库有什么" | `read-globe` | 仅 Document 层级,无内容 Rem |
|
|
342
|
-
| "
|
|
343
|
-
| "
|
|
343
|
+
| "当前页面的内容" | `read-context` | 以页面为根展开(默认,推荐) |
|
|
344
|
+
| "我光标在哪" / "我正在编辑的 Rem" | `read-context --mode focus` | 鱼眼视图(需光标在某 Rem 上,否则报错) |
|
|
344
345
|
| "展开某个主题的细节" | `read-tree <id>` | 完整子树,可缓存供编辑 |
|
|
345
346
|
| "展开子树并查看每个节点属性" | `read-rem-in-tree <id>` | 大纲 + RemObject,双重缓存 |
|
|
346
347
|
|
|
@@ -354,18 +355,25 @@ Agent 需要根据用户意图选择正确的读取命令:
|
|
|
354
355
|
|
|
355
356
|
#### read-context 特性
|
|
356
357
|
|
|
357
|
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
360
|
-
-
|
|
361
|
-
- 焦点 Rem 前标记 `* ` 前缀
|
|
362
|
-
- 输出头部含 path 和 focus 信息
|
|
358
|
+
**模式选择指引**:
|
|
359
|
+
- **绝大多数场景用 page(默认)**——用户通常只是打开页面浏览,不会特意点击某个 Rem 让光标停在上面。page 只需有打开的页面就能工作,几乎总能成功
|
|
360
|
+
- **仅当需要定位用户光标时用 focus**——如用户说"我正在编辑的这个"、"光标所在的 Rem"。focus 要求光标停在某个 Rem 上,否则报错"当前没有聚焦的 Rem"
|
|
361
|
+
- **不确定时用 page**——最坏情况只是展开整个页面;focus 的最坏情况是报错
|
|
363
362
|
|
|
364
|
-
**page
|
|
363
|
+
**page 模式**(默认,推荐):
|
|
365
364
|
- 获取当前面板打开的页面 Rem
|
|
366
365
|
- 以该页面为根展开子树
|
|
367
366
|
- 参数:`depth`(默认 3)、`maxNodes`、`maxSiblings`
|
|
368
367
|
- 输出头部含 page 名和 breadcrumb path
|
|
368
|
+
- 前置条件宽松:只需有打开的页面
|
|
369
|
+
|
|
370
|
+
**focus 模式**(仅特定场景):
|
|
371
|
+
- 获取用户当前聚焦的 Rem(需要用户在 RemNote 中点击某个 Rem)
|
|
372
|
+
- 鱼眼视图展开策略:焦点 depth=3,焦点的 siblings depth=1,叔伯节点 depth=0
|
|
373
|
+
- 向上追溯 `ancestorLevels` 层(默认 2)
|
|
374
|
+
- 焦点 Rem 前标记 `* ` 前缀
|
|
375
|
+
- 输出头部含 path 和 focus 信息
|
|
376
|
+
- ⚠️ 前置条件严格:用户光标必须停在某个 Rem 上,否则报错
|
|
369
377
|
|
|
370
378
|
两者共同点:不缓存、Portal 感知、Powerup 噪音过滤、返回 breadcrumb。
|
|
371
379
|
|
|
@@ -613,7 +621,7 @@ Portal:portalType [R], portalDirectlyIncludedRem [Portal-W]
|
|
|
613
621
|
|
|
614
622
|
`h` 颜色值:0=无, 1=Red, 2=Orange, 3=Yellow, 4=Green, 5=Purple, 6=Blue, 7=Gray, 8=Brown, 9=Pink。
|
|
615
623
|
|
|
616
|
-
**序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F
|
|
624
|
+
**序列化确定性**:RichText 对象内部按 key 字母序排列(`sortRichTextKeys()`)。`_id` 的 `_`(U+005F)排在所有小写字母之前。这对语义并发检测和 edit-tree 的 str_replace 至关重要。
|
|
617
625
|
|
|
618
626
|
---
|
|
619
627
|
|
|
@@ -749,13 +757,18 @@ read-tree / read-globe / read-context 的输出核心是 Markdown 大纲文本
|
|
|
749
757
|
|
|
750
758
|
未缓存的 Rem 不允许编辑。确保 Agent 看到的数据和即将编辑的数据是同一份。
|
|
751
759
|
|
|
752
|
-
#### 防线 2
|
|
760
|
+
#### 防线 2:语义并发检测
|
|
753
761
|
|
|
754
762
|
```
|
|
755
|
-
edit 时重新从 SDK 读取最新数据 →
|
|
763
|
+
edit 时重新从 SDK 读取最新数据 → 只比较语义字段(内容、类型、格式、标签等)
|
|
756
764
|
```
|
|
757
765
|
|
|
758
|
-
|
|
766
|
+
将 RemObject 字段分为三层:
|
|
767
|
+
- **语义字段**(text, backText, type, tags, highlightColor, fontSize 等):变化 → 硬拒绝,迫使 Agent 重新 read
|
|
768
|
+
- **敏感元数据**(parent):变化 → 放行 + `"⚠️ parent has changed (was: oldId, now: newId). The Rem has been moved to a different parent since last read. Proceeding with edit."`
|
|
769
|
+
- **普通元数据**(positionAmongstSiblings, updatedAt, siblingRem, children, descendants 等):变化 → 放行 + `"ℹ️ Metadata fields changed since last read: {fields}. This is expected after structural operations. Proceeding with edit."`
|
|
770
|
+
|
|
771
|
+
这意味着 `edit-tree` 移动/重排 Rem 后,可以直接 `edit-rem` 修改受影响节点的文本格式,无需逐个重新 `read-rem`。只有真正的内容/属性并发冲突才会被拦截。
|
|
759
772
|
|
|
760
773
|
#### edit-rem 防线 3:字段白名单校验
|
|
761
774
|
|
|
@@ -781,7 +794,8 @@ oldStr 必须在目标文本中恰好匹配 1 次
|
|
|
781
794
|
| 场景 | 缓存行为 |
|
|
782
795
|
|:-----|:---------|
|
|
783
796
|
| 写入全部成功 | 从 SDK 重新读取最新状态 → **更新缓存** |
|
|
784
|
-
|
|
|
797
|
+
| 仅元数据变化(位置/时间戳等) | **静默刷新缓存并放行**(返回警告) |
|
|
798
|
+
| 语义字段冲突(内容/类型/格式等) | **不更新缓存**(迫使 Agent 重新 read) |
|
|
785
799
|
| 枚举值非法 | **报错拒绝**,不更新缓存 |
|
|
786
800
|
| 部分写入失败 | **不更新缓存** |
|
|
787
801
|
|
|
@@ -859,7 +873,7 @@ RemNote 的格式设置通过 Powerup 机制实现,会向 Rem 注入隐藏的
|
|
|
859
873
|
"readTreeAncestorLevels": 0,
|
|
860
874
|
"readTreeIncludePowerup": false,
|
|
861
875
|
"readGlobeDepth": -1,
|
|
862
|
-
"readContextMode": "
|
|
876
|
+
"readContextMode": "page",
|
|
863
877
|
"readContextAncestorLevels": 2,
|
|
864
878
|
"readContextDepth": 3,
|
|
865
879
|
"searchNumResults": 20
|
|
@@ -903,7 +917,7 @@ Agent 遇到错误时的诊断和恢复指南:
|
|
|
903
917
|
| 错误 | 原因 | 恢复 |
|
|
904
918
|
|:-----|:-----|:-----|
|
|
905
919
|
| has not been read yet | 未先执行 read-rem / read-tree | 执行对应 read 命令后重试 |
|
|
906
|
-
| has been modified since last read | Rem
|
|
920
|
+
| has been modified since last read | Rem 的语义字段在 read 和 edit 之间被外部修改 | 重新执行 read 获取最新状态后重试 |
|
|
907
921
|
|
|
908
922
|
### edit-tree str_replace 错误
|
|
909
923
|
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
`read-context` 根据用户在 RemNote 中的当前位置,生成上下文感知的 Markdown 大纲。两种模式适用于不同场景:
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
11
|
+
- **page 模式**(默认,推荐):以当前打开的页面 Rem 为根,均匀展开子树。只需有打开的页面即可,几乎总能成功
|
|
12
|
+
- **focus 模式**(仅特定场景):以当前焦点 Rem 为中心,向上追溯祖先,构建鱼眼视图——焦点完全展开,周围递减。需用户光标停在某个 Rem 上,否则报错
|
|
13
|
+
|
|
14
|
+
**何时用哪个模式**:
|
|
15
|
+
- 绝大多数场景用 page(默认)——用户通常只是打开页面浏览,不会特意让光标停在某个 Rem 上
|
|
16
|
+
- 仅当需要知道用户光标具体在哪个 Rem 上时才用 focus——如用户说"我正在编辑的这个"、"光标所在位置"
|
|
17
|
+
- 不确定时用 page——最坏情况只是展开整个页面;focus 的最坏情况是报错"当前没有聚焦的 Rem"
|
|
13
18
|
|
|
14
19
|
核心能力:
|
|
15
20
|
- 鱼眼深度梯度(focus 模式):焦点 depth=3、siblings depth=1、叔伯 depth=0
|
|
@@ -32,7 +37,7 @@ remnote-bridge read-context [--mode <mode>] [--ancestor-levels <N>] [--depth <N>
|
|
|
32
37
|
|
|
33
38
|
| 参数/选项 | 类型 | 必需 | 说明 |
|
|
34
39
|
|-----------|------|:----:|------|
|
|
35
|
-
| `--mode <mode>` | string | 否 | 模式:`
|
|
40
|
+
| `--mode <mode>` | string | 否 | 模式:`page`(默认)或 `focus` |
|
|
36
41
|
| `--ancestor-levels <N>` | integer | 否 | 向上追溯几层祖先(默认 2,仅 focus 模式) |
|
|
37
42
|
| `--depth <N>` | integer | 否 | 展开深度(默认 3,仅 page 模式) |
|
|
38
43
|
| `--max-nodes <N>` | integer | 否 | 全局节点上限(默认 200) |
|
|
@@ -84,7 +89,7 @@ remnote-bridge read-context --json '{"mode":"page","depth":5,"maxSiblings":10}'
|
|
|
84
89
|
|
|
85
90
|
| 字段 | 类型 | 必需 | 说明 |
|
|
86
91
|
|------|------|:----:|------|
|
|
87
|
-
| `mode` | string | 否 | 模式:`"
|
|
92
|
+
| `mode` | string | 否 | 模式:`"page"` 或 `"focus"`(默认 `"page"`) |
|
|
88
93
|
| `ancestorLevels` | number | 否 | 向上追溯几层祖先(默认 2,仅 focus 模式) |
|
|
89
94
|
| `depth` | number | 否 | 展开深度(默认 3,仅 page 模式) |
|
|
90
95
|
| `maxNodes` | number | 否 | 全局节点上限(默认 200) |
|
|
@@ -359,7 +364,7 @@ focus 模式的核心特点是**渐进式展开**——离焦点越近,展开
|
|
|
359
364
|
|
|
360
365
|
| 配置项 | 默认值 | 说明 |
|
|
361
366
|
|--------|--------|------|
|
|
362
|
-
| `defaults.readContextMode` | `"
|
|
367
|
+
| `defaults.readContextMode` | `"page"` | 默认模式 |
|
|
363
368
|
| `defaults.readContextAncestorLevels` | 2 | focus 模式默认祖先层数 |
|
|
364
369
|
| `defaults.readContextDepth` | 3 | page 模式默认展开深度 |
|
|
365
370
|
| `defaults.maxNodes` | 200 | 默认全局节点上限 |
|