@xopcai/xopc 0.0.92 → 0.0.93

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 (250) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  4. package/dist/extensions/telegram/src/plugin.js +1 -1
  5. package/dist/extensions/telegram/src/routing-integration.js +2 -2
  6. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/extensions/weixin/src/api/api.js +2 -2
  9. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  10. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  11. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  12. package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
  13. package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
  14. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  15. package/dist/extensions/weixin/src/plugin.js +1 -1
  16. package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
  17. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  18. package/dist/gateway/static/root/assets/agents-C7tTJLP9.js +222 -0
  19. package/dist/gateway/static/root/assets/apps-page-BbzdMyrg.js +1 -0
  20. package/dist/gateway/static/root/assets/channels-settings-B49vG2hE.js +1 -0
  21. package/dist/gateway/static/root/assets/{channels-status-swr-XzddfJW2.js → channels-status-swr-CsGkK9h9.js} +1 -1
  22. package/dist/gateway/static/root/assets/{cron-api--I8LJ44S.js → cron-api-CyAm0xJT.js} +1 -1
  23. package/dist/gateway/static/root/assets/cron-page-Bjx7IOdR.js +1 -0
  24. package/dist/gateway/static/root/assets/{dist-CYgHMQO0.js → dist-DHwVV8XK.js} +1 -1
  25. package/dist/gateway/static/root/assets/{extension-debug-page-6cRP0nA9.js → extension-debug-page-BK8kcc4F.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-DpwIkspI.js → extension-page-Cf8X_QUc.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-settings-page-DYbnQUxH.js → extension-settings-page-C5-YLMmy.js} +1 -1
  28. package/dist/gateway/static/root/assets/{fetch-DTN0w7rV.js → fetch-BAPnkYbC.js} +1 -1
  29. package/dist/gateway/static/root/assets/{field-primitives-CslW6HwD.js → field-primitives-8p7ucXa1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{heartbeat-config-api-2UiKevxG.js → heartbeat-config-api-CpgW2sGp.js} +1 -1
  31. package/dist/gateway/static/root/assets/index-CwDdudZM.css +1 -0
  32. package/dist/gateway/static/root/assets/{index-DnevRVa6.js → index-Do52EfZK.js} +82 -82
  33. package/dist/gateway/static/root/assets/{logs-page-sOP4TXJ4.js → logs-page-BxukQ-J-.js} +1 -1
  34. package/dist/gateway/static/root/assets/{note-detail-page-DvW2qg4i.js → note-detail-page-WLM6FUIi.js} +53 -53
  35. package/dist/gateway/static/root/assets/{note-time-BEiibLJv.js → note-time-EFyIVhec.js} +1 -1
  36. package/dist/gateway/static/root/assets/notes-page-BYPVYcYn.js +1 -0
  37. package/dist/gateway/static/root/assets/sessions-page-BFD2_-Cl.js +1 -0
  38. package/dist/gateway/static/root/assets/{settings-advanced-gate-BctKqHcf.js → settings-advanced-gate-CEs8pGh6.js} +1 -1
  39. package/dist/gateway/static/root/assets/{settings-form-section-QJh5ruel.js → settings-form-section-C6cGTVwK.js} +1 -1
  40. package/dist/gateway/static/root/assets/settings-page-BiP5iH46.js +2 -0
  41. package/dist/gateway/static/root/assets/{share-preview-page-DBsvvbmD.js → share-preview-page-tnIfJ4K6.js} +1 -1
  42. package/dist/gateway/static/root/assets/skills-page-CNDctFIn.js +2 -0
  43. package/dist/gateway/static/root/assets/{theme-store-ht5iswWS.js → theme-store-D6EsNTPr.js} +1 -1
  44. package/dist/gateway/static/root/assets/url-CTjpm0Uz.js +3 -0
  45. package/dist/gateway/static/root/assets/{utils-DhPv9xoB.js → utils-C86AVfY-.js} +1 -1
  46. package/dist/gateway/static/root/assets/voice-api-key-field-CalxUkxm.js +1 -0
  47. package/dist/gateway/static/root/assets/{workflow-page.utils-CJqnPWkW.js → workflow-page.utils-DsEriMFW.js} +1 -1
  48. package/dist/gateway/static/root/assets/workflows-page-D2fRxXJG.js +27 -0
  49. package/dist/gateway/static/root/index.html +5 -5
  50. package/dist/package.js +1 -1
  51. package/dist/src/agent/agent-manager.js +7 -7
  52. package/dist/src/agent/agent-scope.js +1 -1
  53. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  54. package/dist/src/agent/context/workspace-seed.js +2 -2
  55. package/dist/src/agent/goals/goal-run-store.js +4 -4
  56. package/dist/src/agent/goals/persistent-goal-service.js +1 -1
  57. package/dist/src/agent/goals/post-turn.js +2 -2
  58. package/dist/src/agent/image/load-image-media.js +2 -2
  59. package/dist/src/agent/ipc/bus.js +1 -1
  60. package/dist/src/agent/ipc/inbox.js +2 -2
  61. package/dist/src/agent/ipc/socket.js +1 -1
  62. package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
  63. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  64. package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
  65. package/dist/src/agent/mcp/mcp-transport.js +1 -1
  66. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  67. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  68. package/dist/src/agent/memory/dreaming/events.js +1 -1
  69. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  70. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  71. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  72. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  73. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  74. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  75. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  76. package/dist/src/agent/models/manager.js +1 -1
  77. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  78. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  79. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  80. package/dist/src/agent/sandbox/path-policy.js +2 -2
  81. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  82. package/dist/src/agent/service.js +4 -4
  83. package/dist/src/agent/session/session-inspector.js +1 -1
  84. package/dist/src/agent/skills/config.js +1 -1
  85. package/dist/src/agent/skills/hub-hash.js +2 -2
  86. package/dist/src/agent/skills/hub-lock.js +1 -1
  87. package/dist/src/agent/skills/hub-pull.js +2 -2
  88. package/dist/src/agent/skills/index.js +1 -1
  89. package/dist/src/agent/skills/managed-store.js +1 -1
  90. package/dist/src/agent/skills/scanner.js +1 -1
  91. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  92. package/dist/src/agent/skills/skill-manager.js +1 -1
  93. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  94. package/dist/src/agent/tools/factory.js +1 -1
  95. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  96. package/dist/src/agent/tools/send-media.js +1 -1
  97. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  98. package/dist/src/agent/tools/workflow-tool.js +1 -1
  99. package/dist/src/agent/tools/write.js +1 -1
  100. package/dist/src/agent/workflow/catalog.js +1 -1
  101. package/dist/src/auth/credentials.js +3 -3
  102. package/dist/src/auth/profiles/store.js +1 -1
  103. package/dist/src/auth/sync-provider-auth.js +1 -1
  104. package/dist/src/browser/cache-dir-policy.js +1 -1
  105. package/dist/src/browser/cdp-local-launcher.js +2 -2
  106. package/dist/src/browser/providers/browser-ext-install.js +3 -3
  107. package/dist/src/browser/providers/cloakbrowser.js +4 -4
  108. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  109. package/dist/src/browser/stealth.js +1 -1
  110. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  111. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  112. package/dist/src/channels/outbound/persist-store.js +1 -1
  113. package/dist/src/channels/pairing/allow-from-file.js +1 -1
  114. package/dist/src/channels/pairing/pairing-store.js +2 -2
  115. package/dist/src/chat-commands/agent-edit.js +2 -2
  116. package/dist/src/chat-commands/builtins/config.js +2 -2
  117. package/dist/src/chat-commands/context.js +1 -1
  118. package/dist/src/cli/commands/config.js +1 -1
  119. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  120. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  121. package/dist/src/cli/commands/doctor/checks/session-integrity.js +2 -2
  122. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  123. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  124. package/dist/src/cli/commands/extension-dev.js +1 -1
  125. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  126. package/dist/src/cli/commands/extension-pack.js +1 -1
  127. package/dist/src/cli/commands/gateway/logs.js +1 -1
  128. package/dist/src/cli/commands/image.js +1 -1
  129. package/dist/src/cli/commands/init.js +4 -4
  130. package/dist/src/cli/commands/onboard.js +1 -1
  131. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  132. package/dist/src/commands/agents.config.js +1 -1
  133. package/dist/src/config/agent-profile.js +1 -1
  134. package/dist/src/config/gateway-bind.js +1 -1
  135. package/dist/src/config/index.js +5 -5
  136. package/dist/src/config/loader.js +2 -2
  137. package/dist/src/config/models-json.js +2 -2
  138. package/dist/src/config/paths-state.js +1 -1
  139. package/dist/src/config/profile.js +2 -2
  140. package/dist/src/config/workspace-path.js +1 -1
  141. package/dist/src/cron/executor.js +2 -2
  142. package/dist/src/cron/persistence.js +1 -1
  143. package/dist/src/cron/run-log-store.js +1 -1
  144. package/dist/src/daemon/constants.js +1 -1
  145. package/dist/src/daemon/install-plan.js +2 -2
  146. package/dist/src/daemon/launchd.js +2 -2
  147. package/dist/src/daemon/schtasks.js +2 -2
  148. package/dist/src/daemon/systemd.js +2 -2
  149. package/dist/src/extensions/bundle-mcp.js +1 -1
  150. package/dist/src/extensions/discover-extensions.js +1 -1
  151. package/dist/src/extensions/health.js +1 -1
  152. package/dist/src/extensions/loader.js +1 -1
  153. package/dist/src/extensions/lockfile.js +2 -2
  154. package/dist/src/extensions/update.js +1 -1
  155. package/dist/src/gateway/agents-admin.js +3 -3
  156. package/dist/src/gateway/file-path-classifier.js +2 -2
  157. package/dist/src/gateway/heartbeat/service.js +1 -1
  158. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  159. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  160. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  161. package/dist/src/gateway/hono/oauth.js +1 -1
  162. package/dist/src/gateway/hono/routes/agents.js +1 -1
  163. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  164. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  165. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  166. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  167. package/dist/src/gateway/hono/routes/models.js +1 -1
  168. package/dist/src/gateway/hono/routes/notes.js +105 -1
  169. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  170. package/dist/src/gateway/hono/routes/shares.js +1 -1
  171. package/dist/src/gateway/hono/routes/workspace.js +4 -4
  172. package/dist/src/gateway/host.d.ts +2 -0
  173. package/dist/src/gateway/host.js +6 -3
  174. package/dist/src/gateway/host.js.map +1 -1
  175. package/dist/src/gateway/lock.js +3 -3
  176. package/dist/src/gateway/ports.js +1 -1
  177. package/dist/src/gateway/service/agent-runner.js +2 -2
  178. package/dist/src/gateway/service/marketplace-service.js +2 -2
  179. package/dist/src/gateway/service.js +1 -1
  180. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  181. package/dist/src/heartbeat/index.js +1 -1
  182. package/dist/src/infra/brew.js +1 -1
  183. package/dist/src/infra/package-json.js +1 -1
  184. package/dist/src/infra/package-update-steps.js +1 -1
  185. package/dist/src/infra/path-env.js +2 -2
  186. package/dist/src/infra/restart.js +2 -2
  187. package/dist/src/infra/stable-node-path.js +1 -1
  188. package/dist/src/infra/update-check.js +1 -1
  189. package/dist/src/infra/update-global.js +1 -1
  190. package/dist/src/infra/update-lock.js +3 -3
  191. package/dist/src/infra/update-runner.js +1 -1
  192. package/dist/src/infra/update-startup.js +2 -2
  193. package/dist/src/infra/write-file-atomic.js +2 -2
  194. package/dist/src/notes/service.d.ts +13 -1
  195. package/dist/src/notes/service.js +237 -0
  196. package/dist/src/notes/service.js.map +1 -1
  197. package/dist/src/notes/store.d.ts +3 -0
  198. package/dist/src/notes/store.js +8 -4
  199. package/dist/src/notes/store.js.map +1 -1
  200. package/dist/src/notes/types.d.ts +31 -0
  201. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  202. package/dist/src/providers/index.js +2 -2
  203. package/dist/src/providers/model-registry.js +1 -1
  204. package/dist/src/session/config-store.js +2 -2
  205. package/dist/src/session/init-session-turn.js +2 -2
  206. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  207. package/dist/src/session/parity/sessions-json-file.js +1 -1
  208. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  209. package/dist/src/session/parity/transcript-paths.js +1 -1
  210. package/dist/src/session/resolve-session.js +4 -4
  211. package/dist/src/session/search-index-cache.js +1 -1
  212. package/dist/src/session/search-index.js +1 -1
  213. package/dist/src/session/session-title.js +2 -2
  214. package/dist/src/session/store.js +6 -6
  215. package/dist/src/share/share-auto.js +2 -2
  216. package/dist/src/share/share-store.js +3 -3
  217. package/dist/src/share/share-thumbnail.js +2 -2
  218. package/dist/src/share/share-zip.js +1 -1
  219. package/dist/src/share/site-share-store.js +3 -3
  220. package/dist/src/share/site-static-serve.js +1 -1
  221. package/dist/src/tui/clipboard-image.js +3 -3
  222. package/dist/src/tui/theme-manager.js +1 -1
  223. package/dist/src/tui/tui-keybindings-file.js +1 -1
  224. package/dist/src/tui/tui-scoped-models.js +2 -2
  225. package/dist/src/tui/tui-settings.js +1 -1
  226. package/dist/src/tui/tui.js +3 -3
  227. package/dist/src/tunnel/frpc-binary.js +3 -3
  228. package/dist/src/tunnel/frpc-config.js +1 -1
  229. package/dist/src/tunnel/frpc-extract.js +1 -1
  230. package/dist/src/tunnel/tunnel-state.js +1 -1
  231. package/dist/src/utils/logger/audit.js +1 -1
  232. package/dist/src/utils/logger/log-store.js +1 -1
  233. package/dist/src/utils/logger/rotation.js +1 -1
  234. package/dist/src/voice/tts/audio.js +1 -1
  235. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  236. package/dist/src/workflows/store/event-store.js +1 -1
  237. package/dist/src/workflows/store/run-store.js +1 -1
  238. package/package.json +5 -1
  239. package/dist/gateway/static/root/assets/agents-uwPn7ZW9.js +0 -222
  240. package/dist/gateway/static/root/assets/apps-page-CWKdhSPU.js +0 -1
  241. package/dist/gateway/static/root/assets/channels-settings-hEhW7Mbk.js +0 -1
  242. package/dist/gateway/static/root/assets/cron-page-B0kvgZGR.js +0 -1
  243. package/dist/gateway/static/root/assets/index-BUKUv7QW.css +0 -1
  244. package/dist/gateway/static/root/assets/notes-page-BFQaquHU.js +0 -1
  245. package/dist/gateway/static/root/assets/sessions-page-CptjDKAX.js +0 -1
  246. package/dist/gateway/static/root/assets/settings-page-V3p-hISB.js +0 -2
  247. package/dist/gateway/static/root/assets/skills-page-q2zPUJAR.js +0 -2
  248. package/dist/gateway/static/root/assets/url-CWWpfkq1.js +0 -3
  249. package/dist/gateway/static/root/assets/voice-api-key-field-DLSKUipa.js +0 -1
  250. package/dist/gateway/static/root/assets/workflows-page-DRRQ1A0l.js +0 -27
@@ -1,9 +1,12 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
+ import { getDefaultModelSync, init_providers, resolveModel } from "../providers/index.js";
3
4
  import { attachmentIdFromTarget, buildNoteAttachmentRef } from "./attachment-ref.js";
4
5
  import { partitionAttachmentsByReference } from "./note-attachment-sync.js";
5
6
  import { randomUUID } from "node:crypto";
7
+ import { complete } from "@earendil-works/pi-ai";
6
8
  //#region src/notes/service.ts
9
+ init_providers();
7
10
  init_logger();
8
11
  const log = createLogger("NotesService");
9
12
  function inferKind(text, hasAttachments, attachments) {
@@ -15,6 +18,11 @@ function inferKind(text, hasAttachments, attachments) {
15
18
  if (/^https?:\/\//.test(text.trim())) return "bookmark";
16
19
  return "thought";
17
20
  }
21
+ function deriveDefaultTitle(text) {
22
+ const normalizedText = text?.trim().replace(/\s+/g, " ");
23
+ if (!normalizedText) return void 0;
24
+ return Array.from(normalizedText).slice(0, 10).join("");
25
+ }
18
26
  function createBlockId() {
19
27
  return `block_${Date.now()}_${randomUUID().slice(0, 8)}`;
20
28
  }
@@ -103,6 +111,148 @@ function createAiOrganizedBlocks(blocks, instruction) {
103
111
  updatedAt: now
104
112
  }))];
