@xopcai/xopc 0.0.101 → 0.0.103
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
- package/dist/extensions/feishu/src/transport/webhook/monitor.js +2 -2
- package/dist/extensions/feishu/src/transport/webhook/monitor.js.map +1 -1
- package/dist/extensions/feishu/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/src/adapters/streaming-adapter.js +1 -1
- package/dist/extensions/telegram/src/inbound-processor.js +1 -1
- package/dist/extensions/telegram/src/plugin.js +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +2 -2
- package/dist/extensions/telegram/src/send-options.js +8 -5
- package/dist/extensions/telegram/src/send-options.js.map +1 -1
- package/dist/extensions/telegram/src/update-offset-store.js +1 -1
- package/dist/extensions/telegram/src/workflow-progress.js +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/cdn/upload.js +1 -1
- package/dist/extensions/weixin/src/media/data-url.js +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
- package/dist/extensions/weixin/src/plugin.js +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js +1 -1
- package/dist/gateway/static/root/assets/{agents-u8cmUfGQ.js → agents-BwXkia_W.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-DF8m9cUE.js → apps-page-C5RmwmTT.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-BM3doRvi.js → channels-settings-tlOTdaXw.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-ah4zWEIP.js → channels-status-swr-D4XjqwqC.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-DgMuL_2b.js → cron-api-DcmfGuj0.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-COfUPKfq.js → cron-page-CijJ4G6F.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-Dzy3Q0ZH.js → dist-CWm8hTXZ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-kyxxefKh.js → extension-debug-page-BzYgTkS9.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BVbCBHoI.js → extension-page-BmTZH6gL.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-t1lb2A7v.js → extension-settings-page-CyJk_G64.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-drJUxxKP.js → fetch-CMoWY2Si.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-6Vo1Yga4.js → field-primitives-C4GpxsDo.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BF3psu4O.js → heartbeat-config-api-CLD0Gpi3.js} +1 -1
- package/dist/gateway/static/root/assets/index-0HmunJW9.js +4845 -0
- package/dist/gateway/static/root/assets/index-Bk75Phrd.css +1 -0
- package/dist/gateway/static/root/assets/{logs-page-BTVPXcHV.js → logs-page-BjdQjHwk.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-page-DqSAg_5Z.js → note-detail-page-8Er4197b.js} +2 -2
- package/dist/gateway/static/root/assets/{note-time-FSOjchic.js → note-time-CF3vf1jm.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-DkRX7BE2.js → notes-page-BBnTh0uI.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-Khdw35Zk.js → sessions-page-B0riYK7j.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-advanced-gate-DspC8oEm.js → settings-advanced-gate-DiP_WHu_.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DuxvUuur.js → settings-form-section-C7qA7y0J.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-DN8JBZ8F.js → settings-page-1ZPhcCZ9.js} +2 -2
- package/dist/gateway/static/root/assets/share-preview-page-Tvr5Mtq5.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page-ByYT0fXZ.js → skills-page-BQ1hllKD.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-B6KXPHgK.js → theme-store-HTvJlFLL.js} +1 -1
- package/dist/gateway/static/root/assets/url-F3_D_6zZ.js +3 -0
- package/dist/gateway/static/root/assets/{utils-DK_1H47k.js → utils-CPr3FBh2.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-BYN2EIMs.js → voice-api-key-field-BkbDl6dH.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-page.utils-Bi19YNTr.js → workflow-page.utils-CeBkAjP7.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-DP57FEKW.js → workflows-page-D059ysoF.js} +1 -1
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +7 -7
- package/dist/src/agent/agent-scope.js +1 -1
- package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
- package/dist/src/agent/context/workspace-seed.js +2 -2
- package/dist/src/agent/context/workspace-state.js +1 -1
- package/dist/src/agent/goals/goal-run-store.js +4 -4
- package/dist/src/agent/goals/persistent-goal-service.js +1 -1
- package/dist/src/agent/goals/post-turn.js +2 -2
- package/dist/src/agent/image/load-image-media.js +2 -2
- package/dist/src/agent/inbound/attachment-pipeline.js +2 -2
- package/dist/src/agent/inbound/attachment-pipeline.js.map +1 -1
- package/dist/src/agent/ipc/bus.js +1 -1
- package/dist/src/agent/ipc/inbox.js +2 -2
- package/dist/src/agent/ipc/socket.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
- package/dist/src/agent/memory/dreaming/events.js +1 -1
- package/dist/src/agent/memory/dreaming/last-run.js +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
- package/dist/src/agent/memory/dreaming/preview.js +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
- package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
- package/dist/src/agent/memory/dreaming/utils.js +1 -1
- package/dist/src/agent/memory/memory-flush.js +1 -1
- package/dist/src/agent/memory/plugin-discovery.js +1 -1
- package/dist/src/agent/models/manager.js +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
- package/dist/src/agent/reply/post-compaction-context.js +1 -1
- package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
- package/dist/src/agent/sandbox/path-policy.js +2 -2
- package/dist/src/agent/service.js +4 -4
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/config.js +1 -1
- package/dist/src/agent/skills/hub-hash.js +2 -2
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/skills/hub-pull.js +2 -2
- package/dist/src/agent/skills/index.js +1 -1
- package/dist/src/agent/skills/managed-store.js +1 -1
- package/dist/src/agent/skills/scanner.js +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/skills/skill-manager.js +1 -1
- package/dist/src/agent/tools/dreaming-tool.js +1 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +1 -1
- package/dist/src/agent/tools/send-media.js +2 -3
- package/dist/src/agent/tools/send-media.js.map +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js +1 -1
- package/dist/src/agent/tools/workflow-tool.js +1 -1
- package/dist/src/agent/tools/write.js +1 -1
- package/dist/src/agent/workflow/catalog.js +1 -1
- package/dist/src/auth/credentials.d.ts +7 -0
- package/dist/src/auth/credentials.js +37 -4
- package/dist/src/auth/credentials.js.map +1 -1
- package/dist/src/auth/index.d.ts +1 -1
- package/dist/src/auth/index.js.map +1 -1
- package/dist/src/auth/oauth/github-copilot.d.ts +1 -1
- package/dist/src/auth/oauth/google-antigravity.d.ts +1 -3
- package/dist/src/auth/oauth/google-antigravity.js +1 -1
- package/dist/src/auth/oauth/google-gemini-cli.d.ts +1 -3
- package/dist/src/auth/oauth/google-gemini-cli.js +1 -1
- package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-antigravity.js +3 -5
- package/dist/src/auth/oauth/google-oauth/google-antigravity.js.map +1 -0
- package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-gemini-cli.js +4 -5
- package/dist/src/auth/oauth/google-oauth/google-gemini-cli.js.map +1 -0
- package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/oauth-page.js +12 -4
- package/dist/src/auth/oauth/google-oauth/oauth-page.js.map +1 -0
- package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/pkce.d.ts +1 -1
- package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/pkce.js +2 -2
- package/dist/src/auth/oauth/google-oauth/pkce.js.map +1 -0
- package/dist/src/auth/oauth/index.js +2 -2
- package/dist/src/auth/oauth/kimi-coding.d.ts +1 -1
- package/dist/src/auth/oauth/kimi-coding.js +1 -1
- package/dist/src/auth/oauth/kimi-coding.js.map +1 -1
- package/dist/src/auth/oauth/minimax-cn.js +1 -1
- package/dist/src/auth/oauth/minimax-cn.js.map +1 -1
- package/dist/src/auth/oauth/minimax.js +1 -1
- package/dist/src/auth/oauth/minimax.js.map +1 -1
- package/dist/src/auth/oauth/openai-codex.d.ts +7 -2
- package/dist/src/auth/oauth/openai-codex.js +318 -1
- package/dist/src/auth/oauth/openai-codex.js.map +1 -0
- package/dist/src/auth/profiles/store.js +4 -3
- package/dist/src/auth/profiles/store.js.map +1 -1
- package/dist/src/auth/sync-provider-auth.js +1 -1
- package/dist/src/browser/cache-dir-policy.js +1 -1
- package/dist/src/browser/cdp-local-launcher.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +3 -3
- package/dist/src/browser/providers/cloakbrowser.js +4 -4
- package/dist/src/browser/providers/playwright-doctor.js +1 -1
- package/dist/src/browser/stealth.js +1 -1
- package/dist/src/channels/attachments/attachment-classifiers.d.ts +1 -0
- package/dist/src/channels/attachments/attachment-classifiers.js +13 -2
- package/dist/src/channels/attachments/attachment-classifiers.js.map +1 -1
- package/dist/src/channels/outbound/persist-store.js +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +1 -1
- package/dist/src/channels/pairing/pairing-store.js +2 -2
- package/dist/src/chat-commands/agent-edit.js +2 -2
- package/dist/src/chat-commands/builtins/config.js +2 -2
- package/dist/src/chat-commands/builtins/model.js +2 -2
- package/dist/src/chat-commands/builtins/model.js.map +1 -1
- package/dist/src/chat-commands/context.js +1 -1
- package/dist/src/cli/command-manifest.js +1 -1
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/cli/commands/auth.js +39 -51
- package/dist/src/cli/commands/auth.js.map +1 -1
- package/dist/src/cli/commands/config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
- package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
- package/dist/src/cli/commands/extension-dev.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension-pack.js +1 -1
- package/dist/src/cli/commands/gateway/logs.js +1 -1
- package/dist/src/cli/commands/image.js +1 -1
- package/dist/src/cli/commands/init.js +3 -3
- package/dist/src/cli/commands/models.js +189 -54
- package/dist/src/cli/commands/models.js.map +1 -1
- package/dist/src/cli/commands/onboard/model.d.ts +1 -4
- package/dist/src/cli/commands/onboard/model.js +253 -183
- package/dist/src/cli/commands/onboard/model.js.map +1 -1
- package/dist/src/cli/utils/init-workspace-core.js +2 -2
- package/dist/src/cli/utils/oauth-login.d.ts +17 -0
- package/dist/src/cli/utils/oauth-login.js +93 -0
- package/dist/src/cli/utils/oauth-login.js.map +1 -0
- package/dist/src/cli/utils/oauth-providers.js +2 -2
- package/dist/src/commands/agents.config.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/gateway-bind.js +1 -1
- package/dist/src/config/index.js +5 -5
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +2 -2
- package/dist/src/config/paths-state.js +1 -1
- package/dist/src/config/profile.js +2 -2
- package/dist/src/config/workspace-path.js +1 -1
- package/dist/src/cron/executor.js +2 -2
- package/dist/src/cron/persistence.js +1 -1
- package/dist/src/daemon/constants.js +1 -1
- package/dist/src/daemon/install-plan.js +2 -2
- package/dist/src/daemon/launchd.js +2 -2
- package/dist/src/daemon/schtasks.js +2 -2
- package/dist/src/daemon/systemd.js +2 -2
- package/dist/src/extensions/bundle-mcp.js +1 -1
- package/dist/src/extensions/discover-extensions.js +1 -1
- package/dist/src/extensions/health.js +1 -1
- package/dist/src/extensions/loader.js +1 -1
- package/dist/src/extensions/lockfile.js +2 -2
- package/dist/src/extensions/update.js +1 -1
- package/dist/src/gateway/agents-admin.js +3 -3
- package/dist/src/gateway/file-path-classifier.js +2 -2
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/extension-store.js +2 -2
- package/dist/src/gateway/hono/lib/static-ui.js +2 -2
- package/dist/src/gateway/hono/oauth-async.js +3 -3
- package/dist/src/gateway/hono/oauth.js +3 -3
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/dreaming.js +1 -1
- package/dist/src/gateway/hono/routes/host-fs.js +2 -2
- package/dist/src/gateway/hono/routes/models.js +26 -6
- package/dist/src/gateway/hono/routes/models.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.js +9 -22
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/hono/routes/shares.js +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +2 -2
- package/dist/src/gateway/lock.js +3 -3
- package/dist/src/gateway/ports.js +1 -1
- package/dist/src/gateway/service/agent-runner.js +2 -2
- package/dist/src/gateway/service/marketplace-service.js +2 -2
- package/dist/src/gateway/service.js +1 -1
- package/dist/src/gateway/workspace-fs-file-list.js +1 -1
- package/dist/src/infra/brew.js +1 -1
- package/dist/src/infra/package-json.js +1 -1
- package/dist/src/infra/package-update-steps.js +1 -1
- package/dist/src/infra/path-env.js +2 -2
- package/dist/src/infra/restart.js +2 -2
- package/dist/src/infra/stable-node-path.js +1 -1
- package/dist/src/infra/update-check.js +1 -1
- package/dist/src/infra/update-global.js +1 -1
- package/dist/src/infra/update-lock.js +3 -3
- package/dist/src/infra/update-runner.js +1 -1
- package/dist/src/infra/update-startup.js +2 -2
- package/dist/src/infra/write-file-atomic.js +2 -2
- package/dist/src/media/store.js +4 -1
- package/dist/src/media/store.js.map +1 -1
- package/dist/src/notes/note-attachment-sync.d.ts +2 -3
- package/dist/src/notes/note-attachment-sync.js +2 -27
- package/dist/src/notes/note-attachment-sync.js.map +1 -1
- package/dist/src/notes/note-index-meta.d.ts +6 -2
- package/dist/src/notes/note-index-meta.js +11 -16
- package/dist/src/notes/note-index-meta.js.map +1 -1
- package/dist/src/notes/note-markdown.d.ts +61 -0
- package/dist/src/notes/note-markdown.js +177 -0
- package/dist/src/notes/note-markdown.js.map +1 -0
- package/dist/src/notes/service.d.ts +9 -7
- package/dist/src/notes/service.js +163 -312
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/notes/store.js +4 -5
- package/dist/src/notes/store.js.map +1 -1
- package/dist/src/notes/types.d.ts +29 -58
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/index.d.ts +3 -3
- package/dist/src/providers/index.js +25 -17
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/presentation.d.ts +34 -0
- package/dist/src/providers/presentation.js +162 -0
- package/dist/src/providers/presentation.js.map +1 -0
- package/dist/src/session/init-session-turn.js +2 -2
- package/dist/src/session/resolve-session.js +3 -3
- package/dist/src/session/session-title.js +2 -2
- package/dist/src/session/store.js +2 -2
- package/dist/src/share/share-auto.js +2 -2
- package/dist/src/share/share-store.js +3 -3
- package/dist/src/share/share-thumbnail.js +2 -2
- package/dist/src/share/share-zip.js +1 -1
- package/dist/src/share/site-share-store.js +3 -3
- package/dist/src/share/site-static-serve.js +1 -1
- package/dist/src/storage/sqlite/connection.js +2 -2
- package/dist/src/storage/sqlite/memory-index-repository.js +2 -2
- package/dist/src/storage/sqlite/migrations/003_add_note_markdown_meta.sql +4 -0
- package/dist/src/storage/sqlite/migrations/discover.js +1 -1
- package/dist/src/storage/sqlite/migrations/runner.d.ts +2 -2
- package/dist/src/storage/sqlite/migrations/runner.js +5 -5
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +2 -2
- package/dist/src/storage/sqlite/notes-repository.js +18 -6
- package/dist/src/storage/sqlite/notes-repository.js.map +1 -1
- package/dist/src/storage/sqlite/schema.js +4 -4
- package/dist/src/storage/sqlite/schema.sql +5 -1
- package/dist/src/tui/clipboard-image.js +3 -3
- package/dist/src/tui/theme-manager.js +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -1
- package/dist/src/tui/tui-scoped-models.js +2 -2
- package/dist/src/tui/tui-settings.js +1 -1
- package/dist/src/tui/tui.js +3 -3
- package/dist/src/tunnel/frpc-binary.js +3 -3
- package/dist/src/tunnel/frpc-config.js +1 -1
- package/dist/src/tunnel/frpc-extract.js +1 -1
- package/dist/src/tunnel/tunnel-state.js +1 -1
- package/dist/src/utils/logger/audit.js +1 -1
- package/dist/src/utils/logger/log-store.js +1 -1
- package/dist/src/utils/logger/rotation.js +1 -1
- package/dist/src/voice/tts/audio.js +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/dist/src/workflows/store/event-store.js +1 -1
- package/dist/src/workflows/store/run-store.js +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/index-DjNDHzwG.css +0 -1
- package/dist/gateway/static/root/assets/index-WAyKAilR.js +0 -4840
- package/dist/gateway/static/root/assets/share-preview-page-UNAcbiMb.js +0 -2
- package/dist/gateway/static/root/assets/url-q28QhI7l.js +0 -3
- package/dist/src/auth/oauth/vendor-pi-google/google-antigravity.js.map +0 -1
- package/dist/src/auth/oauth/vendor-pi-google/google-gemini-cli.js.map +0 -1
- package/dist/src/auth/oauth/vendor-pi-google/oauth-page.js.map +0 -1
- package/dist/src/auth/oauth/vendor-pi-google/pkce.js.map +0 -1
- /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-antigravity.d.ts +0 -0
- /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/google-gemini-cli.d.ts +0 -0
- /package/dist/src/auth/oauth/{vendor-pi-google → google-oauth}/oauth-page.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../../src/notes/service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteAttachmentRef, attachmentIdFromTarget } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\n NoteBlock,\n NoteCatalysisAction,\n NoteCatalysisMeta,\n NoteCatalysisReport,\n NoteIndexEntry,\n NoteKind,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesService');\n\nfunction inferKind(\n text?: string,\n hasAttachments?: boolean,\n attachments?: NoteAttachment[],\n): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) {\n return 'voice';\n }\n if (hasAttachments) return 'media';\n if (!text) return 'thought';\n const lower = text.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) ||\n /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(text)) {\n return 'todo';\n }\n if (/^https?:\\/\\//.test(text.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction deriveDefaultTitle(text?: string): string | undefined {\n const normalizedText = text?.trim().replace(/\\s+/g, ' ');\n if (!normalizedText) return undefined;\n return Array.from(normalizedText).slice(0, 10).join('');\n}\n\nfunction createBlockId(): string {\n return `block_${Date.now()}_${randomUUID().slice(0, 8)}`;\n}\n\nconst IMAGE_MARKDOWN = /^!\\[([^\\]]*)\\]\\(([^)]+)\\)$/;\n\nfunction noteTextToBlocks(text?: string, noteId?: string): NoteBlock[] | undefined {\n if (!text?.trim()) return undefined;\n const now = Date.now();\n return text.split(/\\n{2,}/).map((part) => {\n const trimmed = part.trim();\n if (noteId) {\n const imageMatch = trimmed.match(IMAGE_MARKDOWN);\n if (imageMatch) {\n const attachmentId = attachmentIdFromTarget(imageMatch[2], noteId);\n if (attachmentId) {\n return {\n id: createBlockId(),\n type: 'image' as const,\n attachmentId,\n alt: imageMatch[1] || undefined,\n createdAt: now,\n updatedAt: now,\n };\n }\n }\n }\n return {\n id: createBlockId(),\n type: 'paragraph' as const,\n text: trimmed,\n createdAt: now,\n updatedAt: now,\n };\n });\n}\n\nfunction blocksToPlainText(blocks?: NoteBlock[], noteId?: string): string | undefined {\n if (!blocks?.length) return undefined;\n return blocks\n .map((block) => {\n if (block.type === 'divider') return '---';\n if (block.type === 'todo') return `${block.checked ? '[x]' : '[ ]'} ${block.text}`;\n if (block.type === 'image') {\n if (noteId) {\n return `})`;\n }\n return block.alt ?? '';\n }\n return block.text;\n })\n .filter((text) => text.trim().length > 0)\n .join('\\n\\n');\n}\n\nfunction createAiOrganizedBlocks(blocks: NoteBlock[], instruction: string): NoteBlock[] {\n const now = Date.now();\n const plainText = blocksToPlainText(blocks) || '';\n const lines = plainText\n .split(/\\n+/)\n .map((line) => line.trim())\n .filter(Boolean);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n\n if (wantsTodos) {\n const candidates = lines.filter((line) => /要|需|记得|todo|task|完成|提交|联系|跟进|提醒/i.test(line));\n return (candidates.length ? candidates : lines).slice(0, 12).map((line) => ({\n id: createBlockId(),\n type: 'todo' as const,\n text: line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim(),\n checked: false,\n createdAt: now,\n updatedAt: now,\n }));\n }\n\n if (wantsSummary) {\n const summary = lines.join(' ').slice(0, 220);\n return [{\n id: createBlockId(),\n type: 'paragraph',\n text: summary,\n createdAt: now,\n updatedAt: now,\n }];\n }\n\n const titleText = lines[0]?.slice(0, 40) || '整理后的笔记';\n const bodyLines = lines.slice(1).length ? lines.slice(1) : lines;\n return [\n {\n id: createBlockId(),\n type: 'heading',\n text: titleText,\n level: 2,\n createdAt: now,\n updatedAt: now,\n },\n ...bodyLines.map((line) => ({\n id: createBlockId(),\n type: 'bulletList' as const,\n text: line.replace(/^[-*\\d.\\s]+/, '').trim(),\n indent: 0,\n createdAt: now,\n updatedAt: now,\n })),\n ];\n}\n\nfunction splitMeaningfulLines(text?: string): string[] {\n return (text ?? '')\n .split(/[\\n。!?!?;;]+/)\n .map((line) => line.trim().replace(/^[-*\\d.\\s]+/, ''))\n .filter((line) => line.length > 0);\n}\n\nfunction summarizeIdea(text?: string): string {\n const lines = splitMeaningfulLines(text);\n const joined = lines.join(' ');\n return Array.from(joined || '这个想法').slice(0, 90).join('');\n}\n\nfunction inferCatalysisStage(note: Note): NonNullable<NoteCatalysisMeta['stage']> {\n const text = `${note.title ?? ''}\\n${note.text ?? ''}`;\n if (/发布|上线|分享|推广|launch|ship/i.test(text)) return 'shipped';\n if (/验证|实验|用户|指标|反馈|validate|experiment/i.test(text)) return 'validating';\n if (/实现|开发|MVP|原型|prototype|build/i.test(text)) return 'developing';\n if (text.trim().length > 80) return 'incubating';\n return 'seed';\n}\n\nfunction buildCatalysisReport(note: Note): NoteCatalysisReport {\n const generatedAt = Date.now();\n const summary = summarizeIdea(note.text ?? note.title);\n const title = note.title?.trim() || summary.slice(0, 28) || '未命名想法';\n const hasUserSignal = /用户|客户|读者|创作者|团队|个人|开发者|founder|creator|user/i.test(summary);\n const hasProductSignal = /产品|工具|平台|workflow|agent|AI|自动|系统|应用/i.test(summary);\n const confidence = Math.min(0.86, Math.max(0.42, 0.48 + (hasUserSignal ? 0.16 : 0) + (hasProductSignal ? 0.18 : 0) + Math.min(summary.length, 120) / 600));\n\n return {\n originalNoteId: note.id,\n generatedAt,\n title,\n valueHypothesis: `如果把「${summary}」推进成一个可体验的小成果,它最可能的价值是帮助用户更快完成判断、表达或行动。`,\n targetUsers: hasUserSignal ? ['笔记作者自己', '有类似场景的目标用户'] : ['笔记作者自己', '未来可能被这个想法帮助的人'],\n keyQuestions: [\n '这个想法最想解决的具体痛点是什么?',\n '第一个可验证的用户场景是什么?',\n '什么样的最小成果能证明它值得继续投入?',\n ],\n mvpPath: [\n '把想法改写成一句清晰的问题陈述。',\n '列出 1 个目标用户和 1 个高频使用场景。',\n '产出一个最小原型、提纲或行动清单。',\n ],\n risks: [\n '想法还停留在概念层,缺少明确使用场景。',\n '下一步过大时容易变成长期搁置的项目。',\n ],\n nextActions: [\n { kind: 'chat', text: '和 AI 继续深聊这个想法,收敛成问题陈述。' },\n { kind: 'research', text: '补充 3 个相似产品、案例或用户反馈。' },\n { kind: 'task', text: '写下今天能完成的一个最小推进动作。' },\n ],\n confidence: Number(confidence.toFixed(2)),\n };\n}\n\nfunction stripJsonCodeFence(raw: string): string {\n return raw.trim().replace(/^```(?:json)?\\s*/i, '').replace(/\\s*```$/i, '').trim();\n}\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw.trim()) return null;\n const text = stripJsonCodeFence(raw);\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? data as Record<string, unknown> : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? data as Record<string, unknown> : null;\n } catch {\n return null;\n }\n }\n}\n\nfunction extractAssistantText(result: { content?: unknown }): string {\n if (!Array.isArray(result.content)) return '';\n return result.content\n .filter((block): block is { type: string; text: string } => {\n return !!block && typeof block === 'object' && (block as { type?: string }).type === 'text';\n })\n .map((block) => block.text)\n .join('')\n .trim();\n}\n\nfunction stringArray(value: unknown, fallback: string[], limit: number): string[] {\n if (!Array.isArray(value)) return fallback;\n const items = value\n .map((item) => typeof item === 'string' ? item.trim() : '')\n .filter(Boolean)\n .slice(0, limit);\n return items.length ? items : fallback;\n}\n\nfunction catalysisActions(value: unknown, fallback: NoteCatalysisAction[]): NoteCatalysisAction[] {\n if (!Array.isArray(value)) return fallback;\n const allowed = new Set<NoteCatalysisAction['kind']>(['task', 'workflow', 'research', 'share', 'chat']);\n const items: NoteCatalysisAction[] = [];\n for (const item of value) {\n if (!item || typeof item !== 'object') continue;\n const row = item as { kind?: unknown; text?: unknown };\n const kind = typeof row.kind === 'string' && allowed.has(row.kind as NoteCatalysisAction['kind'])\n ? row.kind as NoteCatalysisAction['kind']\n : 'task';\n const text = typeof row.text === 'string' ? row.text.trim() : '';\n if (text) items.push({ kind, text });\n }\n return items.length ? items.slice(0, 5) : fallback;\n}\n\nfunction normalizeAiCatalysisReport(note: Note, data: Record<string, unknown>): NoteCatalysisReport {\n const fallback = buildCatalysisReport(note);\n const confidenceRaw = typeof data.confidence === 'number' ? data.confidence : fallback.confidence;\n const confidence = Math.min(1, Math.max(0, confidenceRaw));\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title: typeof data.title === 'string' && data.title.trim() ? data.title.trim().slice(0, 80) : fallback.title,\n valueHypothesis: typeof data.valueHypothesis === 'string' && data.valueHypothesis.trim()\n ? data.valueHypothesis.trim()\n : fallback.valueHypothesis,\n targetUsers: stringArray(data.targetUsers, fallback.targetUsers, 5),\n keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions, 6),\n mvpPath: stringArray(data.mvpPath, fallback.mvpPath, 6),\n risks: stringArray(data.risks, fallback.risks, 5),\n nextActions: catalysisActions(data.nextActions, fallback.nextActions),\n confidence: Number(confidence.toFixed(2)),\n };\n}\n\nfunction buildCatalysisPrompt(note: Note): string {\n const title = note.title?.trim() || '未命名笔记';\n const text = (note.text ?? '').trim() || '(无正文)';\n return `你是一个帮助用户把想法推进成成果的个人 AI Agent。请基于这条 Note 做“想法催化”,帮助用户进入:想法 → 创造 → 分享 → 反馈 的循环。\\n\\n要求:\\n- 使用自然、具体的中文。\\n- 不要空泛鼓励,要给出可执行路径。\\n- 输出必须是单个 JSON 对象,不要 Markdown,不要代码块。\\n- 字段必须包含:title, valueHypothesis, targetUsers, keyQuestions, mvpPath, risks, nextActions, confidence。\\n- nextActions 每项格式为 {\"kind\":\"task|workflow|research|share|chat\",\"text\":\"...\"}。\\n- confidence 是 0 到 1 的数字。\\n\\nNote 标题:${title}\\n\\nNote 正文:\\n${text.slice(0, 6000)}`;\n}\n\nasync function buildAiCatalysisReport(note: Note, config?: Config): Promise<NoteCatalysisReport> {\n const model = resolveModel(getDefaultModelSync(config));\n const user: UserMessage = {\n role: 'user',\n content: buildCatalysisPrompt(note),\n timestamp: Date.now(),\n };\n const result = await complete(\n model,\n { messages: [user] },\n { maxTokens: 1800, temperature: 0.2 },\n );\n const text = extractAssistantText(result);\n const data = extractJsonObject(text);\n if (!data) {\n throw new Error('Catalysis model did not return valid JSON');\n }\n return normalizeAiCatalysisReport(note, data);\n}\n\nconst SNAPSHOT_THROTTLE_MS = 60_000;\nconst MAX_SNAPSHOTS_PER_NOTE = 30;\n\nexport class NotesService {\n private store: NotesStore;\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(store: NotesStore) {\n this.store = store;\n }\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n log.debug('NotesService initialized');\n }\n\n async quickCapture(text: string, source: CaptureSource): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = noteTextToBlocks(text, id);\n const note: Note = {\n id,\n title: deriveDefaultTitle(text),\n kind: inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Quick capture');\n return note;\n }\n\n async createNote(params: CreateNoteParams): Promise<Note> {\n const now = Date.now();\n const id = randomUUID();\n const blocks = params.blocks ?? noteTextToBlocks(params.text, id);\n const text = params.text ?? blocksToPlainText(blocks, id);\n const note: Note = {\n id,\n title: params.title,\n kind: params.kind || inferKind(text),\n status: 'inbox',\n text,\n blocks,\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n localVersion: 0,\n remoteVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Note created');\n return note;\n }\n\n async getNote(id: string): Promise<Note | null> {\n return this.store.getNote(id);\n }\n\n async updateNote(id: string, patch: Partial<Note>, trigger: SnapshotTrigger = 'edit'): Promise<Note | null> {\n const existing = await this.store.getNote(id);\n if (!existing) return null;\n\n const contentTouched = patch.text !== undefined || patch.blocks !== undefined || patch.title !== undefined;\n if (contentTouched) {\n await this.maybeSaveSnapshot(existing, trigger);\n }\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.blocks) {\n normalizedPatch.text = patch.text ?? blocksToPlainText(patch.blocks, existing.id);\n } else if (typeof patch.text === 'string') {\n normalizedPatch.blocks = patch.blocks ?? noteTextToBlocks(patch.text, existing.id);\n }\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 0) + 1;\n\n if (contentTouched) {\n const merged: Note = {\n ...existing,\n ...normalizedPatch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: Date.now(),\n };\n const reconciled = await this.reconcileAttachments(merged);\n normalizedPatch.attachments = reconciled.attachments;\n normalizedPatch.kind = reconciled.kind;\n }\n\n return this.store.updateNote(id, normalizedPatch);\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n\n for (const attachment of removed) {\n await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n }\n\n log.debug(\n { noteId: note.id, removedIds: removed.map((attachment) => attachment.id) },\n 'Pruned orphan note attachments',\n );\n\n const hasAttachments = kept.length > 0;\n return {\n ...note,\n attachments: hasAttachments ? kept : undefined,\n kind: inferKind(note.text, hasAttachments, kept),\n };\n }\n\n async syncNote(\n id: string,\n patch: Partial<Note>,\n baseRemoteVersion?: number,\n ): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n\n const currentRemoteVersion = existing.remoteVersion ?? 0;\n if (baseRemoteVersion !== undefined && baseRemoteVersion < currentRemoteVersion) {\n return { note: existing, conflict: true };\n }\n\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(\n id: string,\n instruction: string,\n blocks?: NoteBlock[],\n ): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n\n const sourceBlocks = blocks?.length ? blocks : note.blocks ?? noteTextToBlocks(note.text, note.id) ?? [];\n const organizedBlocks = createAiOrganizedBlocks(sourceBlocks, instruction);\n const patch: NoteAiPatch = {\n id: randomUUID(),\n summary: `已根据「${instruction.slice(0, 40)}」生成可预览的块级整理建议`,\n operations: [{ type: 'replaceBlocks', blocks: organizedBlocks }],\n };\n\n return {\n message: 'AI edit patch generated',\n patch,\n };\n }\n\n async catalyzeNote(id: string, config?: Config): Promise<{ note: Note; report: NoteCatalysisReport } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n\n let report: NoteCatalysisReport;\n try {\n report = await buildAiCatalysisReport(note, config);\n } catch (err) {\n log.warn({ err, noteId: id }, 'AI note catalysis failed; using local fallback report');\n report = buildCatalysisReport(note);\n }\n\n const existingDeep = note.aiDeep;\n const catalysis: NoteCatalysisMeta = {\n ...existingDeep?.catalysis,\n status: 'catalyzed',\n stage: inferCatalysisStage(note),\n lastCatalyzedAt: report.generatedAt,\n confidence: report.confidence,\n report,\n };\n\n const updated = await this.updateNote(id, {\n ai: {\n ...note.ai,\n intent: note.ai?.intent ?? 'idea',\n summary: note.ai?.summary ?? report.valueHypothesis,\n },\n aiDeep: {\n ...existingDeep,\n processedAt: report.generatedAt,\n insights: report.valueHypothesis,\n catalysis,\n },\n status: note.status === 'inbox' ? 'processed' : note.status,\n }, 'ai_edit');\n\n return updated ? { note: updated, report } : null;\n }\n\n async recordCatalysisFeedback(\n id: string,\n feedback: NonNullable<NoteCatalysisMeta['feedback']>,\n ): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const now = Date.now();\n return this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: now,\n catalysis: {\n status: note.aiDeep?.catalysis?.status ?? 'catalyzed',\n ...note.aiDeep?.catalysis,\n feedback,\n },\n },\n }, 'ai_edit');\n }\n\n async linkNoteThread(id: string, sessionKey: string): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const existingKeys = note.aiDeep?.catalysis?.linkedSessionKeys ?? [];\n const linkedSessionKeys = Array.from(new Set([sessionKey, ...existingKeys]));\n return this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: note.aiDeep?.catalysis?.status ?? 'none',\n ...note.aiDeep?.catalysis,\n sourceSessionKey: note.aiDeep?.catalysis?.sourceSessionKey ?? sessionKey,\n linkedSessionKeys,\n },\n },\n }, 'sync');\n }\n\n async listNoteThreads(id: string): Promise<string[] | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const keys = [\n note.aiDeep?.catalysis?.sourceSessionKey,\n ...(note.aiDeep?.catalysis?.linkedSessionKeys ?? []),\n ].filter((key): key is string => typeof key === 'string' && key.length > 0);\n return Array.from(new Set(keys));\n }\n\n async appendTextToNote(id: string, content: string, heading = 'AI 讨论沉淀'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const trimmed = content.trim();\n if (!trimmed) return note;\n const currentText = note.text?.trimEnd() ?? '';\n const nextText = `${currentText}${currentText ? '\\n\\n' : ''}## ${heading}\\n\\n${trimmed}`;\n return this.updateNote(id, { text: nextText }, 'ai_edit');\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const deleted = await this.store.deleteNote(id);\n if (deleted) {\n await this.store.deleteAllSnapshots(id);\n this.lastSnapshotAt.delete(id);\n }\n return deleted;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean }> {\n return this.store.listNotes(query);\n }\n\n async addAttachment(\n noteId: string,\n file: { name: string; buffer: Buffer; mimeType: string; duration?: number },\n ): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: inferAttachmentType(file.mimeType),\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n\n const attachments = [...(note.attachments || []), attachment];\n const kind: NoteKind =\n note.kind === 'thought' && attachment.type === 'audio'\n ? 'voice'\n : note.kind === 'thought'\n ? 'media'\n : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n\n return attachment;\n }\n\n async getAttachmentPath(\n noteId: string,\n attachmentId: string,\n ): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n\n const fullPath = this.store.resolveAttachmentPath(noteId, attachment.relativePath);\n return { filePath: fullPath, mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> {\n return this.store.listSnapshots(noteId);\n }\n\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> {\n return this.store.getSnapshot(noteId, timestamp);\n }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n if (!snapshot) return null;\n const existing = await this.store.getNote(noteId);\n if (!existing) return null;\n\n await this.store.saveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n\n return this.store.updateNote(noteId, {\n title: snapshot.title,\n text: snapshot.text,\n blocks: snapshot.blocks,\n tags: snapshot.tags,\n });\n }\n\n async flush(): Promise<void> {\n await this.store.flush();\n }\n\n // ── Space grouping ──────────────────────────────────────────────────\n\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> {\n return this.updateNote(noteId, { groupId: groupId ?? undefined });\n }\n\n // ── Task lifecycle ──────────────────────────────────────────────────\n\n async createTask(\n title: string,\n source: CaptureSource,\n options?: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string },\n ): Promise<Note> {\n return this.createNote({\n title,\n kind: 'task',\n capturedVia: source,\n groupId: options?.groupId,\n taskMeta: {\n done: false,\n dueAt: options?.dueAt,\n priority: options?.priority,\n sourceSessionKey: options?.sourceSessionKey,\n sourceNoteId: options?.sourceNoteId,\n },\n });\n }\n\n async toggleTaskDone(noteId: string): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n const done = !note.taskMeta?.done;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done },\n status: done ? 'archived' : 'processed',\n });\n }\n\n async updateTaskMeta(noteId: string, patch: Partial<import('./types.js').NoteTaskMeta>): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n return this.updateNote(noteId, {\n taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch },\n });\n }\n\n // ── Open tracking ──────────────────────────────────────────────────\n\n async recordOpen(noteId: string): Promise<Note | null> {\n return this.updateNote(noteId, { lastOpenedAt: Date.now() } as Partial<Note>);\n }\n\n private async maybeSaveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n if (trigger !== 'edit') {\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n return;\n }\n const last = this.lastSnapshotAt.get(note.id) ?? 0;\n if (Date.now() - last < SNAPSHOT_THROTTLE_MS) return;\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n }\n}\n\nfunction inferAttachmentType(mimeType: string): NoteAttachment['type'] {\n if (mimeType.startsWith('image/')) return 'image';\n if (mimeType.startsWith('video/')) return 'video';\n if (mimeType.startsWith('audio/')) return 'audio';\n return 'file';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../src/notes/service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { complete, type UserMessage } from '@earendil-works/pi-ai';\n\nimport type { Config } from '../config/schema.js';\nimport { getDefaultModelSync, resolveModel } from '../providers/index.js';\nimport { createLogger } from '../utils/logger.js';\nimport { buildNoteAttachmentRef } from './attachment-ref.js';\nimport { partitionAttachmentsByReference } from './note-attachment-sync.js';\nimport { parseNoteMarkdown } from './note-markdown.js';\nimport { NotesStore } from './store.js';\nimport type {\n CaptureSource,\n CreateNoteParams,\n Note,\n NoteAiPatch,\n NoteAttachment,\n NoteCatalysisAction,\n NoteCatalysisMeta,\n NoteCatalysisReport,\n NoteIndexEntry,\n NoteKind,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesService');\nconst MAX_SNAPSHOTS_PER_NOTE = 50;\nconst SNAPSHOT_THROTTLE_MS = 60_000;\n\nfunction inferKind(markdown?: string, hasAttachments?: boolean, attachments?: NoteAttachment[]): NoteKind {\n if (hasAttachments && attachments?.length && attachments.every((item) => item.type === 'audio')) return 'voice';\n if (hasAttachments) return 'media';\n if (!markdown) return 'thought';\n const lower = markdown.toLowerCase();\n if (/^(todo|task|remind|buy|call|email|meet|finish|submit|send)\\b/i.test(lower) || /\\b(明天|今天|记得|别忘|待办|提醒)\\b/.test(markdown)) return 'todo';\n if (/^https?:\\/\\//.test(markdown.trim())) return 'bookmark';\n return 'thought';\n}\n\nfunction deriveDefaultTitle(markdown?: string): string | undefined {\n const normalized = parseNoteMarkdown(markdown ?? '').plainText.trim().replace(/\\s+/g, ' ');\n return normalized ? Array.from(normalized).slice(0, 10).join('') : undefined;\n}\n\nfunction splitMeaningfulLines(markdown?: string): string[] {\n return parseNoteMarkdown(markdown ?? '').plainText.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n}\n\nfunction summarizeIdea(markdown?: string): string {\n const lines = splitMeaningfulLines(markdown);\n return lines.slice(0, 3).join(';').slice(0, 140);\n}\n\nfunction inferCatalysisStage(note: Note): NonNullable<NoteCatalysisMeta['stage']> {\n const text = `${note.title ?? ''}\\n${note.markdown}`;\n if (/上线|发布|launch|ship/i.test(text)) return 'shipped';\n if (/验证|访谈|用户|测试|validate/i.test(text)) return 'validating';\n if (/开发|实现|构建|build|mvp/i.test(text)) return 'developing';\n if (/想法|机会|问题|idea|seed/i.test(text)) return 'seed';\n return 'incubating';\n}\n\nfunction buildCatalysisReport(note: Note): NoteCatalysisReport {\n const summary = summarizeIdea(note.markdown || note.title);\n const title = note.title?.trim() || summary.slice(0, 28) || '未命名想法';\n const actions: NoteCatalysisAction[] = [\n { kind: 'research', text: `验证「${title}」的真实需求和目标用户` },\n { kind: 'task', text: `拆解「${title}」的最小可行下一步` },\n { kind: 'chat', text: `继续和 AI 讨论「${title}」的方案` },\n ];\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title,\n valueHypothesis: summary || '这条笔记可能包含一个待澄清的想法。',\n targetUsers: ['待确认的核心用户'],\n keyQuestions: ['用户为什么现在需要它?', '最小可验证版本是什么?', '如何判断它值得继续投入?'],\n mvpPath: ['定义目标用户', '写出最小场景', '完成一次验证'],\n risks: ['需求不够明确', '执行路径过大'],\n nextActions: actions,\n confidence: summary ? 0.58 : 0.35,\n };\n}\n\nfunction normalizeAiCatalysisReport(note: Note, data: Record<string, unknown>): NoteCatalysisReport {\n const fallback = buildCatalysisReport(note);\n const stringArray = (value: unknown, fallbackValue: string[]) => Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string').slice(0, 12) : fallbackValue;\n const actions = Array.isArray(data.nextActions)\n ? data.nextActions.map((item): NoteCatalysisAction | null => {\n if (!item || typeof item !== 'object') return null;\n const row = item as { kind?: unknown; text?: unknown };\n const kind = row.kind === 'task' || row.kind === 'workflow' || row.kind === 'research' || row.kind === 'share' || row.kind === 'chat' ? row.kind : 'task';\n return typeof row.text === 'string' && row.text.trim() ? { kind, text: row.text.trim() } : null;\n }).filter((item): item is NoteCatalysisAction => Boolean(item)).slice(0, 12)\n : fallback.nextActions;\n return {\n originalNoteId: note.id,\n generatedAt: Date.now(),\n title: typeof data.title === 'string' && data.title.trim() ? data.title.trim() : fallback.title,\n valueHypothesis: typeof data.valueHypothesis === 'string' && data.valueHypothesis.trim() ? data.valueHypothesis.trim() : fallback.valueHypothesis,\n targetUsers: stringArray(data.targetUsers, fallback.targetUsers),\n keyQuestions: stringArray(data.keyQuestions, fallback.keyQuestions),\n mvpPath: stringArray(data.mvpPath, fallback.mvpPath),\n risks: stringArray(data.risks, fallback.risks),\n nextActions: actions,\n confidence: typeof data.confidence === 'number' ? Math.max(0, Math.min(1, data.confidence)) : fallback.confidence,\n };\n}\n\nfunction buildCatalysisPrompt(note: Note): string {\n const title = note.title?.trim() || '未命名笔记';\n const markdown = note.markdown.trim() || '(无正文)';\n return `请把下面的 Markdown 笔记催化成可执行的想法报告,输出 JSON:\\n{\\n \"title\": string,\\n \"valueHypothesis\": string,\\n \"targetUsers\": string[],\\n \"keyQuestions\": string[],\\n \"mvpPath\": string[],\\n \"risks\": string[],\\n \"nextActions\": [{\"text\": string, \"kind\": \"task\"|\"workflow\"|\"research\"|\"share\"|\"chat\"}],\\n \"confidence\": number\\n}\\n\\n标题:${title}\\n\\nMarkdown:\\n${markdown}`;\n}\n\nasync function buildAiCatalysisReport(note: Note, config?: Config): Promise<NoteCatalysisReport> {\n const modelRef = getDefaultModelSync(config) ?? 'openai/gpt-5.5';\n const resolved = resolveModel(modelRef);\n const messages: UserMessage[] = [{ role: 'user', content: buildCatalysisPrompt(note), timestamp: Date.now() }];\n const response = await complete(resolved, { messages }, { temperature: 0.2 });\n let responseText = '';\n if (Array.isArray(response.content)) {\n for (const part of response.content) {\n if (part && typeof part === 'object' && (part as { type?: string }).type === 'text') {\n responseText += String((part as { text?: string }).text || '');\n }\n }\n }\n const raw = responseText.trim().replace(/^```json\\s*/i, '').replace(/```$/i, '').trim();\n return normalizeAiCatalysisReport(note, JSON.parse(raw) as Record<string, unknown>);\n}\n\nexport class NotesService {\n private lastSnapshotAt = new Map<string, number>();\n\n constructor(private readonly store = new NotesStore()) {}\n\n async initialize(): Promise<void> {\n await this.store.initialize();\n }\n\n async quickCapture(markdown: string, source: CaptureSource): Promise<Note> {\n const id = randomUUID();\n const now = Date.now();\n const note: Note = {\n id,\n title: deriveDefaultTitle(markdown),\n kind: inferKind(markdown),\n status: 'inbox',\n markdown,\n createdAt: now,\n updatedAt: now,\n capturedVia: source,\n remoteVersion: 1,\n localVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Quick capture');\n return note;\n }\n\n async createNote(params: CreateNoteParams): Promise<Note> {\n const id = randomUUID();\n const now = Date.now();\n const markdown = params.markdown ?? '';\n const note: Note = {\n id,\n title: params.title ?? deriveDefaultTitle(markdown),\n kind: params.kind ?? inferKind(markdown),\n status: 'inbox',\n markdown,\n attachments: [],\n createdAt: now,\n updatedAt: now,\n capturedVia: params.capturedVia,\n tags: params.tags,\n pinned: params.pinned,\n groupId: params.groupId,\n taskMeta: params.taskMeta,\n remoteVersion: 1,\n localVersion: 1,\n };\n await this.store.addNote(note);\n log.debug({ id: note.id, kind: note.kind }, 'Note created');\n return note;\n }\n\n async getNote(id: string): Promise<Note | null> {\n return this.store.getNote(id);\n }\n\n async updateNote(id: string, patch: Partial<Note>, trigger: SnapshotTrigger = 'edit'): Promise<Note | null> {\n const existing = await this.store.getNote(id);\n if (!existing) return null;\n const contentTouched = patch.markdown !== undefined || patch.title !== undefined;\n if (contentTouched) await this.maybeSaveSnapshot(existing, trigger);\n\n const normalizedPatch: Partial<Note> = { ...patch };\n if (patch.markdown !== undefined) {\n normalizedPatch.kind = patch.kind ?? inferKind(patch.markdown, Boolean(existing.attachments?.length), existing.attachments);\n }\n if (contentTouched || patch.attachments) {\n normalizedPatch.remoteVersion = (existing.remoteVersion ?? 1) + 1;\n normalizedPatch.localVersion = patch.localVersion ?? (existing.localVersion ?? 1) + 1;\n }\n\n const updatedRaw = await this.store.updateNote(id, normalizedPatch);\n return updatedRaw ? this.reconcileAttachments(updatedRaw) : null;\n }\n\n private async reconcileAttachments(note: Note): Promise<Note> {\n const { kept, removed } = partitionAttachmentsByReference(note);\n if (removed.length === 0) return note;\n for (const attachment of removed) await this.store.deleteAttachmentFile(note.id, attachment.relativePath);\n log.debug({ noteId: note.id, removedIds: removed.map((attachment) => attachment.id) }, 'Pruned orphan note attachments');\n const hasAttachments = kept.length > 0;\n const updated = await this.store.updateNote(note.id, { attachments: kept, kind: inferKind(note.markdown, hasAttachments, kept) });\n return updated ?? { ...note, attachments: kept };\n }\n\n async syncNote(id: string, patch: Partial<Note>, baseRemoteVersion?: number): Promise<{ note: Note | null; conflict: boolean }> {\n const existing = await this.store.getNote(id);\n if (!existing) return { note: null, conflict: false };\n if (baseRemoteVersion !== undefined && existing.remoteVersion !== undefined && baseRemoteVersion < existing.remoteVersion) {\n return { note: existing, conflict: true };\n }\n const updated = await this.updateNote(id, patch, 'sync');\n return { note: updated, conflict: false };\n }\n\n async createAiEditPatch(id: string, instruction: string, markdownOverride?: string): Promise<{ message: string; patch: NoteAiPatch } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const source = markdownOverride ?? note.markdown;\n const parsed = parseNoteMarkdown(source, id);\n const wantsTodos = /待办|todo|task|行动|提醒/i.test(instruction);\n const wantsSummary = /摘要|总结|summary|压缩/i.test(instruction);\n let generated = '';\n if (wantsTodos) {\n const lines = source.split(/\\n|。|;|;/).map((line) => line.trim()).filter(Boolean);\n generated = (lines.length ? lines : [parsed.plainText]).slice(0, 12).map((line) => `- [ ] ${line.replace(/^[-*\\d.\\s\\[\\]x]+/i, '').trim()}`).join('\\n');\n } else if (wantsSummary) {\n generated = `> [!SUMMARY]\\n> ${parsed.plainText.slice(0, 220)}`;\n } else {\n generated = `${source.trimEnd()}\\n\\n## AI 整理\\n\\n${instruction.trim()}`;\n }\n return {\n message: 'AI edit patch generated',\n patch: {\n id: randomUUID(),\n summary: instruction,\n operations: [{ type: 'replaceRange', from: 0, to: source.length, markdown: generated || source }],\n },\n };\n }\n\n async catalyzeNote(id: string, config?: Config): Promise<{ note: Note; report: NoteCatalysisReport } | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n let report: NoteCatalysisReport;\n try {\n report = await buildAiCatalysisReport(note, config);\n } catch (err) {\n log.warn({ err, noteId: id }, 'AI note catalysis failed; using local fallback report');\n report = buildCatalysisReport(note);\n }\n const updated = await this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: 'catalyzed',\n stage: inferCatalysisStage(note),\n lastCatalyzedAt: Date.now(),\n confidence: report.confidence,\n report,\n ...note.aiDeep?.catalysis,\n },\n },\n ai: { ...note.ai, intent: note.ai?.intent ?? 'idea', summary: note.ai?.summary ?? report.valueHypothesis },\n status: note.status === 'inbox' ? 'processed' : note.status,\n }, 'ai_edit');\n return updated ? { note: updated, report } : null;\n }\n\n async recordCatalysisFeedback(id: string, feedback: 'helpful' | 'not_helpful' | 'neutral'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n return this.updateNote(id, { aiDeep: { ...note.aiDeep, processedAt: Date.now(), catalysis: { status: note.aiDeep?.catalysis?.status ?? 'catalyzed', ...note.aiDeep?.catalysis, feedback } } });\n }\n\n async linkNoteThread(id: string, sessionKey: string): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const existingKeys = note.aiDeep?.catalysis?.linkedSessionKeys ?? [];\n return this.updateNote(id, {\n aiDeep: {\n ...note.aiDeep,\n processedAt: Date.now(),\n catalysis: {\n status: note.aiDeep?.catalysis?.status ?? 'none',\n ...note.aiDeep?.catalysis,\n sourceSessionKey: note.aiDeep?.catalysis?.sourceSessionKey ?? sessionKey,\n linkedSessionKeys: Array.from(new Set([...existingKeys, sessionKey])),\n },\n },\n }, 'sync');\n }\n\n async listNoteThreads(id: string): Promise<string[] | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n return Array.from(new Set([note.aiDeep?.catalysis?.sourceSessionKey, ...(note.aiDeep?.catalysis?.linkedSessionKeys ?? [])].filter((key): key is string => Boolean(key))));\n }\n\n async appendTextToNote(id: string, content: string, heading = 'AI 讨论沉淀'): Promise<Note | null> {\n const note = await this.store.getNote(id);\n if (!note) return null;\n const trimmed = content.trim();\n if (!trimmed) return note;\n const current = note.markdown.trimEnd();\n const nextMarkdown = `${current}${current ? '\\n\\n' : ''}## ${heading}\\n\\n${trimmed}\\n`;\n return this.updateNote(id, { markdown: nextMarkdown }, 'ai_edit');\n }\n\n async deleteNote(id: string): Promise<boolean> {\n const removed = await this.store.deleteNote(id);\n if (removed) await this.store.deleteAllSnapshots(id);\n return removed;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{ items: NoteIndexEntry[]; total: number; limit: number; offset: number; hasMore: boolean }> {\n return this.store.listNotes(query);\n }\n\n async addAttachment(noteId: string, file: { name: string; buffer: Buffer; mimeType: string; duration?: number }): Promise<NoteAttachment | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);\n const attachment: NoteAttachment = {\n id: randomUUID(),\n type: file.mimeType.startsWith('image/') ? 'image' : file.mimeType.startsWith('audio/') ? 'audio' : file.mimeType.startsWith('video/') ? 'video' : 'file',\n mimeType: file.mimeType,\n fileName: file.name,\n size,\n relativePath,\n duration: file.duration,\n };\n const attachments = [...(note.attachments || []), attachment];\n const kind = note.kind === 'thought' && attachment.type === 'audio' ? 'voice' : note.kind === 'thought' ? 'media' : note.kind;\n await this.store.updateNote(noteId, { attachments, kind });\n return attachment;\n }\n\n async getAttachmentPath(noteId: string, attachmentId: string): Promise<{ filePath: string; mimeType: string; fileName: string } | null> {\n const note = await this.store.getNote(noteId);\n if (!note) return null;\n const attachment = note.attachments?.find((a) => a.id === attachmentId);\n if (!attachment) return null;\n return { filePath: this.store.resolveAttachmentPath(noteId, attachment.relativePath), mimeType: attachment.mimeType, fileName: attachment.fileName };\n }\n\n async listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]> { return this.store.listSnapshots(noteId); }\n async getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null> { return this.store.getSnapshot(noteId, timestamp); }\n\n async restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null> {\n const snapshot = await this.store.getSnapshot(noteId, timestamp);\n const existing = await this.store.getNote(noteId);\n if (!snapshot || !existing) return null;\n await this.maybeSaveSnapshot(existing, 'restore');\n this.lastSnapshotAt.set(noteId, Date.now());\n await this.store.pruneSnapshots(noteId, MAX_SNAPSHOTS_PER_NOTE);\n return this.store.updateNote(noteId, { title: snapshot.title, markdown: snapshot.markdown, tags: snapshot.tags, kind: snapshot.kind, status: snapshot.status });\n }\n\n async flush(): Promise<void> { await this.store.flush(); }\n async moveToGroup(noteId: string, groupId: string | null): Promise<Note | null> { return this.updateNote(noteId, { groupId: groupId ?? undefined }); }\n\n async createTask(title: string, source: CaptureSource, opts: { dueAt?: number; priority?: 'high' | 'medium' | 'low'; sourceSessionKey?: string; sourceNoteId?: string; groupId?: string } = {}): Promise<Note> {\n return this.createNote({\n title,\n markdown: `- [ ] ${title}`,\n kind: 'task',\n capturedVia: source,\n groupId: opts.groupId,\n taskMeta: { done: false, dueAt: opts.dueAt, priority: opts.priority, sourceSessionKey: opts.sourceSessionKey, sourceNoteId: opts.sourceNoteId },\n });\n }\n\n async toggleTaskDone(noteId: string): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n const done = !note.taskMeta?.done;\n const markdown = note.markdown.replace(/^- \\[[ xX]\\]/m, `- [${done ? 'x' : ' '}]`);\n return this.updateNote(noteId, { markdown, taskMeta: { ...note.taskMeta, done } });\n }\n\n async updateTaskMeta(noteId: string, patch: Partial<import('./types.js').NoteTaskMeta>): Promise<Note | null> {\n const note = await this.store.getNote(noteId);\n if (!note || note.kind !== 'task') return null;\n return this.updateNote(noteId, { taskMeta: { ...note.taskMeta, done: note.taskMeta?.done ?? false, ...patch } });\n }\n\n async recordOpen(noteId: string): Promise<Note | null> {\n return this.updateNote(noteId, { lastOpenedAt: Date.now() } as Partial<Note>);\n }\n\n private async maybeSaveSnapshot(note: Note, trigger: SnapshotTrigger): Promise<void> {\n if (trigger !== 'edit') {\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n return;\n }\n const last = this.lastSnapshotAt.get(note.id) ?? 0;\n if (Date.now() - last < SNAPSHOT_THROTTLE_MS) return;\n await this.store.saveSnapshot(note, trigger);\n this.lastSnapshotAt.set(note.id, Date.now());\n await this.store.pruneSnapshots(note.id, MAX_SNAPSHOTS_PER_NOTE);\n }\n}\n\nexport { buildNoteAttachmentRef };\n"],"mappings":"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"}
|
package/dist/src/notes/store.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
|
|
1
2
|
import { createLogger } from "../utils/logger/index.js";
|
|
2
3
|
import { init_logger } from "../utils/logger.js";
|
|
3
|
-
import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
|
|
4
4
|
import { requireXopcDatabase } from "../storage/sqlite/connection.js";
|
|
5
5
|
import { buildNoteIndexMeta } from "./note-index-meta.js";
|
|
6
6
|
import { deleteNoteRecord, getNoteRecord, listNoteRecords, upsertNoteRecord } from "../storage/sqlite/notes-repository.js";
|
|
7
7
|
import "../storage/sqlite/index.js";
|
|
8
8
|
import { resolveNoteHistoryDir, resolveNoteMediaDir } from "./paths.js";
|
|
9
|
-
import { join } from "node:path";
|
|
10
9
|
import { randomUUID } from "node:crypto";
|
|
11
10
|
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
12
|
//#region src/notes/store.ts
|
|
13
13
|
init_write_file_atomic();
|
|
14
14
|
init_logger();
|
|
@@ -86,8 +86,7 @@ var NotesStore = class {
|
|
|
86
86
|
timestamp: Date.now(),
|
|
87
87
|
trigger,
|
|
88
88
|
title: note.title,
|
|
89
|
-
|
|
90
|
-
blocks: note.blocks,
|
|
89
|
+
markdown: note.markdown,
|
|
91
90
|
tags: note.tags,
|
|
92
91
|
kind: note.kind,
|
|
93
92
|
status: note.status
|
|
@@ -115,7 +114,7 @@ var NotesStore = class {
|
|
|
115
114
|
try {
|
|
116
115
|
const content = await readFile(join(historyDir, file), "utf-8");
|
|
117
116
|
const snapshot = JSON.parse(content);
|
|
118
|
-
const rawText = snapshot.
|
|
117
|
+
const rawText = snapshot.markdown ?? "";
|
|
119
118
|
entries.push({
|
|
120
119
|
timestamp: snapshot.timestamp,
|
|
121
120
|
trigger: snapshot.trigger,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../../../src/notes/store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile, 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 {\n deleteNoteRecord,\n getNoteRecord,\n listNoteRecords,\n requireXopcDatabase,\n upsertNoteRecord,\n} from '../storage/sqlite/index.js';\nimport { buildNoteIndexMeta } from './note-index-meta.js';\nimport { resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';\nimport type {\n Note,\n NoteIndexEntry,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesStore');\n\nexport class NotesStore {\n private initialized = false;\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n requireXopcDatabase();\n this.initialized = true;\n log.debug('NotesStore initialized');\n }\n\n async addNote(note: Note): Promise<void> {\n requireXopcDatabase();\n upsertNoteRecord(note);\n }\n\n async getNote(id: string): Promise<Note | null> {\n requireXopcDatabase();\n return getNoteRecord(id);\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 requireXopcDatabase();\n upsertNoteRecord(updated);\n return updated;\n }\n\n async deleteNote(id: string): Promise<boolean> {\n requireXopcDatabase();\n const deleted = deleteNoteRecord(id);\n if (!deleted) {\n return false;\n }\n\n const mediaDir = resolveNoteMediaDir(id);\n await rm(mediaDir, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{\n items: NoteIndexEntry[];\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n }> {\n requireXopcDatabase();\n return listNoteRecords(query);\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
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../../src/notes/store.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { readFile, 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 {\n deleteNoteRecord,\n getNoteRecord,\n listNoteRecords,\n requireXopcDatabase,\n upsertNoteRecord,\n} from '../storage/sqlite/index.js';\nimport { buildNoteIndexMeta } from './note-index-meta.js';\nimport { resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';\nimport type {\n Note,\n NoteIndexEntry,\n NoteSnapshot,\n NoteSnapshotEntry,\n NotesListQuery,\n SnapshotTrigger,\n} from './types.js';\n\nconst log = createLogger('NotesStore');\n\nexport class NotesStore {\n private initialized = false;\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n requireXopcDatabase();\n this.initialized = true;\n log.debug('NotesStore initialized');\n }\n\n async addNote(note: Note): Promise<void> {\n requireXopcDatabase();\n upsertNoteRecord(note);\n }\n\n async getNote(id: string): Promise<Note | null> {\n requireXopcDatabase();\n return getNoteRecord(id);\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 requireXopcDatabase();\n upsertNoteRecord(updated);\n return updated;\n }\n\n async deleteNote(id: string): Promise<boolean> {\n requireXopcDatabase();\n const deleted = deleteNoteRecord(id);\n if (!deleted) {\n return false;\n }\n\n const mediaDir = resolveNoteMediaDir(id);\n await rm(mediaDir, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n\n async listNotes(query: NotesListQuery = {}): Promise<{\n items: NoteIndexEntry[];\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n }> {\n requireXopcDatabase();\n return listNoteRecords(query);\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 markdown: note.markdown,\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.markdown ?? '';\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.filter((f) => f.endsWith('.json')).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 /** No-op: SQLite writes are synchronous. Kept for API compatibility with tests. */\n async flush(): Promise<void> {}\n}\n\nexport { buildNoteIndexMeta };\n"],"mappings":"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"}
|
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
export type NoteKind = 'thought' | 'todo' | 'voice' | 'media' | 'bookmark' | 'mixed' | 'task';
|
|
2
2
|
export type NoteStatus = 'inbox' | 'processed' | 'archived' | 'trashed';
|
|
3
3
|
export type CaptureChannel = 'app' | 'web' | 'electron' | 'tui' | 'telegram' | 'wechat' | 'feishu';
|
|
4
|
-
export type NoteBlockType = 'paragraph' | 'heading' | 'todo' | 'bulletList' | 'numberedList' | 'quote' | 'code' | 'divider' | 'image' | 'aiSuggestion';
|
|
5
|
-
export interface BaseNoteBlock {
|
|
6
|
-
id: string;
|
|
7
|
-
type: NoteBlockType;
|
|
8
|
-
createdAt: number;
|
|
9
|
-
updatedAt: number;
|
|
10
|
-
}
|
|
11
|
-
export interface TextNoteBlock extends BaseNoteBlock {
|
|
12
|
-
type: 'paragraph' | 'heading' | 'bulletList' | 'numberedList' | 'quote' | 'code' | 'aiSuggestion';
|
|
13
|
-
text: string;
|
|
14
|
-
level?: 1 | 2 | 3;
|
|
15
|
-
indent?: number;
|
|
16
|
-
}
|
|
17
|
-
export interface TodoNoteBlock extends BaseNoteBlock {
|
|
18
|
-
type: 'todo';
|
|
19
|
-
text: string;
|
|
20
|
-
checked: boolean;
|
|
21
|
-
}
|
|
22
|
-
export interface DividerNoteBlock extends BaseNoteBlock {
|
|
23
|
-
type: 'divider';
|
|
24
|
-
}
|
|
25
|
-
export interface ImageNoteBlock extends BaseNoteBlock {
|
|
26
|
-
type: 'image';
|
|
27
|
-
attachmentId: string;
|
|
28
|
-
alt?: string;
|
|
29
|
-
width?: number;
|
|
30
|
-
}
|
|
31
|
-
export type NoteBlock = TextNoteBlock | TodoNoteBlock | DividerNoteBlock | ImageNoteBlock;
|
|
32
4
|
export type NotePatchOperation = {
|
|
33
|
-
type: '
|
|
34
|
-
|
|
5
|
+
type: 'replaceRange';
|
|
6
|
+
from: number;
|
|
7
|
+
to: number;
|
|
8
|
+
markdown: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'insertAt';
|
|
11
|
+
offset: number;
|
|
12
|
+
markdown: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'replaceSection';
|
|
15
|
+
sectionId: string;
|
|
16
|
+
markdown: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'appendSection';
|
|
19
|
+
heading: string;
|
|
20
|
+
markdown: string;
|
|
35
21
|
} | {
|
|
36
|
-
type: '
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
type: 'prependSection';
|
|
23
|
+
heading: string;
|
|
24
|
+
markdown: string;
|
|
39
25
|
} | {
|
|
40
|
-
type: '
|
|
41
|
-
|
|
42
|
-
patch: Partial<NoteBlock>;
|
|
26
|
+
type: 'updateFrontmatter';
|
|
27
|
+
patch: Record<string, unknown>;
|
|
43
28
|
} | {
|
|
44
29
|
type: 'updateMetadata';
|
|
45
30
|
title?: string;
|
|
@@ -117,9 +102,7 @@ export interface NoteTaskMeta {
|
|
|
117
102
|
done: boolean;
|
|
118
103
|
dueAt?: number;
|
|
119
104
|
priority?: 'high' | 'medium' | 'low';
|
|
120
|
-
/** Session key of the thread that created this task. */
|
|
121
105
|
sourceSessionKey?: string;
|
|
122
|
-
/** Note id of the page this task was extracted from. */
|
|
123
106
|
sourceNoteId?: string;
|
|
124
107
|
}
|
|
125
108
|
export interface Note {
|
|
@@ -127,8 +110,8 @@ export interface Note {
|
|
|
127
110
|
title?: string;
|
|
128
111
|
kind: NoteKind;
|
|
129
112
|
status: NoteStatus;
|
|
130
|
-
|
|
131
|
-
|
|
113
|
+
/** Canonical note body. Markdown is the only content truth. */
|
|
114
|
+
markdown: string;
|
|
132
115
|
attachments?: NoteAttachment[];
|
|
133
116
|
createdAt: number;
|
|
134
117
|
updatedAt: number;
|
|
@@ -139,11 +122,8 @@ export interface Note {
|
|
|
139
122
|
pinned?: boolean;
|
|
140
123
|
localVersion?: number;
|
|
141
124
|
remoteVersion?: number;
|
|
142
|
-
/** Space grouping — null means ungrouped (appears in root). */
|
|
143
125
|
groupId?: string;
|
|
144
|
-
/** Last time the user explicitly opened this note. */
|
|
145
126
|
lastOpenedAt?: number;
|
|
146
|
-
/** Task lifecycle metadata (only when kind === 'task'). */
|
|
147
127
|
taskMeta?: NoteTaskMeta;
|
|
148
128
|
}
|
|
149
129
|
export interface NoteIndexEntry {
|
|
@@ -156,22 +136,18 @@ export interface NoteIndexEntry {
|
|
|
156
136
|
pinned?: boolean;
|
|
157
137
|
tags?: string[];
|
|
158
138
|
snippet?: string;
|
|
159
|
-
/** First image attachment id for list thumbnails. */
|
|
160
139
|
coverAttachmentId?: string;
|
|
161
|
-
/** First audio attachment id for inline voice playback. */
|
|
162
140
|
voiceAttachmentId?: string;
|
|
163
|
-
/** Duration in seconds of the voice attachment (when available). */
|
|
164
141
|
voiceDurationSec?: number;
|
|
165
|
-
/** Lowercased attachment file names for list search. */
|
|
166
142
|
attachmentNames?: string[];
|
|
167
|
-
/** Space group id. */
|
|
168
143
|
groupId?: string;
|
|
169
|
-
/** Last opened timestamp for "continue" rail. */
|
|
170
144
|
lastOpenedAt?: number;
|
|
171
|
-
/** Task done flag (only when kind === 'task'). */
|
|
172
145
|
taskDone?: boolean;
|
|
173
|
-
/** Task due timestamp (only when kind === 'task'). */
|
|
174
146
|
taskDueAt?: number;
|
|
147
|
+
headingCount?: number;
|
|
148
|
+
taskCount?: number;
|
|
149
|
+
uncheckedTaskCount?: number;
|
|
150
|
+
linkCount?: number;
|
|
175
151
|
}
|
|
176
152
|
export interface NotesIndexFile {
|
|
177
153
|
version: number;
|
|
@@ -183,8 +159,7 @@ export interface NoteSnapshot {
|
|
|
183
159
|
timestamp: number;
|
|
184
160
|
trigger: SnapshotTrigger;
|
|
185
161
|
title?: string;
|
|
186
|
-
|
|
187
|
-
blocks?: NoteBlock[];
|
|
162
|
+
markdown: string;
|
|
188
163
|
tags?: string[];
|
|
189
164
|
kind: NoteKind;
|
|
190
165
|
status: NoteStatus;
|
|
@@ -204,15 +179,12 @@ export interface NotesListQuery {
|
|
|
204
179
|
offset?: number;
|
|
205
180
|
sortBy?: 'createdAt' | 'updatedAt' | 'lastOpenedAt';
|
|
206
181
|
sortOrder?: 'asc' | 'desc';
|
|
207
|
-
/** Filter by space group id. Use `'ungrouped'` for root-level notes. */
|
|
208
182
|
groupId?: string;
|
|
209
|
-
/** Filter to only task notes with done === false. */
|
|
210
183
|
pendingTasksOnly?: boolean;
|
|
211
184
|
}
|
|
212
185
|
export interface CreateNoteParams {
|
|
213
186
|
title?: string;
|
|
214
|
-
|
|
215
|
-
blocks?: NoteBlock[];
|
|
187
|
+
markdown?: string;
|
|
216
188
|
kind?: NoteKind;
|
|
217
189
|
tags?: string[];
|
|
218
190
|
capturedVia: CaptureSource;
|
|
@@ -220,7 +192,6 @@ export interface CreateNoteParams {
|
|
|
220
192
|
groupId?: string;
|
|
221
193
|
taskMeta?: NoteTaskMeta;
|
|
222
194
|
}
|
|
223
|
-
/** A lightweight space group (persisted as a note with kind='mixed', used for grouping). */
|
|
224
195
|
export interface NoteGroup {
|
|
225
196
|
id: string;
|
|
226
197
|
name: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
3
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
//#region src/providers/auth-runtime/auth-profile-store.ts
|
|
6
6
|
/**
|
|
7
7
|
* {@link AuthProfileStore} implementations.
|
|
@@ -61,7 +61,7 @@ export declare function providerSupportsApiKey(provider: string): boolean;
|
|
|
61
61
|
* Get a default model reference.
|
|
62
62
|
* Priority:
|
|
63
63
|
* 1. Explicitly configured model in agents.defaults.model (if set and available)
|
|
64
|
-
* 2. Default fallback:
|
|
64
|
+
* 2. Default fallback: openai/gpt-5.5
|
|
65
65
|
*/
|
|
66
66
|
export declare function getDefaultModel(config?: Config | null | undefined): Promise<string>;
|
|
67
67
|
/**
|
|
@@ -69,8 +69,8 @@ export declare function getDefaultModel(config?: Config | null | undefined): Pro
|
|
|
69
69
|
* Uses catalog/registry only (no async credential checks).
|
|
70
70
|
*
|
|
71
71
|
* When no model is explicitly configured, returns the preferred default
|
|
72
|
-
* (`
|
|
73
|
-
*
|
|
72
|
+
* (`openai/gpt-5.5`) rather than picking an arbitrary first model from the
|
|
73
|
+
* full catalog.
|
|
74
74
|
*/
|
|
75
75
|
export declare function getDefaultModelSync(config?: Config | null | undefined): string;
|
|
76
76
|
export { ModelRegistry, getModelRegistry, resetModelRegistry, prewarmModelRegistry } from './model-registry.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { __esmMin, __exportAll } from "../../_virtual/_rolldown/runtime.js";
|
|
2
|
-
import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
|
|
3
2
|
import { PROVIDER_ENV_MAP, getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
|
|
4
|
-
import { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry } from "./model-registry.js";
|
|
5
3
|
import { CredentialResolver, hasCredentials, init_credentials, resolveApiKey } from "../auth/credentials.js";
|
|
4
|
+
import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
|
|
5
|
+
import { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry } from "./model-registry.js";
|
|
6
6
|
import { getProviderRegistry, init_plugin_registry } from "./plugin-registry.js";
|
|
7
7
|
import { getModel, getModels, getProviders } from "@earendil-works/pi-ai";
|
|
8
8
|
//#region src/providers/index.ts
|
|
@@ -38,6 +38,13 @@ var providers_exports = /* @__PURE__ */ __exportAll({
|
|
|
38
38
|
resetModelRegistry: () => resetModelRegistry,
|
|
39
39
|
resolveModel: () => resolveModel
|
|
40
40
|
});
|
|
41
|
+
function normalizeProviderModel(model) {
|
|
42
|
+
if (model.provider === "openai-codex" && model.api === "openai-codex-responses") return {
|
|
43
|
+
...model,
|
|
44
|
+
baseUrl: OPENAI_CODEX_CANONICAL_BASE_URL
|
|
45
|
+
};
|
|
46
|
+
return model;
|
|
47
|
+
}
|
|
41
48
|
/** Map a plugin registry model to the pi-ai {@link Model} shape. */
|
|
42
49
|
function pluginModelToModel(providerId, definition) {
|
|
43
50
|
return {
|
|
@@ -80,7 +87,7 @@ function resolveModel(ref) {
|
|
|
80
87
|
if (ref.includes("/")) {
|
|
81
88
|
const [provider, modelId] = ref.split("/");
|
|
82
89
|
const piAiModel = getModel(provider, modelId);
|
|
83
|
-
if (piAiModel) return piAiModel;
|
|
90
|
+
if (piAiModel) return normalizeProviderModel(piAiModel);
|
|
84
91
|
const plugin = getProviderRegistry().get(provider);
|
|
85
92
|
if (plugin) {
|
|
86
93
|
const pluginModel = plugin.models.find((m) => m.id === modelId);
|
|
@@ -90,7 +97,7 @@ function resolveModel(ref) {
|
|
|
90
97
|
}
|
|
91
98
|
for (const provider of getProviders()) try {
|
|
92
99
|
const found = getModels(provider).find((m) => m.id === ref);
|
|
93
|
-
if (found) return found;
|
|
100
|
+
if (found) return normalizeProviderModel(found);
|
|
94
101
|
} catch {
|
|
95
102
|
continue;
|
|
96
103
|
}
|
|
@@ -102,7 +109,7 @@ function resolveModel(ref) {
|
|
|
102
109
|
throw new Error(`Model not found: ${ref}. Use format: provider/model-id`);
|
|
103
110
|
}
|
|
104
111
|
function getModelsByProvider(provider) {
|
|
105
|
-
const fromRegistry = getModelRegistry().getAll().filter((m) => m.provider === provider);
|
|
112
|
+
const fromRegistry = getModelRegistry().getAll().filter((m) => m.provider === provider).map(normalizeProviderModel);
|
|
106
113
|
const plugin = getProviderRegistry().get(provider);
|
|
107
114
|
if (!plugin) return fromRegistry;
|
|
108
115
|
const pluginModels = plugin.models.map((m) => pluginModelToModel(provider, m));
|
|
@@ -191,7 +198,7 @@ async function getConfiguredProviders() {
|
|
|
191
198
|
return configured;
|
|
192
199
|
}
|
|
193
200
|
function getAllModels() {
|
|
194
|
-
const registryModels = getModelRegistry().getAll();
|
|
201
|
+
const registryModels = getModelRegistry().getAll().map(normalizeProviderModel);
|
|
195
202
|
const pluginProviders = getProviderRegistry().listAll();
|
|
196
203
|
if (pluginProviders.length === 0) return registryModels;
|
|
197
204
|
const existingIds = new Set(registryModels.map((m) => `${m.provider}/${m.id}`));
|
|
@@ -245,7 +252,7 @@ function providerSupportsApiKey(provider) {
|
|
|
245
252
|
* Get a default model reference.
|
|
246
253
|
* Priority:
|
|
247
254
|
* 1. Explicitly configured model in agents.defaults.model (if set and available)
|
|
248
|
-
* 2. Default fallback:
|
|
255
|
+
* 2. Default fallback: openai/gpt-5.5
|
|
249
256
|
*/
|
|
250
257
|
async function getDefaultModel(config) {
|
|
251
258
|
const defaultModel = config?.agents?.defaults?.model;
|
|
@@ -264,8 +271,8 @@ async function getDefaultModel(config) {
|
|
|
264
271
|
* Uses catalog/registry only (no async credential checks).
|
|
265
272
|
*
|
|
266
273
|
* When no model is explicitly configured, returns the preferred default
|
|
267
|
-
* (`
|
|
268
|
-
*
|
|
274
|
+
* (`openai/gpt-5.5`) rather than picking an arbitrary first model from the
|
|
275
|
+
* full catalog.
|
|
269
276
|
*/
|
|
270
277
|
function getDefaultModelSync(config) {
|
|
271
278
|
const defaultModel = config?.agents?.defaults?.model;
|
|
@@ -275,7 +282,7 @@ function getDefaultModelSync(config) {
|
|
|
275
282
|
}
|
|
276
283
|
return DEFAULT_FALLBACK_MODEL;
|
|
277
284
|
}
|
|
278
|
-
var EXTENSION_PROVIDER_BASE_URL, PROVIDER_META, DEFAULT_FALLBACK_MODEL;
|
|
285
|
+
var EXTENSION_PROVIDER_BASE_URL, OPENAI_CODEX_CANONICAL_BASE_URL, PROVIDER_META, DEFAULT_FALLBACK_MODEL;
|
|
279
286
|
var init_providers = __esmMin((() => {
|
|
280
287
|
init_model_registry();
|
|
281
288
|
init_credentials();
|
|
@@ -283,14 +290,15 @@ var init_providers = __esmMin((() => {
|
|
|
283
290
|
init_env_keys();
|
|
284
291
|
init_plugin_registry();
|
|
285
292
|
EXTENSION_PROVIDER_BASE_URL = "extension://provider-plugin";
|
|
293
|
+
OPENAI_CODEX_CANONICAL_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
286
294
|
PROVIDER_META = {
|
|
287
295
|
"openai": {
|
|
288
|
-
name: "OpenAI
|
|
296
|
+
name: "OpenAI",
|
|
289
297
|
category: "common",
|
|
290
298
|
supportsApiKey: true
|
|
291
299
|
},
|
|
292
300
|
"anthropic": {
|
|
293
|
-
name: "Anthropic
|
|
301
|
+
name: "Anthropic",
|
|
294
302
|
category: "common",
|
|
295
303
|
supportsApiKey: true,
|
|
296
304
|
supportsOAuth: true
|
|
@@ -301,12 +309,12 @@ var init_providers = __esmMin((() => {
|
|
|
301
309
|
supportsApiKey: true
|
|
302
310
|
},
|
|
303
311
|
"google": {
|
|
304
|
-
name: "Google
|
|
312
|
+
name: "Google AI",
|
|
305
313
|
category: "common",
|
|
306
314
|
supportsApiKey: true
|
|
307
315
|
},
|
|
308
316
|
"groq": {
|
|
309
|
-
name: "Groq
|
|
317
|
+
name: "Groq",
|
|
310
318
|
category: "common",
|
|
311
319
|
supportsApiKey: true
|
|
312
320
|
},
|
|
@@ -329,7 +337,7 @@ var init_providers = __esmMin((() => {
|
|
|
329
337
|
supportsOAuth: true
|
|
330
338
|
},
|
|
331
339
|
"xai": {
|
|
332
|
-
name: "xAI
|
|
340
|
+
name: "xAI",
|
|
333
341
|
category: "specialty",
|
|
334
342
|
supportsApiKey: true
|
|
335
343
|
},
|
|
@@ -344,7 +352,7 @@ var init_providers = __esmMin((() => {
|
|
|
344
352
|
supportsApiKey: true
|
|
345
353
|
},
|
|
346
354
|
"openrouter": {
|
|
347
|
-
name: "OpenRouter
|
|
355
|
+
name: "OpenRouter",
|
|
348
356
|
category: "specialty",
|
|
349
357
|
supportsApiKey: true
|
|
350
358
|
},
|
|
@@ -470,7 +478,7 @@ var init_providers = __esmMin((() => {
|
|
|
470
478
|
supportsApiKey: false
|
|
471
479
|
}
|
|
472
480
|
};
|
|
473
|
-
DEFAULT_FALLBACK_MODEL = "
|
|
481
|
+
DEFAULT_FALLBACK_MODEL = "openai/gpt-5.5";
|
|
474
482
|
}));
|
|
475
483
|
//#endregion
|
|
476
484
|
init_providers();
|