patchwarden 1.5.1 → 1.6.2
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 +158 -26
- package/README.md +124 -24
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
- package/dist/assessments/assessmentDiagnostics.js +132 -0
- package/dist/assessments/assessmentStore.d.ts +39 -0
- package/dist/assessments/assessmentStore.js +207 -45
- package/dist/assessments/securitySnapshot.d.ts +50 -0
- package/dist/assessments/securitySnapshot.js +158 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +66 -13
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +6 -0
- package/dist/control/middleware/auth.js +14 -5
- package/dist/control/middleware/static.js +16 -3
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.js +17 -7
- package/dist/control/routes/evidence.js +1 -1
- package/dist/control/routes/lineage.js +1 -1
- package/dist/control/routes/process.d.ts +7 -0
- package/dist/control/routes/process.js +329 -42
- package/dist/control/routes/sessions.d.ts +9 -1
- package/dist/control/routes/sessions.js +84 -21
- package/dist/control/routes/status.d.ts +60 -1
- package/dist/control/routes/status.js +94 -15
- package/dist/control/routes/taskActions.js +24 -60
- package/dist/control/routes/tasks.d.ts +4 -0
- package/dist/control/routes/tasks.js +61 -62
- package/dist/control/routes/workspace.js +50 -23
- package/dist/control/runtime.d.ts +3 -2
- package/dist/control/runtime.js +65 -40
- package/dist/control/server.js +37 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +42 -29
- 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 +128 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +65 -57
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.js +5 -12
- package/dist/goal/goalStatus.d.ts +2 -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.js +97 -96
- package/dist/goal/subgoalSync.js +11 -9
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +111 -32
- package/dist/logging.js +5 -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 +13 -5
- package/dist/runner/changeCapture.js +243 -61
- package/dist/runner/postTaskCleanup.js +48 -19
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +2 -1
- package/dist/runner/runTask.js +162 -93
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +50 -36
- 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} +3 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +35 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +320 -49
- package/dist/security/contentRedaction.js +2 -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 +27 -26
- 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} +6 -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} +2 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
- 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} +12 -8
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
- 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.js → direct/createDirectSession.js} +6 -6
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
- 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.js +32 -31
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
- package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
- package/dist/tools/dispatch/directDispatch.js +11 -10
- package/dist/tools/dispatch/goalDispatch.js +5 -4
- package/dist/tools/dispatch/releaseDispatch.js +3 -2
- 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} +1 -1
- 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 +7 -11
- package/dist/tools/registry.js +17 -1178
- 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 +86 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
- package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
- 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} +11 -6
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
- 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} +5 -4
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
- package/dist/tools/tasks/reconcileTasks.js +496 -0
- 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} +63 -41
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/tasks/taskStates.d.ts +6 -0
- package/dist/tools/tasks/taskStates.js +52 -0
- package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
- 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/workspace/listAgents.d.ts +23 -0
- package/dist/tools/workspace/listAgents.js +56 -0
- 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 +67 -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 +808 -143
- package/docs/architecture.md +15 -55
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +4 -4
- package/docs/desktop-app.md +175 -0
- package/docs/evidence-pack-schema.md +3 -3
- package/docs/open-source-application.md +16 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +35 -39
- package/docs/task-safe-review-workflow.md +5 -5
- package/docs/threat-model.md +19 -1
- package/examples/config.example.json +4 -2
- package/package.json +27 -10
- 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 +102 -10
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +15 -12
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +58 -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 +38 -1
- 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 +191 -33
- package/scripts/e2e/demo-runtime.mjs +153 -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 +26 -12
- package/src/assessments/assessmentDiagnostics.ts +172 -0
- package/src/assessments/assessmentStore.ts +288 -51
- package/src/assessments/securitySnapshot.ts +231 -0
- package/src/config.ts +70 -13
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +22 -4
- package/src/control/middleware/static.ts +16 -3
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +14 -6
- package/src/control/routes/evidence.ts +1 -1
- package/src/control/routes/lineage.ts +1 -1
- package/src/control/routes/process.ts +341 -40
- package/src/control/routes/sessions.ts +88 -25
- package/src/control/routes/status.ts +134 -17
- package/src/control/routes/taskActions.ts +24 -58
- package/src/control/routes/tasks.ts +66 -58
- package/src/control/routes/workspace.ts +56 -25
- package/src/control/runtime.ts +71 -43
- package/src/control/server.ts +37 -342
- package/src/control/shared.ts +77 -11
- package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +72 -44
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +213 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +76 -64
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +5 -13
- package/src/goal/goalStatus.ts +19 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +12 -12
- package/src/goal/subgoalSync.ts +11 -8
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +113 -30
- package/src/logging.ts +10 -3
- 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 +291 -57
- package/src/runner/postTaskCleanup.ts +41 -17
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +197 -97
- package/src/runner/simpleProcess.ts +66 -31
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
- package/src/runner/taskStatusStore.ts +69 -0
- package/src/runner/watch.ts +348 -52
- package/src/security/contentRedaction.ts +2 -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 +27 -26
- 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} +7 -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} +136 -39
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
- 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} +27 -17
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
- 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 +36 -31
- package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
- package/src/tools/dispatch/directDispatch.ts +11 -10
- package/src/tools/dispatch/goalDispatch.ts +5 -4
- package/src/tools/dispatch/releaseDispatch.ts +3 -2
- 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} +41 -26
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +17 -1261
- 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 +110 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
- package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
- 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/tasks/taskStates.ts +54 -0
- package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
- 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/workspace/listAgents.ts +75 -0
- 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 +74 -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 +12 -0
- package/ui/pages/dashboard.html +94 -147
- package/ui/pages/direct-sessions.html +123 -26
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +134 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +12 -0
- package/ui/pages/tasks.html +54 -8
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +254 -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/apply-patch.test.d.ts +0 -1
- package/dist/test/unit/apply-patch.test.js +0 -225
- 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/create-task.test.d.ts +0 -1
- package/dist/test/unit/create-task.test.js +0 -197
- 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 -328
- 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 -224
- package/dist/test/unit/get-task-status.test.d.ts +0 -1
- package/dist/test/unit/get-task-status.test.js +0 -174
- package/dist/test/unit/get-task-summary.test.d.ts +0 -1
- package/dist/test/unit/get-task-summary.test.js +0 -146
- 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-report.test.d.ts +0 -1
- package/dist/test/unit/goal-report.test.js +0 -159
- 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 -192
- 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 -133
- 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/spec-kit-import.test.d.ts +0 -1
- package/dist/test/unit/spec-kit-import.test.js +0 -341
- 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/wait-for-task.test.d.ts +0 -1
- package/dist/test/unit/wait-for-task.test.js +0 -144
- 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/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/listAgents.d.ts +0 -12
- package/dist/tools/listAgents.js +0 -42
- package/dist/tools/reconcileTasks.js +0 -352
- 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/execution-plan-2026-07-09.md +0 -315
- 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/docs/roadmap-execution-and-acceptance.md +0 -365
- 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/apply-patch.test.ts +0 -293
- 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/create-task.test.ts +0 -255
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -467
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -251
- package/src/test/unit/get-task-status.test.ts +0 -203
- package/src/test/unit/get-task-summary.test.ts +0 -173
- 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-report.test.ts +0 -189
- 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 -245
- 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 -180
- 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/spec-kit-import.test.ts +0 -429
- 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/wait-for-task.test.ts +0 -176
- 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/listAgents.ts +0 -54
- 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/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +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/{evidencePack.d.ts → tasks/evidencePack.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/{retryTask.d.ts → tasks/retryTask.d.ts} +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/{waitForTask.d.ts → tasks/waitForTask.d.ts} +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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwarden",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Secure local MCP bridge for ChatGPT and local agents, with workspace-scoped task loops, safe summaries, audits, and Direct editing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist/",
|
|
15
|
+
"!dist/test/",
|
|
15
16
|
"docs/",
|
|
17
|
+
"!docs/archive/",
|
|
16
18
|
"!docs/optimization-proposal.md",
|
|
17
19
|
"src/",
|
|
20
|
+
"!src/test/",
|
|
18
21
|
"examples/",
|
|
19
22
|
"scripts/",
|
|
20
23
|
"!scripts/control/kill-patchwarden.ps1",
|
|
@@ -22,18 +25,14 @@
|
|
|
22
25
|
"!.local/",
|
|
23
26
|
"ui/",
|
|
24
27
|
"PatchWarden.cmd",
|
|
25
|
-
"PatchWarden-Control.cmd",
|
|
26
|
-
"PatchWarden-Control-Tray.cmd",
|
|
27
|
-
"PatchWarden-Desktop.cmd",
|
|
28
|
-
"Restart-PatchWarden-Control.cmd",
|
|
29
|
-
"Stop-PatchWarden.cmd",
|
|
30
28
|
"README.md",
|
|
31
29
|
"README.en.md",
|
|
32
30
|
"LICENSE",
|
|
33
31
|
"tsconfig.json"
|
|
34
32
|
],
|
|
35
33
|
"scripts": {
|
|
36
|
-
"build": "node
|
|
34
|
+
"build": "node scripts/build.js",
|
|
35
|
+
"check:build-output": "node scripts/checks/build-output-check.js",
|
|
37
36
|
"start": "node dist/index.js",
|
|
38
37
|
"start:http": "node dist/httpServer.js",
|
|
39
38
|
"start:control": "node dist/controlCenter.js",
|
|
@@ -43,7 +42,7 @@
|
|
|
43
42
|
"doctor": "node dist/doctor.js",
|
|
44
43
|
"doctor:ci": "node dist/doctor.js --allow-default-config",
|
|
45
44
|
"test:unit": "node scripts/checks/unit-tests.js",
|
|
46
|
-
"test": "node dist/smoke-test.js && node scripts/checks/unit-tests.js && node scripts/checks/lifecycle-smoke.js && node scripts/checks/doctor-smoke.js && node scripts/checks/tunnel-supervisor-smoke.js && node scripts/checks/watcher-supervisor-smoke.js && node scripts/checks/control-smoke.js && node scripts/checks/mcp-manifest-check.js && node scripts/checks/brand-check.js && node scripts/checks/control-center-smoke.js",
|
|
45
|
+
"test": "node scripts/checks/build-output-check.js && node dist/smoke-test.js && node scripts/checks/unit-tests.js && node scripts/checks/lifecycle-smoke.js && node scripts/checks/doctor-smoke.js && node scripts/checks/tunnel-supervisor-smoke.js && node scripts/checks/watcher-supervisor-smoke.js && node scripts/checks/control-smoke.js && node scripts/checks/mcp-manifest-check.js && node scripts/checks/brand-check.js && node scripts/checks/control-center-smoke.js",
|
|
47
46
|
"test:lifecycle": "npm run build && node scripts/checks/lifecycle-smoke.js",
|
|
48
47
|
"test:doctor": "npm run build && node scripts/checks/doctor-smoke.js",
|
|
49
48
|
"test:tunnel-supervisor": "npm run build && node scripts/checks/tunnel-supervisor-smoke.js",
|
|
@@ -53,18 +52,36 @@
|
|
|
53
52
|
"check:search-tool-manifest": "npm run build && node scripts/checks/mcp-manifest-check.js --profile chatgpt_search",
|
|
54
53
|
"check:brand": "node scripts/checks/brand-check.js",
|
|
55
54
|
"test:mcp": "npm run build && node scripts/checks/mcp-smoke.js",
|
|
55
|
+
"test:mcp:built": "node scripts/checks/mcp-smoke.js",
|
|
56
56
|
"test:http-mcp": "npm run build && node scripts/checks/http-mcp-smoke.js",
|
|
57
|
+
"test:http-mcp:built": "node scripts/checks/http-mcp-smoke.js",
|
|
57
58
|
"pretest": "npm run build",
|
|
58
59
|
"pack:clean": "npm run build && node scripts/release/pack-clean.js",
|
|
59
|
-
"
|
|
60
|
+
"pack:clean:built": "node scripts/release/pack-clean.js",
|
|
61
|
+
"verify:package": "npm run build && node scripts/checks/package-manifest-check.js",
|
|
62
|
+
"verify:package:built": "node scripts/checks/package-manifest-check.js",
|
|
63
|
+
"desktop:install": "npm.cmd --prefix desktop install --cache ..\\.npm-cache",
|
|
64
|
+
"desktop:test": "npm.cmd --prefix desktop test",
|
|
65
|
+
"desktop:smoke:unpacked": "npm.cmd --prefix desktop run smoke:unpacked --",
|
|
66
|
+
"desktop:stage": "npm run build && npm.cmd --prefix desktop run stage",
|
|
67
|
+
"desktop:dev": "npm run build && npm.cmd --prefix desktop run dev",
|
|
68
|
+
"desktop:package:dir": "npm run build && npm.cmd --prefix desktop run build:dir",
|
|
69
|
+
"desktop:package": "npm run build && npm.cmd --prefix desktop run build",
|
|
70
|
+
"desktop:preflight": "node scripts/release/desktop-preflight.js",
|
|
71
|
+
"desktop:preflight:release": "node scripts/release/desktop-preflight.js --require-clean"
|
|
60
72
|
},
|
|
61
73
|
"dependencies": {
|
|
62
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
63
75
|
},
|
|
64
76
|
"devDependencies": {
|
|
65
77
|
"@types/node": "^26.1.0",
|
|
66
78
|
"typescript": "^6.0.3"
|
|
67
79
|
},
|
|
80
|
+
"overrides": {
|
|
81
|
+
"@hono/node-server": "2.0.11",
|
|
82
|
+
"hono": "4.12.31",
|
|
83
|
+
"fast-uri": "4.1.1"
|
|
84
|
+
},
|
|
68
85
|
"engines": {
|
|
69
86
|
"node": ">=18.0.0"
|
|
70
87
|
},
|
package/scripts/README.md
CHANGED
|
@@ -4,13 +4,18 @@ This directory keeps implementation scripts out of the root folder. Normal
|
|
|
4
4
|
desktop use should start from the root entrypoints:
|
|
5
5
|
|
|
6
6
|
```powershell
|
|
7
|
-
.\PatchWarden-Desktop.cmd
|
|
8
|
-
.\PatchWarden-Control.cmd
|
|
9
|
-
.\PatchWarden-Control-Tray.cmd --foreground
|
|
10
|
-
.\Stop-PatchWarden.cmd
|
|
7
|
+
.\scripts\launchers\PatchWarden-Desktop.cmd
|
|
8
|
+
.\scripts\launchers\PatchWarden-Control.cmd
|
|
9
|
+
.\scripts\launchers\PatchWarden-Control-Tray.cmd --foreground
|
|
10
|
+
.\scripts\launchers\Stop-PatchWarden.cmd
|
|
11
11
|
.\PatchWarden.cmd status all
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
The installable Windows desktop shell is built separately with
|
|
15
|
+
`npm.cmd run desktop:package`. It preserves these source/npm entrypoints and
|
|
16
|
+
uses the same Control Center API rather than adding another process-management
|
|
17
|
+
surface. See `docs/desktop-app.md`.
|
|
18
|
+
|
|
14
19
|
## Control Scripts
|
|
15
20
|
|
|
16
21
|
- `control/manage-patchwarden.ps1`: backing implementation for `PatchWarden.cmd`.
|
|
@@ -34,11 +39,15 @@ desktop use should start from the root entrypoints:
|
|
|
34
39
|
- `checks/mcp-manifest-check.js`: validates MCP manifest expectations.
|
|
35
40
|
- `brand-check.js`: checks public brand strings.
|
|
36
41
|
- `checks/package-manifest-check.js`: verifies package contents.
|
|
42
|
+
- `checks/build-output-check.js`: verifies clean-output confinement and exact recursive unit-test compilation.
|
|
43
|
+
- `release/desktop-preflight.js`: builds an isolated Windows directory package and writes a bounded preflight receipt.
|
|
37
44
|
|
|
38
45
|
## Release Helpers
|
|
39
46
|
|
|
40
|
-
- `release/pack-clean.js`: rebuilds
|
|
41
|
-
|
|
47
|
+
- `release/pack-clean.js`: rebuilds the isolated `release/package/` staging
|
|
48
|
+
directory, `patchwarden-release.tar.gz`, and the versioned
|
|
49
|
+
`PatchWarden-v*.zip` artifact. Desktop and preflight siblings under
|
|
50
|
+
`release/` are preserved.
|
|
42
51
|
|
|
43
52
|
## Compatibility Launchers
|
|
44
53
|
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { cleanGeneratedOutput } from "./lib/clean-generated-output.js";
|
|
5
|
+
|
|
6
|
+
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
7
|
+
const dist = resolve(root, "dist");
|
|
8
|
+
|
|
9
|
+
cleanGeneratedOutput(root, dist);
|
|
10
|
+
process.chdir(root);
|
|
11
|
+
await import("../node_modules/typescript/lib/tsc.js");
|
|
@@ -12,6 +12,9 @@ const allowedLegacyFiles = new Set([
|
|
|
12
12
|
"docs/release-v0.3.0.md",
|
|
13
13
|
"docs/release-v0.4.0.md",
|
|
14
14
|
"docs/release-v0.6.0.md",
|
|
15
|
+
"docs/archive/releases/release-v0.6.0.md",
|
|
16
|
+
"docs/archive/releases/release-v0.6.1.md",
|
|
17
|
+
"docs/archive/releases/release-v0.6.4.md",
|
|
15
18
|
"scripts/checks/brand-check.js",
|
|
16
19
|
"scripts/release/pack-clean.js",
|
|
17
20
|
]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { relative, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { cleanGeneratedOutput } from "../lib/clean-generated-output.js";
|
|
8
|
+
import { collectMatchingFiles } from "../lib/file-discovery.js";
|
|
9
|
+
|
|
10
|
+
const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
11
|
+
const sourceRoot = resolve(root, "src", "test", "unit");
|
|
12
|
+
const compiledRoot = resolve(root, "dist", "test", "unit");
|
|
13
|
+
|
|
14
|
+
const sourceTests = collectMatchingFiles(sourceRoot, (name) => name.endsWith(".test.ts"))
|
|
15
|
+
.map((path) => relative(sourceRoot, path).replace(/\\/g, "/").replace(/\.ts$/, ".js"));
|
|
16
|
+
const compiledTests = collectMatchingFiles(compiledRoot, (name) => name.endsWith(".test.js"))
|
|
17
|
+
.map((path) => relative(compiledRoot, path).replace(/\\/g, "/"));
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(compiledTests, sourceTests, "compiled unit tests must exactly match source test paths");
|
|
20
|
+
assert.ok(compiledTests.some((path) => path.includes("/")), "unit discovery must include nested tests");
|
|
21
|
+
|
|
22
|
+
const fixtureRoot = mkdtempSync(resolve(tmpdir(), "patchwarden-clean-build-"));
|
|
23
|
+
try {
|
|
24
|
+
const fixtureDist = resolve(fixtureRoot, "dist");
|
|
25
|
+
const outside = resolve(fixtureRoot, "keep.txt");
|
|
26
|
+
mkdirSync(fixtureDist);
|
|
27
|
+
writeFileSync(resolve(fixtureDist, "stale.js"), "stale\n", "utf8");
|
|
28
|
+
writeFileSync(outside, "keep\n", "utf8");
|
|
29
|
+
cleanGeneratedOutput(fixtureRoot, fixtureDist);
|
|
30
|
+
assert.equal(existsSync(fixtureDist), false, "clean build must remove stale dist contents");
|
|
31
|
+
assert.equal(existsSync(outside), true, "clean build must preserve files outside dist");
|
|
32
|
+
assert.throws(() => cleanGeneratedOutput(fixtureRoot, resolve(fixtureRoot, "release")), /Refusing/);
|
|
33
|
+
} finally {
|
|
34
|
+
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`[build-output-check] OK: ${sourceTests.length} source tests match ${compiledTests.length} compiled tests.`);
|
|
@@ -121,9 +121,9 @@ process.on("SIGTERM", () => {
|
|
|
121
121
|
process.exit(143);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
function httpGet(url) {
|
|
124
|
+
function httpGet(url, headers = {}) {
|
|
125
125
|
return new Promise((resolve, reject) => {
|
|
126
|
-
const req = http.get(url, (res) => {
|
|
126
|
+
const req = http.get(url, { headers }, (res) => {
|
|
127
127
|
let data = "";
|
|
128
128
|
res.on("data", (chunk) => (data += chunk));
|
|
129
129
|
res.on("end", () => resolve({ status: res.statusCode, headers: res.headers, body: data }));
|
|
@@ -133,6 +133,34 @@ function httpGet(url) {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
async function testLoopbackHostBoundary() {
|
|
137
|
+
const name = "Control Center rejects DNS-rebinding Host headers and cannot be framed";
|
|
138
|
+
try {
|
|
139
|
+
const hostileToken = await httpGet(`${BASE_URL}/control-token.json`, {
|
|
140
|
+
Host: "patchwarden.attacker.invalid",
|
|
141
|
+
});
|
|
142
|
+
const hostilePage = await httpGet(`${BASE_URL}/`, {
|
|
143
|
+
Host: "127.0.0.1.attacker.invalid",
|
|
144
|
+
});
|
|
145
|
+
const localPage = await httpGet(`${BASE_URL}/`);
|
|
146
|
+
const problems = [];
|
|
147
|
+
if (hostileToken.status !== 421 || hostileToken.body.includes("token")) {
|
|
148
|
+
problems.push(`hostile token request returned ${hostileToken.status}: ${hostileToken.body.slice(0, 120)}`);
|
|
149
|
+
}
|
|
150
|
+
if (hostilePage.status !== 421) problems.push(`hostile page request returned ${hostilePage.status}`);
|
|
151
|
+
if (localPage.status !== 200) problems.push(`loopback page returned ${localPage.status}`);
|
|
152
|
+
if (String(localPage.headers["x-frame-options"] || "").toUpperCase() !== "DENY") {
|
|
153
|
+
problems.push("X-Frame-Options is not DENY");
|
|
154
|
+
}
|
|
155
|
+
if (!String(localPage.headers["content-security-policy"] || "").includes("frame-ancestors 'none'")) {
|
|
156
|
+
problems.push("CSP does not deny frame ancestors");
|
|
157
|
+
}
|
|
158
|
+
record(name, problems.length === 0, problems.join("; "));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
record(name, false, `error: ${err.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
136
164
|
function httpPost(url, headers = {}) {
|
|
137
165
|
return new Promise((resolve, reject) => {
|
|
138
166
|
const parsed = new URL(url);
|
|
@@ -221,6 +249,13 @@ async function testStaticFiles() {
|
|
|
221
249
|
const root = await httpGet(`${BASE_URL}/`);
|
|
222
250
|
const vendor = await httpGet(`${BASE_URL}/vendor/tailwindcss-browser.js`);
|
|
223
251
|
const css = await httpGet(`${BASE_URL}/colors_and_type.css`);
|
|
252
|
+
const desktopCss = await httpGet(`${BASE_URL}/desktop.css`);
|
|
253
|
+
const desktopBridge = await httpGet(`${BASE_URL}/desktop-bridge.js`);
|
|
254
|
+
const logParser = await httpGet(`${BASE_URL}/log-parser.js`);
|
|
255
|
+
const i18n = await httpGet(`${BASE_URL}/i18n.js`);
|
|
256
|
+
const gettingStarted = await httpGet(`${BASE_URL}/pages/getting-started.html`);
|
|
257
|
+
const gettingStartedJs = await httpGet(`${BASE_URL}/getting-started.js`);
|
|
258
|
+
const settings = await httpGet(`${BASE_URL}/pages/settings.html`);
|
|
224
259
|
const favicon = await httpGet(`${BASE_URL}/favicon.ico`);
|
|
225
260
|
|
|
226
261
|
const checks = [];
|
|
@@ -228,7 +263,8 @@ async function testStaticFiles() {
|
|
|
228
263
|
const rootCt = root.headers["content-type"] || "";
|
|
229
264
|
if (!rootCt.includes("text/html")) checks.push(`GET / -> Content-Type ${rootCt} (expected text/html)`);
|
|
230
265
|
if (!root.body.includes("setup-checklist-card")) checks.push("dashboard missing setup checklist card");
|
|
231
|
-
if (!root.body.includes("
|
|
266
|
+
if (!root.body.includes("查看 Core / Direct 本次日志尾部")) checks.push("dashboard activity log is not collapsed behind a summary");
|
|
267
|
+
if (!root.body.includes("pw-dashboard-toolbar")) checks.push("dashboard missing stable desktop toolbar layout");
|
|
232
268
|
if (!root.body.includes("evidence-pack-card")) checks.push("dashboard missing v1.5 evidence pack card");
|
|
233
269
|
|
|
234
270
|
if (vendor.status !== 200) checks.push(`GET /vendor/tailwindcss-browser.js -> status ${vendor.status}`);
|
|
@@ -239,6 +275,34 @@ async function testStaticFiles() {
|
|
|
239
275
|
const cssCt = css.headers["content-type"] || "";
|
|
240
276
|
if (!cssCt.includes("text/css")) checks.push(`GET /colors_and_type.css -> Content-Type ${cssCt}`);
|
|
241
277
|
|
|
278
|
+
if (desktopCss.status !== 200 || !(desktopCss.headers["content-type"] || "").includes("text/css")) {
|
|
279
|
+
checks.push("desktop refinement stylesheet is not served as CSS");
|
|
280
|
+
}
|
|
281
|
+
if (desktopBridge.status !== 200 || !(desktopBridge.headers["content-type"] || "").includes("javascript")) {
|
|
282
|
+
checks.push("desktop bridge is not served as JavaScript");
|
|
283
|
+
}
|
|
284
|
+
if (logParser.status !== 200 || !(logParser.headers["content-type"] || "").includes("javascript") || !logParser.body.includes("PatchWardenLogParser")) {
|
|
285
|
+
checks.push("log parser is not served as JavaScript");
|
|
286
|
+
}
|
|
287
|
+
if (i18n.status !== 200 || !i18n.body.includes("applyTranslations") || !i18n.body.includes('"zh-CN"') || !i18n.body.includes("en:")) {
|
|
288
|
+
checks.push("shared Chinese/English translation dictionary is unavailable");
|
|
289
|
+
}
|
|
290
|
+
if (gettingStarted.status !== 200 || (gettingStarted.body.match(/class="readiness-item"/g) || []).length !== 4 || !gettingStarted.body.includes("home.manual")) {
|
|
291
|
+
checks.push("desktop getting-started page is missing its four readiness checks or manual ChatGPT state");
|
|
292
|
+
}
|
|
293
|
+
if (gettingStartedJs.status !== 200 || !gettingStartedJs.body.includes('fetchJson("/api/status")') || !gettingStartedJs.body.includes("health_check")) {
|
|
294
|
+
checks.push("getting-started controller is missing bounded Tunnel status or health_check guidance");
|
|
295
|
+
}
|
|
296
|
+
if (settings.status !== 200 || !settings.body.includes("/settings.js")) {
|
|
297
|
+
checks.push("desktop settings page is unavailable or missing its bounded bridge");
|
|
298
|
+
}
|
|
299
|
+
for (const marker of ["detectTunnel", "chooseTunnel", "enableDirectProfile", "proxyScope", "saveRuntime", "tunnelId", "runtimeKey", "provisionTunnel", "forgetCredential", "language"]) {
|
|
300
|
+
if (!settings.body.includes(marker)) checks.push(`desktop settings missing runtime control: ${marker}`);
|
|
301
|
+
}
|
|
302
|
+
if (!desktopBridge.body.includes("patchwardenApplyTheme") || !desktopBridge.body.includes("请求超过 10 秒")) {
|
|
303
|
+
checks.push("desktop bridge is missing renderer theme propagation or bounded loading timeout");
|
|
304
|
+
}
|
|
305
|
+
|
|
242
306
|
if (favicon.status !== 200) checks.push(`GET /favicon.ico -> status ${favicon.status}`);
|
|
243
307
|
const faviconCt = favicon.headers["content-type"] || "";
|
|
244
308
|
if (!faviconCt.includes("image/svg+xml")) checks.push(`GET /favicon.ico -> Content-Type ${faviconCt}`);
|
|
@@ -268,6 +332,9 @@ async function testPageNavigationRoutes() {
|
|
|
268
332
|
"/pages/workspace.html",
|
|
269
333
|
"/pages/audit.html",
|
|
270
334
|
"/pages/task-detail.html?id=smoke",
|
|
335
|
+
"/settings.html",
|
|
336
|
+
"/pages/settings.html",
|
|
337
|
+
"/pages/getting-started.html",
|
|
271
338
|
];
|
|
272
339
|
|
|
273
340
|
for (const route of routes) {
|
|
@@ -411,6 +478,13 @@ async function testTasksJson() {
|
|
|
411
478
|
if (!Array.isArray(json.tasks)) problems.push("'tasks' is not an array");
|
|
412
479
|
if (typeof json.total !== "number") problems.push("'total' is not a number");
|
|
413
480
|
if (typeof json.returned !== "number") problems.push("'returned' is not a number");
|
|
481
|
+
if (!(json.nextCursor === null || typeof json.nextCursor === "string")) problems.push("'nextCursor' is neither null nor string");
|
|
482
|
+
if (!isObject(json.filters) || !isObject(json.filters.applied) || !isObject(json.filters.options)) problems.push("'filters' contract is missing");
|
|
483
|
+
|
|
484
|
+
const traversal = await httpGet(`${BASE_URL}/api/tasks/..%2Foutside`);
|
|
485
|
+
if (traversal.status !== 400) {
|
|
486
|
+
problems.push(`encoded task traversal returned ${traversal.status} (expected 400)`);
|
|
487
|
+
}
|
|
414
488
|
|
|
415
489
|
if (problems.length === 0) {
|
|
416
490
|
record(name, true);
|
|
@@ -823,7 +897,7 @@ async function testStaleTasksApi() {
|
|
|
823
897
|
|
|
824
898
|
// ── Test 12: GET /api/workspace/<traversal>/status -> 400 ───────
|
|
825
899
|
async function testWorkspaceRepoPathTraversal() {
|
|
826
|
-
const name = "Test 12:
|
|
900
|
+
const name = "Test 12: workspace status rejects traversal without rejecting dotted names";
|
|
827
901
|
// Use percent-encoded dots to bypass URL-parser segment normalization;
|
|
828
902
|
// after decode the repoParam is "../secret" which contains ".." and is
|
|
829
903
|
// rejected by the server's traversal guard.
|
|
@@ -833,7 +907,12 @@ async function testWorkspaceRepoPathTraversal() {
|
|
|
833
907
|
if (res.status === 400) {
|
|
834
908
|
const json = tryJson(res.body);
|
|
835
909
|
if (json && typeof json.error === "string" && /traversal/i.test(json.error)) {
|
|
836
|
-
|
|
910
|
+
const dotted = await httpGet(`${BASE_URL}/api/workspace/release..candidate/status`);
|
|
911
|
+
if (dotted.status === 404) {
|
|
912
|
+
record(name, true);
|
|
913
|
+
} else {
|
|
914
|
+
record(name, false, `valid dotted repo name returned ${dotted.status} (expected 404 for missing directory)`);
|
|
915
|
+
}
|
|
837
916
|
} else {
|
|
838
917
|
record(name, false, `status 400 but error field missing/invalid: ${res.body.slice(0, 200)}`);
|
|
839
918
|
}
|
|
@@ -865,9 +944,9 @@ async function testReconcileNoToken() {
|
|
|
865
944
|
}
|
|
866
945
|
}
|
|
867
946
|
|
|
868
|
-
// ── Test 14: GET /api/direct-sessions returns
|
|
947
|
+
// ── Test 14: GET /api/direct-sessions returns a bounded list (not 500)
|
|
869
948
|
async function testDirectSessionsEmptyList() {
|
|
870
|
-
const name = "Test 14: /api/direct-sessions returns
|
|
949
|
+
const name = "Test 14: /api/direct-sessions returns a bounded list (not 500)";
|
|
871
950
|
try {
|
|
872
951
|
const res = await httpGet(`${BASE_URL}/api/direct-sessions`);
|
|
873
952
|
if (res.status !== 200) {
|
|
@@ -882,10 +961,20 @@ async function testDirectSessionsEmptyList() {
|
|
|
882
961
|
const problems = [];
|
|
883
962
|
if (!Array.isArray(json.sessions)) problems.push("'sessions' is not an array");
|
|
884
963
|
if (typeof json.total !== "number") problems.push("'total' is not a number");
|
|
964
|
+
if (!(json.nextCursor === null || typeof json.nextCursor === "string")) problems.push("'nextCursor' is neither null nor string");
|
|
965
|
+
if (!isObject(json.filters) || !isObject(json.filters.options)) problems.push("'filters' contract is missing");
|
|
966
|
+
if (typeof json.direct_profile_enabled !== "boolean") problems.push("'direct_profile_enabled' is not a boolean");
|
|
885
967
|
if (json.reason !== null && typeof json.reason !== "string") problems.push("'reason' is neither null nor string");
|
|
886
|
-
if (Array.isArray(json.sessions) && json.sessions.length
|
|
887
|
-
problems.push(`sessions.length (${json.sessions.length})
|
|
968
|
+
if (Array.isArray(json.sessions) && json.sessions.length > json.total) {
|
|
969
|
+
problems.push(`sessions.length (${json.sessions.length}) exceeds total (${json.total})`);
|
|
970
|
+
}
|
|
971
|
+
if (Array.isArray(json.sessions) && json.sessions.length < json.total && typeof json.nextCursor !== "string") {
|
|
972
|
+
problems.push("paginated direct sessions response is missing nextCursor");
|
|
888
973
|
}
|
|
974
|
+
const invalidDetail = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id`);
|
|
975
|
+
const invalidSummary = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id/summary`);
|
|
976
|
+
if (invalidDetail.status !== 400) problems.push(`invalid detail id returned ${invalidDetail.status} (expected 400)`);
|
|
977
|
+
if (invalidSummary.status !== 400) problems.push(`invalid summary id returned ${invalidSummary.status} (expected 400)`);
|
|
889
978
|
if (problems.length === 0) {
|
|
890
979
|
record(name, true);
|
|
891
980
|
} else {
|
|
@@ -915,6 +1004,8 @@ async function testLogsTailParam() {
|
|
|
915
1004
|
if (typeof json.stderr !== "string") problems.push("'stderr' is not a string");
|
|
916
1005
|
if (json.tail !== 300) problems.push(`'tail' is ${json.tail} (expected 300)`);
|
|
917
1006
|
if (json.category !== "core") problems.push(`'category' is ${json.category} (expected "core")`);
|
|
1007
|
+
if (json.historical_snapshot !== true) problems.push("'historical_snapshot' is not true");
|
|
1008
|
+
if (json.redacted !== true) problems.push("'redacted' is not true");
|
|
918
1009
|
if (problems.length === 0) {
|
|
919
1010
|
record(name, true);
|
|
920
1011
|
} else {
|
|
@@ -1440,7 +1531,7 @@ async function main() {
|
|
|
1440
1531
|
process.exit(1);
|
|
1441
1532
|
}
|
|
1442
1533
|
|
|
1443
|
-
child = spawn(
|
|
1534
|
+
child = spawn(process.execPath, [serverPath], {
|
|
1444
1535
|
cwd: projectRoot,
|
|
1445
1536
|
env: {
|
|
1446
1537
|
...process.env,
|
|
@@ -1480,6 +1571,7 @@ async function main() {
|
|
|
1480
1571
|
console.log(`[control-center-smoke] server ready at ${BASE_URL}`);
|
|
1481
1572
|
|
|
1482
1573
|
await testStaticFiles();
|
|
1574
|
+
await testLoopbackHostBoundary();
|
|
1483
1575
|
await testPageNavigationRoutes();
|
|
1484
1576
|
await testStatusJson();
|
|
1485
1577
|
await testTasksJson();
|
|
@@ -139,12 +139,13 @@ try {
|
|
|
139
139
|
|
|
140
140
|
const expectedFiles = [
|
|
141
141
|
"PatchWarden.cmd",
|
|
142
|
-
"PatchWarden-Control.cmd",
|
|
143
|
-
"PatchWarden-Control-Tray.cmd",
|
|
144
|
-
"PatchWarden-Desktop.cmd",
|
|
145
|
-
"Restart-PatchWarden-Control.cmd",
|
|
146
|
-
"Stop-PatchWarden.cmd",
|
|
142
|
+
"scripts/launchers/PatchWarden-Control.cmd",
|
|
143
|
+
"scripts/launchers/PatchWarden-Control-Tray.cmd",
|
|
144
|
+
"scripts/launchers/PatchWarden-Desktop.cmd",
|
|
145
|
+
"scripts/launchers/Restart-PatchWarden-Control.cmd",
|
|
146
|
+
"scripts/launchers/Stop-PatchWarden.cmd",
|
|
147
147
|
"scripts/control/manage-patchwarden.ps1",
|
|
148
|
+
"scripts/control/run-background-supervisor.ps1",
|
|
148
149
|
"scripts/control/stop-patchwarden.ps1",
|
|
149
150
|
"scripts/launchers/Start-PatchWarden-Tunnel.cmd",
|
|
150
151
|
"scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
|
|
@@ -158,14 +159,24 @@ try {
|
|
|
158
159
|
if (!rootEntry.includes("manage-patchwarden.ps1")) {
|
|
159
160
|
throw new Error("PatchWarden.cmd does not invoke the consolidated manager");
|
|
160
161
|
}
|
|
161
|
-
const stopEntry = readFileSync(join(root, "Stop-PatchWarden.cmd"), "utf8");
|
|
162
|
-
if (!stopEntry.includes("
|
|
162
|
+
const stopEntry = readFileSync(join(root, "scripts", "launchers", "Stop-PatchWarden.cmd"), "utf8");
|
|
163
|
+
if (!stopEntry.includes("control\\stop-patchwarden.ps1")) {
|
|
163
164
|
throw new Error("Stop-PatchWarden.cmd does not invoke the one-click shutdown script");
|
|
164
165
|
}
|
|
165
|
-
const desktopEntry = readFileSync(join(root, "PatchWarden-Desktop.cmd"), "utf8");
|
|
166
|
+
const desktopEntry = readFileSync(join(root, "scripts", "launchers", "PatchWarden-Desktop.cmd"), "utf8");
|
|
166
167
|
if (!desktopEntry.includes("control-center-tray.ps1") || !desktopEntry.includes("WindowStyle Hidden")) {
|
|
167
168
|
throw new Error("PatchWarden-Desktop.cmd must launch the tray hidden as the daily desktop entry");
|
|
168
169
|
}
|
|
170
|
+
const managerSource = readFileSync(manager, "utf8");
|
|
171
|
+
const wrapperSource = readFileSync(join(root, "scripts", "control", "run-background-supervisor.ps1"), "utf8");
|
|
172
|
+
for (const expected of ["supervisor-status.json", "supervisor.stdout.log", "supervisor.stderr.log", "run-background-supervisor.ps1", "Write-BackgroundStartingState"]) {
|
|
173
|
+
if (!managerSource.includes(expected)) {
|
|
174
|
+
throw new Error(`background manager is missing supervisor observability: ${expected}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!wrapperSource.includes("1> $stdout 2> $stderr") || wrapperSource.includes("CONTROL_PLANE_API_KEY")) {
|
|
178
|
+
throw new Error("background wrapper must redirect supervisor logs without credential arguments");
|
|
179
|
+
}
|
|
169
180
|
console.log("ok - control handles orphan cleanup, scoped kill, port conflicts, health fallback, and Core/Direct lifecycle actions");
|
|
170
181
|
} finally {
|
|
171
182
|
if (fakeTunnel?.exitCode === null) fakeTunnel.kill();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import http from "node:http";
|
|
3
4
|
import {
|
|
4
5
|
existsSync,
|
|
5
6
|
mkdirSync,
|
|
6
7
|
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
7
9
|
rmSync,
|
|
8
10
|
writeFileSync,
|
|
9
11
|
} from "node:fs";
|
|
@@ -11,7 +13,7 @@ import { tmpdir } from "node:os";
|
|
|
11
13
|
import { dirname, join, resolve } from "node:path";
|
|
12
14
|
import { fileURLToPath } from "node:url";
|
|
13
15
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
|
-
import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
|
|
16
|
+
import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/catalog/toolCatalog.js";
|
|
15
17
|
|
|
16
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
19
|
const root = resolve(__dirname, "..", "..");
|
|
@@ -96,6 +98,25 @@ function toolJson(result) {
|
|
|
96
98
|
return JSON.parse(toolText(result));
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
function requestWithHost(path, hostHeader) {
|
|
102
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
103
|
+
const request = http.request({
|
|
104
|
+
hostname: host,
|
|
105
|
+
port,
|
|
106
|
+
path,
|
|
107
|
+
method: "GET",
|
|
108
|
+
headers: { Host: hostHeader },
|
|
109
|
+
}, (response) => {
|
|
110
|
+
let body = "";
|
|
111
|
+
response.setEncoding("utf-8");
|
|
112
|
+
response.on("data", (chunk) => { body += chunk; });
|
|
113
|
+
response.on("end", () => resolvePromise({ status: response.statusCode, body }));
|
|
114
|
+
});
|
|
115
|
+
request.on("error", rejectPromise);
|
|
116
|
+
request.end();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
console.log("\n=== PatchWarden HTTP MCP Smoke Tests ===\n");
|
|
100
121
|
|
|
101
122
|
try {
|
|
@@ -126,7 +147,7 @@ try {
|
|
|
126
147
|
);
|
|
127
148
|
|
|
128
149
|
console.log(`Starting HTTP MCP server on ${mcpUrl}...`);
|
|
129
|
-
serverProcess = spawn(
|
|
150
|
+
serverProcess = spawn(process.execPath, [serverPath], {
|
|
130
151
|
cwd: root,
|
|
131
152
|
env: {
|
|
132
153
|
...process.env,
|
|
@@ -163,6 +184,13 @@ try {
|
|
|
163
184
|
}
|
|
164
185
|
});
|
|
165
186
|
|
|
187
|
+
await test("HTTP MCP rejects DNS-rebinding Host headers", async () => {
|
|
188
|
+
const response = await requestWithHost("/healthz", "patchwarden.attacker.invalid");
|
|
189
|
+
if (response.status !== 421 || !response.body.includes("Untrusted Host")) {
|
|
190
|
+
throw new Error(`Unexpected hostile Host response: ${JSON.stringify(response)}`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
166
194
|
await test("wrong endpoint returns structured 404 guidance", async () => {
|
|
167
195
|
const response = await fetch(`http://${host}:${port}/wrong-path`);
|
|
168
196
|
const data = await response.json();
|
|
@@ -262,7 +290,7 @@ try {
|
|
|
262
290
|
|
|
263
291
|
// Start server WITH owner token
|
|
264
292
|
console.log("\n Starting server with owner token...");
|
|
265
|
-
serverProcess = spawn(
|
|
293
|
+
serverProcess = spawn(process.execPath, [serverPath], {
|
|
266
294
|
cwd: root,
|
|
267
295
|
env: {
|
|
268
296
|
...process.env,
|
|
@@ -278,7 +306,7 @@ try {
|
|
|
278
306
|
// Wait for server to be ready
|
|
279
307
|
for (let i = 0; i < 10; i++) {
|
|
280
308
|
try {
|
|
281
|
-
const r = await fetch(
|
|
309
|
+
const r = await fetch(`http://${host}:${port}/healthz`);
|
|
282
310
|
if (r.status === 200) break;
|
|
283
311
|
} catch {}
|
|
284
312
|
await sleep(500);
|
|
@@ -328,6 +356,56 @@ try {
|
|
|
328
356
|
}
|
|
329
357
|
});
|
|
330
358
|
|
|
359
|
+
const acceptanceTaskId = "task_http_acceptance";
|
|
360
|
+
const acceptanceTaskDir = join(workspaceRoot, ".patchwarden", "tasks", acceptanceTaskId);
|
|
361
|
+
mkdirSync(acceptanceTaskDir, { recursive: true });
|
|
362
|
+
writeFileSync(join(acceptanceTaskDir, "status.json"), JSON.stringify({
|
|
363
|
+
task_id: acceptanceTaskId,
|
|
364
|
+
status: "done_by_agent",
|
|
365
|
+
acceptance_status: "pending",
|
|
366
|
+
}), "utf-8");
|
|
367
|
+
|
|
368
|
+
await test("admin acceptance requires the owner token", async () => {
|
|
369
|
+
const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
headers: { "content-type": "application/json" },
|
|
372
|
+
body: JSON.stringify({ notes: "reviewed" }),
|
|
373
|
+
});
|
|
374
|
+
if (response.status !== 401) throw new Error(`Expected 401, got ${response.status}`);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await test("admin acceptance writes bounded atomic evidence", async () => {
|
|
378
|
+
const secret = `ghp_${"a".repeat(24)}`;
|
|
379
|
+
const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/accept`, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
headers: {
|
|
382
|
+
"content-type": "application/json",
|
|
383
|
+
Authorization: `Bearer ${OWNER_TOKEN}`,
|
|
384
|
+
},
|
|
385
|
+
body: JSON.stringify({ notes: `reviewed ${secret}` }),
|
|
386
|
+
});
|
|
387
|
+
if (response.status !== 200) throw new Error(`Expected 200, got ${response.status}: ${await response.text()}`);
|
|
388
|
+
const acceptance = JSON.parse(readFileSync(join(acceptanceTaskDir, "acceptance.json"), "utf-8"));
|
|
389
|
+
const status = JSON.parse(readFileSync(join(acceptanceTaskDir, "status.json"), "utf-8"));
|
|
390
|
+
if (acceptance.status !== "accepted" || status.acceptance_status !== "accepted") {
|
|
391
|
+
throw new Error("Acceptance state was not persisted consistently");
|
|
392
|
+
}
|
|
393
|
+
if (JSON.stringify(acceptance).includes(secret)) throw new Error("Acceptance notes persisted a token");
|
|
394
|
+
if (existsSync(join(acceptanceTaskDir, "status.json.lock"))) throw new Error("Status lock was not released");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await test("admin acceptance rejects oversized request bodies", async () => {
|
|
398
|
+
const response = await fetch(`http://${host}:${port}/admin/tasks/${acceptanceTaskId}/reject`, {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers: {
|
|
401
|
+
"content-type": "application/json",
|
|
402
|
+
Authorization: `Bearer ${OWNER_TOKEN}`,
|
|
403
|
+
},
|
|
404
|
+
body: JSON.stringify({ reason: "x".repeat(70 * 1024) }),
|
|
405
|
+
});
|
|
406
|
+
if (response.status !== 413) throw new Error(`Expected 413, got ${response.status}`);
|
|
407
|
+
});
|
|
408
|
+
|
|
331
409
|
} catch (error) {
|
|
332
410
|
fail("HTTP MCP smoke setup", error);
|
|
333
411
|
} finally {
|
|
@@ -152,16 +152,16 @@ try {
|
|
|
152
152
|
);
|
|
153
153
|
process.env.PATCHWARDEN_CONFIG = configPath;
|
|
154
154
|
|
|
155
|
-
const { savePlan } = await import("../../dist/tools/savePlan.js");
|
|
156
|
-
const { createTask } = await import("../../dist/tools/createTask.js");
|
|
157
|
-
const { getTaskStatus } = await import("../../dist/tools/getTaskStatus.js");
|
|
158
|
-
const { cancelTask } = await import("../../dist/tools/cancelTask.js");
|
|
159
|
-
const { killTask } = await import("../../dist/tools/killTask.js");
|
|
160
|
-
const { getDiff } = await import("../../dist/tools/taskOutputs.js");
|
|
161
|
-
const { listAgents } = await import("../../dist/tools/listAgents.js");
|
|
162
|
-
const { getTaskSummary } = await import("../../dist/tools/getTaskSummary.js");
|
|
163
|
-
const { auditTask } = await import("../../dist/tools/auditTask.js");
|
|
164
|
-
const { waitForTask } = await import("../../dist/tools/waitForTask.js");
|
|
155
|
+
const { savePlan } = await import("../../dist/tools/goals/savePlan.js");
|
|
156
|
+
const { createTask } = await import("../../dist/tools/tasks/createTask.js");
|
|
157
|
+
const { getTaskStatus } = await import("../../dist/tools/tasks/getTaskStatus.js");
|
|
158
|
+
const { cancelTask } = await import("../../dist/tools/tasks/cancelTask.js");
|
|
159
|
+
const { killTask } = await import("../../dist/tools/tasks/killTask.js");
|
|
160
|
+
const { getDiff } = await import("../../dist/tools/tasks/taskOutputs.js");
|
|
161
|
+
const { listAgents } = await import("../../dist/tools/workspace/listAgents.js");
|
|
162
|
+
const { getTaskSummary } = await import("../../dist/tools/tasks/getTaskSummary.js");
|
|
163
|
+
const { auditTask } = await import("../../dist/tools/diagnostics/auditTask.js");
|
|
164
|
+
const { waitForTask } = await import("../../dist/tools/tasks/waitForTask.js");
|
|
165
165
|
const { runTask } = await import("../../dist/runner/runTask.js");
|
|
166
166
|
const { reloadConfig } = await import("../../dist/config.js");
|
|
167
167
|
|
|
@@ -170,6 +170,9 @@ try {
|
|
|
170
170
|
if (result.total !== 8 || result.agents.some((agent) => !agent.available)) {
|
|
171
171
|
throw new Error(`Unexpected agent availability: ${JSON.stringify(result)}`);
|
|
172
172
|
}
|
|
173
|
+
if (result.agents.some((agent) => agent.availability_scope !== "executable_only" || agent.provider_status !== "not_checked")) {
|
|
174
|
+
throw new Error(`Agent readiness scope was not explicit: ${JSON.stringify(result)}`);
|
|
175
|
+
}
|
|
173
176
|
});
|
|
174
177
|
|
|
175
178
|
await test("git diff captures tracked and untracked task changes", async () => {
|
|
@@ -803,12 +806,12 @@ try {
|
|
|
803
806
|
const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 1 });
|
|
804
807
|
const started = Date.now();
|
|
805
808
|
const result = await runTask(task.task_id);
|
|
806
|
-
if (result.status !== "
|
|
809
|
+
if (result.status !== "timeout" || !result.error?.includes("timed out")) {
|
|
807
810
|
throw new Error(`Expected timeout failure, got ${JSON.stringify(result)}`);
|
|
808
811
|
}
|
|
809
812
|
if (Date.now() - started > 10000) throw new Error("Timeout took too long to stop the process");
|
|
810
813
|
const status = getTaskStatus(task.task_id);
|
|
811
|
-
if (status.phase !== "
|
|
814
|
+
if (status.phase !== "timeout" || status.termination_reason !== "timeout" || !status.last_heartbeat_at) {
|
|
812
815
|
throw new Error(`Missing timeout phase/heartbeat: ${JSON.stringify(status)}`);
|
|
813
816
|
}
|
|
814
817
|
if (!existsSync(join(task.path, "progress.md"))) throw new Error("progress.md missing");
|