@winspan/claude-forge 8.50.6 → 8.51.0
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/index.d.ts.map +1 -1
- package/dist/daemon/index.js +23 -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/index.ts +24 -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/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
|
@@ -83,6 +83,7 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// ── Database accessors ─────────────────────────────────────────────────
|
|
86
|
+
/** @internal 见 SQLiteBase.getDatabase 注释。Routes/Handlers/Analytics 禁止调用。 */
|
|
86
87
|
getDatabase(): Database.Database { return this.base.getDatabase(); }
|
|
87
88
|
getDbPath(): string { return this.base.getDbPath(); }
|
|
88
89
|
|
|
@@ -96,11 +97,58 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
96
97
|
return this.events.searchEvents(filter);
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
// ── H2: Event aggregates (前 routes/handlers/analytics 越权 SQL) ──
|
|
101
|
+
countAllEvents(): number { return this.events.countAllEvents(); }
|
|
102
|
+
aggregateToolUsage(opts?: Parameters<EventOperations['aggregateToolUsage']>[0]): ReturnType<EventOperations['aggregateToolUsage']> {
|
|
103
|
+
return this.events.aggregateToolUsage(opts);
|
|
104
|
+
}
|
|
105
|
+
aggregateDailyEventCounts(opts: Parameters<EventOperations['aggregateDailyEventCounts']>[0]): ReturnType<EventOperations['aggregateDailyEventCounts']> {
|
|
106
|
+
return this.events.aggregateDailyEventCounts(opts);
|
|
107
|
+
}
|
|
108
|
+
aggregateHookTypeBySession(session_id: string): ReturnType<EventOperations['aggregateHookTypeBySession']> {
|
|
109
|
+
return this.events.aggregateHookTypeBySession(session_id);
|
|
110
|
+
}
|
|
111
|
+
aggregateAgentTypeBySession(session_id: string): ReturnType<EventOperations['aggregateAgentTypeBySession']> {
|
|
112
|
+
return this.events.aggregateAgentTypeBySession(session_id);
|
|
113
|
+
}
|
|
114
|
+
aggregateToolUsageBySession(session_id: string): ReturnType<EventOperations['aggregateToolUsageBySession']> {
|
|
115
|
+
return this.events.aggregateToolUsageBySession(session_id);
|
|
116
|
+
}
|
|
117
|
+
countActiveDays(opts: Parameters<EventOperations['countActiveDays']>[0]): number {
|
|
118
|
+
return this.events.countActiveDays(opts);
|
|
119
|
+
}
|
|
120
|
+
aggregateOverviewByRange(opts: Parameters<EventOperations['aggregateOverviewByRange']>[0]): ReturnType<EventOperations['aggregateOverviewByRange']> {
|
|
121
|
+
return this.events.aggregateOverviewByRange(opts);
|
|
122
|
+
}
|
|
123
|
+
queryDistinctProjects(opts: Parameters<EventOperations['queryDistinctProjects']>[0]): string[] {
|
|
124
|
+
return this.events.queryDistinctProjects(opts);
|
|
125
|
+
}
|
|
126
|
+
aggregateToolFailureRate(opts: Parameters<EventOperations['aggregateToolFailureRate']>[0]): ReturnType<EventOperations['aggregateToolFailureRate']> {
|
|
127
|
+
return this.events.aggregateToolFailureRate(opts);
|
|
128
|
+
}
|
|
129
|
+
queryFileEditInputs(opts: Parameters<EventOperations['queryFileEditInputs']>[0]): ReturnType<EventOperations['queryFileEditInputs']> {
|
|
130
|
+
return this.events.queryFileEditInputs(opts);
|
|
131
|
+
}
|
|
132
|
+
queryEventsByTimeRange(opts: Parameters<EventOperations['queryEventsByTimeRange']>[0]): ForgeEvent[] {
|
|
133
|
+
return this.events.queryEventsByTimeRange(opts);
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
// ── Session operations ─────────────────────────────────────────────────
|
|
100
137
|
upsertSession(event: ForgeEvent): void { this.sessions.upsertSession(event); }
|
|
101
138
|
querySessions(filter: Parameters<SessionOperations['querySessions']>[0] = {}): SessionSummary[] {
|
|
102
139
|
return this.sessions.querySessions(filter);
|
|
103
140
|
}
|
|
141
|
+
// ── H2: Session aggregates ──
|
|
142
|
+
countAllSessions(): number { return this.sessions.countAllSessions(); }
|
|
143
|
+
aggregateDailySessionCounts(opts: Parameters<SessionOperations['aggregateDailySessionCounts']>[0]): ReturnType<SessionOperations['aggregateDailySessionCounts']> {
|
|
144
|
+
return this.sessions.aggregateDailySessionCounts(opts);
|
|
145
|
+
}
|
|
146
|
+
querySessionsByTimeRange(opts: Parameters<SessionOperations['querySessionsByTimeRange']>[0]): SessionSummary[] {
|
|
147
|
+
return this.sessions.querySessionsByTimeRange(opts);
|
|
148
|
+
}
|
|
149
|
+
countSessionsByRange(opts: Parameters<SessionOperations['countSessionsByRange']>[0]): number {
|
|
150
|
+
return this.sessions.countSessionsByRange(opts);
|
|
151
|
+
}
|
|
104
152
|
|
|
105
153
|
// ── Injection operations ───────────────────────────────────────────────
|
|
106
154
|
writeInjection(injection: Injection): void { this.injections.writeInjection(injection); }
|
|
@@ -127,7 +175,29 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
127
175
|
queryTaskProjects(): string[] {
|
|
128
176
|
return this.tasks.queryTaskProjects();
|
|
129
177
|
}
|
|
178
|
+
// ── H2: Task counts ──
|
|
179
|
+
countTasksByRange(opts: Parameters<TaskOperations['countTasksByRange']>[0]): number {
|
|
180
|
+
return this.tasks.countTasksByRange(opts);
|
|
181
|
+
}
|
|
130
182
|
getTaskEventIds(taskId: string): string[] { return this.tasks.getTaskEventIds(taskId); }
|
|
183
|
+
completeStaleActiveTasks(idleMinutes: number): number {
|
|
184
|
+
return this.tasks.completeStaleActiveTasks(idleMinutes);
|
|
185
|
+
}
|
|
186
|
+
getTask(taskId: string): TaskRecord | null {
|
|
187
|
+
return this.tasks.getTask(taskId);
|
|
188
|
+
}
|
|
189
|
+
queryEventsByTaskId(taskId: string, opts?: Parameters<TaskOperations['queryEventsByTaskId']>[1]): ForgeEvent[] {
|
|
190
|
+
return this.tasks.queryEventsByTaskId(taskId, opts);
|
|
191
|
+
}
|
|
192
|
+
queryInjectionsByTaskId(taskId: string): Injection[] {
|
|
193
|
+
return this.tasks.queryInjectionsByTaskId(taskId);
|
|
194
|
+
}
|
|
195
|
+
querySkillInvocationsByTaskWindow(
|
|
196
|
+
taskId: string,
|
|
197
|
+
opts?: Parameters<TaskOperations['querySkillInvocationsByTaskWindow']>[1],
|
|
198
|
+
): SkillInvocationRow[] {
|
|
199
|
+
return this.tasks.querySkillInvocationsByTaskWindow(taskId, opts);
|
|
200
|
+
}
|
|
131
201
|
|
|
132
202
|
// ── Routing events ─────────────────────────────────────────────────────
|
|
133
203
|
writeRoutingEvent(record: RoutingEventRecord): number {
|
|
@@ -139,12 +209,25 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
139
209
|
queryRoutingEvents(filter: Parameters<RoutingOperations['queryRoutingEvents']>[0] = {}): RoutingEventRow[] {
|
|
140
210
|
return this.routing.queryRoutingEvents(filter);
|
|
141
211
|
}
|
|
142
|
-
|
|
143
|
-
|
|
212
|
+
aggregateRoutingStats(
|
|
213
|
+
filter: Parameters<RoutingOperations['aggregateRoutingStats']>[0],
|
|
214
|
+
): ReturnType<RoutingOperations['aggregateRoutingStats']> {
|
|
215
|
+
return this.routing.aggregateRoutingStats(filter);
|
|
216
|
+
}
|
|
217
|
+
aggregateRoutingTrendByDay(
|
|
218
|
+
filter: Parameters<RoutingOperations['aggregateRoutingTrendByDay']>[0],
|
|
219
|
+
): ReturnType<RoutingOperations['aggregateRoutingTrendByDay']> {
|
|
220
|
+
return this.routing.aggregateRoutingTrendByDay(filter);
|
|
221
|
+
}
|
|
222
|
+
queryPendingRoutingEvents(ageMs: number): RoutingEventRow[] {
|
|
223
|
+
return this.routing.queryPendingRoutingEvents(ageMs);
|
|
144
224
|
}
|
|
145
225
|
getRecentRoutingEvent(sessionId: string): { id: number } | null {
|
|
146
226
|
return this.routing.getRecentRoutingEvent(sessionId);
|
|
147
227
|
}
|
|
228
|
+
getRecentPendingRoutingEvent(sessionId: string): { id: number } | null {
|
|
229
|
+
return this.routing.getRecentPendingRoutingEvent(sessionId);
|
|
230
|
+
}
|
|
148
231
|
|
|
149
232
|
// ── Experiment assignments ─────────────────────────────────────────────
|
|
150
233
|
getExperimentAssignment(sessionId: string, experimentId: string): string | null {
|
|
@@ -158,8 +241,8 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
158
241
|
}
|
|
159
242
|
|
|
160
243
|
// ── Token usage ────────────────────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
this.tokenUsage.
|
|
244
|
+
writeTokenUsage(params: Parameters<TokenUsageOperations['writeTokenUsage']>[0]): void {
|
|
245
|
+
this.tokenUsage.writeTokenUsage(params);
|
|
163
246
|
}
|
|
164
247
|
getTokenUsageBySession(session_id: string): ReturnType<TokenUsageOperations['getTokenUsageBySession']> {
|
|
165
248
|
return this.tokenUsage.getTokenUsageBySession(session_id);
|
|
@@ -175,9 +258,25 @@ export class SQLiteStorage extends EventEmitter {
|
|
|
175
258
|
querySkillInvocations(filter: Parameters<SkillOperations['querySkillInvocations']>[0] = {}): SkillInvocationRow[] {
|
|
176
259
|
return this.skills.querySkillInvocations(filter);
|
|
177
260
|
}
|
|
261
|
+
aggregateSkillInvocationsBySkill(
|
|
262
|
+
filter: Parameters<SkillOperations['aggregateSkillInvocationsBySkill']>[0],
|
|
263
|
+
): ReturnType<SkillOperations['aggregateSkillInvocationsBySkill']> {
|
|
264
|
+
return this.skills.aggregateSkillInvocationsBySkill(filter);
|
|
265
|
+
}
|
|
178
266
|
queryWorkflowProgress(filter: Parameters<SkillOperations['queryWorkflowProgress']>[0] = {}): ReturnType<SkillOperations['queryWorkflowProgress']> {
|
|
179
267
|
return this.skills.queryWorkflowProgress(filter);
|
|
180
268
|
}
|
|
269
|
+
// ── H2: Skill counts ──
|
|
270
|
+
countAllSkillInvocations(): number { return this.skills.countAllSkillInvocations(); }
|
|
271
|
+
countSkillInvocationsBySession(session_id: string): number {
|
|
272
|
+
return this.skills.countSkillInvocationsBySession(session_id);
|
|
273
|
+
}
|
|
274
|
+
countDistinctSkillsSince(since_ms: number): number {
|
|
275
|
+
return this.skills.countDistinctSkillsSince(since_ms);
|
|
276
|
+
}
|
|
277
|
+
queryDistinctSkillIdsBySession(session_id: string): string[] {
|
|
278
|
+
return this.skills.queryDistinctSkillIdsBySession(session_id);
|
|
279
|
+
}
|
|
181
280
|
|
|
182
281
|
// ── Maintenance ────────────────────────────────────────────────────────
|
|
183
282
|
cleanOldEvents(daysToKeep: number = 30): number { return this.maintenance.cleanOldEvents(daysToKeep); }
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type Database from 'better-sqlite3';
|
|
8
|
+
import type { ForgeEvent } from '../types.js';
|
|
9
|
+
import type { SkillInvocationRow } from './rows.js';
|
|
10
|
+
import type { Injection } from './injections.js';
|
|
8
11
|
|
|
9
12
|
export interface TaskRecord {
|
|
10
13
|
id: string;
|
|
@@ -16,6 +19,9 @@ export interface TaskRecord {
|
|
|
16
19
|
status: 'active' | 'completed' | 'abandoned';
|
|
17
20
|
event_count: number;
|
|
18
21
|
project_path?: string;
|
|
22
|
+
/** First UserPromptSubmit user_prompt text for this task's session window.
|
|
23
|
+
* Populated by queryTasksFiltered via correlated subquery on idx_events_session_hook. */
|
|
24
|
+
first_prompt?: string | null;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
export interface TaskFilter {
|
|
@@ -92,7 +98,14 @@ export class TaskOperations {
|
|
|
92
98
|
// Data query
|
|
93
99
|
const dataParams = [...params, limit, offset];
|
|
94
100
|
const rows = this.db.prepare(
|
|
95
|
-
`SELECT t.*, s.project_path
|
|
101
|
+
`SELECT t.*, s.project_path,
|
|
102
|
+
(SELECT e.user_prompt
|
|
103
|
+
FROM events e
|
|
104
|
+
WHERE e.session_id = t.session_id
|
|
105
|
+
AND e.hook_type = 'UserPromptSubmit'
|
|
106
|
+
AND e.timestamp >= t.start_time
|
|
107
|
+
AND (t.end_time IS NULL OR e.timestamp <= t.end_time)
|
|
108
|
+
ORDER BY e.timestamp ASC LIMIT 1) AS first_prompt
|
|
96
109
|
FROM tasks t
|
|
97
110
|
JOIN sessions s ON t.session_id = s.session_id
|
|
98
111
|
${where}
|
|
@@ -104,6 +117,19 @@ export class TaskOperations {
|
|
|
104
117
|
return { items, total, has_more: offset + items.length < total };
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
/** Count of tasks with start_time in [since, until?). */
|
|
121
|
+
countTasksByRange(opts: { since: string; until?: string }): number {
|
|
122
|
+
const conditions: string[] = ['start_time >= ?'];
|
|
123
|
+
const params: unknown[] = [opts.since];
|
|
124
|
+
if (opts.until !== undefined) {
|
|
125
|
+
conditions.push('start_time < ?');
|
|
126
|
+
params.push(opts.until);
|
|
127
|
+
}
|
|
128
|
+
const sql = `SELECT COUNT(*) as cnt FROM tasks WHERE ${conditions.join(' AND ')}`;
|
|
129
|
+
const row = this.db.prepare(sql).get(...params) as { cnt: number } | undefined;
|
|
130
|
+
return row?.cnt ?? 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
/** Returns distinct project paths that have at least one task. */
|
|
108
134
|
queryTaskProjects(): string[] {
|
|
109
135
|
const rows = this.db.prepare(
|
|
@@ -120,6 +146,127 @@ export class TaskOperations {
|
|
|
120
146
|
return rows.map(r => r.event_id);
|
|
121
147
|
}
|
|
122
148
|
|
|
149
|
+
/**
|
|
150
|
+
* 将 idle 超过阈值的 active task 批量转为 completed。
|
|
151
|
+
* 条件:status='active' AND end_time IS NOT NULL
|
|
152
|
+
* AND (now - end_time) > idleMinutes 分钟。
|
|
153
|
+
* 返回:受影响行数。
|
|
154
|
+
*/
|
|
155
|
+
completeStaleActiveTasks(idleMinutes: number): number {
|
|
156
|
+
const result = this.db.prepare(`
|
|
157
|
+
UPDATE tasks
|
|
158
|
+
SET status = 'completed'
|
|
159
|
+
WHERE status = 'active'
|
|
160
|
+
AND end_time IS NOT NULL
|
|
161
|
+
AND (julianday('now') - julianday(end_time)) * 24 * 60 > ?
|
|
162
|
+
`).run(idleMinutes);
|
|
163
|
+
return result.changes as number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── H1: detail route 用的 PK 直查 / JOIN 接口 ──────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 按 taskId 直查单个 task(LEFT JOIN sessions 取 project_path)。
|
|
170
|
+
* 替代旧 detail route 的「拉 5000 tasks 再 find(id)」。
|
|
171
|
+
*/
|
|
172
|
+
getTask(taskId: string): TaskRecord | null {
|
|
173
|
+
const row = this.db.prepare(`
|
|
174
|
+
SELECT t.*, s.project_path
|
|
175
|
+
FROM tasks t
|
|
176
|
+
LEFT JOIN sessions s ON s.session_id = t.session_id
|
|
177
|
+
WHERE t.id = ?
|
|
178
|
+
`).get(taskId) as Record<string, unknown> | undefined;
|
|
179
|
+
if (!row) return null;
|
|
180
|
+
return this.mapRow(row);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 按 taskId JOIN task_events 拉关联事件,按 timestamp 升序。
|
|
185
|
+
* 默认 limit 5000(防御过大 task;前端不分页但要有上限)。
|
|
186
|
+
*/
|
|
187
|
+
queryEventsByTaskId(taskId: string, opts: { limit?: number } = {}): ForgeEvent[] {
|
|
188
|
+
const limit = opts.limit ?? 5000;
|
|
189
|
+
const rows = this.db.prepare(`
|
|
190
|
+
SELECT e.*
|
|
191
|
+
FROM events e
|
|
192
|
+
JOIN task_events te ON te.event_id = e.event_id
|
|
193
|
+
WHERE te.task_id = ?
|
|
194
|
+
ORDER BY e.timestamp ASC
|
|
195
|
+
LIMIT ?
|
|
196
|
+
`).all(taskId, limit) as Array<Record<string, unknown>>;
|
|
197
|
+
return rows.map(r => this.rowToEvent(r));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 按 taskId JOIN task_events 拉关联 injections,按 timestamp 升序。
|
|
202
|
+
* 替代「拉 500 injections + JS Set.has 过滤」。
|
|
203
|
+
*/
|
|
204
|
+
queryInjectionsByTaskId(taskId: string): Injection[] {
|
|
205
|
+
const rows = this.db.prepare(`
|
|
206
|
+
SELECT i.*
|
|
207
|
+
FROM injections i
|
|
208
|
+
JOIN task_events te ON te.event_id = i.event_id
|
|
209
|
+
WHERE te.task_id = ?
|
|
210
|
+
ORDER BY i.timestamp ASC
|
|
211
|
+
`).all(taskId) as Array<Record<string, unknown>>;
|
|
212
|
+
return rows.map(r => ({
|
|
213
|
+
id: r.id as string,
|
|
214
|
+
event_id: (r.event_id as string) || undefined,
|
|
215
|
+
session_id: r.session_id as string,
|
|
216
|
+
timestamp: r.timestamp as string,
|
|
217
|
+
source_handler: r.source_handler as string,
|
|
218
|
+
injection_type: r.injection_type as Injection['injection_type'],
|
|
219
|
+
content: r.content as string,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 按 task 的时间窗口 + session_id 过滤 skill_invocations。
|
|
225
|
+
* 替代「拉 200 invocations + JS 时间比较」。
|
|
226
|
+
* `now_ms` 通过参数注入(end_time IS NULL 时取此值),便于测试时 mock。
|
|
227
|
+
*
|
|
228
|
+
* 时间转换:strftime('%s', ISO_TS) 返回 unix 秒(丢毫秒),× 1000 → ms。
|
|
229
|
+
* 与旧实现 `new Date(taskStart).getTime()` 相比可能损失最多 999ms,
|
|
230
|
+
* 但 task 时间窗口通常远大于秒级,对结果集影响可忽略。
|
|
231
|
+
*/
|
|
232
|
+
querySkillInvocationsByTaskWindow(taskId: string, opts: { now_ms?: number } = {}): SkillInvocationRow[] {
|
|
233
|
+
const nowMs = opts.now_ms ?? Date.now();
|
|
234
|
+
const rows = this.db.prepare(`
|
|
235
|
+
SELECT si.*
|
|
236
|
+
FROM skill_invocations si
|
|
237
|
+
JOIN tasks t ON t.session_id = si.session_id
|
|
238
|
+
WHERE t.id = ?
|
|
239
|
+
AND si.timestamp >= CAST(strftime('%s', t.start_time) AS INTEGER) * 1000
|
|
240
|
+
AND si.timestamp <= COALESCE(
|
|
241
|
+
CAST(strftime('%s', t.end_time) AS INTEGER) * 1000 + 999,
|
|
242
|
+
?
|
|
243
|
+
)
|
|
244
|
+
ORDER BY si.timestamp ASC
|
|
245
|
+
`).all(taskId, nowMs) as SkillInvocationRow[];
|
|
246
|
+
return rows;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Map a raw events row to ForgeEvent (parses JSON columns). */
|
|
250
|
+
private rowToEvent(row: Record<string, unknown>): ForgeEvent {
|
|
251
|
+
const parseJson = (val: unknown): unknown => {
|
|
252
|
+
if (val == null) return undefined;
|
|
253
|
+
if (typeof val !== 'string') return val;
|
|
254
|
+
try { return JSON.parse(val); } catch { return val; }
|
|
255
|
+
};
|
|
256
|
+
return {
|
|
257
|
+
event_id: row.event_id as string,
|
|
258
|
+
session_id: row.session_id as string,
|
|
259
|
+
project_path: row.project_path as string,
|
|
260
|
+
timestamp: row.timestamp as string,
|
|
261
|
+
hook_type: row.hook_type as ForgeEvent['hook_type'],
|
|
262
|
+
tool_name: (row.tool_name as string) || undefined,
|
|
263
|
+
tool_input: parseJson(row.tool_input) as ForgeEvent['tool_input'],
|
|
264
|
+
tool_output: parseJson(row.tool_output) as ForgeEvent['tool_output'],
|
|
265
|
+
user_prompt: (row.user_prompt as string) || undefined,
|
|
266
|
+
ai_response: (row.ai_response as string) || undefined,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
123
270
|
private buildWhereConditions(filter: TaskFilter): { conditions: string[]; params: unknown[] } {
|
|
124
271
|
const conditions: string[] = [];
|
|
125
272
|
const params: unknown[] = [];
|
|
@@ -165,6 +312,7 @@ export class TaskOperations {
|
|
|
165
312
|
status: (r.status as TaskRecord['status']) || 'active',
|
|
166
313
|
event_count: (r.event_count as number) || 0,
|
|
167
314
|
project_path: (r.project_path as string) || undefined,
|
|
315
|
+
first_prompt: r.first_prompt !== undefined ? (r.first_prompt as string | null) : undefined,
|
|
168
316
|
};
|
|
169
317
|
}
|
|
170
318
|
}
|
|
@@ -10,7 +10,7 @@ import { logger } from '../utils/logger.js';
|
|
|
10
10
|
export class TokenUsageOperations {
|
|
11
11
|
constructor(private db: Database.Database) {}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
writeTokenUsage(params: {
|
|
14
14
|
session_id: string;
|
|
15
15
|
input_tokens: number;
|
|
16
16
|
output_tokens: number;
|
package/src/core/types.ts
CHANGED
|
@@ -21,17 +21,45 @@
|
|
|
21
21
|
* (no more `event.tool_name!`) without forcing every caller of the wire
|
|
22
22
|
* shape to do a 5-way switch.
|
|
23
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* Lenient bag of common tool_input fields across all Claude Code tools.
|
|
26
|
+
*
|
|
27
|
+
* Intentionally not a discriminated union — the tool set is open-ended and
|
|
28
|
+
* almost all access sites are defensive (`?.command ?? ''`). The index
|
|
29
|
+
* signature keeps forward compatibility with unknown tool kinds; prefer
|
|
30
|
+
* the typed getters in `core/event-fields.ts` over direct field access
|
|
31
|
+
* when the value is fed downstream as `string`.
|
|
32
|
+
*/
|
|
33
|
+
export interface ToolInputFields {
|
|
34
|
+
// Bash
|
|
35
|
+
command?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
// Edit / Write / Read / NotebookEdit
|
|
38
|
+
file_path?: string;
|
|
39
|
+
notebook_path?: string;
|
|
40
|
+
old_string?: string;
|
|
41
|
+
new_string?: string;
|
|
42
|
+
content?: string;
|
|
43
|
+
// Agent / Task
|
|
44
|
+
subagent_type?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
prompt?: string;
|
|
47
|
+
// UserPromptSubmit fallback envelope (some hook flows pack user_prompt here)
|
|
48
|
+
user_prompt?: string;
|
|
49
|
+
// Forward-compat for unknown tools / new Claude Code releases
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
24
53
|
export interface ForgeEvent {
|
|
25
54
|
session_id: string;
|
|
26
55
|
project_path: string;
|
|
27
56
|
timestamp: string;
|
|
28
57
|
hook_type: 'PreToolUse' | 'PostToolUse' | 'UserPromptSubmit' | 'Notification' | 'Stop';
|
|
29
58
|
tool_name?: string;
|
|
30
|
-
tool_input?:
|
|
59
|
+
tool_input?: ToolInputFields;
|
|
31
60
|
tool_output?: Record<string, unknown>;
|
|
32
61
|
user_prompt?: string;
|
|
33
62
|
ai_response?: string;
|
|
34
|
-
pipeline_id?: string;
|
|
35
63
|
event_id?: string;
|
|
36
64
|
}
|
|
37
65
|
|
|
@@ -40,7 +68,6 @@ interface BaseEvent {
|
|
|
40
68
|
project_path: string;
|
|
41
69
|
timestamp: string;
|
|
42
70
|
event_id?: string;
|
|
43
|
-
pipeline_id?: string;
|
|
44
71
|
}
|
|
45
72
|
|
|
46
73
|
export interface PreToolUseEvent extends BaseEvent {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { logger } from './logger.js';
|
|
8
|
+
import { DEFAULTS } from '../constants.js';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
|
|
10
11
|
export interface ErrorSolution {
|
|
@@ -221,7 +222,7 @@ export class ErrorHandler {
|
|
|
221
222
|
|
|
222
223
|
// 根据错误类型自动识别并生成解决方案
|
|
223
224
|
if (error.code === 'EADDRINUSE') {
|
|
224
|
-
const port = parseInt(error.message?.match(/:(\d+)/)?.[1] ||
|
|
225
|
+
const port = parseInt(error.message?.match(/:(\d+)/)?.[1] || String(DEFAULTS.WEB_PORT));
|
|
225
226
|
solution = this.handlePortInUse(port, error);
|
|
226
227
|
} else if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
227
228
|
const path = error.path || error.message?.match(/['"]([^'"]+)['"]/)?.[1] || 'unknown';
|
|
@@ -235,7 +236,7 @@ export class ErrorHandler {
|
|
|
235
236
|
const dbPath = error.message?.match(/['"]([^'"]+\.db)['"]/)?.[1] || '~/.claude-forge/data.db';
|
|
236
237
|
solution = this.handleDatabaseError(dbPath, error);
|
|
237
238
|
} else if (error.code === 'ECONNREFUSED' || error.code === 'ENOENT') {
|
|
238
|
-
const socketPath = error.path || '~/.claude-forge/
|
|
239
|
+
const socketPath = error.path || '~/.claude-forge/daemon.sock';
|
|
239
240
|
solution = this.handleSocketError(socketPath, error);
|
|
240
241
|
} else {
|
|
241
242
|
// 通用错误
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Walk up the directory tree from `start` using `git rev-parse --show-toplevel`
|
|
5
|
+
* to find the git repository root.
|
|
6
|
+
*
|
|
7
|
+
* Fallback: returns `start` unchanged if:
|
|
8
|
+
* - `start` is not inside a git repository
|
|
9
|
+
* - the git command fails for any reason (git not on PATH, permission error, etc.)
|
|
10
|
+
*/
|
|
11
|
+
export function resolveGitRoot(start: string = process.cwd()): string {
|
|
12
|
+
try {
|
|
13
|
+
const result = execSync('git rev-parse --show-toplevel', {
|
|
14
|
+
cwd: start,
|
|
15
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
16
|
+
})
|
|
17
|
+
.toString()
|
|
18
|
+
.trim();
|
|
19
|
+
return result || start;
|
|
20
|
+
} catch {
|
|
21
|
+
return start;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/core/utils/logger.ts
CHANGED
|
@@ -12,7 +12,22 @@ export enum LogLevel {
|
|
|
12
12
|
ERROR = 3,
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Parse log level from LOG_LEVEL env var.
|
|
17
|
+
* Accepts: debug, info, warn, error (case-insensitive). Falls back to INFO.
|
|
18
|
+
*/
|
|
19
|
+
function getLogLevelFromEnv(): LogLevel {
|
|
20
|
+
const envLevel = (process.env.LOG_LEVEL ?? '').toLowerCase();
|
|
21
|
+
const levelMap: Record<string, LogLevel> = {
|
|
22
|
+
debug: LogLevel.DEBUG,
|
|
23
|
+
info: LogLevel.INFO,
|
|
24
|
+
warn: LogLevel.WARN,
|
|
25
|
+
error: LogLevel.ERROR,
|
|
26
|
+
};
|
|
27
|
+
return levelMap[envLevel] ?? LogLevel.INFO;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let currentLevel: LogLevel = getLogLevelFromEnv();
|
|
16
31
|
let logFilePath: string | null = null;
|
|
17
32
|
let lastSizeCheck = 0;
|
|
18
33
|
let cachedSize = 0;
|
|
@@ -33,7 +33,7 @@ export class TokenTracker {
|
|
|
33
33
|
*/
|
|
34
34
|
record(params: TokenRecordParams): void {
|
|
35
35
|
try {
|
|
36
|
-
this.storage.
|
|
36
|
+
this.storage.writeTokenUsage({
|
|
37
37
|
session_id: params.session_id,
|
|
38
38
|
input_tokens: params.usage.input_tokens,
|
|
39
39
|
output_tokens: params.usage.output_tokens,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type { ForgeEvent } from '../core/types.js';
|
|
2
|
+
import type { ForgeEvent, ToolInputFields } from '../core/types.js';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
|
|
5
5
|
const RawHookEventSchema = z.object({
|
|
@@ -12,6 +12,7 @@ const RawHookEventSchema = z.object({
|
|
|
12
12
|
tool_output: z.record(z.string(), z.unknown()).optional(),
|
|
13
13
|
user_prompt: z.string().optional(),
|
|
14
14
|
ai_response: z.string().optional(),
|
|
15
|
+
event_id: z.string().uuid().optional(),
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
export class EventParser {
|
|
@@ -20,13 +21,13 @@ export class EventParser {
|
|
|
20
21
|
const validated = RawHookEventSchema.parse(json);
|
|
21
22
|
|
|
22
23
|
return {
|
|
23
|
-
event_id: randomUUID(),
|
|
24
|
+
event_id: validated.event_id ?? randomUUID(),
|
|
24
25
|
session_id: validated.session_id,
|
|
25
26
|
project_path: validated.project_path,
|
|
26
27
|
timestamp: validated.timestamp,
|
|
27
28
|
hook_type: validated.hook_type,
|
|
28
29
|
tool_name: validated.tool_name,
|
|
29
|
-
tool_input: validated.tool_input as
|
|
30
|
+
tool_input: validated.tool_input as ToolInputFields | undefined,
|
|
30
31
|
tool_output: validated.tool_output as Record<string, unknown> | undefined,
|
|
31
32
|
user_prompt: validated.user_prompt,
|
|
32
33
|
ai_response: validated.ai_response,
|
|
@@ -99,7 +99,7 @@ export class HistoryExporter {
|
|
|
99
99
|
lines.push('');
|
|
100
100
|
for (const t of toolCalls.slice(-30)) {
|
|
101
101
|
const time = new Date(t.timestamp).toLocaleTimeString();
|
|
102
|
-
const input = t.tool_input
|
|
102
|
+
const input = t.tool_input;
|
|
103
103
|
const filePath = input?.file_path ?? input?.command ?? '';
|
|
104
104
|
const summary =
|
|
105
105
|
typeof filePath === 'string' ? filePath.slice(0, 80) : '';
|
|
@@ -9,6 +9,7 @@ import type { PostToolUseEvent, HookResult } from '../../core/types.js';
|
|
|
9
9
|
import type { SQLiteStorage } from '../../core/storage/sqlite.js';
|
|
10
10
|
import { logger } from '../../core/utils/logger.js';
|
|
11
11
|
import { TokenTracker } from '../../core/utils/token-tracker.js';
|
|
12
|
+
import { getSubagentType } from '../../core/event-fields.js';
|
|
12
13
|
|
|
13
14
|
export class PostToolUseHandler {
|
|
14
15
|
private tokenTracker: TokenTracker | null = null;
|
|
@@ -26,10 +27,13 @@ export class PostToolUseHandler {
|
|
|
26
27
|
if (this.storage && event.tool_name === 'Agent') {
|
|
27
28
|
try {
|
|
28
29
|
// PostToolUseEvent.tool_input is already a Record (narrowed by type guard).
|
|
29
|
-
const agentType = (event
|
|
30
|
+
const agentType = getSubagentType(event) ?? 'general-purpose';
|
|
30
31
|
|
|
31
|
-
// Find the most recent routing_event for this session
|
|
32
|
-
|
|
32
|
+
// Find the most recent PENDING routing_event for this session
|
|
33
|
+
// (obeyed IS NULL). Filtering on pending prevents re-overwriting an
|
|
34
|
+
// already-completed event when 2+ Agents fire within the same prompt,
|
|
35
|
+
// or when an old obeyed=1 row leaks past a missed UserPromptSubmit.
|
|
36
|
+
const recentEvent = this.storage.getRecentPendingRoutingEvent(event.session_id);
|
|
33
37
|
if (recentEvent) {
|
|
34
38
|
this.storage.updateRoutingEvent(recentEvent.id, {
|
|
35
39
|
routed_to_type: 'agent',
|