@xopcai/xopc 0.0.104 → 0.0.106

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 (184) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/gateway-setup.js +2 -2
  3. package/dist/extensions/feishu/src/adapters/gateway-setup.js.map +1 -1
  4. package/dist/extensions/feishu/src/directory/directory-adapter.js +1 -1
  5. package/dist/extensions/feishu/src/directory/directory-adapter.js.map +1 -1
  6. package/dist/extensions/feishu/src/outbound/outbound-adapter.js +4 -3
  7. package/dist/extensions/feishu/src/outbound/outbound-adapter.js.map +1 -1
  8. package/dist/extensions/feishu/src/plugin.d.ts +6 -3
  9. package/dist/extensions/feishu/src/plugin.js +59 -40
  10. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  11. package/dist/extensions/feishu/src/streaming/streaming-adapter.js +20 -16
  12. package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
  13. package/dist/extensions/feishu/src/transport/socket-mode/monitor.js +1 -1
  14. package/dist/extensions/feishu/src/transport/webhook/monitor.js +1 -1
  15. package/dist/extensions/feishu/src/workflow-progress.d.ts +1 -1
  16. package/dist/extensions/feishu/src/workflow-progress.js +2 -2
  17. package/dist/extensions/feishu/src/workflow-progress.js.map +1 -1
  18. package/dist/extensions/telegram/src/adapters/gateway-setup.js +8 -6
  19. package/dist/extensions/telegram/src/adapters/gateway-setup.js.map +1 -1
  20. package/dist/extensions/telegram/src/command-handler.d.ts +1 -1
  21. package/dist/extensions/telegram/src/command-handler.js +11 -9
  22. package/dist/extensions/telegram/src/command-handler.js.map +1 -1
  23. package/dist/extensions/telegram/src/inbound-processor.js +1 -2
  24. package/dist/extensions/telegram/src/inbound-processor.js.map +1 -1
  25. package/dist/extensions/telegram/src/plugin.d.ts +1 -0
  26. package/dist/extensions/telegram/src/plugin.js +6 -6
  27. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  28. package/dist/extensions/telegram/src/workflow-progress.d.ts +1 -1
  29. package/dist/extensions/telegram/src/workflow-progress.js.map +1 -1
  30. package/dist/extensions/telegram/xopc.extension.json +1 -1
  31. package/dist/extensions/weixin/src/cli/gateway-qr-login.d.ts +4 -0
  32. package/dist/extensions/weixin/src/cli/gateway-qr-login.js +34 -5
  33. package/dist/extensions/weixin/src/cli/gateway-qr-login.js.map +1 -1
  34. package/dist/extensions/weixin/src/cli/qr-login.js +4 -3
  35. package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
  36. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  37. package/dist/extensions/weixin/src/plugin.js +7 -6
  38. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  39. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  40. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  41. package/dist/gateway/static/root/assets/agents-3eP0vqFO.js +396 -0
  42. package/dist/gateway/static/root/assets/{apps-page-L-IwQTe0.js → apps-page-CZ2ZKrko.js} +1 -1
  43. package/dist/gateway/static/root/assets/channels-settings-BbETa93K.js +1 -0
  44. package/dist/gateway/static/root/assets/cron-page-DCtUkHMu.js +1 -0
  45. package/dist/gateway/static/root/assets/{dist-C6vclgEL.js → dist-OM9qGfm5.js} +1 -1
  46. package/dist/gateway/static/root/assets/{extension-debug-page-D4JYAbsX.js → extension-debug-page-D-WHrr_w.js} +1 -1
  47. package/dist/gateway/static/root/assets/{extension-page-_T62gi6e.js → extension-page-CCgjSoKn.js} +1 -1
  48. package/dist/gateway/static/root/assets/{extension-settings-page-CkJbjyM-.js → extension-settings-page-BJH5a-Gn.js} +1 -1
  49. package/dist/gateway/static/root/assets/{fetch-B5aiIguv.js → fetch-CkvRO30i.js} +1 -1
  50. package/dist/gateway/static/root/assets/{gateway-startup-retry-DqmLXhs6.js → gateway-startup-retry-BkKyRDrF.js} +1 -1
  51. package/dist/gateway/static/root/assets/{heartbeat-config-api-HwJV1K_Y.js → heartbeat-config-api-CVyP6pFj.js} +1 -1
  52. package/dist/gateway/static/root/assets/index-CEZxETzw.js +4845 -0
  53. package/dist/gateway/static/root/assets/{logs-page-DsjtTjBy.js → logs-page-R6Vv_Ja6.js} +2 -2
  54. package/dist/gateway/static/root/assets/{note-detail-page-B2V7RhgD.js → note-detail-page-BUDarLhh.js} +54 -54
  55. package/dist/gateway/static/root/assets/{note-time-BG8GCTr4.js → note-time-Cc2-T21g.js} +1 -1
  56. package/dist/gateway/static/root/assets/{notes-page-xrcVBQ6Z.js → notes-page-EAHl16bq.js} +1 -1
  57. package/dist/gateway/static/root/assets/{schema-form-S-QUvhul.js → schema-form-CrlOmEZt.js} +1 -1
  58. package/dist/gateway/static/root/assets/sessions-page-Ce_xuuSB.js +1 -0
  59. package/dist/gateway/static/root/assets/{settings-advanced-gate-DujQl9c7.js → settings-advanced-gate-BBAQxxew.js} +1 -1
  60. package/dist/gateway/static/root/assets/{settings-form-section-r1V7dRV6.js → settings-form-section-DjPD-ErC.js} +1 -1
  61. package/dist/gateway/static/root/assets/settings-page-KhQ9QgH0.js +3 -0
  62. package/dist/gateway/static/root/assets/{share-preview-page-DOFy0FNt.js → share-preview-page-BjvPht3V.js} +1 -1
  63. package/dist/gateway/static/root/assets/skills-page-C0laq7l1.js +2 -0
  64. package/dist/gateway/static/root/assets/{theme-store-Bo3j5Ovv.js → theme-store-DYgVdRB4.js} +1 -1
  65. package/dist/gateway/static/root/assets/url-DPEStU4M.js +3 -0
  66. package/dist/gateway/static/root/assets/{voice-api-key-field-AjmUAlEQ.js → voice-api-key-field-DeGckWkl.js} +1 -1
  67. package/dist/gateway/static/root/assets/{workflow-page.utils-DLEJ92gK.js → workflow-page.utils-D4sqR0yX.js} +1 -1
  68. package/dist/gateway/static/root/assets/workflows-page-DFjM95g5.js +27 -0
  69. package/dist/gateway/static/root/index.html +5 -5
  70. package/dist/package.js +1 -1
  71. package/dist/src/agent/context/workspace-seed.js +1 -1
  72. package/dist/src/agent/inbound/turn-dispatcher.d.ts +2 -0
  73. package/dist/src/agent/inbound/turn-dispatcher.js +1 -0
  74. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  75. package/dist/src/agent/mcp/bundle-mcp-config.js +16 -8
  76. package/dist/src/agent/mcp/bundle-mcp-config.js.map +1 -1
  77. package/dist/src/agent/mcp/bundle-mcp-policy.js +2 -2
  78. package/dist/src/agent/mcp/bundle-mcp-policy.js.map +1 -1
  79. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  80. package/dist/src/agent/mcp/mcp-http.js +1 -1
  81. package/dist/src/agent/mcp/mcp-http.js.map +1 -1
  82. package/dist/src/agent/mcp/mcp-transport-config.js +2 -6
  83. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  84. package/dist/src/agent/prompt/memory/index.js +30 -2
  85. package/dist/src/agent/prompt/memory/index.js.map +1 -1
  86. package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
  87. package/dist/src/agent/service/direct-turn-helpers.js +5 -2
  88. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  89. package/dist/src/agent/service/process-direct-streaming.d.ts +2 -0
  90. package/dist/src/agent/service/process-direct-streaming.js +15 -1
  91. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  92. package/dist/src/agent/service.js +2 -1
  93. package/dist/src/agent/service.js.map +1 -1
  94. package/dist/src/agent/service.types.d.ts +3 -0
  95. package/dist/src/agent/skills/hub-pull.js +1 -1
  96. package/dist/src/agent/source-context/index.d.ts +2 -0
  97. package/dist/src/agent/source-context/index.js +3 -0
  98. package/dist/src/agent/source-context/injector.d.ts +3 -0
  99. package/dist/src/agent/source-context/injector.js +36 -0
  100. package/dist/src/agent/source-context/injector.js.map +1 -0
  101. package/dist/src/agent/source-context/types.d.ts +19 -0
  102. package/dist/src/agent/source-context/types.js +10 -0
  103. package/dist/src/agent/source-context/types.js.map +1 -0
  104. package/dist/src/agent/tools/factory.js +5 -5
  105. package/dist/src/agent/tools/index.js +3 -3
  106. package/dist/src/agent/tools/session-search-tool.js +1 -1
  107. package/dist/src/agent/tools/shell.js +1 -1
  108. package/dist/src/agent/tools/web-extract.js +2 -2
  109. package/dist/src/agent/tools/web.js +1 -1
  110. package/dist/src/agent/workflow/index.js +2 -1
  111. package/dist/src/agent/workflow/runtime.d.ts +2 -2
  112. package/dist/src/agent/workflow/runtime.js +1 -14
  113. package/dist/src/agent/workflow/runtime.js.map +1 -1
  114. package/dist/src/agent/workflow/snapshot-empty.d.ts +2 -0
  115. package/dist/src/agent/workflow/snapshot-empty.js +19 -0
  116. package/dist/src/agent/workflow/snapshot-empty.js.map +1 -0
  117. package/dist/src/agent/workflow/snapshot.js +1 -1
  118. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  119. package/dist/src/browser/actions/extension-registry-bridge.js +1 -1
  120. package/dist/src/browser/actions/registry.js +2 -2
  121. package/dist/src/browser/cdp-local-launcher.js +1 -1
  122. package/dist/src/browser/index.js +3 -3
  123. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  124. package/dist/src/browser/providers/cloakbrowser.js +1 -1
  125. package/dist/src/cli/commands/browser-cli-helpers.js +1 -1
  126. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  127. package/dist/src/config/paths.d.ts +1 -0
  128. package/dist/src/config/paths.js +5 -1
  129. package/dist/src/config/paths.js.map +1 -1
  130. package/dist/src/connectors/health.js +1 -1
  131. package/dist/src/connectors/install.js +1 -1
  132. package/dist/src/document-understanding/extract.d.ts +13 -0
  133. package/dist/src/document-understanding/extract.js +63 -0
  134. package/dist/src/document-understanding/extract.js.map +1 -0
  135. package/dist/src/gateway/hono/routes/mcp.js +1 -1
  136. package/dist/src/gateway/hono/routes/notes.js +46 -27
  137. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  138. package/dist/src/gateway/service.js +11 -0
  139. package/dist/src/gateway/service.js.map +1 -1
  140. package/dist/src/mcp/channel-bridge.js +1 -1
  141. package/dist/src/notes/agent-context.d.ts +40 -0
  142. package/dist/src/notes/agent-context.js +383 -0
  143. package/dist/src/notes/agent-context.js.map +1 -0
  144. package/dist/src/notes/index.d.ts +2 -0
  145. package/dist/src/notes/index.js +2 -1
  146. package/dist/src/notes/service.d.ts +5 -0
  147. package/dist/src/notes/service.js +22 -0
  148. package/dist/src/notes/service.js.map +1 -1
  149. package/dist/src/notes/store.js +2 -0
  150. package/dist/src/notes/store.js.map +1 -1
  151. package/dist/src/share/share-thumbnail.js +1 -1
  152. package/dist/src/storage/sqlite/index.d.ts +1 -0
  153. package/dist/src/storage/sqlite/index.js +2 -1
  154. package/dist/src/storage/sqlite/migrations/005_add_note_agent_contexts.sql +10 -0
  155. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  156. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  157. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  158. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  159. package/dist/src/storage/sqlite/note-agent-context-repository.d.ts +10 -0
  160. package/dist/src/storage/sqlite/note-agent-context-repository.js +35 -0
  161. package/dist/src/storage/sqlite/note-agent-context-repository.js.map +1 -0
  162. package/dist/src/storage/sqlite/schema.js +1 -1
  163. package/dist/src/tui/tui-startup-resources.js +1 -1
  164. package/dist/src/voice/stt/availability.d.ts +6 -0
  165. package/dist/src/voice/stt/availability.js +14 -6
  166. package/dist/src/voice/stt/availability.js.map +1 -1
  167. package/dist/src/voice/stt/index.js +2 -1
  168. package/dist/src/voice/stt/index.js.map +1 -1
  169. package/dist/src/voice/stt/mention.d.ts +2 -0
  170. package/dist/src/voice/stt/mention.js +20 -0
  171. package/dist/src/voice/stt/mention.js.map +1 -0
  172. package/dist/src/voice/stt/preflight.d.ts +1 -4
  173. package/dist/src/voice/stt/preflight.js +1 -18
  174. package/dist/src/voice/stt/preflight.js.map +1 -1
  175. package/package.json +4 -4
  176. package/dist/gateway/static/root/assets/agents-CvsBrBfi.js +0 -396
  177. package/dist/gateway/static/root/assets/channels-settings-D95KuPeP.js +0 -1
  178. package/dist/gateway/static/root/assets/cron-page-CfkWXDAI.js +0 -1
  179. package/dist/gateway/static/root/assets/index-DvTb6wNR.js +0 -4845
  180. package/dist/gateway/static/root/assets/sessions-page-CrQer6Au.js +0 -1
  181. package/dist/gateway/static/root/assets/settings-page-C9nrONQ2.js +0 -3
  182. package/dist/gateway/static/root/assets/skills-page-DmTsrs_a.js +0 -2
  183. package/dist/gateway/static/root/assets/url-WgQIjFZu.js +0 -3
  184. package/dist/gateway/static/root/assets/workflows-page-BnjjKrM1.js +0 -27
