@xopcai/xopc 0.0.91 → 0.0.92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-uwPn7ZW9.js +222 -0
- package/dist/gateway/static/root/assets/{apps-page-CIC8bmvZ.js → apps-page-CWKdhSPU.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-hEhW7Mbk.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-CYWL5DLD.js → channels-status-swr-XzddfJW2.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-TVqLlGAC.js → cron-api--I8LJ44S.js} +1 -1
- package/dist/gateway/static/root/assets/cron-page-B0kvgZGR.js +1 -0
- package/dist/gateway/static/root/assets/{dist-CUV1uY5f.js → dist-CYgHMQO0.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-mTLHRDp1.js → extension-debug-page-6cRP0nA9.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-iI8BI7WK.js → extension-page-DpwIkspI.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-ByXcdubM.js → extension-settings-page-DYbnQUxH.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-BWtQq_Ys.js → fetch-DTN0w7rV.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-BsZ-4VT5.js → field-primitives-CslW6HwD.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-config-api-2UiKevxG.js +1 -0
- package/dist/gateway/static/root/assets/index-BUKUv7QW.css +1 -0
- package/dist/gateway/static/root/assets/{index-CKkR-v9U.js → index-DnevRVa6.js} +82 -82
- package/dist/gateway/static/root/assets/logs-page-sOP4TXJ4.js +1 -0
- package/dist/gateway/static/root/assets/{note-detail-page-DJ2Mb4x7.js → note-detail-page-DvW2qg4i.js} +3 -3
- package/dist/gateway/static/root/assets/{note-time-JLBPSLzK.js → note-time-BEiibLJv.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-BE-75qz9.js → notes-page-BFQaquHU.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CptjDKAX.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BctKqHcf.js +2 -0
- package/dist/gateway/static/root/assets/{settings-form-section-DSYCknxM.js → settings-form-section-QJh5ruel.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-V3p-hISB.js +2 -0
- package/dist/gateway/static/root/assets/{share-preview-page-awRqs4hV.js → share-preview-page-DBsvvbmD.js} +1 -1
- package/dist/gateway/static/root/assets/skills-page-q2zPUJAR.js +2 -0
- package/dist/gateway/static/root/assets/{theme-store-BC-42BoZ.js → theme-store-ht5iswWS.js} +1 -1
- package/dist/gateway/static/root/assets/{url-CY1RQKTU.js → url-CWWpfkq1.js} +2 -2
- package/dist/gateway/static/root/assets/{utils-DX3TQuap.js → utils-DhPv9xoB.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-DLSKUipa.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-page.utils-ClC37yEp.js → workflow-page.utils-CJqnPWkW.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-DRRQ1A0l.js +27 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-config.d.ts +2 -9
- package/dist/src/agent/mcp/bundle-mcp-config.js +10 -34
- package/dist/src/agent/mcp/bundle-mcp-config.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-policy.js +2 -2
- package/dist/src/agent/mcp/bundle-mcp-policy.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +4 -4
- package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
- package/dist/src/agent/mcp/index.js +2 -2
- package/dist/src/cli/command-catalog.js +0 -4
- package/dist/src/cli/command-catalog.js.map +1 -1
- package/dist/src/cli/command-loaders.js +1 -2
- package/dist/src/cli/command-loaders.js.map +1 -1
- package/dist/src/cli/command-manifest.js +0 -4
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/config/index.d.ts +0 -1
- package/dist/src/config/index.js +1 -2
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/schema.d.ts +36 -6
- package/dist/src/config/schema.js +13 -11
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/connectors/builtin-catalog.d.ts +2 -0
- package/dist/src/connectors/builtin-catalog.js +152 -0
- package/dist/src/connectors/builtin-catalog.js.map +1 -0
- package/dist/src/connectors/catalog.d.ts +5 -0
- package/dist/src/connectors/catalog.js +13 -0
- package/dist/src/connectors/catalog.js.map +1 -0
- package/dist/src/connectors/health.d.ts +3 -0
- package/dist/src/connectors/health.js +61 -0
- package/dist/src/connectors/health.js.map +1 -0
- package/dist/src/connectors/install.d.ts +5 -0
- package/dist/src/connectors/install.js +46 -0
- package/dist/src/connectors/install.js.map +1 -0
- package/dist/src/connectors/instances.d.ts +4 -0
- package/dist/src/connectors/instances.js +43 -0
- package/dist/src/connectors/instances.js.map +1 -0
- package/dist/src/connectors/materialize.d.ts +9 -0
- package/dist/src/connectors/materialize.js +76 -0
- package/dist/src/connectors/materialize.js.map +1 -0
- package/dist/src/connectors/oauth.d.ts +22 -0
- package/dist/src/connectors/oauth.js +99 -0
- package/dist/src/connectors/oauth.js.map +1 -0
- package/dist/src/connectors/providers.d.ts +9 -0
- package/dist/src/connectors/providers.js +20 -0
- package/dist/src/connectors/providers.js.map +1 -0
- package/dist/src/connectors/secret-store.d.ts +7 -0
- package/dist/src/connectors/secret-store.js +47 -0
- package/dist/src/connectors/secret-store.js.map +1 -0
- package/dist/src/connectors/types.d.ts +102 -0
- package/dist/src/connectors/types.js +1 -0
- package/dist/src/connectors/usage.d.ts +6 -0
- package/dist/src/connectors/usage.js +63 -0
- package/dist/src/connectors/usage.js.map +1 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/routes/connectors.d.ts +3 -0
- package/dist/src/gateway/hono/routes/connectors.js +177 -0
- package/dist/src/gateway/hono/routes/connectors.js.map +1 -0
- package/dist/src/gateway/hono/routes/home.d.ts +12 -0
- package/dist/src/gateway/hono/routes/home.js +50 -0
- package/dist/src/gateway/hono/routes/home.js.map +1 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +12 -4
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.js +31 -0
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/mcp/channel-bridge.js.map +1 -1
- package/dist/src/notes/index.d.ts +1 -1
- package/dist/src/notes/service.d.ts +11 -0
- package/dist/src/notes/service.js +42 -0
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/notes/store.d.ts +1 -0
- package/dist/src/notes/store.js +29 -4
- package/dist/src/notes/store.js.map +1 -1
- package/dist/src/notes/types.d.ts +39 -2
- package/dist/src/session/store.d.ts +2 -0
- package/dist/src/session/store.js +21 -1
- package/dist/src/session/store.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-bVWUlrlD.js +0 -222
- package/dist/gateway/static/root/assets/channels-settings-C8G8RAAP.js +0 -1
- package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-BtcFYlvv.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-config-api-WjTsRLCU.js +0 -1
- package/dist/gateway/static/root/assets/index-VlELBY99.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-ClnIpxfd.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-bJJkWtTl.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-WcMXLq2U.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-Lu-i1JG7.js +0 -2
- package/dist/gateway/static/root/assets/voice-api-key-field-B5uKlDqA.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-C7VhIXtR.js +0 -27
- package/dist/src/cli/commands/mcp.d.ts +0 -4
- package/dist/src/cli/commands/mcp.js +0 -85
- package/dist/src/cli/commands/mcp.js.map +0 -1
- package/dist/src/config/mcp-config.d.ts +0 -34
- package/dist/src/config/mcp-config.js +0 -116
- package/dist/src/config/mcp-config.js.map +0 -1
- package/dist/src/gateway/hono/routes/mcp.d.ts +0 -3
- package/dist/src/gateway/hono/routes/mcp.js +0 -99
- package/dist/src/gateway/hono/routes/mcp.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notes.js","names":[],"sources":["../../../../../src/gateway/hono/routes/notes.ts"],"sourcesContent":["import { createReadStream } from 'node:fs';\nimport { access, stat } from 'node:fs/promises';\nimport { Readable } from 'node:stream';\n\nimport type { Hono } from 'hono';\nimport { stream } from 'hono/streaming';\n\nimport type { CaptureChannel, CaptureSource, Note, NoteBlock, NoteKind, NoteStatus, SnapshotTrigger } from '../../../notes/types.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst VALID_KINDS = new Set<NoteKind>(['thought', 'todo', 'voice', 'media', 'bookmark', 'mixed']);\nconst VALID_STATUSES = new Set<NoteStatus>(['inbox', 'processed', 'archived', 'trashed']);\nconst VALID_CHANNELS = new Set<CaptureChannel>(['app', 'web', 'electron', 'tui', 'telegram', 'wechat', 'feishu']);\n\nfunction parseCaptureSource(body: Record<string, unknown>): CaptureSource {\n const channel = typeof body.channel === 'string' && VALID_CHANNELS.has(body.channel as CaptureChannel)\n ? (body.channel as CaptureChannel)\n : 'web';\n const platform = body.platform === 'ios' || body.platform === 'android' ? body.platform : undefined;\n return { channel, platform };\n}\n\nfunction parseBlocks(value: unknown): NoteBlock[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return value.filter((block): block is NoteBlock => {\n if (!block || typeof block !== 'object') return false;\n const candidate = block as Record<string, unknown>;\n if (typeof candidate.id !== 'string' || typeof candidate.type !== 'string') return false;\n if (candidate.type === 'image') {\n return typeof candidate.attachmentId === 'string';\n }\n return true;\n });\n}\n\nfunction buildNotePatch(body: Record<string, unknown>): Partial<Note> {\n const patch: Partial<Note> = {};\n if (typeof body.title === 'string') patch.title = body.title;\n if (typeof body.text === 'string') patch.text = body.text;\n if (Array.isArray(body.blocks)) patch.blocks = parseBlocks(body.blocks);\n if (typeof body.kind === 'string' && VALID_KINDS.has(body.kind as NoteKind)) patch.kind = body.kind as NoteKind;\n if (typeof body.status === 'string' && VALID_STATUSES.has(body.status as NoteStatus)) patch.status = body.status as NoteStatus;\n if (Array.isArray(body.tags)) patch.tags = body.tags.filter((tag): tag is string => typeof tag === 'string');\n if (typeof body.pinned === 'boolean') patch.pinned = body.pinned;\n if (typeof body.localVersion === 'number') patch.localVersion = body.localVersion;\n if (body.ai && typeof body.ai === 'object') patch.ai = body.ai as Note['ai'];\n if (body.aiDeep && typeof body.aiDeep === 'object') patch.aiDeep = body.aiDeep as Note['aiDeep'];\n return patch;\n}\n\nexport function registerNotesRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n\n // POST /api/notes/quick-capture — minimal text capture\n authenticated.post('/api/notes/quick-capture', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ error: 'Missing required field: text' }, 400);\n }\n const source = parseCaptureSource(body);\n const note = await service.notesServiceInstance.quickCapture(text, source);\n return c.json({ note }, 201);\n });\n\n // GET /api/notes — list with filters\n authenticated.get('/api/notes', async (c) => {\n const status = c.req.query('status') as NoteStatus | undefined;\n const kind = c.req.query('kind') as NoteKind | undefined;\n const tag = c.req.query('tag');\n const search = c.req.query('search');\n const pinnedRaw = c.req.query('pinned');\n const limitRaw = c.req.query('limit');\n const offsetRaw = c.req.query('offset');\n const sortBy = c.req.query('sortBy') as 'createdAt' | 'updatedAt' | undefined;\n const sortOrder = c.req.query('sortOrder') as 'asc' | 'desc' | undefined;\n\n const result = await service.notesServiceInstance.listNotes({\n status: status && VALID_STATUSES.has(status) ? status : undefined,\n kind: kind && VALID_KINDS.has(kind) ? kind : undefined,\n tag: tag || undefined,\n search: search || undefined,\n pinned: pinnedRaw === 'true' ? true : pinnedRaw === 'false' ? false : undefined,\n limit: limitRaw ? parseInt(limitRaw, 10) : undefined,\n offset: offsetRaw ? parseInt(offsetRaw, 10) : undefined,\n sortBy: sortBy === 'createdAt' || sortBy === 'updatedAt' ? sortBy : undefined,\n sortOrder: sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : undefined,\n });\n return c.json(result);\n });\n\n // POST /api/notes — full create (JSON or multipart)\n authenticated.post('/api/notes', async (c) => {\n const contentType = c.req.header('content-type') || '';\n\n if (contentType.includes('multipart/form-data')) {\n let body: Record<string, unknown>;\n try {\n body = await c.req.parseBody({ all: true });\n } catch {\n return c.json({ error: 'Invalid multipart body' }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : undefined;\n const kindRaw = typeof body.kind === 'string' ? body.kind : undefined;\n const tagsRaw = typeof body.tags === 'string' ? body.tags : undefined;\n const source = parseCaptureSource(body as Record<string, unknown>);\n\n const note = await service.notesServiceInstance.createNote({\n text,\n kind: kindRaw && VALID_KINDS.has(kindRaw as NoteKind) ? (kindRaw as NoteKind) : undefined,\n tags: tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : undefined,\n capturedVia: source,\n });\n\n const file = body.file;\n if (file && typeof file === 'object') {\n let buf: Buffer | null = null;\n let fileName = 'upload';\n let mimeType = 'application/octet-stream';\n\n if (file instanceof File) {\n buf = Buffer.from(await file.arrayBuffer());\n fileName = file.name || fileName;\n mimeType = file.type || mimeType;\n } else if (typeof (file as Blob).arrayBuffer === 'function') {\n buf = Buffer.from(await (file as Blob).arrayBuffer());\n }\n\n if (buf) {\n const durationRaw = body.duration;\n const duration =\n typeof durationRaw === 'string'\n ? parseInt(durationRaw, 10)\n : typeof durationRaw === 'number'\n ? durationRaw\n : undefined;\n await service.notesServiceInstance.addAttachment(note.id, {\n name: fileName,\n buffer: buf,\n mimeType,\n duration: Number.isFinite(duration) ? duration : undefined,\n });\n }\n }\n\n const full = await service.notesServiceInstance.getNote(note.id);\n return c.json({ note: full }, 201);\n }\n\n // JSON body\n const body = await c.req.json().catch(() => ({}));\n const text = typeof body.text === 'string' ? body.text.trim() : undefined;\n const blocks = parseBlocks(body.blocks);\n const kindRaw = typeof body.kind === 'string' ? body.kind : undefined;\n const tagsRaw = Array.isArray(body.tags) ? body.tags.filter((t: unknown) => typeof t === 'string') : undefined;\n const source = parseCaptureSource(body);\n\n const note = await service.notesServiceInstance.createNote({\n text,\n blocks,\n kind: kindRaw && VALID_KINDS.has(kindRaw as NoteKind) ? (kindRaw as NoteKind) : undefined,\n tags: tagsRaw,\n capturedVia: source,\n pinned: body.pinned === true,\n });\n return c.json({ note }, 201);\n });\n\n // POST /api/notes/sync — local-first block sync with optimistic conflict check\n authenticated.post('/api/notes/sync', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const noteId = typeof body.noteId === 'string' ? body.noteId : '';\n if (!noteId) {\n return c.json({ error: 'Missing required field: noteId' }, 400);\n }\n\n const baseRemoteVersion = typeof body.baseRemoteVersion === 'number' ? body.baseRemoteVersion : undefined;\n const patch = buildNotePatch(body);\n const result = await service.notesServiceInstance.syncNote(noteId, patch, baseRemoteVersion);\n if (!result.note) {\n return c.json({ error: 'Note not found' }, 404);\n }\n if (result.conflict) {\n return c.json({ conflict: true, note: result.note }, 409);\n }\n return c.json({ conflict: false, note: result.note });\n });\n\n // GET /api/notes/:id — single note\n authenticated.get('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const note = await service.notesServiceInstance.getNote(id);\n if (!note) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ note });\n });\n\n // PATCH /api/notes/:id — update\n authenticated.patch('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n\n const patch = buildNotePatch(body);\n const trigger: SnapshotTrigger =\n body.trigger === 'ai_edit' || body.trigger === 'sync' || body.trigger === 'restore'\n ? body.trigger\n : 'edit';\n\n const updated = await service.notesServiceInstance.updateNote(id, patch, trigger);\n if (!updated) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ note: updated });\n });\n\n // DELETE /api/notes/:id — delete note\n authenticated.delete('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const removed = await service.notesServiceInstance.deleteNote(id);\n if (!removed) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ deleted: true });\n });\n\n // GET /api/notes/:id/history — list version snapshots\n authenticated.get('/api/notes/:id/history', async (c) => {\n const id = c.req.param('id');\n const entries = await service.notesServiceInstance.listNoteHistory(id);\n return c.json({ entries });\n });\n\n // GET /api/notes/:id/history/:timestamp — get full snapshot\n authenticated.get('/api/notes/:id/history/:timestamp', async (c) => {\n const id = c.req.param('id');\n const timestamp = parseInt(c.req.param('timestamp'), 10);\n if (!Number.isFinite(timestamp)) {\n return c.json({ error: 'Invalid timestamp' }, 400);\n }\n const snapshot = await service.notesServiceInstance.getNoteSnapshot(id, timestamp);\n if (!snapshot) {\n return c.json({ error: 'Snapshot not found' }, 404);\n }\n return c.json({ snapshot });\n });\n\n // POST /api/notes/:id/history/restore — restore a snapshot\n authenticated.post('/api/notes/:id/history/restore', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n const timestamp = typeof body.timestamp === 'number' ? body.timestamp : 0;\n if (!timestamp) {\n return c.json({ error: 'Missing required field: timestamp' }, 400);\n }\n const note = await service.notesServiceInstance.restoreNoteSnapshot(id, timestamp);\n if (!note) {\n return c.json({ error: 'Snapshot or note not found' }, 404);\n }\n return c.json({ note });\n });\n\n // POST /api/notes/:id/ai/edit — generate previewable block-level AI patch\n authenticated.post('/api/notes/:id/ai/edit', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n const instruction = typeof body.instruction === 'string' ? body.instruction.trim() : '';\n if (!instruction) {\n return c.json({ error: 'Missing required field: instruction' }, 400);\n }\n\n const blocks = parseBlocks(body.blocks);\n const result = await service.notesServiceInstance.createAiEditPatch(id, instruction, blocks);\n if (!result) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json(result);\n });\n\n // POST /api/notes/:id/media — upload attachment to existing note\n authenticated.post('/api/notes/:id/media', async (c) => {\n const noteId = c.req.param('id');\n let body: Record<string, unknown>;\n try {\n body = await c.req.parseBody({ all: true });\n } catch {\n return c.json({ error: 'Invalid multipart body' }, 400);\n }\n\n const file = body.file;\n if (!file || typeof file !== 'object') {\n return c.json({ error: 'Missing file field' }, 400);\n }\n\n let buf: Buffer;\n let fileName = 'upload';\n let mimeType = 'application/octet-stream';\n\n if (file instanceof File) {\n buf = Buffer.from(await file.arrayBuffer());\n fileName = file.name || fileName;\n mimeType = file.type || mimeType;\n } else if (typeof (file as Blob).arrayBuffer === 'function') {\n buf = Buffer.from(await (file as Blob).arrayBuffer());\n } else {\n return c.json({ error: 'Invalid file upload' }, 400);\n }\n\n const durationRaw = body.duration;\n const duration = typeof durationRaw === 'string' ? parseInt(durationRaw, 10) : undefined;\n\n const attachment = await service.notesServiceInstance.addAttachment(noteId, {\n name: fileName,\n buffer: buf,\n mimeType,\n duration: Number.isFinite(duration) ? duration : undefined,\n });\n\n if (!attachment) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ attachment }, 201);\n });\n\n // GET /api/notes/:id/media/:attachmentId — serve attachment file\n authenticated.get('/api/notes/:id/media/:attachmentId', async (c) => {\n const noteId = c.req.param('id');\n const attachmentId = c.req.param('attachmentId');\n\n const result = await service.notesServiceInstance.getAttachmentPath(noteId, attachmentId);\n if (!result) {\n return c.json({ error: 'Attachment not found' }, 404);\n }\n\n const { filePath, mimeType, fileName } = result;\n\n try {\n await access(filePath);\n } catch {\n return c.json({ error: 'Attachment file missing' }, 404);\n }\n\n const fileStat = await stat(filePath);\n\n c.header('Content-Type', mimeType);\n c.header('Content-Length', String(fileStat.size));\n c.header('Content-Disposition', `inline; filename=\"${encodeURIComponent(fileName)}\"`);\n c.header('Cache-Control', 'private, max-age=31536000, immutable');\n\n return stream(c, async (s) => {\n const readable = Readable.toWeb(createReadStream(filePath)) as ReadableStream<Uint8Array>;\n await s.pipe(readable);\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notes.js","names":[],"sources":["../../../../../src/gateway/hono/routes/notes.ts"],"sourcesContent":["import { createReadStream } from 'node:fs';\nimport { access, stat } from 'node:fs/promises';\nimport { Readable } from 'node:stream';\n\nimport type { Hono } from 'hono';\nimport { stream } from 'hono/streaming';\n\nimport type { CaptureChannel, CaptureSource, Note, NoteBlock, NoteKind, NoteStatus, SnapshotTrigger } from '../../../notes/types.js';\nimport type { AuthenticatedRouteDeps } from './deps.js';\n\nconst VALID_KINDS = new Set<NoteKind>(['thought', 'todo', 'voice', 'media', 'bookmark', 'mixed']);\nconst VALID_STATUSES = new Set<NoteStatus>(['inbox', 'processed', 'archived', 'trashed']);\nconst VALID_CHANNELS = new Set<CaptureChannel>(['app', 'web', 'electron', 'tui', 'telegram', 'wechat', 'feishu']);\n\nfunction parseCaptureSource(body: Record<string, unknown>): CaptureSource {\n const channel = typeof body.channel === 'string' && VALID_CHANNELS.has(body.channel as CaptureChannel)\n ? (body.channel as CaptureChannel)\n : 'web';\n const platform = body.platform === 'ios' || body.platform === 'android' ? body.platform : undefined;\n return { channel, platform };\n}\n\nfunction parseBlocks(value: unknown): NoteBlock[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return value.filter((block): block is NoteBlock => {\n if (!block || typeof block !== 'object') return false;\n const candidate = block as Record<string, unknown>;\n if (typeof candidate.id !== 'string' || typeof candidate.type !== 'string') return false;\n if (candidate.type === 'image') {\n return typeof candidate.attachmentId === 'string';\n }\n return true;\n });\n}\n\nfunction buildNotePatch(body: Record<string, unknown>): Partial<Note> {\n const patch: Partial<Note> = {};\n if (typeof body.title === 'string') patch.title = body.title;\n if (typeof body.text === 'string') patch.text = body.text;\n if (Array.isArray(body.blocks)) patch.blocks = parseBlocks(body.blocks);\n if (typeof body.kind === 'string' && VALID_KINDS.has(body.kind as NoteKind)) patch.kind = body.kind as NoteKind;\n if (typeof body.status === 'string' && VALID_STATUSES.has(body.status as NoteStatus)) patch.status = body.status as NoteStatus;\n if (Array.isArray(body.tags)) patch.tags = body.tags.filter((tag): tag is string => typeof tag === 'string');\n if (typeof body.pinned === 'boolean') patch.pinned = body.pinned;\n if (typeof body.localVersion === 'number') patch.localVersion = body.localVersion;\n if (body.ai && typeof body.ai === 'object') patch.ai = body.ai as Note['ai'];\n if (body.aiDeep && typeof body.aiDeep === 'object') patch.aiDeep = body.aiDeep as Note['aiDeep'];\n return patch;\n}\n\nexport function registerNotesRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void {\n const { service } = deps;\n\n // POST /api/notes/quick-capture — minimal text capture\n authenticated.post('/api/notes/quick-capture', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const text = typeof body.text === 'string' ? body.text.trim() : '';\n if (!text) {\n return c.json({ error: 'Missing required field: text' }, 400);\n }\n const source = parseCaptureSource(body);\n const note = await service.notesServiceInstance.quickCapture(text, source);\n return c.json({ note }, 201);\n });\n\n // GET /api/notes — list with filters\n authenticated.get('/api/notes', async (c) => {\n const status = c.req.query('status') as NoteStatus | undefined;\n const kind = c.req.query('kind') as NoteKind | undefined;\n const tag = c.req.query('tag');\n const search = c.req.query('search');\n const pinnedRaw = c.req.query('pinned');\n const limitRaw = c.req.query('limit');\n const offsetRaw = c.req.query('offset');\n const sortBy = c.req.query('sortBy') as 'createdAt' | 'updatedAt' | undefined;\n const sortOrder = c.req.query('sortOrder') as 'asc' | 'desc' | undefined;\n\n const result = await service.notesServiceInstance.listNotes({\n status: status && VALID_STATUSES.has(status) ? status : undefined,\n kind: kind && VALID_KINDS.has(kind) ? kind : undefined,\n tag: tag || undefined,\n search: search || undefined,\n pinned: pinnedRaw === 'true' ? true : pinnedRaw === 'false' ? false : undefined,\n limit: limitRaw ? parseInt(limitRaw, 10) : undefined,\n offset: offsetRaw ? parseInt(offsetRaw, 10) : undefined,\n sortBy: sortBy === 'createdAt' || sortBy === 'updatedAt' ? sortBy : undefined,\n sortOrder: sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : undefined,\n });\n return c.json(result);\n });\n\n // POST /api/notes — full create (JSON or multipart)\n authenticated.post('/api/notes', async (c) => {\n const contentType = c.req.header('content-type') || '';\n\n if (contentType.includes('multipart/form-data')) {\n let body: Record<string, unknown>;\n try {\n body = await c.req.parseBody({ all: true });\n } catch {\n return c.json({ error: 'Invalid multipart body' }, 400);\n }\n\n const text = typeof body.text === 'string' ? body.text.trim() : undefined;\n const kindRaw = typeof body.kind === 'string' ? body.kind : undefined;\n const tagsRaw = typeof body.tags === 'string' ? body.tags : undefined;\n const source = parseCaptureSource(body as Record<string, unknown>);\n\n const note = await service.notesServiceInstance.createNote({\n text,\n kind: kindRaw && VALID_KINDS.has(kindRaw as NoteKind) ? (kindRaw as NoteKind) : undefined,\n tags: tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : undefined,\n capturedVia: source,\n });\n\n const file = body.file;\n if (file && typeof file === 'object') {\n let buf: Buffer | null = null;\n let fileName = 'upload';\n let mimeType = 'application/octet-stream';\n\n if (file instanceof File) {\n buf = Buffer.from(await file.arrayBuffer());\n fileName = file.name || fileName;\n mimeType = file.type || mimeType;\n } else if (typeof (file as Blob).arrayBuffer === 'function') {\n buf = Buffer.from(await (file as Blob).arrayBuffer());\n }\n\n if (buf) {\n const durationRaw = body.duration;\n const duration =\n typeof durationRaw === 'string'\n ? parseInt(durationRaw, 10)\n : typeof durationRaw === 'number'\n ? durationRaw\n : undefined;\n await service.notesServiceInstance.addAttachment(note.id, {\n name: fileName,\n buffer: buf,\n mimeType,\n duration: Number.isFinite(duration) ? duration : undefined,\n });\n }\n }\n\n const full = await service.notesServiceInstance.getNote(note.id);\n return c.json({ note: full }, 201);\n }\n\n // JSON body\n const body = await c.req.json().catch(() => ({}));\n const text = typeof body.text === 'string' ? body.text.trim() : undefined;\n const blocks = parseBlocks(body.blocks);\n const kindRaw = typeof body.kind === 'string' ? body.kind : undefined;\n const tagsRaw = Array.isArray(body.tags) ? body.tags.filter((t: unknown) => typeof t === 'string') : undefined;\n const source = parseCaptureSource(body);\n\n const note = await service.notesServiceInstance.createNote({\n text,\n blocks,\n kind: kindRaw && VALID_KINDS.has(kindRaw as NoteKind) ? (kindRaw as NoteKind) : undefined,\n tags: tagsRaw,\n capturedVia: source,\n pinned: body.pinned === true,\n });\n return c.json({ note }, 201);\n });\n\n // POST /api/notes/sync — local-first block sync with optimistic conflict check\n authenticated.post('/api/notes/sync', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n const noteId = typeof body.noteId === 'string' ? body.noteId : '';\n if (!noteId) {\n return c.json({ error: 'Missing required field: noteId' }, 400);\n }\n\n const baseRemoteVersion = typeof body.baseRemoteVersion === 'number' ? body.baseRemoteVersion : undefined;\n const patch = buildNotePatch(body);\n const result = await service.notesServiceInstance.syncNote(noteId, patch, baseRemoteVersion);\n if (!result.note) {\n return c.json({ error: 'Note not found' }, 404);\n }\n if (result.conflict) {\n return c.json({ conflict: true, note: result.note }, 409);\n }\n return c.json({ conflict: false, note: result.note });\n });\n\n // GET /api/notes/:id — single note\n authenticated.get('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const note = await service.notesServiceInstance.getNote(id);\n if (!note) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ note });\n });\n\n // PATCH /api/notes/:id — update\n authenticated.patch('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n\n const patch = buildNotePatch(body);\n const trigger: SnapshotTrigger =\n body.trigger === 'ai_edit' || body.trigger === 'sync' || body.trigger === 'restore'\n ? body.trigger\n : 'edit';\n\n const updated = await service.notesServiceInstance.updateNote(id, patch, trigger);\n if (!updated) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ note: updated });\n });\n\n // DELETE /api/notes/:id — delete note\n authenticated.delete('/api/notes/:id', async (c) => {\n const id = c.req.param('id');\n const removed = await service.notesServiceInstance.deleteNote(id);\n if (!removed) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ deleted: true });\n });\n\n // GET /api/notes/:id/history — list version snapshots\n authenticated.get('/api/notes/:id/history', async (c) => {\n const id = c.req.param('id');\n const entries = await service.notesServiceInstance.listNoteHistory(id);\n return c.json({ entries });\n });\n\n // GET /api/notes/:id/history/:timestamp — get full snapshot\n authenticated.get('/api/notes/:id/history/:timestamp', async (c) => {\n const id = c.req.param('id');\n const timestamp = parseInt(c.req.param('timestamp'), 10);\n if (!Number.isFinite(timestamp)) {\n return c.json({ error: 'Invalid timestamp' }, 400);\n }\n const snapshot = await service.notesServiceInstance.getNoteSnapshot(id, timestamp);\n if (!snapshot) {\n return c.json({ error: 'Snapshot not found' }, 404);\n }\n return c.json({ snapshot });\n });\n\n // POST /api/notes/:id/history/restore — restore a snapshot\n authenticated.post('/api/notes/:id/history/restore', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n const timestamp = typeof body.timestamp === 'number' ? body.timestamp : 0;\n if (!timestamp) {\n return c.json({ error: 'Missing required field: timestamp' }, 400);\n }\n const note = await service.notesServiceInstance.restoreNoteSnapshot(id, timestamp);\n if (!note) {\n return c.json({ error: 'Snapshot or note not found' }, 404);\n }\n return c.json({ note });\n });\n\n // POST /api/notes/:id/ai/edit — generate previewable block-level AI patch\n authenticated.post('/api/notes/:id/ai/edit', async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json().catch(() => ({}));\n const instruction = typeof body.instruction === 'string' ? body.instruction.trim() : '';\n if (!instruction) {\n return c.json({ error: 'Missing required field: instruction' }, 400);\n }\n\n const blocks = parseBlocks(body.blocks);\n const result = await service.notesServiceInstance.createAiEditPatch(id, instruction, blocks);\n if (!result) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json(result);\n });\n\n // POST /api/notes/:id/media — upload attachment to existing note\n authenticated.post('/api/notes/:id/media', async (c) => {\n const noteId = c.req.param('id');\n let body: Record<string, unknown>;\n try {\n body = await c.req.parseBody({ all: true });\n } catch {\n return c.json({ error: 'Invalid multipart body' }, 400);\n }\n\n const file = body.file;\n if (!file || typeof file !== 'object') {\n return c.json({ error: 'Missing file field' }, 400);\n }\n\n let buf: Buffer;\n let fileName = 'upload';\n let mimeType = 'application/octet-stream';\n\n if (file instanceof File) {\n buf = Buffer.from(await file.arrayBuffer());\n fileName = file.name || fileName;\n mimeType = file.type || mimeType;\n } else if (typeof (file as Blob).arrayBuffer === 'function') {\n buf = Buffer.from(await (file as Blob).arrayBuffer());\n } else {\n return c.json({ error: 'Invalid file upload' }, 400);\n }\n\n const durationRaw = body.duration;\n const duration = typeof durationRaw === 'string' ? parseInt(durationRaw, 10) : undefined;\n\n const attachment = await service.notesServiceInstance.addAttachment(noteId, {\n name: fileName,\n buffer: buf,\n mimeType,\n duration: Number.isFinite(duration) ? duration : undefined,\n });\n\n if (!attachment) {\n return c.json({ error: 'Note not found' }, 404);\n }\n return c.json({ attachment }, 201);\n });\n\n // GET /api/notes/:id/media/:attachmentId — serve attachment file\n authenticated.get('/api/notes/:id/media/:attachmentId', async (c) => {\n const noteId = c.req.param('id');\n const attachmentId = c.req.param('attachmentId');\n\n const result = await service.notesServiceInstance.getAttachmentPath(noteId, attachmentId);\n if (!result) {\n return c.json({ error: 'Attachment not found' }, 404);\n }\n\n const { filePath, mimeType, fileName } = result;\n\n try {\n await access(filePath);\n } catch {\n return c.json({ error: 'Attachment file missing' }, 404);\n }\n\n const fileStat = await stat(filePath);\n\n c.header('Content-Type', mimeType);\n c.header('Content-Length', String(fileStat.size));\n c.header('Content-Disposition', `inline; filename=\"${encodeURIComponent(fileName)}\"`);\n c.header('Cache-Control', 'private, max-age=31536000, immutable');\n\n return stream(c, async (s) => {\n const readable = Readable.toWeb(createReadStream(filePath)) as ReadableStream<Uint8Array>;\n await s.pipe(readable);\n });\n });\n\n // ── Task / Space / Open tracking ────────────────────────────────────\n\n authenticated.post('/api/notes/task', async (c) => {\n const body = await c.req.json().catch(() => ({})) as Record<string, unknown>;\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title) return c.json({ error: 'Missing required field: title' }, 400);\n\n const source = parseCaptureSource(body);\n const note = await service.notesServiceInstance.createTask(title, source, {\n dueAt: typeof body.dueAt === 'number' ? body.dueAt : undefined,\n priority: body.priority === 'high' || body.priority === 'medium' || body.priority === 'low' ? body.priority : undefined,\n sourceSessionKey: typeof body.sourceSessionKey === 'string' ? body.sourceSessionKey : undefined,\n sourceNoteId: typeof body.sourceNoteId === 'string' ? body.sourceNoteId : undefined,\n groupId: typeof body.groupId === 'string' ? body.groupId : undefined,\n });\n return c.json({ note }, 201);\n });\n\n authenticated.post('/api/notes/:id/toggle-done', async (c) => {\n const note = await service.notesServiceInstance.toggleTaskDone(c.req.param('id'));\n if (!note) return c.json({ error: 'Not found or not a task' }, 404);\n return c.json({ note });\n });\n\n authenticated.post('/api/notes/:id/open', async (c) => {\n const note = await service.notesServiceInstance.recordOpen(c.req.param('id'));\n if (!note) return c.json({ error: 'Not found' }, 404);\n return c.json({ note });\n });\n\n authenticated.post('/api/notes/:id/move', async (c) => {\n const body = await c.req.json().catch(() => ({})) as Record<string, unknown>;\n const groupId = typeof body.groupId === 'string' ? body.groupId : null;\n const note = await service.notesServiceInstance.moveToGroup(c.req.param('id'), groupId);\n if (!note) return c.json({ error: 'Not found' }, 404);\n return c.json({ note });\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
|
|
2
|
+
import { appendCronEventLines } from "./event-prompt.js";
|
|
2
3
|
import { isWithinActiveHours } from "./active-hours.js";
|
|
3
4
|
import { isHeartbeatContentEmpty } from "./content-check.js";
|
|
4
|
-
import { appendCronEventLines } from "./event-prompt.js";
|
|
5
5
|
import { createHeartbeatWake } from "./wake.js";
|
|
6
6
|
export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
|
|
@@ -149,7 +149,7 @@ var XopcChannelBridge = class {
|
|
|
149
149
|
return Array.from(this.pendingApprovals.values());
|
|
150
150
|
}
|
|
151
151
|
async respondToApproval(params) {
|
|
152
|
-
return this.client.postJson("/api/
|
|
152
|
+
return this.client.postJson("/api/connectors/approvals/respond", params);
|
|
153
153
|
}
|
|
154
154
|
async handleClaudePermissionRequest(_params) {}
|
|
155
155
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-bridge.js","names":[],"sources":["../../../src/mcp/channel-bridge.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport { loadConfig } from '../config/loader.js';\nimport type {\n ApprovalDecision,\n ApprovalKind,\n ClaudeChannelMode,\n ConversationDescriptor,\n PendingApproval,\n QueueEvent,\n SessionRow,\n WaitFilter,\n} from './channel-shared.js';\nimport { toConversation } from './channel-shared.js';\nimport {\n createGatewayHttpClientFromConfig,\n resolveGatewayHttpBaseUrl,\n type GatewayHttpClient,\n} from './gateway-http-client.js';\nimport { loadUndiciRuntimeDeps } from '../infra/undici-fetch.js';\n\nconst QUEUE_LIMIT = 1000;\n\nexport class XopcChannelBridge {\n private client: GatewayHttpClient | null = null;\n private readonly queue: QueueEvent[] = [];\n private readonly pendingApprovals = new Map<string, PendingApproval>();\n private cursor = 0;\n private closed = false;\n private eventsAbort: AbortController | null = null;\n\n constructor(\n private readonly cfg: Config,\n private readonly params: {\n gatewayUrl?: string;\n gatewayToken?: string;\n claudeChannelMode: ClaudeChannelMode;\n verbose: boolean;\n },\n ) {}\n\n setServer(_server: unknown): void {\n void _server;\n }\n\n async start(): Promise<void> {\n this.client = createGatewayHttpClientFromConfig({\n config: this.cfg,\n gatewayUrl: this.params.gatewayUrl,\n gatewayToken: this.params.gatewayToken,\n });\n this.connectEvents();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.eventsAbort?.abort();\n this.eventsAbort = null;\n this.queue.length = 0;\n this.pendingApprovals.clear();\n }\n\n private connectEvents(): void {\n if (this.closed) {\n return;\n }\n this.eventsAbort?.abort();\n const abort = new AbortController();\n this.eventsAbort = abort;\n const baseUrl = resolveGatewayHttpBaseUrl(this.cfg, this.params.gatewayUrl);\n void this.runEventsLoop(baseUrl, this.params.gatewayToken, abort.signal);\n }\n\n private async runEventsLoop(\n baseUrl: string,\n token: string | undefined,\n signal: AbortSignal,\n ): Promise<void> {\n const url = new URL(`${baseUrl}/api/events`);\n if (token?.trim()) {\n url.searchParams.set('token', token.trim());\n }\n try {\n const res = await loadUndiciRuntimeDeps().fetch(url.toString(), {\n headers: { Accept: 'text/event-stream' },\n signal,\n });\n if (!res.ok || !res.body) {\n return;\n }\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n while (!this.closed && !signal.aborted) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n buffer += decoder.decode(value, { stream: true });\n let splitAt = buffer.indexOf('\\n\\n');\n while (splitAt >= 0) {\n this.handleSseChunk(buffer.slice(0, splitAt));\n buffer = buffer.slice(splitAt + 2);\n splitAt = buffer.indexOf('\\n\\n');\n }\n }\n } catch {\n // Best-effort reconnect while bridge stays open.\n }\n if (!this.closed && !signal.aborted) {\n await new Promise((resolve) => setTimeout(resolve, 2000));\n if (!this.closed) {\n this.connectEvents();\n }\n }\n }\n\n private handleSseChunk(chunk: string): void {\n let eventName = 'message';\n let data = '';\n for (const line of chunk.split('\\n')) {\n if (line.startsWith('event:')) {\n eventName = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n data += line.slice(5).trim();\n }\n }\n if (!data) {\n return;\n }\n try {\n const parsed = JSON.parse(data) as Record<string, unknown>;\n this.enqueueFromBroadcast({ type: eventName, ...parsed });\n } catch {\n this.enqueueFromBroadcast({ type: eventName, raw: data });\n }\n }\n\n private enqueueFromBroadcast(data: Record<string, unknown>): void {\n const type = String(data.type ?? data.event ?? '');\n if (!type) {\n return;\n }\n if (type.includes('message') || type.includes('session')) {\n this.pushEvent({\n cursor: ++this.cursor,\n type: 'message',\n sessionKey: String(data.sessionKey ?? data.key ?? ''),\n raw: data,\n });\n }\n }\n\n private pushEvent(event: QueueEvent): void {\n this.queue.push(event);\n while (this.queue.length > QUEUE_LIMIT) {\n this.queue.shift();\n }\n }\n\n async listConversations(args: {\n limit?: number;\n search?: string;\n channel?: string;\n }): Promise<ConversationDescriptor[]> {\n const client = this.client!;\n const query = new URLSearchParams();\n if (args.limit) query.set('limit', String(args.limit));\n if (args.search) query.set('search', args.search);\n if (args.channel) query.set('channel', args.channel);\n const qs = query.toString();\n const rows = await client.getJson<SessionRow[] | { sessions?: SessionRow[] }>(\n `/api/sessions${qs ? `?${qs}` : ''}`,\n );\n const sessions = Array.isArray(rows) ? rows : (rows.sessions ?? []);\n return sessions\n .map((row) => toConversation(row))\n .filter((c): c is ConversationDescriptor => c !== null);\n }\n\n async getConversation(sessionKey: string): Promise<ConversationDescriptor | null> {\n const client = this.client!;\n try {\n const row = await client.getJson<SessionRow>(`/api/sessions/${encodeURIComponent(sessionKey)}`);\n return toConversation(row);\n } catch {\n return null;\n }\n }\n\n async readMessages(\n sessionKey: string,\n limit: number,\n ): Promise<Array<Record<string, unknown>>> {\n const client = this.client!;\n const payload = await client.getJson<{ messages?: Array<Record<string, unknown>> }>(\n `/api/sessions/${encodeURIComponent(sessionKey)}/messages?limit=${limit}`,\n );\n return payload.messages ?? [];\n }\n\n pollEvents(\n filter: WaitFilter,\n limit: number,\n ): { events: QueueEvent[]; nextCursor: number } {\n const events = this.queue\n .filter((e) => e.cursor > filter.afterCursor)\n .filter((e) => !filter.sessionKey || ('sessionKey' in e && e.sessionKey === filter.sessionKey))\n .slice(0, limit);\n const nextCursor = events.length > 0 ? events[events.length - 1]!.cursor : filter.afterCursor;\n return { events, nextCursor };\n }\n\n async waitForEvent(filter: WaitFilter, timeoutMs: number): Promise<QueueEvent | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline && !this.closed) {\n const polled = this.pollEvents(filter, 1);\n if (polled.events.length > 0) {\n return polled.events[0] ?? null;\n }\n await new Promise((r) => setTimeout(r, 250));\n }\n return null;\n }\n\n async sendMessage(params: { sessionKey: string; text: string }): Promise<Record<string, unknown>> {\n const client = this.client!;\n return client.postJson('/api/agent', {\n sessionKey: params.sessionKey,\n message: params.text,\n });\n }\n\n listPendingApprovals(): PendingApproval[] {\n return Array.from(this.pendingApprovals.values());\n }\n\n async respondToApproval(params: {\n kind: ApprovalKind;\n id: string;\n decision: ApprovalDecision;\n }): Promise<Record<string, unknown>> {\n const client = this.client!;\n return client.postJson('/api/mcp/approvals/respond', params);\n }\n\n async handleClaudePermissionRequest(_params: {\n requestId: string;\n toolName: string;\n description: string;\n inputPreview: string;\n }): Promise<void> {\n void _params;\n }\n}\n\n// `serveXopcChannelMcp` lazy wrapper removed — it dynamically imported\n// `./channel-server.js`, forming a bridge ↔ server ↔ tools ↔ bridge cycle.\n// Callers (currently only `cli/commands/mcp.ts`) should import\n// `serveXopcChannelMcpImpl` directly from `./channel-server.js`.\n\nexport function loadMcpServeConfig(): Config {\n return loadConfig();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"channel-bridge.js","names":[],"sources":["../../../src/mcp/channel-bridge.ts"],"sourcesContent":["import type { Config } from '../config/schema.js';\nimport { loadConfig } from '../config/loader.js';\nimport type {\n ApprovalDecision,\n ApprovalKind,\n ClaudeChannelMode,\n ConversationDescriptor,\n PendingApproval,\n QueueEvent,\n SessionRow,\n WaitFilter,\n} from './channel-shared.js';\nimport { toConversation } from './channel-shared.js';\nimport {\n createGatewayHttpClientFromConfig,\n resolveGatewayHttpBaseUrl,\n type GatewayHttpClient,\n} from './gateway-http-client.js';\nimport { loadUndiciRuntimeDeps } from '../infra/undici-fetch.js';\n\nconst QUEUE_LIMIT = 1000;\n\nexport class XopcChannelBridge {\n private client: GatewayHttpClient | null = null;\n private readonly queue: QueueEvent[] = [];\n private readonly pendingApprovals = new Map<string, PendingApproval>();\n private cursor = 0;\n private closed = false;\n private eventsAbort: AbortController | null = null;\n\n constructor(\n private readonly cfg: Config,\n private readonly params: {\n gatewayUrl?: string;\n gatewayToken?: string;\n claudeChannelMode: ClaudeChannelMode;\n verbose: boolean;\n },\n ) {}\n\n setServer(_server: unknown): void {\n void _server;\n }\n\n async start(): Promise<void> {\n this.client = createGatewayHttpClientFromConfig({\n config: this.cfg,\n gatewayUrl: this.params.gatewayUrl,\n gatewayToken: this.params.gatewayToken,\n });\n this.connectEvents();\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.eventsAbort?.abort();\n this.eventsAbort = null;\n this.queue.length = 0;\n this.pendingApprovals.clear();\n }\n\n private connectEvents(): void {\n if (this.closed) {\n return;\n }\n this.eventsAbort?.abort();\n const abort = new AbortController();\n this.eventsAbort = abort;\n const baseUrl = resolveGatewayHttpBaseUrl(this.cfg, this.params.gatewayUrl);\n void this.runEventsLoop(baseUrl, this.params.gatewayToken, abort.signal);\n }\n\n private async runEventsLoop(\n baseUrl: string,\n token: string | undefined,\n signal: AbortSignal,\n ): Promise<void> {\n const url = new URL(`${baseUrl}/api/events`);\n if (token?.trim()) {\n url.searchParams.set('token', token.trim());\n }\n try {\n const res = await loadUndiciRuntimeDeps().fetch(url.toString(), {\n headers: { Accept: 'text/event-stream' },\n signal,\n });\n if (!res.ok || !res.body) {\n return;\n }\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n while (!this.closed && !signal.aborted) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n buffer += decoder.decode(value, { stream: true });\n let splitAt = buffer.indexOf('\\n\\n');\n while (splitAt >= 0) {\n this.handleSseChunk(buffer.slice(0, splitAt));\n buffer = buffer.slice(splitAt + 2);\n splitAt = buffer.indexOf('\\n\\n');\n }\n }\n } catch {\n // Best-effort reconnect while bridge stays open.\n }\n if (!this.closed && !signal.aborted) {\n await new Promise((resolve) => setTimeout(resolve, 2000));\n if (!this.closed) {\n this.connectEvents();\n }\n }\n }\n\n private handleSseChunk(chunk: string): void {\n let eventName = 'message';\n let data = '';\n for (const line of chunk.split('\\n')) {\n if (line.startsWith('event:')) {\n eventName = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n data += line.slice(5).trim();\n }\n }\n if (!data) {\n return;\n }\n try {\n const parsed = JSON.parse(data) as Record<string, unknown>;\n this.enqueueFromBroadcast({ type: eventName, ...parsed });\n } catch {\n this.enqueueFromBroadcast({ type: eventName, raw: data });\n }\n }\n\n private enqueueFromBroadcast(data: Record<string, unknown>): void {\n const type = String(data.type ?? data.event ?? '');\n if (!type) {\n return;\n }\n if (type.includes('message') || type.includes('session')) {\n this.pushEvent({\n cursor: ++this.cursor,\n type: 'message',\n sessionKey: String(data.sessionKey ?? data.key ?? ''),\n raw: data,\n });\n }\n }\n\n private pushEvent(event: QueueEvent): void {\n this.queue.push(event);\n while (this.queue.length > QUEUE_LIMIT) {\n this.queue.shift();\n }\n }\n\n async listConversations(args: {\n limit?: number;\n search?: string;\n channel?: string;\n }): Promise<ConversationDescriptor[]> {\n const client = this.client!;\n const query = new URLSearchParams();\n if (args.limit) query.set('limit', String(args.limit));\n if (args.search) query.set('search', args.search);\n if (args.channel) query.set('channel', args.channel);\n const qs = query.toString();\n const rows = await client.getJson<SessionRow[] | { sessions?: SessionRow[] }>(\n `/api/sessions${qs ? `?${qs}` : ''}`,\n );\n const sessions = Array.isArray(rows) ? rows : (rows.sessions ?? []);\n return sessions\n .map((row) => toConversation(row))\n .filter((c): c is ConversationDescriptor => c !== null);\n }\n\n async getConversation(sessionKey: string): Promise<ConversationDescriptor | null> {\n const client = this.client!;\n try {\n const row = await client.getJson<SessionRow>(`/api/sessions/${encodeURIComponent(sessionKey)}`);\n return toConversation(row);\n } catch {\n return null;\n }\n }\n\n async readMessages(\n sessionKey: string,\n limit: number,\n ): Promise<Array<Record<string, unknown>>> {\n const client = this.client!;\n const payload = await client.getJson<{ messages?: Array<Record<string, unknown>> }>(\n `/api/sessions/${encodeURIComponent(sessionKey)}/messages?limit=${limit}`,\n );\n return payload.messages ?? [];\n }\n\n pollEvents(\n filter: WaitFilter,\n limit: number,\n ): { events: QueueEvent[]; nextCursor: number } {\n const events = this.queue\n .filter((e) => e.cursor > filter.afterCursor)\n .filter((e) => !filter.sessionKey || ('sessionKey' in e && e.sessionKey === filter.sessionKey))\n .slice(0, limit);\n const nextCursor = events.length > 0 ? events[events.length - 1]!.cursor : filter.afterCursor;\n return { events, nextCursor };\n }\n\n async waitForEvent(filter: WaitFilter, timeoutMs: number): Promise<QueueEvent | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline && !this.closed) {\n const polled = this.pollEvents(filter, 1);\n if (polled.events.length > 0) {\n return polled.events[0] ?? null;\n }\n await new Promise((r) => setTimeout(r, 250));\n }\n return null;\n }\n\n async sendMessage(params: { sessionKey: string; text: string }): Promise<Record<string, unknown>> {\n const client = this.client!;\n return client.postJson('/api/agent', {\n sessionKey: params.sessionKey,\n message: params.text,\n });\n }\n\n listPendingApprovals(): PendingApproval[] {\n return Array.from(this.pendingApprovals.values());\n }\n\n async respondToApproval(params: {\n kind: ApprovalKind;\n id: string;\n decision: ApprovalDecision;\n }): Promise<Record<string, unknown>> {\n const client = this.client!;\n return client.postJson('/api/connectors/approvals/respond', params);\n }\n\n async handleClaudePermissionRequest(_params: {\n requestId: string;\n toolName: string;\n description: string;\n inputPreview: string;\n }): Promise<void> {\n void _params;\n }\n}\n\nexport function loadMcpServeConfig(): Config {\n return loadConfig();\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { NotesStore } from './store.js';
|
|
2
2
|
export { NotesService } from './service.js';
|
|
3
3
|
export { resolveNotesDir, resolveNotesIndexPath, resolveNoteItemPath, resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';
|
|
4
|
-
export type { Note, NoteKind, NoteStatus, NoteAttachment, NoteAiMeta, NoteAiDeepMeta, NoteIndexEntry, NoteSnapshot, NoteSnapshotEntry, NotesIndexFile, NotesListQuery, CaptureSource, CaptureChannel, CreateNoteParams, SnapshotTrigger, } from './types.js';
|
|
4
|
+
export type { Note, NoteKind, NoteStatus, NoteAttachment, NoteAiMeta, NoteAiDeepMeta, NoteIndexEntry, NoteSnapshot, NoteSnapshotEntry, NotesIndexFile, NotesListQuery, CaptureSource, CaptureChannel, CreateNoteParams, SnapshotTrigger, NoteTaskMeta, NoteGroup, } from './types.js';
|
|
@@ -38,5 +38,16 @@ export declare class NotesService {
|
|
|
38
38
|
getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null>;
|
|
39
39
|
restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null>;
|
|
40
40
|
flush(): Promise<void>;
|
|
41
|
+
moveToGroup(noteId: string, groupId: string | null): Promise<Note | null>;
|
|
42
|
+
createTask(title: string, source: CaptureSource, options?: {
|
|
43
|
+
dueAt?: number;
|
|
44
|
+
priority?: 'high' | 'medium' | 'low';
|
|
45
|
+
sourceSessionKey?: string;
|
|
46
|
+
sourceNoteId?: string;
|
|
47
|
+
groupId?: string;
|
|
48
|
+
}): Promise<Note>;
|
|
49
|
+
toggleTaskDone(noteId: string): Promise<Note | null>;
|
|
50
|
+
updateTaskMeta(noteId: string, patch: Partial<import('./types.js').NoteTaskMeta>): Promise<Note | null>;
|
|
51
|
+
recordOpen(noteId: string): Promise<Note | null>;
|
|
41
52
|
private maybeSaveSnapshot;
|
|
42
53
|
}
|
|
@@ -305,6 +305,48 @@ var NotesService = class {
|
|
|
305
305
|
async flush() {
|
|
306
306
|
await this.store.flush();
|
|
307
307
|
}
|
|
308
|
+
async moveToGroup(noteId, groupId) {
|
|
309
|
+
return this.updateNote(noteId, { groupId: groupId ?? void 0 });
|
|
310
|
+
}
|
|
311
|
+
async createTask(title, source, options) {
|
|
312
|
+
return this.createNote({
|
|
313
|
+
title,
|
|
314
|
+
kind: "task",
|
|
315
|
+
capturedVia: source,
|
|
316
|
+
groupId: options?.groupId,
|
|
317
|
+
taskMeta: {
|
|
318
|
+
done: false,
|
|
319
|
+
dueAt: options?.dueAt,
|
|
320
|
+
priority: options?.priority,
|
|
321
|
+
sourceSessionKey: options?.sourceSessionKey,
|
|
322
|
+
sourceNoteId: options?.sourceNoteId
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async toggleTaskDone(noteId) {
|
|
327
|
+
const note = await this.store.getNote(noteId);
|
|
328
|
+
if (!note || note.kind !== "task") return null;
|
|
329
|
+
const done = !note.taskMeta?.done;
|
|
330
|
+
return this.updateNote(noteId, {
|
|
331
|
+
taskMeta: {
|
|
332
|
+
...note.taskMeta,
|
|
333
|
+
done
|
|
334
|
+
},
|
|
335
|
+
status: done ? "archived" : "processed"
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
async updateTaskMeta(noteId, patch) {
|
|
339
|
+
const note = await this.store.getNote(noteId);
|
|
340
|
+
if (!note || note.kind !== "task") return null;
|
|
341
|
+
return this.updateNote(noteId, { taskMeta: {
|
|
342
|
+
...note.taskMeta,
|
|
343
|
+
done: note.taskMeta?.done ?? false,
|
|
344
|
+
...patch
|
|
345
|
+
} });
|
|
346
|
+
}
|
|
347
|
+
async recordOpen(noteId) {
|
|
348
|
+
return this.updateNote(noteId, { lastOpenedAt: Date.now() });
|
|
349
|
+
}
|
|
308
350
|
async maybeSaveSnapshot(note, trigger) {
|
|
309
351
|
if (trigger !== "edit") {
|
|
310
352
|
await this.store.saveSnapshot(note, trigger);
|
|
@@ -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 `})`;\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 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 { 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 `})`;\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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"}
|
package/dist/src/notes/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../utils/logger.js";
|
|
3
3
|
import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
|
|
4
|
-
import { buildNoteIndexMeta } from "./note-index-meta.js";
|
|
4
|
+
import { buildNoteIndexMeta, notePlainText } from "./note-index-meta.js";
|
|
5
5
|
import { resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath } from "./paths.js";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
@@ -31,7 +31,11 @@ function noteToIndexEntry(note) {
|
|
|
31
31
|
coverAttachmentId,
|
|
32
32
|
voiceAttachmentId,
|
|
33
33
|
voiceDurationSec,
|
|
34
|
-
attachmentNames
|
|
34
|
+
attachmentNames,
|
|
35
|
+
groupId: note.groupId || void 0,
|
|
36
|
+
lastOpenedAt: note.lastOpenedAt || void 0,
|
|
37
|
+
taskDone: note.taskMeta?.done,
|
|
38
|
+
taskDueAt: note.taskMeta?.dueAt
|
|
35
39
|
};
|
|
36
40
|
}
|
|
37
41
|
var NotesStore = class {
|
|
@@ -118,13 +122,31 @@ var NotesStore = class {
|
|
|
118
122
|
if (query.kind) results = results.filter((n) => n.kind === query.kind);
|
|
119
123
|
if (query.tag) results = results.filter((n) => n.tags?.includes(query.tag));
|
|
120
124
|
if (query.pinned !== void 0) results = results.filter((n) => Boolean(n.pinned) === query.pinned);
|
|
125
|
+
if (query.groupId !== void 0) if (query.groupId === "ungrouped") results = results.filter((n) => !n.groupId);
|
|
126
|
+
else results = results.filter((n) => n.groupId === query.groupId);
|
|
127
|
+
if (query.pendingTasksOnly) results = results.filter((n) => n.kind === "task" && !n.taskDone);
|
|
121
128
|
if (query.search) {
|
|
122
129
|
const term = query.search.toLowerCase();
|
|
123
|
-
|
|
130
|
+
const indexMatches = results.filter((n) => this.noteIndexEntryMatchesSearch(n, term));
|
|
131
|
+
const indexMatchedIds = new Set(indexMatches.map((n) => n.id));
|
|
132
|
+
const contentMatches = [];
|
|
133
|
+
const candidates = results.filter((n) => !indexMatchedIds.has(n.id));
|
|
134
|
+
for (const candidate of candidates) {
|
|
135
|
+
const note = await this.getNote(candidate.id);
|
|
136
|
+
if (!note) continue;
|
|
137
|
+
if ([
|
|
138
|
+
note.title,
|
|
139
|
+
notePlainText(note),
|
|
140
|
+
note.attachments?.map((a) => a.transcript).join(" ")
|
|
141
|
+
].filter(Boolean).join(" ").toLowerCase().includes(term)) contentMatches.push(candidate);
|
|
142
|
+
}
|
|
143
|
+
results = [...indexMatches, ...contentMatches];
|
|
124
144
|
}
|
|
125
145
|
const sortField = query.sortBy || "createdAt";
|
|
126
146
|
const sortDir = query.sortOrder === "asc" ? 1 : -1;
|
|
127
|
-
results = [...results].sort((a, b) =>
|
|
147
|
+
results = [...results].sort((a, b) => {
|
|
148
|
+
return ((a[sortField] ?? 0) - (b[sortField] ?? 0)) * sortDir;
|
|
149
|
+
});
|
|
128
150
|
const total = results.length;
|
|
129
151
|
const offset = query.offset || 0;
|
|
130
152
|
const limit = Math.min(query.limit || 50, 200);
|
|
@@ -133,6 +155,9 @@ var NotesStore = class {
|
|
|
133
155
|
total
|
|
134
156
|
};
|
|
135
157
|
}
|
|
158
|
+
noteIndexEntryMatchesSearch(entry, term) {
|
|
159
|
+
return Boolean(entry.title?.toLowerCase().includes(term) || entry.snippet?.toLowerCase().includes(term) || entry.tags?.some((tag) => tag.toLowerCase().includes(term)) || entry.attachmentNames?.some((name) => name.toLowerCase().includes(term)));
|
|
160
|
+
}
|
|
136
161
|
async saveAttachment(noteId, fileName, buffer) {
|
|
137
162
|
const mediaDir = resolveNoteMediaDir(noteId);
|
|
138
163
|
await mkdir(mediaDir, { recursive: true });
|
|
@@ -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 } 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 };\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.search) {\n const term = query.search.toLowerCase();\n results = results.filter((n) =>\n n.title?.toLowerCase().includes(term) ||\n n.snippet?.toLowerCase().includes(term) ||\n n.tags?.some((t) => t.toLowerCase().includes(term)) ||\n n.attachmentNames?.some((name) => name.includes(term)),\n );\n }\n\n const sortField = query.sortBy || 'createdAt';\n const sortDir = query.sortOrder === 'asc' ? 1 : -1;\n results = [...results].sort((a, b) => (a[sortField] - b[sortField]) * sortDir);\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 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 }> {\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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"}
|