agent-remnote 0.0.1 → 0.0.2

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.
Files changed (102) hide show
  1. package/cli.js +2 -0
  2. package/dist/apps/cli/src/adapters/mcp.js +1 -0
  3. package/dist/apps/cli/src/commands/_enqueue.js +138 -0
  4. package/dist/apps/cli/src/commands/_shared.js +57 -0
  5. package/dist/apps/cli/src/commands/_tool.js +28 -0
  6. package/dist/apps/cli/src/commands/apply.js +81 -0
  7. package/dist/apps/cli/src/commands/config/index.js +3 -0
  8. package/dist/apps/cli/src/commands/config/print.js +28 -0
  9. package/dist/apps/cli/src/commands/daily/index.js +4 -0
  10. package/dist/apps/cli/src/commands/daily/summary.js +25 -0
  11. package/dist/apps/cli/src/commands/daily/write.js +145 -0
  12. package/dist/apps/cli/src/commands/db/backups.js +23 -0
  13. package/dist/apps/cli/src/commands/db/index.js +4 -0
  14. package/dist/apps/cli/src/commands/db/recent.js +178 -0
  15. package/dist/apps/cli/src/commands/doctor.js +124 -0
  16. package/dist/apps/cli/src/commands/index.js +73 -0
  17. package/dist/apps/cli/src/commands/ops/index.js +4 -0
  18. package/dist/apps/cli/src/commands/ops/list.js +12 -0
  19. package/dist/apps/cli/src/commands/ops/schema.js +77 -0
  20. package/dist/apps/cli/src/commands/queue/enqueue.js +73 -0
  21. package/dist/apps/cli/src/commands/queue/index.js +5 -0
  22. package/dist/apps/cli/src/commands/queue/inspect.js +26 -0
  23. package/dist/apps/cli/src/commands/queue/stats.js +14 -0
  24. package/dist/apps/cli/src/commands/read/by-reference.js +35 -0
  25. package/dist/apps/cli/src/commands/read/connections.js +15 -0
  26. package/dist/apps/cli/src/commands/read/index.js +21 -0
  27. package/dist/apps/cli/src/commands/read/inspect.js +34 -0
  28. package/dist/apps/cli/src/commands/read/outline.js +59 -0
  29. package/dist/apps/cli/src/commands/read/query.js +95 -0
  30. package/dist/apps/cli/src/commands/read/references.js +41 -0
  31. package/dist/apps/cli/src/commands/read/resolve-ref.js +32 -0
  32. package/dist/apps/cli/src/commands/read/search.js +40 -0
  33. package/dist/apps/cli/src/commands/read/table.js +32 -0
  34. package/dist/apps/cli/src/commands/todos/index.js +3 -0
  35. package/dist/apps/cli/src/commands/todos/list.js +33 -0
  36. package/dist/apps/cli/src/commands/topic/index.js +3 -0
  37. package/dist/apps/cli/src/commands/topic/summary.js +44 -0
  38. package/dist/apps/cli/src/commands/wechat/index.js +3 -0
  39. package/dist/apps/cli/src/commands/wechat/outline.js +430 -0
  40. package/dist/apps/cli/src/commands/write/bullet.js +76 -0
  41. package/dist/apps/cli/src/commands/write/index.js +4 -0
  42. package/dist/apps/cli/src/commands/write/md.js +91 -0
  43. package/dist/apps/cli/src/commands/ws/_shared.js +129 -0
  44. package/dist/apps/cli/src/commands/ws/ensure.js +22 -0
  45. package/dist/apps/cli/src/commands/ws/health.js +15 -0
  46. package/dist/apps/cli/src/commands/ws/index.js +21 -0
  47. package/dist/apps/cli/src/commands/ws/logs.js +95 -0
  48. package/dist/apps/cli/src/commands/ws/restart.js +73 -0
  49. package/dist/apps/cli/src/commands/ws/serve.js +52 -0
  50. package/dist/apps/cli/src/commands/ws/start.js +70 -0
  51. package/dist/apps/cli/src/commands/ws/status.js +60 -0
  52. package/dist/apps/cli/src/commands/ws/stop.js +59 -0
  53. package/dist/apps/cli/src/commands/ws/trigger.js +20 -0
  54. package/dist/apps/cli/src/main.js +79 -0
  55. package/dist/apps/cli/src/services/AppConfig.js +3 -0
  56. package/dist/apps/cli/src/services/Config.js +91 -0
  57. package/dist/apps/cli/src/services/DaemonFiles.js +91 -0
  58. package/dist/apps/cli/src/services/Errors.js +49 -0
  59. package/dist/apps/cli/src/services/Output.js +16 -0
  60. package/dist/apps/cli/src/services/Payload.js +90 -0
  61. package/dist/apps/cli/src/services/Process.js +94 -0
  62. package/dist/apps/cli/src/services/Queue.js +120 -0
  63. package/dist/apps/cli/src/services/RefResolver.js +111 -0
  64. package/dist/apps/cli/src/services/RemDb.js +35 -0
  65. package/dist/apps/cli/src/services/WsClient.js +170 -0
  66. package/dist/apps/cli/tests/apply.contract.test.js +31 -0
  67. package/dist/apps/cli/tests/db-recent.contract.test.js +22 -0
  68. package/dist/apps/cli/tests/help.contract.test.js +30 -0
  69. package/dist/apps/cli/tests/helpers/runCli.js +45 -0
  70. package/dist/apps/cli/tests/ids-output.contract.test.js +30 -0
  71. package/dist/apps/cli/tests/payload-stdin.contract.test.js +15 -0
  72. package/dist/apps/cli/tests/read-search.contract.test.js +22 -0
  73. package/dist/apps/cli/tests/ws-health.contract.test.js +36 -0
  74. package/dist/apps/cli/vitest.config.js +7 -0
  75. package/dist/main.js +100985 -0
  76. package/dist/packages/mcp/src/public.js +18 -0
  77. package/dist/packages/mcp/src/queue/dao.js +165 -0
  78. package/dist/packages/mcp/src/queue/db.js +26 -0
  79. package/dist/packages/mcp/src/tools/executeSearchQuery.js +914 -0
  80. package/dist/packages/mcp/src/tools/findRemsByReference.js +447 -0
  81. package/dist/packages/mcp/src/tools/getRemConnections.js +566 -0
  82. package/dist/packages/mcp/src/tools/inspectRemDoc.js +60 -0
  83. package/dist/packages/mcp/src/tools/listRemBackups.js +35 -0
  84. package/dist/packages/mcp/src/tools/listRemReferences.js +421 -0
  85. package/dist/packages/mcp/src/tools/listSupportedOps.js +41 -0
  86. package/dist/packages/mcp/src/tools/listTodos.js +815 -0
  87. package/dist/packages/mcp/src/tools/outlineRemSubtree.js +203 -0
  88. package/dist/packages/mcp/src/tools/readRemTable.js +252 -0
  89. package/dist/packages/mcp/src/tools/resolveRemReference.js +174 -0
  90. package/dist/packages/mcp/src/tools/searchQueryTypes.js +127 -0
  91. package/dist/packages/mcp/src/tools/searchRemOverview.js +422 -0
  92. package/dist/packages/mcp/src/tools/searchUtils.js +32 -0
  93. package/dist/packages/mcp/src/tools/shared.js +393 -0
  94. package/dist/packages/mcp/src/tools/summarizeDailyNotes.js +221 -0
  95. package/dist/packages/mcp/src/tools/summarizeTopicActivity.js +605 -0
  96. package/dist/packages/mcp/src/tools/timeFilters.js +130 -0
  97. package/dist/packages/mcp/src/ws/bridge.js +377 -0
  98. package/package.json +40 -8
  99. package/README.md +0 -3
  100. package/dist/index.d.ts +0 -2
  101. package/dist/index.d.ts.map +0 -1
  102. package/dist/index.js +0 -5