@@ -0,0 +1,383 @@
1
+ import { getNoteAgentContextRecord, upsertNoteAgentContextRecord } from "../storage/sqlite/note-agent-context-repository.js";
2
+ import "../storage/sqlite/index.js";
3
+ import { resolveImageModelConfigForTool } from "../agent/image/tool-model-config.js";
4
+ import { describeImagesWithFallback } from "../agent/image/understanding/runtime.js";
5
+ import { transcribe } from "../voice/stt/transcribe-core.js";
6
+ import { isSTTAvailable } from "../voice/stt/availability.js";
7
+ import "../voice/stt/index.js";
8
+ import { mergeSttConfigFromAppConfig } from "../channels/attachments/voice-stt-webchat.js";
9
+ import { extractDocumentText } from "../document-understanding/extract.js";
10
+ import { readFile } from "node:fs/promises";
11
+ import { extname } from "node:path";
12
+ //#region src/notes/agent-context.ts
13
+ const MAX_NOTE_MARKDOWN_CHARS = 4e4;
14
+ const MAX_TEXT_ATTACHMENT_CHARS = 2e4;
15
+ const MAX_TOTAL_CONTEXT_CHARS = 8e4;
16
+ const MAX_NATIVE_VISION_IMAGES = 4;
17
+ const MAX_NATIVE_VISION_IMAGE_BYTES = 8 * 1024 * 1024;
18
+ const TEXT_MIME_PREFIXES = ["text/"];
19
+ const TEXT_MIME_TYPES = new Set([
20
+ "application/json",
21
+ "application/ld+json",
22
+ "application/xml",
23
+ "application/yaml",
24
+ "application/x-yaml",
25
+ "application/javascript",
26
+ "application/typescript"
27
+ ]);
28
+ const TEXT_EXTENSIONS = new Set([
29
+ ".txt",
30
+ ".md",
31
+ ".markdown",
32
+ ".json",
33
+ ".jsonl",
34
+ ".csv",
35
+ ".tsv",
36
+ ".xml",
37
+ ".yaml",
38
+ ".yml",
39
+ ".log",
40
+ ".js",
41
+ ".ts",
42
+ ".tsx",
43
+ ".jsx",
44
+ ".css",
45
+ ".html",
46
+ ".sql"
47
+ ]);
48
+ const DOCUMENT_EXTENSIONS = new Set([
49
+ ".pdf",
50
+ ".doc",
51
+ ".docx",
52
+ ".xls",
53
+ ".xlsx",
54
+ ".ppt",
55
+ ".pptx"
56
+ ]);
57
+ function truncateText(text, maxChars) {
58
+ if (text.length <= maxChars) return {
59
+ text,
60
+ truncated: false
61
+ };
62
+ return {
63
+ text: `${text.slice(0, maxChars)}\n\n[truncated ${text.length - maxChars} chars]`,
64
+ truncated: true
65
+ };
66
+ }
67
+ function isTextLikeAttachment(att) {
68
+ const mime = att.mimeType.toLowerCase();
69
+ if (TEXT_MIME_PREFIXES.some((prefix) => mime.startsWith(prefix))) return true;
70
+ if (TEXT_MIME_TYPES.has(mime)) return true;
71
+ return TEXT_EXTENSIONS.has(extname(att.fileName).toLowerCase());
72
+ }
73
+ function isDocumentAttachment(att) {
74
+ const mime = att.mimeType.toLowerCase();
75
+ return mime === "application/pdf" || mime.includes("officedocument") || DOCUMENT_EXTENSIONS.has(extname(att.fileName).toLowerCase());
76
+ }
77
+ function formatTime(ms) {
78
+ return typeof ms === "number" && Number.isFinite(ms) ? new Date(ms).toISOString() : "unknown";
79
+ }
80
+ async function readAttachmentBuffer(notesService, noteId, att) {
81
+ const located = await notesService.getAttachmentPath(noteId, att.id);
82
+ if (!located) return null;
83
+ return readFile(located.filePath).catch(() => null);
84
+ }
85
+ async function attachmentTextExcerpt(notesService, noteId, att) {
86
+ if (!isTextLikeAttachment(att)) return null;
87
+ const located = await notesService.getAttachmentPath(noteId, att.id);
88
+ if (!located) return null;
89
+ const raw = await readFile(located.filePath, "utf8").catch(() => null);
90
+ if (raw === null) return null;
91
+ return truncateText(raw, MAX_TEXT_ATTACHMENT_CHARS).text;
92
+ }
93
+ async function ensureMediaTranscript(params) {
94
+ const existing = params.att.transcript?.trim();
95
+ if (existing) return existing;
96
+ if (params.att.type !== "audio" && params.att.type !== "video") return void 0;
97
+ const sttConfig = mergeSttConfigFromAppConfig(params.config?.tools?.media?.audio, params.config?.tools?.media);
98
+ if (!isSTTAvailable(sttConfig)) return void 0;
99
+ const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);
100
+ if (!buffer) return void 0;
101
+ const transcript = (await transcribe(buffer, sttConfig)).text?.trim();
102
+ if (!transcript) return void 0;
103
+ const attachments = (params.note.attachments ?? []).map((item) => item.id === params.att.id ? {
104
+ ...item,
105
+ transcript
106
+ } : item);
107
+ await params.notesService.updateNote(params.note.id, { attachments }, "ai_edit").catch(() => null);
108
+ params.att.transcript = transcript;
109
+ return transcript;
110
+ }
111
+ async function describeImageAttachment(params) {
112
+ const toolCfg = resolveImageModelConfigForTool({ cfg: params.config });
113
+ const primary = toolCfg?.primary;
114
+ if (!primary) return void 0;
115
+ const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);
116
+ if (!buffer) return void 0;
117
+ return (await describeImagesWithFallback({
118
+ modelRef: primary,
119
+ fallbacks: toolCfg.fallbacks ?? [],
120
+ images: [{
121
+ buffer,
122
+ mimeType: params.att.mimeType
123
+ }],
124
+ prompt: `Describe this note attachment for later AI discussion. File name: ${params.att.fileName}. Focus on visible text, diagrams, UI, objects, and any product-relevant details.`,
125
+ maxTokens: 1024,
126
+ timeoutMs: 3e4
127
+ })).text.trim() || void 0;
128
+ }
129
+ async function extractDocumentAttachment(params) {
130
+ const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);
131
+ if (!buffer) return void 0;
132
+ const result = extractDocumentText({
133
+ buffer,
134
+ fileName: params.att.fileName,
135
+ mimeType: params.att.mimeType
136
+ });
137
+ if (!result.ok) return void 0;
138
+ return truncateText(result.text, MAX_TEXT_ATTACHMENT_CHARS).text;
139
+ }
140
+ function fallbackImageSummary(att) {
141
+ return `Image attachment available for native vision models: ${att.fileName} (${att.mimeType}, ${att.size} bytes). Configure an image understanding model to persist a text summary for non-vision models.`;
142
+ }
143
+ function fallbackVideoSummary(att) {
144
+ return `Video attachment available: ${att.fileName} (${att.mimeType}, ${att.size} bytes). Audio transcription will be included when STT accepts this media.`;
145
+ }
146
+ function unsupportedDocumentSummary(att) {
147
+ return `Document extraction is not available for ${att.fileName} (${att.mimeType}).`;
148
+ }
149
+ async function buildAttachmentContext(params) {
150
+ const { note, att, notesService, config } = params;
151
+ try {
152
+ if (att.type === "audio") {
153
+ const transcript = await ensureMediaTranscript({
154
+ note,
155
+ att,
156
+ notesService,
157
+ config
158
+ });
159
+ return transcript ? {
160
+ attachmentId: att.id,
161
+ type: att.type,
162
+ fileName: att.fileName,
163
+ mimeType: att.mimeType,
164
+ size: att.size,
165
+ status: "ready",
166
+ transcript
167
+ } : {
168
+ attachmentId: att.id,
169
+ type: att.type,
170
+ fileName: att.fileName,
171
+ mimeType: att.mimeType,
172
+ size: att.size,
173
+ status: "unsupported",
174
+ summary: "Audio transcription is not configured or produced no transcript."
175
+ };
176
+ }
177
+ const extractedText = await attachmentTextExcerpt(notesService, note.id, att);
178
+ if (extractedText) return {
179
+ attachmentId: att.id,
180
+ type: att.type,
181
+ fileName: att.fileName,
182
+ mimeType: att.mimeType,
183
+ size: att.size,
184
+ status: "ready",
185
+ extractedText
186
+ };
187
+ if (att.type === "image") {
188
+ const summary = await describeImageAttachment({
189
+ note,
190
+ att,
191
+ notesService,
192
+ config
193
+ }).catch(() => void 0);
194
+ return {
195
+ attachmentId: att.id,
196
+ type: att.type,
197
+ fileName: att.fileName,
198
+ mimeType: att.mimeType,
199
+ size: att.size,
200
+ status: summary ? "ready" : "unsupported",
201
+ summary: summary ?? fallbackImageSummary(att)
202
+ };
203
+ }
204
+ if (att.type === "video") {
205
+ const transcript = await ensureMediaTranscript({
206
+ note,
207
+ att,
208
+ notesService,
209
+ config
210
+ }).catch(() => void 0);
211
+ return transcript ? {
212
+ attachmentId: att.id,
213
+ type: att.type,
214
+ fileName: att.fileName,
215
+ mimeType: att.mimeType,
216
+ size: att.size,
217
+ status: "ready",
218
+ transcript,
219
+ summary: fallbackVideoSummary(att)
220
+ } : {
221
+ attachmentId: att.id,
222
+ type: att.type,
223
+ fileName: att.fileName,
224
+ mimeType: att.mimeType,
225
+ size: att.size,
226
+ status: "unsupported",
227
+ summary: fallbackVideoSummary(att)
228
+ };
229
+ }
230
+ if (isDocumentAttachment(att)) {
231
+ const extractedDocumentText = await extractDocumentAttachment({
232
+ note,
233
+ att,
234
+ notesService
235
+ }).catch(() => void 0);
236
+ return extractedDocumentText ? {
237
+ attachmentId: att.id,
238
+ type: att.type,
239
+ fileName: att.fileName,
240
+ mimeType: att.mimeType,
241
+ size: att.size,
242
+ status: "ready",
243
+ extractedText: extractedDocumentText
244
+ } : {
245
+ attachmentId: att.id,
246
+ type: att.type,
247
+ fileName: att.fileName,
248
+ mimeType: att.mimeType,
249
+ size: att.size,
250
+ status: "unsupported",
251
+ summary: unsupportedDocumentSummary(att)
252
+ };
253
+ }
254
+ return {
255
+ attachmentId: att.id,
256
+ type: att.type,
257
+ fileName: att.fileName,
258
+ mimeType: att.mimeType,
259
+ size: att.size,
260
+ status: "unsupported",
261
+ summary: "No extractor is configured for this attachment type."
262
+ };
263
+ } catch (err) {
264
+ return {
265
+ attachmentId: att.id,
266
+ type: att.type,
267
+ fileName: att.fileName,
268
+ mimeType: att.mimeType,
269
+ size: att.size,
270
+ status: "failed",
271
+ error: err instanceof Error ? err.message : String(err)
272
+ };
273
+ }
274
+ }
275
+ function renderAttachmentContext(att) {
276
+ const lines = [
277
+ `### Attachment: ${att.fileName}`,
278
+ `- id: ${att.attachmentId}`,
279
+ `- type: ${att.type}`,
280
+ `- mimeType: ${att.mimeType}`,
281
+ `- size: ${att.size}`,
282
+ `- status: ${att.status}`
283
+ ];
284
+ if (att.transcript) lines.push("", "Transcript:", truncateText(att.transcript, MAX_TEXT_ATTACHMENT_CHARS).text);
285
+ if (att.extractedText) lines.push("", "Extracted text:", truncateText(att.extractedText, MAX_TEXT_ATTACHMENT_CHARS).text);
286
+ if (att.summary) lines.push("", "Summary:", att.summary);
287
+ if (att.error) lines.push("", "Error:", att.error);
288
+ return lines.join("\n");
289
+ }
290
+ async function loadNativeVisionImages(notesService, note) {
291
+ const images = [];
292
+ for (const att of note.attachments ?? []) {
293
+ if (att.type !== "image") continue;
294
+ if (!att.mimeType.toLowerCase().startsWith("image/")) continue;
295
+ if (att.size > MAX_NATIVE_VISION_IMAGE_BYTES) continue;
296
+ const buffer = await readAttachmentBuffer(notesService, note.id, att);
297
+ if (!buffer || buffer.length > MAX_NATIVE_VISION_IMAGE_BYTES) continue;
298
+ images.push({
299
+ type: "image",
300
+ data: buffer.toString("base64"),
301
+ mimeType: att.mimeType
302
+ });
303
+ if (images.length >= MAX_NATIVE_VISION_IMAGES) break;
304
+ }
305
+ return images;
306
+ }
307
+ async function buildNoteAgentContextArtifact(params) {
308
+ const { note, notesService, config, force } = params;
309
+ if (!force) {
310
+ const cached = getNoteAgentContextRecord(note.id);
311
+ if (cached && cached.noteUpdatedAt === note.updatedAt && cached.contextVersion === String(note.updatedAt)) return cached.payload;
312
+ }
313
+ const markdown = truncateText(note.markdown.trim() || "(empty)", MAX_NOTE_MARKDOWN_CHARS);
314
+ let truncated = markdown.truncated;
315
+ const attachmentContexts = [];
316
+ for (const att of note.attachments ?? []) attachmentContexts.push(await buildAttachmentContext({
317
+ note,
318
+ att,
319
+ notesService,
320
+ config
321
+ }));
322
+ const blocks = [
323
+ `# Source Note: ${note.title?.trim() || "Untitled"}`,
324
+ "",
325
+ `- noteId: ${note.id}`,
326
+ `- kind: ${note.kind}`,
327
+ `- status: ${note.status}`,
328
+ `- createdAt: ${formatTime(note.createdAt)}`,
329
+ `- updatedAt: ${formatTime(note.updatedAt)}`,
330
+ note.tags?.length ? `- tags: ${note.tags.join(", ")}` : "- tags: []",
331
+ "",
332
+ "## Markdown",
333
+ markdown.text
334
+ ];
335
+ if (attachmentContexts.length > 0) {
336
+ blocks.push("", "## Attachments");
337
+ for (const att of attachmentContexts) blocks.push("", renderAttachmentContext(att));
338
+ }
339
+ const finalText = truncateText(blocks.join("\n"), MAX_TOTAL_CONTEXT_CHARS);
340
+ truncated = truncated || finalText.truncated;
341
+ const failed = attachmentContexts.filter((att) => att.status === "failed").length;
342
+ const unsupported = attachmentContexts.filter((att) => att.status === "unsupported").length;
343
+ const artifact = {
344
+ noteId: note.id,
345
+ noteUpdatedAt: note.updatedAt,
346
+ contextVersion: String(note.updatedAt),
347
+ generatedAt: Date.now(),
348
+ text: finalText.text,
349
+ attachments: attachmentContexts,
350
+ tokenEstimate: Math.ceil(finalText.text.length / 4),
351
+ truncated,
352
+ status: failed > 0 ? "failed" : unsupported > 0 ? "partial" : "ready"
353
+ };
354
+ upsertNoteAgentContextRecord({
355
+ noteId: note.id,
356
+ noteUpdatedAt: note.updatedAt,
357
+ contextVersion: artifact.contextVersion,
358
+ generatedAt: artifact.generatedAt,
359
+ payload: artifact
360
+ });
361
+ return artifact;
362
+ }
363
+ async function buildNoteAgentContext(params) {
364
+ const artifact = await buildNoteAgentContextArtifact(params);
365
+ const images = await loadNativeVisionImages(params.notesService, params.note);
366
+ return {
367
+ kind: "note",
368
+ sourceId: params.note.id,
369
+ version: artifact.contextVersion,
370
+ title: params.note.title?.trim() || "Untitled",
371
+ text: artifact.text,
372
+ ...images.length ? { images } : {},
373
+ tokenEstimate: artifact.tokenEstimate,
374
+ truncated: artifact.truncated
375
+ };
376
+ }
377
+ function getCachedNoteAgentContextArtifact(noteId) {
378
+ return getNoteAgentContextRecord(noteId)?.payload ?? null;
379
+ }
380
+ //#endregion
381
+ export { buildNoteAgentContext, buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact };
382
+
383
+ //# sourceMappingURL=agent-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-context.js","names":[],"sources":["../../../src/notes/agent-context.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { extname } from 'node:path';\n\nimport type { ImageContent } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../config/schema.js';\nimport type { AgentSourceContext } from '../agent/source-context/types.js';\nimport { getNoteAgentContextRecord, upsertNoteAgentContextRecord } from '../storage/sqlite/index.js';\nimport { mergeSttConfigFromAppConfig } from '../channels/attachments/voice-stt-webchat.js';\nimport { extractDocumentText } from '../document-understanding/extract.js';\nimport { describeImagesWithFallback } from '../agent/image/understanding/runtime.js';\nimport { resolveImageModelConfigForTool } from '../agent/image/tool-model-config.js';\nimport { isSTTAvailable, transcribe } from '../voice/stt/index.js';\nimport type { Note, NoteAttachment } from './types.js';\nimport type { NotesService } from './service.js';\n\nconst MAX_NOTE_MARKDOWN_CHARS = 40_000;\nconst MAX_TEXT_ATTACHMENT_CHARS = 20_000;\nconst MAX_TOTAL_CONTEXT_CHARS = 80_000;\nconst MAX_NATIVE_VISION_IMAGES = 4;\nconst MAX_NATIVE_VISION_IMAGE_BYTES = 8 * 1024 * 1024;\n\nexport interface NoteAgentAttachmentContext {\n attachmentId: string;\n type: NoteAttachment['type'];\n fileName: string;\n mimeType: string;\n size: number;\n status: 'ready' | 'unsupported' | 'failed';\n summary?: string;\n transcript?: string;\n extractedText?: string;\n error?: string;\n}\n\nexport interface NoteAgentContextArtifact {\n noteId: string;\n noteUpdatedAt: number;\n contextVersion: string;\n generatedAt: number;\n text: string;\n attachments: NoteAgentAttachmentContext[];\n tokenEstimate: number;\n truncated: boolean;\n status: 'ready' | 'partial' | 'failed';\n}\n\nconst TEXT_MIME_PREFIXES = ['text/'];\nconst TEXT_MIME_TYPES = new Set([\n 'application/json', 'application/ld+json', 'application/xml', 'application/yaml', 'application/x-yaml',\n 'application/javascript', 'application/typescript',\n]);\nconst TEXT_EXTENSIONS = new Set([\n '.txt', '.md', '.markdown', '.json', '.jsonl', '.csv', '.tsv', '.xml', '.yaml', '.yml', '.log', '.js', '.ts', '.tsx', '.jsx', '.css', '.html', '.sql',\n]);\nconst DOCUMENT_EXTENSIONS = new Set(['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']);\n\nfunction truncateText(text: string, maxChars: number): { text: string; truncated: boolean } {\n if (text.length <= maxChars) return { text, truncated: false };\n return { text: `${text.slice(0, maxChars)}\\n\\n[truncated ${text.length - maxChars} chars]`, truncated: true };\n}\n\nfunction isTextLikeAttachment(att: NoteAttachment): boolean {\n const mime = att.mimeType.toLowerCase();\n if (TEXT_MIME_PREFIXES.some((prefix) => mime.startsWith(prefix))) return true;\n if (TEXT_MIME_TYPES.has(mime)) return true;\n return TEXT_EXTENSIONS.has(extname(att.fileName).toLowerCase());\n}\n\nfunction isDocumentAttachment(att: NoteAttachment): boolean {\n const mime = att.mimeType.toLowerCase();\n return mime === 'application/pdf'\n || mime.includes('officedocument')\n || DOCUMENT_EXTENSIONS.has(extname(att.fileName).toLowerCase());\n}\n\nfunction formatTime(ms: number | undefined): string {\n return typeof ms === 'number' && Number.isFinite(ms) ? new Date(ms).toISOString() : 'unknown';\n}\n\nasync function readAttachmentBuffer(notesService: NotesService, noteId: string, att: NoteAttachment): Promise<Buffer | null> {\n const located = await notesService.getAttachmentPath(noteId, att.id);\n if (!located) return null;\n return readFile(located.filePath).catch(() => null);\n}\n\nasync function attachmentTextExcerpt(notesService: NotesService, noteId: string, att: NoteAttachment): Promise<string | null> {\n if (!isTextLikeAttachment(att)) return null;\n const located = await notesService.getAttachmentPath(noteId, att.id);\n if (!located) return null;\n const raw = await readFile(located.filePath, 'utf8').catch(() => null);\n if (raw === null) return null;\n return truncateText(raw, MAX_TEXT_ATTACHMENT_CHARS).text;\n}\n\nasync function ensureMediaTranscript(params: {\n note: Note;\n att: NoteAttachment;\n notesService: NotesService;\n config?: Config;\n}): Promise<string | undefined> {\n const existing = params.att.transcript?.trim();\n if (existing) return existing;\n if (params.att.type !== 'audio' && params.att.type !== 'video') return undefined;\n\n const sttConfig = mergeSttConfigFromAppConfig(params.config?.tools?.media?.audio, params.config?.tools?.media);\n if (!isSTTAvailable(sttConfig)) return undefined;\n const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);\n if (!buffer) return undefined;\n const result = await transcribe(buffer, sttConfig);\n const transcript = result.text?.trim();\n if (!transcript) return undefined;\n const attachments = (params.note.attachments ?? []).map((item) =>\n item.id === params.att.id ? { ...item, transcript } : item,\n );\n await params.notesService.updateNote(params.note.id, { attachments }, 'ai_edit').catch(() => null);\n params.att.transcript = transcript;\n return transcript;\n}\n\nasync function describeImageAttachment(params: {\n note: Note;\n att: NoteAttachment;\n notesService: NotesService;\n config?: Config;\n}): Promise<string | undefined> {\n const toolCfg = resolveImageModelConfigForTool({ cfg: params.config });\n const primary = toolCfg?.primary;\n if (!primary) return undefined;\n const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);\n if (!buffer) return undefined;\n const result = await describeImagesWithFallback({\n modelRef: primary,\n fallbacks: toolCfg.fallbacks ?? [],\n images: [{ buffer, mimeType: params.att.mimeType }],\n prompt: `Describe this note attachment for later AI discussion. File name: ${params.att.fileName}. Focus on visible text, diagrams, UI, objects, and any product-relevant details.`,\n maxTokens: 1024,\n timeoutMs: 30_000,\n });\n return result.text.trim() || undefined;\n}\n\nasync function extractDocumentAttachment(params: {\n note: Note;\n att: NoteAttachment;\n notesService: NotesService;\n}): Promise<string | undefined> {\n const buffer = await readAttachmentBuffer(params.notesService, params.note.id, params.att);\n if (!buffer) return undefined;\n const result = extractDocumentText({ buffer, fileName: params.att.fileName, mimeType: params.att.mimeType });\n if (!result.ok) return undefined;\n return truncateText(result.text, MAX_TEXT_ATTACHMENT_CHARS).text;\n}\n\nfunction fallbackImageSummary(att: NoteAttachment): string {\n return `Image attachment available for native vision models: ${att.fileName} (${att.mimeType}, ${att.size} bytes). Configure an image understanding model to persist a text summary for non-vision models.`;\n}\n\nfunction fallbackVideoSummary(att: NoteAttachment): string {\n return `Video attachment available: ${att.fileName} (${att.mimeType}, ${att.size} bytes). Audio transcription will be included when STT accepts this media.`;\n}\n\nfunction unsupportedDocumentSummary(att: NoteAttachment): string {\n return `Document extraction is not available for ${att.fileName} (${att.mimeType}).`;\n}\n\nasync function buildAttachmentContext(params: {\n note: Note;\n att: NoteAttachment;\n notesService: NotesService;\n config?: Config;\n}): Promise<NoteAgentAttachmentContext> {\n const { note, att, notesService, config } = params;\n try {\n if (att.type === 'audio') {\n const transcript = await ensureMediaTranscript({ note, att, notesService, config });\n return transcript\n ? { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'ready', transcript }\n : { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'unsupported', summary: 'Audio transcription is not configured or produced no transcript.' };\n }\n const extractedText = await attachmentTextExcerpt(notesService, note.id, att);\n if (extractedText) {\n return { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'ready', extractedText };\n }\n if (att.type === 'image') {\n const summary = await describeImageAttachment({ note, att, notesService, config }).catch(() => undefined);\n return { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: summary ? 'ready' : 'unsupported', summary: summary ?? fallbackImageSummary(att) };\n }\n if (att.type === 'video') {\n const transcript = await ensureMediaTranscript({ note, att, notesService, config }).catch(() => undefined);\n return transcript\n ? { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'ready', transcript, summary: fallbackVideoSummary(att) }\n : { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'unsupported', summary: fallbackVideoSummary(att) };\n }\n if (isDocumentAttachment(att)) {\n const extractedDocumentText = await extractDocumentAttachment({ note, att, notesService }).catch(() => undefined);\n return extractedDocumentText\n ? { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'ready', extractedText: extractedDocumentText }\n : { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'unsupported', summary: unsupportedDocumentSummary(att) };\n }\n return { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'unsupported', summary: 'No extractor is configured for this attachment type.' };\n } catch (err) {\n return { attachmentId: att.id, type: att.type, fileName: att.fileName, mimeType: att.mimeType, size: att.size, status: 'failed', error: err instanceof Error ? err.message : String(err) };\n }\n}\n\nfunction renderAttachmentContext(att: NoteAgentAttachmentContext): string {\n const lines = [\n `### Attachment: ${att.fileName}`,\n `- id: ${att.attachmentId}`,\n `- type: ${att.type}`,\n `- mimeType: ${att.mimeType}`,\n `- size: ${att.size}`,\n `- status: ${att.status}`,\n ];\n if (att.transcript) lines.push('', 'Transcript:', truncateText(att.transcript, MAX_TEXT_ATTACHMENT_CHARS).text);\n if (att.extractedText) lines.push('', 'Extracted text:', truncateText(att.extractedText, MAX_TEXT_ATTACHMENT_CHARS).text);\n if (att.summary) lines.push('', 'Summary:', att.summary);\n if (att.error) lines.push('', 'Error:', att.error);\n return lines.join('\\n');\n}\n\nasync function loadNativeVisionImages(notesService: NotesService, note: Note): Promise<ImageContent[]> {\n const images: ImageContent[] = [];\n for (const att of note.attachments ?? []) {\n if (att.type !== 'image') continue;\n if (!att.mimeType.toLowerCase().startsWith('image/')) continue;\n if (att.size > MAX_NATIVE_VISION_IMAGE_BYTES) continue;\n const buffer = await readAttachmentBuffer(notesService, note.id, att);\n if (!buffer || buffer.length > MAX_NATIVE_VISION_IMAGE_BYTES) continue;\n images.push({ type: 'image', data: buffer.toString('base64'), mimeType: att.mimeType });\n if (images.length >= MAX_NATIVE_VISION_IMAGES) break;\n }\n return images;\n}\n\nexport async function buildNoteAgentContextArtifact(params: {\n note: Note;\n notesService: NotesService;\n config?: Config;\n force?: boolean;\n}): Promise<NoteAgentContextArtifact> {\n const { note, notesService, config, force } = params;\n if (!force) {\n const cached = getNoteAgentContextRecord<NoteAgentContextArtifact>(note.id);\n if (cached && cached.noteUpdatedAt === note.updatedAt && cached.contextVersion === String(note.updatedAt)) {\n return cached.payload;\n }\n }\n\n const markdown = truncateText(note.markdown.trim() || '(empty)', MAX_NOTE_MARKDOWN_CHARS);\n let truncated = markdown.truncated;\n const attachmentContexts: NoteAgentAttachmentContext[] = [];\n for (const att of note.attachments ?? []) {\n attachmentContexts.push(await buildAttachmentContext({ note, att, notesService, config }));\n }\n\n const blocks = [\n `# Source Note: ${note.title?.trim() || 'Untitled'}`,\n '',\n `- noteId: ${note.id}`,\n `- kind: ${note.kind}`,\n `- status: ${note.status}`,\n `- createdAt: ${formatTime(note.createdAt)}`,\n `- updatedAt: ${formatTime(note.updatedAt)}`,\n note.tags?.length ? `- tags: ${note.tags.join(', ')}` : '- tags: []',\n '',\n '## Markdown',\n markdown.text,\n ];\n if (attachmentContexts.length > 0) {\n blocks.push('', '## Attachments');\n for (const att of attachmentContexts) blocks.push('', renderAttachmentContext(att));\n }\n\n const finalText = truncateText(blocks.join('\\n'), MAX_TOTAL_CONTEXT_CHARS);\n truncated = truncated || finalText.truncated;\n const failed = attachmentContexts.filter((att) => att.status === 'failed').length;\n const unsupported = attachmentContexts.filter((att) => att.status === 'unsupported').length;\n const artifact: NoteAgentContextArtifact = {\n noteId: note.id,\n noteUpdatedAt: note.updatedAt,\n contextVersion: String(note.updatedAt),\n generatedAt: Date.now(),\n text: finalText.text,\n attachments: attachmentContexts,\n tokenEstimate: Math.ceil(finalText.text.length / 4),\n truncated,\n status: failed > 0 ? 'failed' : unsupported > 0 ? 'partial' : 'ready',\n };\n upsertNoteAgentContextRecord({\n noteId: note.id,\n noteUpdatedAt: note.updatedAt,\n contextVersion: artifact.contextVersion,\n generatedAt: artifact.generatedAt,\n payload: artifact,\n });\n return artifact;\n}\n\nexport async function buildNoteAgentContext(params: {\n note: Note;\n notesService: NotesService;\n config?: Config;\n force?: boolean;\n}): Promise<AgentSourceContext> {\n const artifact = await buildNoteAgentContextArtifact(params);\n const images = await loadNativeVisionImages(params.notesService, params.note);\n return {\n kind: 'note',\n sourceId: params.note.id,\n version: artifact.contextVersion,\n title: params.note.title?.trim() || 'Untitled',\n text: artifact.text,\n ...(images.length ? { images } : {}),\n tokenEstimate: artifact.tokenEstimate,\n truncated: artifact.truncated,\n };\n}\n\nexport function getCachedNoteAgentContextArtifact(noteId: string): NoteAgentContextArtifact | null {\n return getNoteAgentContextRecord<NoteAgentContextArtifact>(noteId)?.payload ?? null;\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  export { NotesStore } from './store.js';
2
2
  export { NotesService } from './service.js';
