patchwarden 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +194 -26
- package/README.md +160 -22
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +24 -15
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +44 -36
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +22 -0
- package/dist/config.js +98 -11
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +16 -0
- package/dist/control/middleware/auth.js +17 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +78 -0
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +287 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +12 -0
- package/dist/control/routes/process.js +487 -0
- package/dist/control/routes/sessions.d.ts +30 -0
- package/dist/control/routes/sessions.js +287 -0
- package/dist/control/routes/status.d.ts +51 -0
- package/dist/control/routes/status.js +307 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +197 -0
- package/dist/control/routes/tasks.d.ts +24 -0
- package/dist/control/routes/tasks.js +309 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +220 -0
- package/dist/control/runtime.d.ts +92 -0
- package/dist/control/runtime.js +425 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +150 -0
- package/dist/control/shared.d.ts +59 -0
- package/dist/control/shared.js +341 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2197
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +72 -37
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +614 -373
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +197 -0
- package/dist/goal/goalStatus.d.ts +8 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +221 -0
- package/dist/goal/subgoalSync.js +13 -10
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +126 -44
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +13 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +14 -6
- package/dist/runner/changeCapture.js +287 -81
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +60 -7
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +1 -1
- package/dist/runner/runTask.js +339 -287
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +52 -38
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +24 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +265 -54
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +24 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +280 -273
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +283 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +116 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +92 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +46 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +10 -14
- package/dist/tools/registry.js +47 -1651
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
- package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
- package/dist/tools/tasks/evidencePack.js +387 -0
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
- package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
- package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
- package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +48 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +1648 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +23 -0
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/desktop-app.md +175 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +168 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +82 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +97 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/examples/config.example.json +4 -2
- package/package.json +28 -11
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +447 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +39 -33
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +30 -4
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +9 -0
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
- package/scripts/generate-demo-gif.py +320 -0
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +29 -15
- package/src/assessments/assessmentStore.ts +55 -43
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +105 -11
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +35 -0
- package/src/control/middleware/static.ts +84 -0
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +329 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +505 -0
- package/src/control/routes/sessions.ts +314 -0
- package/src/control/routes/status.ts +405 -0
- package/src/control/routes/taskActions.ts +214 -0
- package/src/control/routes/tasks.ts +331 -0
- package/src/control/routes/workspace.ts +234 -0
- package/src/control/runtime.ts +508 -0
- package/src/control/server.ts +165 -0
- package/src/control/shared.ts +360 -0
- package/src/controlCenter.ts +7 -2347
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +100 -51
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +766 -492
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +263 -0
- package/src/goal/goalStatus.ts +25 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +15 -10
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +130 -44
- package/src/index.ts +7 -4
- package/src/logging.ts +20 -4
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +342 -80
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +55 -7
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +441 -292
- package/src/runner/simpleProcess.ts +68 -33
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
- package/src/runner/taskStatusStore.ts +54 -0
- package/src/runner/watch.ts +290 -57
- package/src/security/contentRedaction.ts +31 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +276 -273
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +379 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
- package/src/tools/dispatch/directDispatch.ts +168 -0
- package/src/tools/dispatch/goalDispatch.ts +128 -0
- package/src/tools/dispatch/releaseDispatch.ts +66 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +54 -1858
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- package/src/tools/tasks/evidencePack.ts +504 -0
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +58 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +204 -3
- package/ui/pages/dashboard.html +937 -79
- package/ui/pages/direct-sessions.html +609 -60
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +105 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +468 -438
- package/ui/pages/tasks.html +648 -65
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +245 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -213
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -130
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -190
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -109
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/evidencePack.js +0 -168
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -297
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -142
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -243
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -150
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/evidencePack.ts +0 -205
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
- /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
package/ui/pages/dashboard.html
CHANGED
|
@@ -133,6 +133,8 @@ html.dark {
|
|
|
133
133
|
background-color: currentColor;
|
|
134
134
|
}
|
|
135
135
|
</style>
|
|
136
|
+
<script src="/desktop-bootstrap.js"></script>
|
|
137
|
+
<link rel="stylesheet" href="/desktop.css">
|
|
136
138
|
</head>
|
|
137
139
|
<body class="flex h-screen overflow-hidden">
|
|
138
140
|
|
|
@@ -150,6 +152,10 @@ html.dark {
|
|
|
150
152
|
|
|
151
153
|
<!-- Navigation -->
|
|
152
154
|
<nav class="flex-1 flex flex-col gap-1 px-3 pt-4 overflow-y-auto">
|
|
155
|
+
<a href="/pages/getting-started.html" data-nav-key="getting-started" class="desktop-only-nav flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
156
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.87 12.87 0 0 1 22 2c0 2.72-.78 7.5-6.05 11a22.35 22.35 0 0 1-3.95 2z"/></svg>
|
|
157
|
+
<span class="truncate" data-i18n="nav.gettingStarted">开始使用</span>
|
|
158
|
+
</a>
|
|
153
159
|
|
|
154
160
|
<a href="/pages/dashboard.html" data-nav-key="dashboard" data-active="true" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
155
161
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
|
|
@@ -205,6 +211,10 @@ html.dark {
|
|
|
205
211
|
<span class="truncate" data-zh="日志" data-en="Logs">日志</span>
|
|
206
212
|
</a>
|
|
207
213
|
|
|
214
|
+
<a href="/pages/settings.html" data-nav-key="settings" class="desktop-only-nav flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
|
|
215
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4V21a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1A1.7 1.7 0 0 0 9 4.6 1.7 1.7 0 0 0 10 3V2.8h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9A1.7 1.7 0 0 0 21 10h.2v4H21a1.7 1.7 0 0 0-1.6 1z"/></svg>
|
|
216
|
+
<span class="truncate" data-i18n="nav.settings">设置</span>
|
|
217
|
+
</a>
|
|
208
218
|
</nav>
|
|
209
219
|
|
|
210
220
|
<!-- Bottom Section -->
|
|
@@ -238,46 +248,67 @@ html.dark {
|
|
|
238
248
|
<main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
|
|
239
249
|
|
|
240
250
|
<!-- ===== TOP TOOLBAR ===== -->
|
|
241
|
-
<div class="
|
|
251
|
+
<div class="pw-dashboard-toolbar mb-4">
|
|
252
|
+
<div class="pw-toolbar-actions">
|
|
242
253
|
<button data-action="start-all" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-state-success); color: var(--pw-text-inverse); font-family: var(--pw-font-sans);">
|
|
243
254
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
244
|
-
<span
|
|
255
|
+
<span data-zh="全部启动" data-en="Start All">全部启动</span>
|
|
245
256
|
</button>
|
|
246
257
|
<button data-action="stop-all" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-state-error); color: #fff; font-family: var(--pw-font-sans);">
|
|
247
258
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="1"/></svg>
|
|
248
|
-
<span
|
|
259
|
+
<span data-zh="全部停止" data-en="Stop All">全部停止</span>
|
|
249
260
|
</button>
|
|
250
261
|
<button data-action="restart-all" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-state-warning); color: var(--pw-text-inverse); font-family: var(--pw-font-sans);">
|
|
251
262
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/></svg>
|
|
252
|
-
<span
|
|
263
|
+
<span data-zh="全部重启" data-en="Restart All">全部重启</span>
|
|
253
264
|
</button>
|
|
254
265
|
<button data-action="refresh" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-bg-surface); color: var(--pw-text-primary); border: 1px solid var(--pw-border); font-family: var(--pw-font-sans);">
|
|
255
266
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/></svg>
|
|
256
|
-
<span
|
|
267
|
+
<span data-zh="刷新" data-en="Refresh">刷新</span>
|
|
257
268
|
</button>
|
|
258
|
-
|
|
269
|
+
</div>
|
|
270
|
+
<div class="pw-toolbar-repo flex items-center gap-2 px-3 py-1.5 rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); font-family: var(--pw-font-sans);">
|
|
271
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--pw-text-tertiary);"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/></svg>
|
|
272
|
+
<select id="repo-selector" class="bg-transparent text-sm outline-none cursor-pointer" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 180px;">
|
|
273
|
+
<option value=".">工作区根目录</option>
|
|
274
|
+
</select>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="pw-toolbar-links">
|
|
259
277
|
<button data-action="open-core-tunnel" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: transparent; color: var(--pw-accent); border: 1px solid var(--pw-accent); font-family: var(--pw-font-sans);">
|
|
260
278
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
261
|
-
<span
|
|
279
|
+
<span data-zh="Core 隧道" data-en="Open Core Tunnel UI">Core 隧道</span>
|
|
262
280
|
</button>
|
|
263
281
|
<button data-action="open-direct-tunnel" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: transparent; color: var(--pw-accent); border: 1px solid var(--pw-accent); font-family: var(--pw-font-sans);">
|
|
264
282
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
265
|
-
<span
|
|
283
|
+
<span data-zh="Direct 隧道" data-en="Open Direct Tunnel UI">Direct 隧道</span>
|
|
266
284
|
</button>
|
|
267
285
|
<button data-action="open-logs-folder" class="flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-bg-surface); color: var(--pw-text-primary); border: 1px solid var(--pw-border); font-family: var(--pw-font-sans);">
|
|
268
286
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2z"/></svg>
|
|
269
|
-
<span
|
|
287
|
+
<span data-zh="日志目录" data-en="Open Logs Folder">日志目录</span>
|
|
270
288
|
</button>
|
|
289
|
+
</div>
|
|
271
290
|
</div>
|
|
272
291
|
|
|
273
292
|
<!-- ===== ERROR BANNER ===== -->
|
|
274
293
|
<div id="error-banner" class="hidden mb-4 rounded-lg px-4 py-3 text-sm" style="background-color: var(--pw-state-error-bg); border: 1px solid var(--pw-state-error); color: var(--pw-state-error); font-family: var(--pw-font-sans);"></div>
|
|
275
294
|
|
|
295
|
+
<!-- ===== LIVE READINESS CARD ===== -->
|
|
296
|
+
<div class="mb-2"><h2 class="text-xs font-semibold uppercase tracking-wide" style="color:var(--pw-text-tertiary);" data-zh="运行状态" data-en="Live status">运行状态</h2></div>
|
|
297
|
+
<div id="health-score-card" class="rounded-lg mb-4" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
298
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
299
|
+
<h2 class="text-sm font-semibold truncate" data-zh="实时就绪状态" data-en="Live Readiness" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">实时就绪状态</h2>
|
|
300
|
+
<span id="health-score-badge" class="text-xs font-semibold px-2.5 py-1 rounded-full whitespace-nowrap" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover);">—</span>
|
|
301
|
+
</div>
|
|
302
|
+
<div id="health-score-body" class="px-4 py-3 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
|
|
303
|
+
<div class="text-xs" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
276
307
|
<!-- ===== SETUP CHECKLIST ===== -->
|
|
277
308
|
<div id="setup-checklist-card" class="rounded-lg mb-4" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
278
309
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
279
|
-
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);"
|
|
280
|
-
<span id="setup-checklist-summary" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);"
|
|
310
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">启动准备</h2>
|
|
311
|
+
<span id="setup-checklist-summary" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">正在检查</span>
|
|
281
312
|
</div>
|
|
282
313
|
<div id="setup-checklist-body" class="grid grid-cols-1 md:grid-cols-3 gap-0"></div>
|
|
283
314
|
</div>
|
|
@@ -296,6 +327,7 @@ html.dark {
|
|
|
296
327
|
</div>
|
|
297
328
|
|
|
298
329
|
<!-- ===== HEALTH SUGGESTIONS CARD ===== -->
|
|
330
|
+
<div class="mb-2"><h2 class="text-xs font-semibold uppercase tracking-wide" style="color:var(--pw-text-tertiary);" data-zh="待处理项" data-en="Needs attention">待处理项</h2></div>
|
|
299
331
|
<div id="suggestions-card" class="hidden rounded-lg mb-6" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
300
332
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
301
333
|
<h2 class="text-sm font-semibold truncate" data-zh="健康建议" data-en="Health Suggestions" style="color: var(--pw-text-primary);">健康建议</h2>
|
|
@@ -305,35 +337,45 @@ html.dark {
|
|
|
305
337
|
</div>
|
|
306
338
|
|
|
307
339
|
<!-- ===== V1.5 RELEASE / POLICY / LINEAGE / EVIDENCE OVERVIEW ===== -->
|
|
340
|
+
<div class="mb-2 flex items-center justify-between gap-3"><h2 class="text-xs font-semibold uppercase tracking-wide" style="color:var(--pw-text-tertiary);" data-zh="历史概览" data-en="History overview">历史概览</h2><span class="text-xs" style="color:var(--pw-text-tertiary);" data-zh="历史任务、审计和发布检查不影响实时健康" data-en="History, audits, and release checks do not affect live health">历史任务、审计和发布检查不影响实时健康</span></div>
|
|
308
341
|
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-6">
|
|
309
342
|
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
310
343
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
311
|
-
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);"
|
|
344
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">任务链路</h2>
|
|
312
345
|
<span id="lineage-count" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
313
346
|
</div>
|
|
314
|
-
<div id="lineage-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);"
|
|
347
|
+
<div id="lineage-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
315
348
|
</div>
|
|
316
349
|
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
317
350
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
318
|
-
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);"
|
|
351
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">项目策略</h2>
|
|
319
352
|
<span id="policy-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
320
353
|
</div>
|
|
321
|
-
<div id="policy-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);"
|
|
354
|
+
<div id="policy-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
322
355
|
</div>
|
|
323
356
|
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
324
357
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
325
|
-
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);"
|
|
358
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">发布检查</h2>
|
|
326
359
|
<span id="release-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
327
360
|
</div>
|
|
328
|
-
<div id="release-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);"
|
|
361
|
+
<div id="release-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
329
362
|
</div>
|
|
330
363
|
<div id="evidence-pack-card" class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
331
364
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
332
|
-
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);"
|
|
365
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">证据包</h2>
|
|
333
366
|
<span id="evidence-pack-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
334
367
|
</div>
|
|
335
|
-
<div id="evidence-pack-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);"
|
|
368
|
+
<div id="evidence-pack-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<!-- ===== STALE TASKS CARD ===== -->
|
|
373
|
+
<div id="stale-tasks-card" class="hidden rounded-lg mb-6" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); border-left: 3px solid var(--pw-state-warning);">
|
|
374
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
375
|
+
<h2 class="text-sm font-semibold truncate" data-zh="过期任务" data-en="Stale Tasks" style="color: var(--pw-text-primary);">过期任务</h2>
|
|
376
|
+
<span id="stale-tasks-count" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">—</span>
|
|
336
377
|
</div>
|
|
378
|
+
<div id="stale-tasks-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">正在加载…</div>
|
|
337
379
|
</div>
|
|
338
380
|
|
|
339
381
|
<!-- ===== TWO-COLUMN LAYOUT ===== -->
|
|
@@ -352,8 +394,12 @@ html.dark {
|
|
|
352
394
|
|
|
353
395
|
<!-- RIGHT COLUMN: 系统状态 (2/5 width) -->
|
|
354
396
|
<div class="lg:col-span-2 rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
355
|
-
<div class="px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
397
|
+
<div class="flex items-center justify-between gap-2 px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
356
398
|
<h2 class="text-sm font-semibold truncate" data-zh="系统状态" data-en="System Status" style="color: var(--pw-text-primary);">系统状态</h2>
|
|
399
|
+
<button id="btn-copy-diagnostics" class="flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-colors whitespace-nowrap" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);" type="button" title="Copy diagnostics to clipboard">
|
|
400
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
401
|
+
<span data-zh="复制诊断" data-en="Copy diagnostics">复制诊断</span>
|
|
402
|
+
</button>
|
|
357
403
|
</div>
|
|
358
404
|
<div id="system-status-body" class="px-4 py-2">
|
|
359
405
|
<div class="py-3 text-center text-sm" style="color: var(--pw-text-tertiary);">加载中…</div>
|
|
@@ -366,7 +412,7 @@ html.dark {
|
|
|
366
412
|
<div class="rounded-lg mb-6" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
367
413
|
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
368
414
|
<h2 class="text-sm font-semibold truncate" data-zh="活动日志" data-en="Activity Log" style="color: var(--pw-text-primary);">活动日志</h2>
|
|
369
|
-
<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">Core + Direct
|
|
415
|
+
<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">Core + Direct 输出摘要</span>
|
|
370
416
|
</div>
|
|
371
417
|
<div id="activity-log-body">
|
|
372
418
|
<div class="px-4 py-3 text-xs" style="color: var(--pw-text-tertiary);">加载日志中…</div>
|
|
@@ -384,6 +430,17 @@ html.dark {
|
|
|
384
430
|
</div>
|
|
385
431
|
</div>
|
|
386
432
|
|
|
433
|
+
<!-- ===== SAFE MODAL ===== -->
|
|
434
|
+
<div id="safe-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4" style="background-color: rgba(0,0,0,0.6);">
|
|
435
|
+
<div class="rounded-lg w-full max-w-2xl max-h-[80vh] flex flex-col" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); box-shadow: var(--pw-shadow-modal);">
|
|
436
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
437
|
+
<h3 id="safe-modal-title" class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">—</h3>
|
|
438
|
+
<button id="safe-modal-close" class="text-lg leading-none" style="color: var(--pw-text-tertiary);" type="button">×</button>
|
|
439
|
+
</div>
|
|
440
|
+
<div id="safe-modal-body" class="flex-1 overflow-auto px-4 py-3 text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono); white-space: pre-wrap; word-break: break-all;"></div>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
387
444
|
</main>
|
|
388
445
|
|
|
389
446
|
</div>
|
|
@@ -477,6 +534,44 @@ html.dark {
|
|
|
477
534
|
@keyframes pw-toast-out {
|
|
478
535
|
to { opacity: 0; transform: translateX(20px); }
|
|
479
536
|
}
|
|
537
|
+
/* Quick action icon buttons */
|
|
538
|
+
.pw-quick-btn {
|
|
539
|
+
display: inline-flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
justify-content: center;
|
|
542
|
+
width: 24px;
|
|
543
|
+
height: 24px;
|
|
544
|
+
border-radius: 4px;
|
|
545
|
+
border: 1px solid var(--pw-border-subtle);
|
|
546
|
+
background-color: var(--pw-bg-hover);
|
|
547
|
+
color: var(--pw-text-secondary);
|
|
548
|
+
cursor: pointer;
|
|
549
|
+
transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
|
|
550
|
+
text-decoration: none;
|
|
551
|
+
}
|
|
552
|
+
.pw-quick-btn:hover {
|
|
553
|
+
background-color: var(--pw-bg-active);
|
|
554
|
+
color: var(--pw-text-primary);
|
|
555
|
+
opacity: 1;
|
|
556
|
+
}
|
|
557
|
+
/* Acceptance status badge - more prominent than status */
|
|
558
|
+
.pw-acceptance-badge {
|
|
559
|
+
font-size: var(--pw-text-sm);
|
|
560
|
+
font-weight: var(--pw-weight-bold);
|
|
561
|
+
padding: 2px 8px;
|
|
562
|
+
border-width: 1px;
|
|
563
|
+
border-style: solid;
|
|
564
|
+
}
|
|
565
|
+
/* Stale task explanation - prominent */
|
|
566
|
+
.pw-stale-explanation {
|
|
567
|
+
font-size: var(--pw-text-sm);
|
|
568
|
+
font-weight: var(--pw-weight-medium);
|
|
569
|
+
color: var(--pw-text-primary);
|
|
570
|
+
padding: 6px 10px;
|
|
571
|
+
border-radius: var(--pw-radius-sm);
|
|
572
|
+
background-color: var(--pw-state-warning-bg);
|
|
573
|
+
border-left: 2px solid var(--pw-state-warning);
|
|
574
|
+
}
|
|
480
575
|
</style>
|
|
481
576
|
|
|
482
577
|
<script>
|
|
@@ -485,6 +580,7 @@ html.dark {
|
|
|
485
580
|
|
|
486
581
|
let controlToken = null;
|
|
487
582
|
let statusTimer = null;
|
|
583
|
+
let currentRepoPath = ".";
|
|
488
584
|
|
|
489
585
|
// ---------- Utilities ----------
|
|
490
586
|
function $(id) { return document.getElementById(id); }
|
|
@@ -656,26 +752,26 @@ html.dark {
|
|
|
656
752
|
{
|
|
657
753
|
label: 'tunnel-client.exe',
|
|
658
754
|
ok: !!tunnel.available,
|
|
659
|
-
value: tunnel.available ? (tunnel.path || '
|
|
660
|
-
hint: tunnel.available ? '
|
|
755
|
+
value: tunnel.available ? (tunnel.path || '已找到') : '未找到',
|
|
756
|
+
hint: tunnel.available ? '已可供桌面启动和重启使用。' : '请在设置中自动检测或选择 tunnel-client.exe。'
|
|
661
757
|
},
|
|
662
758
|
{
|
|
663
|
-
label: '
|
|
759
|
+
label: '工作区',
|
|
664
760
|
ok: !!workspaceRoot,
|
|
665
|
-
value: workspaceRoot || '
|
|
666
|
-
hint: '
|
|
761
|
+
value: workspaceRoot || '未加载',
|
|
762
|
+
hint: 'PatchWarden 任务仅在此工作区内运行。'
|
|
667
763
|
},
|
|
668
764
|
{
|
|
669
|
-
label: '
|
|
765
|
+
label: '任务监视器',
|
|
670
766
|
ok: watcher.status === 'healthy',
|
|
671
|
-
value: watcher.status || '
|
|
672
|
-
hint: watcher.status === 'healthy' ? '
|
|
767
|
+
value: watcher.status || '未运行',
|
|
768
|
+
hint: watcher.status === 'healthy' ? '任务监视器运行正常。' : (watcher.reason || '启动 Core 后,任务监视器会处理排队任务。')
|
|
673
769
|
}
|
|
674
770
|
];
|
|
675
771
|
|
|
676
772
|
const missingCount = items.filter(function (item) { return !item.ok; }).length;
|
|
677
773
|
if (summary) {
|
|
678
|
-
summary.textContent = missingCount === 0 ? '
|
|
774
|
+
summary.textContent = missingCount === 0 ? '已就绪' : (missingCount + ' 项需要处理');
|
|
679
775
|
summary.style.color = missingCount === 0 ? 'var(--pw-state-success)' : 'var(--pw-state-warning)';
|
|
680
776
|
}
|
|
681
777
|
|
|
@@ -688,7 +784,7 @@ html.dark {
|
|
|
688
784
|
+ '<div class="flex items-center gap-2 mb-1">'
|
|
689
785
|
+ dotHtml(kind)
|
|
690
786
|
+ '<span class="text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(item.label) + '</span>'
|
|
691
|
-
+ '<span class="ml-auto text-xs px-2 py-0.5 rounded-full" style="color: ' + c.color + '; background-color: ' + c.bg + ';">' + (item.ok ? '
|
|
787
|
+
+ '<span class="ml-auto text-xs px-2 py-0.5 rounded-full" style="color: ' + c.color + '; background-color: ' + c.bg + ';">' + (item.ok ? '正常' : '处理') + '</span>'
|
|
692
788
|
+ '</div>'
|
|
693
789
|
+ '<div class="text-xs truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeHtml(item.value) + '">' + escapeHtml(item.value) + '</div>'
|
|
694
790
|
+ '<div class="text-xs mt-1" style="color: var(--pw-text-tertiary);">' + escapeHtml(item.hint) + '</div>'
|
|
@@ -866,9 +962,11 @@ html.dark {
|
|
|
866
962
|
let data = null;
|
|
867
963
|
try { data = await res.json(); } catch (_) { /* ignore */ }
|
|
868
964
|
if (!res.ok) {
|
|
869
|
-
|
|
965
|
+
var msg = (data && (data.error || data.reason || data.message)) || ('HTTP ' + res.status);
|
|
966
|
+
if (data && Array.isArray(data.next_steps) && data.next_steps.length) msg += ' 下一步:' + data.next_steps.join(';');
|
|
870
967
|
throw new Error(msg);
|
|
871
968
|
}
|
|
969
|
+
if (data && data.ok === false) throw new Error(data.error || data.reason || '操作未完成');
|
|
872
970
|
return data;
|
|
873
971
|
} finally {
|
|
874
972
|
if (btnEl) {
|
|
@@ -878,6 +976,155 @@ html.dark {
|
|
|
878
976
|
}
|
|
879
977
|
}
|
|
880
978
|
|
|
979
|
+
// ---------- Clipboard + Safe Modal ----------
|
|
980
|
+
function copyToClipboard(text, label) {
|
|
981
|
+
if (!text) return;
|
|
982
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
983
|
+
navigator.clipboard.writeText(text).then(function () {
|
|
984
|
+
showToast('success', '已复制', (label || '内容') + '已复制到剪贴板');
|
|
985
|
+
}).catch(function () {
|
|
986
|
+
showToast('error', '复制失败', '请手动复制');
|
|
987
|
+
});
|
|
988
|
+
} else {
|
|
989
|
+
showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Format diagnostics response as plain text for clipboard.
|
|
994
|
+
function formatDiagnosticsText(d) {
|
|
995
|
+
if (!d || typeof d !== 'object') return '';
|
|
996
|
+
const lines = [];
|
|
997
|
+
lines.push('PatchWarden Diagnostics');
|
|
998
|
+
lines.push('======================');
|
|
999
|
+
lines.push('server_version: ' + (d.server_version != null ? d.server_version : '—'));
|
|
1000
|
+
lines.push('schema_epoch: ' + (d.schema_epoch != null ? d.schema_epoch : '—'));
|
|
1001
|
+
lines.push('tool_manifest_sha256: ' + (d.tool_manifest_sha256 != null ? d.tool_manifest_sha256 : '—'));
|
|
1002
|
+
lines.push('watcher_status: ' + (d.watcher_status != null ? d.watcher_status : '—'));
|
|
1003
|
+
lines.push('tunnel_core_ready: ' + (d.tunnel_core_ready === true ? 'true' : 'false'));
|
|
1004
|
+
lines.push('tunnel_direct_ready: ' + (d.tunnel_direct_ready === true ? 'true' : 'false'));
|
|
1005
|
+
lines.push('core_tool_count: ' + (d.core_tool_count != null ? d.core_tool_count : '—'));
|
|
1006
|
+
lines.push('direct_tool_count: ' + (d.direct_tool_count != null ? d.direct_tool_count : '—'));
|
|
1007
|
+
lines.push('direct_profile_enabled: ' + (d.direct_profile_enabled === true ? 'true' : 'false'));
|
|
1008
|
+
lines.push('workspace_root: ' + (d.workspace_root != null ? d.workspace_root : '—'));
|
|
1009
|
+
lines.push('');
|
|
1010
|
+
const agents = Array.isArray(d.agent_status) ? d.agent_status : [];
|
|
1011
|
+
lines.push('Agents:');
|
|
1012
|
+
if (agents.length === 0) {
|
|
1013
|
+
lines.push(' (none)');
|
|
1014
|
+
} else {
|
|
1015
|
+
agents.forEach(function (a) {
|
|
1016
|
+
lines.push(' - ' + (a.name || '?') + ': ' + (a.available ? 'available' : 'unavailable'));
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
lines.push('');
|
|
1020
|
+
const failures = Array.isArray(d.recent_failures) ? d.recent_failures : [];
|
|
1021
|
+
lines.push('Recent failures:');
|
|
1022
|
+
if (failures.length === 0) {
|
|
1023
|
+
lines.push(' (none)');
|
|
1024
|
+
} else {
|
|
1025
|
+
failures.forEach(function (f) {
|
|
1026
|
+
lines.push(' - task_id: ' + (f.task_id || '?') + ', status: ' + (f.status || '?'));
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
return lines.join('\n');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
async function copyDiagnostics(btnEl) {
|
|
1033
|
+
const orig = btnEl ? btnEl.innerHTML : null;
|
|
1034
|
+
if (btnEl) {
|
|
1035
|
+
btnEl.disabled = true;
|
|
1036
|
+
btnEl.innerHTML =
|
|
1037
|
+
'<span class="inline-block w-3 h-3 align-middle border-2 border-current border-t-transparent rounded-full pw-spin"></span>'
|
|
1038
|
+
+ '<span class="ml-1.5">处理中…</span>';
|
|
1039
|
+
}
|
|
1040
|
+
try {
|
|
1041
|
+
const data = await fetchJson('/api/diagnostics');
|
|
1042
|
+
const text = formatDiagnosticsText(data);
|
|
1043
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1044
|
+
await navigator.clipboard.writeText(text);
|
|
1045
|
+
showToast('success', '诊断信息已复制到剪贴板', 'PatchWarden Diagnostics 已复制到剪贴板');
|
|
1046
|
+
} else {
|
|
1047
|
+
showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
|
|
1048
|
+
}
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
showToast('error', '诊断复制失败', err.message);
|
|
1051
|
+
} finally {
|
|
1052
|
+
if (btnEl) {
|
|
1053
|
+
btnEl.disabled = false;
|
|
1054
|
+
btnEl.innerHTML = orig;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function showSafeModal(title, data) {
|
|
1060
|
+
const modal = $('safe-modal');
|
|
1061
|
+
const titleEl = $('safe-modal-title');
|
|
1062
|
+
const bodyEl = $('safe-modal-body');
|
|
1063
|
+
if (!modal || !titleEl || !bodyEl) return;
|
|
1064
|
+
titleEl.textContent = title || '—';
|
|
1065
|
+
let content;
|
|
1066
|
+
if (data && typeof data === 'object') {
|
|
1067
|
+
content = JSON.stringify(data, null, 2);
|
|
1068
|
+
} else {
|
|
1069
|
+
content = String(data || '');
|
|
1070
|
+
}
|
|
1071
|
+
bodyEl.textContent = content;
|
|
1072
|
+
modal.classList.remove('hidden');
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function closeSafeModal() {
|
|
1076
|
+
const modal = $('safe-modal');
|
|
1077
|
+
if (modal) modal.classList.add('hidden');
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
async function fetchSafeSummary(taskId, kind, title) {
|
|
1081
|
+
if (!taskId) return;
|
|
1082
|
+
try {
|
|
1083
|
+
const data = await fetchJson('/api/tasks/' + encodeURIComponent(taskId) + '/' + kind);
|
|
1084
|
+
showSafeModal(title, data);
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
showError('加载 ' + title + ' 失败: ' + err.message);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// ---------- Task status priority ----------
|
|
1091
|
+
function primaryStatus(task) {
|
|
1092
|
+
if (!task) return { key: '', label: '—', kind: 'neutral' };
|
|
1093
|
+
const priority = [
|
|
1094
|
+
'failed_policy_violation',
|
|
1095
|
+
'failed_scope_violation',
|
|
1096
|
+
'failed_verification',
|
|
1097
|
+
'needs_fix',
|
|
1098
|
+
'ready_for_review',
|
|
1099
|
+
'done_by_agent',
|
|
1100
|
+
'running',
|
|
1101
|
+
'pending'
|
|
1102
|
+
];
|
|
1103
|
+
const status = task.status || '';
|
|
1104
|
+
const acceptance = task.acceptance_status || '';
|
|
1105
|
+
const statusIdx = priority.indexOf(status);
|
|
1106
|
+
const acceptanceIdx = priority.indexOf(acceptance);
|
|
1107
|
+
let chosen = '';
|
|
1108
|
+
if (statusIdx >= 0 && acceptanceIdx >= 0) {
|
|
1109
|
+
chosen = statusIdx <= acceptanceIdx ? status : acceptance;
|
|
1110
|
+
} else if (statusIdx >= 0) {
|
|
1111
|
+
chosen = status;
|
|
1112
|
+
} else if (acceptanceIdx >= 0) {
|
|
1113
|
+
chosen = acceptance;
|
|
1114
|
+
} else {
|
|
1115
|
+
chosen = acceptance || status || '';
|
|
1116
|
+
}
|
|
1117
|
+
return { key: chosen, label: chosen || '—', kind: statusKind(chosen || status) };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function verificationHeadline(t) {
|
|
1121
|
+
if (!t) return '—';
|
|
1122
|
+
if (t.verification && t.verification.headline) return t.verification.headline;
|
|
1123
|
+
if (t.safe_result && t.safe_result.verification && t.safe_result.verification.headline) return t.safe_result.verification.headline;
|
|
1124
|
+
if (t.verification_headline) return t.verification_headline;
|
|
1125
|
+
return '—';
|
|
1126
|
+
}
|
|
1127
|
+
|
|
881
1128
|
// ---------- Render: Service Card ----------
|
|
882
1129
|
function renderServiceCard(prefix, label, status) {
|
|
883
1130
|
const cardEl = $('card-' + prefix);
|
|
@@ -938,11 +1185,12 @@ html.dark {
|
|
|
938
1185
|
const tunnel = (status && status.tunnel) || {};
|
|
939
1186
|
const tools = (status && status.tools && status.tools.core) || {};
|
|
940
1187
|
const agents = (status && status.agents) || [];
|
|
1188
|
+
const directProfileEnabled = !!(status && status.direct_profile_enabled);
|
|
941
1189
|
|
|
942
1190
|
const watcherKind = statusKind(watcher.status);
|
|
943
1191
|
const coreReady = !!(tunnel.core && tunnel.core.ready);
|
|
944
1192
|
const directReady = !!(tunnel.direct && tunnel.direct.ready);
|
|
945
|
-
const tunnelReady = coreReady && directReady;
|
|
1193
|
+
const tunnelReady = coreReady && (!directProfileEnabled || directReady);
|
|
946
1194
|
const tunnelKind = tunnelReady ? 'success' : (coreReady || directReady ? 'warning' : 'error');
|
|
947
1195
|
|
|
948
1196
|
let agentsHtml = '';
|
|
@@ -972,7 +1220,7 @@ html.dark {
|
|
|
972
1220
|
row('Watcher',
|
|
973
1221
|
'<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + watcherText + '</span>',
|
|
974
1222
|
watcherKind)
|
|
975
|
-
+ row('
|
|
1223
|
+
+ row('隧道就绪',
|
|
976
1224
|
'<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">core ' + (coreReady ? '✓' : '✗') + ' / direct ' + (directReady ? '✓' : '✗') + '</span>',
|
|
977
1225
|
tunnelKind)
|
|
978
1226
|
+ row('schema_epoch',
|
|
@@ -1000,21 +1248,77 @@ html.dark {
|
|
|
1000
1248
|
bodyEl.innerHTML = '<div class="px-4 py-6 text-center text-sm" style="color: var(--pw-text-tertiary);">暂无任务</div>';
|
|
1001
1249
|
return;
|
|
1002
1250
|
}
|
|
1251
|
+
const titleMap = {
|
|
1252
|
+
'safe-result': 'Safe Result',
|
|
1253
|
+
'safe-audit': 'Safe Audit',
|
|
1254
|
+
'safe-test-summary': 'Safe Test Summary',
|
|
1255
|
+
'safe-diff-summary': 'Safe Diff Summary'
|
|
1256
|
+
};
|
|
1003
1257
|
let html = '';
|
|
1004
1258
|
top.forEach(function (t) {
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1259
|
+
const taskId = t.task_id || '';
|
|
1260
|
+
const href = '/pages/task-detail.html?id=' + encodeURIComponent(taskId);
|
|
1261
|
+
const sKind = statusKind(t.status);
|
|
1262
|
+
const sColor = statusColors(sKind);
|
|
1263
|
+
const aKind = statusKind(t.acceptance_status);
|
|
1264
|
+
const aColor = statusColors(aKind);
|
|
1265
|
+
// Skip status badge when redundant with acceptance (done + done_by_agent)
|
|
1266
|
+
const skipStatusBadge = (t.status === 'done' && t.acceptance_status === 'done_by_agent');
|
|
1267
|
+
const headline = verificationHeadline(t);
|
|
1268
|
+
const updated = t.updated_at || t.created_at || '';
|
|
1269
|
+
|
|
1008
1270
|
html +=
|
|
1009
|
-
'<
|
|
1010
|
-
+ '<
|
|
1011
|
-
|
|
1012
|
-
+ '<span class="shrink-0 text-xs whitespace-nowrap" style="color: var(--pw-text-
|
|
1013
|
-
+ '<
|
|
1014
|
-
+ '<
|
|
1015
|
-
+ '</
|
|
1271
|
+
'<div data-task-row class="px-4 py-3 transition-colors" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
1272
|
+
+ '<div class="flex items-center gap-2 flex-wrap">'
|
|
1273
|
+
// short task_id + copy button
|
|
1274
|
+
+ '<span class="shrink-0 text-xs font-medium px-2 py-0.5 rounded whitespace-nowrap" style="color: var(--pw-text-secondary); background-color: var(--pw-bg-hover); font-family: var(--pw-font-mono);" title="' + escapeHtml(taskId) + '">' + escapeHtml(shortId(taskId)) + '</span>'
|
|
1275
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeHtml(taskId) + '" title="复制 task_id" type="button">'
|
|
1276
|
+
+ '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>'
|
|
1277
|
+
+ '</button>'
|
|
1278
|
+
// title (truncated, link to detail)
|
|
1279
|
+
+ '<a href="' + href + '" class="flex-1 min-w-0 text-sm truncate hover:underline" style="color: var(--pw-text-primary);" title="' + escapeHtml(t.title || '') + '">' + escapeHtml(t.title || '—') + '</a>'
|
|
1280
|
+
// agent badge
|
|
1281
|
+
+ (t.agent ? badgeHtml(t.agent, 'info') : '')
|
|
1282
|
+
// status badge (skip if redundant)
|
|
1283
|
+
+ (skipStatusBadge ? '' : '<span class="shrink-0 text-xs font-medium px-2 py-0.5 rounded-full whitespace-nowrap" style="color: ' + sColor.color + '; background-color: ' + sColor.bg + ';">' + escapeHtml(t.status || '—') + '</span>')
|
|
1284
|
+
// acceptance badge (more prominent)
|
|
1285
|
+
+ (t.acceptance_status ? '<span class="shrink-0 pw-acceptance-badge rounded-full whitespace-nowrap" style="color: ' + aColor.color + '; background-color: ' + aColor.bg + '; border-color: ' + aColor.color + ';">' + escapeHtml(t.acceptance_status) + '</span>' : '')
|
|
1286
|
+
+ '</div>'
|
|
1287
|
+
// second line: repo_path, verification headline, updated_at, next_action
|
|
1288
|
+
+ '<div class="flex items-center gap-3 mt-1.5 flex-wrap">'
|
|
1289
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono); max-width: 200px;" title="' + escapeHtml(t.repo_path || '') + '">' + escapeHtml(t.repo_path || '—') + '</span>'
|
|
1290
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-secondary); max-width: 280px;" title="' + escapeHtml(headline) + '">verify: ' + escapeHtml(headline) + '</span>'
|
|
1291
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">' + escapeHtml(fmtTime(updated)) + '</span>'
|
|
1292
|
+
+ (t.next_action ? '<span class="text-xs truncate" style="color: var(--pw-text-tertiary); max-width: 200px;" title="' + escapeHtml(t.next_action) + '">next: ' + escapeHtml(t.next_action) + '</span>' : '')
|
|
1293
|
+
+ '</div>'
|
|
1294
|
+
// quick action buttons
|
|
1295
|
+
+ '<div class="flex items-center gap-1 mt-1.5">'
|
|
1296
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-result" data-task-id="' + escapeHtml(taskId) + '" title="safe result" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></button>'
|
|
1297
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-audit" data-task-id="' + escapeHtml(taskId) + '" title="safe audit" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></button>'
|
|
1298
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-test-summary" data-task-id="' + escapeHtml(taskId) + '" title="safe test summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></button>'
|
|
1299
|
+
+ '<button class="pw-quick-btn" data-safe-action="safe-diff-summary" data-task-id="' + escapeHtml(taskId) + '" title="safe diff summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg></button>'
|
|
1300
|
+
+ '<a href="' + href + '" class="pw-quick-btn" title="打开详情"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>'
|
|
1301
|
+
+ '</div>'
|
|
1302
|
+
+ '</div>';
|
|
1016
1303
|
});
|
|
1017
1304
|
bodyEl.innerHTML = html;
|
|
1305
|
+
// Wire quick action buttons
|
|
1306
|
+
bodyEl.querySelectorAll('[data-safe-action]').forEach(function (btn) {
|
|
1307
|
+
btn.addEventListener('click', function (e) {
|
|
1308
|
+
e.preventDefault();
|
|
1309
|
+
e.stopPropagation();
|
|
1310
|
+
const action = btn.getAttribute('data-safe-action');
|
|
1311
|
+
const tid = btn.getAttribute('data-task-id');
|
|
1312
|
+
fetchSafeSummary(tid, action, titleMap[action] || action);
|
|
1313
|
+
});
|
|
1314
|
+
});
|
|
1315
|
+
bodyEl.querySelectorAll('[data-copy-id]').forEach(function (btn) {
|
|
1316
|
+
btn.addEventListener('click', function (e) {
|
|
1317
|
+
e.preventDefault();
|
|
1318
|
+
e.stopPropagation();
|
|
1319
|
+
copyToClipboard(btn.getAttribute('data-copy-id'), 'task_id');
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1018
1322
|
}
|
|
1019
1323
|
|
|
1020
1324
|
// ---------- Render: Activity Log ----------
|
|
@@ -1054,7 +1358,7 @@ html.dark {
|
|
|
1054
1358
|
|
|
1055
1359
|
el.innerHTML =
|
|
1056
1360
|
'<details class="px-4 py-3">'
|
|
1057
|
-
+ '<summary class="cursor-pointer text-xs font-medium" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);"
|
|
1361
|
+
+ '<summary class="cursor-pointer text-xs font-medium" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">查看 Core / Direct 本次日志尾部</summary>'
|
|
1058
1362
|
+ '<pre class="mt-3 text-xs overflow-auto m-0" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono); white-space: pre-wrap; word-break: break-all; max-height: 320px;">' + escapeHtml(merged) + '</pre>'
|
|
1059
1363
|
+ '</details>';
|
|
1060
1364
|
}
|
|
@@ -1069,49 +1373,262 @@ html.dark {
|
|
|
1069
1373
|
const lineages = Array.isArray(data.lineages) ? data.lineages : [];
|
|
1070
1374
|
if (count) count.textContent = String(data.total || lineages.length);
|
|
1071
1375
|
if (!lineages.length) {
|
|
1072
|
-
body.innerHTML =
|
|
1376
|
+
body.innerHTML =
|
|
1377
|
+
'<div class="text-xs mb-2" style="color: var(--pw-text-tertiary);">暂无任务链路</div>'
|
|
1378
|
+
+ '<div class="flex items-center gap-2">'
|
|
1379
|
+
+ '<a href="/tasks.html" class="text-xs px-2.5 py-1 rounded whitespace-nowrap" style="background-color: var(--pw-accent-muted); color: var(--pw-accent); border: 1px solid var(--pw-accent);">创建受控任务链</a>'
|
|
1380
|
+
+ '<a href="/tasks.html" class="text-xs px-2.5 py-1 rounded whitespace-nowrap" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">查看最近运行</a>'
|
|
1381
|
+
+ '</div>';
|
|
1073
1382
|
return;
|
|
1074
1383
|
}
|
|
1075
1384
|
const top = lineages[0];
|
|
1076
|
-
const
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
const directStatus = direct
|
|
1080
|
-
? ((direct.status || 'unknown') + ' / audit ' + (direct.audit_decision || 'not_run'))
|
|
1385
|
+
const dv = top.direct_verification || null;
|
|
1386
|
+
const directStatus = dv
|
|
1387
|
+
? (escapeHtml(dv.status || 'unknown') + ' / audit ' + escapeHtml(dv.audit_decision || 'not_run'))
|
|
1081
1388
|
: 'not run';
|
|
1389
|
+
const mainCount = top.main_task_count != null ? top.main_task_count : (top.tasks && top.tasks.main ? 1 : 0);
|
|
1390
|
+
const fixCount = top.fix_task_count != null ? top.fix_task_count : (top.tasks && Array.isArray(top.tasks.fix) ? top.tasks.fix.length : 0);
|
|
1391
|
+
const cleanupCount = top.cleanup_task_count != null ? top.cleanup_task_count : (top.tasks && Array.isArray(top.tasks.cleanup) ? top.tasks.cleanup.length : 0);
|
|
1392
|
+
const iterCount = top.iterations != null ? top.iterations : (Array.isArray(top.rounds) ? top.rounds.length : 0);
|
|
1393
|
+
const warnCount = top.warnings_count != null ? top.warnings_count : (Array.isArray(top.warnings) ? top.warnings.length : 0);
|
|
1082
1394
|
body.innerHTML =
|
|
1083
1395
|
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1084
1396
|
+ '<span class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(top.lineage_id || '') + '">' + escapeHtml(shortId(top.lineage_id)) + '</span>'
|
|
1085
|
-
+ badgeHtml(top.
|
|
1397
|
+
+ badgeHtml(top.final_status || 'unknown', statusKind(top.final_status))
|
|
1086
1398
|
+ '</div>'
|
|
1087
1399
|
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-secondary);" title="' + escapeHtml(top.goal || '') + '">' + escapeHtml(top.goal || 'No goal') + '</div>'
|
|
1088
|
-
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);">
|
|
1089
|
-
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);"
|
|
1090
|
-
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);"
|
|
1400
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);">stop_reason: ' + escapeHtml(top.stop_reason || 'unknown') + '</div>'
|
|
1401
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);">iterations: ' + escapeHtml(String(iterCount)) + ' · main/fix/cleanup: ' + escapeHtml(String(mainCount)) + '/' + escapeHtml(String(fixCount)) + '/' + escapeHtml(String(cleanupCount)) + '</div>'
|
|
1402
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);" title="' + escapeHtml(dv && dv.session_id ? dv.session_id : '') + '">direct: ' + directStatus + '</div>'
|
|
1403
|
+
+ '<div class="text-xs mb-2" style="color: var(--pw-text-tertiary);">warnings: ' + escapeHtml(String(warnCount)) + '</div>'
|
|
1404
|
+
+ '<div class="flex items-center gap-2">'
|
|
1405
|
+
+ '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-lineage-detail="' + escapeHtml(top.lineage_id || '') + '" type="button" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">View detail</button>'
|
|
1406
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeHtml(top.lineage_id || '') + '" title="复制 lineage_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
1407
|
+
+ '</div>';
|
|
1408
|
+
// Wire lineage detail button
|
|
1409
|
+
const detailBtn = body.querySelector('[data-lineage-detail]');
|
|
1410
|
+
if (detailBtn) {
|
|
1411
|
+
detailBtn.addEventListener('click', function () {
|
|
1412
|
+
const lid = detailBtn.getAttribute('data-lineage-detail');
|
|
1413
|
+
if (lid) showLineageDetail(lid);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
// Wire copy button
|
|
1417
|
+
const copyBtn = body.querySelector('[data-copy-id]');
|
|
1418
|
+
if (copyBtn) {
|
|
1419
|
+
copyBtn.addEventListener('click', function (e) {
|
|
1420
|
+
e.preventDefault();
|
|
1421
|
+
e.stopPropagation();
|
|
1422
|
+
copyToClipboard(copyBtn.getAttribute('data-copy-id'), 'lineage_id');
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1091
1425
|
} catch (err) {
|
|
1092
1426
|
if (count) count.textContent = 'error';
|
|
1093
1427
|
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1094
1428
|
}
|
|
1095
1429
|
}
|
|
1096
1430
|
|
|
1431
|
+
async function showLineageDetail(lineageId) {
|
|
1432
|
+
if (!lineageId) return;
|
|
1433
|
+
const modal = $('safe-modal');
|
|
1434
|
+
const titleEl = $('safe-modal-title');
|
|
1435
|
+
const bodyEl = $('safe-modal-body');
|
|
1436
|
+
if (!modal || !titleEl || !bodyEl) return;
|
|
1437
|
+
titleEl.textContent = 'Lineage Detail · ' + shortId(lineageId);
|
|
1438
|
+
bodyEl.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">Loading…</div>';
|
|
1439
|
+
modal.classList.remove('hidden');
|
|
1440
|
+
try {
|
|
1441
|
+
const data = await fetchJson('/api/lineages/' + encodeURIComponent(lineageId));
|
|
1442
|
+
bodyEl.innerHTML = renderLineageDetail(data);
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
bodyEl.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function renderLineageDetail(d) {
|
|
1449
|
+
if (!d) return '<div class="text-xs" style="color: var(--pw-text-tertiary);">No data</div>';
|
|
1450
|
+
const rounds = Array.isArray(d.rounds) ? d.rounds : [];
|
|
1451
|
+
const mainRounds = rounds.filter(function (r) { return r.role === 'main'; });
|
|
1452
|
+
const fixRounds = rounds.filter(function (r) { return r.role === 'fix_tests'; });
|
|
1453
|
+
const cleanupRounds = rounds.filter(function (r) { return r.role === 'cleanup'; });
|
|
1454
|
+
const directSessions = (d.tasks && Array.isArray(d.tasks.direct_sessions)) ? d.tasks.direct_sessions : [];
|
|
1455
|
+
|
|
1456
|
+
function roundRow(r) {
|
|
1457
|
+
return '<div class="py-1.5" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
1458
|
+
+ '<div class="flex items-center gap-2 mb-1 flex-wrap">'
|
|
1459
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(r.task_id || '') + '">' + escapeHtml(shortId(r.task_id)) + '</span>'
|
|
1460
|
+
+ badgeHtml(r.status || '—', statusKind(r.status))
|
|
1461
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">iter ' + escapeHtml(String(r.iteration || 0)) + '</span>'
|
|
1462
|
+
+ '</div>'
|
|
1463
|
+
+ '<div class="text-xs mb-0.5" style="color: var(--pw-text-secondary);">verification: ' + escapeHtml(r.verification_status || '—') + '</div>'
|
|
1464
|
+
+ '<div class="text-xs mb-0.5" style="color: var(--pw-text-secondary);">audit: ' + escapeHtml(r.audit_verdict || '—') + '</div>'
|
|
1465
|
+
+ (r.next_action ? '<div class="text-xs" style="color: var(--pw-text-tertiary);">next: ' + escapeHtml(r.next_action) + '</div>' : '')
|
|
1466
|
+
+ '</div>';
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function directRow(s) {
|
|
1470
|
+
return '<div class="py-1.5" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
1471
|
+
+ '<div class="flex items-center gap-2 mb-1 flex-wrap">'
|
|
1472
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(s.session_id || '') + '">' + escapeHtml(shortId(s.session_id)) + '</span>'
|
|
1473
|
+
+ badgeHtml(s.status || 'unknown', statusKind(s.status))
|
|
1474
|
+
+ '</div>'
|
|
1475
|
+
+ '<div class="text-xs mb-0.5" style="color: var(--pw-text-secondary);">audit: ' + escapeHtml(s.audit_decision || 'not_run') + '</div>'
|
|
1476
|
+
+ '<div class="text-xs mb-0.5" style="color: var(--pw-text-secondary);">commands: ' + escapeHtml(String(s.command_count || 0)) + ' (passed ' + escapeHtml(String(s.passed_commands || 0)) + ' / failed ' + escapeHtml(String(s.failed_commands || 0)) + ')</div>'
|
|
1477
|
+
+ (s.next_action ? '<div class="text-xs" style="color: var(--pw-text-tertiary);">next: ' + escapeHtml(s.next_action) + '</div>' : '')
|
|
1478
|
+
+ '</div>';
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function section(title, items, renderFn) {
|
|
1482
|
+
if (!items || items.length === 0) return '';
|
|
1483
|
+
return '<div class="mt-3">'
|
|
1484
|
+
+ '<div class="text-xs font-semibold mb-1" style="color: var(--pw-text-primary);">' + escapeHtml(title) + ' (' + items.length + ')</div>'
|
|
1485
|
+
+ items.map(renderFn).join('')
|
|
1486
|
+
+ '</div>';
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const header =
|
|
1490
|
+
'<div class="mb-2">'
|
|
1491
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-secondary);"><span style="color: var(--pw-text-tertiary);">goal:</span> ' + escapeHtml(d.goal || '—') + '</div>'
|
|
1492
|
+
+ '<div class="flex items-center gap-2 mb-1 flex-wrap">'
|
|
1493
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">final_status:</span>' + badgeHtml(d.final_status || 'unknown', statusKind(d.final_status))
|
|
1494
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">stop_reason:</span><span class="text-xs" style="color: var(--pw-text-secondary);">' + escapeHtml(d.stop_reason || '—') + '</span>'
|
|
1495
|
+
+ '</div>'
|
|
1496
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">iterations: ' + escapeHtml(String(d.iterations != null ? d.iterations : rounds.length)) + ' · warnings: ' + escapeHtml(String(d.warnings_count != null ? d.warnings_count : (Array.isArray(d.warnings) ? d.warnings.length : 0))) + '</div>'
|
|
1497
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">main: ' + escapeHtml(String(d.main_task_count != null ? d.main_task_count : 0)) + ' · fix: ' + escapeHtml(String(d.fix_task_count != null ? d.fix_task_count : 0)) + ' · cleanup: ' + escapeHtml(String(d.cleanup_task_count != null ? d.cleanup_task_count : 0)) + '</div>'
|
|
1498
|
+
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);">final_recommended_next_action: ' + escapeHtml(d.next_action || '—') + '</div>'
|
|
1499
|
+
+ '</div>';
|
|
1500
|
+
|
|
1501
|
+
return header
|
|
1502
|
+
+ section('Main Tasks', mainRounds, roundRow)
|
|
1503
|
+
+ section('Fix Tasks', fixRounds, roundRow)
|
|
1504
|
+
+ section('Cleanup Tasks', cleanupRounds, roundRow)
|
|
1505
|
+
+ section('Direct Verification', directSessions, directRow);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// ---------- Live readiness ----------
|
|
1509
|
+
// The status route owns this classification. Historic task records remain
|
|
1510
|
+
// visible elsewhere and never change the live readiness result.
|
|
1511
|
+
function calculateHealthScore(status) {
|
|
1512
|
+
const experience = status && status.experience;
|
|
1513
|
+
if (!experience || !Array.isArray(experience.live_checks)) {
|
|
1514
|
+
return { level: 'blocked', dimensions: [] };
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
level: experience.readiness === 'ready' ? 'healthy' : experience.readiness,
|
|
1518
|
+
dimensions: experience.live_checks.map(function (check) {
|
|
1519
|
+
return { key: check.key, label: check.label, ok: check.state === 'ready' || check.state === 'optional', optional: check.state === 'optional', detail: check.detail };
|
|
1520
|
+
}),
|
|
1521
|
+
history: experience.historical_summary || null
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function healthScoreColor(level) {
|
|
1526
|
+
if (level === 'healthy') return { color: 'var(--pw-state-success)', bg: 'var(--pw-state-success-bg)' };
|
|
1527
|
+
if (level === 'needs_action') return { color: 'var(--pw-state-warning)', bg: 'var(--pw-state-warning-bg)' };
|
|
1528
|
+
if (level === 'warning') return { color: 'var(--pw-state-warning)', bg: 'var(--pw-state-warning-bg)' };
|
|
1529
|
+
if (level === 'degraded') return { color: '#f97316', bg: 'rgba(249, 115, 22, 0.1)' };
|
|
1530
|
+
if (level === 'blocked') return { color: 'var(--pw-state-error)', bg: 'var(--pw-state-error-bg)' };
|
|
1531
|
+
return { color: 'var(--pw-text-secondary)', bg: 'var(--pw-bg-hover)' };
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function renderHealthScore(score) {
|
|
1535
|
+
const card = $('health-score-card');
|
|
1536
|
+
const badge = $('health-score-badge');
|
|
1537
|
+
const body = $('health-score-body');
|
|
1538
|
+
if (!card || !badge || !body || !score) return;
|
|
1539
|
+
|
|
1540
|
+
const c = healthScoreColor(score.level);
|
|
1541
|
+
badge.textContent = score.level;
|
|
1542
|
+
badge.style.color = c.color;
|
|
1543
|
+
badge.style.backgroundColor = c.bg;
|
|
1544
|
+
|
|
1545
|
+
let html = '';
|
|
1546
|
+
score.dimensions.forEach(function (d) {
|
|
1547
|
+
const okColor = d.optional ? 'var(--pw-text-tertiary)' : (d.ok ? 'var(--pw-state-success)' : 'var(--pw-state-error)');
|
|
1548
|
+
const icon = d.optional ? '○' : (d.ok ? '✓' : '✗');
|
|
1549
|
+
html +=
|
|
1550
|
+
'<div class="flex items-center gap-2 px-2 py-1.5 rounded" style="background-color: var(--pw-bg-hover); border: 1px solid var(--pw-border-subtle);">'
|
|
1551
|
+
+ '<span class="shrink-0 text-xs font-semibold" style="color: ' + okColor + '; font-family: var(--pw-font-mono);">' + icon + '</span>'
|
|
1552
|
+
+ '<div class="min-w-0 flex-1">'
|
|
1553
|
+
+ '<div class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(d.label) + '</div>'
|
|
1554
|
+
+ '<div class="text-xs truncate" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);" title="' + escapeHtml(d.detail) + '">' + escapeHtml(d.detail) + '</div>'
|
|
1555
|
+
+ '</div>'
|
|
1556
|
+
+ '</div>';
|
|
1557
|
+
});
|
|
1558
|
+
if (score.history) {
|
|
1559
|
+
html += '<div class="md:col-span-2 lg:col-span-4 text-xs px-2 py-1" style="color: var(--pw-text-tertiary);">历史摘要:' + escapeHtml(String(score.history.stale_tasks || 0)) + ' 个过期任务,' + escapeHtml(String(score.history.failed_tasks || 0)) + ' 个失败任务</div>';
|
|
1560
|
+
}
|
|
1561
|
+
body.innerHTML = html;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
async function refreshHealthScore() {
|
|
1565
|
+
const card = $('health-score-card');
|
|
1566
|
+
if (!card) return;
|
|
1567
|
+
try {
|
|
1568
|
+
const status = await fetchJson('/api/status');
|
|
1569
|
+
const score = calculateHealthScore(status);
|
|
1570
|
+
renderHealthScore(score);
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
const body = $('health-score-body');
|
|
1573
|
+
const badge = $('health-score-badge');
|
|
1574
|
+
if (body) body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">健康评分加载失败: ' + escapeHtml(err.message) + '</div>';
|
|
1575
|
+
if (badge) { badge.textContent = 'error'; badge.style.color = 'var(--pw-state-error)'; badge.style.backgroundColor = 'var(--pw-state-error-bg)'; }
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1097
1579
|
async function refreshProjectPolicy() {
|
|
1098
1580
|
const body = $('policy-overview-body');
|
|
1099
1581
|
const status = $('policy-status');
|
|
1100
1582
|
if (!body) return;
|
|
1101
1583
|
try {
|
|
1102
|
-
const data = await fetchJson('/api/project-policy?repo_path
|
|
1584
|
+
const data = await fetchJson('/api/project-policy?repo_path=' + encodeURIComponent(currentRepoPath));
|
|
1103
1585
|
const valid = data && data.valid !== false;
|
|
1104
|
-
if (status)
|
|
1586
|
+
if (status) {
|
|
1587
|
+
status.textContent = valid ? 'valid' : 'issues';
|
|
1588
|
+
status.style.color = valid ? 'var(--pw-state-success)' : 'var(--pw-state-error)';
|
|
1589
|
+
}
|
|
1105
1590
|
const issueCount = Array.isArray(data.issues) ? data.issues.length : 0;
|
|
1106
1591
|
const policy = data.effective_policy || {};
|
|
1107
1592
|
const release = (policy.release_mode || {});
|
|
1593
|
+
|
|
1594
|
+
// auto_cleanup: boolean or object with enabled field
|
|
1595
|
+
const autoCleanupRaw = policy.auto_cleanup;
|
|
1596
|
+
let autoCleanupEnabled = false;
|
|
1597
|
+
if (autoCleanupRaw === true) autoCleanupEnabled = true;
|
|
1598
|
+
else if (autoCleanupRaw && autoCleanupRaw.enabled === true) autoCleanupEnabled = true;
|
|
1599
|
+
|
|
1600
|
+
// protected_paths / high_risk_commands counts
|
|
1601
|
+
const protectedPathsCount = Array.isArray(policy.protected_paths) ? policy.protected_paths.length : 0;
|
|
1602
|
+
const highRiskCmdCount = Array.isArray(policy.high_risk_commands) ? policy.high_risk_commands.length : 0;
|
|
1603
|
+
|
|
1604
|
+
// release_mode summary
|
|
1605
|
+
const versionSource = release.version_source || 'package.json';
|
|
1606
|
+
const readyState = release.ready_state || '';
|
|
1607
|
+
|
|
1608
|
+
function row(label, valueHtml) {
|
|
1609
|
+
return '<div class="flex items-center justify-between gap-2 py-1" style="border-bottom: 1px solid var(--pw-border-subtle);">'
|
|
1610
|
+
+ '<span class="text-xs whitespace-nowrap" style="color: var(--pw-text-secondary);">' + label + '</span>'
|
|
1611
|
+
+ '<span class="text-xs text-right min-w-0 truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + valueHtml + '</span>'
|
|
1612
|
+
+ '</div>';
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1108
1615
|
body.innerHTML =
|
|
1109
|
-
'<div class="flex items-center justify-between gap-2 mb-
|
|
1616
|
+
'<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1110
1617
|
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">policy file</span>'
|
|
1111
1618
|
+ badgeHtml(data.exists ? 'present' : 'defaults', data.exists ? 'success' : 'warning')
|
|
1112
1619
|
+ '</div>'
|
|
1113
|
-
+ '<div class="
|
|
1114
|
-
+ '<
|
|
1620
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1621
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">validity</span>'
|
|
1622
|
+
+ badgeHtml(valid ? 'valid' : 'invalid', valid ? 'success' : 'error')
|
|
1623
|
+
+ '</div>'
|
|
1624
|
+
+ row('issues', String(issueCount))
|
|
1625
|
+
+ row('auto_cleanup', autoCleanupEnabled
|
|
1626
|
+
? '<span style="color: var(--pw-state-success);">enabled</span>'
|
|
1627
|
+
: '<span style="color: var(--pw-text-tertiary);">disabled</span>')
|
|
1628
|
+
+ row('protected_paths', String(protectedPathsCount))
|
|
1629
|
+
+ row('high_risk_commands', String(highRiskCmdCount))
|
|
1630
|
+
+ row('version_source', escapeHtml(versionSource))
|
|
1631
|
+
+ (readyState ? row('ready_state', escapeHtml(readyState)) : '');
|
|
1115
1632
|
} catch (err) {
|
|
1116
1633
|
if (status) status.textContent = 'error';
|
|
1117
1634
|
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
@@ -1123,19 +1640,89 @@ html.dark {
|
|
|
1123
1640
|
const status = $('release-status');
|
|
1124
1641
|
if (!body) return;
|
|
1125
1642
|
try {
|
|
1126
|
-
const data = await fetchJson('/api/release/status?repo_path
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
const
|
|
1130
|
-
const commands = Array.isArray(
|
|
1131
|
-
const
|
|
1643
|
+
const data = await fetchJson('/api/release/status?repo_path=' + encodeURIComponent(currentRepoPath));
|
|
1644
|
+
const vs = data.version_source || '';
|
|
1645
|
+
const ver = data.version || '';
|
|
1646
|
+
const consistent = data.version_consistent;
|
|
1647
|
+
const commands = Array.isArray(data.required_commands) ? data.required_commands : [];
|
|
1648
|
+
const blockedCount = data.commands_blocked_count != null ? data.commands_blocked_count : commands.filter(function (e) { return !e.allowed; }).length;
|
|
1649
|
+
const readyState = data.ready_state || 'unknown';
|
|
1650
|
+
const nextAction = data.next_action || '';
|
|
1651
|
+
|
|
1652
|
+
// Version display logic
|
|
1653
|
+
let versionDisplay;
|
|
1654
|
+
if (vs === 'workspace_root' || !ver) {
|
|
1655
|
+
versionDisplay = '选择具体 repo 以读取 version';
|
|
1656
|
+
} else {
|
|
1657
|
+
versionDisplay = ver;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// ready_state badge
|
|
1661
|
+
let readyKind = 'warning';
|
|
1662
|
+
if (readyState === 'ready') readyKind = 'success';
|
|
1663
|
+
else if (readyState === 'blocked') readyKind = 'error';
|
|
1664
|
+
|
|
1665
|
+
// version_consistent badge
|
|
1666
|
+
const consistentKind = consistent === true ? 'success' : (consistent === false ? 'error' : 'neutral');
|
|
1667
|
+
const consistentText = consistent === true ? '✓' : (consistent === false ? '✗' : '—');
|
|
1668
|
+
|
|
1669
|
+
// commands list
|
|
1670
|
+
let cmdHtml = '';
|
|
1671
|
+
if (commands.length > 0) {
|
|
1672
|
+
cmdHtml = '<div class="mt-2 space-y-1">';
|
|
1673
|
+
commands.forEach(function (cmd) {
|
|
1674
|
+
const allowedIcon = cmd.allowed ? '✓' : '✗';
|
|
1675
|
+
const cmdKind = cmd.allowed ? 'success' : 'error';
|
|
1676
|
+
const c = statusColors(cmdKind);
|
|
1677
|
+
cmdHtml += '<div class="flex items-center gap-2 text-xs">'
|
|
1678
|
+
+ '<span style="color: ' + c.color + ';">' + allowedIcon + '</span>'
|
|
1679
|
+
+ '<span style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(cmd.command) + '</span>'
|
|
1680
|
+
+ (cmd.blocked_reason ? '<span style="color: var(--pw-state-warning); font-family: var(--pw-font-sans);">(' + escapeHtml(cmd.blocked_reason) + ')</span>' : '')
|
|
1681
|
+
+ '</div>';
|
|
1682
|
+
});
|
|
1683
|
+
cmdHtml += '</div>';
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// blocked reason text
|
|
1687
|
+
let blockedReasonHtml = '';
|
|
1688
|
+
if (readyState === 'blocked' && data.blocked_reason) {
|
|
1689
|
+
blockedReasonHtml = '<div class="text-xs mt-2" style="color: var(--pw-state-error);">' + escapeHtml(data.blocked_reason) + '</div>';
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (status) {
|
|
1693
|
+
status.textContent = readyState;
|
|
1694
|
+
const sc = statusColors(readyKind);
|
|
1695
|
+
status.style.color = sc.color;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1132
1698
|
body.innerHTML =
|
|
1133
|
-
'<div class="flex items-center justify-between gap-2 mb-
|
|
1699
|
+
'<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1700
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">package</span>'
|
|
1701
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(data.package_name || '—') + '</span>'
|
|
1702
|
+
+ '</div>'
|
|
1703
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1704
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">version_source</span>'
|
|
1705
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(vs || 'unknown') + '</span>'
|
|
1706
|
+
+ '</div>'
|
|
1707
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1134
1708
|
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">version</span>'
|
|
1135
|
-
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(
|
|
1709
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(versionDisplay) + '</span>'
|
|
1710
|
+
+ '</div>'
|
|
1711
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1712
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">consistent</span>'
|
|
1713
|
+
+ '<span class="text-xs" style="color: ' + statusColors(consistentKind).color + ';">' + consistentText + '</span>'
|
|
1714
|
+
+ '</div>'
|
|
1715
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1716
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">commands</span>'
|
|
1717
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + blockedCount + ' / ' + commands.length + ' blocked</span>'
|
|
1136
1718
|
+ '</div>'
|
|
1137
|
-
+ '<div class="
|
|
1138
|
-
+ '<
|
|
1719
|
+
+ '<div class="flex items-center justify-between gap-2 mb-1">'
|
|
1720
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">ready</span>'
|
|
1721
|
+
+ badgeHtml(readyState, readyKind)
|
|
1722
|
+
+ '</div>'
|
|
1723
|
+
+ cmdHtml
|
|
1724
|
+
+ blockedReasonHtml
|
|
1725
|
+
+ (nextAction ? '<div class="text-xs mt-1" style="color: var(--pw-text-tertiary);">next: ' + escapeHtml(nextAction) + '</div>' : '');
|
|
1139
1726
|
} catch (err) {
|
|
1140
1727
|
if (status) status.textContent = 'error';
|
|
1141
1728
|
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
@@ -1150,24 +1737,261 @@ html.dark {
|
|
|
1150
1737
|
const data = await fetchJson('/api/evidence-packs');
|
|
1151
1738
|
const packs = Array.isArray(data.evidence_packs) ? data.evidence_packs : [];
|
|
1152
1739
|
if (status) status.textContent = String(data.total || packs.length);
|
|
1740
|
+
const hasLineages = data.has_lineages === true;
|
|
1741
|
+
const pendingIds = Array.isArray(data.pending_lineage_ids) ? data.pending_lineage_ids : [];
|
|
1742
|
+
|
|
1153
1743
|
if (!packs.length) {
|
|
1154
|
-
|
|
1744
|
+
if (!hasLineages) {
|
|
1745
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">Evidence pack is available after run_task_loop.</div>';
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
// Lineage exists but no evidence pack yet — offer export for the most recent pending lineage.
|
|
1749
|
+
const exportId = pendingIds.length > 0 ? pendingIds[0] : null;
|
|
1750
|
+
if (exportId) {
|
|
1751
|
+
body.innerHTML =
|
|
1752
|
+
'<div class="text-xs mb-2" style="color: var(--pw-text-tertiary);">Lineage exists but no evidence pack yet.</div>'
|
|
1753
|
+
+ '<div class="flex items-center gap-2">'
|
|
1754
|
+
+ '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-export-pack="' + escapeHtml(exportId) + '" type="button" style="background-color: var(--pw-accent-muted); color: var(--pw-accent); border: 1px solid var(--pw-accent);">Export evidence pack</button>'
|
|
1755
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeHtml(exportId) + '" title="复制 lineage_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
1756
|
+
+ '</div>';
|
|
1757
|
+
const exportBtn = body.querySelector('[data-export-pack]');
|
|
1758
|
+
if (exportBtn) {
|
|
1759
|
+
exportBtn.addEventListener('click', function () {
|
|
1760
|
+
const lid = exportBtn.getAttribute('data-export-pack');
|
|
1761
|
+
if (!lid) return;
|
|
1762
|
+
postAction('/api/evidence-packs/' + encodeURIComponent(lid) + '/export', exportBtn).then(function () {
|
|
1763
|
+
showToast('success', '导出成功', 'Evidence pack 已导出');
|
|
1764
|
+
refreshEvidencePacks();
|
|
1765
|
+
}).catch(function (err) {
|
|
1766
|
+
showToast('error', '导出失败', err.message);
|
|
1767
|
+
});
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
const copyBtn = body.querySelector('[data-copy-id]');
|
|
1771
|
+
if (copyBtn) {
|
|
1772
|
+
copyBtn.addEventListener('click', function (e) {
|
|
1773
|
+
e.preventDefault();
|
|
1774
|
+
e.stopPropagation();
|
|
1775
|
+
copyToClipboard(copyBtn.getAttribute('data-copy-id'), 'lineage_id');
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
} else {
|
|
1779
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">No exported evidence pack yet</div>';
|
|
1780
|
+
}
|
|
1155
1781
|
return;
|
|
1156
1782
|
}
|
|
1783
|
+
|
|
1157
1784
|
const top = packs[0];
|
|
1785
|
+
const exportStatus = top.export_status || 'exported';
|
|
1786
|
+
const jsonExists = top.evidence_json_exists === true;
|
|
1787
|
+
const mdExists = top.evidence_md_exists === true;
|
|
1788
|
+
const exportedAt = top.exported_at || (top.generated_at || '—');
|
|
1789
|
+
const lineageId = top.lineage_id || '';
|
|
1158
1790
|
body.innerHTML =
|
|
1159
1791
|
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1160
|
-
+ '<span class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(
|
|
1161
|
-
+ badgeHtml(
|
|
1792
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(lineageId) + '">' + escapeHtml(shortId(lineageId)) + '</span>'
|
|
1793
|
+
+ badgeHtml(exportStatus, exportStatus === 'exported' ? 'success' : 'warning')
|
|
1162
1794
|
+ '</div>'
|
|
1163
|
-
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">
|
|
1164
|
-
+ '<div class="text-xs
|
|
1795
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">evidence.json: ' + (jsonExists ? '✓' : '✗') + ' · EVIDENCE.md: ' + (mdExists ? '✓' : '✗') + '</div>'
|
|
1796
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);" title="' + escapeHtml(exportedAt) + '">exported_at: ' + escapeHtml(fmtTime(exportedAt)) + '</div>'
|
|
1797
|
+
+ '<div class="text-xs mb-2" style="color: var(--pw-text-tertiary);">policy issues: ' + escapeHtml(String(top.policy ? top.policy.issue_count : 0)) + '</div>'
|
|
1798
|
+
+ '<div class="flex items-center gap-2 flex-wrap">'
|
|
1799
|
+
+ (exportStatus !== 'exported' ? '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-export-pack="' + escapeHtml(lineageId) + '" type="button" style="background-color: var(--pw-accent-muted); color: var(--pw-accent); border: 1px solid var(--pw-accent);">Export evidence pack</button>' : '')
|
|
1800
|
+
+ '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-view-evidence="' + escapeHtml(lineageId) + '" type="button" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">View detail</button>'
|
|
1801
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeHtml(lineageId) + '" title="复制 lineage_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
1802
|
+
+ '</div>';
|
|
1803
|
+
// Wire export button (if present)
|
|
1804
|
+
const exportBtn2 = body.querySelector('[data-export-pack]');
|
|
1805
|
+
if (exportBtn2) {
|
|
1806
|
+
exportBtn2.addEventListener('click', function () {
|
|
1807
|
+
const lid = exportBtn2.getAttribute('data-export-pack');
|
|
1808
|
+
if (!lid) return;
|
|
1809
|
+
postAction('/api/evidence-packs/' + encodeURIComponent(lid) + '/export', exportBtn2).then(function () {
|
|
1810
|
+
showToast('success', '导出成功', 'Evidence pack 已导出');
|
|
1811
|
+
refreshEvidencePacks();
|
|
1812
|
+
}).catch(function (err) {
|
|
1813
|
+
showToast('error', '导出失败', err.message);
|
|
1814
|
+
});
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
// Wire view detail button
|
|
1818
|
+
const viewBtn = body.querySelector('[data-view-evidence]');
|
|
1819
|
+
if (viewBtn) {
|
|
1820
|
+
viewBtn.addEventListener('click', function () {
|
|
1821
|
+
const lid = viewBtn.getAttribute('data-view-evidence');
|
|
1822
|
+
if (lid) showEvidencePackDetail(lid);
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
// Wire copy button
|
|
1826
|
+
const copyBtn2 = body.querySelector('[data-copy-id]');
|
|
1827
|
+
if (copyBtn2) {
|
|
1828
|
+
copyBtn2.addEventListener('click', function (e) {
|
|
1829
|
+
e.preventDefault();
|
|
1830
|
+
e.stopPropagation();
|
|
1831
|
+
copyToClipboard(copyBtn2.getAttribute('data-copy-id'), 'lineage_id');
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1165
1834
|
} catch (err) {
|
|
1166
1835
|
if (status) status.textContent = 'error';
|
|
1167
1836
|
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1168
1837
|
}
|
|
1169
1838
|
}
|
|
1170
1839
|
|
|
1840
|
+
async function showEvidencePackDetail(lineageId) {
|
|
1841
|
+
if (!lineageId) return;
|
|
1842
|
+
const modal = $('safe-modal');
|
|
1843
|
+
const titleEl = $('safe-modal-title');
|
|
1844
|
+
const bodyEl = $('safe-modal-body');
|
|
1845
|
+
if (!modal || !titleEl || !bodyEl) return;
|
|
1846
|
+
titleEl.textContent = 'Evidence Pack · ' + shortId(lineageId);
|
|
1847
|
+
bodyEl.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">Loading…</div>';
|
|
1848
|
+
modal.classList.remove('hidden');
|
|
1849
|
+
try {
|
|
1850
|
+
const data = await fetchJson('/api/evidence-packs/' + encodeURIComponent(lineageId));
|
|
1851
|
+
const jsonPath = data.files && data.files.json ? data.files.json : '';
|
|
1852
|
+
const mdPath = data.files && data.files.markdown ? data.files.markdown : '';
|
|
1853
|
+
const lineage = data.lineage || {};
|
|
1854
|
+
bodyEl.innerHTML =
|
|
1855
|
+
'<div class="mb-3">'
|
|
1856
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-secondary);"><span style="color: var(--pw-text-tertiary);">lineage_id:</span> ' + escapeHtml(data.lineage_id || lineageId) + '</div>'
|
|
1857
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-secondary);"><span style="color: var(--pw-text-tertiary);">generated_at:</span> ' + escapeHtml(fmtTime(data.generated_at || '—')) + '</div>'
|
|
1858
|
+
+ '<div class="flex items-center gap-2 mb-1 flex-wrap">'
|
|
1859
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">final_status:</span>' + badgeHtml(lineage.final_status || 'unknown', statusKind(lineage.final_status))
|
|
1860
|
+
+ '<span class="text-xs" style="color: var(--pw-text-tertiary);">stop_reason:</span><span class="text-xs" style="color: var(--pw-text-secondary);">' + escapeHtml(lineage.stop_reason || '—') + '</span>'
|
|
1861
|
+
+ '</div>'
|
|
1862
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">policy valid: ' + (data.policy && data.policy.valid ? '✓' : '✗') + ' · issues: ' + escapeHtml(String(data.policy ? data.policy.issue_count : 0)) + '</div>'
|
|
1863
|
+
+ (data.next_action ? '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">next: ' + escapeHtml(data.next_action) + '</div>' : '')
|
|
1864
|
+
+ '</div>'
|
|
1865
|
+
+ '<div class="mt-2">'
|
|
1866
|
+
+ '<div class="text-xs font-semibold mb-1" style="color: var(--pw-text-primary);">Files</div>'
|
|
1867
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono); word-break: break-all;" title="' + escapeHtml(jsonPath) + '">evidence.json: ' + (jsonPath ? escapeHtml(jsonPath) : '—') + '</div>'
|
|
1868
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono); word-break: break-all;" title="' + escapeHtml(mdPath) + '">EVIDENCE.md: ' + (mdPath ? escapeHtml(mdPath) : '—') + '</div>'
|
|
1869
|
+
+ '<div class="flex items-center gap-2 mt-2">'
|
|
1870
|
+
+ (jsonPath ? '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-copy-path="' + escapeHtml(jsonPath) + '" type="button" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">Copy evidence.json path</button>' : '')
|
|
1871
|
+
+ (mdPath ? '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-copy-path="' + escapeHtml(mdPath) + '" type="button" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">Copy EVIDENCE.md path</button>' : '')
|
|
1872
|
+
+ '</div>'
|
|
1873
|
+
+ '</div>';
|
|
1874
|
+
// Wire copy path buttons
|
|
1875
|
+
bodyEl.querySelectorAll('[data-copy-path]').forEach(function (btn) {
|
|
1876
|
+
btn.addEventListener('click', function () {
|
|
1877
|
+
copyToClipboard(btn.getAttribute('data-copy-path'), 'file path');
|
|
1878
|
+
});
|
|
1879
|
+
});
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
bodyEl.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// ---------- Repo selector ----------
|
|
1886
|
+
async function loadRepos() {
|
|
1887
|
+
const select = $('repo-selector');
|
|
1888
|
+
if (!select) return;
|
|
1889
|
+
try {
|
|
1890
|
+
const data = await fetchJson('/api/workspace/repos');
|
|
1891
|
+
const repos = Array.isArray(data.repos) ? data.repos : [];
|
|
1892
|
+
let html = '<option value=".">工作区根目录</option>';
|
|
1893
|
+
repos.forEach(function (r) {
|
|
1894
|
+
const label = r.name + (r.has_package_json && r.version ? ' (v' + r.version + ')' : '');
|
|
1895
|
+
const val = r.path || r.name;
|
|
1896
|
+
html += '<option value="' + escapeHtml(val) + '">' + escapeHtml(label) + '</option>';
|
|
1897
|
+
});
|
|
1898
|
+
select.innerHTML = html;
|
|
1899
|
+
} catch (err) {
|
|
1900
|
+
// keep default option
|
|
1901
|
+
console.error('loadRepos failed:', err);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
function onRepoChange(selectedRepoPath) {
|
|
1906
|
+
currentRepoPath = selectedRepoPath || '.';
|
|
1907
|
+
Promise.all([
|
|
1908
|
+
refreshProjectPolicy(),
|
|
1909
|
+
refreshReleaseStatus(),
|
|
1910
|
+
refreshTasks(),
|
|
1911
|
+
refreshLineages(),
|
|
1912
|
+
refreshEvidencePacks(),
|
|
1913
|
+
refreshStaleTasks(),
|
|
1914
|
+
refreshHealthScore()
|
|
1915
|
+
]).catch(function (err) {
|
|
1916
|
+
showError('切换 repo 刷新失败: ' + err.message);
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// ---------- Stale tasks ----------
|
|
1921
|
+
async function refreshStaleTasks() {
|
|
1922
|
+
const body = $('stale-tasks-body');
|
|
1923
|
+
const countEl = $('stale-tasks-count');
|
|
1924
|
+
const card = $('stale-tasks-card');
|
|
1925
|
+
if (!body) return;
|
|
1926
|
+
try {
|
|
1927
|
+
const data = await fetchJson('/api/tasks/stale');
|
|
1928
|
+
const staleTasks = Array.isArray(data.stale_tasks) ? data.stale_tasks : [];
|
|
1929
|
+
const total = (typeof data.total === 'number') ? data.total : staleTasks.length;
|
|
1930
|
+
if (countEl) countEl.textContent = String(total);
|
|
1931
|
+
if (!staleTasks.length) {
|
|
1932
|
+
if (card) card.classList.add('hidden');
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (card) card.classList.remove('hidden');
|
|
1936
|
+
let html = '';
|
|
1937
|
+
staleTasks.forEach(function (st) {
|
|
1938
|
+
const taskId = st.task_id || '';
|
|
1939
|
+
const href = '/pages/task-detail.html?id=' + encodeURIComponent(taskId);
|
|
1940
|
+
const statusBadge = badgeHtml(st.status || '—', statusKind(st.status));
|
|
1941
|
+
const errorText = st.error ? escapeHtml(st.error.length > 100 ? st.error.slice(0, 100) + '…' : st.error) : '';
|
|
1942
|
+
const explanation = st.explanation || '';
|
|
1943
|
+
const nextAction = st.next_action || '';
|
|
1944
|
+
html +=
|
|
1945
|
+
'<div class="py-3" style="border-bottom: 1px solid var(--pw-border-subtle);" data-stale-task="' + escapeHtml(taskId) + '">'
|
|
1946
|
+
+ '<div class="flex items-center gap-2 mb-1.5 flex-wrap">'
|
|
1947
|
+
+ '<span class="text-xs font-medium px-2 py-0.5 rounded whitespace-nowrap" style="color: var(--pw-text-secondary); background-color: var(--pw-bg-hover); font-family: var(--pw-font-mono);" title="' + escapeHtml(taskId) + '">' + escapeHtml(shortId(taskId)) + '</span>'
|
|
1948
|
+
+ statusBadge
|
|
1949
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono); max-width: 200px;" title="' + escapeHtml(st.repo_path || '') + '">' + escapeHtml(st.repo_path || '—') + '</span>'
|
|
1950
|
+
+ '<div class="flex-1"></div>'
|
|
1951
|
+
+ '<button class="pw-quick-btn" data-copy-id="' + escapeHtml(taskId) + '" title="复制 task_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
1952
|
+
+ '</div>'
|
|
1953
|
+
+ (explanation ? '<div class="pw-stale-explanation mb-1.5">' + escapeHtml(explanation) + '</div>' : '')
|
|
1954
|
+
+ (errorText ? '<div class="text-xs mb-1" style="color: var(--pw-state-error); font-family: var(--pw-font-mono);" title="' + escapeHtml(st.error || '') + '">' + errorText + '</div>' : '')
|
|
1955
|
+
+ (nextAction ? '<div class="text-xs mb-1.5" style="color: var(--pw-text-secondary);">next: ' + escapeHtml(nextAction) + '</div>' : '')
|
|
1956
|
+
+ '<div class="flex items-center gap-2">'
|
|
1957
|
+
+ '<a href="' + href + '" class="text-xs px-2.5 py-1 rounded whitespace-nowrap" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle);">查看详情</a>'
|
|
1958
|
+
+ '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" data-hide-stale="' + escapeHtml(taskId) + '" type="button" style="background-color: var(--pw-state-error-bg); color: var(--pw-state-error); border: 1px solid var(--pw-state-error);">隐藏</button>'
|
|
1959
|
+
+ '<button class="text-xs px-2.5 py-1 rounded whitespace-nowrap" disabled style="background-color: var(--pw-bg-hover); color: var(--pw-text-tertiary); border: 1px solid var(--pw-border-subtle);">重建任务</button>'
|
|
1960
|
+
+ '</div>'
|
|
1961
|
+
+ '</div>';
|
|
1962
|
+
});
|
|
1963
|
+
body.innerHTML = html;
|
|
1964
|
+
// Wire copy buttons
|
|
1965
|
+
body.querySelectorAll('[data-copy-id]').forEach(function (btn) {
|
|
1966
|
+
btn.addEventListener('click', function (e) {
|
|
1967
|
+
e.preventDefault();
|
|
1968
|
+
e.stopPropagation();
|
|
1969
|
+
copyToClipboard(btn.getAttribute('data-copy-id'), 'task_id');
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
// Wire hide-stale buttons
|
|
1973
|
+
body.querySelectorAll('[data-hide-stale]').forEach(function (btn) {
|
|
1974
|
+
btn.addEventListener('click', function (e) {
|
|
1975
|
+
e.preventDefault();
|
|
1976
|
+
e.stopPropagation();
|
|
1977
|
+
const tid = btn.getAttribute('data-hide-stale');
|
|
1978
|
+
postAction('/api/tasks/' + encodeURIComponent(tid) + '/hide-stale', btn).then(function () {
|
|
1979
|
+
showToast('success', '已隐藏', 'Stale 任务已隐藏');
|
|
1980
|
+
const row = btn.closest('[data-stale-task]');
|
|
1981
|
+
if (row && row.parentNode) row.parentNode.removeChild(row);
|
|
1982
|
+
const remaining = body.querySelectorAll('[data-stale-task]').length;
|
|
1983
|
+
if (countEl) countEl.textContent = String(remaining);
|
|
1984
|
+
if (remaining === 0 && card) card.classList.add('hidden');
|
|
1985
|
+
}).catch(function (err) {
|
|
1986
|
+
showToast('error', '隐藏失败', err.message);
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
if (card) card.classList.add('hidden');
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1171
1995
|
// ---------- Refresh orchestrators ----------
|
|
1172
1996
|
async function refreshStatus() {
|
|
1173
1997
|
try {
|
|
@@ -1197,7 +2021,7 @@ html.dark {
|
|
|
1197
2021
|
|
|
1198
2022
|
async function refreshTasks() {
|
|
1199
2023
|
try {
|
|
1200
|
-
const data = await fetchJson('/api/tasks');
|
|
2024
|
+
const data = await fetchJson('/api/tasks?repo_path=' + encodeURIComponent(currentRepoPath));
|
|
1201
2025
|
renderTasks(data);
|
|
1202
2026
|
} catch (err) {
|
|
1203
2027
|
const bodyEl = $('recent-tasks-body');
|
|
@@ -1208,14 +2032,14 @@ html.dark {
|
|
|
1208
2032
|
}
|
|
1209
2033
|
|
|
1210
2034
|
async function refreshAll() {
|
|
1211
|
-
await Promise.all([refreshStatus(), refreshTasks(), refreshLogs(), refreshEvents(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks()]);
|
|
2035
|
+
await Promise.all([refreshStatus(), refreshTasks(), refreshLogs(), refreshEvents(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks(), refreshStaleTasks(), refreshHealthScore()]);
|
|
1212
2036
|
}
|
|
1213
2037
|
|
|
1214
2038
|
// ---------- Actions ----------
|
|
1215
2039
|
async function handleAction(action, btnEl) {
|
|
1216
2040
|
try {
|
|
1217
2041
|
if (action === 'refresh') {
|
|
1218
|
-
await Promise.all([refreshStatus(), refreshTasks(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks()]);
|
|
2042
|
+
await Promise.all([refreshStatus(), refreshTasks(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks(), refreshStaleTasks(), refreshHealthScore()]);
|
|
1219
2043
|
return;
|
|
1220
2044
|
}
|
|
1221
2045
|
|
|
@@ -1251,7 +2075,8 @@ html.dark {
|
|
|
1251
2075
|
};
|
|
1252
2076
|
const url = map[action];
|
|
1253
2077
|
if (!url) return;
|
|
1254
|
-
await postAction(url, btnEl);
|
|
2078
|
+
const result = await postAction(url, btnEl);
|
|
2079
|
+
if (action !== 'open-logs-folder') showToast('success', '操作完成', result && result.verified ? '服务已通过就绪检查。' : '命令已完成。');
|
|
1255
2080
|
// Refresh state after state-changing POSTs (open-logs-folder has no state change)
|
|
1256
2081
|
if (action !== 'open-logs-folder') {
|
|
1257
2082
|
await Promise.all([refreshStatus(), refreshTasks()]);
|
|
@@ -1269,6 +2094,35 @@ html.dark {
|
|
|
1269
2094
|
});
|
|
1270
2095
|
});
|
|
1271
2096
|
|
|
2097
|
+
// Copy diagnostics button (system status card header)
|
|
2098
|
+
const copyDiagBtn = $('btn-copy-diagnostics');
|
|
2099
|
+
if (copyDiagBtn) {
|
|
2100
|
+
copyDiagBtn.addEventListener('click', function () {
|
|
2101
|
+
copyDiagnostics(copyDiagBtn);
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Repo selector change handler
|
|
2106
|
+
const repoSelector = $('repo-selector');
|
|
2107
|
+
if (repoSelector) {
|
|
2108
|
+
repoSelector.addEventListener('change', function () {
|
|
2109
|
+
onRepoChange(repoSelector.value);
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// Safe modal close handlers
|
|
2114
|
+
const modalClose = $('safe-modal-close');
|
|
2115
|
+
if (modalClose) {
|
|
2116
|
+
modalClose.addEventListener('click', function () { closeSafeModal(); });
|
|
2117
|
+
}
|
|
2118
|
+
const safeModal = $('safe-modal');
|
|
2119
|
+
if (safeModal) {
|
|
2120
|
+
safeModal.addEventListener('click', function (e) {
|
|
2121
|
+
if (e.target === safeModal) closeSafeModal();
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
loadRepos();
|
|
1272
2126
|
loadToken().then(function () {
|
|
1273
2127
|
auditSeeded = false;
|
|
1274
2128
|
seenAuditKeys = new Set();
|
|
@@ -1282,6 +2136,8 @@ html.dark {
|
|
|
1282
2136
|
|
|
1283
2137
|
// Auto-refresh /api/status every 30s (no logs to avoid performance issues)
|
|
1284
2138
|
statusTimer = setInterval(function () { refreshStatus(); }, 30000);
|
|
2139
|
+
// Health score refresh (lightweight, reuses some endpoints) every 60s
|
|
2140
|
+
setInterval(function () { refreshHealthScore(); }, 60000);
|
|
1285
2141
|
// Refresh activity timeline every 60s
|
|
1286
2142
|
setInterval(function () { refreshEvents(); }, 60000);
|
|
1287
2143
|
// Audit toast polling: every 90s, best-effort
|
|
@@ -1291,5 +2147,7 @@ html.dark {
|
|
|
1291
2147
|
</script>
|
|
1292
2148
|
|
|
1293
2149
|
<script>lucide.createIcons();</script>
|
|
2150
|
+
<script src="/i18n.js"></script>
|
|
2151
|
+
<script src="/desktop-bridge.js"></script>
|
|
1294
2152
|
</body>
|
|
1295
2153
|
</html>
|