@@ -0,0 +1,447 @@
1
+ import { z } from "zod";
2
+ import { buildGuidedResponse, withResolvedDatabase, parseOrThrow, } from "./shared.js";
3
+ import { TIME_RANGE_PATTERN, timeValueSchema, resolveTimeFilters, describeFilterSummary, } from "./timeFilters.js";
4
+ import { createPreview, coalesceText } from "./searchUtils.js";
5
+ const inputShape = {
6
+ targetIds: z
7
+ .array(z.string().min(1))
8
+ .min(1, "targetIds 至少包含 1 个元素")
9
+ .describe("目标锚点 Rem ID 列表(被引用的对象)"),
10
+ maxDepth: z
11
+ .number()
12
+ .int()
13
+ .min(1)
14
+ .max(3)
15
+ .optional()
16
+ .describe("最大引用层级(默认 1,建议 ≤2)"),
17
+ limit: z
18
+ .number()
19
+ .int()
20
+ .min(1)
21
+ .max(200)
22
+ .optional()
23
+ .describe("返回条数上限(分页)"),
24
+ offset: z
25
+ .number()
26
+ .int()
27
+ .min(0)
28
+ .optional()
29
+ .describe("分页偏移量"),
30
+ maxCandidates: z
31
+ .number()
32
+ .int()
33
+ .min(1)
34
+ .max(1000)
35
+ .optional()
36
+ .describe("候选上限(控制搜索规模,默认 200)"),
37
+ timeRange: z
38
+ .union([
39
+ z.literal("all"),
40
+ z.literal("*"),
41
+ z.string().regex(TIME_RANGE_PATTERN, "timeRange 需形如 '30d'、'2w'、'12h'"),
42
+ ])
43
+ .optional()
44
+ .describe("时间范围(如 30d/2w/12h 或 all/*)"),
45
+ createdAfter: timeValueSchema.optional().describe("创建时间下界(ISO/毫秒/秒)"),
46
+ createdBefore: timeValueSchema.optional().describe("创建时间上界(ISO/毫秒/秒)"),
47
+ updatedAfter: timeValueSchema.optional().describe("更新时间下界(ISO/毫秒/秒)"),
48
+ updatedBefore: timeValueSchema.optional().describe("更新时间上界(ISO/毫秒/秒)"),
49
+ autoExpandDepth: z
50
+ .boolean()
51
+ .optional()
52
+ .describe("未命中时自动逐级提高深度(默认 true)"),
53
+ maxAutoDepth: z
54
+ .number()
55
+ .int()
56
+ .min(1)
57
+ .max(3)
58
+ .optional()
59
+ .describe("自动扩展深度上限(默认 3)"),
60
+ dbPath: z.string().optional().describe("数据库文件路径(默认自动发现)"),
61
+ detail: z.boolean().optional().describe("是否返回更详细字段(parentId/ancestorIds 等)"),
62
+ };
63
+ export const findRemsByReferenceSchema = z.object(inputShape);
64
+ export async function executeFindRemsByReference(params) {
65
+ const parsed = parseOrThrow(findRemsByReferenceSchema, params, { label: "find_rems_by_reference" });
66
+ const limit = parsed.limit ?? 40;
67
+ const offset = parsed.offset ?? 0;
68
+ const requestedDepth = parsed.maxDepth ?? 1;
69
+ const autoExpand = parsed.autoExpandDepth ?? true;
70
+ const maxAutoDepth = parsed.maxAutoDepth ?? 3;
71
+ const depthCeiling = Math.min(Math.max(requestedDepth, maxAutoDepth), 3);
72
+ const maxCandidates = parsed.maxCandidates ?? 200;
73
+ const detail = parsed.detail ?? false;
74
+ const { filters, summary } = resolveTimeFilters(parsed);
75
+ const { result, info } = await withResolvedDatabase(parsed.dbPath, async (db) => {
76
+ let matches = new Map();
77
+ const depthAttempts = [];
78
+ let appliedDepth = requestedDepth;
79
+ for (let depth = requestedDepth; depth <= depthCeiling; depth++) {
80
+ depthAttempts.push(depth);
81
+ matches = collectReferenceMatches(db, {
82
+ targetIds: parsed.targetIds,
83
+ filters,
84
+ maxDepth: depth,
85
+ maxCandidates,
86
+ });
87
+ appliedDepth = depth;
88
+ if (matches.size > 0 || !autoExpand) {
89
+ break;
90
+ }
91
+ }
92
+ const all = Array.from(matches.values());
93
+ all.sort((a, b) => {
94
+ const diff = (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
95
+ if (diff !== 0)
96
+ return diff;
97
+ return a.id.localeCompare(b.id);
98
+ });
99
+ const sliced = all.slice(offset, offset + limit);
100
+ const hasMore = offset + sliced.length < all.length;
101
+ const nextOffset = hasMore ? offset + sliced.length : null;
102
+ const snippetLength = 200;
103
+ const items = sliced.map((match) => {
104
+ const preview = createPreview(match.text, snippetLength);
105
+ const anchors = Array.from(match.anchors);
106
+ const sources = Array.from(match.sources);
107
+ return {
108
+ id: match.id,
109
+ title: preview.title,
110
+ snippet: preview.snippet,
111
+ truncated: preview.truncated,
112
+ parentId: match.parentId,
113
+ ancestor: match.ancestorText,
114
+ ancestorIds: match.ancestorIds?.split(/\s+/).filter(Boolean) ?? [],
115
+ matchedTargets: match.matchedTargetIds,
116
+ anchorIds: anchors,
117
+ sourceIds: sources,
118
+ depth: match.depth,
119
+ updatedAt: match.updatedAt,
120
+ createdAt: match.createdAt,
121
+ };
122
+ });
123
+ return {
124
+ items,
125
+ total: all.length,
126
+ hasMore,
127
+ nextOffset,
128
+ appliedDepth,
129
+ depthAttempts,
130
+ };
131
+ });
132
+ const guidance = result.total > 0
133
+ ? `共找到 ${result.total} 条引用匹配(${describeFilterSummary(summary) ?? "无额外筛选"})。`
134
+ : `未找到符合条件的引用匹配(${describeFilterSummary(summary) ?? "无筛选"})。`;
135
+ const matches = detail ? result.items : result.items.map((item) => simplifyMatch(item));
136
+ const referenceIndex = buildReferenceIndex(result.items);
137
+ return {
138
+ dbPath: info.dbPath,
139
+ resolution: info.source,
140
+ dirName: info.dirName,
141
+ targetIds: params.targetIds,
142
+ depthRequested: requestedDepth,
143
+ depthApplied: result.appliedDepth,
144
+ depthAttempts: result.depthAttempts,
145
+ autoExpandDepth: autoExpand,
146
+ depthLimit: depthCeiling,
147
+ maxCandidates,
148
+ limit,
149
+ offset,
150
+ hasMore: result.hasMore,
151
+ nextOffset: result.nextOffset,
152
+ totalCount: result.total,
153
+ filtersApplied: summary,
154
+ count: matches.length,
155
+ matches,
156
+ markdown: buildMatchesMarkdown(matches),
157
+ referenceIndex,
158
+ guidance,
159
+ };
160
+ }
161
+ export function registerFindRemsByReference(server) {
162
+ server.tool("find_rems_by_reference", `<usecase>查找引用指定 Rem 的节点,可探索隐性关联。</usecase>
163
+ <instructions>
164
+ - targetIds 允许一次传入多个锚点 Rem。
165
+ - maxDepth 控制引用层级,默认仅检索直接引用;建议≤2 以避免过多结果。
166
+ - 可结合 timeRange/updatedAfter 等参数限定时间窗口。
167
+ - 如需进一步阅读结果,可继续使用 outline_rem_subtree 或 inspect_rem_doc。
168
+ </instructions>`, inputShape, async (input) => {
169
+ const parsed = findRemsByReferenceSchema.parse(input);
170
+ const result = await executeFindRemsByReference(parsed);
171
+ return buildGuidedResponse(result, buildSuggestions({
172
+ total: result.totalCount,
173
+ hasMore: result.hasMore,
174
+ nextOffset: result.nextOffset,
175
+ depthApplied: result.depthApplied,
176
+ depthLimit: result.depthLimit,
177
+ autoExpand: result.autoExpandDepth,
178
+ }, parsed.targetIds));
179
+ });
180
+ }
181
+ function simplifyMatch(item) {
182
+ return {
183
+ id: item.id,
184
+ title: item.title,
185
+ snippet: item.snippet,
186
+ truncated: item.truncated,
187
+ ancestor: item.ancestor,
188
+ matchedTargets: item.matchedTargets ?? [],
189
+ anchorIds: item.anchorIds ?? [],
190
+ depth: item.depth ?? 0,
191
+ };
192
+ }
193
+ function buildMatchesMarkdown(matches) {
194
+ if (!matches || matches.length === 0) {
195
+ return "未找到引用匹配。";
196
+ }
197
+ const lines = ["# 引用匹配概览"];
198
+ matches.forEach((match, index) => {
199
+ const title = match.title?.trim() ? match.title.trim() : "(未命名)";
200
+ const ancestor = match.ancestor ? `,所在:${match.ancestor}` : "";
201
+ const targets = match.matchedTargets.length > 0 ? `,目标:${match.matchedTargets.join(", ")}` : "";
202
+ lines.push(`- **${index + 1}. ${title}**(ID: ${match.id}${ancestor},深度 ${match.depth}${targets})`);
203
+ if (match.snippet) {
204
+ lines.push(` - ${match.snippet}`);
205
+ }
206
+ });
207
+ return lines.join("\n");
208
+ }
209
+ function buildReferenceIndex(matches) {
210
+ const index = new Map();
211
+ if (!Array.isArray(matches)) {
212
+ return {};
213
+ }
214
+ for (const match of matches) {
215
+ const sample = {
216
+ viaRemId: match.id,
217
+ viaRemTitle: match.title,
218
+ snippet: match.snippet,
219
+ depth: match.depth ?? 0,
220
+ };
221
+ const register = (id) => {
222
+ if (!id)
223
+ return;
224
+ const entry = index.get(id);
225
+ if (entry) {
226
+ entry.totalOccurrences += 1;
227
+ if (entry.samples.length < 5) {
228
+ entry.samples.push(sample);
229
+ }
230
+ }
231
+ else {
232
+ index.set(id, {
233
+ totalOccurrences: 1,
234
+ samples: [sample],
235
+ });
236
+ }
237
+ };
238
+ for (const target of match.matchedTargets ?? []) {
239
+ register(target);
240
+ }
241
+ for (const anchor of match.anchorIds ?? []) {
242
+ register(anchor);
243
+ }
244
+ }
245
+ const result = {};
246
+ for (const [id, entry] of index.entries()) {
247
+ result[id] = entry;
248
+ }
249
+ return result;
250
+ }
251
+ function collectReferenceMatches(db, options) {
252
+ const visited = new Set(options.targetIds);
253
+ const anchorMap = new Map();
254
+ for (const id of options.targetIds) {
255
+ anchorMap.set(id, new Set([id]));
256
+ }
257
+ const results = new Map();
258
+ let depth = 1;
259
+ let frontier = new Set(options.targetIds);
260
+ while (frontier.size > 0 && depth <= options.maxDepth && results.size < options.maxCandidates) {
261
+ const targets = Array.from(frontier);
262
+ frontier = new Set();
263
+ const rows = queryDirectReferences(db, targets, options.filters);
264
+ for (const row of rows) {
265
+ if (row.id === undefined)
266
+ continue;
267
+ const sources = row.matchedTargetIds;
268
+ const anchorSets = sources.map((sourceId) => anchorMap.get(sourceId) ?? new Set([sourceId]));
269
+ const anchors = mergeSets(anchorSets);
270
+ const existing = results.get(row.id);
271
+ if (existing) {
272
+ existing.depth = Math.min(existing.depth, depth);
273
+ existing.anchors = mergeSets([existing.anchors, anchors]);
274
+ existing.sources = mergeSets([existing.sources, new Set(sources)]);
275
+ existing.matchedTargetIds = Array.from(new Set([...existing.matchedTargetIds, ...row.matchedTargetIds]));
276
+ existing.updatedAt = chooseLatest(existing.updatedAt, row.updatedAt);
277
+ existing.createdAt = chooseLatest(existing.createdAt, row.createdAt);
278
+ }
279
+ else {
280
+ results.set(row.id, {
281
+ ...row,
282
+ depth,
283
+ anchors,
284
+ sources: new Set(sources),
285
+ });
286
+ }
287
+ if (!visited.has(row.id)) {
288
+ visited.add(row.id);
289
+ frontier.add(row.id);
290
+ if (!anchorMap.has(row.id)) {
291
+ anchorMap.set(row.id, anchors);
292
+ }
293
+ else {
294
+ anchorMap.set(row.id, mergeSets([anchorMap.get(row.id) ?? new Set(), anchors]));
295
+ }
296
+ }
297
+ if (results.size >= options.maxCandidates) {
298
+ break;
299
+ }
300
+ }
301
+ if (results.size >= options.maxCandidates) {
302
+ break;
303
+ }
304
+ depth += 1;
305
+ }
306
+ return results;
307
+ }
308
+ function queryDirectReferences(db, targetIds, filters) {
309
+ if (targetIds.length === 0)
310
+ return [];
311
+ const targetsJson = JSON.stringify(targetIds);
312
+ const { clause, params } = buildQuantaTimeFilterClause(filters, "q");
313
+ const sql = `WITH targets AS (
314
+ SELECT value AS targetId FROM json_each(@targetsJson)
315
+ ),
316
+ refs AS (
317
+ SELECT
318
+ q._id AS id,
319
+ targets.targetId AS targetId,
320
+ CAST(json_extract(q.doc, '$.lm') AS INTEGER) AS updatedAt,
321
+ CAST(json_extract(q.doc, '$.ct') AS INTEGER) AS createdAt
322
+ FROM quanta q
323
+ JOIN targets
324
+ WHERE q._id != targets.targetId
325
+ AND json_valid(q.doc)
326
+ AND (
327
+ (
328
+ json_extract(q.doc, '$.key') IS NOT NULL
329
+ AND (
330
+ (instr(json_extract(q.doc, '$.key'), '"i":"q"') > 0 OR instr(json_extract(q.doc, '$.key'), '"i":"p"') > 0)
331
+ AND instr(json_extract(q.doc, '$.key'), '"_id":"' || targets.targetId || '"') > 0
332
+ )
333
+ )
334
+ OR (
335
+ json_type(json_extract(q.doc, '$.value')) IS NOT NULL
336
+ AND (
337
+ (instr(json_extract(q.doc, '$.value'), '"i":"q"') > 0 OR instr(json_extract(q.doc, '$.value'), '"i":"p"') > 0)
338
+ AND instr(json_extract(q.doc, '$.value'), '"_id":"' || targets.targetId || '"') > 0
339
+ )
340
+ )
341
+ )
342
+ ${clause ? `AND ${clause}` : ""}
343
+ )
344
+ SELECT
345
+ refs.id AS id,
346
+ json_extract(rsi.doc, '$.p') AS parentId,
347
+ rsi.ancestor_not_ref_text AS ancestorText,
348
+ rsi.ancestor_ids AS ancestorIds,
349
+ json_extract(rsi.doc, '$.kt') AS kt,
350
+ json_extract(rsi.doc, '$.ke') AS ke,
351
+ GROUP_CONCAT(DISTINCT refs.targetId) AS matchedTargetIds,
352
+ MAX(refs.updatedAt) AS updatedAt,
353
+ MAX(refs.createdAt) AS createdAt
354
+ FROM refs
355
+ JOIN remsSearchInfos rsi ON rsi.id = refs.id
356
+ GROUP BY refs.id`;
357
+ const rows = db
358
+ .prepare(sql)
359
+ .all({ targetsJson, ...params });
360
+ return rows.map((row) => ({
361
+ id: row.id,
362
+ parentId: row.parentId,
363
+ ancestorText: row.ancestorText,
364
+ ancestorIds: row.ancestorIds,
365
+ text: coalesceText(row.kt, row.ke),
366
+ matchedTargetIds: row.matchedTargetIds
367
+ ? row.matchedTargetIds.split(",").map((value) => value.trim()).filter(Boolean)
368
+ : [],
369
+ updatedAt: row.updatedAt,
370
+ createdAt: row.createdAt,
371
+ }));
372
+ }
373
+ function buildQuantaTimeFilterClause(filters, alias) {
374
+ const conditions = [];
375
+ const params = {};
376
+ const createdExpr = `COALESCE(
377
+ CAST(json_extract(${alias}.doc, '$.createdAt') AS INTEGER),
378
+ CAST(json_extract(${alias}.doc, '$.c') AS INTEGER)
379
+ )`;
380
+ const updatedExpr = `COALESCE(
381
+ CAST(json_extract(${alias}.doc, '$.m') AS INTEGER),
382
+ CAST(json_extract(${alias}.doc, '$.createdAt') AS INTEGER)
383
+ )`;
384
+ if (filters.createdAfter !== undefined) {
385
+ conditions.push(`${createdExpr} >= @createdAfter`);
386
+ params.createdAfter = filters.createdAfter;
387
+ }
388
+ if (filters.createdBefore !== undefined) {
389
+ conditions.push(`${createdExpr} <= @createdBefore`);
390
+ params.createdBefore = filters.createdBefore;
391
+ }
392
+ if (filters.updatedAfter !== undefined) {
393
+ conditions.push(`${updatedExpr} >= @updatedAfter`);
394
+ params.updatedAfter = filters.updatedAfter;
395
+ }
396
+ if (filters.updatedBefore !== undefined) {
397
+ conditions.push(`${updatedExpr} <= @updatedBefore`);
398
+ params.updatedBefore = filters.updatedBefore;
399
+ }
400
+ return {
401
+ clause: conditions.join(" AND "),
402
+ params,
403
+ };
404
+ }
405
+ function mergeSets(sets) {
406
+ const result = new Set();
407
+ for (const set of sets) {
408
+ if (!set)
409
+ continue;
410
+ for (const value of set) {
411
+ result.add(value);
412
+ }
413
+ }
414
+ return result;
415
+ }
416
+ function chooseLatest(current, incoming) {
417
+ if (incoming == null)
418
+ return current ?? null;
419
+ if (current == null)
420
+ return incoming;
421
+ return Math.max(current, incoming);
422
+ }
423
+ function buildSuggestions(result, targetIds) {
424
+ const suggestions = [];
425
+ if (result.hasMore && result.nextOffset != null) {
426
+ suggestions.push(`还有更多引用匹配,可再次调用 find_rems_by_reference 并设置 offset=${result.nextOffset}`);
427
+ }
428
+ if (result.total === 0) {
429
+ if (result.depthApplied < result.depthLimit) {
430
+ suggestions.unshift(`未命中引用,可尝试提高 maxDepth 至 ${result.depthApplied + 1}(或开启 autoExpandDepth)`);
431
+ }
432
+ else if (!result.autoExpand && result.depthApplied < 3) {
433
+ suggestions.unshift(`已使用 maxDepth=${result.depthApplied},可开启 autoExpandDepth 或手动提高至 ${Math.min(result.depthApplied + 1, 3)}`);
434
+ }
435
+ else {
436
+ suggestions.unshift("未找到引用,可尝试放宽时间范围或更换锚点 Rem");
437
+ }
438
+ }
439
+ else {
440
+ if (result.depthApplied < result.depthLimit) {
441
+ suggestions.push(`需要更广范围时,可将 maxDepth 提至 ${result.depthApplied + 1}(当前尝试深度 ${result.depthApplied})`);
442
+ }
443
+ }
444
+ suggestions.push(`若需阅读详细内容,可调用 outline_rem_subtree id=<remId> 或 inspect_rem_doc`);
445
+ suggestions.push(`如需更深层引用,可适当调大 maxDepth(当前锚点:${targetIds.join(", ")})`);
446
+ return suggestions;
447
+ }