coding-tool-x 3.8.3 → 3.8.5
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/CHANGELOG.md +18 -0
- package/README.md +86 -15
- package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
- package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
- package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
- package/dist/web/assets/Analytics-CyHhR126.css +1 -0
- package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
- package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
- package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
- package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
- package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
- package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
- package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
- package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
- package/dist/web/assets/Divider-D28g1gwj.js +1 -0
- package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
- package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
- package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
- package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
- package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
- package/dist/web/assets/Home-D7CCOFQC.js +1 -0
- package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
- package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
- package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
- package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
- package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
- package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
- package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
- package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
- package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
- package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
- package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
- package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
- package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
- package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
- package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
- package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
- package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
- package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
- package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
- package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
- package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
- package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
- package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
- package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
- package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
- package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
- package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
- package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
- package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
- package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
- package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
- package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
- package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
- package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
- package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
- package/dist/web/assets/client-UDwXBP9i.js +1 -0
- package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
- package/dist/web/assets/headers-cf-QapUq.js +1 -0
- package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
- package/dist/web/assets/index-C0EHRym0.css +1 -0
- package/dist/web/assets/index-DnEocV0M.js +2 -0
- package/dist/web/assets/light-Cp0t7v32.js +1 -0
- package/dist/web/assets/markdown-u082bbtj.js +1 -0
- package/dist/web/assets/platforms-CBkB45JP.js +1 -0
- package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
- package/dist/web/assets/project-config-B2baPzYL.js +1 -0
- package/dist/web/assets/sessions-Cmy31slD.js +1 -0
- package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
- package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
- package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
- package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
- package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
- package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
- package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
- package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
- package/dist/web/assets/xml-CgY3RMlX.js +1 -0
- package/dist/web/index.html +12 -8
- package/docs/native-log-memory-2026-09-16.md +43 -0
- package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
- package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
- package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
- package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
- package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
- package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
- package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
- package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
- package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
- package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
- package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
- package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
- package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
- package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
- package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
- package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
- package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
- package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
- package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
- package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
- package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
- package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
- package/package.json +13 -8
- package/src/commands/channels.js +88 -191
- package/src/commands/cli-type.js +19 -11
- package/src/commands/daemon.js +94 -32
- package/src/commands/doctor.js +16 -9
- package/src/commands/export-config.js +23 -5
- package/src/commands/list.js +130 -52
- package/src/commands/logs.js +44 -27
- package/src/commands/platform-command-registry.js +78 -0
- package/src/commands/plugin.js +11 -5
- package/src/commands/port-config.js +34 -52
- package/src/commands/proxy-control.js +207 -145
- package/src/commands/proxy.js +2 -2
- package/src/commands/resume.js +87 -91
- package/src/commands/search.js +137 -67
- package/src/commands/stats.js +30 -22
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +94 -111
- package/src/commands/ui.js +67 -6
- package/src/commands/workspace.js +90 -8
- package/src/config/default.js +6 -53
- package/src/config/loader.js +61 -24
- package/src/config/model-metadata.js +49 -59
- package/src/config/model-metadata.json +4095 -99
- package/src/config/paths.js +181 -8
- package/src/index.js +134 -71
- package/src/platforms/access.js +196 -0
- package/src/{server/services → platforms}/agents-service.js +354 -197
- package/src/platforms/channel-auth-registry.js +162 -0
- package/src/platforms/channel-auth-service.js +82 -0
- package/src/{server/services → platforms}/commands-service.js +195 -115
- package/src/platforms/driver-registry.js +59 -0
- package/src/platforms/drivers/claude/api-operations.js +62 -0
- package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
- package/src/platforms/drivers/claude/channels.js +39 -0
- package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
- package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
- package/src/platforms/drivers/claude/health.js +22 -0
- package/src/platforms/drivers/claude/mcp.js +9 -0
- package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
- package/src/platforms/drivers/claude/native-config.js +46 -0
- package/src/platforms/drivers/claude/native-logs.js +72 -0
- package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
- package/src/platforms/drivers/claude/project-config.js +21 -0
- package/src/platforms/drivers/claude/projects.js +62 -0
- package/src/platforms/drivers/claude/prompts.js +9 -0
- package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
- package/src/platforms/drivers/claude/proxy.js +16 -0
- package/src/platforms/drivers/claude/resource-sync.js +9 -0
- package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
- package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
- package/src/platforms/drivers/claude/sessions.js +76 -0
- package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/claude/statistics.js +16 -0
- package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
- package/src/platforms/drivers/codex/api-operations.js +9 -0
- package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
- package/src/platforms/drivers/codex/channels.js +45 -0
- package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
- package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
- package/src/platforms/drivers/codex/mcp.js +9 -0
- package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
- package/src/platforms/drivers/codex/native-config.js +47 -0
- package/src/platforms/drivers/codex/native-logs.js +166 -0
- package/src/platforms/drivers/codex/project-config.js +21 -0
- package/src/platforms/drivers/codex/projects.js +26 -0
- package/src/platforms/drivers/codex/prompts.js +9 -0
- package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
- package/src/platforms/drivers/codex/proxy.js +16 -0
- package/src/platforms/drivers/codex/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
- package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
- package/src/platforms/drivers/codex/sessions.js +36 -0
- package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/codex/statistics.js +16 -0
- package/src/platforms/drivers/gemini/api-operations.js +9 -0
- package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
- package/src/platforms/drivers/gemini/channels.js +39 -0
- package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
- package/src/platforms/drivers/gemini/mcp.js +9 -0
- package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
- package/src/platforms/drivers/gemini/native-config.js +39 -0
- package/src/platforms/drivers/gemini/native-logs.js +180 -0
- package/src/platforms/drivers/gemini/project-config.js +21 -0
- package/src/platforms/drivers/gemini/projects.js +26 -0
- package/src/platforms/drivers/gemini/prompts.js +9 -0
- package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
- package/src/platforms/drivers/gemini/proxy.js +16 -0
- package/src/platforms/drivers/gemini/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
- package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
- package/src/platforms/drivers/gemini/sessions.js +47 -0
- package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/gemini/statistics.js +16 -0
- package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
- package/src/platforms/drivers/generic-filesystem.js +166 -0
- package/src/platforms/drivers/generic-jsonl.js +141 -0
- package/src/platforms/drivers/generic-mcp.js +44 -0
- package/src/platforms/drivers/generic-openai-compatible.js +45 -0
- package/src/platforms/drivers/generic-prompt.js +41 -0
- package/src/platforms/drivers/legacy.js +677 -0
- package/src/platforms/drivers/native-config-snapshot.js +125 -0
- package/src/platforms/drivers/native-log-utils.js +1292 -0
- package/src/platforms/drivers/notification-hooks.js +578 -0
- package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
- package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
- package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
- package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
- package/src/platforms/drivers/omp/channels.js +58 -0
- package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
- package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
- package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
- package/src/platforms/drivers/omp/mcp.js +9 -0
- package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
- package/src/platforms/drivers/omp/native-config.js +46 -0
- package/src/platforms/drivers/omp/native-logs.js +52 -0
- package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
- package/src/platforms/drivers/omp/project-config.js +29 -0
- package/src/platforms/drivers/omp/projects.js +20 -0
- package/src/platforms/drivers/omp/prompts.js +9 -0
- package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
- package/src/platforms/drivers/omp/proxy.js +16 -0
- package/src/platforms/drivers/omp/resource-sync.js +9 -0
- package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
- package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
- package/src/platforms/drivers/omp/sessions.js +36 -0
- package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
- package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
- package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
- package/src/platforms/drivers/omp/statistics.js +16 -0
- package/src/platforms/drivers/opencode/api-operations.js +9 -0
- package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
- package/src/platforms/drivers/opencode/channels.js +40 -0
- package/src/platforms/drivers/opencode/conversion.js +41 -0
- package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
- package/src/platforms/drivers/opencode/mcp.js +9 -0
- package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
- package/src/platforms/drivers/opencode/native-config.js +26 -0
- package/src/platforms/drivers/opencode/native-logs.js +133 -0
- package/src/platforms/drivers/opencode/project-config.js +98 -0
- package/src/platforms/drivers/opencode/projects.js +20 -0
- package/src/platforms/drivers/opencode/prompts.js +9 -0
- package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
- package/src/platforms/drivers/opencode/proxy.js +16 -0
- package/src/platforms/drivers/opencode/resource-sync.js +9 -0
- package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
- package/src/platforms/drivers/opencode/sessions.js +48 -0
- package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
- package/src/platforms/drivers/opencode/statistics.js +16 -0
- package/src/platforms/drivers/secure-file-driver.js +272 -0
- package/src/platforms/drivers/unsupported.js +7 -0
- package/src/platforms/manifest-schema.js +398 -0
- package/src/platforms/manifests/claude.json +489 -0
- package/src/platforms/manifests/codex.json +448 -0
- package/src/platforms/manifests/gemini.json +447 -0
- package/src/platforms/manifests/omp.json +496 -0
- package/src/platforms/manifests/opencode.json +422 -0
- package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
- package/src/{server/services → platforms}/notification-hooks.js +140 -1023
- package/src/platforms/oauth-credentials-service.js +506 -0
- package/src/platforms/path-resolver.js +179 -0
- package/src/platforms/platform-path-context.js +305 -0
- package/src/platforms/registry.js +248 -0
- package/src/platforms/remote-notification-providers.js +678 -0
- package/src/platforms/runtime.js +259 -0
- package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
- package/src/plugins/plugin-loader.js +2 -2
- package/src/plugins/plugin-manager.js +2 -2
- package/src/plugins/registry.js +27 -10
- package/src/reset-config.js +21 -5
- package/src/server/api/agents.js +46 -72
- package/src/server/api/commands.js +16 -6
- package/src/server/api/config-registry.js +206 -59
- package/src/server/api/config-sync.js +3 -2
- package/src/server/api/config-templates.js +20 -4
- package/src/server/api/config.js +111 -102
- package/src/server/api/convert.js +54 -178
- package/src/server/api/dashboard.js +168 -102
- package/src/server/api/hooks.js +1 -1
- package/src/server/api/mcp.js +224 -84
- package/src/server/api/platform-api-config.js +102 -0
- package/src/server/api/platform-route-factory.js +215 -0
- package/src/server/api/platform-routes.js +26 -0
- package/src/server/api/platforms.js +110 -0
- package/src/server/api/plugins.js +57 -57
- package/src/server/api/project-config.js +168 -0
- package/src/server/api/prompts.js +58 -45
- package/src/server/api/settings.js +55 -8
- package/src/server/api/skills.js +262 -133
- package/src/server/api/ui-config.js +2 -2
- package/src/server/api/validation-errors.js +18 -4
- package/src/server/api/workspaces.js +47 -12
- package/src/server/index.js +164 -213
- package/src/server/platform-context.js +81 -0
- package/src/server/services/channel-balance.js +11 -20
- package/src/server/services/channel-health.js +21 -6
- package/src/server/services/channel-scheduler.js +29 -39
- package/src/server/services/channel-sync-utils.js +23 -13
- package/src/server/services/config-export-service.js +350 -319
- package/src/server/services/config-registry-service.js +280 -54
- package/src/server/services/config-sync-manager.js +172 -944
- package/src/server/services/config-templates-service.js +419 -353
- package/src/server/services/control-manifest-store.js +206 -0
- package/src/server/services/dashboard-snapshot-worker.js +656 -100
- package/src/server/services/effective-control-service.js +557 -0
- package/src/server/services/local-resource-index.js +314 -0
- package/src/server/services/mcp-service.js +607 -977
- package/src/server/services/model-detector.js +15 -10
- package/src/server/services/native-log-channel-resolver.js +214 -0
- package/src/server/services/native-log-observer.js +291 -0
- package/src/server/services/network-access.js +1 -1
- package/src/server/services/platform-catalog.js +82 -0
- package/src/server/services/platform-resolution.js +32 -29
- package/src/server/services/plugins-service.js +376 -122
- package/src/server/services/project-config-service.js +550 -0
- package/src/server/services/project-path-validation.js +31 -0
- package/src/server/services/prompts-service.js +267 -233
- package/src/server/services/proxy-runtime.js +7 -4
- package/src/server/services/remote-credential-cache.js +43 -0
- package/src/server/services/repo-scanner-base.js +182 -75
- package/src/server/services/request-logger.js +7 -4
- package/src/server/services/session-history-index.js +780 -213
- package/src/server/services/session-history-worker.js +228 -33
- package/src/server/services/session-snapshot-worker.js +124 -34
- package/src/server/services/session-snapshots.js +4 -2
- package/src/server/services/skill-artifact-store.js +355 -0
- package/src/server/services/skill-format-adapters.js +65 -0
- package/src/server/services/skill-projection-service.js +323 -0
- package/src/server/services/skill-refresh-task-service.js +275 -0
- package/src/server/services/skill-service.js +2020 -677
- package/src/server/services/snapshot-cache.js +30 -8
- package/src/server/services/speed-test.js +8 -8
- package/src/server/services/sqlite-connection.js +19 -3
- package/src/server/services/statistics-service.js +240 -57
- package/src/server/services/ui-config.js +107 -76
- package/src/server/services/usage-log-utils.js +98 -0
- package/src/server/services/web-build.js +1 -1
- package/src/server/services/workspace-service.js +59 -29
- package/src/server/utils/pricing.js +10 -19
- package/src/server/websocket-server.js +153 -28
- package/src/{server/services/base → shared}/base-channel-service.js +156 -28
- package/src/shared/capability-driver.js +104 -0
- package/src/shared/channel-auth-policy.js +27 -0
- package/src/shared/channel-driver.js +161 -0
- package/src/shared/driver-factories/api.js +180 -0
- package/src/shared/driver-factories/mcp.js +244 -0
- package/src/shared/driver-factories/projects.js +121 -0
- package/src/shared/driver-factories/prompts.js +58 -0
- package/src/shared/driver-factories/resource-sync.js +375 -0
- package/src/shared/driver-result.js +78 -0
- package/src/shared/mcp-format.js +189 -0
- package/src/shared/platforms.js +104 -167
- package/src/shared/project-config.js +512 -0
- package/src/shared/proxy-driver.js +57 -0
- package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
- package/src/ui/menu.js +91 -67
- package/src/utils/port-helper.js +35 -19
- package/src/utils/session.js +1 -1
- package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
- package/dist/web/assets/Home-DCEyLzwX.js +0 -1
- package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
- package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
- package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
- package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
- package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
- package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
- package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
- package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
- package/dist/web/assets/icons-nGdSKQn2.js +0 -1
- package/dist/web/assets/index-D5hyHPj7.js +0 -2
- package/dist/web/assets/index-bJRG3RXH.css +0 -1
- package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
- package/dist/web/assets/markdown-BfC0goYb.css +0 -10
- package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
- package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
- package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
- package/src/config/model-pricing.js +0 -35
- package/src/server/api/channels.js +0 -439
- package/src/server/api/claude-hooks.js +0 -56
- package/src/server/api/claude-statistics.js +0 -57
- package/src/server/api/codex-channels.js +0 -491
- package/src/server/api/codex-projects.js +0 -117
- package/src/server/api/codex-proxy.js +0 -293
- package/src/server/api/codex-sessions.js +0 -652
- package/src/server/api/codex-statistics.js +0 -57
- package/src/server/api/gemini-channels.js +0 -392
- package/src/server/api/gemini-projects.js +0 -103
- package/src/server/api/gemini-proxy.js +0 -231
- package/src/server/api/gemini-sessions.js +0 -550
- package/src/server/api/gemini-statistics.js +0 -57
- package/src/server/api/health-check.js +0 -31
- package/src/server/api/oauth-credentials.js +0 -198
- package/src/server/api/omp-channels.js +0 -575
- package/src/server/api/omp-projects.js +0 -88
- package/src/server/api/omp-proxy.js +0 -125
- package/src/server/api/omp-sessions.js +0 -386
- package/src/server/api/omp-statistics.js +0 -40
- package/src/server/api/opencode-channels.js +0 -471
- package/src/server/api/opencode-projects.js +0 -110
- package/src/server/api/opencode-proxy.js +0 -354
- package/src/server/api/opencode-sessions.js +0 -466
- package/src/server/api/opencode-statistics.js +0 -57
- package/src/server/api/projects.js +0 -120
- package/src/server/api/proxy.js +0 -330
- package/src/server/api/sessions.js +0 -810
- package/src/server/services/oauth-credentials-service.js +0 -945
- package/src/server/services/omp-session-log-observer.js +0 -138
- package/src/server/services/opencode-gateway-adapters.js +0 -43
- package/src/server/services/project-snapshots.js +0 -59
- package/src/server/services/proxy-log-helper.js +0 -298
- package/src/server/services/session-cache.js +0 -127
- package/src/server/services/session-converter.js +0 -582
- package/src/server/services/session-history-adapters/claude.js +0 -235
- /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
- /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
- /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
- /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
- /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
- /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
- /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
- /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { openDatabase, closeDatabase } = require('./sqlite-connection');
|
|
4
|
+
|
|
4
5
|
const { PATHS } = require('../../config/paths');
|
|
6
|
+
const platformRuntime = require('../../platforms/runtime');
|
|
5
7
|
const fs = require('fs');
|
|
6
8
|
const path = require('path');
|
|
7
9
|
|
|
@@ -12,11 +14,93 @@ const path = require('path');
|
|
|
12
14
|
/** @type {number} Maximum time a source inventory is considered fresh */
|
|
13
15
|
const INDEX_INVENTORY_TTL_MS = 30000;
|
|
14
16
|
|
|
15
|
-
/** @type {number} Max wait for stale-ok on a cold source */
|
|
16
|
-
const COLD_STALE_WAIT_MS = 2500;
|
|
17
17
|
|
|
18
|
-
/** @type {number}
|
|
19
|
-
const
|
|
18
|
+
/** @type {number} Maximum time a cold stale-ok read waits for inventory */
|
|
19
|
+
const INDEX_COLD_WAIT_MS = 1500;
|
|
20
|
+
/** @type {number} Maximum age for a lock whose owner cannot be inspected */
|
|
21
|
+
const INVENTORY_LOCK_STALE_MS = 30 * 60 * 1000;
|
|
22
|
+
/** @type {number} Number of files parsed before results are released */
|
|
23
|
+
const INVENTORY_PARSE_BATCH_SIZE = 8;
|
|
24
|
+
/** @type {number} Maximum number of files parsed concurrently */
|
|
25
|
+
const INVENTORY_PARSE_CONCURRENCY = 2;
|
|
26
|
+
const BUILTIN_SESSION_SOURCES = new Set(['claude', 'codex', 'gemini', 'omp']);
|
|
27
|
+
// Keep synchronized with the parserVersion exposed by the built-in session drivers.
|
|
28
|
+
const BUILTIN_SESSION_PARSER_VERSIONS = Object.freeze({ claude: 2, codex: 2, gemini: 2, omp: 2 });
|
|
29
|
+
function _parserVersionForDriver(driver) {
|
|
30
|
+
const version = Number(driver?.parserVersion);
|
|
31
|
+
return Number.isInteger(version) && version >= 1 ? version : 1;
|
|
32
|
+
}
|
|
33
|
+
function _isUsableRuntime(runtime) {
|
|
34
|
+
return !!runtime && typeof runtime === 'object' && typeof runtime.getDriver === 'function';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _inventoryLockPath(dbPath, source) {
|
|
38
|
+
const sourceKey = Buffer.from(String(source), 'utf8').toString('hex');
|
|
39
|
+
return `${dbPath}.${sourceKey}.inventory.lock`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _isProcessAlive(pid) {
|
|
43
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
44
|
+
try {
|
|
45
|
+
process.kill(pid, 0);
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return error && error.code === 'EPERM';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Acquire a durable per-database/source lock. The in-memory single-flight
|
|
54
|
+
* map only protects requests inside one Node process; production indexing is
|
|
55
|
+
* deliberately performed in child processes, so it cannot prevent two
|
|
56
|
+
* workers from rebuilding the same source after a parent restart.
|
|
57
|
+
*/
|
|
58
|
+
function _tryAcquireInventoryLock(dbPath, source) {
|
|
59
|
+
const lockPath = _inventoryLockPath(dbPath, source);
|
|
60
|
+
|
|
61
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
62
|
+
let fd;
|
|
63
|
+
try {
|
|
64
|
+
fd = fs.openSync(lockPath, 'wx');
|
|
65
|
+
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), 'utf8');
|
|
66
|
+
return { fd, lockPath };
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (fd !== undefined) {
|
|
69
|
+
try { fs.closeSync(fd); } catch (_err) {}
|
|
70
|
+
}
|
|
71
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let owner = null;
|
|
75
|
+
let ageMs = 0;
|
|
76
|
+
try {
|
|
77
|
+
owner = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
78
|
+
ageMs = Math.max(0, Date.now() - fs.statSync(lockPath).mtimeMs);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error?.code === 'ENOENT') continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ownerPid = Number(owner?.pid);
|
|
84
|
+
if (_isProcessAlive(ownerPid) || (!Number.isFinite(ageMs) || ageMs < INVENTORY_LOCK_STALE_MS)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
fs.unlinkSync(lockPath);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error?.code !== 'ENOENT') return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _releaseInventoryLock(lock) {
|
|
99
|
+
if (!lock) return;
|
|
100
|
+
try { fs.closeSync(lock.fd); } catch (_err) {}
|
|
101
|
+
try { fs.unlinkSync(lock.lockPath); } catch (_err) {}
|
|
102
|
+
}
|
|
103
|
+
|
|
20
104
|
|
|
21
105
|
// ---------------------------------------------------------------------------
|
|
22
106
|
// Schema
|
|
@@ -49,6 +133,7 @@ CREATE TABLE IF NOT EXISTS session_file (
|
|
|
49
133
|
message_count INTEGER NOT NULL DEFAULT 0,
|
|
50
134
|
usage_json TEXT,
|
|
51
135
|
extra_json TEXT,
|
|
136
|
+
parser_version INTEGER NOT NULL DEFAULT 0,
|
|
52
137
|
PRIMARY KEY (source, file_path),
|
|
53
138
|
UNIQUE (source, session_id)
|
|
54
139
|
);
|
|
@@ -80,6 +165,9 @@ CREATE TABLE IF NOT EXISTS session_message (
|
|
|
80
165
|
|
|
81
166
|
CREATE INDEX IF NOT EXISTS idx_session_message_sid_ord
|
|
82
167
|
ON session_message(source, session_id, ordinal);
|
|
168
|
+
|
|
169
|
+
CREATE INDEX IF NOT EXISTS idx_session_message_user_number
|
|
170
|
+
ON session_message(source, session_id, user_message_number, ordinal);
|
|
83
171
|
`;
|
|
84
172
|
|
|
85
173
|
const FTS_SETUP_SQL = `
|
|
@@ -144,27 +232,197 @@ function _ftsQuote(s) {
|
|
|
144
232
|
return '"' + String(s).replace(/"/g, '""') + '"';
|
|
145
233
|
}
|
|
146
234
|
|
|
147
|
-
|
|
148
|
-
|
|
235
|
+
function _normalizeTypedSessionsResult(result, defaultOperation = 'resolve-driver') {
|
|
236
|
+
if (!result || typeof result !== 'object') {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
if (result.status !== 'failed' && result.status !== 'unsupported') {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
if (typeof result.platform !== 'string' || typeof result.capability !== 'string') {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
...result,
|
|
247
|
+
operation: typeof result.operation === 'string' ? result.operation : defaultOperation
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function _isTypedSessionsResult(result) {
|
|
252
|
+
return !!_normalizeTypedSessionsResult(result);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function _typedSessionsFailure(source, error, operation = 'resolve-driver') {
|
|
256
|
+
const result = {
|
|
257
|
+
status: 'failed',
|
|
258
|
+
platform: source,
|
|
259
|
+
capability: 'sessions',
|
|
260
|
+
operation,
|
|
261
|
+
error: error && error.message ? error.message : String(error)
|
|
262
|
+
};
|
|
263
|
+
if (error) {
|
|
264
|
+
Object.defineProperty(result, 'cause', { value: error, enumerable: false });
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function _typedSessionsUnsupported(source, error, operation = 'resolve-driver') {
|
|
270
|
+
const result = {
|
|
271
|
+
status: 'unsupported',
|
|
272
|
+
platform: source,
|
|
273
|
+
capability: 'sessions',
|
|
274
|
+
operation,
|
|
275
|
+
error: error && error.message ? error.message : String(error)
|
|
276
|
+
};
|
|
277
|
+
if (error) {
|
|
278
|
+
Object.defineProperty(result, 'cause', { value: error, enumerable: false });
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function _getRuntimeSessionsDriver(runtime, source, config = {}) {
|
|
284
|
+
if (!runtime || typeof runtime.getDriver !== 'function') {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const driver = runtime.getDriver(source, 'sessions', { config });
|
|
290
|
+
const typedResult = _normalizeTypedSessionsResult(driver);
|
|
291
|
+
if (typedResult) {
|
|
292
|
+
return typedResult;
|
|
293
|
+
}
|
|
294
|
+
if (!driver || typeof driver !== 'object') {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (typeof driver.inventory !== 'function' || typeof driver.parse !== 'function') {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
return driver;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return _typedSessionsFailure(source, error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function _safeParseJson(value, fallback) {
|
|
307
|
+
if (!value) return fallback;
|
|
308
|
+
try {
|
|
309
|
+
return JSON.parse(value);
|
|
310
|
+
} catch (_) {
|
|
311
|
+
return fallback;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function _safeParseObject(value) {
|
|
316
|
+
const parsed = _safeParseJson(value, {});
|
|
317
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
318
|
+
return {};
|
|
319
|
+
}
|
|
320
|
+
return parsed;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
function _normalizeRuntimeParseResult(result, descriptor = {}, { preservePayloadUpdatedAt = false } = {}) {
|
|
325
|
+
if (!result || typeof result !== 'object') {
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (result.session && Array.isArray(result.messages)) {
|
|
330
|
+
const sessionExtra = _safeParseObject(result.session.extraJson);
|
|
331
|
+
const extraJson = JSON.stringify({
|
|
332
|
+
...sessionExtra,
|
|
333
|
+
projectHint: result.session.projectHint || descriptor.projectHint || null,
|
|
334
|
+
mtimeMs: descriptor.mtimeMs ?? sessionExtra.mtimeMs ?? null
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
...result,
|
|
338
|
+
session: {
|
|
339
|
+
...result.session,
|
|
340
|
+
projectHint: result.session.projectHint || descriptor.projectHint || '',
|
|
341
|
+
projectName: result.session.projectName || result.session.projectHint || descriptor.projectHint || '',
|
|
342
|
+
projectDisplayName: result.session.projectDisplayName || result.session.projectName || result.session.projectHint || descriptor.projectHint || '',
|
|
343
|
+
projectFullPath: result.session.projectFullPath || result.session.projectPath || '',
|
|
344
|
+
updatedAt: preservePayloadUpdatedAt ? (result.session.updatedAt ?? descriptor.mtimeMs ?? null) : descriptor.mtimeMs ?? null,
|
|
345
|
+
extraJson
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (result.sessionId) {
|
|
351
|
+
const projectHint = descriptor.projectHint || result.projectHint || result.projectName || '';
|
|
352
|
+
const projectName = result.projectName || projectHint || '';
|
|
353
|
+
const projectDisplayName = result.projectDisplayName || result.projectName || projectHint || '';
|
|
354
|
+
const resultExtra = _safeParseObject(result.extraJson);
|
|
355
|
+
const descriptorMtime = descriptor.mtimeMs ?? null;
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
session: {
|
|
359
|
+
sessionId: result.sessionId,
|
|
360
|
+
projectHint,
|
|
361
|
+
projectName,
|
|
362
|
+
projectDisplayName,
|
|
363
|
+
projectFullPath: result.projectFullPath || result.projectPath || '',
|
|
364
|
+
firstMessage: result.firstMessage || null,
|
|
365
|
+
gitBranch: result.gitBranch || null,
|
|
366
|
+
provider: result.provider || null,
|
|
367
|
+
model: result.model || null,
|
|
368
|
+
startedAt: result.startedAt || null,
|
|
369
|
+
updatedAt: descriptorMtime ?? result.updatedAt ?? null,
|
|
370
|
+
usageJson: result.usageJson || null,
|
|
371
|
+
extraJson: JSON.stringify({
|
|
372
|
+
...resultExtra,
|
|
373
|
+
projectHint: projectHint || null,
|
|
374
|
+
mtimeMs: descriptor.mtimeMs ?? resultExtra.mtimeMs ?? null
|
|
375
|
+
})
|
|
376
|
+
},
|
|
377
|
+
messages: Array.isArray(result.messages) ? result.messages : []
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function _adaptRuntimeSessionsDriver(driver) {
|
|
385
|
+
if (!driver) return null;
|
|
386
|
+
const unwrap = result => result && result.status === 'ok' ? result.data : result;
|
|
387
|
+
return {
|
|
388
|
+
parserVersion: driver.parserVersion,
|
|
389
|
+
inventory: async (...args) => unwrap(await driver.inventory(...args)),
|
|
390
|
+
parse: async (descriptor, ...args) => _normalizeRuntimeParseResult(
|
|
391
|
+
await unwrap(await driver.parse(descriptor, ...args)),
|
|
392
|
+
descriptor,
|
|
393
|
+
{ preservePayloadUpdatedAt: driver.preservePayloadUpdatedAt === true }
|
|
394
|
+
)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
149
397
|
// ---------------------------------------------------------------------------
|
|
150
398
|
|
|
151
399
|
/**
|
|
152
400
|
* @param {object} [opts]
|
|
153
401
|
* @param {string} [opts.dbPath]
|
|
154
402
|
* @param {object} [opts.adapterRegistry] - source → { inventory, parse }
|
|
403
|
+
* @param {object} [opts.runtime] - runtime with getDriver(source, capability)
|
|
155
404
|
* @param {Function} [opts.workerRunner] - (source, indexDbPath) => Promise<void>
|
|
156
405
|
* @param {boolean|null} [opts.ftsEnabledOverride] - override FTS detection for tests
|
|
157
406
|
* @returns {object} index API
|
|
158
407
|
*/
|
|
159
408
|
function createSessionHistoryIndex(opts = {}) {
|
|
160
409
|
const dbPath = opts.dbPath || PATHS?.sessionHistoryIndex || path.join(PATHS?.base || process.cwd(), 'session-history.sqlite');
|
|
161
|
-
const
|
|
410
|
+
const explicitProjectsDir = opts.projectsDir;
|
|
411
|
+
const indexConfig = opts.config || (opts.projectsDir ? { projectsDir: opts.projectsDir } : {});
|
|
412
|
+
const explicitAdapters = opts.adapterRegistry || null;
|
|
413
|
+
const adapters = explicitAdapters || {};
|
|
414
|
+
const runtimeProvided = !explicitAdapters && process.env.NODE_ENV === 'test' && _isUsableRuntime(opts.runtime);
|
|
415
|
+
const runtime = explicitAdapters ? null : (runtimeProvided ? opts.runtime : platformRuntime.getPlatformRuntime());
|
|
162
416
|
const workerRunner = opts.workerRunner || _defaultWorkerRunner;
|
|
163
417
|
const ftsEnabled = opts.ftsEnabledOverride !== undefined
|
|
164
418
|
? opts.ftsEnabledOverride
|
|
165
419
|
: null;
|
|
166
420
|
const shouldUseWorker = process.env.NODE_ENV !== 'test' && process.env.CC_TOOL_SESSION_HISTORY_CHILD !== '1';
|
|
167
421
|
let _db = null;
|
|
422
|
+
/** @type {Map<string, {size: number, mtimeMs: number, checkedAt: number, filePath: string}>} */
|
|
423
|
+
const fileVersions = new Map();
|
|
424
|
+
const fileChecks = new Map();
|
|
425
|
+
const sourceFreshness = new Map();
|
|
168
426
|
let _ftsAvailable = null;
|
|
169
427
|
/** @type {Map<string, Promise<void>>} */
|
|
170
428
|
const _inflight = new Map();
|
|
@@ -183,10 +441,68 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
183
441
|
}
|
|
184
442
|
return _db;
|
|
185
443
|
}
|
|
444
|
+
function _parserVersionForSource(source, adapter = null) {
|
|
445
|
+
if (adapter) return _parserVersionForDriver(adapter);
|
|
446
|
+
if (explicitAdapters) return _parserVersionForDriver(adapters[source]);
|
|
447
|
+
const runtimeDriver = _getRuntimeSessionsDriver(runtime, source, indexConfig);
|
|
448
|
+
return _parserVersionForDriver(runtimeDriver);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Check only the persisted inventory timestamp. Inventory owns file checks.
|
|
453
|
+
*
|
|
454
|
+
* @param {string} source
|
|
455
|
+
* @returns {boolean}
|
|
456
|
+
*/
|
|
457
|
+
function _isSourceFresh(source) {
|
|
458
|
+
const row = _getDb().prepare(
|
|
459
|
+
'SELECT last_inventory_ms FROM source_state WHERE source = ?'
|
|
460
|
+
).get(source);
|
|
461
|
+
return Boolean(
|
|
462
|
+
row?.last_inventory_ms
|
|
463
|
+
&& Date.now() - Number(row.last_inventory_ms) < INDEX_INVENTORY_TTL_MS
|
|
464
|
+
&& !_hasKnownParserMigrationPending(source)
|
|
465
|
+
&& (!explicitAdapters || !_hasParserMigrationPending(source))
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function _hasKnownParserMigrationPending(source) {
|
|
470
|
+
if (explicitAdapters || runtimeProvided) return false;
|
|
471
|
+
const parserVersion = BUILTIN_SESSION_PARSER_VERSIONS[source];
|
|
472
|
+
if (!parserVersion) return false;
|
|
473
|
+
const row = _getDb().prepare(
|
|
474
|
+
'SELECT 1 AS pending FROM session_file WHERE source = ? AND parser_version <> ? LIMIT 1'
|
|
475
|
+
).get(source, parserVersion);
|
|
476
|
+
return Boolean(row);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function _hasParserMigrationPending(source) {
|
|
480
|
+
const row = _getDb().prepare(
|
|
481
|
+
'SELECT 1 AS pending FROM session_file WHERE source = ? AND parser_version <> ? LIMIT 1'
|
|
482
|
+
).get(source, _parserVersionForSource(source));
|
|
483
|
+
return Boolean(row);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function _hasIndexedData(source) {
|
|
487
|
+
const row = _getDb().prepare(
|
|
488
|
+
'SELECT 1 AS indexed FROM session_file WHERE source = ? LIMIT 1'
|
|
489
|
+
).get(source);
|
|
490
|
+
return Boolean(row);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function _hasUsableIndexedData(source) {
|
|
494
|
+
return _hasIndexedData(source)
|
|
495
|
+
&& !_hasKnownParserMigrationPending(source)
|
|
496
|
+
&& (!explicitAdapters || !_hasParserMigrationPending(source));
|
|
497
|
+
}
|
|
186
498
|
|
|
187
499
|
function _initSchema(db) {
|
|
188
500
|
db.exec('PRAGMA journal_mode = WAL');
|
|
189
501
|
db.exec(SCHEMA_SQL);
|
|
502
|
+
const columns = db.prepare('PRAGMA table_info(session_file)').all();
|
|
503
|
+
if (!columns.some((column) => column.name === 'parser_version')) {
|
|
504
|
+
db.exec('ALTER TABLE session_file ADD COLUMN parser_version INTEGER NOT NULL DEFAULT 0');
|
|
505
|
+
}
|
|
190
506
|
}
|
|
191
507
|
|
|
192
508
|
function _initFts(db) {
|
|
@@ -199,28 +515,41 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
199
515
|
|
|
200
516
|
/**
|
|
201
517
|
* @param {string} source
|
|
202
|
-
* @param {{ force?: boolean, consistency?: string }} [options]
|
|
518
|
+
* @param {{ force?: boolean, consistency?: string, allowStaleData?: boolean }} [options]
|
|
203
519
|
* @returns {Promise<void>}
|
|
204
520
|
*/
|
|
205
521
|
async function ensureSourceIndexed(source, options = {}) {
|
|
206
522
|
const consistency = options.consistency || 'stale-ok';
|
|
207
523
|
const force = options.force === true;
|
|
208
|
-
|
|
524
|
+
const allowStaleData = options.allowStaleData === true;
|
|
209
525
|
const key = `ensure:${source}`;
|
|
526
|
+
|
|
527
|
+
if (!force && _isSourceFresh(source)) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
210
531
|
if (_inflight.has(key)) {
|
|
211
532
|
if (consistency === 'complete') {
|
|
212
533
|
return _inflight.get(key);
|
|
213
534
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
535
|
+
// The project directory can safely use the previous rows while a
|
|
536
|
+
// parser migration or inventory refresh is running. Session reads
|
|
537
|
+
// retain their existing behavior and wait for migration completion.
|
|
538
|
+
if (allowStaleData ? _hasIndexedData(source) : _hasUsableIndexedData(source)) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
return _inflight.get(key);
|
|
219
542
|
}
|
|
220
543
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
544
|
+
const useWorker = shouldUseWorker && !runtimeProvided && !explicitAdapters;
|
|
545
|
+
const workerOptions = { force };
|
|
546
|
+
const workerProjectsDir = source === 'claude'
|
|
547
|
+
? (options.config?.projectsDir || explicitProjectsDir)
|
|
548
|
+
: explicitProjectsDir;
|
|
549
|
+
if (workerProjectsDir) workerOptions.projectsDir = workerProjectsDir;
|
|
550
|
+
const promise = useWorker
|
|
551
|
+
? workerRunner(source, dbPath, workerOptions)
|
|
552
|
+
: _runInventory(source, { force, config: options.config || indexConfig });
|
|
224
553
|
_inflight.set(key, promise);
|
|
225
554
|
promise.finally(() => {
|
|
226
555
|
if (_inflight.get(key) === promise) {
|
|
@@ -228,166 +557,298 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
228
557
|
}
|
|
229
558
|
}).catch(() => {});
|
|
230
559
|
|
|
560
|
+
if (consistency === 'stale-ok' && (allowStaleData ? _hasIndexedData(source) : _hasUsableIndexedData(source))) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
231
564
|
if (consistency === 'stale-ok') {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
565
|
+
await Promise.race([
|
|
566
|
+
promise,
|
|
567
|
+
new Promise(resolve => setTimeout(resolve, INDEX_COLD_WAIT_MS))
|
|
568
|
+
]);
|
|
569
|
+
return;
|
|
237
570
|
}
|
|
238
571
|
|
|
239
572
|
await promise;
|
|
240
573
|
}
|
|
574
|
+
function _isTypedFailureResult(result) {
|
|
575
|
+
return _isTypedSessionsResult(result);
|
|
576
|
+
}
|
|
577
|
+
function _typedFailureToError(result) {
|
|
578
|
+
const fallbackMessage = result.status === 'unsupported'
|
|
579
|
+
? `unsupported ${result.capability}`
|
|
580
|
+
: 'inventory failed';
|
|
581
|
+
const cause = result.cause || (result.error instanceof Error ? result.error : new Error(String(result.error || fallbackMessage)));
|
|
582
|
+
const error = new Error(`Runtime ${result.capability} ${result.operation} ${result.status} on ${result.platform}: ${cause.message}`);
|
|
583
|
+
error.status = result.status;
|
|
584
|
+
error.platform = result.platform;
|
|
585
|
+
error.capability = result.capability;
|
|
586
|
+
error.operation = result.operation;
|
|
587
|
+
error.context = result;
|
|
588
|
+
error.cause = cause;
|
|
589
|
+
error.failure = result;
|
|
590
|
+
return error;
|
|
591
|
+
}
|
|
241
592
|
|
|
242
|
-
|
|
593
|
+
function _recordSourceState(db, source, lastInventoryMs, lastError) {
|
|
594
|
+
db.prepare(
|
|
595
|
+
'INSERT INTO source_state(source, last_inventory_ms, last_error) VALUES(?, ?, ?) ON CONFLICT(source) DO UPDATE SET last_inventory_ms = excluded.last_inventory_ms, last_error = excluded.last_error'
|
|
596
|
+
).run(source, lastInventoryMs, lastError);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function _runInventory(source, { force = false, config = indexConfig } = {}) {
|
|
243
600
|
const db = _getDb();
|
|
244
|
-
const
|
|
245
|
-
if (!
|
|
601
|
+
const inventoryLock = _tryAcquireInventoryLock(dbPath, source);
|
|
602
|
+
if (!inventoryLock) {
|
|
603
|
+
// Another process is already doing the expensive work. Existing rows
|
|
604
|
+
// remain readable, and the next scheduled inventory will observe the
|
|
605
|
+
// new source_state timestamp once that worker finishes.
|
|
246
606
|
return;
|
|
247
607
|
}
|
|
608
|
+
let errorMsg = null;
|
|
609
|
+
let stateToRecord = null;
|
|
248
610
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (row && row.last_inventory_ms && (Date.now() - row.last_inventory_ms) < INDEX_INVENTORY_TTL_MS) {
|
|
611
|
+
try {
|
|
612
|
+
if (!force && _isSourceFresh(source)) {
|
|
252
613
|
return;
|
|
253
614
|
}
|
|
254
|
-
|
|
615
|
+
let adapter = null;
|
|
255
616
|
|
|
256
|
-
|
|
257
|
-
|
|
617
|
+
if (explicitAdapters) {
|
|
618
|
+
adapter = adapters[source];
|
|
619
|
+
} else {
|
|
620
|
+
const runtimeDriver = _getRuntimeSessionsDriver(runtime, source, config);
|
|
621
|
+
if (_isTypedFailureResult(runtimeDriver)) {
|
|
622
|
+
throw _typedFailureToError(runtimeDriver);
|
|
623
|
+
}
|
|
258
624
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
625
|
+
if (runtimeDriver) {
|
|
626
|
+
adapter = _adaptRuntimeSessionsDriver(runtimeDriver);
|
|
627
|
+
} else if (BUILTIN_SESSION_SOURCES.has(source) && adapters[source]) {
|
|
628
|
+
adapter = adapters[source];
|
|
629
|
+
} else {
|
|
630
|
+
throw _typedFailureToError(_typedSessionsUnsupported(source, new Error(`unsupported sessions source: ${source}`)));
|
|
631
|
+
}
|
|
264
632
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const toParse = [];
|
|
274
|
-
const activePaths = new Set();
|
|
275
|
-
for (const d of seen.values()) {
|
|
276
|
-
activePaths.add(d.filePath);
|
|
277
|
-
const idx = indexedFiles.get(d.filePath);
|
|
278
|
-
if (!idx || idx.size !== d.size || idx.mtime_ms !== d.mtimeMs) {
|
|
279
|
-
toParse.push(d);
|
|
633
|
+
const parserVersion = _parserVersionForSource(source, adapter);
|
|
634
|
+
const inventoryResult = await adapter.inventory({
|
|
635
|
+
projectsDir: source === 'claude' ? config.projectsDir : explicitProjectsDir
|
|
636
|
+
});
|
|
637
|
+
if (_isTypedFailureResult(inventoryResult)) {
|
|
638
|
+
throw _typedFailureToError(inventoryResult);
|
|
280
639
|
}
|
|
281
|
-
}
|
|
282
640
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
641
|
+
const indexedFiles = new Map();
|
|
642
|
+
for (const row of db.prepare('SELECT file_path, size, mtime_ms, session_id, parser_version FROM session_file WHERE source = ?').all(source)) {
|
|
643
|
+
indexedFiles.set(row.file_path, {
|
|
644
|
+
size: row.size,
|
|
645
|
+
mtime_ms: row.mtime_ms,
|
|
646
|
+
sessionId: row.session_id,
|
|
647
|
+
parserVersion: row.parser_version
|
|
648
|
+
});
|
|
286
649
|
}
|
|
287
|
-
}
|
|
288
650
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const postStat = fs.statSync(d.filePath);
|
|
297
|
-
if (postStat.size !== preStat.size || postStat.mtimeMs !== preStat.mtimeMs) {
|
|
298
|
-
const retry = await adapter.parse(d);
|
|
299
|
-
const retryStat = fs.statSync(d.filePath);
|
|
300
|
-
if (retryStat.size !== postStat.size || retryStat.mtimeMs !== postStat.mtimeMs) {
|
|
301
|
-
const existing = db.prepare('SELECT 1 FROM session_file WHERE source = ? AND file_path = ?').get(source, d.filePath);
|
|
302
|
-
if (existing) continue;
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
_upsertSession(db, source, d, retry.session, retry.messages);
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
} catch (_statErr) {
|
|
309
|
-
continue;
|
|
651
|
+
const winnersBySessionId = new Map();
|
|
652
|
+
for (const descriptor of inventoryResult) {
|
|
653
|
+
const current = winnersBySessionId.get(descriptor.sessionId);
|
|
654
|
+
if (!current
|
|
655
|
+
|| descriptor.mtimeMs > current.mtimeMs
|
|
656
|
+
|| (descriptor.mtimeMs === current.mtimeMs && descriptor.filePath < current.filePath)) {
|
|
657
|
+
winnersBySessionId.set(descriptor.sessionId, descriptor);
|
|
310
658
|
}
|
|
659
|
+
}
|
|
311
660
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
661
|
+
const toParse = [];
|
|
662
|
+
const activePaths = new Set();
|
|
663
|
+
for (const d of winnersBySessionId.values()) {
|
|
664
|
+
activePaths.add(d.filePath);
|
|
665
|
+
const idx = indexedFiles.get(d.filePath);
|
|
666
|
+
if (!idx
|
|
667
|
+
|| idx.size !== d.size
|
|
668
|
+
|| idx.mtime_ms !== d.mtimeMs
|
|
669
|
+
|| idx.parserVersion !== parserVersion) {
|
|
670
|
+
toParse.push(d);
|
|
317
671
|
}
|
|
318
672
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
673
|
+
const parseDescriptor = async (d) => {
|
|
674
|
+
const preFingerprint = { size: d.size, mtimeMs: d.mtimeMs };
|
|
675
|
+
const parseResult = await adapter.parse({ ...d, projectsDir: config.projectsDir });
|
|
676
|
+
if (_isTypedFailureResult(parseResult)) throw _typedFailureToError(parseResult);
|
|
677
|
+
if (!parseResult || typeof parseResult !== 'object' || !parseResult.session || typeof parseResult.session !== 'object' || typeof parseResult.session.sessionId !== 'string' || !parseResult.session.sessionId.trim() || !Array.isArray(parseResult.messages)) {
|
|
678
|
+
throw new Error('invalid parsed session result');
|
|
679
|
+
}
|
|
325
680
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
681
|
+
let postStat;
|
|
682
|
+
try {
|
|
683
|
+
postStat = await fs.promises.stat(d.filePath);
|
|
684
|
+
} catch (_) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
if (postStat.size === preFingerprint.size && postStat.mtimeMs === preFingerprint.mtimeMs) {
|
|
688
|
+
return { descriptor: d, parseResult };
|
|
689
|
+
}
|
|
690
|
+
const retryDescriptor = { ...d, size: postStat.size, mtimeMs: postStat.mtimeMs };
|
|
691
|
+
const retryResult = await adapter.parse(retryDescriptor);
|
|
692
|
+
if (_isTypedFailureResult(retryResult)) {
|
|
693
|
+
throw _typedFailureToError(retryResult);
|
|
694
|
+
}
|
|
695
|
+
if (!retryResult || typeof retryResult !== 'object' || !retryResult.session || typeof retryResult.session !== 'object' || typeof retryResult.session.sessionId !== 'string' || !retryResult.session.sessionId.trim() || !Array.isArray(retryResult.messages)) {
|
|
696
|
+
throw new Error('invalid parsed session result');
|
|
697
|
+
}
|
|
698
|
+
let retryStat;
|
|
699
|
+
try {
|
|
700
|
+
retryStat = await fs.promises.stat(d.filePath);
|
|
701
|
+
} catch (_) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
if (retryStat.size !== postStat.size || retryStat.mtimeMs !== postStat.mtimeMs) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
descriptor: { ...retryDescriptor, size: retryStat.size, mtimeMs: retryStat.mtimeMs },
|
|
709
|
+
parseResult: retryResult
|
|
710
|
+
};
|
|
711
|
+
};
|
|
330
712
|
|
|
331
|
-
db.prepare(
|
|
713
|
+
const deletePath = db.prepare('DELETE FROM session_file WHERE source = ? AND file_path = ?');
|
|
714
|
+
const deleteSession = db.prepare('DELETE FROM session_file WHERE source = ? AND session_id = ?');
|
|
715
|
+
const insertFile = db.prepare(`
|
|
332
716
|
INSERT INTO session_file(
|
|
333
717
|
source, file_path, size, mtime_ms, session_id,
|
|
334
718
|
project_name, project_display_name, project_full_path,
|
|
335
719
|
first_message, git_branch, provider, model,
|
|
336
|
-
started_at, updated_at, message_count, usage_json, extra_json
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
session.projectFullPath || null,
|
|
347
|
-
session.firstMessage || null,
|
|
348
|
-
session.gitBranch || null,
|
|
349
|
-
session.provider || null,
|
|
350
|
-
session.model || null,
|
|
351
|
-
session.startedAt || null,
|
|
352
|
-
session.updatedAt || null,
|
|
353
|
-
messages.length,
|
|
354
|
-
session.usageJson || null,
|
|
355
|
-
session.extraJson || null
|
|
356
|
-
);
|
|
720
|
+
started_at, updated_at, message_count, usage_json, extra_json,
|
|
721
|
+
parser_version
|
|
722
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
723
|
+
`);
|
|
724
|
+
const insertMessage = db.prepare(`
|
|
725
|
+
INSERT INTO session_message(
|
|
726
|
+
source, session_id, ordinal, message_id, role, type, subtype,
|
|
727
|
+
content, timestamp, model, provider, user_message_number, extra_json
|
|
728
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
729
|
+
`);
|
|
357
730
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
731
|
+
db.exec('BEGIN IMMEDIATE');
|
|
732
|
+
try {
|
|
733
|
+
for (const filePath of indexedFiles.keys()) {
|
|
734
|
+
if (!activePaths.has(filePath)) deletePath.run(source, filePath);
|
|
735
|
+
}
|
|
736
|
+
db.exec('COMMIT');
|
|
737
|
+
} catch (error) {
|
|
738
|
+
try { db.exec('ROLLBACK'); } catch (_) {}
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
365
741
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
742
|
+
const recordParseError = (descriptor, error) => {
|
|
743
|
+
const detail = `${descriptor.filePath}: ${error.message}`;
|
|
744
|
+
errorMsg = errorMsg ? `${errorMsg}; ${detail}` : detail;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
for (let batchStart = 0; batchStart < toParse.length; batchStart += INVENTORY_PARSE_BATCH_SIZE) {
|
|
748
|
+
const batch = toParse.slice(batchStart, batchStart + INVENTORY_PARSE_BATCH_SIZE);
|
|
749
|
+
const parsed = new Array(batch.length);
|
|
750
|
+
let next = 0;
|
|
751
|
+
const parseWorker = async () => {
|
|
752
|
+
while (true) {
|
|
753
|
+
const index = next++;
|
|
754
|
+
if (index >= batch.length) return;
|
|
755
|
+
const descriptor = batch[index];
|
|
756
|
+
try {
|
|
757
|
+
parsed[index] = { descriptor, value: await parseDescriptor(descriptor) };
|
|
758
|
+
} catch (error) {
|
|
759
|
+
parsed[index] = { descriptor, error };
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
await Promise.all(Array.from({ length: Math.min(INVENTORY_PARSE_CONCURRENCY, batch.length) }, parseWorker));
|
|
764
|
+
|
|
765
|
+
for (const item of parsed) {
|
|
766
|
+
if (item?.error) recordParseError(item.descriptor, item.error);
|
|
384
767
|
}
|
|
768
|
+
|
|
769
|
+
db.exec('BEGIN IMMEDIATE');
|
|
770
|
+
try {
|
|
771
|
+
for (const item of parsed) {
|
|
772
|
+
if (item?.value) {
|
|
773
|
+
const { descriptor, parseResult } = item.value;
|
|
774
|
+
_insertSession({ deletePath, deleteSession, insertFile, insertMessage }, source, descriptor, parseResult.session, parseResult.messages, parserVersion);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
db.exec('COMMIT');
|
|
778
|
+
} catch (error) {
|
|
779
|
+
try { db.exec('ROLLBACK'); } catch (_) {}
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
// Drop the batch references before parsing the next group. This is
|
|
783
|
+
// the key difference from the old whole-inventory array: large
|
|
784
|
+
// session payloads no longer accumulate until the final COMMIT.
|
|
385
785
|
}
|
|
386
786
|
|
|
387
|
-
|
|
787
|
+
stateToRecord = {
|
|
788
|
+
lastInventoryMs: Date.now(),
|
|
789
|
+
lastError: errorMsg
|
|
790
|
+
};
|
|
388
791
|
} catch (err) {
|
|
389
|
-
|
|
792
|
+
errorMsg = err && err.message ? err.message : String(err);
|
|
793
|
+
stateToRecord = {
|
|
794
|
+
lastInventoryMs: null,
|
|
795
|
+
lastError: errorMsg
|
|
796
|
+
};
|
|
390
797
|
throw err;
|
|
798
|
+
} finally {
|
|
799
|
+
if (stateToRecord) {
|
|
800
|
+
try {
|
|
801
|
+
_recordSourceState(db, source, stateToRecord.lastInventoryMs, stateToRecord.lastError);
|
|
802
|
+
} catch (_err) {}
|
|
803
|
+
}
|
|
804
|
+
_releaseInventoryLock(inventoryLock);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
function _insertSession(statements, source, descriptor, session, messages, parserVersion) {
|
|
810
|
+
const { deletePath, deleteSession, insertFile, insertMessage } = statements;
|
|
811
|
+
deletePath.run(source, descriptor.filePath);
|
|
812
|
+
deleteSession.run(source, session.sessionId);
|
|
813
|
+
insertFile.run(
|
|
814
|
+
source,
|
|
815
|
+
descriptor.filePath,
|
|
816
|
+
descriptor.size,
|
|
817
|
+
descriptor.mtimeMs,
|
|
818
|
+
session.sessionId,
|
|
819
|
+
session.projectName || '',
|
|
820
|
+
session.projectDisplayName || null,
|
|
821
|
+
session.projectFullPath || null,
|
|
822
|
+
session.firstMessage || null,
|
|
823
|
+
session.gitBranch || null,
|
|
824
|
+
session.provider || null,
|
|
825
|
+
session.model || null,
|
|
826
|
+
session.startedAt || null,
|
|
827
|
+
session.updatedAt || null,
|
|
828
|
+
messages.length,
|
|
829
|
+
session.usageJson || null,
|
|
830
|
+
session.extraJson || null,
|
|
831
|
+
parserVersion
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
let ordinal = 0;
|
|
835
|
+
for (const msg of messages) {
|
|
836
|
+
insertMessage.run(
|
|
837
|
+
source,
|
|
838
|
+
session.sessionId,
|
|
839
|
+
ordinal,
|
|
840
|
+
msg.messageId || null,
|
|
841
|
+
msg.role || null,
|
|
842
|
+
msg.type || null,
|
|
843
|
+
msg.subtype || null,
|
|
844
|
+
msg.content || null,
|
|
845
|
+
msg.timestamp || null,
|
|
846
|
+
msg.model || null,
|
|
847
|
+
msg.provider || null,
|
|
848
|
+
msg.userMessageNumber != null ? msg.userMessageNumber : null,
|
|
849
|
+
msg.extraJson || null
|
|
850
|
+
);
|
|
851
|
+
ordinal++;
|
|
391
852
|
}
|
|
392
853
|
}
|
|
393
854
|
|
|
@@ -401,7 +862,12 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
401
862
|
* @returns {Promise<Array<{name, displayName, fullPath, path, sessionCount, lastUsed, latestSession, source}>>}
|
|
402
863
|
*/
|
|
403
864
|
async function listProjects(source, options = {}) {
|
|
404
|
-
await ensureSourceIndexed(source, {
|
|
865
|
+
await ensureSourceIndexed(source, {
|
|
866
|
+
force: options.force === true,
|
|
867
|
+
consistency: options.consistency || 'stale-ok',
|
|
868
|
+
allowStaleData: true,
|
|
869
|
+
config: options.config
|
|
870
|
+
});
|
|
405
871
|
const db = _getDb();
|
|
406
872
|
|
|
407
873
|
const rows = db.prepare(`
|
|
@@ -436,7 +902,11 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
436
902
|
* @returns {Promise<Array>}
|
|
437
903
|
*/
|
|
438
904
|
async function listSessions(source, projectName, options = {}) {
|
|
439
|
-
await ensureSourceIndexed(source, {
|
|
905
|
+
await ensureSourceIndexed(source, {
|
|
906
|
+
force: options.force === true,
|
|
907
|
+
consistency: options.consistency || 'stale-ok',
|
|
908
|
+
config: options.config
|
|
909
|
+
});
|
|
440
910
|
|
|
441
911
|
const db = _getDb();
|
|
442
912
|
const rows = db.prepare(`
|
|
@@ -445,25 +915,30 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
445
915
|
ORDER BY updated_at DESC
|
|
446
916
|
`).all(source, projectName);
|
|
447
917
|
|
|
448
|
-
return rows.map(r =>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
918
|
+
return rows.map(r => {
|
|
919
|
+
const extra = _safeParseObject(r.extra_json);
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
sessionId: r.session_id,
|
|
923
|
+
filePath: r.file_path,
|
|
924
|
+
size: r.size,
|
|
925
|
+
mtime: r.mtime_ms,
|
|
926
|
+
firstMessage: r.first_message,
|
|
927
|
+
gitBranch: r.git_branch,
|
|
928
|
+
provider: r.provider,
|
|
929
|
+
model: r.model,
|
|
930
|
+
messageCount: r.message_count,
|
|
931
|
+
tokens: _safeParseJson(r.usage_json, null),
|
|
932
|
+
extra,
|
|
933
|
+
source: r.source,
|
|
934
|
+
projectName: r.project_name,
|
|
935
|
+
projectHint: extra.projectHint || r.project_name,
|
|
936
|
+
projectDisplayName: r.project_display_name,
|
|
937
|
+
projectFullPath: r.project_full_path,
|
|
938
|
+
startedAt: r.started_at,
|
|
939
|
+
updatedAt: r.updated_at
|
|
940
|
+
};
|
|
941
|
+
});
|
|
467
942
|
}
|
|
468
943
|
|
|
469
944
|
/**
|
|
@@ -473,23 +948,13 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
473
948
|
* @returns {Promise<{sessionId, lastModified, size, filePath}|null>}
|
|
474
949
|
*/
|
|
475
950
|
async function getSessionStatus(source, sessionId, options = {}) {
|
|
476
|
-
await
|
|
951
|
+
await _ensureSessionCurrent(source, sessionId);
|
|
477
952
|
const db = _getDb();
|
|
478
953
|
const row = db.prepare(
|
|
479
954
|
'SELECT session_id, mtime_ms, size, file_path FROM session_file WHERE source = ? AND session_id = ?'
|
|
480
955
|
).get(source, sessionId);
|
|
481
|
-
|
|
482
956
|
if (!row) return null;
|
|
483
957
|
|
|
484
|
-
// Verify file still exists
|
|
485
|
-
try {
|
|
486
|
-
await fs.promises.stat(row.file_path);
|
|
487
|
-
} catch (_) {
|
|
488
|
-
// File gone - invalidate and return null
|
|
489
|
-
db.prepare('DELETE FROM session_file WHERE source = ? AND session_id = ?').run(source, sessionId);
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
958
|
return {
|
|
494
959
|
sessionId: row.session_id,
|
|
495
960
|
lastModified: row.mtime_ms,
|
|
@@ -575,7 +1040,7 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
575
1040
|
model: r.model,
|
|
576
1041
|
provider: r.provider,
|
|
577
1042
|
userMessageNumber: r.user_message_number,
|
|
578
|
-
extra:
|
|
1043
|
+
extra: _safeParseObject(r.extra_json)
|
|
579
1044
|
})),
|
|
580
1045
|
metadata: _buildMessageMetadata(sf),
|
|
581
1046
|
pagination: {
|
|
@@ -594,7 +1059,11 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
594
1059
|
* @returns {Promise<Array>}
|
|
595
1060
|
*/
|
|
596
1061
|
async function getRecentSessions(source, limit = 5, options = {}) {
|
|
597
|
-
await ensureSourceIndexed(source, {
|
|
1062
|
+
await ensureSourceIndexed(source, {
|
|
1063
|
+
force: options.force === true,
|
|
1064
|
+
consistency: options.consistency || 'stale-ok',
|
|
1065
|
+
config: options.config
|
|
1066
|
+
});
|
|
598
1067
|
const db = _getDb();
|
|
599
1068
|
|
|
600
1069
|
const rows = db.prepare(`
|
|
@@ -629,7 +1098,12 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
629
1098
|
* @returns {Promise<Array>}
|
|
630
1099
|
*/
|
|
631
1100
|
async function searchSessions(source, keyword, options = {}) {
|
|
632
|
-
|
|
1101
|
+
if (!String(keyword || '').trim()) return [];
|
|
1102
|
+
await ensureSourceIndexed(source, {
|
|
1103
|
+
force: options.force === true,
|
|
1104
|
+
consistency: options.consistency || 'complete',
|
|
1105
|
+
config: options.config
|
|
1106
|
+
});
|
|
633
1107
|
const db = _getDb();
|
|
634
1108
|
const contextLength = options.contextLength || 35;
|
|
635
1109
|
const projectName = options.projectName || null;
|
|
@@ -641,13 +1115,15 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
641
1115
|
const needle = String(keyword);
|
|
642
1116
|
if ([...needle].length >= 3) {
|
|
643
1117
|
const ftsRows = db.prepare(`
|
|
644
|
-
SELECT sm.source, sm.session_id, sm.ordinal, sm.content, sm.role, sm.type, sm.timestamp
|
|
1118
|
+
SELECT sm.source, sm.session_id, sm.ordinal, sm.content, sm.role, sm.type, sm.timestamp,
|
|
1119
|
+
sf.project_name, sf.project_display_name, sf.project_full_path,
|
|
1120
|
+
sf.file_path, sf.first_message, sf.updated_at
|
|
645
1121
|
FROM session_message_fts fts
|
|
646
1122
|
JOIN session_message sm ON sm.rowid = fts.rowid
|
|
647
1123
|
JOIN session_file sf ON sf.source = sm.source AND sf.session_id = sm.session_id
|
|
648
1124
|
WHERE sm.source = ? AND session_message_fts MATCH ?
|
|
649
1125
|
${projectName ? 'AND sf.project_name = ?' : ''}
|
|
650
|
-
ORDER BY sf.updated_at DESC
|
|
1126
|
+
ORDER BY sf.updated_at DESC, sm.ordinal ASC, sm.rowid ASC
|
|
651
1127
|
LIMIT 500
|
|
652
1128
|
`);
|
|
653
1129
|
const params = [source, _ftsQuote(needle)];
|
|
@@ -680,20 +1156,26 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
680
1156
|
idx += lowerKeyword.length;
|
|
681
1157
|
}
|
|
682
1158
|
if (count === 0) continue;
|
|
683
|
-
|
|
684
|
-
if (!
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1159
|
+
let entry = matchMap.get(c.session_id);
|
|
1160
|
+
if (!entry) {
|
|
1161
|
+
entry = {
|
|
1162
|
+
session: {
|
|
1163
|
+
source: c.source,
|
|
1164
|
+
session_id: c.session_id,
|
|
1165
|
+
project_name: c.project_name,
|
|
1166
|
+
project_display_name: c.project_display_name,
|
|
1167
|
+
project_full_path: c.project_full_path,
|
|
1168
|
+
file_path: c.file_path,
|
|
1169
|
+
first_message: c.first_message,
|
|
1170
|
+
updated_at: c.updated_at
|
|
1171
|
+
},
|
|
689
1172
|
matchCount: 0,
|
|
690
1173
|
messages: []
|
|
691
|
-
}
|
|
1174
|
+
};
|
|
1175
|
+
matchMap.set(c.session_id, entry);
|
|
692
1176
|
}
|
|
693
1177
|
|
|
694
|
-
|
|
695
|
-
// Claude: count every occurrence; Codex/Gemini/OMP: at most 1 per message
|
|
696
|
-
entry.matchCount += (source === 'claude') ? count : 1;
|
|
1178
|
+
entry.matchCount += source === 'claude' ? count : 1;
|
|
697
1179
|
entry.messages.push({
|
|
698
1180
|
ordinal: c.ordinal,
|
|
699
1181
|
content,
|
|
@@ -724,6 +1206,7 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
724
1206
|
|
|
725
1207
|
// Sort: matchCount DESC, then updated_at DESC
|
|
726
1208
|
results.sort((a, b) => {
|
|
1209
|
+
|
|
727
1210
|
const cm = b.matchCount - a.matchCount;
|
|
728
1211
|
if (cm !== 0) return cm;
|
|
729
1212
|
const aRow = matchMap.get(a.sessionId);
|
|
@@ -737,20 +1220,50 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
737
1220
|
}
|
|
738
1221
|
|
|
739
1222
|
function _scanMessagesRelational(db, source, keyword, projectName = null) {
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1223
|
+
const useSqlMatch = /^[\x00-\x7F]*$/.test(String(keyword));
|
|
1224
|
+
const select = `
|
|
1225
|
+
SELECT sm.source, sm.session_id, sm.ordinal, sm.content, sm.role, sm.type, sm.timestamp,
|
|
1226
|
+
sf.project_name, sf.project_display_name, sf.project_full_path,
|
|
1227
|
+
sf.file_path, sf.first_message, sf.updated_at
|
|
744
1228
|
FROM session_message sm
|
|
745
1229
|
JOIN session_file sf ON sf.source = sm.source AND sf.session_id = sm.session_id
|
|
746
|
-
WHERE sm.source = ?
|
|
1230
|
+
WHERE sm.source = ?
|
|
747
1231
|
${projectName ? 'AND sf.project_name = ?' : ''}
|
|
748
|
-
ORDER BY sf.updated_at DESC
|
|
749
|
-
LIMIT 500
|
|
1232
|
+
ORDER BY sf.updated_at DESC, sm.ordinal ASC, sm.rowid ASC
|
|
750
1233
|
`;
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
|
|
1234
|
+
|
|
1235
|
+
if (useSqlMatch) {
|
|
1236
|
+
const sql = `${select.replace('WHERE sm.source = ?', 'WHERE sm.source = ? AND instr(lower(sm.content), lower(?)) > 0')}\nLIMIT 500`;
|
|
1237
|
+
const params = [source, keyword];
|
|
1238
|
+
if (projectName) params.push(projectName);
|
|
1239
|
+
return db.prepare(sql).all(...params);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// SQLite's lower() is ASCII-oriented. Page the broad joined query and
|
|
1243
|
+
// apply the same locale-aware JavaScript matcher used by searchSessions,
|
|
1244
|
+
// collecting at most the 500 matching candidates (not 500 raw rows).
|
|
1245
|
+
const candidates = [];
|
|
1246
|
+
const pageSize = 500;
|
|
1247
|
+
let offset = 0;
|
|
1248
|
+
while (candidates.length < pageSize) {
|
|
1249
|
+
const params = [source];
|
|
1250
|
+
if (projectName) params.push(projectName);
|
|
1251
|
+
params.push(pageSize, offset);
|
|
1252
|
+
const batch = db.prepare(`${select}\nLIMIT ? OFFSET ?`).all(...params);
|
|
1253
|
+
if (batch.length === 0) break;
|
|
1254
|
+
for (const row of batch) {
|
|
1255
|
+
if (_containsLocaleMatch(row.content, keyword)) candidates.push(row);
|
|
1256
|
+
if (candidates.length >= pageSize) break;
|
|
1257
|
+
}
|
|
1258
|
+
if (batch.length < pageSize) break;
|
|
1259
|
+
offset += batch.length;
|
|
1260
|
+
}
|
|
1261
|
+
return candidates;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function _containsLocaleMatch(content, keyword) {
|
|
1265
|
+
const lowerKeyword = String(keyword).toLocaleLowerCase();
|
|
1266
|
+
return String(content || '').toLocaleLowerCase().indexOf(lowerKeyword) !== -1;
|
|
754
1267
|
}
|
|
755
1268
|
|
|
756
1269
|
function _extractContext(content, position, contextLength, keywordLength) {
|
|
@@ -778,8 +1291,8 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
778
1291
|
provider: sf.provider,
|
|
779
1292
|
model: sf.model,
|
|
780
1293
|
messageCount: sf.message_count,
|
|
781
|
-
usage:
|
|
782
|
-
extra:
|
|
1294
|
+
usage: _safeParseObject(sf.usage_json),
|
|
1295
|
+
extra: _safeParseObject(sf.extra_json)
|
|
783
1296
|
};
|
|
784
1297
|
}
|
|
785
1298
|
|
|
@@ -794,17 +1307,41 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
794
1307
|
).get(source, sessionId);
|
|
795
1308
|
if (!row) return;
|
|
796
1309
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1310
|
+
const fileKey = `${source}:${row.file_path}`;
|
|
1311
|
+
const now = Date.now();
|
|
1312
|
+
const cached = fileVersions.get(fileKey);
|
|
1313
|
+
if (cached && now - cached.checkedAt < INDEX_INVENTORY_TTL_MS) return;
|
|
1314
|
+
|
|
1315
|
+
const activeCheck = fileChecks.get(fileKey);
|
|
1316
|
+
if (activeCheck) {
|
|
1317
|
+
await activeCheck;
|
|
802
1318
|
return;
|
|
803
1319
|
}
|
|
804
1320
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1321
|
+
const check = (async () => {
|
|
1322
|
+
let currentStat;
|
|
1323
|
+
try {
|
|
1324
|
+
currentStat = await fs.promises.stat(row.file_path);
|
|
1325
|
+
} catch (_) {
|
|
1326
|
+
fileVersions.set(fileKey, { size: -1, mtimeMs: -1, checkedAt: Date.now(), missing: true });
|
|
1327
|
+
db.prepare('DELETE FROM session_file WHERE source = ? AND file_path = ?').run(source, row.file_path);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
fileVersions.set(fileKey, {
|
|
1332
|
+
size: currentStat.size,
|
|
1333
|
+
mtimeMs: currentStat.mtimeMs,
|
|
1334
|
+
checkedAt: Date.now()
|
|
1335
|
+
});
|
|
1336
|
+
if (currentStat.size !== row.size || currentStat.mtimeMs !== row.mtime_ms) {
|
|
1337
|
+
await ensureSourceIndexed(source, { force: true, consistency: 'complete' });
|
|
1338
|
+
}
|
|
1339
|
+
})();
|
|
1340
|
+
fileChecks.set(fileKey, check);
|
|
1341
|
+
try {
|
|
1342
|
+
await check;
|
|
1343
|
+
} finally {
|
|
1344
|
+
if (fileChecks.get(fileKey) === check) fileChecks.delete(fileKey);
|
|
808
1345
|
}
|
|
809
1346
|
}
|
|
810
1347
|
|
|
@@ -819,16 +1356,38 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
819
1356
|
if (options.deleted && options.sessionId) {
|
|
820
1357
|
db.prepare('DELETE FROM session_file WHERE source = ? AND session_id = ?').run(source, options.sessionId);
|
|
821
1358
|
}
|
|
1359
|
+
if (options.projectName) {
|
|
1360
|
+
db.prepare('DELETE FROM session_file WHERE source = ? AND project_name = ?').run(source, options.projectName);
|
|
1361
|
+
}
|
|
822
1362
|
|
|
823
1363
|
// Mark state as stale
|
|
824
1364
|
db.prepare(
|
|
825
1365
|
'UPDATE source_state SET last_inventory_ms = NULL WHERE source = ?'
|
|
826
1366
|
).run(source);
|
|
1367
|
+
for (const key of fileVersions.keys()) {
|
|
1368
|
+
if (key.startsWith(`${source}:`)) fileVersions.delete(key);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
function getSourceIndexMeta(source) {
|
|
1372
|
+
const row = _getDb().prepare(
|
|
1373
|
+
'SELECT last_inventory_ms, last_error FROM source_state WHERE source = ?'
|
|
1374
|
+
).get(source);
|
|
1375
|
+
const generatedAt = row?.last_inventory_ms ? Number(row.last_inventory_ms) : null;
|
|
1376
|
+
const stale = !generatedAt || Date.now() - generatedAt >= INDEX_INVENTORY_TTL_MS;
|
|
1377
|
+
const refreshing = _inflight.has(`ensure:${source}`);
|
|
1378
|
+
return {
|
|
1379
|
+
generatedAt,
|
|
1380
|
+
stale,
|
|
1381
|
+
refreshing,
|
|
1382
|
+
fallback: Boolean(row?.last_error && !_hasUsableIndexedData(source)),
|
|
1383
|
+
error: row?.last_error || null
|
|
1384
|
+
};
|
|
827
1385
|
}
|
|
828
|
-
|
|
829
1386
|
function closeSessionHistoryIndex() {
|
|
830
1387
|
closeDatabase(dbPath);
|
|
831
1388
|
_db = null;
|
|
1389
|
+
fileVersions.clear();
|
|
1390
|
+
fileChecks.clear();
|
|
832
1391
|
}
|
|
833
1392
|
|
|
834
1393
|
// Build the API object
|
|
@@ -842,6 +1401,7 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
842
1401
|
getRecentSessions,
|
|
843
1402
|
searchSessions,
|
|
844
1403
|
invalidateSource,
|
|
1404
|
+
getSourceIndexMeta,
|
|
845
1405
|
closeSessionHistoryIndex,
|
|
846
1406
|
};
|
|
847
1407
|
|
|
@@ -858,9 +1418,11 @@ function createSessionHistoryIndex(opts = {}) {
|
|
|
858
1418
|
|
|
859
1419
|
let _defaultIndex = null;
|
|
860
1420
|
|
|
861
|
-
function _getDefaultIndex() {
|
|
1421
|
+
function _getDefaultIndex(options = {}) {
|
|
862
1422
|
if (!_defaultIndex) {
|
|
863
|
-
_defaultIndex =
|
|
1423
|
+
_defaultIndex = Object.keys(options).length > 0
|
|
1424
|
+
? createSessionHistoryIndex({ config: options })
|
|
1425
|
+
: createSessionHistoryIndex();
|
|
864
1426
|
}
|
|
865
1427
|
return _defaultIndex;
|
|
866
1428
|
}
|
|
@@ -870,7 +1432,7 @@ async function ensureSourceIndexed(source, options) {
|
|
|
870
1432
|
}
|
|
871
1433
|
|
|
872
1434
|
async function listProjects(source, options) {
|
|
873
|
-
return _getDefaultIndex().listProjects(source, options);
|
|
1435
|
+
return _getDefaultIndex(options).listProjects(source, options);
|
|
874
1436
|
}
|
|
875
1437
|
|
|
876
1438
|
async function listSessions(source, projectName, options) {
|
|
@@ -897,6 +1459,10 @@ async function searchSessions(source, keyword, options) {
|
|
|
897
1459
|
return _getDefaultIndex().searchSessions(source, keyword, options);
|
|
898
1460
|
}
|
|
899
1461
|
|
|
1462
|
+
function getSourceIndexMeta(source) {
|
|
1463
|
+
return _getDefaultIndex().getSourceIndexMeta(source);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
900
1466
|
function invalidateSource(source, options) {
|
|
901
1467
|
_getDefaultIndex().invalidateSource(source, options);
|
|
902
1468
|
}
|
|
@@ -926,5 +1492,6 @@ module.exports = {
|
|
|
926
1492
|
getRecentSessions,
|
|
927
1493
|
searchSessions,
|
|
928
1494
|
invalidateSource,
|
|
929
|
-
|
|
1495
|
+
getSourceIndexMeta,
|
|
1496
|
+
closeSessionHistoryIndex,
|
|
930
1497
|
};
|