105
113
  }
114
+ function splitMeaningfulLines(text) {
115
+ return (text ?? "").split(/[\n。!?!?;;]+/).map((line) => line.trim().replace(/^[-*\d.\s]+/, "")).filter((line) => line.length > 0);
116
+ }
117
+ function summarizeIdea(text) {
118
+ const joined = splitMeaningfulLines(text).join(" ");
119
+ return Array.from(joined || "这个想法").slice(0, 90).join("");
120
+ }
121
+ function inferCatalysisStage(note) {
122
+ const text = `${note.title ?? ""}\n${note.text ?? ""}`;
123
+ if (/发布|上线|分享|推广|launch|ship/i.test(text)) return "shipped";
124
+ if (/验证|实验|用户|指标|反馈|validate|experiment/i.test(text)) return "validating";
125
+ if (/实现|开发|MVP|原型|prototype|build/i.test(text)) return "developing";
126
+ if (text.trim().length > 80) return "incubating";
127
+ return "seed";
128
+ }
129
+ function buildCatalysisReport(note) {
130
+ const generatedAt = Date.now();
131
+ const summary = summarizeIdea(note.text ?? note.title);
132
+ const title = note.title?.trim() || summary.slice(0, 28) || "未命名想法";
133
+ const hasUserSignal = /用户|客户|读者|创作者|团队|个人|开发者|founder|creator|user/i.test(summary);
134
+ const hasProductSignal = /产品|工具|平台|workflow|agent|AI|自动|系统|应用/i.test(summary);
135
+ const confidence = Math.min(.86, Math.max(.42, .48 + (hasUserSignal ? .16 : 0) + (hasProductSignal ? .18 : 0) + Math.min(summary.length, 120) / 600));
136
+ return {
137
+ originalNoteId: note.id,
138
+ generatedAt,
139
+ title,
140
+ valueHypothesis: `如果把「${summary}」推进成一个可体验的小成果,它最可能的价值是帮助用户更快完成判断、表达或行动。`,
141
+ targetUsers: hasUserSignal ? ["笔记作者自己", "有类似场景的目标用户"] : ["笔记作者自己", "未来可能被这个想法帮助的人"],
142
+ keyQuestions: [
143
+ "这个想法最想解决的具体痛点是什么?",
144
+ "第一个可验证的用户场景是什么?",
145
+ "什么样的最小成果能证明它值得继续投入?"
146
+ ],
147
+ mvpPath: [
148
+ "把想法改写成一句清晰的问题陈述。",
149
+ "列出 1 个目标用户和 1 个高频使用场景。",
150
+ "产出一个最小原型、提纲或行动清单。"
151
+ ],
152
+ risks: ["想法还停留在概念层,缺少明确使用场景。", "下一步过大时容易变成长期搁置的项目。"],
153
+ nextActions: [
154
+ {
155
+ kind: "chat",
156
+ text: "和 AI 继续深聊这个想法,收敛成问题陈述。"
157
+ },
158
+ {
159
+ kind: "research",
160
+ text: "补充 3 个相似产品、案例或用户反馈。"
161
+ },
162
+ {
163
+ kind: "task",
164
+ text: "写下今天能完成的一个最小推进动作。"
165
+ }
166
+ ],
167
+ confidence: Number(confidence.toFixed(2))
168
+ };
169
+ }
170
+ function stripJsonCodeFence(raw) {
171
+ return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
172
+ }
173
+ function extractJsonObject(raw) {
174
+ if (!raw.trim()) return null;
175
+ const text = stripJsonCodeFence(raw);
176
+ try {
177
+ const data = JSON.parse(text);
178
+ return data && typeof data === "object" && !Array.isArray(data) ? data : null;
179
+ } catch {
180
+ const start = text.indexOf("{");
181
+ const end = text.lastIndexOf("}");
182
+ if (start < 0 || end <= start) return null;
183
+ try {
184
+ const data = JSON.parse(text.slice(start, end + 1));
185
+ return data && typeof data === "object" && !Array.isArray(data) ? data : null;
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ }
191
+ function extractAssistantText(result) {
192
+ if (!Array.isArray(result.content)) return "";
193
+ return result.content.filter((block) => {
194
+ return !!block && typeof block === "object" && block.type === "text";
195
+ }).map((block) => block.text).join("").trim();
196
+ }
197
+ function stringArray(value, fallback, limit) {
198
+ if (!Array.isArray(value)) return fallback;
199
+ const items = value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean).slice(0, limit);
200
+ return items.length ? items : fallback;
201
+ }
202
+ function catalysisActions(value, fallback) {
203
+ if (!Array.isArray(value)) return fallback;
204
+ const allowed = new Set([
205
+ "task",
206
+ "workflow",
207
+ "research",
208
+ "share",
209
+ "chat"
210
+ ]);
211
+ const items = [];
212
+ for (const item of value) {
213
+ if (!item || typeof item !== "object") continue;
214
+ const row = item;
215
+ const kind = typeof row.kind === "string" && allowed.has(row.kind) ? row.kind : "task";
216
+ const text = typeof row.text === "string" ? row.text.trim() : "";
217
+ if (text) items.push({
218
+ kind,
219
+ text
220
+ });
221
+ }
222
+ return items.length ? items.slice(0, 5) : fallback;
223
+ }
224
+ function normalizeAiCatalysisReport(note, data) {
225
+ const fallback = buildCatalysisReport(note);
226
+ const confidenceRaw = typeof data.confidence === "number" ? data.confidence : fallback.confidence;
227
+ const confidence = Math.min(1, Math.max(0, confidenceRaw));
228
+ return {
229
+ originalNoteId: note.id,
230
+ generatedAt: Date.now(),
231
+ title: typeof data.title === "string" && data.title.trim() ? data.title.trim().slice(0, 80) : fallback.title,
232
+ valueHypothesis: typeof data.valueHypothesis === "string" && data.valueHypothesis.trim() ? data.valueHypothesis.trim() : fallback.valueHypothesis,
233
+ targetUsers: stringArray(data.targetUsers, fallback.targetUsers, 5),
234
+ keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions, 6),
235
+ mvpPath: stringArray(data.mvpPath, fallback.mvpPath, 6),
236
+ risks: stringArray(data.risks, fallback.risks, 5),
237
+ nextActions: catalysisActions(data.nextActions, fallback.nextActions),
238
+ confidence: Number(confidence.toFixed(2))
239
+ };
240
+ }
241
+ function buildCatalysisPrompt(note) {
242
+ return `你是一个帮助用户把想法推进成成果的个人 AI Agent。请基于这条 Note 做“想法催化”,帮助用户进入:想法 → 创造 → 分享 → 反馈 的循环。\n\n要求:\n- 使用自然、具体的中文。\n- 不要空泛鼓励,要给出可执行路径。\n- 输出必须是单个 JSON 对象,不要 Markdown,不要代码块。\n- 字段必须包含:title, valueHypothesis, targetUsers, keyQuestions, mvpPath, risks, nextActions, confidence。\n- nextActions 每项格式为 {"kind":"task|workflow|research|share|chat","text":"..."}。\n- confidence 是 0 到 1 的数字。\n\nNote 标题:${note.title?.trim() || "未命名笔记"}\n\nNote 正文:\n${((note.text ?? "").trim() || "(无正文)").slice(0, 6e3)}`;
243
+ }
244
+ async function buildAiCatalysisReport(note, config) {
245
+ const data = extractJsonObject(extractAssistantText(await complete(resolveModel(getDefaultModelSync(config)), { messages: [{
246
+ role: "user",
247
+ content: buildCatalysisPrompt(note),
248
+ timestamp: Date.now()
249
+ }] }, {
250
+ maxTokens: 1800,
251
+ temperature: .2
252
+ })));
253
+ if (!data) throw new Error("Catalysis model did not return valid JSON");
254
+ return normalizeAiCatalysisReport(note, data);
255
+ }
106
256
  const SNAPSHOT_THROTTLE_MS = 6e4;
