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
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatchWarden Control Center — runtime state readers and domain helpers.
|
|
3
|
+
*
|
|
4
|
+
* Provides health probing, runtime file readers (tunnel status, tool manifest,
|
|
5
|
+
* watcher status), stale-task classification, the control-center status/event
|
|
6
|
+
* timeline, hidden-id UI state, and small shared helpers (verdict parsing,
|
|
7
|
+
* file mtime, id validation). These are pure helpers consumed by the route
|
|
8
|
+
* modules; they hold no HTTP-layer concerns beyond reusing `sendJson`.
|
|
9
|
+
*/
|
|
10
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { get as httpGet } from "node:http";
|
|
13
|
+
import { listTasks } from "../tools/tasks/listTasks.js";
|
|
14
|
+
import { listAgents } from "../tools/workspace/listAgents.js";
|
|
15
|
+
import { readWatcherStatus } from "../watcherStatus.js";
|
|
16
|
+
import { resolveWorkspaceRoot } from "../config.js";
|
|
17
|
+
import { PATCHWARDEN_VERSION } from "../version.js";
|
|
18
|
+
import { isValidDirectSessionId as isValidStoredDirectSessionId } from "../direct/directSessionStore.js";
|
|
19
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
20
|
+
import { withFileLockSync } from "../utils/lockedJsonFile.js";
|
|
21
|
+
import { logger } from "../logging.js";
|
|
22
|
+
import { config, controlCenterEventsPath, controlCenterStatusPath, errorMessage, getControlCenterLogDir, getRuntimeRoot, host, MAX_EVENT_LINES, port, readJsonFileSafe, } from "./shared.js";
|
|
23
|
+
export function probeHealthStatus(targetUrl) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
let settled = false;
|
|
27
|
+
const finish = (result) => {
|
|
28
|
+
if (settled)
|
|
29
|
+
return;
|
|
30
|
+
settled = true;
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
resolve(result);
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
try {
|
|
36
|
+
controller.abort();
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore */ }
|
|
39
|
+
finish({ available: false, status: null, reason: "timeout after 2000ms" });
|
|
40
|
+
}, 2000);
|
|
41
|
+
try {
|
|
42
|
+
const req = httpGet(targetUrl, { signal: controller.signal }, (resp) => {
|
|
43
|
+
resp.resume();
|
|
44
|
+
const status = resp.statusCode ?? 0;
|
|
45
|
+
// Only 2xx counts as available. A 404 from an unrelated service (or a
|
|
46
|
+
// 4xx/5xx from the real service) must NOT be treated as healthy.
|
|
47
|
+
if (status >= 200 && status < 300) {
|
|
48
|
+
finish({ available: true, status, reason: null });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
finish({ available: false, status, reason: `unexpected status ${status}` });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
req.on("error", (err) => {
|
|
55
|
+
finish({ available: false, status: null, reason: err.message });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
finish({ available: false, status: null, reason: errorMessage(err) });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export async function probeRuntimeHealth(baseUrl) {
|
|
64
|
+
const [h, r] = await Promise.all([
|
|
65
|
+
probeHealthStatus(`${baseUrl}/healthz`),
|
|
66
|
+
probeHealthStatus(`${baseUrl}/readyz`),
|
|
67
|
+
]);
|
|
68
|
+
if (h.available && r.available && h.status !== null && r.status !== null) {
|
|
69
|
+
return { available: true, reason: null, healthz: { status: h.status }, readyz: { status: r.status } };
|
|
70
|
+
}
|
|
71
|
+
const failed = !h.available ? h : r;
|
|
72
|
+
return { available: false, reason: failed.reason ?? "unavailable", healthz: null, readyz: null };
|
|
73
|
+
}
|
|
74
|
+
// ── Runtime file readers ──────────────────────────────────────────
|
|
75
|
+
export function readTunnelStatus(direct) {
|
|
76
|
+
const filePath = join(getRuntimeRoot(direct), "tunnel-status.json");
|
|
77
|
+
if (!existsSync(filePath))
|
|
78
|
+
return { observed: false };
|
|
79
|
+
try {
|
|
80
|
+
const data = readJsonFileSafe(filePath);
|
|
81
|
+
if (data === null)
|
|
82
|
+
return { observed: true, error: "invalid JSON" };
|
|
83
|
+
return { observed: true, ...data };
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
return { observed: true, error: errorMessage(err) };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function readToolManifest(direct) {
|
|
90
|
+
const empty = {
|
|
91
|
+
tool_profile: null,
|
|
92
|
+
tool_count: null,
|
|
93
|
+
schema_epoch: null,
|
|
94
|
+
tool_manifest_sha256: null,
|
|
95
|
+
tool_names: null,
|
|
96
|
+
};
|
|
97
|
+
const filePath = join(getRuntimeRoot(direct), "tool-manifest.json");
|
|
98
|
+
if (!existsSync(filePath))
|
|
99
|
+
return empty;
|
|
100
|
+
const data = readJsonFileSafe(filePath);
|
|
101
|
+
if (!data)
|
|
102
|
+
return empty;
|
|
103
|
+
return {
|
|
104
|
+
tool_profile: typeof data.tool_profile === "string" ? data.tool_profile : null,
|
|
105
|
+
tool_count: typeof data.tool_count === "number" ? data.tool_count : null,
|
|
106
|
+
schema_epoch: typeof data.schema_epoch === "string" ? data.schema_epoch : null,
|
|
107
|
+
tool_manifest_sha256: typeof data.tool_manifest_sha256 === "string" ? data.tool_manifest_sha256 : null,
|
|
108
|
+
tool_names: Array.isArray(data.tool_names) ? data.tool_names : null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function readTunnelUrl(direct) {
|
|
112
|
+
const filePath = join(getRuntimeRoot(direct), "tunnel-health-url.txt");
|
|
113
|
+
if (!existsSync(filePath))
|
|
114
|
+
return { url: null, reason: "tunnel-health-url.txt not found" };
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(filePath, "utf-8").trim();
|
|
117
|
+
if (!content)
|
|
118
|
+
return { url: null, reason: "tunnel-health-url.txt is empty" };
|
|
119
|
+
return { url: content, reason: null };
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
return { url: null, reason: errorMessage(err) };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ── Safe wrappers around reusable modules ─────────────────────────
|
|
126
|
+
export function readWatcherStatusSafe() {
|
|
127
|
+
try {
|
|
128
|
+
return readWatcherStatus(config);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
return {
|
|
132
|
+
status: "unreadable",
|
|
133
|
+
available: false,
|
|
134
|
+
stale_after_seconds: config.watcherStaleSeconds,
|
|
135
|
+
last_heartbeat_at: null,
|
|
136
|
+
heartbeat_age_seconds: null,
|
|
137
|
+
heartbeat_pid: null,
|
|
138
|
+
instance_id: null,
|
|
139
|
+
launcher_pid: null,
|
|
140
|
+
reason: errorMessage(err),
|
|
141
|
+
activity: null,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function listAgentsSafe() {
|
|
146
|
+
try {
|
|
147
|
+
return listAgents().agents;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function resolveWorkspaceRootSafe() {
|
|
154
|
+
try {
|
|
155
|
+
return resolveWorkspaceRoot(config);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export const TERMINAL_TASK_STATUSES = new Set([
|
|
162
|
+
"done",
|
|
163
|
+
"done_by_agent",
|
|
164
|
+
"failed",
|
|
165
|
+
"failed_verification",
|
|
166
|
+
"failed_scope_violation",
|
|
167
|
+
"failed_policy_violation",
|
|
168
|
+
"canceled",
|
|
169
|
+
"timeout",
|
|
170
|
+
]);
|
|
171
|
+
/**
|
|
172
|
+
* Classify a task as stale based on Phase 2 rules:
|
|
173
|
+
* - status=running but last_heartbeat_at exceeds threshold
|
|
174
|
+
* - phase=collecting_artifacts exceeds threshold
|
|
175
|
+
* - current_command=null AND watcher currently healthy
|
|
176
|
+
* - task last_heartbeat_at significantly earlier than current watcher heartbeat
|
|
177
|
+
*
|
|
178
|
+
* Only pending/running tasks can be stale; terminal tasks are never stale.
|
|
179
|
+
*/
|
|
180
|
+
export function classifyStaleTask(task, watcher, nowMs = Date.now()) {
|
|
181
|
+
const reasons = [];
|
|
182
|
+
if (TERMINAL_TASK_STATUSES.has(task.status)) {
|
|
183
|
+
return { is_stale: false, stale_reasons: reasons };
|
|
184
|
+
}
|
|
185
|
+
// Only pending/running are candidates for staleness.
|
|
186
|
+
if (task.status !== "pending" && task.status !== "running") {
|
|
187
|
+
return { is_stale: false, stale_reasons: reasons };
|
|
188
|
+
}
|
|
189
|
+
const staleThresholdMs = config.watcherStaleSeconds * 1000;
|
|
190
|
+
const hbMs = Date.parse(task.last_heartbeat_at || "");
|
|
191
|
+
const heartbeatAgeMs = Number.isFinite(hbMs) ? Math.max(0, nowMs - hbMs) : null;
|
|
192
|
+
// Rule 1: running with stale heartbeat
|
|
193
|
+
if (task.status === "running" && heartbeatAgeMs !== null && heartbeatAgeMs > staleThresholdMs) {
|
|
194
|
+
reasons.push("heartbeat_stale");
|
|
195
|
+
}
|
|
196
|
+
// Rule 2: collecting_artifacts phase exceeds threshold
|
|
197
|
+
if (task.phase === "collecting_artifacts" && heartbeatAgeMs !== null && heartbeatAgeMs > staleThresholdMs) {
|
|
198
|
+
reasons.push("collecting_artifacts_stale");
|
|
199
|
+
}
|
|
200
|
+
// Rule 3: running with no current_command while watcher is healthy
|
|
201
|
+
if (task.status === "running" &&
|
|
202
|
+
(task.current_command === null || task.current_command === "") &&
|
|
203
|
+
watcher.status === "healthy") {
|
|
204
|
+
reasons.push("running_no_command_watcher_healthy");
|
|
205
|
+
}
|
|
206
|
+
// Rule 4: task heartbeat significantly earlier than watcher heartbeat
|
|
207
|
+
if (heartbeatAgeMs !== null && watcher.last_heartbeat_at) {
|
|
208
|
+
const watcherHbMs = Date.parse(watcher.last_heartbeat_at);
|
|
209
|
+
if (Number.isFinite(watcherHbMs)) {
|
|
210
|
+
const gapMs = watcherHbMs - hbMs;
|
|
211
|
+
// Task heartbeat is "significantly earlier" than watcher heartbeat when
|
|
212
|
+
// the task has not heartbeat for at least 2x the stale threshold while
|
|
213
|
+
// the watcher is alive.
|
|
214
|
+
if (gapMs > staleThresholdMs * 2 && watcher.status === "healthy") {
|
|
215
|
+
reasons.push("heartbeat_far_behind_watcher");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { is_stale: reasons.length > 0, stale_reasons: reasons };
|
|
220
|
+
}
|
|
221
|
+
export function augmentTaskWithStale(task, watcher, nowMs = Date.now()) {
|
|
222
|
+
const cls = classifyStaleTask(task, watcher, nowMs);
|
|
223
|
+
return { ...task, is_stale: cls.is_stale, stale_reasons: cls.stale_reasons };
|
|
224
|
+
}
|
|
225
|
+
export function listTasksForStatus() {
|
|
226
|
+
try {
|
|
227
|
+
const result = listTasks({ limit: 100 });
|
|
228
|
+
const watcher = result.watcher;
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
let active = 0;
|
|
231
|
+
let stale = 0;
|
|
232
|
+
const staleTaskIds = [];
|
|
233
|
+
const augmented = result.tasks.map((t) => {
|
|
234
|
+
const a = augmentTaskWithStale(t, watcher, now);
|
|
235
|
+
if (t.status === "pending" || t.status === "running")
|
|
236
|
+
active++;
|
|
237
|
+
if (a.is_stale) {
|
|
238
|
+
stale++;
|
|
239
|
+
staleTaskIds.push(t.task_id);
|
|
240
|
+
}
|
|
241
|
+
return a;
|
|
242
|
+
});
|
|
243
|
+
return { tasks: augmented, total: result.total, active, stale, stale_task_ids: staleTaskIds, reason: null };
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
return { tasks: [], total: 0, active: 0, stale: 0, stale_task_ids: [], reason: errorMessage(err) };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
export function writeStatusFile() {
|
|
250
|
+
const status = {
|
|
251
|
+
pid: process.pid,
|
|
252
|
+
port,
|
|
253
|
+
started_at: new Date().toISOString(),
|
|
254
|
+
url: `http://${host}:${port}/`,
|
|
255
|
+
version: PATCHWARDEN_VERSION,
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
mkdirSync(getControlCenterLogDir(), { recursive: true });
|
|
259
|
+
atomicWriteJsonFileSync(controlCenterStatusPath, status);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
logger.error("[control-center] Failed to write status file", { error: errorMessage(err) });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
export function removeStatusFile() {
|
|
266
|
+
try {
|
|
267
|
+
if (existsSync(controlCenterStatusPath)) {
|
|
268
|
+
unlinkSync(controlCenterStatusPath);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
logger.error("[control-center] Failed to remove status file", { error: errorMessage(err) });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Append a single event line to the JSONL timeline. Best-effort: a write
|
|
277
|
+
* failure is logged but never crashes the server. Trims the file to
|
|
278
|
+
* MAX_EVENT_LINES lazily when it grows past 1.5x the cap, so we don't pay the
|
|
279
|
+
* trim cost on every event.
|
|
280
|
+
*/
|
|
281
|
+
export function recordEvent(type, payload) {
|
|
282
|
+
const event = {
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
type,
|
|
285
|
+
payload,
|
|
286
|
+
};
|
|
287
|
+
try {
|
|
288
|
+
mkdirSync(getControlCenterLogDir(), { recursive: true });
|
|
289
|
+
withFileLockSync(controlCenterEventsPath, () => {
|
|
290
|
+
appendFileSync(controlCenterEventsPath, JSON.stringify(event) + "\n", "utf-8");
|
|
291
|
+
const stat = statSync(controlCenterEventsPath);
|
|
292
|
+
if (stat.size > 512 * 1024)
|
|
293
|
+
trimEventsFileUnlocked();
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
logger.error("[control-center] Failed to write event", { error: errorMessage(err) });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function trimEventsFileUnlocked() {
|
|
301
|
+
try {
|
|
302
|
+
if (!existsSync(controlCenterEventsPath))
|
|
303
|
+
return;
|
|
304
|
+
const raw = readFileSync(controlCenterEventsPath, "utf-8");
|
|
305
|
+
const lines = raw.split(/\r?\n/).filter((l) => l.length > 0);
|
|
306
|
+
if (lines.length <= MAX_EVENT_LINES)
|
|
307
|
+
return;
|
|
308
|
+
const trimmed = lines.slice(lines.length - MAX_EVENT_LINES);
|
|
309
|
+
atomicWriteFileSync(controlCenterEventsPath, trimmed.join("\n") + "\n");
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
logger.error("[control-center] Failed to trim events file", { error: errorMessage(err) });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export function readEvents(limit) {
|
|
316
|
+
if (!existsSync(controlCenterEventsPath))
|
|
317
|
+
return [];
|
|
318
|
+
try {
|
|
319
|
+
const raw = readFileSync(controlCenterEventsPath, "utf-8");
|
|
320
|
+
const lines = raw.split(/\r?\n/).filter((l) => l.length > 0);
|
|
321
|
+
const sliced = lines.slice(Math.max(0, lines.length - limit));
|
|
322
|
+
const events = [];
|
|
323
|
+
for (const line of sliced) {
|
|
324
|
+
try {
|
|
325
|
+
events.push(JSON.parse(line));
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
/* skip malformed line */
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return events;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// ── Hidden stale task IDs (local UI state) ────────────────────────
|
|
338
|
+
export function readHiddenStaleIds() {
|
|
339
|
+
const p = join(getControlCenterLogDir(), "hidden-stale-ids.json");
|
|
340
|
+
if (!existsSync(p))
|
|
341
|
+
return [];
|
|
342
|
+
return readStoredStringArray(p);
|
|
343
|
+
}
|
|
344
|
+
export function reconstructTaskEntry(taskId, statusData, runtimeData = {}, watcher = readWatcherStatusSafe()) {
|
|
345
|
+
const validAcceptance = new Set(["pending", "accepted", "rejected", "needs_fix", "blocked"]);
|
|
346
|
+
const taskStatus = String(statusData.status || "pending");
|
|
347
|
+
const acceptanceStatus = taskStatus === "done_by_agent"
|
|
348
|
+
? (typeof statusData.acceptance_status === "string" && validAcceptance.has(statusData.acceptance_status)
|
|
349
|
+
? statusData.acceptance_status
|
|
350
|
+
: "pending")
|
|
351
|
+
: null;
|
|
352
|
+
return {
|
|
353
|
+
task_id: taskId,
|
|
354
|
+
plan_id: String(statusData.plan_id || ""),
|
|
355
|
+
title: "",
|
|
356
|
+
agent: String(statusData.agent || ""),
|
|
357
|
+
status: taskStatus,
|
|
358
|
+
phase: String(runtimeData.phase || statusData.phase || "queued"),
|
|
359
|
+
acceptance_status: acceptanceStatus,
|
|
360
|
+
created_at: String(statusData.created_at || ""),
|
|
361
|
+
updated_at: String(statusData.updated_at || ""),
|
|
362
|
+
workspace_root: String(statusData.workspace_root || config.workspaceRoot),
|
|
363
|
+
repo_path: String(statusData.repo_path || "."),
|
|
364
|
+
resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
|
|
365
|
+
test_command: String(statusData.test_command || ""),
|
|
366
|
+
verify_commands: Array.isArray(statusData.verify_commands)
|
|
367
|
+
? statusData.verify_commands.filter((command) => typeof command === "string")
|
|
368
|
+
: [],
|
|
369
|
+
error: statusData.error ? String(statusData.error) : null,
|
|
370
|
+
last_heartbeat_at: String(runtimeData.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
|
|
371
|
+
current_command: runtimeData.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
|
|
372
|
+
timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
|
|
373
|
+
pending_reason: null,
|
|
374
|
+
watcher_status: watcher.status,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
export function writeHiddenStaleIds(ids) {
|
|
378
|
+
const dir = getControlCenterLogDir();
|
|
379
|
+
if (!existsSync(dir))
|
|
380
|
+
mkdirSync(dir, { recursive: true });
|
|
381
|
+
atomicWriteJsonFileSync(join(dir, "hidden-stale-ids.json"), normalizeStoredIds(ids));
|
|
382
|
+
}
|
|
383
|
+
// ── Hidden direct session IDs (local UI state) ────────────────────
|
|
384
|
+
export function readHiddenDirectSessionIds() {
|
|
385
|
+
const p = join(getControlCenterLogDir(), "hidden-direct-session-ids.json");
|
|
386
|
+
if (!existsSync(p))
|
|
387
|
+
return [];
|
|
388
|
+
return readStoredStringArray(p);
|
|
389
|
+
}
|
|
390
|
+
export function writeHiddenDirectSessionIds(ids) {
|
|
391
|
+
const dir = getControlCenterLogDir();
|
|
392
|
+
if (!existsSync(dir))
|
|
393
|
+
mkdirSync(dir, { recursive: true });
|
|
394
|
+
atomicWriteJsonFileSync(join(dir, "hidden-direct-session-ids.json"), normalizeStoredIds(ids));
|
|
395
|
+
}
|
|
396
|
+
export function isValidDirectSessionId(sessionId) {
|
|
397
|
+
return isValidStoredDirectSessionId(sessionId);
|
|
398
|
+
}
|
|
399
|
+
export function isValidTaskId(taskId) {
|
|
400
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,159}$/.test(taskId);
|
|
401
|
+
}
|
|
402
|
+
function readStoredStringArray(path) {
|
|
403
|
+
const value = readJsonFileSafe(path);
|
|
404
|
+
if (!Array.isArray(value))
|
|
405
|
+
return [];
|
|
406
|
+
return normalizeStoredIds(value);
|
|
407
|
+
}
|
|
408
|
+
function normalizeStoredIds(value) {
|
|
409
|
+
return [...new Set(value.filter((entry) => typeof entry === "string" && entry.length > 0))];
|
|
410
|
+
}
|
|
411
|
+
// ── Small shared helpers ──────────────────────────────────────────
|
|
412
|
+
export function parseReviewVerdict(content) {
|
|
413
|
+
// independent-review.md format: "**Verdict**: PASS" (case-insensitive)
|
|
414
|
+
const m = content.match(/\*\*Verdict\*\*\s*:\s*([A-Za-z]+)/);
|
|
415
|
+
return m ? m[1].toLowerCase() : null;
|
|
416
|
+
}
|
|
417
|
+
export function fileMtimeIso(filePath) {
|
|
418
|
+
try {
|
|
419
|
+
const m = statSync(filePath).mtime;
|
|
420
|
+
return m ? m.toISOString() : null;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatchWarden Control Center — HTTP server creation, routing, and lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* This module wires together the route handlers (routes/*.ts), middleware
|
|
5
|
+
* (auth/static), and the shared/runtime helpers. It owns the `handleRequest`
|
|
6
|
+
* router that dispatches every GET/POST route, the POST control-token gate,
|
|
7
|
+
* and the server bootstrap/shutdown lifecycle (status file, activity events,
|
|
8
|
+
* SIGINT/SIGTERM).
|
|
9
|
+
*
|
|
10
|
+
* Entry point: `controlCenter.ts` imports `startServer()` from here.
|
|
11
|
+
*/
|
|
12
|
+
import { type Server } from "node:http";
|
|
13
|
+
export declare function startServer(): Server;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatchWarden Control Center — HTTP server creation, routing, and lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* This module wires together the route handlers (routes/*.ts), middleware
|
|
5
|
+
* (auth/static), and the shared/runtime helpers. It owns the `handleRequest`
|
|
6
|
+
* router that dispatches every GET/POST route, the POST control-token gate,
|
|
7
|
+
* and the server bootstrap/shutdown lifecycle (status file, activity events,
|
|
8
|
+
* SIGINT/SIGTERM).
|
|
9
|
+
*
|
|
10
|
+
* Entry point: `controlCenter.ts` imports `startServer()` from here.
|
|
11
|
+
*/
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
14
|
+
import { logger } from "../logging.js";
|
|
15
|
+
import { config, controlToken, errorMessage, host, PAGE_ALIASES, port, readBody, sendJson, } from "./shared.js";
|
|
16
|
+
import { recordEvent, removeStatusFile, writeStatusFile } from "./runtime.js";
|
|
17
|
+
import { checkControlToken, isTrustedControlHost } from "./middleware/auth.js";
|
|
18
|
+
import { serveFavicon, serveStatic } from "./middleware/static.js";
|
|
19
|
+
import { buildRoutes } from "./routeTable.js";
|
|
20
|
+
// ── Request router ────────────────────────────────────────────────
|
|
21
|
+
async function handleRequest(req, res) {
|
|
22
|
+
res.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
|
|
23
|
+
res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
|
|
24
|
+
res.setHeader("Referrer-Policy", "no-referrer");
|
|
25
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
26
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
27
|
+
if (!isTrustedControlHost(req, port)) {
|
|
28
|
+
sendJson(res, 421, { error: "Untrusted Host header" });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const parsedUrl = new URL(req.url || "/", `http://${host}:${port}`);
|
|
32
|
+
const pathname = parsedUrl.pathname;
|
|
33
|
+
const method = (req.method || "GET").toUpperCase();
|
|
34
|
+
// ── Static resource priority branches (not in routeTable.ts) ──
|
|
35
|
+
if (method === "GET" && pathname === "/") {
|
|
36
|
+
serveStatic(res, "pages/dashboard.html");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const pageAlias = PAGE_ALIASES[pathname];
|
|
40
|
+
if (method === "GET" && pageAlias) {
|
|
41
|
+
serveStatic(res, pageAlias);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (method === "GET" && pathname === "/control-token.json") {
|
|
45
|
+
sendJson(res, 200, { token: controlToken });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (method === "GET" && pathname === "/favicon.ico") {
|
|
49
|
+
serveFavicon(res);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (method === "GET" &&
|
|
53
|
+
(pathname === "/colors_and_type.css" ||
|
|
54
|
+
pathname === "/desktop.css" ||
|
|
55
|
+
pathname === "/desktop-bridge.js" ||
|
|
56
|
+
pathname === "/i18n.js" ||
|
|
57
|
+
pathname === "/getting-started.js" ||
|
|
58
|
+
pathname === "/settings.js" ||
|
|
59
|
+
pathname.startsWith("/pages/") ||
|
|
60
|
+
pathname.startsWith("/partials/") ||
|
|
61
|
+
pathname.startsWith("/vendor/"))) {
|
|
62
|
+
serveStatic(res, pathname);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// ── POST routes: drain body + verify control token before dispatch ──
|
|
66
|
+
// All POST routes in the table declare `requiresToken: true`; the body is
|
|
67
|
+
// drained up-front so HTTP keep-alive connections stay clean even when the
|
|
68
|
+
// route is unknown (preserving the original handleRequest behavior).
|
|
69
|
+
if (method === "POST") {
|
|
70
|
+
await readBody(req);
|
|
71
|
+
if (!checkControlToken(req)) {
|
|
72
|
+
sendJson(res, 403, { error: "Missing or invalid control token" });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ── Declarative route table dispatch ──
|
|
77
|
+
// Filter by method → match pattern (in declaration order, so specific
|
|
78
|
+
// patterns win over generic `:id`) → call handler with capture-group slice.
|
|
79
|
+
const routes = buildRoutes(parsedUrl);
|
|
80
|
+
for (const route of routes) {
|
|
81
|
+
if (route.method !== method)
|
|
82
|
+
continue;
|
|
83
|
+
const match = pathname.match(route.pattern);
|
|
84
|
+
if (!match)
|
|
85
|
+
continue;
|
|
86
|
+
await route.handler(res, match.slice(1));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
sendJson(res, 404, { error: "Not found" });
|
|
90
|
+
}
|
|
91
|
+
// ── Server bootstrap ──────────────────────────────────────────────
|
|
92
|
+
let server = null;
|
|
93
|
+
export function startServer() {
|
|
94
|
+
server = createServer((req, res) => {
|
|
95
|
+
handleRequest(req, res).catch((err) => {
|
|
96
|
+
if (!res.headersSent) {
|
|
97
|
+
sendJson(res, 500, { error: errorMessage(err) });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
try {
|
|
101
|
+
res.end();
|
|
102
|
+
}
|
|
103
|
+
catch { /* ignore */ }
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
server.on("error", (err) => {
|
|
108
|
+
logger.fatal("[control-center] Server error", { error: errorMessage(err) });
|
|
109
|
+
process.exit(1);
|
|
110
|
+
});
|
|
111
|
+
server.listen(port, host, () => {
|
|
112
|
+
const addr = server.address();
|
|
113
|
+
const formatted = addr && typeof addr === "object" ? `http://${addr.address}:${addr.port}/` : `http://${host}:${port}/`;
|
|
114
|
+
logger.info(`[control-center] PatchWarden v${PATCHWARDEN_VERSION} (schema epoch ${TOOL_SCHEMA_EPOCH})`);
|
|
115
|
+
logger.info(`[control-center] Workspace: ${config.workspaceRoot}`);
|
|
116
|
+
logger.info(`[control-center] Listening: ${formatted}`);
|
|
117
|
+
logger.info(`[control-center] Bound to 127.0.0.1 only — not exposed to network`);
|
|
118
|
+
// Persist status file so the launcher can detect a running instance and
|
|
119
|
+
// open the browser without spawning a second server.
|
|
120
|
+
writeStatusFile();
|
|
121
|
+
recordEvent("control_center.started", {
|
|
122
|
+
pid: process.pid,
|
|
123
|
+
port,
|
|
124
|
+
url: formatted,
|
|
125
|
+
version: PATCHWARDEN_VERSION,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
process.on("SIGINT", shutdown);
|
|
129
|
+
process.on("SIGTERM", shutdown);
|
|
130
|
+
return server;
|
|
131
|
+
}
|
|
132
|
+
function shutdown() {
|
|
133
|
+
logger.info("[control-center] Shutting down...");
|
|
134
|
+
recordEvent("control_center.stopped", { pid: process.pid });
|
|
135
|
+
removeStatusFile();
|
|
136
|
+
if (server) {
|
|
137
|
+
server.close(() => {
|
|
138
|
+
try {
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
catch { /* ignore */ }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
try {
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
catch { /* ignore */ }
|
|
149
|
+
}, 3000).unref();
|
|
150
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { delimiter, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
|
+
import { type PatchWardenConfig } from "../config.js";
|
|
4
|
+
export declare const projectRoot: string;
|
|
5
|
+
export declare const uiRoot: string;
|
|
6
|
+
export declare const manageScriptPath: string;
|
|
7
|
+
export declare const activeConfigPath: string;
|
|
8
|
+
export declare const configIdentitySha256: string;
|
|
9
|
+
export declare const PAGE_ALIASES: Record<string, string>;
|
|
10
|
+
export declare function createFallbackConfig(): PatchWardenConfig;
|
|
11
|
+
export declare let config: PatchWardenConfig;
|
|
12
|
+
export declare const controlToken: `${string}-${string}-${string}-${string}-${string}`;
|
|
13
|
+
export declare function resolvePort(): number;
|
|
14
|
+
export declare const port: number;
|
|
15
|
+
export declare const host = "127.0.0.1";
|
|
16
|
+
export declare const CORE_BASE_URL: string;
|
|
17
|
+
export declare const DIRECT_BASE_URL: string;
|
|
18
|
+
export declare const CONTROL_CENTER_FAVICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#0a0e14\"/>\n <path d=\"M32 52s17-8 17-27V14L32 8 15 14v11c0 19 17 27 17 27z\" fill=\"#111820\" stroke=\"#2dd4a8\" stroke-width=\"4\" stroke-linejoin=\"round\"/>\n <path d=\"M24 32l6 6 12-14\" fill=\"none\" stroke=\"#2dd4a8\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>";
|
|
19
|
+
export declare function errorMessage(err: unknown): string;
|
|
20
|
+
export declare function getRuntimeRoot(direct: boolean): string;
|
|
21
|
+
export declare function getControlCenterLogDir(): string;
|
|
22
|
+
export declare const controlCenterStatusPath: string;
|
|
23
|
+
export declare const controlCenterEventsPath: string;
|
|
24
|
+
export declare const MAX_EVENT_LINES = 2000;
|
|
25
|
+
export declare const ALLOWED_LOG_TAILS: Set<number>;
|
|
26
|
+
export declare function resolveTailParam(value: string | null): number;
|
|
27
|
+
export declare const CONTENT_TYPES: Record<string, string>;
|
|
28
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
|
|
29
|
+
export declare function readBody(req: IncomingMessage): Promise<unknown | null>;
|
|
30
|
+
export declare function readJsonFileSafe<T = unknown>(filePath: string): T | null;
|
|
31
|
+
export declare function isPathInside(root: string, target: string): boolean;
|
|
32
|
+
export declare function readJsonFileSafeUnder<T = unknown>(root: string, relPath: string): T | null;
|
|
33
|
+
export declare function readTextFileSafe(filePath: string): string | null;
|
|
34
|
+
/** Resolve a Control Center artifact path and reject symlink/junction escapes. */
|
|
35
|
+
export declare function guardControlPath(path: string, allowedPrefix: string): string | null;
|
|
36
|
+
export declare function readFileTail(filePath: string, lines?: number): string;
|
|
37
|
+
export declare function findLatestLog(dir: string, pattern: RegExp): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Declarative route descriptor consumed by `routeTable.ts` and dispatched by
|
|
40
|
+
* `server.ts`'s `handleRequest`. The handler receives the slice of path
|
|
41
|
+
* capture groups `[match[1], match[2], ...]` so wrappers can decode them via
|
|
42
|
+
* `decodeParam`. POST routes default to `requiresToken: true`.
|
|
43
|
+
*/
|
|
44
|
+
export interface Route {
|
|
45
|
+
method: "GET" | "POST";
|
|
46
|
+
pattern: RegExp;
|
|
47
|
+
handler: (res: ServerResponse, params: string[]) => Promise<void> | void;
|
|
48
|
+
requiresToken?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Safely decode a path capture group, falling back to the raw value if the
|
|
52
|
+
* percent-encoding is malformed. Accepts the raw `RegExpMatchArray` or the
|
|
53
|
+
* sliced `string[]` params passed to a Route handler — both are readable by
|
|
54
|
+
* numeric index. Note: under `lib: es2024+` `RegExpMatchArray` is a tuple
|
|
55
|
+
* `[string, ...string[]]`, so we widen the parameter to `readonly string[]` to
|
|
56
|
+
* accept both shapes without forcing callers to cast.
|
|
57
|
+
*/
|
|
58
|
+
export declare function decodeParam(match: readonly string[] | RegExpMatchArray | null, index: number): string;
|
|
59
|
+
export { delimiter, extname, join, resolve, isAbsolute, relative };
|