patchwarden 1.5.1 → 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 +152 -25
- package/README.md +120 -23
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentStore.js +42 -34
- 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 +46 -1
- package/dist/control/routes/status.js +72 -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 +63 -30
- package/dist/control/server.js +36 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- 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 +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +62 -52
- 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 +1 -1
- package/dist/runner/runTask.js +115 -87
- 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} +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 +256 -48
- 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 +24 -23
- 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} +1 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
- 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 +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
- package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
- 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.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} +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/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} +58 -30
- 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 +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 +17 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +39 -29
- 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 +91 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +10 -10
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +27 -3
- 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/{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/assessmentStore.ts +52 -40
- 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 +97 -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 +69 -33
- package/src/control/server.ts +36 -342
- package/src/control/shared.ts +77 -11
- 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 +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +73 -59
- 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 +138 -91
- package/src/runner/simpleProcess.ts +66 -31
- 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 +281 -51
- 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 +25 -24
- 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} +99 -32
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
- 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 +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- 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} +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} +69 -34
- 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 +12 -0
- package/ui/pages/dashboard.html +90 -147
- package/ui/pages/direct-sessions.html +123 -26
- 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 +12 -0
- package/ui/pages/tasks.html +54 -8
- 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/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/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/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/{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
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve, relative, isAbsolute } from "node:path";
|
|
1
|
+
import { resolve, relative, isAbsolute, sep } from "node:path";
|
|
3
2
|
import { buildAgentInvocation, buildAssessmentPrompt } from "../runner/agentInvocation.js";
|
|
4
3
|
import { runSimpleProcessSync } from "../runner/simpleProcess.js";
|
|
5
4
|
import { captureRepoSnapshot, compareSnapshots } from "../runner/changeCapture.js";
|
|
5
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
6
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
6
7
|
const ASSESSMENT_MARKER = "===ASSESSMENT_JSON===";
|
|
7
8
|
export async function runAgentAssessment(input) {
|
|
8
9
|
const logPaths = {
|
|
@@ -26,9 +27,11 @@ export async function runAgentAssessment(input) {
|
|
|
26
27
|
log_paths: logPaths,
|
|
27
28
|
};
|
|
28
29
|
// ── 1. Build prompt and write to file for {prompt_file} support ──
|
|
29
|
-
const
|
|
30
|
+
const safeGoal = redactSensitiveContent(input.goal).content.slice(0, 10_000);
|
|
31
|
+
const safePlan = redactSensitiveContent(input.planContent).content.slice(0, 1024 * 1024);
|
|
32
|
+
const assessmentPrompt = buildAssessmentPrompt(safeGoal, safePlan, input.repoPath);
|
|
30
33
|
const promptFilePath = resolve(input.assessmentDir, "agent-assessment-prompt.md");
|
|
31
|
-
|
|
34
|
+
atomicWriteFileSync(promptFilePath, assessmentPrompt);
|
|
32
35
|
logPaths.prompt = promptFilePath;
|
|
33
36
|
// ── 2. Before snapshot (repo-scoped) ──
|
|
34
37
|
let repoBefore;
|
|
@@ -68,6 +71,8 @@ export async function runAgentAssessment(input) {
|
|
|
68
71
|
maxStderrBytes: Math.max(16384, Math.floor(input.maxOutputBytes / 4)),
|
|
69
72
|
stdoutPath: logPaths.stdout,
|
|
70
73
|
stderrPath: logPaths.stderr,
|
|
74
|
+
environmentVariableNames: invocation.environmentVariableNames,
|
|
75
|
+
blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
|
|
71
76
|
});
|
|
72
77
|
// ── 5. After snapshot + read-only violation check ──
|
|
73
78
|
let readOnlyViolation = false;
|
|
@@ -101,11 +106,11 @@ export async function runAgentAssessment(input) {
|
|
|
101
106
|
stderr_truncated: result.stderrTruncated,
|
|
102
107
|
log_paths: { ...logPaths, violation: resolve(input.assessmentDir, "agent-assessment-violation.json") },
|
|
103
108
|
};
|
|
104
|
-
|
|
109
|
+
atomicWriteJsonFileSync(summary.log_paths.violation, {
|
|
105
110
|
violation: "read_only_violation",
|
|
106
111
|
files: violationFiles,
|
|
107
112
|
exit_code: result.exitCode,
|
|
108
|
-
}
|
|
113
|
+
});
|
|
109
114
|
writeSummary(logPaths.assessment, summary);
|
|
110
115
|
return summary;
|
|
111
116
|
}
|
|
@@ -216,13 +221,17 @@ function parseAssessmentJson(stdout, repoPath) {
|
|
|
216
221
|
const jsonEnd = jsonText.lastIndexOf("}");
|
|
217
222
|
if (jsonStart < 0 || jsonEnd < 0 || jsonEnd <= jsonStart)
|
|
218
223
|
return { output: null, sanitized_reasons };
|
|
219
|
-
let
|
|
224
|
+
let parsedValue;
|
|
220
225
|
try {
|
|
221
|
-
|
|
226
|
+
parsedValue = JSON.parse(jsonText.slice(jsonStart, jsonEnd + 1));
|
|
222
227
|
}
|
|
223
228
|
catch {
|
|
224
229
|
return { output: null, sanitized_reasons };
|
|
225
230
|
}
|
|
231
|
+
if (!parsedValue || typeof parsedValue !== "object" || Array.isArray(parsedValue)) {
|
|
232
|
+
return { output: null, sanitized_reasons };
|
|
233
|
+
}
|
|
234
|
+
const parsed = parsedValue;
|
|
226
235
|
// ── Strict validation ──
|
|
227
236
|
if (parsed.risk_level !== "low" && parsed.risk_level !== "medium" && parsed.risk_level !== "high") {
|
|
228
237
|
return { output: null, sanitized_reasons };
|
|
@@ -235,7 +244,7 @@ function parseAssessmentJson(stdout, repoPath) {
|
|
|
235
244
|
return { output: null, sanitized_reasons };
|
|
236
245
|
if (parsed.reason_codes.length > 50)
|
|
237
246
|
return { output: null, sanitized_reasons };
|
|
238
|
-
const reasonCodes = parsed.reason_codes.filter((
|
|
247
|
+
const reasonCodes = parsed.reason_codes.filter((reason) => typeof reason === "string" && reason.length <= 100);
|
|
239
248
|
if (reasonCodes.length !== parsed.reason_codes.length) {
|
|
240
249
|
sanitized_reasons.push("reason_codes_filtered");
|
|
241
250
|
}
|
|
@@ -257,7 +266,7 @@ function parseAssessmentJson(stdout, repoPath) {
|
|
|
257
266
|
}
|
|
258
267
|
const resolved = resolve(repoPath, p);
|
|
259
268
|
const rel = relative(repoPath, resolved);
|
|
260
|
-
if (rel.startsWith(
|
|
269
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
261
270
|
pathsSanitized = true;
|
|
262
271
|
continue;
|
|
263
272
|
}
|
|
@@ -270,7 +279,7 @@ function parseAssessmentJson(stdout, repoPath) {
|
|
|
270
279
|
return { output: null, sanitized_reasons };
|
|
271
280
|
if (parsed.destructive_actions.length > 20)
|
|
272
281
|
return { output: null, sanitized_reasons };
|
|
273
|
-
const destructiveActions = parsed.destructive_actions.filter((
|
|
282
|
+
const destructiveActions = parsed.destructive_actions.filter((action) => typeof action === "string" && action.length <= 200);
|
|
274
283
|
if (typeof parsed.requires_user_confirm !== "boolean")
|
|
275
284
|
return { output: null, sanitized_reasons };
|
|
276
285
|
const notes = typeof parsed.notes === "string" ? parsed.notes.slice(0, 2000) : "";
|
|
@@ -289,5 +298,5 @@ function parseAssessmentJson(stdout, repoPath) {
|
|
|
289
298
|
};
|
|
290
299
|
}
|
|
291
300
|
function writeSummary(path, summary) {
|
|
292
|
-
|
|
301
|
+
atomicWriteJsonFileSync(path, summary);
|
|
293
302
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { mkdirSync,
|
|
1
|
+
import { mkdirSync, existsSync, readFileSync, } from "node:fs";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { resolve, join } from "node:path";
|
|
4
4
|
import { getAssessmentsDir, getConfig, getRepoAllowedTestCommands } from "../config.js";
|
|
5
5
|
import { guardPath } from "../security/pathGuard.js";
|
|
6
6
|
import { PatchWardenError } from "../errors.js";
|
|
7
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
8
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
9
|
+
import { withFileLockSync } from "../utils/lockedJsonFile.js";
|
|
7
10
|
import { TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
8
|
-
import { getLastToolCatalogSnapshot } from "../tools/toolCatalog.js";
|
|
11
|
+
import { getLastToolCatalogSnapshot } from "../tools/catalog/toolCatalog.js";
|
|
9
12
|
import { captureRepoSnapshot } from "../runner/changeCapture.js";
|
|
10
13
|
const SENSITIVE_PATH_RULES_SIGNATURE = "v1";
|
|
11
14
|
const ARTIFACT_RULES_SIGNATURE = "v1";
|
|
@@ -71,7 +74,7 @@ export function createAssessment(input) {
|
|
|
71
74
|
repo_path: input.repo_path,
|
|
72
75
|
resolved_repo_path: input.resolved_repo_path,
|
|
73
76
|
template: input.template || null,
|
|
74
|
-
goal: input.goal
|
|
77
|
+
goal: input.goal ? redactSensitiveContent(input.goal).content : null,
|
|
75
78
|
test_command: input.test_command || null,
|
|
76
79
|
verify_commands: input.verify_commands || [],
|
|
77
80
|
agent: input.agent,
|
|
@@ -83,7 +86,7 @@ export function createAssessment(input) {
|
|
|
83
86
|
confirm_code: null,
|
|
84
87
|
agent_assessment_summary: input.agent_assessment_summary || null,
|
|
85
88
|
};
|
|
86
|
-
|
|
89
|
+
atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
|
|
87
90
|
return record;
|
|
88
91
|
}
|
|
89
92
|
export function readAssessment(assessmentId) {
|
|
@@ -160,6 +163,9 @@ export async function confirmAssessment(assessmentId) {
|
|
|
160
163
|
if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
|
|
161
164
|
throw new PatchWardenError("assessment_id_invalid", "Local confirmation requires the full assessment_id (32 hexadecimal random characters).", "Copy the complete assessment_id from the assess_only response; assessment_short_id is display-only.");
|
|
162
165
|
}
|
|
166
|
+
const config = getConfig();
|
|
167
|
+
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
168
|
+
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
163
169
|
const assessment = readAssessment(assessmentId);
|
|
164
170
|
if (assessment.decision !== "needs_confirm" || !assessment.requires_confirm) {
|
|
165
171
|
throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" has decision "${assessment.decision}" and cannot be locally confirmed.`, assessment.decision === "blocked"
|
|
@@ -167,37 +173,39 @@ export async function confirmAssessment(assessmentId) {
|
|
|
167
173
|
: "This assessment does not require confirmation; execute it with the minimal assessment_id call.");
|
|
168
174
|
}
|
|
169
175
|
const snapshot = await captureRepoSnapshot(assessment.resolved_repo_path);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
// Re-read and write while holding one cross-process lock. Concurrent local
|
|
177
|
+
// confirmers may compute snapshots in parallel, but they cannot replace one
|
|
178
|
+
// another's confirmation metadata from a stale assessment record.
|
|
179
|
+
return withFileLockSync(assessmentFile, () => {
|
|
180
|
+
const validation = validateAssessmentFreshness(assessmentId, snapshot, {
|
|
181
|
+
allow_unconfirmed: true,
|
|
182
|
+
// A standalone CLI has no active MCP profile snapshot. The execute path
|
|
183
|
+
// still performs the full tool-manifest check before creating the task.
|
|
184
|
+
skip_tool_manifest: true,
|
|
185
|
+
});
|
|
186
|
+
if (!validation.valid || !validation.assessment) {
|
|
187
|
+
throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
|
|
188
|
+
}
|
|
189
|
+
if (validation.assessment.decision !== "needs_confirm" || !validation.assessment.requires_confirm) {
|
|
190
|
+
throw new PatchWardenError("assessment_confirmation_not_allowed", `Assessment "${assessmentId}" is no longer eligible for local confirmation.`, "Run create_task with execution_mode=assess_only again.");
|
|
191
|
+
}
|
|
192
|
+
const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
|
|
193
|
+
const confirmedRecord = {
|
|
194
|
+
...validation.assessment,
|
|
195
|
+
confirmed: true,
|
|
196
|
+
confirmed_at: confirmedAt,
|
|
197
|
+
confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
|
|
198
|
+
};
|
|
199
|
+
atomicWriteJsonFileSync(assessmentFile, confirmedRecord);
|
|
200
|
+
return {
|
|
201
|
+
assessment_id: assessmentId,
|
|
202
|
+
decision: "needs_confirm",
|
|
203
|
+
confirmed: true,
|
|
204
|
+
confirmed_at: confirmedAt,
|
|
205
|
+
expires_at: confirmedRecord.expires_at,
|
|
206
|
+
next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
|
|
207
|
+
};
|
|
175
208
|
});
|
|
176
|
-
if (!validation.valid || !validation.assessment) {
|
|
177
|
-
throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`, "Run create_task with execution_mode=assess_only again, then confirm the new assessment locally.");
|
|
178
|
-
}
|
|
179
|
-
const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
|
|
180
|
-
const confirmedRecord = {
|
|
181
|
-
...validation.assessment,
|
|
182
|
-
confirmed: true,
|
|
183
|
-
confirmed_at: confirmedAt,
|
|
184
|
-
confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
|
|
185
|
-
};
|
|
186
|
-
const config = getConfig();
|
|
187
|
-
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
188
|
-
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
189
|
-
const temporaryFile = `${assessmentFile}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
190
|
-
guardPath(temporaryFile, config.workspaceRoot, config.assessmentsDir);
|
|
191
|
-
writeFileSync(temporaryFile, JSON.stringify(confirmedRecord, null, 2), "utf-8");
|
|
192
|
-
renameSync(temporaryFile, assessmentFile);
|
|
193
|
-
return {
|
|
194
|
-
assessment_id: assessmentId,
|
|
195
|
-
decision: "needs_confirm",
|
|
196
|
-
confirmed: true,
|
|
197
|
-
confirmed_at: confirmedAt,
|
|
198
|
-
expires_at: confirmedRecord.expires_at,
|
|
199
|
-
next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
|
|
200
|
-
};
|
|
201
209
|
}
|
|
202
210
|
export function computeWorkspaceFingerprint(snapshot) {
|
|
203
211
|
const fileHashes = Object.entries(snapshot.files)
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export interface AgentConfig {
|
|
2
2
|
command: string;
|
|
3
3
|
args: string[];
|
|
4
|
+
envAllowlist?: string[];
|
|
5
|
+
adapter?: string;
|
|
6
|
+
model?: string;
|
|
4
7
|
}
|
|
5
8
|
export interface PatchWardenConfig {
|
|
6
9
|
workspaceRoot: string;
|
|
@@ -29,6 +32,18 @@ export interface PatchWardenConfig {
|
|
|
29
32
|
agentAssessmentMaxOutputBytes?: number;
|
|
30
33
|
agentAssessmentAgentName?: string;
|
|
31
34
|
enableDirectProfile?: boolean;
|
|
35
|
+
tunnelClientPath?: string;
|
|
36
|
+
tunnelProxy?: {
|
|
37
|
+
scope: "shared" | "separate";
|
|
38
|
+
core: {
|
|
39
|
+
mode: "environment" | "none" | "manual";
|
|
40
|
+
url?: string;
|
|
41
|
+
};
|
|
42
|
+
direct: {
|
|
43
|
+
mode: "environment" | "none" | "manual";
|
|
44
|
+
url?: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
32
47
|
directSessionsDir: string;
|
|
33
48
|
directSessionTtlSeconds: number;
|
|
34
49
|
directMaxPatchBytes: number;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { basename, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { validateWorkspaceRoot } from "./security/workspaceRootGuard.js";
|
|
3
4
|
// ── Defaults ──────────────────────────────────────────────────────
|
|
4
5
|
const DEFAULT_CONFIG = {
|
|
5
6
|
workspaceRoot: process.cwd(),
|
|
@@ -7,16 +8,7 @@ const DEFAULT_CONFIG = {
|
|
|
7
8
|
tasksDir: ".patchwarden/tasks",
|
|
8
9
|
assessmentsDir: ".patchwarden/assessments",
|
|
9
10
|
assessmentTtlSeconds: 3600,
|
|
10
|
-
agents: {
|
|
11
|
-
codex: {
|
|
12
|
-
command: "codex",
|
|
13
|
-
args: ["exec", "--cd", "{repo}", "{prompt}"],
|
|
14
|
-
},
|
|
15
|
-
opencode: {
|
|
16
|
-
command: "opencode",
|
|
17
|
-
args: ["run", "{prompt}"],
|
|
18
|
-
},
|
|
19
|
-
},
|
|
11
|
+
agents: {},
|
|
20
12
|
allowedTestCommands: [
|
|
21
13
|
"npm test",
|
|
22
14
|
"npm run test",
|
|
@@ -123,6 +115,11 @@ function normalizeConfig(config) {
|
|
|
123
115
|
if (!config.workspaceRoot || typeof config.workspaceRoot !== "string") {
|
|
124
116
|
throw new Error("workspaceRoot must be a non-empty string");
|
|
125
117
|
}
|
|
118
|
+
const workspaceValidation = validateWorkspaceRoot(config.workspaceRoot);
|
|
119
|
+
if (!workspaceValidation.ok) {
|
|
120
|
+
throw new Error(`Invalid workspaceRoot: ${workspaceValidation.reason} (${workspaceValidation.path})`);
|
|
121
|
+
}
|
|
122
|
+
config.workspaceRoot = workspaceValidation.path;
|
|
126
123
|
if (!config.plansDir || typeof config.plansDir !== "string") {
|
|
127
124
|
throw new Error("plansDir must be a non-empty string");
|
|
128
125
|
}
|
|
@@ -138,6 +135,21 @@ function normalizeConfig(config) {
|
|
|
138
135
|
if (!config.agents || typeof config.agents !== "object") {
|
|
139
136
|
throw new Error("agents must be an object");
|
|
140
137
|
}
|
|
138
|
+
const agents = {};
|
|
139
|
+
for (const [agentName, agent] of Object.entries(config.agents)) {
|
|
140
|
+
if (!agent || typeof agent !== "object" || typeof agent.command !== "string" || !Array.isArray(agent.args)) {
|
|
141
|
+
throw new Error(`agents["${agentName}"] must define command and args`);
|
|
142
|
+
}
|
|
143
|
+
const envAllowlist = agent.envAllowlist ?? [];
|
|
144
|
+
if (!Array.isArray(envAllowlist) || envAllowlist.some((name) => typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) {
|
|
145
|
+
throw new Error(`agents["${agentName}"].envAllowlist must contain only environment variable names`);
|
|
146
|
+
}
|
|
147
|
+
const blockedNames = new Set(["CONTROL_PLANE_API_KEY", "PATCHWARDEN_OWNER_TOKEN", config.http?.ownerTokenEnv?.toUpperCase()]);
|
|
148
|
+
const blocked = envAllowlist.find((name) => blockedNames.has(name.toUpperCase()));
|
|
149
|
+
if (blocked)
|
|
150
|
+
throw new Error(`agents["${agentName}"].envAllowlist cannot include reserved variable "${blocked}"`);
|
|
151
|
+
agents[agentName] = { ...agent, args: [...agent.args], envAllowlist: [...new Set(envAllowlist)] };
|
|
152
|
+
}
|
|
141
153
|
if (!Array.isArray(config.allowedTestCommands)) {
|
|
142
154
|
throw new Error("allowedTestCommands must be an array");
|
|
143
155
|
}
|
|
@@ -209,8 +221,10 @@ function normalizeConfig(config) {
|
|
|
209
221
|
if (config.http.host !== undefined && typeof config.http.host !== "string") {
|
|
210
222
|
throw new Error("http.host must be a string");
|
|
211
223
|
}
|
|
212
|
-
if (config.http.ownerTokenEnv !== undefined
|
|
213
|
-
|
|
224
|
+
if (config.http.ownerTokenEnv !== undefined
|
|
225
|
+
&& (typeof config.http.ownerTokenEnv !== "string"
|
|
226
|
+
|| !/^[A-Za-z_][A-Za-z0-9_]*$/.test(config.http.ownerTokenEnv))) {
|
|
227
|
+
throw new Error("http.ownerTokenEnv must be a valid environment variable name");
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
if (config.enableRunTaskTool !== undefined && typeof config.enableRunTaskTool !== "boolean") {
|
|
@@ -235,6 +249,44 @@ function normalizeConfig(config) {
|
|
|
235
249
|
if (config.enableDirectProfile !== undefined && typeof config.enableDirectProfile !== "boolean") {
|
|
236
250
|
throw new Error("enableDirectProfile must be a boolean");
|
|
237
251
|
}
|
|
252
|
+
if (config.tunnelClientPath !== undefined) {
|
|
253
|
+
if (typeof config.tunnelClientPath !== "string" || !isAbsolute(config.tunnelClientPath)) {
|
|
254
|
+
throw new Error("tunnelClientPath must be an absolute path");
|
|
255
|
+
}
|
|
256
|
+
if (basename(config.tunnelClientPath).toLowerCase() !== "tunnel-client.exe") {
|
|
257
|
+
throw new Error("tunnelClientPath must point to tunnel-client.exe");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (config.tunnelProxy !== undefined) {
|
|
261
|
+
if (!config.tunnelProxy || typeof config.tunnelProxy !== "object" || !["shared", "separate"].includes(config.tunnelProxy.scope)) {
|
|
262
|
+
throw new Error("tunnelProxy.scope must be shared or separate");
|
|
263
|
+
}
|
|
264
|
+
for (const key of ["core", "direct"]) {
|
|
265
|
+
const endpoint = config.tunnelProxy[key];
|
|
266
|
+
if (!endpoint || typeof endpoint !== "object" || !["environment", "none", "manual"].includes(endpoint.mode)) {
|
|
267
|
+
throw new Error(`tunnelProxy.${key}.mode must be environment, none, or manual`);
|
|
268
|
+
}
|
|
269
|
+
if (endpoint.mode === "manual") {
|
|
270
|
+
if (typeof endpoint.url !== "string")
|
|
271
|
+
throw new Error(`tunnelProxy.${key}.url is required in manual mode`);
|
|
272
|
+
let parsed;
|
|
273
|
+
try {
|
|
274
|
+
parsed = new URL(endpoint.url);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
throw new Error(`tunnelProxy.${key}.url must be a valid URL`);
|
|
278
|
+
}
|
|
279
|
+
if (!["http:", "https:", "socks5:"].includes(parsed.protocol)) {
|
|
280
|
+
throw new Error(`tunnelProxy.${key}.url must use http, https, or socks5`);
|
|
281
|
+
}
|
|
282
|
+
if (parsed.username || parsed.password)
|
|
283
|
+
throw new Error(`tunnelProxy.${key}.url must not contain credentials`);
|
|
284
|
+
}
|
|
285
|
+
else if (endpoint.url !== undefined) {
|
|
286
|
+
throw new Error(`tunnelProxy.${key}.url is only allowed in manual mode`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
238
290
|
if (!config.directSessionsDir || typeof config.directSessionsDir !== "string") {
|
|
239
291
|
throw new Error("directSessionsDir must be a non-empty string");
|
|
240
292
|
}
|
|
@@ -276,6 +328,7 @@ function normalizeConfig(config) {
|
|
|
276
328
|
}
|
|
277
329
|
return {
|
|
278
330
|
...config,
|
|
331
|
+
agents,
|
|
279
332
|
workspaceRoot: resolve(config.workspaceRoot),
|
|
280
333
|
allowedTestCommands: [...new Set(config.allowedTestCommands.map((command) => command.trim()))],
|
|
281
334
|
repoAllowedTestCommands,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { buildChildEnvironment, resolveTrustedExecutable } from "../runner/processSecurity.js";
|
|
4
|
+
const FILE_MANAGER_ENVIRONMENT = [
|
|
5
|
+
"DISPLAY",
|
|
6
|
+
"WAYLAND_DISPLAY",
|
|
7
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
8
|
+
"XDG_RUNTIME_DIR",
|
|
9
|
+
];
|
|
10
|
+
/** Launch a trusted platform file manager without inheriting ambient secrets. */
|
|
11
|
+
export function launchFileManager(target, cwd) {
|
|
12
|
+
try {
|
|
13
|
+
const env = buildChildEnvironment({ cwd, allowedNames: FILE_MANAGER_ENVIRONMENT });
|
|
14
|
+
const requested = fileManagerCommand(env);
|
|
15
|
+
const command = resolveTrustedExecutable(requested, cwd, { pathValue: env.PATH });
|
|
16
|
+
const child = spawn(command, [target], {
|
|
17
|
+
cwd,
|
|
18
|
+
detached: true,
|
|
19
|
+
env,
|
|
20
|
+
stdio: "ignore",
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
});
|
|
23
|
+
child.on("error", () => { });
|
|
24
|
+
child.unref();
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function fileManagerCommand(env) {
|
|
32
|
+
if (process.platform === "win32") {
|
|
33
|
+
const windowsRoot = env.SystemRoot || env.WINDIR;
|
|
34
|
+
return windowsRoot ? join(windowsRoot, "explorer.exe") : "explorer.exe";
|
|
35
|
+
}
|
|
36
|
+
if (process.platform === "darwin")
|
|
37
|
+
return "open";
|
|
38
|
+
return "xdg-open";
|
|
39
|
+
}
|
|
@@ -8,3 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type IncomingMessage } from "node:http";
|
|
10
10
|
export declare function checkControlToken(req: IncomingMessage): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Reject non-loopback Host headers before exposing the browser-readable
|
|
13
|
+
* control token. Binding to 127.0.0.1 alone does not prevent DNS rebinding.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isTrustedControlHost(req: IncomingMessage, expectedPort: number): boolean;
|
|
16
|
+
export declare function isTrustedControlHostHeader(value: string | string[] | undefined, expectedPort: number): boolean;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { firstHeaderValue, timingSafeStringEqual } from "../../security/secretComparison.js";
|
|
2
|
+
import { isTrustedLoopbackHostHeader } from "../../security/loopbackHost.js";
|
|
1
3
|
import { controlToken } from "../shared.js";
|
|
2
4
|
export function checkControlToken(req) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const provided = firstHeaderValue(req.headers["x-patchwarden-control-token"]);
|
|
6
|
+
return provided.length > 0 && timingSafeStringEqual(provided, controlToken);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reject non-loopback Host headers before exposing the browser-readable
|
|
10
|
+
* control token. Binding to 127.0.0.1 alone does not prevent DNS rebinding.
|
|
11
|
+
*/
|
|
12
|
+
export function isTrustedControlHost(req, expectedPort) {
|
|
13
|
+
return isTrustedControlHostHeader(req.headers.host, expectedPort);
|
|
14
|
+
}
|
|
15
|
+
export function isTrustedControlHostHeader(value, expectedPort) {
|
|
16
|
+
return isTrustedLoopbackHostHeader(value, expectedPort);
|
|
8
17
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* assets, css) with path-traversal protection, plus the inline SVG favicon.
|
|
6
6
|
* Content types are mapped from `CONTENT_TYPES` in shared.ts.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
9
9
|
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
10
10
|
import { CONTENT_TYPES, CONTROL_CENTER_FAVICON, errorMessage, sendJson, uiRoot, } from "../shared.js";
|
|
11
11
|
export function serveStatic(res, urlPath) {
|
|
@@ -45,9 +45,22 @@ export function serveStatic(res, urlPath) {
|
|
|
45
45
|
sendJson(res, 404, { error: "Not found" });
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
// Resolve links before reading. Lexical `..` checks alone do not stop a
|
|
49
|
+
// symlink or junction inside `ui/` from pointing outside the bundle.
|
|
50
|
+
const realRoot = realpathSync(uiRoot);
|
|
51
|
+
const realCandidate = realpathSync(candidate);
|
|
52
|
+
const realRelative = relative(realRoot, realCandidate);
|
|
53
|
+
if (realRelative === "" || realRelative.startsWith("..") || isAbsolute(realRelative)) {
|
|
54
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!statSync(realCandidate).isFile()) {
|
|
58
|
+
sendJson(res, 404, { error: "Not found" });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const ext = extname(realCandidate).toLowerCase();
|
|
49
62
|
const contentType = CONTENT_TYPES[ext] || "application/octet-stream";
|
|
50
|
-
const content = readFileSync(
|
|
63
|
+
const content = readFileSync(realCandidate);
|
|
51
64
|
res.writeHead(200, { "Content-Type": contentType });
|
|
52
65
|
res.end(content);
|
|
53
66
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatchWarden Control Center — declarative route table.
|
|
3
|
+
*
|
|
4
|
+
* Exports `buildRoutes(parsedUrl)` which returns the full `Route[]` array
|
|
5
|
+
* consumed by `server.ts`'s `handleRequest`. Routes that need query-string
|
|
6
|
+
* parameters (e.g. `/api/tasks`, `/api/logs/:category`) close over `parsedUrl`
|
|
7
|
+
* via the `query` helper, so each request gets its own self-contained table
|
|
8
|
+
* without module-scoped request state. This keeps concurrent requests safe.
|
|
9
|
+
*
|
|
10
|
+
* Ordering rules:
|
|
11
|
+
* - GET routes first, POST routes second.
|
|
12
|
+
* - Within each method, more specific patterns precede generic `:id`
|
|
13
|
+
* patterns (e.g. `/api/tasks/stale` before `/api/tasks/:id`).
|
|
14
|
+
* - Static asset routes (`/`, `/favicon.ico`, CSS/JS, `/pages/`,
|
|
15
|
+
* `/partials/`, `/vendor/`) and `/control-token.json` are NOT in this
|
|
16
|
+
* table — they remain priority branches in `server.ts`.
|
|
17
|
+
*/
|
|
18
|
+
import { type Route } from "./shared.js";
|
|
19
|
+
/**
|
|
20
|
+
* Build the per-request `Route[]` table. `parsedUrl` is captured by closures
|
|
21
|
+
* so handlers that need query parameters can read `searchParams` without any
|
|
22
|
+
* module-scoped state.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildRoutes(parsedUrl: URL): Route[];
|