@winspan/claude-forge 8.53.2 → 8.54.3
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/DEVELOPMENT.md +290 -221
- package/README.md +50 -8
- package/dist/cli/commands/skills.d.ts.map +1 -1
- package/dist/cli/commands/skills.js +7 -3
- package/dist/cli/commands/skills.js.map +1 -1
- package/dist/cli/init/hook-manager.d.ts +1 -1
- package/dist/cli/init/hook-manager.d.ts.map +1 -1
- package/dist/cli/init/hook-manager.js +1 -0
- package/dist/cli/init/hook-manager.js.map +1 -1
- package/dist/core/storage/events.d.ts.map +1 -1
- package/dist/core/storage/events.js +0 -1
- package/dist/core/storage/events.js.map +1 -1
- package/dist/core/storage/maintenance.d.ts +25 -3
- package/dist/core/storage/maintenance.d.ts.map +1 -1
- package/dist/core/storage/maintenance.js +33 -4
- package/dist/core/storage/maintenance.js.map +1 -1
- package/dist/core/storage/routing.d.ts +4 -0
- package/dist/core/storage/routing.d.ts.map +1 -1
- package/dist/core/storage/routing.js +10 -4
- package/dist/core/storage/routing.js.map +1 -1
- package/dist/core/storage/sessions.d.ts +17 -0
- package/dist/core/storage/sessions.d.ts.map +1 -1
- package/dist/core/storage/sessions.js +64 -0
- package/dist/core/storage/sessions.js.map +1 -1
- package/dist/core/storage/skills.d.ts +4 -0
- package/dist/core/storage/skills.d.ts.map +1 -1
- package/dist/core/storage/skills.js +10 -2
- package/dist/core/storage/skills.js.map +1 -1
- package/dist/core/storage/sqlite.d.ts +5 -0
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +6 -0
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/core/storage/tasks.d.ts.map +1 -1
- package/dist/core/storage/tasks.js +2 -0
- package/dist/core/storage/tasks.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +19 -4
- package/dist/daemon/index.js.map +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +13 -2
- package/dist/skills/registry.js.map +1 -1
- package/dist/skills/semantic-matcher.d.ts +2 -2
- package/dist/skills/semantic-matcher.d.ts.map +1 -1
- package/dist/skills/semantic-matcher.js +14 -19
- package/dist/skills/semantic-matcher.js.map +1 -1
- package/dist/skills/upgrade-engine.d.ts +3 -1
- package/dist/skills/upgrade-engine.d.ts.map +1 -1
- package/dist/skills/upgrade-engine.js +25 -14
- package/dist/skills/upgrade-engine.js.map +1 -1
- package/dist/web/analytics/weekly-report.d.ts.map +1 -1
- package/dist/web/analytics/weekly-report.js +21 -29
- package/dist/web/analytics/weekly-report.js.map +1 -1
- package/dist/web/routes/patch.d.ts.map +1 -1
- package/dist/web/routes/patch.js +32 -2
- package/dist/web/routes/patch.js.map +1 -1
- package/dist/web/routes/sessions.d.ts.map +1 -1
- package/dist/web/routes/sessions.js +9 -7
- package/dist/web/routes/sessions.js.map +1 -1
- package/dist/web/routes/trace.d.ts.map +1 -1
- package/dist/web/routes/trace.js +2 -3
- package/dist/web/routes/trace.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +3 -2
- package/dist/web/server.js.map +1 -1
- package/package.json +12 -2
- package/scripts/postinstall.cjs +21 -0
- package/.claude/CLAUDE.md +0 -17
- package/.eslintrc.js +0 -23
- package/.prettierrc +0 -8
- package/ARCHITECTURE_ISSUES.md +0 -249
- package/CLAUDE.md +0 -265
- package/CLAUDE.md.backup +0 -488
- package/docs/concurrent-agents.md +0 -129
- package/docs/design/architecture-review-20260516.md +0 -232
- package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
- package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
- package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
- package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
- package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
- package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
- package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
- package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
- package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
- package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
- package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
- package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
- package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
- package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
- package/docs/design/skill-ai-upgrade-spec-20260518-1930.md +0 -297
- package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
- package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
- package/docs/implementation/daemon-skill-sync-changelog-20260518-2000.md +0 -22
- package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
- package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
- package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
- package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
- package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
- package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
- package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
- package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
- package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
- package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
- package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
- package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
- package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
- package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
- package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
- package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
- package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
- package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
- package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
- package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
- package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
- package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
- package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
- package/docs/implementation/skill-ai-upgrade-changelog-20260518-1930.md +0 -49
- package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
- package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
- package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
- package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
- package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
- package/docs/reviews/claudemd-template-sync.md +0 -54
- package/docs/reviews/task-title-summary.md +0 -92
- package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
- package/docs/reviews/tasks-filter-pagination.md +0 -80
- package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
- package/docs/ruflo-learning-strategy.md +0 -322
- package/docs/skills-deduplication-analysis.md +0 -83
- package/docs/skills-multiformat-support.md +0 -177
- package/docs/skills-third-party.md +0 -183
- package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
- package/forge +0 -321
- package/playwright.config.ts +0 -40
- package/scripts/demo-v2.ts +0 -91
- package/scripts/dev-daemon.sh +0 -232
- package/scripts/dev-web.ts +0 -109
- package/scripts/e2e-mcp-link.ts +0 -423
- package/scripts/e2e-methodology-quality.ts +0 -253
- package/scripts/e2e-routing.ts +0 -456
- package/scripts/e2e-user-methodology.ts +0 -326
- package/scripts/e2e-web-workflows.ts +0 -299
- package/scripts/migrate-legacy-to-dynamic.sql +0 -108
- package/scripts/regenerate-execution-docs.ts +0 -116
- package/scripts/sync-agent-skills.ts +0 -193
- package/scripts/test-hook.sh +0 -71
- package/scripts/verify-skill-loading.ts +0 -62
- package/src/claudemd/claudemd-generator.ts +0 -568
- package/src/claudemd/convention-extractor.ts +0 -69
- package/src/claudemd/index.ts +0 -35
- package/src/claudemd/persona-manager.ts +0 -88
- package/src/claudemd/resume-manager.ts +0 -236
- package/src/claudemd/tech-detector.ts +0 -220
- package/src/claudemd/templates/swarm-protocol.md +0 -222
- package/src/cli/commands/claudemd.ts +0 -84
- package/src/cli/commands/config.ts +0 -46
- package/src/cli/commands/daemon.ts +0 -310
- package/src/cli/commands/executions.ts +0 -115
- package/src/cli/commands/init.ts +0 -204
- package/src/cli/commands/logs.ts +0 -181
- package/src/cli/commands/mcp.ts +0 -242
- package/src/cli/commands/menu.ts +0 -357
- package/src/cli/commands/skills.ts +0 -328
- package/src/cli/commands/stats.ts +0 -73
- package/src/cli/commands/status.ts +0 -69
- package/src/cli/commands/template.ts +0 -77
- package/src/cli/commands/trace.ts +0 -148
- package/src/cli/index.ts +0 -42
- package/src/cli/init/hook-manager.ts +0 -132
- package/src/core/ai/provider.ts +0 -308
- package/src/core/ai/types.ts +0 -51
- package/src/core/config.ts +0 -124
- package/src/core/constants.ts +0 -67
- package/src/core/event-fields.ts +0 -32
- package/src/core/queue/index.ts +0 -192
- package/src/core/storage/base.ts +0 -302
- package/src/core/storage/events.ts +0 -434
- package/src/core/storage/injections.ts +0 -78
- package/src/core/storage/maintenance.ts +0 -59
- package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
- package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
- package/src/core/storage/performance-indexes.sql +0 -23
- package/src/core/storage/routing.ts +0 -322
- package/src/core/storage/rows.ts +0 -112
- package/src/core/storage/schema.sql +0 -224
- package/src/core/storage/sessions.ts +0 -168
- package/src/core/storage/skills.ts +0 -233
- package/src/core/storage/sqlite.ts +0 -293
- package/src/core/storage/tasks.ts +0 -318
- package/src/core/storage/token-usage.ts +0 -93
- package/src/core/types.ts +0 -181
- package/src/core/utils/error-handler.ts +0 -257
- package/src/core/utils/forge-resume-block.ts +0 -74
- package/src/core/utils/format.ts +0 -69
- package/src/core/utils/git.ts +0 -23
- package/src/core/utils/logger.ts +0 -134
- package/src/core/utils/lru-cache.ts +0 -54
- package/src/core/utils/path.ts +0 -19
- package/src/core/utils/session.ts +0 -26
- package/src/core/utils/time.ts +0 -37
- package/src/core/utils/token-tracker.ts +0 -97
- package/src/daemon/event-parser.ts +0 -36
- package/src/daemon/handlers/history-exporter.ts +0 -117
- package/src/daemon/handlers/post-tool-use.ts +0 -54
- package/src/daemon/handlers/stop.ts +0 -208
- package/src/daemon/handlers/user-prompt.ts +0 -178
- package/src/daemon/hook-sync.ts +0 -91
- package/src/daemon/index.ts +0 -312
- package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
- package/src/daemon/launchd-installer.ts +0 -260
- package/src/daemon/lifecycle.ts +0 -128
- package/src/daemon/router.ts +0 -40
- package/src/daemon/server.ts +0 -196
- package/src/daemon/services/task-segmenter.ts +0 -112
- package/src/daemon/skill-sync.ts +0 -88
- package/src/hooks/hook-lib.sh +0 -118
- package/src/hooks/notification.sh +0 -35
- package/src/hooks/post-tool-use.sh +0 -61
- package/src/hooks/pre-tool-use.sh +0 -63
- package/src/hooks/stop.sh +0 -43
- package/src/hooks/user-prompt-submit.sh +0 -69
- package/src/mcp/server.ts +0 -322
- package/src/skills/index.ts +0 -2
- package/src/skills/invocation-guard.ts +0 -177
- package/src/skills/matcher.ts +0 -148
- package/src/skills/official/code-simplifier.md +0 -52
- package/src/skills/official/find-skills.md +0 -142
- package/src/skills/official/official-api-design.md +0 -30
- package/src/skills/official/official-architecture-decision.md +0 -41
- package/src/skills/official/official-bmad.md +0 -118
- package/src/skills/official/official-db-schema-design.md +0 -34
- package/src/skills/official/official-debug.md +0 -25
- package/src/skills/official/official-doc-driven.md +0 -31
- package/src/skills/official/official-harness-engineering.md +0 -108
- package/src/skills/official/official-performance-optimization.md +0 -30
- package/src/skills/official/official-pr-review.md +0 -35
- package/src/skills/official/official-release-checklist.md +0 -30
- package/src/skills/official/official-security-hardening.md +0 -32
- package/src/skills/official/official-spec-driven-design.md +0 -31
- package/src/skills/official/planning-with-files.md +0 -241
- package/src/skills/official/ui-ux-pro-max.md +0 -105
- package/src/skills/official/webapp-testing.md +0 -96
- package/src/skills/official-skills.ts +0 -89
- package/src/skills/registry.ts +0 -355
- package/src/skills/semantic-matcher.ts +0 -234
- package/src/skills/tools/pipeline-suggest.ts +0 -226
- package/src/skills/tools/skill-invoke.ts +0 -168
- package/src/skills/tools/skill-list.ts +0 -59
- package/src/skills/upgrade-engine.ts +0 -541
- package/src/skills/upgrade-prompt.ts +0 -84
- package/src/templates/go.yaml +0 -53
- package/src/templates/python.yaml +0 -59
- package/src/templates/react.yaml +0 -55
- package/src/templates/template-manager.ts +0 -170
- package/src/web/analytics/anti-pattern-detector.ts +0 -367
- package/src/web/analytics/drift-detector.ts +0 -219
- package/src/web/analytics/weekly-report.ts +0 -431
- package/src/web/auth-middleware.ts +0 -54
- package/src/web/routes/_helpers.ts +0 -34
- package/src/web/routes/ai.ts +0 -204
- package/src/web/routes/auth.ts +0 -22
- package/src/web/routes/drift.ts +0 -25
- package/src/web/routes/error-handler.ts +0 -120
- package/src/web/routes/events.ts +0 -47
- package/src/web/routes/insights.ts +0 -43
- package/src/web/routes/patch.ts +0 -117
- package/src/web/routes/reports.ts +0 -34
- package/src/web/routes/rules.ts +0 -76
- package/src/web/routes/sessions.ts +0 -250
- package/src/web/routes/skill-stats.ts +0 -92
- package/src/web/routes/skills.ts +0 -350
- package/src/web/routes/static.ts +0 -67
- package/src/web/routes/stats.ts +0 -50
- package/src/web/routes/status.ts +0 -30
- package/src/web/routes/tasks.ts +0 -193
- package/src/web/routes/token-usage.ts +0 -20
- package/src/web/routes/trace.ts +0 -126
- package/src/web/routes/types.ts +0 -57
- package/src/web/server.ts +0 -134
- package/src/web/ssrf-guard.ts +0 -112
- package/src/web/static/index.html +0 -3251
- package/src/web/static/vendor/chart.umd.min.js +0 -20
- package/tests/e2e/dashboard.spec.ts +0 -205
- package/tests/e2e/routing-skill-e2e.test.ts +0 -39
- package/tests/helpers/mock-ai.ts +0 -92
- package/tests/helpers/mock-storage.ts +0 -159
- package/tests/integration/claudemd-generator.test.ts +0 -90
- package/tests/integration/queue-replay.integration.test.ts +0 -193
- package/tests/integration/tasks-filter.integration.test.ts +0 -154
- package/tests/integration/web-analytics.integration.test.ts +0 -133
- package/tests/integration/web-stats.integration.test.ts +0 -135
- package/tests/integration/web-trace.integration.test.ts +0 -175
- package/tests/performance/database.benchmark.ts +0 -161
- package/tests/semantic-matcher.test.ts +0 -99
- package/tests/skill-matcher.test.ts +0 -110
- package/tests/unit/ai-provider-retry.test.ts +0 -194
- package/tests/unit/ai-provider-vision.test.ts +0 -224
- package/tests/unit/claudemd-generator.test.ts +0 -68
- package/tests/unit/cli-mcp.test.ts +0 -141
- package/tests/unit/core/forge-paths.test.ts +0 -99
- package/tests/unit/daemon/hook-sync.test.ts +0 -71
- package/tests/unit/daemon/post-tool-use.test.ts +0 -121
- package/tests/unit/daemon/skill-sync.test.ts +0 -75
- package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
- package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
- package/tests/unit/event-fields.test.ts +0 -88
- package/tests/unit/event-parser.test.ts +0 -55
- package/tests/unit/handlers.test.ts +0 -171
- package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
- package/tests/unit/invocation-guard.test.ts +0 -125
- package/tests/unit/queue.test.ts +0 -272
- package/tests/unit/router.test.ts +0 -138
- package/tests/unit/security.test.ts +0 -128
- package/tests/unit/skill-invocations-workflow.test.ts +0 -495
- package/tests/unit/skill-registry.test.ts +0 -94
- package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
- package/tests/unit/skills/official-skills-loader.test.ts +0 -126
- package/tests/unit/skills/registry-multiformat.test.ts +0 -92
- package/tests/unit/skills/upgrade-engine-parse.test.ts +0 -138
- package/tests/unit/skills/upgrade-engine.test.ts +0 -401
- package/tests/unit/skills/upgrade-prompt.test.ts +0 -89
- package/tests/unit/socket-server.test.ts +0 -183
- package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
- package/tests/unit/storage/migration-idempotent.test.ts +0 -304
- package/tests/unit/storage/routing-aggregates.test.ts +0 -276
- package/tests/unit/storage/routing.test.ts +0 -117
- package/tests/unit/storage/schema-missing.test.ts +0 -81
- package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
- package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
- package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
- package/tests/unit/storage/skills-aggregates.test.ts +0 -104
- package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
- package/tests/unit/storage/task-operations-counts.test.ts +0 -46
- package/tests/unit/storage/tasks-getById.test.ts +0 -343
- package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
- package/tests/unit/storage.test.ts +0 -172
- package/tests/unit/token-usage.test.ts +0 -144
- package/tests/unit/type-guards.test.ts +0 -201
- package/tests/unit/utils/format.test.ts +0 -189
- package/tests/unit/utils/session.test.ts +0 -89
- package/tests/unit/utils/time.test.ts +0 -112
- package/tests/unit/web/navigation-back-contract.test.ts +0 -134
- package/tests/unit/web/routes-auth.test.ts +0 -93
- package/tests/unit/web/routes-events.test.ts +0 -101
- package/tests/unit/web/routes-rules.test.ts +0 -182
- package/tests/unit/web/routes-sessions.test.ts +0 -181
- package/tests/unit/web/routes-skill-stats.test.ts +0 -179
- package/tests/unit/web/routes-stats.test.ts +0 -92
- package/tests/unit/web/routes-tasks.test.ts +0 -385
- package/tests/unit/web/task-title-contract.test.ts +0 -210
- package/tests/unit/web/tasks-component-contract.test.ts +0 -179
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -21
- package/vitest.integration.config.ts +0 -16
- package/web/CLAUDE.md +0 -20
- package/web/index.html +0 -13
- package/web/package-lock.json +0 -4854
- package/web/package.json +0 -35
- package/web/postcss.config.js +0 -6
- package/web/src/App.tsx +0 -110
- package/web/src/components/CodeBlock.tsx +0 -31
- package/web/src/components/Confirm.tsx +0 -96
- package/web/src/components/Drawer.tsx +0 -60
- package/web/src/components/Layout.tsx +0 -145
- package/web/src/components/MarkdownRenderer.tsx +0 -77
- package/web/src/components/SearchInput.tsx +0 -31
- package/web/src/components/SessionDetailContent.tsx +0 -157
- package/web/src/components/Toast.tsx +0 -92
- package/web/src/index.css +0 -19
- package/web/src/main.tsx +0 -31
- package/web/src/pages/AIConfig.tsx +0 -233
- package/web/src/pages/Dashboard.tsx +0 -572
- package/web/src/pages/Events.tsx +0 -271
- package/web/src/pages/Reports.tsx +0 -428
- package/web/src/pages/SessionDetail.tsx +0 -162
- package/web/src/pages/Sessions.tsx +0 -205
- package/web/src/pages/Skills.tsx +0 -180
- package/web/src/pages/TaskDetail.tsx +0 -515
- package/web/src/pages/Tasks.tsx +0 -415
- package/web/src/utils/auth.ts +0 -59
- package/web/src/utils/export.ts +0 -54
- package/web/src/utils/navigation.ts +0 -25
- package/web/src/utils/task-title.ts +0 -49
- package/web/src/utils/time.ts +0 -13
- package/web/tailwind.config.js +0 -11
- package/web/tsconfig.json +0 -21
- package/web/tsconfig.node.json +0 -10
- package/web/vite.config.ts +0 -76
- package/winspan-claude-forge-8.43.0.tgz +0 -0
package/web/src/pages/Tasks.tsx
DELETED
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import { useState, useMemo, useCallback } from 'react'
|
|
3
|
-
import { useNavigate, useSearchParams, useLocation } from 'react-router-dom'
|
|
4
|
-
import { formatDistanceToNow } from 'date-fns'
|
|
5
|
-
import { parseUTC } from '../utils/time'
|
|
6
|
-
import { zhCN } from 'date-fns/locale'
|
|
7
|
-
import {
|
|
8
|
-
Clock,
|
|
9
|
-
Activity,
|
|
10
|
-
CheckCircle2,
|
|
11
|
-
Loader2,
|
|
12
|
-
ChevronLeft,
|
|
13
|
-
ChevronRight,
|
|
14
|
-
FolderOpen,
|
|
15
|
-
} from 'lucide-react'
|
|
16
|
-
import SearchInput from '../components/SearchInput'
|
|
17
|
-
import clsx from 'clsx'
|
|
18
|
-
import { normalizeTaskTitle } from '../utils/task-title'
|
|
19
|
-
|
|
20
|
-
// ── Types ────────────────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
interface Task {
|
|
23
|
-
id: string
|
|
24
|
-
title: string
|
|
25
|
-
status: 'active' | 'completed' | 'abandoned'
|
|
26
|
-
start_time: string
|
|
27
|
-
end_time?: string
|
|
28
|
-
event_count: number
|
|
29
|
-
session_id: string
|
|
30
|
-
project_path?: string
|
|
31
|
-
first_prompt?: string | null
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface TaskPage {
|
|
35
|
-
items: Task[]
|
|
36
|
-
total: number
|
|
37
|
-
has_more: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
const PAGE_SIZES = [20, 50, 100] as const
|
|
43
|
-
const DEFAULT_PAGE_SIZE = 50
|
|
44
|
-
|
|
45
|
-
type TimePreset = '1h' | '24h' | '7d' | 'custom' | ''
|
|
46
|
-
|
|
47
|
-
function presetToFrom(preset: TimePreset): string {
|
|
48
|
-
const now = Date.now()
|
|
49
|
-
if (preset === '1h') return new Date(now - 3600_000).toISOString()
|
|
50
|
-
if (preset === '24h') return new Date(now - 86_400_000).toISOString()
|
|
51
|
-
if (preset === '7d') return new Date(now - 7 * 86_400_000).toISOString()
|
|
52
|
-
return ''
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ── Fetch ─────────────────────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
async function fetchTasks(params: URLSearchParams): Promise<TaskPage> {
|
|
58
|
-
const res = await fetch(`/api/tasks?${params.toString()}`)
|
|
59
|
-
if (!res.ok) throw new Error('Failed to fetch tasks')
|
|
60
|
-
return res.json() as Promise<TaskPage>
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function fetchProjects(): Promise<string[]> {
|
|
64
|
-
const res = await fetch('/api/tasks/projects')
|
|
65
|
-
if (!res.ok) throw new Error('Failed to fetch projects')
|
|
66
|
-
return res.json() as Promise<string[]>
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
function shortPath(p: string): string {
|
|
72
|
-
const parts = p.replace(/\\/g, '/').split('/').filter(Boolean)
|
|
73
|
-
if (parts.length <= 2) return p
|
|
74
|
-
return '…/' + parts.slice(-2).join('/')
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export default function Tasks() {
|
|
78
|
-
const navigate = useNavigate()
|
|
79
|
-
const location = useLocation()
|
|
80
|
-
const [searchParams, setSearchParams] = useSearchParams()
|
|
81
|
-
|
|
82
|
-
// ── URL-synced state ──────────────────────────────────────────────────────
|
|
83
|
-
const page = Math.max(1, Number(searchParams.get('page') ?? '1'))
|
|
84
|
-
const pageSize = (PAGE_SIZES as readonly number[]).includes(Number(searchParams.get('size') ?? ''))
|
|
85
|
-
? Number(searchParams.get('size'))
|
|
86
|
-
: DEFAULT_PAGE_SIZE
|
|
87
|
-
const selectedProjects = searchParams.getAll('project')
|
|
88
|
-
const timePreset = (searchParams.get('preset') ?? '') as TimePreset
|
|
89
|
-
const fromParam = searchParams.get('from') ?? ''
|
|
90
|
-
const toParam = searchParams.get('to') ?? ''
|
|
91
|
-
const searchVal = searchParams.get('search') ?? ''
|
|
92
|
-
|
|
93
|
-
// ── Local state for project dropdown ─────────────────────────────────────
|
|
94
|
-
const [projectDropdownOpen, setProjectDropdownOpen] = useState(false)
|
|
95
|
-
|
|
96
|
-
// ── Patch URL helper ──────────────────────────────────────────────────────
|
|
97
|
-
const patchSearch = useCallback((patch: Record<string, string | string[] | null>) => {
|
|
98
|
-
setSearchParams(prev => {
|
|
99
|
-
const next = new URLSearchParams(prev)
|
|
100
|
-
for (const [k, v] of Object.entries(patch)) {
|
|
101
|
-
if (v === null || v === '') {
|
|
102
|
-
next.delete(k)
|
|
103
|
-
} else if (Array.isArray(v)) {
|
|
104
|
-
next.delete(k)
|
|
105
|
-
v.forEach(val => next.append(k, val))
|
|
106
|
-
} else {
|
|
107
|
-
next.set(k, v)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return next
|
|
111
|
-
}, { replace: true })
|
|
112
|
-
}, [setSearchParams])
|
|
113
|
-
|
|
114
|
-
// ── Compute effective from/to ─────────────────────────────────────────────
|
|
115
|
-
const effectiveFrom = timePreset && timePreset !== 'custom' ? presetToFrom(timePreset) : fromParam
|
|
116
|
-
const effectiveTo = timePreset && timePreset !== 'custom' ? '' : toParam
|
|
117
|
-
|
|
118
|
-
// ── Build query params for /api/tasks ────────────────────────────────────
|
|
119
|
-
const queryParams = useMemo(() => {
|
|
120
|
-
const p = new URLSearchParams()
|
|
121
|
-
p.set('limit', String(pageSize))
|
|
122
|
-
p.set('offset', String((page - 1) * pageSize))
|
|
123
|
-
selectedProjects.forEach(pr => p.append('project', pr))
|
|
124
|
-
if (effectiveFrom) p.set('from', effectiveFrom)
|
|
125
|
-
if (effectiveTo) p.set('to', effectiveTo)
|
|
126
|
-
if (searchVal) p.set('search', searchVal)
|
|
127
|
-
return p
|
|
128
|
-
}, [page, pageSize, selectedProjects, effectiveFrom, effectiveTo, searchVal])
|
|
129
|
-
|
|
130
|
-
// ── Queries ───────────────────────────────────────────────────────────────
|
|
131
|
-
const { data, isLoading, error } = useQuery<TaskPage>({
|
|
132
|
-
queryKey: ['tasks', queryParams.toString()],
|
|
133
|
-
queryFn: () => fetchTasks(queryParams),
|
|
134
|
-
refetchInterval: 15_000,
|
|
135
|
-
placeholderData: prev => prev,
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
const { data: projects = [] } = useQuery<string[]>({
|
|
139
|
-
queryKey: ['task-projects'],
|
|
140
|
-
queryFn: fetchProjects,
|
|
141
|
-
staleTime: 60_000,
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
const tasks = data?.items ?? []
|
|
145
|
-
const total = data?.total ?? 0
|
|
146
|
-
const hasMore = data?.has_more ?? false
|
|
147
|
-
const totalPages = Math.ceil(total / pageSize) || 1
|
|
148
|
-
|
|
149
|
-
// ── Event handlers ────────────────────────────────────────────────────────
|
|
150
|
-
const handleSearchChange = (val: string) => {
|
|
151
|
-
patchSearch({ search: val || null, page: '1' })
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const toggleProject = (proj: string) => {
|
|
155
|
-
const next = selectedProjects.includes(proj)
|
|
156
|
-
? selectedProjects.filter(p => p !== proj)
|
|
157
|
-
: [...selectedProjects, proj]
|
|
158
|
-
patchSearch({ project: next.length ? next : null, page: '1' })
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const clearProjects = () => patchSearch({ project: null, page: '1' })
|
|
162
|
-
|
|
163
|
-
const handlePreset = (preset: TimePreset) => {
|
|
164
|
-
patchSearch({ preset: preset || null, from: null, to: null, page: '1' })
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const handlePageSize = (size: number) => {
|
|
168
|
-
patchSearch({ size: String(size), page: '1' })
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const goPage = (p: number) => {
|
|
172
|
-
patchSearch({ page: String(Math.max(1, Math.min(p, totalPages))) })
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ── Status icon ───────────────────────────────────────────────────────────
|
|
176
|
-
const getStatusIcon = (status: string) => {
|
|
177
|
-
switch (status) {
|
|
178
|
-
case 'completed':
|
|
179
|
-
return <CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
180
|
-
case 'active':
|
|
181
|
-
return <Loader2 className="h-4 w-4 text-blue-600 animate-spin" />
|
|
182
|
-
default:
|
|
183
|
-
return <Activity className="h-4 w-4 text-gray-400" />
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── Loading / error states ────────────────────────────────────────────────
|
|
188
|
-
if (isLoading && !data) {
|
|
189
|
-
return (
|
|
190
|
-
<div>
|
|
191
|
-
<h1 className="text-2xl font-bold text-gray-900 mb-6">任务</h1>
|
|
192
|
-
<div className="bg-white rounded-lg shadow p-6">
|
|
193
|
-
<p className="text-gray-600">加载中...</p>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (error) {
|
|
200
|
-
return (
|
|
201
|
-
<div>
|
|
202
|
-
<h1 className="text-2xl font-bold text-gray-900 mb-6">任务</h1>
|
|
203
|
-
<div className="bg-white rounded-lg shadow p-6">
|
|
204
|
-
<p className="text-red-600">加载失败: {(error as Error).message}</p>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── Render ────────────────────────────────────────────────────────────────
|
|
211
|
-
return (
|
|
212
|
-
<div>
|
|
213
|
-
{/* Header */}
|
|
214
|
-
<div className="flex items-center justify-between mb-4 flex-wrap gap-3">
|
|
215
|
-
<h1 className="text-2xl font-bold text-gray-900">任务</h1>
|
|
216
|
-
<SearchInput
|
|
217
|
-
value={searchVal}
|
|
218
|
-
onChange={handleSearchChange}
|
|
219
|
-
placeholder="搜索任务标题..."
|
|
220
|
-
className="w-64"
|
|
221
|
-
/>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{/* Filters bar */}
|
|
225
|
-
<div className="flex flex-wrap items-center gap-2 mb-4">
|
|
226
|
-
{/* Time presets */}
|
|
227
|
-
{(['1h', '24h', '7d'] as TimePreset[]).map(preset => (
|
|
228
|
-
<button
|
|
229
|
-
key={preset}
|
|
230
|
-
onClick={() => handlePreset(timePreset === preset ? '' : preset)}
|
|
231
|
-
className={clsx(
|
|
232
|
-
'px-3 py-1.5 text-xs font-medium rounded-md border transition-colors',
|
|
233
|
-
timePreset === preset
|
|
234
|
-
? 'bg-indigo-600 text-white border-indigo-600'
|
|
235
|
-
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
|
236
|
-
)}
|
|
237
|
-
>
|
|
238
|
-
{preset === '1h' ? '近 1 小时' : preset === '24h' ? '近 24 小时' : '近 7 天'}
|
|
239
|
-
</button>
|
|
240
|
-
))}
|
|
241
|
-
|
|
242
|
-
{/* Project multi-select */}
|
|
243
|
-
{projects.length > 0 && (
|
|
244
|
-
<div className="relative">
|
|
245
|
-
<button
|
|
246
|
-
onClick={() => setProjectDropdownOpen(v => !v)}
|
|
247
|
-
className={clsx(
|
|
248
|
-
'inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border transition-colors',
|
|
249
|
-
selectedProjects.length > 0
|
|
250
|
-
? 'bg-indigo-50 text-indigo-700 border-indigo-300'
|
|
251
|
-
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
|
252
|
-
)}
|
|
253
|
-
>
|
|
254
|
-
<FolderOpen className="h-3.5 w-3.5" />
|
|
255
|
-
{selectedProjects.length > 0
|
|
256
|
-
? `项目 (${selectedProjects.length})`
|
|
257
|
-
: '所有项目'}
|
|
258
|
-
</button>
|
|
259
|
-
{projectDropdownOpen && (
|
|
260
|
-
<>
|
|
261
|
-
<div className="fixed inset-0 z-10" onClick={() => setProjectDropdownOpen(false)} />
|
|
262
|
-
<div className="absolute left-0 mt-1 w-72 bg-white border border-gray-200 rounded-md shadow-lg z-20 max-h-64 overflow-y-auto">
|
|
263
|
-
{selectedProjects.length > 0 && (
|
|
264
|
-
<button
|
|
265
|
-
onClick={() => { clearProjects(); setProjectDropdownOpen(false) }}
|
|
266
|
-
className="w-full text-left px-3 py-2 text-xs text-indigo-600 hover:bg-indigo-50 border-b border-gray-100"
|
|
267
|
-
>
|
|
268
|
-
清除筛选
|
|
269
|
-
</button>
|
|
270
|
-
)}
|
|
271
|
-
{projects.map(proj => (
|
|
272
|
-
<label
|
|
273
|
-
key={proj}
|
|
274
|
-
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 cursor-pointer"
|
|
275
|
-
>
|
|
276
|
-
<input
|
|
277
|
-
type="checkbox"
|
|
278
|
-
className="rounded text-indigo-600"
|
|
279
|
-
checked={selectedProjects.includes(proj)}
|
|
280
|
-
onChange={() => toggleProject(proj)}
|
|
281
|
-
/>
|
|
282
|
-
<span className="truncate font-mono text-xs" title={proj}>
|
|
283
|
-
{shortPath(proj)}
|
|
284
|
-
</span>
|
|
285
|
-
</label>
|
|
286
|
-
))}
|
|
287
|
-
</div>
|
|
288
|
-
</>
|
|
289
|
-
)}
|
|
290
|
-
</div>
|
|
291
|
-
)}
|
|
292
|
-
|
|
293
|
-
{/* Active filter summary */}
|
|
294
|
-
{(selectedProjects.length > 0 || timePreset || searchVal) && (
|
|
295
|
-
<button
|
|
296
|
-
onClick={() => setSearchParams({}, { replace: true })}
|
|
297
|
-
className="px-2 py-1.5 text-xs text-gray-500 hover:text-gray-700 underline"
|
|
298
|
-
>
|
|
299
|
-
清除所有筛选
|
|
300
|
-
</button>
|
|
301
|
-
)}
|
|
302
|
-
</div>
|
|
303
|
-
|
|
304
|
-
{/* Result count + page-size selector */}
|
|
305
|
-
<div className="flex items-center justify-between mb-3 text-sm text-gray-500">
|
|
306
|
-
<span>
|
|
307
|
-
共 <span className="font-semibold text-gray-900">{total}</span> 条
|
|
308
|
-
{isLoading && <span className="ml-2 text-xs text-indigo-500">刷新中...</span>}
|
|
309
|
-
</span>
|
|
310
|
-
<div className="flex items-center gap-2">
|
|
311
|
-
<span className="text-xs">每页</span>
|
|
312
|
-
{PAGE_SIZES.map(size => (
|
|
313
|
-
<button
|
|
314
|
-
key={size}
|
|
315
|
-
onClick={() => handlePageSize(size)}
|
|
316
|
-
className={clsx(
|
|
317
|
-
'px-2 py-0.5 text-xs rounded border',
|
|
318
|
-
pageSize === size
|
|
319
|
-
? 'bg-indigo-600 text-white border-indigo-600'
|
|
320
|
-
: 'bg-white text-gray-600 border-gray-300 hover:bg-gray-50'
|
|
321
|
-
)}
|
|
322
|
-
>
|
|
323
|
-
{size}
|
|
324
|
-
</button>
|
|
325
|
-
))}
|
|
326
|
-
</div>
|
|
327
|
-
</div>
|
|
328
|
-
|
|
329
|
-
{/* Task list */}
|
|
330
|
-
<div className="grid gap-4">
|
|
331
|
-
{tasks.map((task) => (
|
|
332
|
-
<div
|
|
333
|
-
key={task.id}
|
|
334
|
-
onClick={() => navigate(`/tasks/${task.id}`, { state: { from: location.pathname + location.search } })}
|
|
335
|
-
className="bg-white rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer p-5"
|
|
336
|
-
>
|
|
337
|
-
<div className="flex items-start justify-between mb-3">
|
|
338
|
-
<div className="flex items-start gap-3 flex-1 min-w-0">
|
|
339
|
-
<div className="mt-0.5 flex-shrink-0">{getStatusIcon(task.status)}</div>
|
|
340
|
-
<div className="flex-1 min-w-0">
|
|
341
|
-
<h3 className="text-base font-semibold text-gray-900 mb-1 truncate">
|
|
342
|
-
{normalizeTaskTitle(task.title, task.first_prompt)}
|
|
343
|
-
</h3>
|
|
344
|
-
{task.project_path && (
|
|
345
|
-
<p className="text-xs text-gray-400 font-mono truncate" title={task.project_path}>
|
|
346
|
-
{shortPath(task.project_path)}
|
|
347
|
-
</p>
|
|
348
|
-
)}
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
<span className={clsx(
|
|
352
|
-
'ml-3 flex-shrink-0 px-2 py-1 text-xs font-medium rounded-full whitespace-nowrap',
|
|
353
|
-
task.status === 'completed' && 'bg-green-100 text-green-700',
|
|
354
|
-
task.status === 'active' && 'bg-blue-100 text-blue-700',
|
|
355
|
-
task.status === 'abandoned' && 'bg-gray-100 text-gray-600',
|
|
356
|
-
)}>
|
|
357
|
-
{task.status}
|
|
358
|
-
</span>
|
|
359
|
-
</div>
|
|
360
|
-
|
|
361
|
-
<div className="flex items-center gap-6 text-sm text-gray-600">
|
|
362
|
-
<span className="inline-flex items-center gap-1">
|
|
363
|
-
<Activity className="h-4 w-4" />
|
|
364
|
-
{task.event_count} 个事件
|
|
365
|
-
</span>
|
|
366
|
-
<span className="inline-flex items-center gap-1">
|
|
367
|
-
<Clock className="h-4 w-4" />
|
|
368
|
-
{task.start_time
|
|
369
|
-
? formatDistanceToNow(parseUTC(task.start_time), {
|
|
370
|
-
addSuffix: true,
|
|
371
|
-
locale: zhCN,
|
|
372
|
-
})
|
|
373
|
-
: '-'}
|
|
374
|
-
</span>
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
))}
|
|
378
|
-
|
|
379
|
-
{tasks.length === 0 && !isLoading && (
|
|
380
|
-
<div className="bg-white rounded-lg shadow p-12 text-center text-gray-500">
|
|
381
|
-
{searchVal || selectedProjects.length > 0 || timePreset
|
|
382
|
-
? '无匹配结果'
|
|
383
|
-
: '暂无任务记录'}
|
|
384
|
-
</div>
|
|
385
|
-
)}
|
|
386
|
-
</div>
|
|
387
|
-
|
|
388
|
-
{/* Pagination */}
|
|
389
|
-
{total > pageSize && (
|
|
390
|
-
<div className="flex items-center justify-between mt-6">
|
|
391
|
-
<button
|
|
392
|
-
onClick={() => goPage(page - 1)}
|
|
393
|
-
disabled={page <= 1}
|
|
394
|
-
className="inline-flex items-center gap-1 px-3 py-1.5 text-sm border rounded-md bg-white hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
395
|
-
>
|
|
396
|
-
<ChevronLeft className="h-4 w-4" />
|
|
397
|
-
上一页
|
|
398
|
-
</button>
|
|
399
|
-
<span className="text-sm text-gray-500">
|
|
400
|
-
第 {page} / {totalPages} 页
|
|
401
|
-
{hasMore ? '' : '(已到末页)'}
|
|
402
|
-
</span>
|
|
403
|
-
<button
|
|
404
|
-
onClick={() => goPage(page + 1)}
|
|
405
|
-
disabled={!hasMore}
|
|
406
|
-
className="inline-flex items-center gap-1 px-3 py-1.5 text-sm border rounded-md bg-white hover:bg-gray-50 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
407
|
-
>
|
|
408
|
-
下一页
|
|
409
|
-
<ChevronRight className="h-4 w-4" />
|
|
410
|
-
</button>
|
|
411
|
-
</div>
|
|
412
|
-
)}
|
|
413
|
-
</div>
|
|
414
|
-
)
|
|
415
|
-
}
|
package/web/src/utils/auth.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Frontend auth helper — fetches the daemon's bearer token from the
|
|
3
|
-
* local-only /api/auth/token endpoint and wraps fetch() to attach
|
|
4
|
-
* the Authorization header on write calls.
|
|
5
|
-
*
|
|
6
|
-
* Token fetch is cached for the lifetime of the page load. On 401, the
|
|
7
|
-
* cache is invalidated and refetched once before giving up.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
let cachedToken: string | null = null
|
|
11
|
-
let inflight: Promise<string | null> | null = null
|
|
12
|
-
|
|
13
|
-
async function fetchToken(): Promise<string | null> {
|
|
14
|
-
if (cachedToken) return cachedToken
|
|
15
|
-
if (inflight) return inflight
|
|
16
|
-
inflight = (async () => {
|
|
17
|
-
try {
|
|
18
|
-
const res = await fetch('/api/auth/token')
|
|
19
|
-
if (!res.ok) return null
|
|
20
|
-
const data = (await res.json()) as { token?: string }
|
|
21
|
-
cachedToken = data.token ?? null
|
|
22
|
-
return cachedToken
|
|
23
|
-
} catch {
|
|
24
|
-
return null
|
|
25
|
-
} finally {
|
|
26
|
-
inflight = null
|
|
27
|
-
}
|
|
28
|
-
})()
|
|
29
|
-
return inflight
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** 清除 token 缓存(收到 401 时调用) */
|
|
33
|
-
export function invalidateToken(): void {
|
|
34
|
-
cachedToken = null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 带鉴权的 fetch:自动附加 Authorization: Bearer <token>
|
|
39
|
-
* 写操作(POST/PUT/DELETE)应统一用这个函数
|
|
40
|
-
*/
|
|
41
|
-
export async function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
|
|
42
|
-
const token = await fetchToken()
|
|
43
|
-
const headers = new Headers(init.headers ?? {})
|
|
44
|
-
if (token && !headers.has('Authorization')) {
|
|
45
|
-
headers.set('Authorization', `Bearer ${token}`)
|
|
46
|
-
}
|
|
47
|
-
const res = await fetch(input, { ...init, headers })
|
|
48
|
-
if (res.status === 401) {
|
|
49
|
-
// 失效重试一次(token 可能被 daemon 轮换)
|
|
50
|
-
invalidateToken()
|
|
51
|
-
const retryToken = await fetchToken()
|
|
52
|
-
if (retryToken) {
|
|
53
|
-
const retryHeaders = new Headers(init.headers ?? {})
|
|
54
|
-
retryHeaders.set('Authorization', `Bearer ${retryToken}`)
|
|
55
|
-
return fetch(input, { ...init, headers: retryHeaders })
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return res
|
|
59
|
-
}
|
package/web/src/utils/export.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 导出工具函数:JSON / CSV
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export function downloadJSON(filename: string, data: unknown): void {
|
|
6
|
-
const content = JSON.stringify(data, null, 2)
|
|
7
|
-
const blob = new Blob([content], { type: 'application/json' })
|
|
8
|
-
triggerDownload(filename, blob)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function downloadCSV<T extends object>(filename: string, rows: T[]): void {
|
|
12
|
-
if (!rows || rows.length === 0) {
|
|
13
|
-
downloadJSON(filename.replace(/\.csv$/, '.json'), rows)
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const headers = Object.keys(rows[0]) as (keyof T & string)[]
|
|
18
|
-
const csv = [
|
|
19
|
-
headers.join(','),
|
|
20
|
-
...rows.map(row =>
|
|
21
|
-
headers.map(h => escapeCSV(row[h])).join(',')
|
|
22
|
-
),
|
|
23
|
-
].join('\n')
|
|
24
|
-
|
|
25
|
-
// 添加 BOM 让 Excel 识别 UTF-8
|
|
26
|
-
const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8' })
|
|
27
|
-
triggerDownload(filename, blob)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function escapeCSV(value: unknown): string {
|
|
31
|
-
if (value === null || value === undefined) return ''
|
|
32
|
-
const str = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
33
|
-
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
34
|
-
return `"${str.replace(/"/g, '""')}"`
|
|
35
|
-
}
|
|
36
|
-
return str
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function triggerDownload(filename: string, blob: Blob): void {
|
|
40
|
-
const url = URL.createObjectURL(blob)
|
|
41
|
-
const a = document.createElement('a')
|
|
42
|
-
a.href = url
|
|
43
|
-
a.download = filename
|
|
44
|
-
document.body.appendChild(a)
|
|
45
|
-
a.click()
|
|
46
|
-
document.body.removeChild(a)
|
|
47
|
-
URL.revokeObjectURL(url)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function getTimestamp(): string {
|
|
51
|
-
const now = new Date()
|
|
52
|
-
const pad = (n: number) => String(n).padStart(2, '0')
|
|
53
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
|
|
54
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Navigation helpers for react-router-based back-navigation with state preservation.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Resolves the "back" URL from react-router navigation state.
|
|
7
|
-
*
|
|
8
|
-
* Usage in a detail page:
|
|
9
|
-
* const location = useLocation()
|
|
10
|
-
* const backTo = resolveBackTo(location.state)
|
|
11
|
-
* // <Link to={backTo}>Back</Link>
|
|
12
|
-
*
|
|
13
|
-
* The navigation state is set by the list page when navigating forward:
|
|
14
|
-
* navigate(`/tasks/${id}`, { state: { from: location.pathname + location.search } })
|
|
15
|
-
*
|
|
16
|
-
* @param state - location.state from react-router (typed as unknown for safety)
|
|
17
|
-
* @param fallback - URL to use when state carries no valid `from` field (default: '/tasks')
|
|
18
|
-
*/
|
|
19
|
-
export function resolveBackTo(state: unknown, fallback = '/tasks'): string {
|
|
20
|
-
if (state !== null && state !== undefined && typeof state === 'object' && 'from' in state) {
|
|
21
|
-
const from = (state as { from?: unknown }).from
|
|
22
|
-
if (typeof from === 'string' && from.length > 0) return from
|
|
23
|
-
}
|
|
24
|
-
return fallback
|
|
25
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* normalizeTaskTitle — pure helper for rendering task titles.
|
|
3
|
-
*
|
|
4
|
-
* Some tasks are stored with their title set to the raw <task-notification>
|
|
5
|
-
* XML blob (sub-agent completion callbacks). When the blob is truncated to
|
|
6
|
-
* 19 characters (just the opening tag), the title is unreadable.
|
|
7
|
-
*
|
|
8
|
-
* This function detects that case and falls back to `firstPrompt` — the first
|
|
9
|
-
* UserPromptSubmit user_prompt for the task, supplied from the backend via the
|
|
10
|
-
* `first_prompt` field in /api/tasks responses.
|
|
11
|
-
*
|
|
12
|
-
* No React dependency. Safe to import in unit tests.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const MAX_LEN = 80
|
|
16
|
-
|
|
17
|
-
export function normalizeTaskTitle(
|
|
18
|
-
title: string | null | undefined,
|
|
19
|
-
firstPrompt?: string | null,
|
|
20
|
-
): string {
|
|
21
|
-
if (!title?.trim()) return '(无标题)'
|
|
22
|
-
const trimmed = title.trim()
|
|
23
|
-
|
|
24
|
-
// Detect <task-notification> XML callback blobs (including bare 19-char token)
|
|
25
|
-
if (trimmed.startsWith('<task-notification>')) {
|
|
26
|
-
// First, try to extract <summary> from the title itself (full XML blob)
|
|
27
|
-
const matchInTitle = trimmed.match(/<summary>([\s\S]*?)<\/summary>/)
|
|
28
|
-
if (matchInTitle && matchInTitle[1].trim()) {
|
|
29
|
-
const summary = matchInTitle[1].trim()
|
|
30
|
-
return summary.length > MAX_LEN ? summary.slice(0, MAX_LEN) + '…' : summary
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Title has no summary (e.g. bare 19-char token) — try firstPrompt
|
|
34
|
-
if (firstPrompt) {
|
|
35
|
-
const matchInPrompt = firstPrompt.match(/<summary>([\s\S]*?)<\/summary>/)
|
|
36
|
-
if (matchInPrompt && matchInPrompt[1].trim()) {
|
|
37
|
-
const summary = matchInPrompt[1].trim()
|
|
38
|
-
return summary.length > MAX_LEN ? summary.slice(0, MAX_LEN) + '…' : summary
|
|
39
|
-
}
|
|
40
|
-
// Has firstPrompt but no <summary> tag — show head of prompt
|
|
41
|
-
const head = firstPrompt.trim().slice(0, 60).replace(/\s+/g, ' ')
|
|
42
|
-
return `(子任务回调) — ${head}${firstPrompt.trim().length > 60 ? '…' : ''}`
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return '(子任务回调)'
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return trimmed.length > MAX_LEN ? trimmed.slice(0, MAX_LEN) + '…' : trimmed
|
|
49
|
-
}
|
package/web/src/utils/time.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite stores ISO timestamps without 'Z' suffix.
|
|
3
|
-
* JS `new Date()` treats such strings as local time, causing an offset.
|
|
4
|
-
* This helper ensures the timestamp is always parsed as UTC.
|
|
5
|
-
*/
|
|
6
|
-
export function parseUTC(timestamp: string | null | undefined): Date {
|
|
7
|
-
if (!timestamp) return new Date(0);
|
|
8
|
-
const s = timestamp.trim();
|
|
9
|
-
if (s.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(s)) {
|
|
10
|
-
return new Date(s);
|
|
11
|
-
}
|
|
12
|
-
return new Date(s + 'Z');
|
|
13
|
-
}
|
package/web/tailwind.config.js
DELETED
package/web/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"useDefineForClassFields": true,
|
|
5
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"moduleResolution": "bundler",
|
|
9
|
-
"allowImportingTsExtensions": true,
|
|
10
|
-
"resolveJsonModule": true,
|
|
11
|
-
"isolatedModules": true,
|
|
12
|
-
"noEmit": true,
|
|
13
|
-
"jsx": "react-jsx",
|
|
14
|
-
"strict": true,
|
|
15
|
-
"noUnusedLocals": true,
|
|
16
|
-
"noUnusedParameters": true,
|
|
17
|
-
"noFallthroughCasesInSwitch": true
|
|
18
|
-
},
|
|
19
|
-
"include": ["src"],
|
|
20
|
-
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
-
}
|