107
257
  const MAX_SNAPSHOTS_PER_NOTE = 30;
108
258
  var NotesService = class {
@@ -121,6 +271,7 @@ var NotesService = class {
121
271
  const blocks = noteTextToBlocks(text, id);
122
272
  const note = {
123
273
  id,
274
+ title: deriveDefaultTitle(text),
124
275
  kind: inferKind(text),
125
276
  status: "inbox",
126
277
  text,
@@ -238,6 +389,92 @@ var NotesService = class {
238
389
  }
239
390
  };
240
391
  }
392
+ async catalyzeNote(id, config) {
393
+ const note = await this.store.getNote(id);
394
+ if (!note) return null;
395
+ let report;
396
+ try {
397
+ report = await buildAiCatalysisReport(note, config);
398
+ } catch (err) {
399
+ log.warn({
400
+ err,
401
+ noteId: id
402
+ }, "AI note catalysis failed; using local fallback report");
403
+ report = buildCatalysisReport(note);
404
+ }
405
+ const existingDeep = note.aiDeep;
406
+ const catalysis = {
407
+ ...existingDeep?.catalysis,
408
+ status: "catalyzed",
409
+ stage: inferCatalysisStage(note),
410
+ lastCatalyzedAt: report.generatedAt,
411
+ confidence: report.confidence,
412
+ report
413
+ };
414
+ const updated = await this.updateNote(id, {
415
+ ai: {
416
+ ...note.ai,
417
+ intent: note.ai?.intent ?? "idea",
418
+ summary: note.ai?.summary ?? report.valueHypothesis
419
+ },
420
+ aiDeep: {
421
+ ...existingDeep,
422
+ processedAt: report.generatedAt,
423
+ insights: report.valueHypothesis,
424
+ catalysis
425
+ },
426
+ status: note.status === "inbox" ? "processed" : note.status
427
+ }, "ai_edit");
428
+ return updated ? {
429
+ note: updated,
430
+ report
431
+ } : null;
432
+ }
433
+ async recordCatalysisFeedback(id, feedback) {
434
+ const note = await this.store.getNote(id);
435
+ if (!note) return null;
436
+ const now = Date.now();
437
+ return this.updateNote(id, { aiDeep: {
438
+ ...note.aiDeep,
439
+ processedAt: now,
440
+ catalysis: {
441
+ status: note.aiDeep?.catalysis?.status ?? "catalyzed",
442
+ ...note.aiDeep?.catalysis,
443
+ feedback
444
+ }
445
+ } }, "ai_edit");
446
+ }
447
+ async linkNoteThread(id, sessionKey) {
448
+ const note = await this.store.getNote(id);
449
+ if (!note) return null;
450
+ const existingKeys = note.aiDeep?.catalysis?.linkedSessionKeys ?? [];
451
+ const linkedSessionKeys = Array.from(new Set([sessionKey, ...existingKeys]));
452
+ return this.updateNote(id, { aiDeep: {
453
+ ...note.aiDeep,
454
+ processedAt: Date.now(),
455
+ catalysis: {
456
+ status: note.aiDeep?.catalysis?.status ?? "none",
457
+ ...note.aiDeep?.catalysis,
458
+ sourceSessionKey: note.aiDeep?.catalysis?.sourceSessionKey ?? sessionKey,
459
+ linkedSessionKeys
460
+ }
461
+ } }, "sync");
462
+ }
463
+ async listNoteThreads(id) {
464
+ const note = await this.store.getNote(id);
465
+ if (!note) return null;
466
+ const keys = [note.aiDeep?.catalysis?.sourceSessionKey, ...note.aiDeep?.catalysis?.linkedSessionKeys ?? []].filter((key) => typeof key === "string" && key.length > 0);
467
+ return Array.from(new Set(keys));
468
+ }
469
+ async appendTextToNote(id, content, heading = "AI 讨论沉淀") {
470
+ const note = await this.store.getNote(id);
471
+ if (!note) return null;
472
+ const trimmed = content.trim();
473
+ if (!trimmed) return note;
474
+ const currentText = note.text?.trimEnd() ?? "";
475
+ const nextText = `${currentText}${currentText ? "\n\n" : ""}## ${heading}\n\n${trimmed}`;
476
+ return this.updateNote(id, { text: nextText }, "ai_edit");
477
+ }
241
478
  async deleteNote(id) {
242
479
  const deleted = await this.store.deleteNote(id);
243
480
  if (deleted) {
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../../src/notes/service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteAttachmentRef, attachmentIdFromTarget } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\n NoteBlock,\n NoteIndexEntry,\n NoteKind,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesService');\n\nfunction inferKind(\n text?: string,\n hasAttachments?: boolean,\n attachments?: NoteAttachment[],\n): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) {\n return 'voice';\n }\n if (hasAttachments) return 'media';\n if (!text) return 'thought';\n const lower = text.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) ||\n /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(text)) {\n return 'todo';\n }\n if (/^https?:\\/\\//.test(text.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction createBlockId(): string {\n return `block_${Date.now()}_${randomUUID().slice(0, 8)}`;\n}\n\nconst IMAGE_MARKDOWN = /^!\\[([^\\]]*)\\]\\(([^)]+)\\)$/;\n\nfunction noteTextToBlocks(text?: string, noteId?: string): NoteBlock[] | undefined {\n if (!text?.trim()) return undefined;\n const now = Date.now();\n return text.split(/\\n{2,}/).map((part) => {\n const trimmed = part.trim();\n if (noteId) {\n const imageMatch = trimmed.match(IMAGE_MARKDOWN);\n if (imageMatch) {\n const attachmentId = attachmentIdFromTarget(imageMatch[2], noteId);\n if (attachmentId) {\n return {\n id: createBlockId(),\n type: 'image' as const,\n attachmentId,\n alt: imageMatch[1] || undefined,\n createdAt: now,\n updatedAt: now,\n };\n }\n }\n }\n return {\n id: createBlockId(),\n type: 'paragraph' as const,\n text: trimmed,\n createdAt: now,\n updatedAt: now,\n };\n });\n}\n\nfunction blocksToPlainText(blocks?: NoteBlock[], noteId?: string): string | undefined {\n if (!blocks?.length) return undefined;\n return blocks\n .map((block) => {\n if (block.type === 'divider') return '---';\n if (block.type === 'todo') return `${block.checked ? '[x]' : '[ ]'} ${block.text}`;\n if (block.type === 'image') {\n if (noteId) {\n return `![${block.alt ?? ''}](${buildNoteAttachmentRef(noteId, block.attachmentId)})`;\n }\n return block.alt ?? '';\n }\n return block.text;\n })\n .filter((text) => text.trim().length > 0)\n .join('\\n\\n');\n}\n\nfunction createAiOrganizedBlocks(blocks: NoteBlock[], instruction: string): NoteBlock[] {\n const now = Date.now();\n const plainText = blocksToPlainText(blocks) || '';\n const lines = plainText\n .split(/\\n+/)\n .map((line) => line.trim())\n .filter(Boolean);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n\n if (wantsTodos) {\n const candidates = lines.filter((line) => /要|需|记得|todo|task|完成|提交|联系|跟进|提醒/i.test(line));\n return (candidates.length ? candidates : lines).slice(0, 12).map((line) => ({\n id: createBlockId(),\n type: 'todo' as const,\n text: line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim(),\n checked: false,\n createdAt: now,\n updatedAt: now,\n }));\n }\n\n if (wantsSummary) {\n const summary = lines.join(' ').slice(0, 220);\n return [{\n id: createBlockId(),\n type: 'paragraph',\n text: summary,\n createdAt: now,\n updatedAt: now,\n }];\n }\n\n const titleText = lines[0]?.slice(0, 40) || '整理后的笔记';\n const bodyLines = lines.slice(1).length ? lines.slice(1) : lines;\n return [\n {\n id: createBlockId(),\n type: 'heading',\n text: titleText,\n level: 2,\n createdAt: now,\n updatedAt: now,\n },\n ...bodyLines.map((line) => ({\n id: createBlockId(),\n type: 'bulletList' as const,\n text: line.replace(/^[-*\\d.\\s]+/, '').trim(),\n indent: 0,\n createdAt: now,\n updatedAt: now,\n })),\n ];\n}\n\nconst SNAPSHOT_THROTTLE_MS = 60_000;\nconst MAX_SNAPSHOTS_PER_NOTE = 30;\n\nexport class NotesService {\n private store: NotesStore;\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(store: NotesStore) {\n this.store = store;\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n log.debug('NotesService initialized');\n }\n\n async quickCapture(text: string, source: CaptureSource): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = noteTextToBlocks(text, id);\n const note: Note = {\n id,\n kind: inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Quick capture');\n return note;\n }\n\n async createNote(params: CreateNoteParams): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = params.blocks ?? noteTextToBlocks(params.text, id);\n const text = params.text ?? blocksToPlainText(blocks, id);\n const note: Note = {\n id,\n title: params.title,\n kind: params.kind || inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Note created');\n return note;\n }\n\n async getNote(id: string): Promise<Note | null> {\n return this.store.getNote(id);\n }\n\n async updateNote(id: string, patch: Partial<Note>, trigger: SnapshotTrigger = 'edit'): Promise<Note | null> {\n const existing = await this.store.getNote(id);\n if (!existing) return null;\n\n const contentTouched = patch.text !== undefined || patch.blocks !== undefined || patch.title !== undefined;\n if (contentTouched) {\n await this.maybeSaveSnapshot(existing, trigger);\n }\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.blocks) {\n normalizedPatch.text = patch.text ?? blocksToPlainText(patch.blocks, existing.id);\n } else if (typeof patch.text === 'string') {\n normalizedPatch.blocks = patch.blocks ?? noteTextToBlocks(patch.text, existing.id);\n }\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 0) + 1;\n\n if (contentTouched) {\n const merged: Note = {\n ...existing,\n ...normalizedPatch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: Date.now(),\n };\n const reconciled = await this.reconcileAttachments(merged);\n normalizedPatch.attachments = reconciled.attachments;\n normalizedPatch.kind = reconciled.kind;\n }\n\n return this.store.updateNote(id, normalizedPatch);\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n\n for (const attachment of removed) {\n await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n }\n\n log.debug(\n { noteId: note.id, removedIds: removed.map((attachment) => attachment.id) },\n 'Pruned orphan note attachments',\n );\n\n const hasAttachments = kept.length > 0;\n return {\n ...note,\n attachments: hasAttachments ? kept : undefined,\n kind: inferKind(note.text, hasAttachments, kept),\n };\n }\n\n async syncNote(\n id: string,\n patch: Partial<Note>,\n baseRemoteVersion?: number,\n ): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n\n const currentRemoteVersion = existing.remoteVersion ?? 0;\n if (baseRemoteVersion !== undefined && baseRemoteVersion < currentRemoteVersion) {\n return { note: existing, conflict: true };\n }\n\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(\n id: string,\n instruction: string,\n blocks?: NoteBlock[],\n ): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n\n const sourceBlocks = blocks?.length ? blocks : note.blocks ?? noteTextToBlocks(note.text, note.id) ?? [];\n const organizedBlocks = createAiOrganizedBlocks(sourceBlocks, instruction);\n const patch: NoteAiPatch = {\n id: randomUUID(),\n summary: `已根据「${instruction.slice(0, 40)}」生成可预览的块级整理建议`,\n operations: [{ type: 'replaceBlocks', blocks: organizedBlocks }],\n };\n\n return {\n message: 'AI edit patch generated',\n patch,\n };\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const deleted = await this.store.deleteNote(id);\n if (deleted) {\n await this.store.deleteAllSnapshots(id);\n this.lastSnapshotAt.delete(id);\n }\n return deleted;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number }> {\n return this.store.listNotes(query);\n }\n\n async addAttachment(\n noteId: string,\n file: { name: string; buffer: Buffer; mimeType: string; duration?: number },\n ): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: inferAttachmentType(file.mimeType),\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n\n const attachments = [...(note.attachments || []), attachment];\n const kind: NoteKind =\n note.kind === 'thought' && attachment.type === 'audio'\n ? 'voice'\n : note.kind === 'thought'\n ? 'media'\n : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n\n return attachment;\n }\n\n async getAttachmentPath(\n noteId: string,\n attachmentId: string,\n ): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n\n const fullPath = this.store.resolveAttachmentPath(noteId, attachment.relativePath);\n return { filePath: fullPath, mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> {\n return this.store.listSnapshots(noteId);\n }\n\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> {\n return this.store.getSnapshot(noteId, timestamp);\n }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n if (!snapshot) return null;\n const existing = await this.store.getNote(noteId);\n if (!existing) return null;\n\n await this.store.saveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n\n return this.store.updateNote(noteId, {\n title: snapshot.title,\n text: snapshot.text,\n blocks: snapshot.blocks,\n tags: snapshot.tags,\n });\n }\n\n async flush(): Promise<void> {\n await this.store.flush();\n }\n\n // ── Space grouping ──────────────────────────────────────────────────\n\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> {\n return this.updateNote(noteId, { groupId: groupId ?? undefined });\n }\n\n // ── Task lifecycle ──────────────────────────────────────────────────\n\n async createTask(\n title: string,\n source: CaptureSource,\n options?: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string },\n ): Promise<Note> {\n return this.createNote({\n title,\n kind: 'task',\n capturedVia: source,\n groupId: options?.groupId,\n taskMeta: {\n done: false,\n dueAt: options?.dueAt,\n priority: options?.priority,\n sourceSessionKey: options?.sourceSessionKey,\n sourceNoteId: options?.sourceNoteId,\n },\n });\n }\n\n async toggleTaskDone(noteId: string): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n const done = !note.taskMeta?.done;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done },\n status: done ? 'archived' : 'processed',\n });\n }\n\n async updateTaskMeta(noteId: string, patch: Partial<import('./types.js').NoteTaskMeta>): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch },\n });\n }\n\n // ── Open tracking ──────────────────────────────────────────────────\n\n async recordOpen(noteId: string): Promise<Note | null> {\n return this.updateNote(noteId, { lastOpenedAt: Date.now() } as Partial<Note>);\n }\n\n private async maybeSaveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n if (trigger !== 'edit') {\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n return;\n }\n const last = this.lastSnapshotAt.get(note.id) ?? 0;\n if (Date.now() - last < SNAPSHOT_THROTTLE_MS) return;\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n }\n}\n\nfunction inferAttachmentType(mimeType: string): NoteAttachment['type'] {\n if (mimeType.startsWith('image/')) return 'image';\n if (mimeType.startsWith('video/')) return 'video';\n if (mimeType.startsWith('audio/')) return 'audio';\n return 'file';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../../src/notes/service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteAttachmentRef, attachmentIdFromTarget } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\n NoteBlock,\n NoteCatalysisAction,\n NoteCatalysisMeta,\n NoteCatalysisReport,\n NoteIndexEntry,\n NoteKind,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesService');\n\nfunction inferKind(\n text?: string,\n hasAttachments?: boolean,\n attachments?: NoteAttachment[],\n): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) {\n return 'voice';\n }\n if (hasAttachments) return 'media';\n if (!text) return 'thought';\n const lower = text.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) ||\n /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(text)) {\n return 'todo';\n }\n if (/^https?:\\/\\//.test(text.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction deriveDefaultTitle(text?: string): string | undefined {\n const normalizedText = text?.trim().replace(/\\s+/g, ' ');\n if (!normalizedText) return undefined;\n return Array.from(normalizedText).slice(0, 10).join('');\n}\n\nfunction createBlockId(): string {\n return `block_${Date.now()}_${randomUUID().slice(0, 8)}`;\n}\n\nconst IMAGE_MARKDOWN = /^!\\[([^\\]]*)\\]\\(([^)]+)\\)$/;\n\nfunction noteTextToBlocks(text?: string, noteId?: string): NoteBlock[] | undefined {\n if (!text?.trim()) return undefined;\n const now = Date.now();\n return text.split(/\\n{2,}/).map((part) => {\n const trimmed = part.trim();\n if (noteId) {\n const imageMatch = trimmed.match(IMAGE_MARKDOWN);\n if (imageMatch) {\n const attachmentId = attachmentIdFromTarget(imageMatch[2], noteId);\n if (attachmentId) {\n return {\n id: createBlockId(),\n type: 'image' as const,\n attachmentId,\n alt: imageMatch[1] || undefined,\n createdAt: now,\n updatedAt: now,\n };\n }\n }\n }\n return {\n id: createBlockId(),\n type: 'paragraph' as const,\n text: trimmed,\n createdAt: now,\n updatedAt: now,\n };\n });\n}\n\nfunction blocksToPlainText(blocks?: NoteBlock[], noteId?: string): string | undefined {\n if (!blocks?.length) return undefined;\n return blocks\n .map((block) => {\n if (block.type === 'divider') return '---';\n if (block.type === 'todo') return `${block.checked ? '[x]' : '[ ]'} ${block.text}`;\n if (block.type === 'image') {\n if (noteId) {\n return `![${block.alt ?? ''}](${buildNoteAttachmentRef(noteId, block.attachmentId)})`;\n }\n return block.alt ?? '';\n }\n return block.text;\n })\n .filter((text) => text.trim().length > 0)\n .join('\\n\\n');\n}\n\nfunction createAiOrganizedBlocks(blocks: NoteBlock[], instruction: string): NoteBlock[] {\n const now = Date.now();\n const plainText = blocksToPlainText(blocks) || '';\n const lines = plainText\n .split(/\\n+/)\n .map((line) => line.trim())\n .filter(Boolean);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n\n if (wantsTodos) {\n const candidates = lines.filter((line) => /要|需|记得|todo|task|完成|提交|联系|跟进|提醒/i.test(line));\n return (candidates.length ? candidates : lines).slice(0, 12).map((line) => ({\n id: createBlockId(),\n type: 'todo' as const,\n text: line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim(),\n checked: false,\n createdAt: now,\n updatedAt: now,\n }));\n }\n\n if (wantsSummary) {\n const summary = lines.join(' ').slice(0, 220);\n return [{\n id: createBlockId(),\n type: 'paragraph',\n text: summary,\n createdAt: now,\n updatedAt: now,\n }];\n }\n\n const titleText = lines[0]?.slice(0, 40) || '整理后的笔记';\n const bodyLines = lines.slice(1).length ? lines.slice(1) : lines;\n return [\n {\n id: createBlockId(),\n type: 'heading',\n text: titleText,\n level: 2,\n createdAt: now,\n updatedAt: now,\n },\n ...bodyLines.map((line) => ({\n id: createBlockId(),\n type: 'bulletList' as const,\n text: line.replace(/^[-*\\d.\\s]+/, '').trim(),\n indent: 0,\n createdAt: now,\n updatedAt: now,\n })),\n ];\n}\n\nfunction splitMeaningfulLines(text?: string): string[] {\n return (text ?? '')\n .split(/[\\n。!?!?;;]+/)\n .map((line) => line.trim().replace(/^[-*\\d.\\s]+/, ''))\n .filter((line) => line.length > 0);\n}\n\nfunction summarizeIdea(text?: string): string {\n const lines = splitMeaningfulLines(text);\n const joined = lines.join(' ');\n return Array.from(joined || '这个想法').slice(0, 90).join('');\n}\n\nfunction inferCatalysisStage(note: Note): NonNullable<NoteCatalysisMeta['stage']> {\n const text = `${note.title ?? ''}\\n${note.text ?? ''}`;\n if (/发布|上线|分享|推广|launch|ship/i.test(text)) return 'shipped';\n if (/验证|实验|用户|指标|反馈|validate|experiment/i.test(text)) return 'validating';\n if (/实现|开发|MVP|原型|prototype|build/i.test(text)) return 'developing';\n if (text.trim().length > 80) return 'incubating';\n return 'seed';\n}\n\nfunction buildCatalysisReport(note: Note): NoteCatalysisReport {\n const generatedAt = Date.now();\n const summary = summarizeIdea(note.text ?? note.title);\n const title = note.title?.trim() || summary.slice(0, 28) || '未命名想法';\n const hasUserSignal = /用户|客户|读者|创作者|团队|个人|开发者|founder|creator|user/i.test(summary);\n const hasProductSignal = /产品|工具|平台|workflow|agent|AI|自动|系统|应用/i.test(summary);\n const confidence = Math.min(0.86, Math.max(0.42, 0.48 + (hasUserSignal ? 0.16 : 0) + (hasProductSignal ? 0.18 : 0) + Math.min(summary.length, 120) / 600));\n\n return {\n originalNoteId: note.id,\n generatedAt,\n title,\n valueHypothesis: `如果把「${summary}」推进成一个可体验的小成果,它最可能的价值是帮助用户更快完成判断、表达或行动。`,\n targetUsers: hasUserSignal ? ['笔记作者自己', '有类似场景的目标用户'] : ['笔记作者自己', '未来可能被这个想法帮助的人'],\n keyQuestions: [\n '这个想法最想解决的具体痛点是什么?',\n '第一个可验证的用户场景是什么?',\n '什么样的最小成果能证明它值得继续投入?',\n ],\n mvpPath: [\n '把想法改写成一句清晰的问题陈述。',\n '列出 1 个目标用户和 1 个高频使用场景。',\n '产出一个最小原型、提纲或行动清单。',\n ],\n risks: [\n '想法还停留在概念层,缺少明确使用场景。',\n '下一步过大时容易变成长期搁置的项目。',\n ],\n nextActions: [\n { kind: 'chat', text: '和 AI 继续深聊这个想法,收敛成问题陈述。' },\n { kind: 'research', text: '补充 3 个相似产品、案例或用户反馈。' },\n { kind: 'task', text: '写下今天能完成的一个最小推进动作。' },\n ],\n confidence: Number(confidence.toFixed(2)),\n };\n}\n\nfunction stripJsonCodeFence(raw: string): string {\n return raw.trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/i, '').trim();\n}\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n const text = stripJsonCodeFence(raw);\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? data as Record<string, unknown> : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? data as Record<string, unknown> : null;\n } catch {\n return null;\n }\n }\n}\n\nfunction extractAssistantText(result: { content?: unknown }): string {\n if (!Array.isArray(result.content)) return '';\n return result.content\n .filter((block): block is { type: string; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('')\n .trim();\n}\n\nfunction stringArray(value: unknown, fallback: string[], limit: number): string[] {\n if (!Array.isArray(value)) return fallback;\n const items = value\n .map((item) => typeof item === 'string' ? item.trim() : '')\n .filter(Boolean)\n .slice(0, limit);\n return items.length ? items : fallback;\n}\n\nfunction catalysisActions(value: unknown, fallback: NoteCatalysisAction[]): NoteCatalysisAction[] {\n if (!Array.isArray(value)) return fallback;\n const allowed = new Set<NoteCatalysisAction['kind']>(['task', 'workflow', 'research', 'share', 'chat']);\n const items: NoteCatalysisAction[] = [];\n for (const item of value) {\n if (!item || typeof item !== 'object') continue;\n const row = item as { kind?: unknown; text?: unknown };\n const kind = typeof row.kind === 'string' && allowed.has(row.kind as NoteCatalysisAction['kind'])\n ? row.kind as NoteCatalysisAction['kind']\n : 'task';\n const text = typeof row.text === 'string' ? row.text.trim() : '';\n if (text) items.push({ kind, text });\n }\n return items.length ? items.slice(0, 5) : fallback;\n}\n\nfunction normalizeAiCatalysisReport(note: Note, data: Record<string, unknown>): NoteCatalysisReport {\n const fallback = buildCatalysisReport(note);\n const confidenceRaw = typeof data.confidence === 'number' ? data.confidence : fallback.confidence;\n const confidence = Math.min(1, Math.max(0, confidenceRaw));\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title: typeof data.title === 'string' && data.title.trim() ? data.title.trim().slice(0, 80) : fallback.title,\n valueHypothesis: typeof data.valueHypothesis === 'string' && data.valueHypothesis.trim()\n ? data.valueHypothesis.trim()\n : fallback.valueHypothesis,\n targetUsers: stringArray(data.targetUsers, fallback.targetUsers, 5),\n keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions, 6),\n mvpPath: stringArray(data.mvpPath, fallback.mvpPath, 6),\n risks: stringArray(data.risks, fallback.risks, 5),\n nextActions: catalysisActions(data.nextActions, fallback.nextActions),\n confidence: Number(confidence.toFixed(2)),\n };\n}\n\nfunction buildCatalysisPrompt(note: Note): string {\n const title = note.title?.trim() || '未命名笔记';\n const text = (note.text ?? '').trim() || '(无正文)';\n return `你是一个帮助用户把想法推进成成果的个人 AI Agent。请基于这条 Note 做“想法催化”,帮助用户进入:想法 → 创造 → 分享 → 反馈 的循环。\\n\\n要求:\\n- 使用自然、具体的中文。\\n- 不要空泛鼓励,要给出可执行路径。\\n- 输出必须是单个 JSON 对象,不要 Markdown,不要代码块。\\n- 字段必须包含:title, valueHypothesis, targetUsers, keyQuestions, mvpPath, risks, nextActions, confidence。\\n- nextActions 每项格式为 {\"kind\":\"task|workflow|research|share|chat\",\"text\":\"...\"}。\\n- confidence 是 0 到 1 的数字。\\n\\nNote 标题:${title}\\n\\nNote 正文:\\n${text.slice(0, 6000)}`;\n}\n\nasync function buildAiCatalysisReport(note: Note, config?: Config): Promise<NoteCatalysisReport> {\n const model = resolveModel(getDefaultModelSync(config));\n const user: UserMessage = {\n role: 'user',\n content: buildCatalysisPrompt(note),\n timestamp: Date.now(),\n };\n const result = await complete(\n model,\n { messages: [user] },\n { maxTokens: 1800, temperature: 0.2 },\n );\n const text = extractAssistantText(result);\n const data = extractJsonObject(text);\n if (!data) {\n throw new Error('Catalysis model did not return valid JSON');\n }\n return normalizeAiCatalysisReport(note, data);\n}\n\nconst SNAPSHOT_THROTTLE_MS = 60_000;\nconst MAX_SNAPSHOTS_PER_NOTE = 30;\n\nexport class NotesService {\n private store: NotesStore;\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(store: NotesStore) {\n this.store = store;\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n log.debug('NotesService initialized');\n }\n\n async quickCapture(text: string, source: CaptureSource): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = noteTextToBlocks(text, id);\n const note: Note = {\n id,\n title: deriveDefaultTitle(text),\n kind: inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Quick capture');\n return note;\n }\n\n async createNote(params: CreateNoteParams): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = params.blocks ?? noteTextToBlocks(params.text, id);\n const text = params.text ?? blocksToPlainText(blocks, id);\n const note: Note = {\n id,\n title: params.title,\n kind: params.kind || inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Note created');\n return note;\n }\n\n async getNote(id: string): Promise<Note | null> {\n return this.store.getNote(id);\n }\n\n async updateNote(id: string, patch: Partial<Note>, trigger: SnapshotTrigger = 'edit'): Promise<Note | null> {\n const existing = await this.store.getNote(id);\n if (!existing) return null;\n\n const contentTouched = patch.text !== undefined || patch.blocks !== undefined || patch.title !== undefined;\n if (contentTouched) {\n await this.maybeSaveSnapshot(existing, trigger);\n }\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.blocks) {\n normalizedPatch.text = patch.text ?? blocksToPlainText(patch.blocks, existing.id);\n } else if (typeof patch.text === 'string') {\n normalizedPatch.blocks = patch.blocks ?? noteTextToBlocks(patch.text, existing.id);\n }\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 0) + 1;\n\n if (contentTouched) {\n const merged: Note = {\n ...existing,\n ...normalizedPatch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: Date.now(),\n };\n const reconciled = await this.reconcileAttachments(merged);\n normalizedPatch.attachments = reconciled.attachments;\n normalizedPatch.kind = reconciled.kind;\n }\n\n return this.store.updateNote(id, normalizedPatch);\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n\n for (const attachment of removed) {\n await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n }\n\n log.debug(\n { noteId: note.id, removedIds: removed.map((attachment) => attachment.id) },\n 'Pruned orphan note attachments',\n );\n\n const hasAttachments = kept.length > 0;\n return {\n ...note,\n attachments: hasAttachments ? kept : undefined,\n kind: inferKind(note.text, hasAttachments, kept),\n };\n }\n\n async syncNote(\n id: string,\n patch: Partial<Note>,\n baseRemoteVersion?: number,\n ): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n\n const currentRemoteVersion = existing.remoteVersion ?? 0;\n if (baseRemoteVersion !== undefined && baseRemoteVersion < currentRemoteVersion) {\n return { note: existing, conflict: true };\n }\n\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(\n id: string,\n instruction: string,\n blocks?: NoteBlock[],\n ): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n\n const sourceBlocks = blocks?.length ? blocks : note.blocks ?? noteTextToBlocks(note.text, note.id) ?? [];\n const organizedBlocks = createAiOrganizedBlocks(sourceBlocks, instruction);\n const patch: NoteAiPatch = {\n id: randomUUID(),\n summary: `已根据「${instruction.slice(0, 40)}」生成可预览的块级整理建议`,\n operations: [{ type: 'replaceBlocks', blocks: organizedBlocks }],\n };\n\n return {\n message: 'AI edit patch generated',\n patch,\n };\n }\n\n async catalyzeNote(id: string, config?: Config): Promise<{ note: Note; report: NoteCatalysisReport } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n\n let report: NoteCatalysisReport;\n try {\n report = await buildAiCatalysisReport(note, config);\n } catch (err) {\n log.warn({ err, noteId: id }, 'AI note catalysis failed; using local fallback report');\n report = buildCatalysisReport(note);\n }\n\n const existingDeep = note.aiDeep;\n const catalysis: NoteCatalysisMeta = {\n ...existingDeep?.catalysis,\n status: 'catalyzed',\n stage: inferCatalysisStage(note),\n lastCatalyzedAt: report.generatedAt,\n confidence: report.confidence,\n report,\n };\n\n const updated = await this.updateNote(id, {\n ai: {\n ...note.ai,\n intent: note.ai?.intent ?? 'idea',\n summary: note.ai?.summary ?? report.valueHypothesis,\n },\n aiDeep: {\n ...existingDeep,\n processedAt: report.generatedAt,\n insights: report.valueHypothesis,\n catalysis,\n },\n status: note.status === 'inbox' ? 'processed' : note.status,\n }, 'ai_edit');\n\n return updated ? { note: updated, report } : null;\n }\n\n async recordCatalysisFeedback(\n id: string,\n feedback: NonNullable<NoteCatalysisMeta['feedback']>,\n ): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const now = Date.now();\n return this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: now,\n catalysis: {\n status: note.aiDeep?.catalysis?.status ?? 'catalyzed',\n ...note.aiDeep?.catalysis,\n feedback,\n },\n },\n }, 'ai_edit');\n }\n\n async linkNoteThread(id: string, sessionKey: string): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const existingKeys = note.aiDeep?.catalysis?.linkedSessionKeys ?? [];\n const linkedSessionKeys = Array.from(new Set([sessionKey, ...existingKeys]));\n return this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: note.aiDeep?.catalysis?.status ?? 'none',\n ...note.aiDeep?.catalysis,\n sourceSessionKey: note.aiDeep?.catalysis?.sourceSessionKey ?? sessionKey,\n linkedSessionKeys,\n },\n },\n }, 'sync');\n }\n\n async listNoteThreads(id: string): Promise<string[] | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const keys = [\n note.aiDeep?.catalysis?.sourceSessionKey,\n ...(note.aiDeep?.catalysis?.linkedSessionKeys ?? []),\n ].filter((key): key is string => typeof key === 'string' && key.length > 0);\n return Array.from(new Set(keys));\n }\n\n async appendTextToNote(id: string, content: string, heading = 'AI 讨论沉淀'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const trimmed = content.trim();\n if (!trimmed) return note;\n const currentText = note.text?.trimEnd() ?? '';\n const nextText = `${currentText}${currentText ? '\\n\\n' : ''}## ${heading}\\n\\n${trimmed}`;\n return this.updateNote(id, { text: nextText }, 'ai_edit');\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const deleted = await this.store.deleteNote(id);\n if (deleted) {\n await this.store.deleteAllSnapshots(id);\n this.lastSnapshotAt.delete(id);\n }\n return deleted;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean }> {\n return this.store.listNotes(query);\n }\n\n async addAttachment(\n noteId: string,\n file: { name: string; buffer: Buffer; mimeType: string; duration?: number },\n ): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: inferAttachmentType(file.mimeType),\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n\n const attachments = [...(note.attachments || []), attachment];\n const kind: NoteKind =\n note.kind === 'thought' && attachment.type === 'audio'\n ? 'voice'\n : note.kind === 'thought'\n ? 'media'\n : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n\n return attachment;\n }\n\n async getAttachmentPath(\n noteId: string,\n attachmentId: string,\n ): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n\n const fullPath = this.store.resolveAttachmentPath(noteId, attachment.relativePath);\n return { filePath: fullPath, mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> {\n return this.store.listSnapshots(noteId);\n }\n\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> {\n return this.store.getSnapshot(noteId, timestamp);\n }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n if (!snapshot) return null;\n const existing = await this.store.getNote(noteId);\n if (!existing) return null;\n\n await this.store.saveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n\n return this.store.updateNote(noteId, {\n title: snapshot.title,\n text: snapshot.text,\n blocks: snapshot.blocks,\n tags: snapshot.tags,\n });\n }\n\n async flush(): Promise<void> {\n await this.store.flush();\n }\n\n // ── Space grouping ──────────────────────────────────────────────────\n\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> {\n return this.updateNote(noteId, { groupId: groupId ?? undefined });\n }\n\n // ── Task lifecycle ──────────────────────────────────────────────────\n\n async createTask(\n title: string,\n source: CaptureSource,\n options?: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string },\n ): Promise<Note> {\n return this.createNote({\n title,\n kind: 'task',\n capturedVia: source,\n groupId: options?.groupId,\n taskMeta: {\n done: false,\n dueAt: options?.dueAt,\n priority: options?.priority,\n sourceSessionKey: options?.sourceSessionKey,\n sourceNoteId: options?.sourceNoteId,\n },\n });\n }\n\n async toggleTaskDone(noteId: string): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n const done = !note.taskMeta?.done;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done },\n status: done ? 'archived' : 'processed',\n });\n }\n\n async updateTaskMeta(noteId: string, patch: Partial<import('./types.js').NoteTaskMeta>): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch },\n });\n }\n\n // ── Open tracking ──────────────────────────────────────────────────\n\n async recordOpen(noteId: string): Promise<Note | null> {\n return this.updateNote(noteId, { lastOpenedAt: Date.now() } as Partial<Note>);\n }\n\n private async maybeSaveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n if (trigger !== 'edit') {\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n return;\n }\n const last = this.lastSnapshotAt.get(note.id) ?? 0;\n if (Date.now() - last < SNAPSHOT_THROTTLE_MS) return;\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n }\n}\n\nfunction inferAttachmentType(mimeType: string): NoteAttachment['type'] {\n if (mimeType.startsWith('image/')) return 'image';\n if (mimeType.startsWith('video/')) return 'video';\n if (mimeType.startsWith('audio/')) return 'audio';\n return 'file';\n}\n"],"mappings":"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"}
@@ -12,6 +12,9 @@ export declare class NotesStore {
12
12
  listNotes(query?: NotesListQuery): Promise<{
13
13
  items: NoteIndexEntry[];
14
14
  total: number;
15
+ limit: number;
16
+ offset: number;
17
+ hasMore: boolean;
15
18
  }>;
16
19
  private noteIndexEntryMatchesSearch;
17
20
  saveAttachment(noteId: string, fileName: string, buffer: Buffer): Promise<{
@@ -1,11 +1,11 @@
1
+ import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
1
2
  import { createLogger } from "../utils/logger/index.js";
2
3
  import { init_logger } from "../utils/logger.js";
3
- import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
4
4
  import { buildNoteIndexMeta, notePlainText } from "./note-index-meta.js";
5
5
  import { resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath } from "./paths.js";
6
- import { join } from "node:path";
7
6
  import { randomUUID } from "node:crypto";
8
7
  import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
9
  //#region src/notes/store.ts
10
10
  init_write_file_atomic();
11
11
  init_logger();
@@ -150,9 +150,13 @@ var NotesStore = class {
150
150
  const total = results.length;
151
151
  const offset = query.offset || 0;
152
152
  const limit = Math.min(query.limit || 50, 200);
153
+ const items = results.slice(offset, offset + limit);
153
154
  return {
154
- items: results.slice(offset, offset + limit),
155
- total
155
+ items,
156
+ total,
157
+ limit,
158
+ offset,
159
+ hasMore: offset + items.length < total
156
160
  };
157
161
  }
158
162
  noteIndexEntryMatchesSearch(entry, term) {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":[],"sources":["../../../src/notes/store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile, access, mkdir, writeFile, rm, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { writeTextAtomic } from '../infra/write-file-atomic.js';\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteIndexMeta, notePlainText } from './note-index-meta.js';\nimport { resolveNotesDir, resolveNotesIndexPath, resolveNoteItemPath, resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';\nimport type {\n Note,\n NoteIndexEntry,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesIndexFile,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesStore');\n\nconst DEFAULT_INDEX: NotesIndexFile = { version: 3, notes: [] };\nconst INDEX_VERSION = 3;\nconst DEBOUNCE_MS = 500;\n\nfunction noteToIndexEntry(note: Note): NoteIndexEntry {\n const { snippet, coverAttachmentId, voiceAttachmentId, voiceDurationSec, attachmentNames } = buildNoteIndexMeta(note);\n return {\n id: note.id,\n title: note.title || undefined,\n kind: note.kind,\n status: note.status,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n pinned: note.pinned || undefined,\n tags: note.tags?.length ? note.tags : undefined,\n snippet,\n coverAttachmentId,\n voiceAttachmentId,\n voiceDurationSec,\n attachmentNames,\n groupId: note.groupId || undefined,\n lastOpenedAt: note.lastOpenedAt || undefined,\n taskDone: note.taskMeta?.done,\n taskDueAt: note.taskMeta?.dueAt,\n };\n}\n\nexport class NotesStore {\n private indexCache: NotesIndexFile | null = null;\n private dirty = false;\n private saveTimeout: ReturnType<typeof setTimeout> | null = null;\n private initialized = false;\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n const indexPath = resolveNotesIndexPath();\n try {\n await access(indexPath);\n await this.loadIndex();\n if ((this.indexCache?.version ?? 0) < INDEX_VERSION) {\n await this.rebuildIndexFromItems();\n }\n } catch {\n await this.writeIndex(DEFAULT_INDEX);\n this.indexCache = DEFAULT_INDEX;\n }\n this.initialized = true;\n log.debug('NotesStore initialized');\n }\n\n async addNote(note: Note): Promise<void> {\n const index = await this.loadIndex();\n await this.writeNoteItem(note);\n index.notes.push(noteToIndexEntry(note));\n index.version++;\n this.scheduleIndexSave(index);\n }\n\n async getNote(id: string): Promise<Note | null> {\n const itemPath = resolveNoteItemPath(id);\n try {\n const content = await readFile(itemPath, 'utf-8');\n return JSON.parse(content) as Note;\n } catch (err) {\n const code = err && typeof err === 'object' && 'code' in err\n ? (err as NodeJS.ErrnoException).code : '';\n if (code !== 'ENOENT') {\n log.debug({ err, id }, 'Failed to read note item');\n }\n return null;\n }\n }\n\n async updateNote(id: string, patch: Partial<Note>): Promise<Note | null> {\n const existing = await this.getNote(id);\n if (!existing) return null;\n\n const updated: Note = {\n ...existing,\n ...patch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: Date.now(),\n };\n\n await this.writeNoteItem(updated);\n\n const index = await this.loadIndex();\n const idx = index.notes.findIndex((n) => n.id === id);\n if (idx !== -1) {\n index.notes[idx] = noteToIndexEntry(updated);\n }\n index.version++;\n this.scheduleIndexSave(index);\n\n return updated;\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const existing = await this.getNote(id);\n if (!existing) return false;\n\n const itemPath = resolveNoteItemPath(id);\n await rm(itemPath, { force: true }).catch((err) => {\n log.warn({ err, id }, 'Failed to remove note item file');\n });\n\n const mediaDir = resolveNoteMediaDir(id);\n await rm(mediaDir, { recursive: true, force: true }).catch(() => undefined);\n\n const index = await this.loadIndex();\n const before = index.notes.length;\n index.notes = index.notes.filter((n) => n.id !== id);\n if (index.notes.length === before) {\n log.debug({ id }, 'Deleted note file but index entry was missing');\n }\n index.version++;\n this.scheduleIndexSave(index);\n\n return true;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number }> {\n const index = await this.loadIndex();\n let results = index.notes;\n\n if (query.status) {\n results = results.filter((n) => n.status === query.status);\n } else {\n results = results.filter((n) => n.status !== 'trashed');\n }\n if (query.kind) {\n results = results.filter((n) => n.kind === query.kind);\n }\n if (query.tag) {\n results = results.filter((n) => n.tags?.includes(query.tag!));\n }\n if (query.pinned !== undefined) {\n results = results.filter((n) => Boolean(n.pinned) === query.pinned);\n }\n if (query.groupId !== undefined) {\n if (query.groupId === 'ungrouped') {\n results = results.filter((n) => !n.groupId);\n } else {\n results = results.filter((n) => n.groupId === query.groupId);\n }\n }\n if (query.pendingTasksOnly) {\n results = results.filter((n) => n.kind === 'task' && !n.taskDone);\n }\n if (query.search) {\n const term = query.search.toLowerCase();\n const indexMatches = results.filter((n) => this.noteIndexEntryMatchesSearch(n, term));\n const indexMatchedIds = new Set(indexMatches.map((n) => n.id));\n const contentMatches: NoteIndexEntry[] = [];\n const candidates = results.filter((n) => !indexMatchedIds.has(n.id));\n for (const candidate of candidates) {\n const note = await this.getNote(candidate.id);\n if (!note) continue;\n const content = [note.title, notePlainText(note), note.attachments?.map((a) => a.transcript).join(' ')]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n if (content.includes(term)) {\n contentMatches.push(candidate);\n }\n }\n results = [...indexMatches, ...contentMatches];\n }\n\n const sortField = query.sortBy || 'createdAt';\n const sortDir = query.sortOrder === 'asc' ? 1 : -1;\n results = [...results].sort((a, b) => {\n const aVal = a[sortField] ?? 0;\n const bVal = b[sortField] ?? 0;\n return (aVal - bVal) * sortDir;\n });\n\n const total = results.length;\n const offset = query.offset || 0;\n const limit = Math.min(query.limit || 50, 200);\n const items = results.slice(offset, offset + limit);\n\n return { items, total };\n }\n\n private noteIndexEntryMatchesSearch(entry: NoteIndexEntry, term: string): boolean {\n return Boolean(\n entry.title?.toLowerCase().includes(term) ||\n entry.snippet?.toLowerCase().includes(term) ||\n entry.tags?.some((tag) => tag.toLowerCase().includes(term)) ||\n entry.attachmentNames?.some((name) => name.toLowerCase().includes(term)),\n );\n }\n\n async saveAttachment(\n noteId: string,\n fileName: string,\n buffer: Buffer,\n ): Promise<{ relativePath: string; size: number }> {\n const mediaDir = resolveNoteMediaDir(noteId);\n await mkdir(mediaDir, { recursive: true });\n const safeName = `${randomUUID().slice(0, 8)}_${fileName.replace(/[^a-zA-Z0-9._-]/g, '_')}`;\n const filePath = join(mediaDir, safeName);\n await writeFile(filePath, buffer);\n return { relativePath: safeName, size: buffer.length };\n }\n\n resolveAttachmentPath(noteId: string, relativePath: string): string {\n return join(resolveNoteMediaDir(noteId), relativePath);\n }\n\n async deleteAttachmentFile(noteId: string, relativePath: string): Promise<void> {\n const filePath = this.resolveAttachmentPath(noteId, relativePath);\n await rm(filePath, { force: true }).catch((err) => {\n log.warn({ err, noteId, relativePath }, 'Failed to remove note attachment file');\n });\n }\n\n async saveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n const historyDir = resolveNoteHistoryDir(note.id);\n await mkdir(historyDir, { recursive: true });\n const snapshot: NoteSnapshot = {\n noteId: note.id,\n timestamp: Date.now(),\n trigger,\n title: note.title,\n text: note.text,\n blocks: note.blocks,\n tags: note.tags,\n kind: note.kind,\n status: note.status,\n };\n const filePath = join(historyDir, `${snapshot.timestamp}.json`);\n await writeTextAtomic(filePath, JSON.stringify(snapshot, null, 2));\n log.debug({ noteId: note.id, trigger, timestamp: snapshot.timestamp }, 'Snapshot saved');\n }\n\n async listSnapshots(noteId: string): Promise<NoteSnapshotEntry[]> {\n const historyDir = resolveNoteHistoryDir(noteId);\n let files: string[];\n try {\n files = await readdir(historyDir);\n } catch {\n return [];\n }\n const entries: NoteSnapshotEntry[] = [];\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n const timestamp = parseInt(file.slice(0, -'.json'.length), 10);\n if (!Number.isFinite(timestamp)) continue;\n try {\n const content = await readFile(join(historyDir, file), 'utf-8');\n const snapshot = JSON.parse(content) as NoteSnapshot;\n const rawText = snapshot.text ?? '';\n entries.push({\n timestamp: snapshot.timestamp,\n trigger: snapshot.trigger,\n snippet: rawText.slice(0, 80) || undefined,\n });\n } catch {\n log.debug({ noteId, file }, 'Skipped unreadable snapshot');\n }\n }\n entries.sort((a, b) => b.timestamp - a.timestamp);\n return entries;\n }\n\n async getSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> {\n const filePath = join(resolveNoteHistoryDir(noteId), `${timestamp}.json`);\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as NoteSnapshot;\n } catch {\n return null;\n }\n }\n\n async pruneSnapshots(noteId: string, maxCount: number): Promise<void> {\n const historyDir = resolveNoteHistoryDir(noteId);\n let files: string[];\n try {\n files = await readdir(historyDir);\n } catch {\n return;\n }\n const jsonFiles = files\n .filter((f) => f.endsWith('.json'))\n .sort();\n if (jsonFiles.length <= maxCount) return;\n const toDelete = jsonFiles.slice(0, jsonFiles.length - maxCount);\n for (const file of toDelete) {\n await rm(join(historyDir, file), { force: true }).catch(() => undefined);\n }\n log.debug({ noteId, deleted: toDelete.length }, 'Pruned old snapshots');\n }\n\n async deleteAllSnapshots(noteId: string): Promise<void> {\n const historyDir = resolveNoteHistoryDir(noteId);\n await rm(historyDir, { recursive: true, force: true }).catch(() => undefined);\n }\n\n async flush(): Promise<void> {\n if (!this.dirty || !this.indexCache) return;\n if (this.saveTimeout) {\n clearTimeout(this.saveTimeout);\n this.saveTimeout = null;\n }\n await this.writeIndex(this.indexCache);\n this.dirty = false;\n }\n\n private async loadIndex(): Promise<NotesIndexFile> {\n if (this.indexCache) return this.indexCache;\n const indexPath = resolveNotesIndexPath();\n try {\n const content = await readFile(indexPath, 'utf-8');\n const data = JSON.parse(content) as NotesIndexFile;\n if (!data.notes || !Array.isArray(data.notes)) {\n log.warn('Notes index invalid, resetting');\n this.indexCache = DEFAULT_INDEX;\n return this.indexCache;\n }\n this.indexCache = data;\n return data;\n } catch {\n this.indexCache = DEFAULT_INDEX;\n return this.indexCache;\n }\n }\n\n private async writeIndex(data: NotesIndexFile): Promise<void> {\n const indexPath = resolveNotesIndexPath();\n await writeTextAtomic(indexPath, JSON.stringify(data, null, 2));\n log.debug({ count: data.notes.length }, 'Notes index saved');\n }\n\n private async writeNoteItem(note: Note): Promise<void> {\n const itemPath = resolveNoteItemPath(note.id);\n await writeTextAtomic(itemPath, JSON.stringify(note, null, 2));\n }\n\n private scheduleIndexSave(data: NotesIndexFile): void {\n this.indexCache = data;\n this.dirty = true;\n if (this.saveTimeout) {\n clearTimeout(this.saveTimeout);\n }\n this.saveTimeout = setTimeout(() => {\n this.flush().catch((err) => {\n log.error({ err }, 'Failed to flush notes index');\n });\n }, DEBOUNCE_MS);\n }\n\n private async rebuildIndexFromItems(): Promise<void> {\n const itemsDir = join(resolveNotesDir(), 'items');\n let files: string[];\n try {\n files = await readdir(itemsDir);\n } catch {\n this.indexCache = DEFAULT_INDEX;\n await this.writeIndex(DEFAULT_INDEX);\n return;\n }\n\n const entries: NoteIndexEntry[] = [];\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n const noteId = file.slice(0, -'.json'.length);\n const note = await this.getNote(noteId);\n if (note) {\n entries.push(noteToIndexEntry(note));\n }\n }\n\n entries.sort((a, b) => b.createdAt - a.createdAt);\n const index: NotesIndexFile = { version: INDEX_VERSION, notes: entries };\n this.indexCache = index;\n await this.writeIndex(index);\n log.debug({ count: entries.length }, 'Notes index rebuilt');\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":[],"sources":["../../../src/notes/store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile, access, mkdir, writeFile, rm, readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { writeTextAtomic } from '../infra/write-file-atomic.js';\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteIndexMeta, notePlainText } from './note-index-meta.js';\nimport { resolveNotesDir, resolveNotesIndexPath, resolveNoteItemPath, resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';\nimport type {\n Note,\n NoteIndexEntry,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesIndexFile,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesStore');\n\nconst DEFAULT_INDEX: NotesIndexFile = { version: 3, notes: [] };\nconst INDEX_VERSION = 3;\nconst DEBOUNCE_MS = 500;\n\nfunction noteToIndexEntry(note: Note): NoteIndexEntry {\n const { snippet, coverAttachmentId, voiceAttachmentId, voiceDurationSec, attachmentNames } = buildNoteIndexMeta(note);\n return {\n id: note.id,\n title: note.title || undefined,\n kind: note.kind,\n status: note.status,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n pinned: note.pinned || undefined,\n tags: note.tags?.length ? note.tags : undefined,\n snippet,\n coverAttachmentId,\n voiceAttachmentId,\n voiceDurationSec,\n attachmentNames,\n groupId: note.groupId || undefined,\n lastOpenedAt: note.lastOpenedAt || undefined,\n taskDone: note.taskMeta?.done,\n taskDueAt: note.taskMeta?.dueAt,\n };\n}\n\nexport class NotesStore {\n private indexCache: NotesIndexFile | null = null;\n private dirty = false;\n private saveTimeout: ReturnType<typeof setTimeout> | null = null;\n private initialized = false;\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n const indexPath = resolveNotesIndexPath();\n try {\n await access(indexPath);\n await this.loadIndex();\n if ((this.indexCache?.version ?? 0) < INDEX_VERSION) {\n await this.rebuildIndexFromItems();\n }\n } catch {\n await this.writeIndex(DEFAULT_INDEX);\n this.indexCache = DEFAULT_INDEX;\n }\n this.initialized = true;\n log.debug('NotesStore initialized');\n }\n\n async addNote(note: Note): Promise<void> {\n const index = await this.loadIndex();\n await this.writeNoteItem(note);\n index.notes.push(noteToIndexEntry(note));\n index.version++;\n this.scheduleIndexSave(index);\n }\n\n async getNote(id: string): Promise<Note | null> {\n const itemPath = resolveNoteItemPath(id);\n try {\n const content = await readFile(itemPath, 'utf-8');\n return JSON.parse(content) as Note;\n } catch (err) {\n const code = err && typeof err === 'object' && 'code' in err\n ? (err as NodeJS.ErrnoException).code : '';\n if (code !== 'ENOENT') {\n log.debug({ err, id }, 'Failed to read note item');\n }\n return null;\n }\n }\n\n async updateNote(id: string, patch: Partial<Note>): Promise<Note | null> {\n const existing = await this.getNote(id);\n if (!existing) return null;\n\n const updated: Note = {\n ...existing,\n ...patch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: Date.now(),\n };\n\n await this.writeNoteItem(updated);\n\n const index = await this.loadIndex();\n const idx = index.notes.findIndex((n) => n.id === id);\n if (idx !== -1) {\n index.notes[idx] = noteToIndexEntry(updated);\n }\n index.version++;\n this.scheduleIndexSave(index);\n\n return updated;\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const existing = await this.getNote(id);\n if (!existing) return false;\n\n const itemPath = resolveNoteItemPath(id);\n await rm(itemPath, { force: true }).catch((err) => {\n log.warn({ err, id }, 'Failed to remove note item file');\n });\n\n const mediaDir = resolveNoteMediaDir(id);\n await rm(mediaDir, { recursive: true, force: true }).catch(() => undefined);\n\n const index = await this.loadIndex();\n const before = index.notes.length;\n index.notes = index.notes.filter((n) => n.id !== id);\n if (index.notes.length === before) {\n log.debug({ id }, 'Deleted note file but index entry was missing');\n }\n index.version++;\n this.scheduleIndexSave(index);\n\n return true;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean }> {\n const index = await this.loadIndex();\n let results = index.notes;\n\n if (query.status) {\n results = results.filter((n) => n.status === query.status);\n } else {\n results = results.filter((n) => n.status !== 'trashed');\n }\n if (query.kind) {\n results = results.filter((n) => n.kind === query.kind);\n }\n if (query.tag) {\n results = results.filter((n) => n.tags?.includes(query.tag!));\n }\n if (query.pinned !== undefined) {\n results = results.filter((n) => Boolean(n.pinned) === query.pinned);\n }\n if (query.groupId !== undefined) {\n if (query.groupId === 'ungrouped') {\n results = results.filter((n) => !n.groupId);\n } else {\n results = results.filter((n) => n.groupId === query.groupId);\n }\n }\n if (query.pendingTasksOnly) {\n results = results.filter((n) => n.kind === 'task' && !n.taskDone);\n }\n if (query.search) {\n const term = query.search.toLowerCase();\n const indexMatches = results.filter((n) => this.noteIndexEntryMatchesSearch(n, term));\n const indexMatchedIds = new Set(indexMatches.map((n) => n.id));\n const contentMatches: NoteIndexEntry[] = [];\n const candidates = results.filter((n) => !indexMatchedIds.has(n.id));\n for (const candidate of candidates) {\n const note = await this.getNote(candidate.id);\n if (!note) continue;\n const content = [note.title, notePlainText(note), note.attachments?.map((a) => a.transcript).join(' ')]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n if (content.includes(term)) {\n contentMatches.push(candidate);\n }\n }\n results = [...indexMatches, ...contentMatches];\n }\n\n const sortField = query.sortBy || 'createdAt';\n const sortDir = query.sortOrder === 'asc' ? 1 : -1;\n results = [...results].sort((a, b) => {\n const aVal = a[sortField] ?? 0;\n const bVal = b[sortField] ?? 0;\n return (aVal - bVal) * sortDir;\n });\n\n const total = results.length;\n const offset = query.offset || 0;\n const limit = Math.min(query.limit || 50, 200);\n const items = results.slice(offset, offset + limit);\n const hasMore = offset + items.length < total;\n\n return { items, total, limit, offset, hasMore };\n }\n\n private noteIndexEntryMatchesSearch(entry: NoteIndexEntry, term: string): boolean {\n return Boolean(\n entry.title?.toLowerCase().includes(term) ||\n entry.snippet?.toLowerCase().includes(term) ||\n entry.tags?.some((tag) => tag.toLowerCase().includes(term)) ||\n entry.attachmentNames?.some((name) => name.toLowerCase().includes(term)),\n );\n }\n\n async saveAttachment(\n noteId: string,\n fileName: string,\n buffer: Buffer,\n ): Promise<{ relativePath: string; size: number }> {\n const mediaDir = resolveNoteMediaDir(noteId);\n await mkdir(mediaDir, { recursive: true });\n const safeName = `${randomUUID().slice(0, 8)}_${fileName.replace(/[^a-zA-Z0-9._-]/g, '_')}`;\n const filePath = join(mediaDir, safeName);\n await writeFile(filePath, buffer);\n return { relativePath: safeName, size: buffer.length };\n }\n\n resolveAttachmentPath(noteId: string, relativePath: string): string {\n return join(resolveNoteMediaDir(noteId), relativePath);\n }\n\n async deleteAttachmentFile(noteId: string, relativePath: string): Promise<void> {\n const filePath = this.resolveAttachmentPath(noteId, relativePath);\n await rm(filePath, { force: true }).catch((err) => {\n log.warn({ err, noteId, relativePath }, 'Failed to remove note attachment file');\n });\n }\n\n async saveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n const historyDir = resolveNoteHistoryDir(note.id);\n await mkdir(historyDir, { recursive: true });\n const snapshot: NoteSnapshot = {\n noteId: note.id,\n timestamp: Date.now(),\n trigger,\n title: note.title,\n text: note.text,\n blocks: note.blocks,\n tags: note.tags,\n kind: note.kind,\n status: note.status,\n };\n const filePath = join(historyDir, `${snapshot.timestamp}.json`);\n await writeTextAtomic(filePath, JSON.stringify(snapshot, null, 2));\n log.debug({ noteId: note.id, trigger, timestamp: snapshot.timestamp }, 'Snapshot saved');\n }\n\n async listSnapshots(noteId: string): Promise<NoteSnapshotEntry[]> {\n const historyDir = resolveNoteHistoryDir(noteId);\n let files: string[];\n try {\n files = await readdir(historyDir);\n } catch {\n return [];\n }\n const entries: NoteSnapshotEntry[] = [];\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n const timestamp = parseInt(file.slice(0, -'.json'.length), 10);\n if (!Number.isFinite(timestamp)) continue;\n try {\n const content = await readFile(join(historyDir, file), 'utf-8');\n const snapshot = JSON.parse(content) as NoteSnapshot;\n const rawText = snapshot.text ?? '';\n entries.push({\n timestamp: snapshot.timestamp,\n trigger: snapshot.trigger,\n snippet: rawText.slice(0, 80) || undefined,\n });\n } catch {\n log.debug({ noteId, file }, 'Skipped unreadable snapshot');\n }\n }\n entries.sort((a, b) => b.timestamp - a.timestamp);\n return entries;\n }\n\n async getSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> {\n const filePath = join(resolveNoteHistoryDir(noteId), `${timestamp}.json`);\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as NoteSnapshot;\n } catch {\n return null;\n }\n }\n\n async pruneSnapshots(noteId: string, maxCount: number): Promise<void> {\n const historyDir = resolveNoteHistoryDir(noteId);\n let files: string[];\n try {\n files = await readdir(historyDir);\n } catch {\n return;\n }\n const jsonFiles = files\n .filter((f) => f.endsWith('.json'))\n .sort();\n if (jsonFiles.length <= maxCount) return;\n const toDelete = jsonFiles.slice(0, jsonFiles.length - maxCount);\n for (const file of toDelete) {\n await rm(join(historyDir, file), { force: true }).catch(() => undefined);\n }\n log.debug({ noteId, deleted: toDelete.length }, 'Pruned old snapshots');\n }\n\n async deleteAllSnapshots(noteId: string): Promise<void> {\n const historyDir = resolveNoteHistoryDir(noteId);\n await rm(historyDir, { recursive: true, force: true }).catch(() => undefined);\n }\n\n async flush(): Promise<void> {\n if (!this.dirty || !this.indexCache) return;\n if (this.saveTimeout) {\n clearTimeout(this.saveTimeout);\n this.saveTimeout = null;\n }\n await this.writeIndex(this.indexCache);\n this.dirty = false;\n }\n\n private async loadIndex(): Promise<NotesIndexFile> {\n if (this.indexCache) return this.indexCache;\n const indexPath = resolveNotesIndexPath();\n try {\n const content = await readFile(indexPath, 'utf-8');\n const data = JSON.parse(content) as NotesIndexFile;\n if (!data.notes || !Array.isArray(data.notes)) {\n log.warn('Notes index invalid, resetting');\n this.indexCache = DEFAULT_INDEX;\n return this.indexCache;\n }\n this.indexCache = data;\n return data;\n } catch {\n this.indexCache = DEFAULT_INDEX;\n return this.indexCache;\n }\n }\n\n private async writeIndex(data: NotesIndexFile): Promise<void> {\n const indexPath = resolveNotesIndexPath();\n await writeTextAtomic(indexPath, JSON.stringify(data, null, 2));\n log.debug({ count: data.notes.length }, 'Notes index saved');\n }\n\n private async writeNoteItem(note: Note): Promise<void> {\n const itemPath = resolveNoteItemPath(note.id);\n await writeTextAtomic(itemPath, JSON.stringify(note, null, 2));\n }\n\n private scheduleIndexSave(data: NotesIndexFile): void {\n this.indexCache = data;\n this.dirty = true;\n if (this.saveTimeout) {\n clearTimeout(this.saveTimeout);\n }\n this.saveTimeout = setTimeout(() => {\n this.flush().catch((err) => {\n log.error({ err }, 'Failed to flush notes index');\n });\n }, DEBOUNCE_MS);\n }\n\n private async rebuildIndexFromItems(): Promise<void> {\n const itemsDir = join(resolveNotesDir(), 'items');\n let files: string[];\n try {\n files = await readdir(itemsDir);\n } catch {\n this.indexCache = DEFAULT_INDEX;\n await this.writeIndex(DEFAULT_INDEX);\n return;\n }\n\n const entries: NoteIndexEntry[] = [];\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n const noteId = file.slice(0, -'.json'.length);\n const note = await this.getNote(noteId);\n if (note) {\n entries.push(noteToIndexEntry(note));\n }\n }\n\n entries.sort((a, b) => b.createdAt - a.createdAt);\n const index: NotesIndexFile = { version: INDEX_VERSION, notes: entries };\n this.indexCache = index;\n await this.writeIndex(index);\n log.debug({ count: entries.length }, 'Notes index rebuilt');\n }\n}\n"],"mappings":"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"}