3
+ export { buildNoteAgentContext, buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact } from './agent-context.js';
4
+ export type { NoteAgentContextArtifact, NoteAgentAttachmentContext } from './agent-context.js';
3
5
  export { resolveNotesDir, resolveNotesIndexPath, resolveNoteItemPath, resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';
4
6
  export type { Note, NoteKind, NoteStatus, NoteAttachment, NoteAiMeta, NoteAiDeepMeta, NoteIndexEntry, NoteSnapshot, NoteSnapshotEntry, NotesIndexFile, NotesListQuery, CaptureSource, CaptureChannel, CreateNoteParams, SnapshotTrigger, NoteTaskMeta, NoteGroup, } from './types.js';
@@ -1,4 +1,5 @@
1
1
  import { resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath } from "./paths.js";
2
2
  import { NotesStore } from "./store.js";
3
+ import { buildNoteAgentContext, buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact } from "./agent-context.js";
3
4
  import { NotesService } from "./service.js";
4
- export { NotesService, NotesStore, resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath };
5
+ export { NotesService, NotesStore, buildNoteAgentContext, buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact, resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath };
@@ -27,6 +27,11 @@ export declare class NotesService {
27
27
  recordCatalysisFeedback(id: string, feedback: 'helpful' | 'not_helpful' | 'neutral'): Promise<Note | null>;
28
28
  linkNoteThread(id: string, sessionKey: string): Promise<Note | null>;
29
29
  listNoteThreads(id: string): Promise<string[] | null>;
30
+ getAgentContextStatus(id: string, config?: Config, force?: boolean): Promise<{
31
+ noteUpdatedAt: number;
32
+ artifact: import('./agent-context.js').NoteAgentContextArtifact | null;
33
+ stale: boolean;
34
+ } | null>;
30
35
  appendTextToNote(id: string, content: string, heading?: string): Promise<Note | null>;
31
36
  deleteNote(id: string): Promise<boolean>;
32
37
  listNotes(query?: NotesListQuery): Promise<{
@@ -5,6 +5,7 @@ import { buildNoteAttachmentRef } from "./attachment-ref.js";
5
5
  import { parseNoteMarkdown } from "./note-markdown.js";
6
6
  import { NotesStore } from "./store.js";
7
7
  import { partitionAttachmentsByReference } from "./note-attachment-sync.js";
8
+ import { buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact } from "./agent-context.js";
8
9
  import { randomUUID } from "node:crypto";
9
10
  import { complete } from "@earendil-works/pi-ai";
10
11
  //#region src/notes/service.ts
@@ -324,6 +325,27 @@ var NotesService = class {
324
325
  if (!note) return null;
325
326
  return Array.from(new Set([note.aiDeep?.catalysis?.sourceSessionKey, ...note.aiDeep?.catalysis?.linkedSessionKeys ?? []].filter((key) => Boolean(key))));
326
327
  }
328
+ async getAgentContextStatus(id, config, force = false) {
329
+ const note = await this.store.getNote(id);
330
+ if (!note) return null;
331
+ const cached = getCachedNoteAgentContextArtifact(id);
332
+ if (!force && cached && cached.noteUpdatedAt === note.updatedAt) return {
333
+ noteUpdatedAt: note.updatedAt,
334
+ artifact: cached,
335
+ stale: false
336
+ };
337
+ const artifact = await buildNoteAgentContextArtifact({
338
+ note,
339
+ notesService: this,
340
+ config,
341
+ force
342
+ });
343
+ return {
344
+ noteUpdatedAt: note.updatedAt,
345
+ artifact,
346
+ stale: false
347
+ };
348
+ }
327
349
  async appendTextToNote(id, content, heading = "AI 讨论沉淀") {
328
350
  const note = await this.store.getNote(id);
329
351
  if (!note) return null;
@@ -1 +1 @@
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 } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { parseNoteMarkdown } from './note-markdown.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\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');\nconst MAX_SNAPSHOTS_PER_NOTE = 50;\nconst SNAPSHOT_THROTTLE_MS = 60_000;\n\nfunction inferKind(markdown?: string, hasAttachments?: boolean, attachments?: NoteAttachment[]): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) return 'voice';\n if (hasAttachments) return 'media';\n if (!markdown) return 'thought';\n const lower = markdown.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) || /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(markdown)) return 'todo';\n if (/^https?:\\/\\//.test(markdown.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction deriveDefaultTitle(markdown?: string): string | undefined {\n const normalized = parseNoteMarkdown(markdown ?? '').plainText.trim().replace(/\\s+/g, ' ');\n return normalized ? Array.from(normalized).slice(0, 10).join('') : undefined;\n}\n\nfunction splitMeaningfulLines(markdown?: string): string[] {\n return parseNoteMarkdown(markdown ?? '').plainText.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n}\n\nfunction summarizeIdea(markdown?: string): string {\n const lines = splitMeaningfulLines(markdown);\n return lines.slice(0, 3).join(';').slice(0, 140);\n}\n\nfunction inferCatalysisStage(note: Note): NonNullable<NoteCatalysisMeta['stage']> {\n const text = `${note.title ?? ''}\\n${note.markdown}`;\n if (/上线|发布|launch|ship/i.test(text)) return 'shipped';\n if (/验证|访谈|用户|测试|validate/i.test(text)) return 'validating';\n if (/开发|实现|构建|build|mvp/i.test(text)) return 'developing';\n if (/想法|机会|问题|idea|seed/i.test(text)) return 'seed';\n return 'incubating';\n}\n\nfunction buildCatalysisReport(note: Note): NoteCatalysisReport {\n const summary = summarizeIdea(note.markdown || note.title);\n const title = note.title?.trim() || summary.slice(0, 28) || '未命名想法';\n const actions: NoteCatalysisAction[] = [\n { kind: 'research', text: `验证「${title}」的真实需求和目标用户` },\n { kind: 'task', text: `拆解「${title}」的最小可行下一步` },\n { kind: 'chat', text: `继续和 AI 讨论「${title}」的方案` },\n ];\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title,\n valueHypothesis: summary || '这条笔记可能包含一个待澄清的想法。',\n targetUsers: ['待确认的核心用户'],\n keyQuestions: ['用户为什么现在需要它?', '最小可验证版本是什么?', '如何判断它值得继续投入?'],\n mvpPath: ['定义目标用户', '写出最小场景', '完成一次验证'],\n risks: ['需求不够明确', '执行路径过大'],\n nextActions: actions,\n confidence: summary ? 0.58 : 0.35,\n };\n}\n\nfunction normalizeAiCatalysisReport(note: Note, data: Record<string, unknown>): NoteCatalysisReport {\n const fallback = buildCatalysisReport(note);\n const stringArray = (value: unknown, fallbackValue: string[]) => Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string').slice(0, 12) : fallbackValue;\n const actions = Array.isArray(data.nextActions)\n ? data.nextActions.map((item): NoteCatalysisAction | null => {\n if (!item || typeof item !== 'object') return null;\n const row = item as { kind?: unknown; text?: unknown };\n const kind = row.kind === 'task' || row.kind === 'workflow' || row.kind === 'research' || row.kind === 'share' || row.kind === 'chat' ? row.kind : 'task';\n return typeof row.text === 'string' && row.text.trim() ? { kind, text: row.text.trim() } : null;\n }).filter((item): item is NoteCatalysisAction => Boolean(item)).slice(0, 12)\n : fallback.nextActions;\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title: typeof data.title === 'string' && data.title.trim() ? data.title.trim() : fallback.title,\n valueHypothesis: typeof data.valueHypothesis === 'string' && data.valueHypothesis.trim() ? data.valueHypothesis.trim() : fallback.valueHypothesis,\n targetUsers: stringArray(data.targetUsers, fallback.targetUsers),\n keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions),\n mvpPath: stringArray(data.mvpPath, fallback.mvpPath),\n risks: stringArray(data.risks, fallback.risks),\n nextActions: actions,\n confidence: typeof data.confidence === 'number' ? Math.max(0, Math.min(1, data.confidence)) : fallback.confidence,\n };\n}\n\nfunction buildCatalysisPrompt(note: Note): string {\n const title = note.title?.trim() || '未命名笔记';\n const markdown = note.markdown.trim() || '(无正文)';\n return `请把下面的 Markdown 笔记催化成可执行的想法报告,输出 JSON:\\n{\\n \"title\": string,\\n \"valueHypothesis\": string,\\n \"targetUsers\": string[],\\n \"keyQuestions\": string[],\\n \"mvpPath\": string[],\\n \"risks\": string[],\\n \"nextActions\": [{\"text\": string, \"kind\": \"task\"|\"workflow\"|\"research\"|\"share\"|\"chat\"}],\\n \"confidence\": number\\n}\\n\\n标题:${title}\\n\\nMarkdown:\\n${markdown}`;\n}\n\nasync function buildAiCatalysisReport(note: Note, config?: Config): Promise<NoteCatalysisReport> {\n const modelRef = getDefaultModelSync(config) ?? 'openai/gpt-5.5';\n const resolved = resolveModel(modelRef);\n const messages: UserMessage[] = [{ role: 'user', content: buildCatalysisPrompt(note), timestamp: Date.now() }];\n const response = await complete(resolved, { messages }, { temperature: 0.2 });\n let responseText = '';\n if (Array.isArray(response.content)) {\n for (const part of response.content) {\n if (part && typeof part === 'object' && (part as { type?: string }).type === 'text') {\n responseText += String((part as { text?: string }).text || '');\n }\n }\n }\n const raw = responseText.trim().replace(/^```json\\s*/i, '').replace(/```$/i, '').trim();\n return normalizeAiCatalysisReport(note, JSON.parse(raw) as Record<string, unknown>);\n}\n\nexport class NotesService {\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(private readonly store = new NotesStore()) {}\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n }\n\n async quickCapture(markdown: string, source: CaptureSource): Promise<Note> {\n const id = randomUUID();\n const now = Date.now();\n const note: Note = {\n id,\n title: deriveDefaultTitle(markdown),\n kind: inferKind(markdown),\n status: 'inbox',\n markdown,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n remoteVersion: 1,\n localVersion: 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 id = randomUUID();\n const now = Date.now();\n const markdown = params.markdown ?? '';\n const note: Note = {\n id,\n title: params.title ?? deriveDefaultTitle(markdown),\n kind: params.kind ?? inferKind(markdown),\n status: 'inbox',\n markdown,\n attachments: [],\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n groupId: params.groupId,\n taskMeta: params.taskMeta,\n remoteVersion: 1,\n localVersion: 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 const contentTouched = patch.markdown !== undefined || patch.title !== undefined;\n if (contentTouched) await this.maybeSaveSnapshot(existing, trigger);\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.markdown !== undefined) {\n normalizedPatch.kind = patch.kind ?? inferKind(patch.markdown, Boolean(existing.attachments?.length), existing.attachments);\n }\n if (contentTouched || patch.attachments) {\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 1) + 1;\n normalizedPatch.localVersion = patch.localVersion ?? (existing.localVersion ?? 1) + 1;\n }\n\n const updatedRaw = await this.store.updateNote(id, normalizedPatch);\n return updatedRaw ? this.reconcileAttachments(updatedRaw) : null;\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n for (const attachment of removed) await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n log.debug({ noteId: note.id, removedIds: removed.map((attachment) => attachment.id) }, 'Pruned orphan note attachments');\n const hasAttachments = kept.length > 0;\n const updated = await this.store.updateNote(note.id, { attachments: kept, kind: inferKind(note.markdown, hasAttachments, kept) });\n return updated ?? { ...note, attachments: kept };\n }\n\n async syncNote(id: string, patch: Partial<Note>, baseRemoteVersion?: number): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n if (baseRemoteVersion !== undefined && existing.remoteVersion !== undefined && baseRemoteVersion < existing.remoteVersion) {\n return { note: existing, conflict: true };\n }\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(id: string, instruction: string, markdownOverride?: string): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const source = markdownOverride ?? note.markdown;\n const parsed = parseNoteMarkdown(source, id);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n let generated = '';\n if (wantsTodos) {\n const lines = source.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n generated = (lines.length ? lines : [parsed.plainText]).slice(0, 12).map((line) => `- [ ] ${line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim()}`).join('\\n');\n } else if (wantsSummary) {\n generated = `> [!SUMMARY]\\n> ${parsed.plainText.slice(0, 220)}`;\n } else {\n generated = `${source.trimEnd()}\\n\\n## AI 整理\\n\\n${instruction.trim()}`;\n }\n return {\n message: 'AI edit patch generated',\n patch: {\n id: randomUUID(),\n summary: instruction,\n operations: [{ type: 'replaceRange', from: 0, to: source.length, markdown: generated || source }],\n },\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 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 const updated = await this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: 'catalyzed',\n stage: inferCatalysisStage(note),\n lastCatalyzedAt: Date.now(),\n confidence: report.confidence,\n report,\n ...note.aiDeep?.catalysis,\n },\n },\n ai: { ...note.ai, intent: note.ai?.intent ?? 'idea', summary: note.ai?.summary ?? report.valueHypothesis },\n status: note.status === 'inbox' ? 'processed' : note.status,\n }, 'ai_edit');\n return updated ? { note: updated, report } : null;\n }\n\n async recordCatalysisFeedback(id: string, feedback: 'helpful' | 'not_helpful' | 'neutral'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n return this.updateNote(id, { aiDeep: { ...note.aiDeep, processedAt: Date.now(), catalysis: { status: note.aiDeep?.catalysis?.status ?? 'catalyzed', ...note.aiDeep?.catalysis, feedback } } });\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 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: Array.from(new Set([...existingKeys, sessionKey])),\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 return Array.from(new Set([note.aiDeep?.catalysis?.sourceSessionKey, ...(note.aiDeep?.catalysis?.linkedSessionKeys ?? [])].filter((key): key is string => Boolean(key))));\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 current = note.markdown.trimEnd();\n const nextMarkdown = `${current}${current ? '\\n\\n' : ''}## ${heading}\\n\\n${trimmed}\\n`;\n return this.updateNote(id, { markdown: nextMarkdown }, 'ai_edit');\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const removed = await this.store.deleteNote(id);\n if (removed) await this.store.deleteAllSnapshots(id);\n return removed;\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(noteId: string, file: { name: string; buffer: Buffer; mimeType: string; duration?: number }): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: file.mimeType.startsWith('image/') ? 'image' : file.mimeType.startsWith('audio/') ? 'audio' : file.mimeType.startsWith('video/') ? 'video' : 'file',\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n const attachments = [...(note.attachments || []), attachment];\n const kind = note.kind === 'thought' && attachment.type === 'audio' ? 'voice' : note.kind === 'thought' ? 'media' : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n return attachment;\n }\n\n async getAttachmentPath(noteId: string, attachmentId: string): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n return { filePath: this.store.resolveAttachmentPath(noteId, attachment.relativePath), mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> { return this.store.listSnapshots(noteId); }\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> { return this.store.getSnapshot(noteId, timestamp); }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n const existing = await this.store.getNote(noteId);\n if (!snapshot || !existing) return null;\n await this.maybeSaveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n return this.store.updateNote(noteId, { title: snapshot.title, markdown: snapshot.markdown, tags: snapshot.tags, kind: snapshot.kind, status: snapshot.status });\n }\n\n async flush(): Promise<void> { await this.store.flush(); }\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> { return this.updateNote(noteId, { groupId: groupId ?? undefined }); }\n\n async createTask(title: string, source: CaptureSource, opts: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string } = {}): Promise<Note> {\n return this.createNote({\n title,\n markdown: `- [ ] ${title}`,\n kind: 'task',\n capturedVia: source,\n groupId: opts.groupId,\n taskMeta: { done: false, dueAt: opts.dueAt, priority: opts.priority, sourceSessionKey: opts.sourceSessionKey, sourceNoteId: opts.sourceNoteId },\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 const markdown = note.markdown.replace(/^- \\[[ xX]\\]/m, `- [${done ? 'x' : ' '}]`);\n return this.updateNote(noteId, { markdown, taskMeta: { ...note.taskMeta, done } });\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, { taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch } });\n }\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\nexport { buildNoteAttachmentRef };\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 } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { parseNoteMarkdown } from './note-markdown.js';\nimport { buildNoteAgentContextArtifact, getCachedNoteAgentContextArtifact } from './agent-context.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\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');\nconst MAX_SNAPSHOTS_PER_NOTE = 50;\nconst SNAPSHOT_THROTTLE_MS = 60_000;\n\nfunction inferKind(markdown?: string, hasAttachments?: boolean, attachments?: NoteAttachment[]): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) return 'voice';\n if (hasAttachments) return 'media';\n if (!markdown) return 'thought';\n const lower = markdown.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) || /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(markdown)) return 'todo';\n if (/^https?:\\/\\//.test(markdown.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction deriveDefaultTitle(markdown?: string): string | undefined {\n const normalized = parseNoteMarkdown(markdown ?? '').plainText.trim().replace(/\\s+/g, ' ');\n return normalized ? Array.from(normalized).slice(0, 10).join('') : undefined;\n}\n\nfunction splitMeaningfulLines(markdown?: string): string[] {\n return parseNoteMarkdown(markdown ?? '').plainText.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n}\n\nfunction summarizeIdea(markdown?: string): string {\n const lines = splitMeaningfulLines(markdown);\n return lines.slice(0, 3).join(';').slice(0, 140);\n}\n\nfunction inferCatalysisStage(note: Note): NonNullable<NoteCatalysisMeta['stage']> {\n const text = `${note.title ?? ''}\\n${note.markdown}`;\n if (/上线|发布|launch|ship/i.test(text)) return 'shipped';\n if (/验证|访谈|用户|测试|validate/i.test(text)) return 'validating';\n if (/开发|实现|构建|build|mvp/i.test(text)) return 'developing';\n if (/想法|机会|问题|idea|seed/i.test(text)) return 'seed';\n return 'incubating';\n}\n\nfunction buildCatalysisReport(note: Note): NoteCatalysisReport {\n const summary = summarizeIdea(note.markdown || note.title);\n const title = note.title?.trim() || summary.slice(0, 28) || '未命名想法';\n const actions: NoteCatalysisAction[] = [\n { kind: 'research', text: `验证「${title}」的真实需求和目标用户` },\n { kind: 'task', text: `拆解「${title}」的最小可行下一步` },\n { kind: 'chat', text: `继续和 AI 讨论「${title}」的方案` },\n ];\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title,\n valueHypothesis: summary || '这条笔记可能包含一个待澄清的想法。',\n targetUsers: ['待确认的核心用户'],\n keyQuestions: ['用户为什么现在需要它?', '最小可验证版本是什么?', '如何判断它值得继续投入?'],\n mvpPath: ['定义目标用户', '写出最小场景', '完成一次验证'],\n risks: ['需求不够明确', '执行路径过大'],\n nextActions: actions,\n confidence: summary ? 0.58 : 0.35,\n };\n}\n\nfunction normalizeAiCatalysisReport(note: Note, data: Record<string, unknown>): NoteCatalysisReport {\n const fallback = buildCatalysisReport(note);\n const stringArray = (value: unknown, fallbackValue: string[]) => Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string').slice(0, 12) : fallbackValue;\n const actions = Array.isArray(data.nextActions)\n ? data.nextActions.map((item): NoteCatalysisAction | null => {\n if (!item || typeof item !== 'object') return null;\n const row = item as { kind?: unknown; text?: unknown };\n const kind = row.kind === 'task' || row.kind === 'workflow' || row.kind === 'research' || row.kind === 'share' || row.kind === 'chat' ? row.kind : 'task';\n return typeof row.text === 'string' && row.text.trim() ? { kind, text: row.text.trim() } : null;\n }).filter((item): item is NoteCatalysisAction => Boolean(item)).slice(0, 12)\n : fallback.nextActions;\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title: typeof data.title === 'string' && data.title.trim() ? data.title.trim() : fallback.title,\n valueHypothesis: typeof data.valueHypothesis === 'string' && data.valueHypothesis.trim() ? data.valueHypothesis.trim() : fallback.valueHypothesis,\n targetUsers: stringArray(data.targetUsers, fallback.targetUsers),\n keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions),\n mvpPath: stringArray(data.mvpPath, fallback.mvpPath),\n risks: stringArray(data.risks, fallback.risks),\n nextActions: actions,\n confidence: typeof data.confidence === 'number' ? Math.max(0, Math.min(1, data.confidence)) : fallback.confidence,\n };\n}\n\nfunction buildCatalysisPrompt(note: Note): string {\n const title = note.title?.trim() || '未命名笔记';\n const markdown = note.markdown.trim() || '(无正文)';\n return `请把下面的 Markdown 笔记催化成可执行的想法报告,输出 JSON:\\n{\\n \"title\": string,\\n \"valueHypothesis\": string,\\n \"targetUsers\": string[],\\n \"keyQuestions\": string[],\\n \"mvpPath\": string[],\\n \"risks\": string[],\\n \"nextActions\": [{\"text\": string, \"kind\": \"task\"|\"workflow\"|\"research\"|\"share\"|\"chat\"}],\\n \"confidence\": number\\n}\\n\\n标题:${title}\\n\\nMarkdown:\\n${markdown}`;\n}\n\nasync function buildAiCatalysisReport(note: Note, config?: Config): Promise<NoteCatalysisReport> {\n const modelRef = getDefaultModelSync(config) ?? 'openai/gpt-5.5';\n const resolved = resolveModel(modelRef);\n const messages: UserMessage[] = [{ role: 'user', content: buildCatalysisPrompt(note), timestamp: Date.now() }];\n const response = await complete(resolved, { messages }, { temperature: 0.2 });\n let responseText = '';\n if (Array.isArray(response.content)) {\n for (const part of response.content) {\n if (part && typeof part === 'object' && (part as { type?: string }).type === 'text') {\n responseText += String((part as { text?: string }).text || '');\n }\n }\n }\n const raw = responseText.trim().replace(/^```json\\s*/i, '').replace(/```$/i, '').trim();\n return normalizeAiCatalysisReport(note, JSON.parse(raw) as Record<string, unknown>);\n}\n\nexport class NotesService {\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(private readonly store = new NotesStore()) {}\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n }\n\n async quickCapture(markdown: string, source: CaptureSource): Promise<Note> {\n const id = randomUUID();\n const now = Date.now();\n const note: Note = {\n id,\n title: deriveDefaultTitle(markdown),\n kind: inferKind(markdown),\n status: 'inbox',\n markdown,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n remoteVersion: 1,\n localVersion: 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 id = randomUUID();\n const now = Date.now();\n const markdown = params.markdown ?? '';\n const note: Note = {\n id,\n title: params.title ?? deriveDefaultTitle(markdown),\n kind: params.kind ?? inferKind(markdown),\n status: 'inbox',\n markdown,\n attachments: [],\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n groupId: params.groupId,\n taskMeta: params.taskMeta,\n remoteVersion: 1,\n localVersion: 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 const contentTouched = patch.markdown !== undefined || patch.title !== undefined;\n if (contentTouched) await this.maybeSaveSnapshot(existing, trigger);\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.markdown !== undefined) {\n normalizedPatch.kind = patch.kind ?? inferKind(patch.markdown, Boolean(existing.attachments?.length), existing.attachments);\n }\n if (contentTouched || patch.attachments) {\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 1) + 1;\n normalizedPatch.localVersion = patch.localVersion ?? (existing.localVersion ?? 1) + 1;\n }\n\n const updatedRaw = await this.store.updateNote(id, normalizedPatch);\n return updatedRaw ? this.reconcileAttachments(updatedRaw) : null;\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n for (const attachment of removed) await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n log.debug({ noteId: note.id, removedIds: removed.map((attachment) => attachment.id) }, 'Pruned orphan note attachments');\n const hasAttachments = kept.length > 0;\n const updated = await this.store.updateNote(note.id, { attachments: kept, kind: inferKind(note.markdown, hasAttachments, kept) });\n return updated ?? { ...note, attachments: kept };\n }\n\n async syncNote(id: string, patch: Partial<Note>, baseRemoteVersion?: number): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n if (baseRemoteVersion !== undefined && existing.remoteVersion !== undefined && baseRemoteVersion < existing.remoteVersion) {\n return { note: existing, conflict: true };\n }\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(id: string, instruction: string, markdownOverride?: string): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const source = markdownOverride ?? note.markdown;\n const parsed = parseNoteMarkdown(source, id);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n let generated = '';\n if (wantsTodos) {\n const lines = source.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n generated = (lines.length ? lines : [parsed.plainText]).slice(0, 12).map((line) => `- [ ] ${line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim()}`).join('\\n');\n } else if (wantsSummary) {\n generated = `> [!SUMMARY]\\n> ${parsed.plainText.slice(0, 220)}`;\n } else {\n generated = `${source.trimEnd()}\\n\\n## AI 整理\\n\\n${instruction.trim()}`;\n }\n return {\n message: 'AI edit patch generated',\n patch: {\n id: randomUUID(),\n summary: instruction,\n operations: [{ type: 'replaceRange', from: 0, to: source.length, markdown: generated || source }],\n },\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 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 const updated = await this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: 'catalyzed',\n stage: inferCatalysisStage(note),\n lastCatalyzedAt: Date.now(),\n confidence: report.confidence,\n report,\n ...note.aiDeep?.catalysis,\n },\n },\n ai: { ...note.ai, intent: note.ai?.intent ?? 'idea', summary: note.ai?.summary ?? report.valueHypothesis },\n status: note.status === 'inbox' ? 'processed' : note.status,\n }, 'ai_edit');\n return updated ? { note: updated, report } : null;\n }\n\n async recordCatalysisFeedback(id: string, feedback: 'helpful' | 'not_helpful' | 'neutral'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n return this.updateNote(id, { aiDeep: { ...note.aiDeep, processedAt: Date.now(), catalysis: { status: note.aiDeep?.catalysis?.status ?? 'catalyzed', ...note.aiDeep?.catalysis, feedback } } });\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 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: Array.from(new Set([...existingKeys, sessionKey])),\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 return Array.from(new Set([note.aiDeep?.catalysis?.sourceSessionKey, ...(note.aiDeep?.catalysis?.linkedSessionKeys ?? [])].filter((key): key is string => Boolean(key))));\n }\n\n async getAgentContextStatus(id: string, config?: Config, force = false): Promise<{ noteUpdatedAt: number; artifact: import('./agent-context.js').NoteAgentContextArtifact | null; stale: boolean } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const cached = getCachedNoteAgentContextArtifact(id);\n if (!force && cached && cached.noteUpdatedAt === note.updatedAt) {\n return { noteUpdatedAt: note.updatedAt, artifact: cached, stale: false };\n }\n const artifact = await buildNoteAgentContextArtifact({ note, notesService: this, config, force });\n return { noteUpdatedAt: note.updatedAt, artifact, stale: false };\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 current = note.markdown.trimEnd();\n const nextMarkdown = `${current}${current ? '\\n\\n' : ''}## ${heading}\\n\\n${trimmed}\\n`;\n return this.updateNote(id, { markdown: nextMarkdown }, 'ai_edit');\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const removed = await this.store.deleteNote(id);\n if (removed) await this.store.deleteAllSnapshots(id);\n return removed;\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(noteId: string, file: { name: string; buffer: Buffer; mimeType: string; duration?: number }): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: file.mimeType.startsWith('image/') ? 'image' : file.mimeType.startsWith('audio/') ? 'audio' : file.mimeType.startsWith('video/') ? 'video' : 'file',\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n const attachments = [...(note.attachments || []), attachment];\n const kind = note.kind === 'thought' && attachment.type === 'audio' ? 'voice' : note.kind === 'thought' ? 'media' : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n return attachment;\n }\n\n async getAttachmentPath(noteId: string, attachmentId: string): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n return { filePath: this.store.resolveAttachmentPath(noteId, attachment.relativePath), mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> { return this.store.listSnapshots(noteId); }\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> { return this.store.getSnapshot(noteId, timestamp); }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n const existing = await this.store.getNote(noteId);\n if (!snapshot || !existing) return null;\n await this.maybeSaveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n return this.store.updateNote(noteId, { title: snapshot.title, markdown: snapshot.markdown, tags: snapshot.tags, kind: snapshot.kind, status: snapshot.status });\n }\n\n async flush(): Promise<void> { await this.store.flush(); }\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> { return this.updateNote(noteId, { groupId: groupId ?? undefined }); }\n\n async createTask(title: string, source: CaptureSource, opts: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string } = {}): Promise<Note> {\n return this.createNote({\n title,\n markdown: `- [ ] ${title}`,\n kind: 'task',\n capturedVia: source,\n groupId: opts.groupId,\n taskMeta: { done: false, dueAt: opts.dueAt, priority: opts.priority, sourceSessionKey: opts.sourceSessionKey, sourceNoteId: opts.sourceNoteId },\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 const markdown = note.markdown.replace(/^- \\[[ xX]\\]/m, `- [${done ? 'x' : ' '}]`);\n return this.updateNote(noteId, { markdown, taskMeta: { ...note.taskMeta, done } });\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, { taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch } });\n }\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\nexport { buildNoteAttachmentRef };\n"],"mappings":"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"}