@winspan/claude-forge 8.50.6 → 8.51.1
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/CLAUDE.md +7 -7
- package/dist/claudemd/claudemd-generator.d.ts.map +1 -1
- package/dist/claudemd/claudemd-generator.js +27 -237
- package/dist/claudemd/claudemd-generator.js.map +1 -1
- package/dist/claudemd/resume-manager.js +1 -1
- package/dist/claudemd/resume-manager.js.map +1 -1
- package/dist/claudemd/templates/swarm-protocol.md +222 -0
- package/dist/cli/commands/daemon.js +6 -6
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/executions.d.ts.map +1 -1
- package/dist/cli/commands/executions.js +4 -3
- package/dist/cli/commands/executions.js.map +1 -1
- package/dist/cli/commands/init.js +2 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +3 -5
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/menu.d.ts.map +1 -1
- package/dist/cli/commands/menu.js +4 -3
- package/dist/cli/commands/menu.js.map +1 -1
- package/dist/cli/commands/stats.d.ts.map +1 -1
- package/dist/cli/commands/stats.js +2 -3
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +11 -23
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/init/hook-manager.d.ts.map +1 -1
- package/dist/cli/init/hook-manager.js +2 -2
- package/dist/cli/init/hook-manager.js.map +1 -1
- package/dist/core/ai/provider.js +2 -2
- package/dist/core/ai/provider.js.map +1 -1
- package/dist/core/constants.d.ts +12 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +15 -1
- package/dist/core/constants.js.map +1 -1
- package/dist/core/event-fields.d.ts +16 -0
- package/dist/core/event-fields.d.ts.map +1 -0
- package/dist/core/event-fields.js +19 -0
- package/dist/core/event-fields.js.map +1 -0
- package/dist/core/queue/index.d.ts.map +1 -1
- package/dist/core/queue/index.js +3 -4
- package/dist/core/queue/index.js.map +1 -1
- package/dist/core/storage/base.d.ts +36 -3
- package/dist/core/storage/base.d.ts.map +1 -1
- package/dist/core/storage/base.js +101 -58
- package/dist/core/storage/base.js.map +1 -1
- package/dist/core/storage/events.d.ts +92 -3
- package/dist/core/storage/events.d.ts.map +1 -1
- package/dist/core/storage/events.js +147 -0
- package/dist/core/storage/events.js.map +1 -1
- package/dist/core/storage/routing.d.ts +54 -1
- package/dist/core/storage/routing.d.ts.map +1 -1
- package/dist/core/storage/routing.js +99 -1
- package/dist/core/storage/routing.js.map +1 -1
- package/dist/core/storage/schema.sql +12 -2
- package/dist/core/storage/sessions.d.ts +20 -0
- package/dist/core/storage/sessions.d.ts.map +1 -1
- package/dist/core/storage/sessions.js +59 -0
- package/dist/core/storage/sessions.js.map +1 -1
- package/dist/core/storage/skills.d.ts +23 -0
- package/dist/core/storage/skills.d.ts.map +1 -1
- package/dist/core/storage/skills.js +47 -0
- package/dist/core/storage/skills.js.map +1 -1
- package/dist/core/storage/sqlite.d.ts +35 -2
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +93 -4
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/core/storage/tasks.d.ts +49 -0
- package/dist/core/storage/tasks.d.ts.map +1 -1
- package/dist/core/storage/tasks.js +143 -1
- package/dist/core/storage/tasks.js.map +1 -1
- package/dist/core/storage/token-usage.d.ts +1 -1
- package/dist/core/storage/token-usage.d.ts.map +1 -1
- package/dist/core/storage/token-usage.js +1 -1
- package/dist/core/storage/token-usage.js.map +1 -1
- package/dist/core/types.d.ts +24 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/utils/error-handler.d.ts.map +1 -1
- package/dist/core/utils/error-handler.js +3 -2
- package/dist/core/utils/error-handler.js.map +1 -1
- package/dist/core/utils/git.d.ts +10 -0
- package/dist/core/utils/git.d.ts.map +1 -0
- package/dist/core/utils/git.js +24 -0
- package/dist/core/utils/git.js.map +1 -0
- package/dist/core/utils/logger.d.ts.map +1 -1
- package/dist/core/utils/logger.js +15 -1
- package/dist/core/utils/logger.js.map +1 -1
- package/dist/core/utils/lru-cache.d.ts +1 -0
- package/dist/core/utils/lru-cache.d.ts.map +1 -1
- package/dist/core/utils/lru-cache.js +3 -0
- package/dist/core/utils/lru-cache.js.map +1 -1
- package/dist/core/utils/token-tracker.js +1 -1
- package/dist/core/utils/token-tracker.js.map +1 -1
- package/dist/daemon/event-parser.d.ts.map +1 -1
- package/dist/daemon/event-parser.js +2 -1
- package/dist/daemon/event-parser.js.map +1 -1
- package/dist/daemon/handlers/history-exporter.js.map +1 -1
- package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
- package/dist/daemon/handlers/post-tool-use.js +7 -3
- package/dist/daemon/handlers/post-tool-use.js.map +1 -1
- package/dist/daemon/handlers/stop.d.ts +4 -0
- package/dist/daemon/handlers/stop.d.ts.map +1 -1
- package/dist/daemon/handlers/stop.js +23 -35
- package/dist/daemon/handlers/stop.js.map +1 -1
- package/dist/daemon/handlers/user-prompt.d.ts +3 -3
- package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.js +12 -22
- package/dist/daemon/handlers/user-prompt.js.map +1 -1
- package/dist/daemon/hook-sync.d.ts +17 -0
- package/dist/daemon/hook-sync.d.ts.map +1 -0
- package/dist/daemon/hook-sync.js +74 -0
- package/dist/daemon/hook-sync.js.map +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +33 -9
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/lifecycle.js +3 -4
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/server.d.ts +6 -4
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +76 -85
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/services/task-segmenter.js +1 -1
- package/dist/daemon/services/task-segmenter.js.map +1 -1
- package/dist/hooks/hook-lib.sh +37 -0
- package/dist/hooks/notification.sh +2 -2
- package/dist/hooks/post-tool-use.sh +2 -2
- package/dist/hooks/pre-tool-use.sh +2 -2
- package/dist/hooks/stop.sh +9 -6
- package/dist/hooks/user-prompt-submit.sh +2 -2
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.d.ts +3 -4
- package/dist/web/analytics/anti-pattern-detector.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js +7 -46
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js.map +1 -1
- package/dist/web/analytics/drift-detector.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/drift-detector.js +10 -13
- package/dist/web/analytics/drift-detector.js.map +1 -0
- package/dist/web/analytics/weekly-report.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/weekly-report.js +51 -50
- package/dist/web/analytics/weekly-report.js.map +1 -0
- package/dist/web/auth-middleware.d.ts.map +1 -1
- package/dist/web/auth-middleware.js +1 -2
- package/dist/web/auth-middleware.js.map +1 -1
- package/dist/web/routes/_helpers.d.ts +16 -0
- package/dist/web/routes/_helpers.d.ts.map +1 -0
- package/dist/web/routes/_helpers.js +32 -0
- package/dist/web/routes/_helpers.js.map +1 -0
- package/dist/web/routes/drift.js +1 -1
- package/dist/web/routes/drift.js.map +1 -1
- package/dist/web/routes/insights.js +1 -1
- package/dist/web/routes/insights.js.map +1 -1
- package/dist/web/routes/reports.js +1 -1
- package/dist/web/routes/reports.js.map +1 -1
- package/dist/web/routes/rules.d.ts +3 -0
- package/dist/web/routes/rules.d.ts.map +1 -1
- package/dist/web/routes/rules.js +28 -52
- package/dist/web/routes/rules.js.map +1 -1
- package/dist/web/routes/sessions.d.ts.map +1 -1
- package/dist/web/routes/sessions.js +16 -30
- package/dist/web/routes/sessions.js.map +1 -1
- package/dist/web/routes/skill-stats.d.ts +2 -0
- package/dist/web/routes/skill-stats.d.ts.map +1 -1
- package/dist/web/routes/skill-stats.js +28 -64
- package/dist/web/routes/skill-stats.js.map +1 -1
- package/dist/web/routes/skills.d.ts.map +1 -1
- package/dist/web/routes/skills.js +5 -4
- package/dist/web/routes/skills.js.map +1 -1
- package/dist/web/routes/stats.d.ts +4 -0
- package/dist/web/routes/stats.d.ts.map +1 -1
- package/dist/web/routes/stats.js +19 -21
- package/dist/web/routes/stats.js.map +1 -1
- package/dist/web/routes/tasks.d.ts.map +1 -1
- package/dist/web/routes/tasks.js +17 -42
- package/dist/web/routes/tasks.js.map +1 -1
- package/dist/web/routes/trace.d.ts.map +1 -1
- package/dist/web/routes/trace.js +7 -17
- package/dist/web/routes/trace.js.map +1 -1
- package/dist/web/routes/types.d.ts.map +1 -1
- package/dist/web/routes/types.js +4 -3
- package/dist/web/routes/types.js.map +1 -1
- package/dist/web/static/assets/{AIConfig-BQCAQE9D.js → AIConfig-CdDWzJyO.js} +2 -2
- package/dist/web/static/assets/{AIConfig-BQCAQE9D.js.map → AIConfig-CdDWzJyO.js.map} +1 -1
- package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js → Dashboard-CoEmmIDt.js} +2 -2
- package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js.map → Dashboard-CoEmmIDt.js.map} +1 -1
- package/dist/web/static/assets/{Drawer-BeHRQxUS.js → Drawer-DdRTzlLB.js} +2 -2
- package/dist/web/static/assets/{Drawer-BeHRQxUS.js.map → Drawer-DdRTzlLB.js.map} +1 -1
- package/dist/web/static/assets/{Events-K_tCY2ti.js → Events-DrIq1SUS.js} +2 -2
- package/dist/web/static/assets/{Events-K_tCY2ti.js.map → Events-DrIq1SUS.js.map} +1 -1
- package/dist/web/static/assets/{Reports-BJCmBnc_.js → Reports-DFBM3MDK.js} +2 -2
- package/dist/web/static/assets/{Reports-BJCmBnc_.js.map → Reports-DFBM3MDK.js.map} +1 -1
- package/dist/web/static/assets/{SearchInput-BX2KhMkw.js → SearchInput-qCj_jAcf.js} +2 -2
- package/dist/web/static/assets/{SearchInput-BX2KhMkw.js.map → SearchInput-qCj_jAcf.js.map} +1 -1
- package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js → SessionDetail-CCzwdoT7.js} +2 -2
- package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js.map → SessionDetail-CCzwdoT7.js.map} +1 -1
- package/dist/web/static/assets/{Sessions-Chx9OCLH.js → Sessions-FfLYkAw9.js} +2 -2
- package/dist/web/static/assets/{Sessions-Chx9OCLH.js.map → Sessions-FfLYkAw9.js.map} +1 -1
- package/dist/web/static/assets/{Skills-O0GT1i7m.js → Skills-C8Gvs3Qa.js} +2 -2
- package/dist/web/static/assets/{Skills-O0GT1i7m.js.map → Skills-C8Gvs3Qa.js.map} +1 -1
- package/dist/web/static/assets/TaskDetail-BS8pYhaR.js +2 -0
- package/dist/web/static/assets/TaskDetail-BS8pYhaR.js.map +1 -0
- package/dist/web/static/assets/Tasks-CyuhizG8.js +2 -0
- package/dist/web/static/assets/Tasks-CyuhizG8.js.map +1 -0
- package/dist/web/static/assets/index-CBX47X8l.js +3 -0
- package/dist/web/static/assets/{index-DxIbmNmr.js.map → index-CBX47X8l.js.map} +1 -1
- package/dist/web/static/assets/index-DjIoMdoR.css +1 -0
- package/dist/web/static/assets/{lucide-fJlPI3H7.js → lucide-Bs_edTLa.js} +44 -39
- package/dist/web/static/assets/lucide-Bs_edTLa.js.map +1 -0
- package/dist/web/static/assets/react-router-r79dBVy4.js +20 -0
- package/dist/web/static/assets/{react-router-I-HqunH7.js.map → react-router-r79dBVy4.js.map} +1 -1
- package/dist/web/static/assets/task-title-BhOcemuR.js +2 -0
- package/dist/web/static/assets/task-title-BhOcemuR.js.map +1 -0
- package/dist/web/static/index.html +4 -4
- package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +299 -0
- package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +191 -0
- package/docs/design/h3-fallback-removal-spec-20260518-1245.md +76 -0
- package/docs/design/h4-index-dedup-spec-20260518-1230.md +109 -0
- package/docs/design/h6-services-migration-spec-20260518-1355.md +82 -0
- package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +106 -0
- package/docs/design/m10-forge-paths-spec-20260518-1320.md +121 -0
- package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +131 -0
- package/docs/design/m7-routing-event-association-spec-20260518-1545.md +103 -0
- package/docs/design/project-path-gitroot-spec-20260518-1715.md +134 -0
- package/docs/design/task-active-gc-spec-20260518-1745.md +146 -0
- package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +82 -0
- package/docs/implementation/h2-final-changelog-20260518-1530.md +61 -0
- package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +70 -0
- package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +120 -0
- package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +71 -0
- package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +71 -0
- package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +60 -0
- package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +46 -0
- package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +46 -0
- package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +45 -0
- package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +63 -0
- package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +38 -0
- package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +58 -0
- package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +60 -0
- package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +43 -0
- package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +56 -0
- package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +69 -0
- package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +63 -0
- package/docs/implementation/task-active-gc-changelog-20260518-1745.md +35 -0
- package/docs/implementation/task-title-summary-changelog-20260518-1130.md +39 -0
- package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +22 -0
- package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +56 -0
- package/docs/reviews/task-title-summary.md +92 -0
- package/docs/reviews/tasks-detail-back-loses-filters.md +58 -0
- package/docs/reviews/tasks-page-white-screen-hotfix.md +126 -0
- package/package.json +2 -2
- package/src/claudemd/claudemd-generator.ts +29 -238
- package/src/claudemd/resume-manager.ts +1 -1
- package/src/claudemd/templates/swarm-protocol.md +222 -0
- package/src/cli/commands/daemon.ts +6 -6
- package/src/cli/commands/executions.ts +4 -3
- package/src/cli/commands/init.ts +2 -2
- package/src/cli/commands/logs.ts +1 -1
- package/src/cli/commands/mcp.ts +3 -5
- package/src/cli/commands/menu.ts +4 -3
- package/src/cli/commands/stats.ts +2 -3
- package/src/cli/commands/status.ts +2 -2
- package/src/cli/commands/trace.ts +10 -26
- package/src/cli/init/hook-manager.ts +2 -2
- package/src/core/ai/provider.ts +2 -2
- package/src/core/constants.ts +18 -1
- package/src/core/event-fields.ts +32 -0
- package/src/core/queue/index.ts +3 -4
- package/src/core/storage/base.ts +132 -56
- package/src/core/storage/events.ts +183 -4
- package/src/core/storage/routing.ts +129 -1
- package/src/core/storage/schema.sql +12 -2
- package/src/core/storage/sessions.ts +64 -0
- package/src/core/storage/skills.ts +69 -0
- package/src/core/storage/sqlite.ts +103 -4
- package/src/core/storage/tasks.ts +149 -1
- package/src/core/storage/token-usage.ts +1 -1
- package/src/core/types.ts +30 -3
- package/src/core/utils/error-handler.ts +3 -2
- package/src/core/utils/git.ts +23 -0
- package/src/core/utils/logger.ts +16 -1
- package/src/core/utils/lru-cache.ts +4 -0
- package/src/core/utils/token-tracker.ts +1 -1
- package/src/daemon/event-parser.ts +4 -3
- package/src/daemon/handlers/history-exporter.ts +1 -1
- package/src/daemon/handlers/post-tool-use.ts +7 -3
- package/src/daemon/handlers/stop.ts +32 -39
- package/src/daemon/handlers/user-prompt.ts +12 -22
- package/src/daemon/hook-sync.ts +91 -0
- package/src/daemon/index.ts +34 -10
- package/src/daemon/lifecycle.ts +3 -3
- package/src/daemon/server.ts +76 -89
- package/src/daemon/services/task-segmenter.ts +1 -1
- package/src/hooks/hook-lib.sh +37 -0
- package/src/hooks/notification.sh +2 -2
- package/src/hooks/post-tool-use.sh +2 -2
- package/src/hooks/pre-tool-use.sh +2 -2
- package/src/hooks/stop.sh +9 -6
- package/src/hooks/user-prompt-submit.sh +2 -2
- package/src/{daemon/services → web/analytics}/anti-pattern-detector.ts +9 -54
- package/src/{daemon/services → web/analytics}/drift-detector.ts +10 -23
- package/src/{daemon/services → web/analytics}/weekly-report.ts +52 -75
- package/src/web/auth-middleware.ts +1 -2
- package/src/web/routes/_helpers.ts +34 -0
- package/src/web/routes/drift.ts +1 -1
- package/src/web/routes/insights.ts +1 -1
- package/src/web/routes/reports.ts +1 -1
- package/src/web/routes/rules.ts +31 -56
- package/src/web/routes/sessions.ts +18 -30
- package/src/web/routes/skill-stats.ts +29 -69
- package/src/web/routes/skills.ts +5 -4
- package/src/web/routes/stats.ts +19 -29
- package/src/web/routes/tasks.ts +17 -42
- package/src/web/routes/trace.ts +7 -19
- package/src/web/routes/types.ts +4 -3
- package/tests/integration/claudemd-generator.test.ts +90 -0
- package/tests/integration/web-analytics.integration.test.ts +133 -0
- package/tests/integration/web-stats.integration.test.ts +135 -0
- package/tests/integration/web-trace.integration.test.ts +175 -0
- package/tests/unit/core/forge-paths.test.ts +99 -0
- package/tests/unit/daemon/hook-sync.test.ts +71 -0
- package/tests/unit/daemon/post-tool-use.test.ts +121 -0
- package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +202 -0
- package/tests/unit/daemon/task-segmenter-recover.test.ts +84 -0
- package/tests/unit/event-fields.test.ts +88 -0
- package/tests/unit/event-parser.test.ts +55 -0
- package/tests/unit/hooks/resolve-project-path.test.ts +122 -0
- package/tests/unit/socket-server.test.ts +183 -0
- package/tests/unit/storage/event-operations-aggregates.test.ts +342 -0
- package/tests/unit/storage/migration-idempotent.test.ts +304 -0
- package/tests/unit/storage/routing-aggregates.test.ts +276 -0
- package/tests/unit/storage/routing.test.ts +117 -0
- package/tests/unit/storage/schema-missing.test.ts +81 -0
- package/tests/unit/storage/session-operations-aggregates.test.ts +120 -0
- package/tests/unit/storage/skill-operations-counts.test.ts +106 -0
- package/tests/unit/storage/skills-aggregates.test.ts +104 -0
- package/tests/unit/storage/sqlite-refactor-harness.test.ts +3 -3
- package/tests/unit/storage/task-operations-counts.test.ts +46 -0
- package/tests/unit/storage/tasks-getById.test.ts +343 -0
- package/tests/unit/storage/tasks-stale-gc.test.ts +86 -0
- package/tests/unit/token-usage.test.ts +6 -6
- package/tests/unit/web/navigation-back-contract.test.ts +134 -0
- package/tests/unit/web/routes-rules.test.ts +182 -0
- package/tests/unit/web/routes-tasks.test.ts +34 -0
- package/tests/unit/web/task-title-contract.test.ts +210 -0
- package/tests/unit/web/tasks-component-contract.test.ts +179 -0
- package/vitest.config.ts +1 -1
- package/web/src/pages/TaskDetail.tsx +9 -5
- package/web/src/pages/Tasks.tsx +315 -50
- package/web/src/utils/navigation.ts +25 -0
- package/web/src/utils/task-title.ts +49 -0
- package/dist/daemon/services/anti-pattern-detector.d.ts.map +0 -1
- package/dist/daemon/services/drift-detector.d.ts.map +0 -1
- package/dist/daemon/services/drift-detector.js.map +0 -1
- package/dist/daemon/services/weekly-report.d.ts.map +0 -1
- package/dist/daemon/services/weekly-report.js.map +0 -1
- package/dist/web/static/assets/TaskDetail-5SR8zGzv.js +0 -2
- package/dist/web/static/assets/TaskDetail-5SR8zGzv.js.map +0 -1
- package/dist/web/static/assets/Tasks-DCgDqvOZ.js +0 -2
- package/dist/web/static/assets/Tasks-DCgDqvOZ.js.map +0 -1
- package/dist/web/static/assets/index-D8AKj26b.css +0 -1
- package/dist/web/static/assets/index-DxIbmNmr.js +0 -3
- package/dist/web/static/assets/lucide-fJlPI3H7.js.map +0 -1
- package/dist/web/static/assets/react-router-I-HqunH7.js +0 -20
- /package/dist/{daemon/services → web/analytics}/drift-detector.d.ts +0 -0
- /package/dist/{daemon/services → web/analytics}/weekly-report.d.ts +0 -0
|
@@ -16,10 +16,6 @@ import { z } from 'zod';
|
|
|
16
16
|
import type { ForgeEvent } from '../types.js';
|
|
17
17
|
import { logger } from '../utils/logger.js';
|
|
18
18
|
|
|
19
|
-
interface UpsertCapableEmitter extends EventEmitter {
|
|
20
|
-
upsertSession(event: ForgeEvent): void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
// Zod schema for runtime validation
|
|
24
20
|
const ForgeEventSchema = z.object({
|
|
25
21
|
event_id: z.string().uuid().optional(),
|
|
@@ -218,6 +214,189 @@ export class EventOperations {
|
|
|
218
214
|
return rows.map((row) => this.rowToEvent(row));
|
|
219
215
|
}
|
|
220
216
|
|
|
217
|
+
// ── H2: 聚合 / 计数方法(消除 routes / handlers / analytics 越权 SQL)─────
|
|
218
|
+
|
|
219
|
+
/** Total event row count. */
|
|
220
|
+
countAllEvents(): number {
|
|
221
|
+
const row = this.db.prepare('SELECT COUNT(*) as cnt FROM events').get() as { cnt: number };
|
|
222
|
+
return row.cnt;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Tool usage distribution (tool_name → count).
|
|
227
|
+
*
|
|
228
|
+
* - `since`: ISO timestamp, inclusive lower bound on `timestamp`. Omit for all-time.
|
|
229
|
+
* - `hook_type`: filter on hook_type (e.g. 'PreToolUse') — used by weekly-report.
|
|
230
|
+
* - `limit`: max distinct tools returned (default 100).
|
|
231
|
+
*/
|
|
232
|
+
aggregateToolUsage(opts: {
|
|
233
|
+
since?: string;
|
|
234
|
+
limit?: number;
|
|
235
|
+
hook_type?: 'PreToolUse' | 'PostToolUse';
|
|
236
|
+
} = {}): Array<{ tool_name: string; count: number }> {
|
|
237
|
+
const conditions: string[] = [`tool_name IS NOT NULL`, `tool_name != ''`];
|
|
238
|
+
const params: unknown[] = [];
|
|
239
|
+
if (opts.since !== undefined) {
|
|
240
|
+
conditions.push('timestamp >= ?');
|
|
241
|
+
params.push(opts.since);
|
|
242
|
+
}
|
|
243
|
+
if (opts.hook_type) {
|
|
244
|
+
conditions.push('hook_type = ?');
|
|
245
|
+
params.push(opts.hook_type);
|
|
246
|
+
}
|
|
247
|
+
const limit = opts.limit ?? 100;
|
|
248
|
+
const sql = `SELECT tool_name, COUNT(*) as count FROM events
|
|
249
|
+
WHERE ${conditions.join(' AND ')}
|
|
250
|
+
GROUP BY tool_name ORDER BY count DESC LIMIT ?`;
|
|
251
|
+
params.push(limit);
|
|
252
|
+
return this.db.prepare(sql).all(...params) as Array<{ tool_name: string; count: number }>;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Per-day event counts (date(timestamp) → count) within [since, until). */
|
|
256
|
+
aggregateDailyEventCounts(opts: { since: string; until?: string }): Array<{ date: string; count: number }> {
|
|
257
|
+
const conditions: string[] = ['timestamp >= ?'];
|
|
258
|
+
const params: unknown[] = [opts.since];
|
|
259
|
+
if (opts.until !== undefined) {
|
|
260
|
+
conditions.push('timestamp < ?');
|
|
261
|
+
params.push(opts.until);
|
|
262
|
+
}
|
|
263
|
+
const sql = `SELECT date(timestamp) as date, COUNT(*) as count FROM events
|
|
264
|
+
WHERE ${conditions.join(' AND ')}
|
|
265
|
+
GROUP BY date ORDER BY date`;
|
|
266
|
+
return this.db.prepare(sql).all(...params) as Array<{ date: string; count: number }>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Hook type distribution for a single session. */
|
|
270
|
+
aggregateHookTypeBySession(session_id: string): Array<{ hook_type: string; count: number }> {
|
|
271
|
+
return this.db.prepare(
|
|
272
|
+
`SELECT hook_type, COUNT(*) as count FROM events
|
|
273
|
+
WHERE session_id = ? GROUP BY hook_type`
|
|
274
|
+
).all(session_id) as Array<{ hook_type: string; count: number }>;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Agent/Task subagent_type distribution for a single session (json_extract from tool_input). */
|
|
278
|
+
aggregateAgentTypeBySession(session_id: string): Array<{ agent_type: string | null; count: number }> {
|
|
279
|
+
return this.db.prepare(
|
|
280
|
+
`SELECT json_extract(tool_input, '$.subagent_type') as agent_type, COUNT(*) as count
|
|
281
|
+
FROM events
|
|
282
|
+
WHERE session_id = ? AND tool_name IN ('Agent', 'Task') AND tool_input IS NOT NULL
|
|
283
|
+
GROUP BY agent_type`
|
|
284
|
+
).all(session_id) as Array<{ agent_type: string | null; count: number }>;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Tool usage distribution for a single session (non-null tool_name only). */
|
|
288
|
+
aggregateToolUsageBySession(session_id: string): Array<{ tool_name: string; count: number }> {
|
|
289
|
+
return this.db.prepare(
|
|
290
|
+
`SELECT tool_name, COUNT(*) as count FROM events
|
|
291
|
+
WHERE session_id = ? AND tool_name IS NOT NULL
|
|
292
|
+
GROUP BY tool_name ORDER BY count DESC`
|
|
293
|
+
).all(session_id) as Array<{ tool_name: string; count: number }>;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Count of distinct active days (date(timestamp)) in [since, until). */
|
|
297
|
+
countActiveDays(opts: { since: string; until?: string }): number {
|
|
298
|
+
const conditions: string[] = ['timestamp >= ?'];
|
|
299
|
+
const params: unknown[] = [opts.since];
|
|
300
|
+
if (opts.until !== undefined) {
|
|
301
|
+
conditions.push('timestamp < ?');
|
|
302
|
+
params.push(opts.until);
|
|
303
|
+
}
|
|
304
|
+
const sql = `SELECT COUNT(DISTINCT date(timestamp)) as cnt FROM events
|
|
305
|
+
WHERE ${conditions.join(' AND ')}`;
|
|
306
|
+
const row = this.db.prepare(sql).get(...params) as { cnt: number } | undefined;
|
|
307
|
+
return row?.cnt ?? 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Composite overview within [since, until): event count + distinct session count + distinct day count. */
|
|
311
|
+
aggregateOverviewByRange(opts: { since: string; until: string }): {
|
|
312
|
+
event_count: number;
|
|
313
|
+
session_count: number;
|
|
314
|
+
day_count: number;
|
|
315
|
+
} {
|
|
316
|
+
const row = this.db.prepare(
|
|
317
|
+
`SELECT COUNT(*) as cnt, COUNT(DISTINCT session_id) as sessionCount,
|
|
318
|
+
COUNT(DISTINCT date(timestamp)) as dayCount
|
|
319
|
+
FROM events
|
|
320
|
+
WHERE timestamp >= ? AND timestamp < ?`
|
|
321
|
+
).get(opts.since, opts.until) as { cnt: number; sessionCount: number; dayCount: number } | undefined;
|
|
322
|
+
return {
|
|
323
|
+
event_count: row?.cnt ?? 0,
|
|
324
|
+
session_count: row?.sessionCount ?? 0,
|
|
325
|
+
day_count: row?.dayCount ?? 0,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Distinct, non-empty project_path values within [since, until). */
|
|
330
|
+
queryDistinctProjects(opts: { since: string; until: string }): string[] {
|
|
331
|
+
const rows = this.db.prepare(
|
|
332
|
+
`SELECT DISTINCT project_path FROM events
|
|
333
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
334
|
+
AND project_path IS NOT NULL AND project_path != ''`
|
|
335
|
+
).all(opts.since, opts.until) as Array<{ project_path: string }>;
|
|
336
|
+
return rows.map(r => r.project_path);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* PostToolUse failure rate within [since, until).
|
|
341
|
+
*
|
|
342
|
+
* Failure detection uses three LIKE branches on tool_output (preserved from
|
|
343
|
+
* weekly-report.ts original SQL):
|
|
344
|
+
* - '%"error"%'
|
|
345
|
+
* - '%"is_error":true%'
|
|
346
|
+
* - '%"isError":true%'
|
|
347
|
+
*/
|
|
348
|
+
aggregateToolFailureRate(opts: { since: string; until: string }): { post_total: number; failed: number } {
|
|
349
|
+
const totalRow = this.db.prepare(
|
|
350
|
+
`SELECT COUNT(*) as total FROM events
|
|
351
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
352
|
+
AND hook_type = 'PostToolUse'`
|
|
353
|
+
).get(opts.since, opts.until) as { total: number } | undefined;
|
|
354
|
+
|
|
355
|
+
const failRow = this.db.prepare(
|
|
356
|
+
`SELECT COUNT(*) as failed FROM events
|
|
357
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
358
|
+
AND hook_type = 'PostToolUse'
|
|
359
|
+
AND tool_output IS NOT NULL
|
|
360
|
+
AND (tool_output LIKE '%"error"%'
|
|
361
|
+
OR tool_output LIKE '%"is_error":true%'
|
|
362
|
+
OR tool_output LIKE '%"isError":true%')`
|
|
363
|
+
).get(opts.since, opts.until) as { failed: number } | undefined;
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
post_total: totalRow?.total ?? 0,
|
|
367
|
+
failed: failRow?.failed ?? 0,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Raw tool_input JSON strings for file-edit tools within [since, until).
|
|
373
|
+
* Caller is responsible for JSON.parse / file_path extraction.
|
|
374
|
+
* Internal IN-list expansion of `tool_names`.
|
|
375
|
+
*/
|
|
376
|
+
queryFileEditInputs(opts: { since: string; until: string; tool_names: string[] }): Array<{ tool_input: string }> {
|
|
377
|
+
if (opts.tool_names.length === 0) return [];
|
|
378
|
+
const placeholders = opts.tool_names.map(() => '?').join(',');
|
|
379
|
+
const sql = `SELECT tool_input FROM events
|
|
380
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
381
|
+
AND hook_type = 'PreToolUse'
|
|
382
|
+
AND tool_name IN (${placeholders})
|
|
383
|
+
AND tool_input IS NOT NULL`;
|
|
384
|
+
return this.db.prepare(sql).all(opts.since, opts.until, ...opts.tool_names) as Array<{ tool_input: string }>;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Full events in [since, until?), ascending by timestamp (used by anti-pattern-detector). */
|
|
388
|
+
queryEventsByTimeRange(opts: { since: string; until?: string }): ForgeEvent[] {
|
|
389
|
+
const conditions: string[] = ['timestamp >= ?'];
|
|
390
|
+
const params: unknown[] = [opts.since];
|
|
391
|
+
if (opts.until !== undefined) {
|
|
392
|
+
conditions.push('timestamp < ?');
|
|
393
|
+
params.push(opts.until);
|
|
394
|
+
}
|
|
395
|
+
const sql = `SELECT * FROM events WHERE ${conditions.join(' AND ')} ORDER BY timestamp ASC`;
|
|
396
|
+
const rows = this.db.prepare(sql).all(...params) as Array<Record<string, unknown>>;
|
|
397
|
+
return rows.map(r => this.rowToEvent(r));
|
|
398
|
+
}
|
|
399
|
+
|
|
221
400
|
rowToEvent(row: Record<string, unknown>): ForgeEvent {
|
|
222
401
|
// Runtime validation of database row
|
|
223
402
|
try {
|
|
@@ -127,7 +127,7 @@ export class RoutingOperations {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/** Pending events with NULL obeyed within the last N seconds; used by daemon restart recovery. */
|
|
130
|
-
|
|
130
|
+
queryPendingRoutingEvents(ageMs: number): RoutingEventRow[] {
|
|
131
131
|
const cutoff = Date.now() - ageMs;
|
|
132
132
|
return this.db.prepare(
|
|
133
133
|
`SELECT * FROM routing_events WHERE obeyed IS NULL AND ts >= ? ORDER BY ts DESC`
|
|
@@ -145,6 +145,30 @@ export class RoutingOperations {
|
|
|
145
145
|
return row ?? null;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Get the most recent PENDING routing_event for a session (obeyed IS NULL).
|
|
150
|
+
*
|
|
151
|
+
* Used by PostToolUseHandler to associate Agent invocations with the
|
|
152
|
+
* still-unfilled routing_event from the current user prompt. Returning only
|
|
153
|
+
* pending rows prevents the handler from re-overwriting an already-completed
|
|
154
|
+
* event (e.g. when 2+ Agents fire within the same prompt, or when an old
|
|
155
|
+
* obeyed=1 row leaks past a missed UserPromptSubmit).
|
|
156
|
+
*
|
|
157
|
+
* Trade-off: in the "same-prompt multi-Agent" case, the 2nd+ Agent will not
|
|
158
|
+
* find a pending row and will be silently skipped. Recording every Agent
|
|
159
|
+
* call is M7 out-of-scope (would require INSERT in PostToolUse rather than
|
|
160
|
+
* UPDATE).
|
|
161
|
+
*/
|
|
162
|
+
getRecentPendingRoutingEvent(sessionId: string): { id: number } | null {
|
|
163
|
+
const row = this.db.prepare(`
|
|
164
|
+
SELECT id FROM routing_events
|
|
165
|
+
WHERE session_id = ? AND obeyed IS NULL
|
|
166
|
+
ORDER BY ts DESC
|
|
167
|
+
LIMIT 1
|
|
168
|
+
`).get(sessionId) as { id: number } | undefined;
|
|
169
|
+
return row ?? null;
|
|
170
|
+
}
|
|
171
|
+
|
|
148
172
|
// ── Experiment Assignments ──────────────────────────────────────────────
|
|
149
173
|
|
|
150
174
|
getExperimentAssignment(sessionId: string, experimentId: string): string | null {
|
|
@@ -161,6 +185,110 @@ export class RoutingOperations {
|
|
|
161
185
|
).run(sessionId, experimentId, groupId);
|
|
162
186
|
}
|
|
163
187
|
|
|
188
|
+
// ── Aggregates (H1: SQL-side GROUP BY for routes/rules/skill-stats) ───
|
|
189
|
+
//
|
|
190
|
+
// 这些 aggregate* 方法把原本 routes 层做的"全表 SELECT + JS reduce"下沉到
|
|
191
|
+
// SQL 端,避免单进程内存放大。命名约定(spec H1 固化):
|
|
192
|
+
// - aggregate* —— SQL 端 GROUP BY / COUNT,返回小结果
|
|
193
|
+
// - query* —— 原始行返回(保留旧 API)
|
|
194
|
+
//
|
|
195
|
+
// 注意:所有方法都用 `ts > 0` 兜底,防止 strftime 在 ts<=0 时返回 NULL。
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 一次性返回 rules + skill-stats 共用的 routing 汇总(4 个独立聚合,
|
|
199
|
+
* 各走自己的索引)。不走 UNION ALL(SQLite 无 GROUPING SETS)。
|
|
200
|
+
*/
|
|
201
|
+
aggregateRoutingStats(filter: { since_ts: number; project_path?: string }): {
|
|
202
|
+
total: number;
|
|
203
|
+
obeyed: number;
|
|
204
|
+
refused: number;
|
|
205
|
+
unknown: number;
|
|
206
|
+
by_type: Array<{ type: 'agent' | 'skill' | 'none'; count: number }>;
|
|
207
|
+
by_agent: Array<{ agent: string; count: number }>;
|
|
208
|
+
by_skill_routed: Array<{ skill: string; count: number }>;
|
|
209
|
+
} {
|
|
210
|
+
const projectCond = filter.project_path ? 'AND project_path = @project' : '';
|
|
211
|
+
const params = {
|
|
212
|
+
since: filter.since_ts,
|
|
213
|
+
project: filter.project_path ?? null,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const totalRow = this.db.prepare(`
|
|
217
|
+
SELECT COUNT(*) AS total,
|
|
218
|
+
SUM(CASE WHEN obeyed=1 THEN 1 ELSE 0 END) AS obeyed,
|
|
219
|
+
SUM(CASE WHEN obeyed=0 THEN 1 ELSE 0 END) AS refused,
|
|
220
|
+
SUM(CASE WHEN obeyed IS NULL THEN 1 ELSE 0 END) AS unknown
|
|
221
|
+
FROM routing_events
|
|
222
|
+
WHERE ts > 0 AND ts >= @since ${projectCond}
|
|
223
|
+
`).get(params) as {
|
|
224
|
+
total: number | null;
|
|
225
|
+
obeyed: number | null;
|
|
226
|
+
refused: number | null;
|
|
227
|
+
unknown: number | null;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const byTypeRows = this.db.prepare(`
|
|
231
|
+
SELECT COALESCE(routed_to_type, 'none') AS type, COUNT(*) AS count
|
|
232
|
+
FROM routing_events
|
|
233
|
+
WHERE ts > 0 AND ts >= @since ${projectCond}
|
|
234
|
+
GROUP BY COALESCE(routed_to_type, 'none')
|
|
235
|
+
ORDER BY count DESC
|
|
236
|
+
`).all(params) as Array<{ type: 'agent' | 'skill' | 'none'; count: number }>;
|
|
237
|
+
|
|
238
|
+
const byAgentRows = this.db.prepare(`
|
|
239
|
+
SELECT routed_to_name AS agent, COUNT(*) AS count
|
|
240
|
+
FROM routing_events
|
|
241
|
+
WHERE ts > 0 AND ts >= @since AND obeyed = 1 AND routed_to_name IS NOT NULL ${projectCond}
|
|
242
|
+
GROUP BY routed_to_name
|
|
243
|
+
ORDER BY count DESC
|
|
244
|
+
`).all(params) as Array<{ agent: string; count: number }>;
|
|
245
|
+
|
|
246
|
+
const bySkillRoutedRows = this.db.prepare(`
|
|
247
|
+
SELECT routed_to_name AS skill, COUNT(*) AS count
|
|
248
|
+
FROM routing_events
|
|
249
|
+
WHERE ts > 0 AND ts >= @since AND routed_to_type = 'skill' AND routed_to_name IS NOT NULL ${projectCond}
|
|
250
|
+
GROUP BY routed_to_name
|
|
251
|
+
ORDER BY count DESC
|
|
252
|
+
`).all(params) as Array<{ skill: string; count: number }>;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
total: totalRow.total ?? 0,
|
|
256
|
+
obeyed: totalRow.obeyed ?? 0,
|
|
257
|
+
refused: totalRow.refused ?? 0,
|
|
258
|
+
unknown: totalRow.unknown ?? 0,
|
|
259
|
+
by_type: byTypeRows,
|
|
260
|
+
by_agent: byAgentRows,
|
|
261
|
+
by_skill_routed: bySkillRoutedRows,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 按天聚合 routing 事件,用于 skill-stats trend chart。
|
|
267
|
+
* 时区固定 UTC(strftime + unixepoch),与旧实现的 `new Date(ts).toISOString().split('T')[0]` 一致。
|
|
268
|
+
*/
|
|
269
|
+
aggregateRoutingTrendByDay(filter: { since_ts: number }): Array<{
|
|
270
|
+
day: string;
|
|
271
|
+
total: number;
|
|
272
|
+
skill: number;
|
|
273
|
+
}> {
|
|
274
|
+
const rows = this.db.prepare(`
|
|
275
|
+
SELECT strftime('%Y-%m-%d', ts / 1000, 'unixepoch') AS day,
|
|
276
|
+
COUNT(*) AS total,
|
|
277
|
+
SUM(CASE WHEN routed_to_type = 'skill' THEN 1 ELSE 0 END) AS skill
|
|
278
|
+
FROM routing_events
|
|
279
|
+
WHERE ts > 0 AND ts >= @since
|
|
280
|
+
GROUP BY day
|
|
281
|
+
ORDER BY day ASC
|
|
282
|
+
`).all({ since: filter.since_ts }) as Array<{
|
|
283
|
+
day: string | null;
|
|
284
|
+
total: number;
|
|
285
|
+
skill: number | null;
|
|
286
|
+
}>;
|
|
287
|
+
return rows
|
|
288
|
+
.filter(r => r.day !== null)
|
|
289
|
+
.map(r => ({ day: r.day as string, total: r.total, skill: r.skill ?? 0 }));
|
|
290
|
+
}
|
|
291
|
+
|
|
164
292
|
queryExperimentStats(experimentId: string): Array<{
|
|
165
293
|
group_id: string;
|
|
166
294
|
total: number;
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
-- └─ skill_invocations.session_id → events.session_id (logical, no FK)
|
|
18
18
|
-- └─ skill_invocations.route_request_id → routing_events.route_request_id (logical, no FK)
|
|
19
19
|
--
|
|
20
|
-
--
|
|
20
|
+
-- token_usage (LLM token accounting)
|
|
21
|
+
-- └─ token_usage.session_id → events.session_id (logical, no FK)
|
|
22
|
+
--
|
|
23
|
+
-- Why no FK on routing_events / skill_invocations / token_usage:
|
|
21
24
|
-- 1. routing_events.route_request_id is nullable and not UNIQUE — cannot be FK target
|
|
22
25
|
-- 2. Write order is not guaranteed: skill_invocations may be written before routing_events
|
|
23
26
|
-- 3. session_id references are maintained by application logic; sessions is an aggregate
|
|
@@ -152,7 +155,7 @@ CREATE INDEX IF NOT EXISTS idx_routing_events_experiment ON routing_events(exper
|
|
|
152
155
|
|
|
153
156
|
CREATE TABLE IF NOT EXISTS token_usage (
|
|
154
157
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
155
|
-
session_id TEXT NOT NULL,
|
|
158
|
+
session_id TEXT NOT NULL, -- logical ref → events.session_id; no FK due to write-order race (sessions is aggregate, may lag)
|
|
156
159
|
timestamp INTEGER NOT NULL,
|
|
157
160
|
input_tokens INTEGER NOT NULL,
|
|
158
161
|
output_tokens INTEGER NOT NULL,
|
|
@@ -212,3 +215,10 @@ CREATE INDEX IF NOT EXISTS idx_events_session_hook ON events(session_id, hook_ty
|
|
|
212
215
|
|
|
213
216
|
-- injections: optimize session + handler composite queries
|
|
214
217
|
CREATE INDEX IF NOT EXISTS idx_injections_session_handler ON injections(session_id, source_handler);
|
|
218
|
+
|
|
219
|
+
-- routing_events: optimize routed_to_type distribution (H1 aggregate path)
|
|
220
|
+
CREATE INDEX IF NOT EXISTS idx_routing_events_type_ts ON routing_events(routed_to_type, ts DESC);
|
|
221
|
+
|
|
222
|
+
-- skill_invocations: optimize workflow/phase and feature_slug queries (H4 dedup: previously migration-only)
|
|
223
|
+
CREATE INDEX IF NOT EXISTS idx_skill_invocations_workflow ON skill_invocations(workflow, phase);
|
|
224
|
+
CREATE INDEX IF NOT EXISTS idx_skill_invocations_feature ON skill_invocations(feature_slug);
|
|
@@ -101,4 +101,68 @@ export class SessionOperations {
|
|
|
101
101
|
end_time: r.end_time as string,
|
|
102
102
|
}));
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
// ── H2: 聚合 / 计数方法 ───────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/** Total session row count. */
|
|
108
|
+
countAllSessions(): number {
|
|
109
|
+
const row = this.db.prepare('SELECT COUNT(*) as cnt FROM sessions').get() as { cnt: number };
|
|
110
|
+
return row.cnt;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Per-day session counts (date(start_time) → count) within [since, until). */
|
|
114
|
+
aggregateDailySessionCounts(opts: { since: string; until?: string }): Array<{ date: string; count: number }> {
|
|
115
|
+
const conditions: string[] = ['start_time >= ?'];
|
|
116
|
+
const params: unknown[] = [opts.since];
|
|
117
|
+
if (opts.until !== undefined) {
|
|
118
|
+
conditions.push('start_time < ?');
|
|
119
|
+
params.push(opts.until);
|
|
120
|
+
}
|
|
121
|
+
const sql = `SELECT date(start_time) as date, COUNT(*) as count FROM sessions
|
|
122
|
+
WHERE ${conditions.join(' AND ')}
|
|
123
|
+
GROUP BY date ORDER BY date`;
|
|
124
|
+
return this.db.prepare(sql).all(...params) as Array<{ date: string; count: number }>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Sessions with start_time in [since, until?), ordered by start_time DESC. */
|
|
128
|
+
querySessionsByTimeRange(opts: { since: string; until?: string }): SessionSummary[] {
|
|
129
|
+
const conditions: string[] = ['start_time >= ?'];
|
|
130
|
+
const params: unknown[] = [opts.since];
|
|
131
|
+
if (opts.until !== undefined) {
|
|
132
|
+
conditions.push('start_time < ?');
|
|
133
|
+
params.push(opts.until);
|
|
134
|
+
}
|
|
135
|
+
const sql = `
|
|
136
|
+
SELECT
|
|
137
|
+
session_id,
|
|
138
|
+
first_prompt,
|
|
139
|
+
event_count,
|
|
140
|
+
start_time,
|
|
141
|
+
COALESCE(end_time, last_event_time, start_time) AS end_time
|
|
142
|
+
FROM sessions
|
|
143
|
+
WHERE ${conditions.join(' AND ')}
|
|
144
|
+
ORDER BY start_time DESC
|
|
145
|
+
`;
|
|
146
|
+
const rows = this.db.prepare(sql).all(...params) as Array<Record<string, unknown>>;
|
|
147
|
+
return rows.map(r => ({
|
|
148
|
+
session_id: r.session_id as string,
|
|
149
|
+
first_prompt: (r.first_prompt as string) || '',
|
|
150
|
+
event_count: (r.event_count as number) ?? 0,
|
|
151
|
+
start_time: r.start_time as string,
|
|
152
|
+
end_time: r.end_time as string,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Count of sessions with start_time in [since, until?). */
|
|
157
|
+
countSessionsByRange(opts: { since: string; until?: string }): number {
|
|
158
|
+
const conditions: string[] = ['start_time >= ?'];
|
|
159
|
+
const params: unknown[] = [opts.since];
|
|
160
|
+
if (opts.until !== undefined) {
|
|
161
|
+
conditions.push('start_time < ?');
|
|
162
|
+
params.push(opts.until);
|
|
163
|
+
}
|
|
164
|
+
const sql = `SELECT COUNT(*) as cnt FROM sessions WHERE ${conditions.join(' AND ')}`;
|
|
165
|
+
const row = this.db.prepare(sql).get(...params) as { cnt: number } | undefined;
|
|
166
|
+
return row?.cnt ?? 0;
|
|
167
|
+
}
|
|
104
168
|
}
|
|
@@ -70,6 +70,43 @@ export class SkillOperations {
|
|
|
70
70
|
return this.db.prepare(sql).all(...params) as SkillInvocationRow[];
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* H1: 按 skill_id 聚合调用总数 / 成功 / 失败。
|
|
75
|
+
*
|
|
76
|
+
* 取代 routes 层的"全表 querySkillInvocations + JS reduce"。
|
|
77
|
+
* 走 `idx_skill_invocations_timestamp`。
|
|
78
|
+
*/
|
|
79
|
+
aggregateSkillInvocationsBySkill(filter: { since: number; limit?: number }): Array<{
|
|
80
|
+
skill_id: string;
|
|
81
|
+
total: number;
|
|
82
|
+
success: number;
|
|
83
|
+
failed: number;
|
|
84
|
+
}> {
|
|
85
|
+
const limit = filter.limit ?? 1000;
|
|
86
|
+
const rows = this.db.prepare(`
|
|
87
|
+
SELECT skill_id,
|
|
88
|
+
COUNT(*) AS total,
|
|
89
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success,
|
|
90
|
+
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failed
|
|
91
|
+
FROM skill_invocations
|
|
92
|
+
WHERE timestamp >= ?
|
|
93
|
+
GROUP BY skill_id
|
|
94
|
+
ORDER BY total DESC
|
|
95
|
+
LIMIT ?
|
|
96
|
+
`).all(filter.since, limit) as Array<{
|
|
97
|
+
skill_id: string;
|
|
98
|
+
total: number;
|
|
99
|
+
success: number | null;
|
|
100
|
+
failed: number | null;
|
|
101
|
+
}>;
|
|
102
|
+
return rows.map(r => ({
|
|
103
|
+
skill_id: r.skill_id,
|
|
104
|
+
total: r.total,
|
|
105
|
+
success: r.success ?? 0,
|
|
106
|
+
failed: r.failed ?? 0,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
/**
|
|
74
111
|
* 按 route_request_id 聚合工作流进度。
|
|
75
112
|
*
|
|
@@ -161,4 +198,36 @@ export class SkillOperations {
|
|
|
161
198
|
};
|
|
162
199
|
});
|
|
163
200
|
}
|
|
201
|
+
|
|
202
|
+
// ── H2: 计数 / 列表方法 ──────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
/** Total skill_invocations row count. */
|
|
205
|
+
countAllSkillInvocations(): number {
|
|
206
|
+
const row = this.db.prepare('SELECT COUNT(*) as cnt FROM skill_invocations').get() as { cnt: number };
|
|
207
|
+
return row.cnt;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Skill invocation count for a single session. */
|
|
211
|
+
countSkillInvocationsBySession(session_id: string): number {
|
|
212
|
+
const row = this.db.prepare(
|
|
213
|
+
`SELECT COUNT(*) as cnt FROM skill_invocations WHERE session_id = ?`
|
|
214
|
+
).get(session_id) as { cnt: number } | undefined;
|
|
215
|
+
return row?.cnt ?? 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Distinct skill_id count since `since_ms` (unix ms). */
|
|
219
|
+
countDistinctSkillsSince(since_ms: number): number {
|
|
220
|
+
const row = this.db.prepare(
|
|
221
|
+
`SELECT COUNT(DISTINCT skill_id) as cnt FROM skill_invocations WHERE timestamp >= ?`
|
|
222
|
+
).get(since_ms) as { cnt: number } | undefined;
|
|
223
|
+
return row?.cnt ?? 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Distinct skill_id values for a single session. */
|
|
227
|
+
queryDistinctSkillIdsBySession(session_id: string): string[] {
|
|
228
|
+
const rows = this.db.prepare(
|
|
229
|
+
`SELECT DISTINCT skill_id FROM skill_invocations WHERE session_id = ?`
|
|
230
|
+
).all(session_id) as Array<{ skill_id: string }>;
|
|
231
|
+
return rows.map(r => r.skill_id);
|
|
232
|
+
}
|
|
164
233
|
}
|