@@ -71,12 +71,43 @@ export interface NoteAiMeta {
71
71
  }>;
72
72
  suggestedTags?: string[];
73
73
  }
74
+ export interface NoteCatalysisAction {
75
+ text: string;
76
+ kind: 'task' | 'workflow' | 'research' | 'share' | 'chat';
77
+ }
78
+ export interface NoteCatalysisReport {
79
+ originalNoteId: string;
80
+ generatedAt: number;
81
+ title: string;
82
+ valueHypothesis: string;
83
+ targetUsers: string[];
84
+ keyQuestions: string[];
85
+ mvpPath: string[];
86
+ risks: string[];
87
+ nextActions: NoteCatalysisAction[];
88
+ confidence: number;
89
+ }
90
+ export interface NoteCatalysisMeta {
91
+ status: 'none' | 'queued' | 'catalyzed' | 'snoozed' | 'dismissed';
92
+ stage?: 'seed' | 'incubating' | 'developing' | 'validating' | 'shipped';
93
+ lastCatalyzedAt?: number;
94
+ nextCatalyzeAt?: number;
95
+ feedback?: 'helpful' | 'not_helpful' | 'neutral';
96
+ confidence?: number;
97
+ report?: NoteCatalysisReport;
98
+ reportNoteId?: string;
99
+ sourceSessionKey?: string;
100
+ linkedSessionKeys?: string[];
101
+ linkedWorkflowRunIds?: string[];
102
+ linkedShareIds?: string[];
103
+ }
74
104
  export interface NoteAiDeepMeta {
75
105
  processedAt: number;
76
106
  priority?: 'high' | 'medium' | 'low';
77
107
  relatedNoteIds?: string[];
78
108
  relatedGoalId?: string;
79
109
  insights?: string;
110
+ catalysis?: NoteCatalysisMeta;
80
111
  }
81
112
  export interface CaptureSource {
82
113
  channel: CaptureChannel;
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
- import { dirname, join } from "node:path";
4
3
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
5
  //#region src/providers/auth-runtime/auth-profile-store.ts
6
6
  /**
7
7
  * {@link AuthProfileStore} implementations.
@@ -1,8 +1,8 @@
1
1
  import { __esmMin, __exportAll } from "../../_virtual/_rolldown/runtime.js";
2
- import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
3
2
  import { PROVIDER_ENV_MAP, getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
4
- import { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry } from "./model-registry.js";
5
3
  import { CredentialResolver, hasCredentials, init_credentials, resolveApiKey } from "../auth/credentials.js";
4
+ import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
5
+ import { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry } from "./model-registry.js";
6
6
  import { getProviderRegistry, init_plugin_registry } from "./plugin-registry.js";
7
7
  import { getModel, getModels, getProviders } from "@earendil-works/pi-ai";
8
8
  //#region src/providers/index.ts