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,605 @@
1
+ import { z } from "zod";
2
+ import { buildGuidedResponse, parseOrThrow } from "./shared.js";
3
+ import { executeSearchRemOverview } from "./searchRemOverview.js";
4
+ import { executeOutlineRemSubtree } from "./outlineRemSubtree.js";
5
+ import { TIME_RANGE_PATTERN, timeValueSchema, resolveTimeFilters, describeFilterSummary, } from "./timeFilters.js";
6
+ import { executeFindRemsByReference, } from "./findRemsByReference.js";
7
+ const inputShape = {
8
+ keywords: z
9
+ .array(z.string().min(1))
10
+ .min(1)
11
+ .optional()
12
+ .describe("关键词数组,与 query 二选一"),
13
+ query: z.string().min(1).optional().describe("搜索查询字符串,与 keywords 二选一"),
14
+ timeRange: z
15
+ .union([
16
+ z.literal("all"),
17
+ z.literal("*"),
18
+ z.string().regex(TIME_RANGE_PATTERN, "timeRange 需形如 '30d'、'2w'、'12h'"),
19
+ ])
20
+ .optional()
21
+ .describe("时间范围(如 30d/2w/12h 或 all/*;默认 30d)"),
22
+ createdAfter: timeValueSchema.optional().describe("创建时间下界(ISO/毫秒/秒)"),
23
+ createdBefore: timeValueSchema.optional().describe("创建时间上界(ISO/毫秒/秒)"),
24
+ updatedAfter: timeValueSchema.optional().describe("更新时间下界(ISO/毫秒/秒)"),
25
+ updatedBefore: timeValueSchema.optional().describe("更新时间上界(ISO/毫秒/秒)"),
26
+ maxResults: z
27
+ .number()
28
+ .int()
29
+ .min(1)
30
+ .max(30)
31
+ .optional()
32
+ .describe("聚合的结果条数上限(默认 8)"),
33
+ maxNodesPerResult: z
34
+ .number()
35
+ .int()
36
+ .min(5)
37
+ .max(200)
38
+ .optional()
39
+ .describe("单个条目展开的最大节点数(默认 60)"),
40
+ expandReferences: z.boolean().optional().describe("是否展开 [[引用]] 文本(默认 true)"),
41
+ maxReferenceDepth: z
42
+ .number()
43
+ .int()
44
+ .min(0)
45
+ .max(5)
46
+ .optional()
47
+ .describe("引用展开最大深度(默认 1)"),
48
+ includeEmpty: z.boolean().optional().describe("是否包含空节点(默认 false)"),
49
+ excludeProperties: z
50
+ .boolean()
51
+ .optional()
52
+ .describe("是否排除表格属性/选项节点(默认 true)"),
53
+ groupBy: z
54
+ .enum(["none", "parent", "date"]).optional().describe("分组方式(默认 parent)"),
55
+ searchLimit: z
56
+ .number()
57
+ .int()
58
+ .min(5)
59
+ .max(100)
60
+ .optional()
61
+ .describe("每轮搜索拉取上限(默认 3×maxResults)"),
62
+ mode: z
63
+ .enum(["auto", "like", "fts"]).optional().describe("检索模式(默认 auto)"),
64
+ offset: z
65
+ .number()
66
+ .int()
67
+ .min(0)
68
+ .optional()
69
+ .describe("搜索起始偏移(分页)"),
70
+ referenceIds: z
71
+ .array(z.string().min(1))
72
+ .optional()
73
+ .describe("可选:作为引用锚点的 Rem ID 列表"),
74
+ includeReferenceMatches: z
75
+ .boolean()
76
+ .optional()
77
+ .describe("是否合并引用命中的条目(默认 true)"),
78
+ referenceDepth: z
79
+ .number()
80
+ .int()
81
+ .min(1)
82
+ .max(3)
83
+ .optional()
84
+ .describe("引用搜索深度(默认 1)"),
85
+ maxReferenceCandidates: z
86
+ .number()
87
+ .int()
88
+ .min(1)
89
+ .max(400)
90
+ .optional()
91
+ .describe("引用候选上限(默认 200)"),
92
+ dbPath: z.string().optional().describe("数据库文件路径(默认自动发现)"),
93
+ detail: z.boolean().optional().describe("是否返回 items/groups 等详细结构(默认 false)"),
94
+ };
95
+ export const summarizeTopicActivitySchema = z.object(inputShape);
96
+ export async function executeSummarizeTopicActivity(params) {
97
+ const parsed = parseOrThrow(summarizeTopicActivitySchema, params, { label: "summarize_topic_activity" });
98
+ const keywords = parsed.keywords ?? [];
99
+ const query = (parsed.query ?? keywords.join(" ")).trim();
100
+ if (!query) {
101
+ throw new Error("必须提供 query 或 keywords");
102
+ }
103
+ const maxResults = parsed.maxResults ?? 8;
104
+ const maxNodesPerResult = parsed.maxNodesPerResult ?? 60;
105
+ const expandReferences = parsed.expandReferences ?? true;
106
+ const maxReferenceDepth = parsed.maxReferenceDepth ?? 1;
107
+ const includeEmpty = parsed.includeEmpty ?? false;
108
+ const excludeProperties = parsed.excludeProperties ?? true;
109
+ const groupBy = parsed.groupBy ?? "parent";
110
+ const searchLimit = parsed.searchLimit ?? Math.min(maxResults * 3, 60);
111
+ const { filters: timeFilters, summary: filterSummary, effectiveTimeRange } = resolveTimeFilters(parsed, { defaultTimeRange: "30d" });
112
+ const searchTimeRange = parsed.timeRange ?? effectiveTimeRange;
113
+ const detail = parsed.detail ?? false;
114
+ const includeReferenceMatches = parsed.includeReferenceMatches ?? true;
115
+ const requestedReferenceDepth = parsed.referenceDepth ?? 1;
116
+ const maxReferenceCandidates = parsed.maxReferenceCandidates ?? 200;
117
+ const anchorIds = new Set(parsed.referenceIds ?? []);
118
+ const collected = new Map();
119
+ const orderedIds = [];
120
+ const referenceIndex = new Map();
121
+ const ensureMatch = (id) => {
122
+ let entry = collected.get(id);
123
+ if (!entry) {
124
+ entry = {
125
+ id,
126
+ title: null,
127
+ snippet: null,
128
+ ancestor: null,
129
+ ancestorIds: [],
130
+ parentId: null,
131
+ matchedBy: new Set(),
132
+ matchedTargets: new Set(),
133
+ anchorIds: new Set(),
134
+ sourceIds: new Set(),
135
+ };
136
+ collected.set(id, entry);
137
+ orderedIds.push(id);
138
+ }
139
+ return entry;
140
+ };
141
+ // 若未提供参考锚点,先进行一次宽松搜索获取候选概念 Rem
142
+ if (anchorIds.size === 0) {
143
+ const anchorSearch = await executeSearchRemOverview({
144
+ query,
145
+ limit: 5,
146
+ mode: parsed.mode,
147
+ dbPath: parsed.dbPath,
148
+ });
149
+ for (const match of anchorSearch.matches) {
150
+ anchorIds.add(match.id);
151
+ }
152
+ }
153
+ let offset = parsed.offset ?? 0;
154
+ let iterations = 0;
155
+ let lastSearchResult = null;
156
+ let totalFetched = 0;
157
+ while (collected.size < maxResults && iterations < 6) {
158
+ const searchResult = await executeSearchRemOverview({
159
+ query,
160
+ limit: searchLimit,
161
+ offset,
162
+ mode: parsed.mode,
163
+ dbPath: parsed.dbPath,
164
+ ...(searchTimeRange ? { timeRange: searchTimeRange } : {}),
165
+ createdAfter: parsed.createdAfter,
166
+ createdBefore: parsed.createdBefore,
167
+ updatedAfter: parsed.updatedAfter,
168
+ updatedBefore: parsed.updatedBefore,
169
+ });
170
+ lastSearchResult = searchResult;
171
+ totalFetched += searchResult.matches.length;
172
+ for (const match of searchResult.matches) {
173
+ const entry = ensureMatch(match.id);
174
+ entry.matchedBy.add("text");
175
+ if (!entry.title && match.title)
176
+ entry.title = match.title;
177
+ if (!entry.snippet && match.snippet)
178
+ entry.snippet = match.snippet;
179
+ if (!entry.ancestor && match.ancestor)
180
+ entry.ancestor = match.ancestor;
181
+ if (entry.ancestorIds.length === 0 &&
182
+ Array.isArray(match.ancestorIds) &&
183
+ (match.ancestorIds?.length ?? 0) > 0) {
184
+ entry.ancestorIds = match.ancestorIds ?? [];
185
+ }
186
+ entry.parentId ??= match.parentId ?? null;
187
+ }
188
+ if (!searchResult.hasMore || searchResult.nextOffset == null) {
189
+ break;
190
+ }
191
+ offset = searchResult.nextOffset;
192
+ iterations += 1;
193
+ }
194
+ let referenceTotalCount = 0;
195
+ let referenceDepthUsed = includeReferenceMatches ? requestedReferenceDepth : null;
196
+ let referenceDepthAttempts = [];
197
+ let referenceAutoExpandUsed = includeReferenceMatches;
198
+ let referenceDepthLimit = includeReferenceMatches ? requestedReferenceDepth : null;
199
+ if (includeReferenceMatches && anchorIds.size > 0) {
200
+ const referenceArgs = {
201
+ targetIds: Array.from(anchorIds),
202
+ maxDepth: requestedReferenceDepth,
203
+ maxCandidates: maxReferenceCandidates,
204
+ limit: maxReferenceCandidates,
205
+ offset: 0,
206
+ timeRange: parsed.timeRange ?? filterSummary.timeRange ?? undefined,
207
+ createdAfter: parsed.createdAfter,
208
+ createdBefore: parsed.createdBefore,
209
+ updatedAfter: parsed.updatedAfter,
210
+ updatedBefore: parsed.updatedBefore,
211
+ dbPath: parsed.dbPath,
212
+ };
213
+ const referenceResult = await executeFindRemsByReference(referenceArgs);
214
+ referenceTotalCount = referenceResult.totalCount;
215
+ const appliedDepth = referenceResult.depthApplied ?? requestedReferenceDepth;
216
+ for (const match of referenceResult.matches) {
217
+ const entry = ensureMatch(match.id);
218
+ entry.matchedBy.add("reference");
219
+ if (!entry.title && match.title)
220
+ entry.title = match.title;
221
+ if (!entry.snippet && match.snippet)
222
+ entry.snippet = match.snippet;
223
+ if (!entry.ancestor && match.ancestor)
224
+ entry.ancestor = match.ancestor;
225
+ if (entry.ancestorIds.length === 0 && match.ancestorIds && match.ancestorIds.length > 0) {
226
+ entry.ancestorIds = match.ancestorIds;
227
+ }
228
+ entry.parentId ??= match.parentId ?? null;
229
+ if (match.matchedTargets) {
230
+ for (const target of match.matchedTargets) {
231
+ entry.matchedTargets.add(target);
232
+ }
233
+ }
234
+ if (match.anchorIds) {
235
+ for (const anchor of match.anchorIds) {
236
+ entry.anchorIds.add(anchor);
237
+ }
238
+ }
239
+ if (match.sourceIds) {
240
+ for (const source of match.sourceIds) {
241
+ entry.sourceIds.add(source);
242
+ }
243
+ }
244
+ entry.referenceDepth = entry.referenceDepth
245
+ ? Math.min(entry.referenceDepth, match.depth ?? appliedDepth)
246
+ : match.depth ?? appliedDepth;
247
+ entry.updatedAt = chooseLatest(entry.updatedAt ?? null, match.updatedAt ?? null);
248
+ entry.createdAt = chooseLatest(entry.createdAt ?? null, match.createdAt ?? null);
249
+ }
250
+ referenceDepthUsed = appliedDepth;
251
+ referenceDepthAttempts = referenceResult.depthAttempts ?? [];
252
+ referenceAutoExpandUsed = referenceResult.autoExpandDepth ?? includeReferenceMatches;
253
+ referenceDepthLimit = referenceResult.depthLimit ?? appliedDepth;
254
+ }
255
+ const orderedMatches = orderedIds
256
+ .map((id) => collected.get(id))
257
+ .slice(0, maxResults);
258
+ const items = [];
259
+ for (const match of orderedMatches) {
260
+ try {
261
+ const outline = await executeOutlineRemSubtree({
262
+ id: match.id,
263
+ dbPath: parsed.dbPath,
264
+ includeEmpty,
265
+ expandReferences,
266
+ maxReferenceDepth,
267
+ startOffset: 0,
268
+ maxNodes: maxNodesPerResult,
269
+ format: "markdown",
270
+ excludeProperties,
271
+ detail: true,
272
+ });
273
+ const markdown = outline.markdown;
274
+ const lineCount = markdown ? markdown.split("\n").length : 0;
275
+ const nodes = Array.isArray(outline.tree)
276
+ ? (outline.tree ?? [])
277
+ : [];
278
+ collectReferenceSummaries(referenceIndex, nodes, match.id, outline.title ?? match.title ?? match.id);
279
+ const item = {
280
+ remId: match.id,
281
+ title: match.title ?? outline.title ?? match.id,
282
+ ancestor: match.ancestor,
283
+ ancestorIds: match.ancestorIds,
284
+ parentId: match.parentId,
285
+ snippet: match.snippet,
286
+ truncated: Boolean(outline.hasMore || lineCount > maxNodesPerResult),
287
+ matchedBy: Array.from(match.matchedBy),
288
+ matchedTargets: Array.from(match.matchedTargets),
289
+ anchorIds: Array.from(match.anchorIds),
290
+ sourceIds: Array.from(match.sourceIds),
291
+ referenceDepth: match.referenceDepth,
292
+ updatedAt: match.updatedAt ?? null,
293
+ createdAt: match.createdAt ?? null,
294
+ markdown,
295
+ outlineTitle: outline.title,
296
+ nodeCount: outline.nodeCount,
297
+ totalNodeCount: outline.totalNodeCount,
298
+ hasMore: outline.hasMore,
299
+ nextOffset: outline.nextOffset,
300
+ };
301
+ if (detail) {
302
+ item.nodes = nodes;
303
+ }
304
+ items.push(item);
305
+ }
306
+ catch (error) {
307
+ items.push({
308
+ remId: match.id,
309
+ title: match.title ?? match.id,
310
+ ancestor: match.ancestor,
311
+ ancestorIds: match.ancestorIds,
312
+ parentId: match.parentId,
313
+ snippet: match.snippet,
314
+ truncated: false,
315
+ matchedBy: Array.from(match.matchedBy),
316
+ matchedTargets: Array.from(match.matchedTargets),
317
+ anchorIds: Array.from(match.anchorIds),
318
+ sourceIds: Array.from(match.sourceIds),
319
+ referenceDepth: match.referenceDepth,
320
+ updatedAt: match.updatedAt ?? null,
321
+ createdAt: match.createdAt ?? null,
322
+ error: String(error),
323
+ });
324
+ }
325
+ }
326
+ const groups = groupItems(items, groupBy);
327
+ const aggregateMarkdown = buildAggregateMarkdown(groups, groupBy);
328
+ const highlights = buildHighlights(items);
329
+ const referenceIndexData = convertReferenceIndex(referenceIndex);
330
+ const count = items.length;
331
+ const info = lastSearchResult ?? {
332
+ dbPath: parsed.dbPath ?? "",
333
+ resolution: "explicit",
334
+ dirName: undefined,
335
+ queryUsed: query,
336
+ totalFetched: 0,
337
+ matches: [],
338
+ count: 0,
339
+ offset: 0,
340
+ limit: 0,
341
+ hasMore: false,
342
+ nextOffset: null,
343
+ filtersApplied: null,
344
+ };
345
+ const response = {
346
+ dbPath: info.dbPath,
347
+ resolution: info.resolution,
348
+ dirName: info.dirName,
349
+ queryUsed: info.queryUsed ?? query,
350
+ keywords,
351
+ groupBy,
352
+ timeRange: filterSummary.timeRange ?? null,
353
+ filtersApplied: filterSummary,
354
+ count,
355
+ maxResults,
356
+ maxNodesPerResult,
357
+ totalFetched,
358
+ collected: items.length,
359
+ hasMore: info.hasMore,
360
+ nextOffset: info.nextOffset,
361
+ includeReferenceMatches,
362
+ referenceDepthRequested: requestedReferenceDepth,
363
+ referenceDepthUsed,
364
+ referenceDepthAttempts,
365
+ referenceAutoExpandUsed,
366
+ referenceDepthLimit,
367
+ referenceAnchors: Array.from(anchorIds),
368
+ referenceTotalCount,
369
+ markdown: aggregateMarkdown || "未找到相关 Rem。",
370
+ highlights,
371
+ referenceIndex: referenceIndexData,
372
+ };
373
+ if (detail) {
374
+ Object.assign(response, { items, groups });
375
+ }
376
+ return response;
377
+ }
378
+ export function registerSummarizeTopicActivity(server) {
379
+ server.tool("summarize_topic_activity", `<usecase>在指定时间范围内按关键词整理 Rem,回答“最近一个月我记录了什么…”之类的问题。</usecase>
380
+ <instructions>
381
+ - keywords 和 query 至少提供其一;默认会在最近 30 天内搜索。
382
+ - 可用 timeRange(如 '30d')、createdAfter/updatedAfter 精确控制时间窗口。
383
+ - 默认按父级节点分组;可通过 groupBy=none/date 调整呈现方式。
384
+ - 如需细读某条目,可继续调用 outline_rem_subtree 或 inspect_rem_doc。
385
+ </instructions>`, inputShape, async (input) => {
386
+ const parsed = parseOrThrow(summarizeTopicActivitySchema, input, { label: "summarize_topic_activity" });
387
+ const result = await executeSummarizeTopicActivity(parsed);
388
+ const suggestions = [
389
+ `可继续调用 outline_rem_subtree id=<remId> startOffset=${result.maxNodesPerResult} 深入阅读`,
390
+ ];
391
+ if (result.includeReferenceMatches &&
392
+ result.referenceTotalCount === 0 &&
393
+ (result.referenceDepthUsed ?? 0) < Math.min(result.referenceDepthLimit ?? 3, 3)) {
394
+ suggestions.unshift(`引用未命中,可尝试提高 referenceDepth 至 ${(result.referenceDepthUsed ?? 0) + 1} 或指定 referenceIds`);
395
+ }
396
+ if (result.hasMore && result.nextOffset != null) {
397
+ suggestions.unshift(`搜索结果仍有剩余,可再次调用 summarize_topic_activity 并设置 searchLimit 或使用 nextOffset=${result.nextOffset}`);
398
+ }
399
+ const count = typeof result.count === "number"
400
+ ? result.count
401
+ : Array.isArray(result.items)
402
+ ? (result.items?.length ?? 0)
403
+ : 0;
404
+ if (count === 0) {
405
+ suggestions.unshift("可尝试放宽时间范围或更换关键词");
406
+ }
407
+ if (result.includeReferenceMatches && result.referenceTotalCount > 0) {
408
+ suggestions.unshift(`引用命中了 ${result.referenceTotalCount} 条,可使用 find_rems_by_reference targetIds=${JSON.stringify(result.referenceAnchors)} 深入探索`);
409
+ }
410
+ const filterDesc = describeFilterSummary(result.filtersApplied);
411
+ const guidance = `整理 "${result.queryUsed}" 的专题活动,共 ${count} 条${result.markdown ? ",已生成 Markdown 摘要" : ""}${filterDesc ? `(${filterDesc})` : ""}。`;
412
+ return buildGuidedResponse({ guidance, ...result }, suggestions);
413
+ });
414
+ }
415
+ function groupItems(items, groupBy) {
416
+ if (groupBy === "none") {
417
+ return [
418
+ {
419
+ key: "all",
420
+ label: "全部",
421
+ items,
422
+ },
423
+ ];
424
+ }
425
+ const map = new Map();
426
+ for (const item of items) {
427
+ const key = groupBy === "parent" ? determineParentLabel(item) : determineDateLabel(item);
428
+ const existing = map.get(key.key);
429
+ if (existing) {
430
+ existing.items.push(item);
431
+ }
432
+ else {
433
+ map.set(key.key, {
434
+ key: key.key,
435
+ label: key.label,
436
+ items: [item],
437
+ });
438
+ }
439
+ }
440
+ return Array.from(map.values());
441
+ }
442
+ function determineParentLabel(item) {
443
+ const label = item.ancestor && item.ancestor.trim() ? item.ancestor.trim() : "未分类";
444
+ return { key: label, label };
445
+ }
446
+ function determineDateLabel(item) {
447
+ const candidates = [
448
+ item.outlineTitle,
449
+ item.title,
450
+ item.snippet,
451
+ item.markdown?.split("\n")[0],
452
+ ];
453
+ for (const candidate of candidates) {
454
+ const normalized = extractDate(candidate);
455
+ if (normalized) {
456
+ return { key: normalized, label: normalized };
457
+ }
458
+ }
459
+ return { key: "未识别日期", label: "未识别日期" };
460
+ }
461
+ function extractDate(text) {
462
+ if (!text)
463
+ return null;
464
+ const trimmed = text.trim();
465
+ if (!trimmed)
466
+ return null;
467
+ const isoMatch = trimmed.match(/(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})/);
468
+ if (isoMatch) {
469
+ return normalizeDateParts(isoMatch[1], isoMatch[2], isoMatch[3]);
470
+ }
471
+ const dotMatch = trimmed.match(/(\d{4})\.(\d{1,2})\.(\d{1,2})/);
472
+ if (dotMatch) {
473
+ return normalizeDateParts(dotMatch[1], dotMatch[2], dotMatch[3]);
474
+ }
475
+ const cnMatch = trimmed.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
476
+ if (cnMatch) {
477
+ return normalizeDateParts(cnMatch[1], cnMatch[2], cnMatch[3]);
478
+ }
479
+ return null;
480
+ }
481
+ function normalizeDateParts(year, month, day) {
482
+ const y = year.padStart(4, "0");
483
+ const m = month.padStart(2, "0");
484
+ const d = day.padStart(2, "0");
485
+ return `${y}-${m}-${d}`;
486
+ }
487
+ function buildAggregateMarkdown(groups, groupBy) {
488
+ const lines = [];
489
+ if (groups.length === 0 || groups.every((group) => group.items.length === 0)) {
490
+ return "未找到相关 Rem。";
491
+ }
492
+ for (const group of groups) {
493
+ if (groupBy !== "none") {
494
+ lines.push(`## ${group.label}`);
495
+ }
496
+ for (const item of group.items) {
497
+ const title = item.outlineTitle || item.title || item.remId;
498
+ const truncatedNote = item.truncated ? " _(内容已截断)_" : "";
499
+ lines.push(`- **${title}**${truncatedNote}`);
500
+ if (item.ancestor && groupBy !== "parent") {
501
+ lines.push(` - 上级:${item.ancestor}`);
502
+ }
503
+ if (item.matchedBy.length > 0) {
504
+ const sourceLabel = item.matchedBy
505
+ .map((source) => (source === "text" ? "文本" : "引用"))
506
+ .join(" + ");
507
+ lines.push(` - 命中来源:${sourceLabel}`);
508
+ }
509
+ if (item.referenceDepth) {
510
+ lines.push(` - 引用深度:${item.referenceDepth}`);
511
+ }
512
+ if (item.anchorIds && item.anchorIds.length > 0) {
513
+ lines.push(` - 对应锚点:${item.anchorIds.join(", ")}`);
514
+ }
515
+ if (item.matchedTargets && item.matchedTargets.length > 0) {
516
+ lines.push(` - 引用目标:${item.matchedTargets.join(", ")}`);
517
+ }
518
+ if (item.markdown) {
519
+ lines.push(indentMarkdown(item.markdown, " "));
520
+ }
521
+ else if (item.snippet) {
522
+ lines.push(` - 摘要:${item.snippet}`);
523
+ }
524
+ if (item.error) {
525
+ lines.push(` - 读取失败:${item.error}`);
526
+ }
527
+ else if (item.truncated) {
528
+ lines.push(` - 提示:继续使用 outline_rem_subtree id=${item.remId} 获取完整内容`);
529
+ }
530
+ }
531
+ lines.push("");
532
+ }
533
+ return lines.join("\n").trim();
534
+ }
535
+ function indentMarkdown(content, prefix) {
536
+ return content
537
+ .split("\n")
538
+ .map((line) => `${prefix}${line}`)
539
+ .join("\n");
540
+ }
541
+ function chooseLatest(current, incoming) {
542
+ if (incoming == null)
543
+ return current ?? null;
544
+ if (current == null)
545
+ return incoming;
546
+ return Math.max(current, incoming);
547
+ }
548
+ function buildHighlights(items) {
549
+ return items.map((item) => ({
550
+ remId: item.remId,
551
+ title: item.title,
552
+ ancestor: item.ancestor,
553
+ matchedBy: item.matchedBy,
554
+ truncated: item.truncated,
555
+ referenceDepth: item.referenceDepth ?? null,
556
+ anchorIds: item.anchorIds ?? [],
557
+ matchedTargets: item.matchedTargets ?? [],
558
+ hasMore: Boolean(item.hasMore),
559
+ }));
560
+ }
561
+ function collectReferenceSummaries(index, nodes, remId, remTitle) {
562
+ if (!nodes || nodes.length === 0)
563
+ return;
564
+ for (const node of nodes) {
565
+ if (!node.references || node.references.length === 0)
566
+ continue;
567
+ for (const refId of node.references) {
568
+ if (!refId)
569
+ continue;
570
+ const entry = ensureReferenceEntry(index, refId);
571
+ entry.totalOccurrences += 1;
572
+ if (entry.samples.length < 5) {
573
+ entry.samples.push({
574
+ remId,
575
+ remTitle,
576
+ nodeId: node.id,
577
+ text: node.text,
578
+ depth: node.depth,
579
+ });
580
+ }
581
+ }
582
+ }
583
+ }
584
+ function ensureReferenceEntry(index, id) {
585
+ let entry = index.get(id);
586
+ if (!entry) {
587
+ entry = {
588
+ id,
589
+ totalOccurrences: 0,
590
+ samples: [],
591
+ };
592
+ index.set(id, entry);
593
+ }
594
+ return entry;
595
+ }
596
+ function convertReferenceIndex(index) {
597
+ const result = {};
598
+ for (const [id, entry] of index.entries()) {
599
+ result[id] = {
600
+ totalOccurrences: entry.totalOccurrences,
601
+ samples: entry.samples,
602
+ };
603
+ }
604
+ return result;
605
+ }