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/src/runner/runTask.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import {
|
|
3
|
-
createWriteStream,
|
|
4
4
|
existsSync,
|
|
5
5
|
readFileSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
type WriteStream,
|
|
8
6
|
} from "node:fs";
|
|
9
7
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
10
8
|
import { getTasksDir, getPlansDir, getConfig, resolveWorkspaceRoot } from "../config.js";
|
|
@@ -12,11 +10,18 @@ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
|
12
10
|
import {
|
|
13
11
|
guardTestCommand,
|
|
14
12
|
} from "../security/commandGuard.js";
|
|
15
|
-
import { writeTaskProgress } from "
|
|
16
|
-
import { writeTaskRuntime } from "
|
|
13
|
+
import { writeTaskProgress } from "./taskProgress.js";
|
|
14
|
+
import { writeTaskRuntime } from "./taskRuntime.js";
|
|
15
|
+
import {
|
|
16
|
+
claimPendingTask,
|
|
17
|
+
readTaskStatusFile,
|
|
18
|
+
updateTaskStatusFile,
|
|
19
|
+
type TaskStatusRecord,
|
|
20
|
+
} from "./taskStatusStore.js";
|
|
17
21
|
import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
|
|
22
|
+
import { recordAssessmentValidationFailure } from "../assessments/assessmentDiagnostics.js";
|
|
18
23
|
import { buildAgentInvocation, buildExecutionPrompt } from "./agentInvocation.js";
|
|
19
|
-
import type { TaskPhase, TaskStatus } from "../tools/createTask.js";
|
|
24
|
+
import type { TaskPhase, TaskStatus } from "../tools/tasks/createTask.js";
|
|
20
25
|
import {
|
|
21
26
|
buildChangeArtifacts,
|
|
22
27
|
captureRepoSnapshot,
|
|
@@ -35,8 +40,18 @@ import {
|
|
|
35
40
|
type ChangedFileGroups,
|
|
36
41
|
} from "./changeCapture.js";
|
|
37
42
|
import { PatchWardenError, errorPayload } from "../errors.js";
|
|
38
|
-
import {
|
|
43
|
+
import { ARTIFACT_SCHEMA_VERSION } from "../version.js";
|
|
44
|
+
import { diagnoseAndroidBuild } from "../tools/workspace/androidDoctor.js";
|
|
39
45
|
import { runPostTaskCleanup, type PostTaskCleanupReport } from "./postTaskCleanup.js";
|
|
46
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
47
|
+
import {
|
|
48
|
+
allowedEnvironmentValues,
|
|
49
|
+
buildChildEnvironment,
|
|
50
|
+
redactProcessOutput,
|
|
51
|
+
resolvePackageManagerInvocation,
|
|
52
|
+
resolveTrustedExecutable,
|
|
53
|
+
SecureProcessLogCapture,
|
|
54
|
+
} from "./processSecurity.js";
|
|
40
55
|
|
|
41
56
|
const HEARTBEAT_INTERVAL_MS = 2000;
|
|
42
57
|
const GRACEFUL_KILL_MS = 2000;
|
|
@@ -95,7 +110,7 @@ interface TaskContext {
|
|
|
95
110
|
wsRoot: string;
|
|
96
111
|
config: ReturnType<typeof getConfig>;
|
|
97
112
|
plansDir: string;
|
|
98
|
-
initialStatus:
|
|
113
|
+
initialStatus: TaskStatusRecord;
|
|
99
114
|
planId: string;
|
|
100
115
|
agentName: string;
|
|
101
116
|
testCommand: string;
|
|
@@ -104,6 +119,7 @@ interface TaskContext {
|
|
|
104
119
|
timeoutSeconds: number;
|
|
105
120
|
startedAtMs: number;
|
|
106
121
|
deadlineMs: number;
|
|
122
|
+
runnerInstanceId: string;
|
|
107
123
|
beforeSnapshot: RepoSnapshot;
|
|
108
124
|
beforeWorkspaceSnapshot: RepoSnapshot;
|
|
109
125
|
externalDirtyBaseline: ExternalDirtyFile[];
|
|
@@ -111,6 +127,7 @@ interface TaskContext {
|
|
|
111
127
|
|
|
112
128
|
interface ExecutionState {
|
|
113
129
|
agentResult: ManagedProcessResult | null;
|
|
130
|
+
agentFailureCategory: string | null;
|
|
114
131
|
testResult: TestExecutionResult;
|
|
115
132
|
verifyResults: TestExecutionResult[];
|
|
116
133
|
finalStatus: TaskStatus;
|
|
@@ -154,7 +171,26 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
|
|
|
154
171
|
const statusFile = join(taskDir, "status.json");
|
|
155
172
|
if (!existsSync(statusFile)) throw new Error(`Task not found: "${taskId}"`);
|
|
156
173
|
|
|
157
|
-
const
|
|
174
|
+
const startedAtMs = Date.now();
|
|
175
|
+
const runnerInstanceId = randomUUID().replace(/-/g, "");
|
|
176
|
+
const claim = claimPendingTask(statusFile, {
|
|
177
|
+
status: "running",
|
|
178
|
+
phase: "preparing",
|
|
179
|
+
started_at: new Date(startedAtMs).toISOString(),
|
|
180
|
+
last_heartbeat_at: new Date(startedAtMs).toISOString(),
|
|
181
|
+
current_command: null,
|
|
182
|
+
error: null,
|
|
183
|
+
});
|
|
184
|
+
if (!claim.claimed) {
|
|
185
|
+
const currentStatus = String(claim.status.status || "failed") as TaskStatus;
|
|
186
|
+
return {
|
|
187
|
+
task_id: taskId,
|
|
188
|
+
status: currentStatus,
|
|
189
|
+
error: `Task is ${currentStatus}; only pending tasks can be claimed for execution.`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const initialStatus = claim.status;
|
|
158
194
|
const planId = String(initialStatus.plan_id || "");
|
|
159
195
|
const agentName = String(initialStatus.agent || "");
|
|
160
196
|
const rawRepoPath = String(initialStatus.resolved_repo_path || initialStatus.repo_path || wsRoot);
|
|
@@ -167,7 +203,6 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
|
|
|
167
203
|
} catch (error) {
|
|
168
204
|
return failBeforeExecution(taskId, taskDir, errorMessage(error), error);
|
|
169
205
|
}
|
|
170
|
-
const startedAtMs = Date.now();
|
|
171
206
|
const deadlineMs = startedAtMs + timeoutSeconds * 1000;
|
|
172
207
|
|
|
173
208
|
let repoPath: string;
|
|
@@ -190,7 +225,11 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
|
|
|
190
225
|
const preExecSnapshot = await captureRepoSnapshot(repoPath);
|
|
191
226
|
const validation = validateAssessmentFreshness(assessmentId, preExecSnapshot);
|
|
192
227
|
if (!validation.valid) {
|
|
193
|
-
|
|
228
|
+
recordAssessmentValidationFailure(validation);
|
|
229
|
+
const changedFields = validation.config_change_categories?.length
|
|
230
|
+
? ` Changed fields: ${validation.config_change_categories.join(", ")}.`
|
|
231
|
+
: "";
|
|
232
|
+
const message = `assessment validation failed: ${validation.failure_reason}.${changedFields} Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.`;
|
|
194
233
|
return failBeforeExecution(taskId, taskDir, message);
|
|
195
234
|
}
|
|
196
235
|
} catch (error) {
|
|
@@ -199,18 +238,12 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
|
|
|
199
238
|
}
|
|
200
239
|
}
|
|
201
240
|
|
|
202
|
-
updateStatus(taskDir, {
|
|
203
|
-
status: "running",
|
|
204
|
-
phase: "preparing",
|
|
205
|
-
started_at: new Date(startedAtMs).toISOString(),
|
|
206
|
-
last_heartbeat_at: new Date().toISOString(),
|
|
207
|
-
timeout_seconds: timeoutSeconds,
|
|
208
|
-
current_command: null,
|
|
209
|
-
error: null,
|
|
210
|
-
});
|
|
241
|
+
updateStatus(taskDir, { timeout_seconds: timeoutSeconds });
|
|
211
242
|
writeTaskRuntime(taskDir, {
|
|
212
243
|
task_started_at: new Date(startedAtMs).toISOString(),
|
|
213
244
|
watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || undefined,
|
|
245
|
+
runner_pid: process.pid,
|
|
246
|
+
runner_instance_id: runnerInstanceId,
|
|
214
247
|
});
|
|
215
248
|
setTaskPhase(taskDir, "preparing", null, "Capturing pre-task repository state.");
|
|
216
249
|
|
|
@@ -231,13 +264,14 @@ async function prepareTask(taskId: string): Promise<TaskContext | TaskRunResult>
|
|
|
231
264
|
return {
|
|
232
265
|
taskId, taskDir, statusFile, repoPath, wsRoot, config, plansDir, initialStatus,
|
|
233
266
|
planId, agentName, testCommand, changePolicy, verifyCommands, timeoutSeconds,
|
|
234
|
-
startedAtMs, deadlineMs, beforeSnapshot, beforeWorkspaceSnapshot, externalDirtyBaseline,
|
|
267
|
+
startedAtMs, deadlineMs, runnerInstanceId, beforeSnapshot, beforeWorkspaceSnapshot, externalDirtyBaseline,
|
|
235
268
|
};
|
|
236
269
|
}
|
|
237
270
|
|
|
238
271
|
async function executeAgent(ctx: TaskContext): Promise<ExecutionState> {
|
|
239
272
|
const state: ExecutionState = {
|
|
240
273
|
agentResult: null,
|
|
274
|
+
agentFailureCategory: null,
|
|
241
275
|
testResult: skippedTest(ctx.testCommand, ctx.repoPath, "Agent did not complete successfully."),
|
|
242
276
|
verifyResults: [],
|
|
243
277
|
finalStatus: "failed",
|
|
@@ -262,8 +296,11 @@ async function executeAgent(ctx: TaskContext): Promise<ExecutionState> {
|
|
|
262
296
|
phase: "executing_agent",
|
|
263
297
|
currentCommand: invocation.commandLabel,
|
|
264
298
|
deadlineMs: ctx.deadlineMs,
|
|
299
|
+
runnerInstanceId: ctx.runnerInstanceId,
|
|
265
300
|
stdoutPath: join(ctx.taskDir, "stdout.log"),
|
|
266
301
|
stderrPath: join(ctx.taskDir, "stderr.log"),
|
|
302
|
+
environmentVariableNames: invocation.environmentVariableNames,
|
|
303
|
+
blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
|
|
267
304
|
});
|
|
268
305
|
|
|
269
306
|
const agentResult = state.agentResult;
|
|
@@ -273,11 +310,15 @@ async function executeAgent(ctx: TaskContext): Promise<ExecutionState> {
|
|
|
273
310
|
? "Task was terminated by kill_task."
|
|
274
311
|
: "Task was canceled by user request.";
|
|
275
312
|
} else if (agentResult.terminationReason === "timeout") {
|
|
313
|
+
state.finalStatus = "timeout";
|
|
276
314
|
state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during agent execution.`;
|
|
277
315
|
} else if (agentResult.spawnError) {
|
|
278
316
|
state.finalError = `Agent spawn failed: ${agentResult.spawnError}`;
|
|
279
317
|
} else if (agentResult.exitCode !== 0) {
|
|
280
|
-
state.
|
|
318
|
+
state.agentFailureCategory = classifyAgentFailure(agentResult.stderr);
|
|
319
|
+
state.finalError = state.agentFailureCategory
|
|
320
|
+
? `Agent provider failure (${state.agentFailureCategory}); process exited with code ${agentResult.exitCode}.`
|
|
321
|
+
: `Agent exited with code ${agentResult.exitCode}.`;
|
|
281
322
|
}
|
|
282
323
|
} catch (error) {
|
|
283
324
|
state.lastCaughtError = error;
|
|
@@ -294,7 +335,14 @@ async function runVerification(ctx: TaskContext, state: ExecutionState): Promise
|
|
|
294
335
|
if (ctx.verifyCommands.length > 0) {
|
|
295
336
|
for (const command of ctx.verifyCommands) {
|
|
296
337
|
setTaskPhase(ctx.taskDir, "running_tests", command);
|
|
297
|
-
const verification = await runTrustedTestCommand(
|
|
338
|
+
const verification = await runTrustedTestCommand(
|
|
339
|
+
command,
|
|
340
|
+
ctx.repoPath,
|
|
341
|
+
ctx.taskDir,
|
|
342
|
+
ctx.statusFile,
|
|
343
|
+
ctx.deadlineMs,
|
|
344
|
+
ctx.runnerInstanceId,
|
|
345
|
+
);
|
|
298
346
|
state.verifyResults.push(verification);
|
|
299
347
|
if (verification.terminationReason || Date.now() >= ctx.deadlineMs) break;
|
|
300
348
|
}
|
|
@@ -307,6 +355,7 @@ async function runVerification(ctx: TaskContext, state: ExecutionState): Promise
|
|
|
307
355
|
? "Task was terminated by kill_task during verification."
|
|
308
356
|
: "Task was canceled during verification.";
|
|
309
357
|
} else if (interrupted?.terminationReason === "timeout" || Date.now() >= ctx.deadlineMs) {
|
|
358
|
+
state.finalStatus = "timeout";
|
|
310
359
|
state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during verification.`;
|
|
311
360
|
} else if (failedVerification) {
|
|
312
361
|
state.finalStatus = "failed_verification";
|
|
@@ -342,7 +391,7 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
|
|
|
342
391
|
skipped: [{ path: ".", reason: "post_task_cleanup", skip_reason: errorMessage(error) }],
|
|
343
392
|
source_files_touched: 0,
|
|
344
393
|
};
|
|
345
|
-
|
|
394
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "post-task-cleanup.json"), cleanupReport);
|
|
346
395
|
updateStatus(ctx.taskDir, { cleanup: cleanupReport });
|
|
347
396
|
}
|
|
348
397
|
}
|
|
@@ -353,21 +402,20 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
|
|
|
353
402
|
let artifactStatus: "collected" | "partial" | "failed" | "timeout" = "collected";
|
|
354
403
|
let artifactCollectionError: string | null = null;
|
|
355
404
|
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
405
|
+
const artifactController = new AbortController();
|
|
356
406
|
try {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
changes = collectionResult;
|
|
407
|
+
timeoutHandle = setTimeout(() => {
|
|
408
|
+
artifactController.abort(new Error("Artifact collection timed out"));
|
|
409
|
+
}, ARTIFACT_COLLECTION_TIMEOUT_MS);
|
|
410
|
+
const afterSnapshot = await captureRepoSnapshot(ctx.repoPath, artifactController.signal);
|
|
411
|
+
writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
|
|
412
|
+
changes = await buildChangeArtifacts(
|
|
413
|
+
ctx.repoPath,
|
|
414
|
+
ctx.beforeSnapshot,
|
|
415
|
+
afterSnapshot,
|
|
416
|
+
artifactController.signal,
|
|
417
|
+
);
|
|
369
418
|
} catch (error) {
|
|
370
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
371
419
|
state.lastCaughtError = error;
|
|
372
420
|
artifactCollectionError = errorMessage(error);
|
|
373
421
|
artifactStatus = artifactCollectionError.includes("timed out") ? "timeout" : "failed";
|
|
@@ -386,7 +434,7 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
|
|
|
386
434
|
unavailable_reason: `Change capture failed: ${artifactCollectionError}`,
|
|
387
435
|
artifact_hygiene: emptyArtifactHygiene(),
|
|
388
436
|
};
|
|
389
|
-
|
|
437
|
+
atomicWriteFileSync(join(ctx.taskDir, "partial_result.md"), [
|
|
390
438
|
"# PatchWarden Partial Result",
|
|
391
439
|
"",
|
|
392
440
|
`Task: ${ctx.taskId}`,
|
|
@@ -406,13 +454,15 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
|
|
|
406
454
|
"## Note",
|
|
407
455
|
"Artifact collection did not complete. The task result may be incomplete. Review stdout.log, stderr.log, and verify.json for details.",
|
|
408
456
|
"",
|
|
409
|
-
].join("\n")
|
|
457
|
+
].join("\n"));
|
|
410
458
|
if (state.finalStatus === "done_by_agent") {
|
|
411
459
|
state.finalError = `Change capture failed: ${artifactCollectionError}`;
|
|
412
460
|
state.finalStatus = "failed";
|
|
413
461
|
} else {
|
|
414
462
|
state.finalError ||= `Change capture failed: ${artifactCollectionError}`;
|
|
415
463
|
}
|
|
464
|
+
} finally {
|
|
465
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
416
466
|
}
|
|
417
467
|
const artifactCollectionFinishedAt = new Date().toISOString();
|
|
418
468
|
|
|
@@ -440,18 +490,18 @@ async function collectArtifacts(ctx: TaskContext, state: ExecutionState): Promis
|
|
|
440
490
|
|
|
441
491
|
applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty);
|
|
442
492
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
493
|
+
atomicWriteFileSync(join(ctx.taskDir, "git.diff"), changes.diff);
|
|
494
|
+
atomicWriteFileSync(join(ctx.taskDir, "diff.patch"), changes.diff);
|
|
495
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "changed-files.json"), { schema_version: ARTIFACT_SCHEMA_VERSION, ...changes });
|
|
446
496
|
const artifactManifest = await buildArtifactManifest(changes.changed_files, ctx.repoPath, ctx.taskId);
|
|
447
|
-
|
|
497
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "artifact_manifest.json"), artifactManifest);
|
|
448
498
|
const changedFileGroups = groupChangedFiles(changes.changed_files);
|
|
449
|
-
|
|
499
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "file-stats.json"), {
|
|
450
500
|
task_id: ctx.taskId,
|
|
451
501
|
additions: changes.additions,
|
|
452
502
|
deletions: changes.deletions,
|
|
453
503
|
files: changes.file_stats,
|
|
454
|
-
}
|
|
504
|
+
});
|
|
455
505
|
|
|
456
506
|
return {
|
|
457
507
|
changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt,
|
|
@@ -470,15 +520,15 @@ function applyScopeViolationVerdict(
|
|
|
470
520
|
if (newOutOfScopeChanges.length > 0) {
|
|
471
521
|
state.finalStatus = "failed_scope_violation";
|
|
472
522
|
state.finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
|
|
473
|
-
|
|
523
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "rollback-plan.json"), {
|
|
474
524
|
task_id: ctx.taskId,
|
|
475
525
|
status: "review_required",
|
|
476
526
|
automatic_rollback_performed: false,
|
|
477
527
|
warning: "Review ownership and concurrent edits before rollback. PatchWarden did not modify or restore these files.",
|
|
478
528
|
out_of_scope_changes: newOutOfScopeChanges,
|
|
479
529
|
preexisting_external_dirty_files: preexistingExternalDirty,
|
|
480
|
-
}
|
|
481
|
-
|
|
530
|
+
});
|
|
531
|
+
atomicWriteFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
|
|
482
532
|
"# Scope Violation Rollback Plan",
|
|
483
533
|
"",
|
|
484
534
|
`Task: ${ctx.taskId}`,
|
|
@@ -488,7 +538,10 @@ function applyScopeViolationVerdict(
|
|
|
488
538
|
"## New out-of-scope files (caused by this task)",
|
|
489
539
|
...newOutOfScopeChanges.map((file) => `- ${file.change}: ${file.path}`),
|
|
490
540
|
"",
|
|
491
|
-
].join("\n")
|
|
541
|
+
].join("\n"));
|
|
542
|
+
} else if (changes.diff_redacted) {
|
|
543
|
+
state.finalStatus = "failed_policy_violation";
|
|
544
|
+
state.finalError = "Credential-like content was detected in the task diff. PatchWarden redacted the evidence; remove the sensitive content before retrying.";
|
|
492
545
|
} else if (ctx.changePolicy === "no_changes" && changes.changed_files.length > 0) {
|
|
493
546
|
state.finalStatus = "failed_policy_violation";
|
|
494
547
|
state.finalError = `Task policy requires no repository changes, but detected ${changes.changed_files.length} change(s).`;
|
|
@@ -500,19 +553,21 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
|
|
|
500
553
|
outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport,
|
|
501
554
|
artifactManifest, changedFileGroups } = evidence;
|
|
502
555
|
|
|
503
|
-
|
|
556
|
+
atomicWriteFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult));
|
|
504
557
|
const verifyJson = buildVerifyJson(ctx.verifyCommands, state.verifyResults, ctx.repoPath);
|
|
505
558
|
if (newOutOfScopeChanges.length > 0) {
|
|
506
559
|
verifyJson.status = "failed";
|
|
507
560
|
verifyJson.failure_reason = "scope_violation";
|
|
508
561
|
} else if (state.finalStatus === "failed_policy_violation") {
|
|
509
562
|
verifyJson.status = "failed";
|
|
510
|
-
verifyJson.failure_reason =
|
|
563
|
+
verifyJson.failure_reason = changes.diff_redacted
|
|
564
|
+
? "sensitive_content_violation"
|
|
565
|
+
: "change_policy_violation";
|
|
511
566
|
}
|
|
512
|
-
|
|
513
|
-
|
|
567
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "verify.json"), verifyJson);
|
|
568
|
+
atomicWriteFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands));
|
|
514
569
|
|
|
515
|
-
if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus)) state.finalStatus = "failed";
|
|
570
|
+
if (!["canceled", "timeout", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus)) state.finalStatus = "failed";
|
|
516
571
|
const finalPhase: TaskPhase = state.finalStatus === "done_by_agent" ? "done_by_agent" : (state.finalStatus as TaskPhase);
|
|
517
572
|
const followup = buildFailureFollowup(state.finalStatus, state.finalError, verifyJson.commands);
|
|
518
573
|
|
|
@@ -547,19 +602,19 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
|
|
|
547
602
|
androidWarning,
|
|
548
603
|
preexistingWarnings,
|
|
549
604
|
});
|
|
550
|
-
|
|
605
|
+
atomicWriteFileSync(join(ctx.taskDir, "result.md"), resultMd);
|
|
551
606
|
|
|
552
607
|
const resultJson = buildResultJson({
|
|
553
608
|
ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning,
|
|
554
609
|
});
|
|
555
|
-
|
|
610
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "result.json"), resultJson);
|
|
556
611
|
|
|
557
612
|
if (state.finalError) {
|
|
558
613
|
const structuredError = errorPayload(state.lastCaughtError);
|
|
559
|
-
|
|
614
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "error.log"), {
|
|
560
615
|
summary: state.finalError,
|
|
561
616
|
...structuredError,
|
|
562
|
-
}
|
|
617
|
+
});
|
|
563
618
|
}
|
|
564
619
|
|
|
565
620
|
const finishedAt = new Date().toISOString();
|
|
@@ -570,6 +625,10 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
|
|
|
570
625
|
last_heartbeat_at: finishedAt,
|
|
571
626
|
finished_at: finishedAt,
|
|
572
627
|
error: state.finalError,
|
|
628
|
+
termination_reason: state.finalStatus === "timeout"
|
|
629
|
+
? "timeout"
|
|
630
|
+
: state.finalStatus === "canceled" ? "canceled" : null,
|
|
631
|
+
agent_failure_category: state.agentFailureCategory,
|
|
573
632
|
changed_files: changes.changed_files.map(({ path, change }) => ({ path, change })),
|
|
574
633
|
artifact_hygiene_counts: changes.artifact_hygiene.counts,
|
|
575
634
|
artifact_status: artifactStatus,
|
|
@@ -584,6 +643,8 @@ function finalizeTask(ctx: TaskContext, state: ExecutionState, evidence: Artifac
|
|
|
584
643
|
verify_commands: ctx.verifyCommands,
|
|
585
644
|
diff_available: changes.diff_available,
|
|
586
645
|
diff_truncated: changes.diff_truncated,
|
|
646
|
+
diff_redacted: changes.diff_redacted === true,
|
|
647
|
+
diff_redaction_categories: changes.diff_redaction_categories ?? [],
|
|
587
648
|
workspace_dirty_before: changes.workspace_dirty_before,
|
|
588
649
|
workspace_dirty_after: changes.workspace_dirty_after,
|
|
589
650
|
workspace_dirty: changes.workspace_dirty_after,
|
|
@@ -619,6 +680,7 @@ function buildResultJson(input: {
|
|
|
619
680
|
preexistingExternalDirty, cleanupReport, artifactManifest, changedFileGroups } = evidence;
|
|
620
681
|
|
|
621
682
|
return {
|
|
683
|
+
schema_version: ARTIFACT_SCHEMA_VERSION,
|
|
622
684
|
task_id: ctx.taskId,
|
|
623
685
|
status: state.finalStatus,
|
|
624
686
|
agent: ctx.agentName,
|
|
@@ -629,6 +691,10 @@ function buildResultJson(input: {
|
|
|
629
691
|
template: ctx.initialStatus.template || null,
|
|
630
692
|
change_policy: ctx.changePolicy,
|
|
631
693
|
summary: state.finalError || "Agent execution and configured verification completed successfully.",
|
|
694
|
+
termination_reason: state.finalStatus === "timeout"
|
|
695
|
+
? "timeout"
|
|
696
|
+
: state.finalStatus === "canceled" ? "canceled" : null,
|
|
697
|
+
agent_failure_category: state.agentFailureCategory,
|
|
632
698
|
changed_files: changes.changed_files,
|
|
633
699
|
changed_file_groups: {
|
|
634
700
|
source_changes: changedFileGroups.source_changes.length,
|
|
@@ -668,6 +734,9 @@ function buildResultJson(input: {
|
|
|
668
734
|
warnings: [
|
|
669
735
|
...ctx.beforeSnapshot.warnings,
|
|
670
736
|
...(changes.diff_truncated ? ["diff.patch was truncated; changed-files.json retains file evidence."] : []),
|
|
737
|
+
...(changes.diff_redacted ? [
|
|
738
|
+
`Credential-like diff content was redacted (${(changes.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`,
|
|
739
|
+
] : []),
|
|
671
740
|
...evidence.preexistingWarnings,
|
|
672
741
|
...(androidWarning ? [androidWarning] : []),
|
|
673
742
|
],
|
|
@@ -753,18 +822,34 @@ async function runManagedProcess(options: {
|
|
|
753
822
|
deadlineMs: number;
|
|
754
823
|
stdoutPath?: string;
|
|
755
824
|
stderrPath?: string;
|
|
825
|
+
environmentVariableNames?: string[];
|
|
826
|
+
blockedEnvironmentVariableNames?: string[];
|
|
827
|
+
maxLogBytes?: number;
|
|
828
|
+
runnerInstanceId: string;
|
|
756
829
|
}): Promise<ManagedProcessResult> {
|
|
757
830
|
if (Date.now() >= options.deadlineMs) {
|
|
758
831
|
return { exitCode: null, stdout: "", stderr: "", spawnError: null, terminationReason: "timeout" };
|
|
759
832
|
}
|
|
760
833
|
|
|
834
|
+
const exactRedactionValues = allowedEnvironmentValues(options.environmentVariableNames);
|
|
835
|
+
const logCapture = new SecureProcessLogCapture(
|
|
836
|
+
[options.stdoutPath, options.stderrPath],
|
|
837
|
+
options.maxLogBytes,
|
|
838
|
+
);
|
|
761
839
|
let child: ChildProcess;
|
|
762
840
|
try {
|
|
763
|
-
|
|
841
|
+
const env = buildChildEnvironment({
|
|
842
|
+
cwd: options.cwd,
|
|
843
|
+
allowedNames: options.environmentVariableNames,
|
|
844
|
+
blockedNames: options.blockedEnvironmentVariableNames,
|
|
845
|
+
});
|
|
846
|
+
const command = resolveTrustedExecutable(options.command, options.cwd, { pathValue: env.PATH });
|
|
847
|
+
child = spawn(command, options.args, {
|
|
764
848
|
cwd: options.cwd,
|
|
765
849
|
stdio: ["ignore", "pipe", "pipe"],
|
|
766
850
|
detached: process.platform !== "win32",
|
|
767
851
|
windowsHide: true,
|
|
852
|
+
env,
|
|
768
853
|
});
|
|
769
854
|
} catch (error) {
|
|
770
855
|
return { exitCode: null, stdout: "", stderr: "", spawnError: errorMessage(error), terminationReason: null };
|
|
@@ -774,10 +859,11 @@ async function runManagedProcess(options: {
|
|
|
774
859
|
writeTaskRuntime(options.taskDir, {
|
|
775
860
|
child_pid: child.pid,
|
|
776
861
|
child_started_at: new Date().toISOString(),
|
|
862
|
+
runner_pid: process.pid,
|
|
863
|
+
runner_instance_id: options.runnerInstanceId,
|
|
864
|
+
child_owned_by_runner_instance_id: options.runnerInstanceId,
|
|
777
865
|
});
|
|
778
866
|
|
|
779
|
-
const stdoutStream = openStream(options.stdoutPath);
|
|
780
|
-
const stderrStream = openStream(options.stderrPath);
|
|
781
867
|
let stdout = "";
|
|
782
868
|
let stderr = "";
|
|
783
869
|
let spawnError: string | null = null;
|
|
@@ -836,12 +922,12 @@ async function runManagedProcess(options: {
|
|
|
836
922
|
child.stdout?.on("data", (chunk: Buffer) => {
|
|
837
923
|
const text = chunk.toString("utf-8");
|
|
838
924
|
stdout = appendBounded(stdout, text);
|
|
839
|
-
|
|
925
|
+
logCapture.append(options.stdoutPath, chunk);
|
|
840
926
|
});
|
|
841
927
|
child.stderr?.on("data", (chunk: Buffer) => {
|
|
842
928
|
const text = chunk.toString("utf-8");
|
|
843
929
|
stderr = appendBounded(stderr, text);
|
|
844
|
-
|
|
930
|
+
logCapture.append(options.stderrPath, chunk);
|
|
845
931
|
});
|
|
846
932
|
|
|
847
933
|
heartbeat();
|
|
@@ -857,12 +943,6 @@ async function runManagedProcess(options: {
|
|
|
857
943
|
};
|
|
858
944
|
childExitFinish = finish;
|
|
859
945
|
child.once("close", (code) => finish(code));
|
|
860
|
-
child.once("exit", (code) => {
|
|
861
|
-
// On Windows, "close" can lag behind "exit" when stdio pipes drain
|
|
862
|
-
// after taskkill. Resolve on exit to avoid hanging the runner, but
|
|
863
|
-
// still let "close" fire if it arrives first.
|
|
864
|
-
finish(code);
|
|
865
|
-
});
|
|
866
946
|
child.once("error", (error) => {
|
|
867
947
|
spawnError = error.message;
|
|
868
948
|
finish(null);
|
|
@@ -871,8 +951,7 @@ async function runManagedProcess(options: {
|
|
|
871
951
|
|
|
872
952
|
clearInterval(heartbeatTimer);
|
|
873
953
|
if (forceTimer) clearTimeout(forceTimer);
|
|
874
|
-
|
|
875
|
-
stderrStream?.destroy();
|
|
954
|
+
logCapture.flush(exactRedactionValues);
|
|
876
955
|
writeTaskRuntime(options.taskDir, {
|
|
877
956
|
phase: options.phase,
|
|
878
957
|
last_heartbeat_at: new Date().toISOString(),
|
|
@@ -880,8 +959,16 @@ async function runManagedProcess(options: {
|
|
|
880
959
|
runner_pid: process.pid,
|
|
881
960
|
child_pid: undefined,
|
|
882
961
|
});
|
|
962
|
+
const safeStdout = redactProcessOutput(stdout, exactRedactionValues);
|
|
963
|
+
const safeStderr = redactProcessOutput(stderr, exactRedactionValues);
|
|
883
964
|
|
|
884
|
-
return {
|
|
965
|
+
return {
|
|
966
|
+
exitCode,
|
|
967
|
+
stdout: safeStdout.length > MAX_CAPTURE_CHARS ? safeStdout.slice(-MAX_CAPTURE_CHARS) : safeStdout,
|
|
968
|
+
stderr: safeStderr.length > MAX_CAPTURE_CHARS ? safeStderr.slice(-MAX_CAPTURE_CHARS) : safeStderr,
|
|
969
|
+
spawnError,
|
|
970
|
+
terminationReason,
|
|
971
|
+
};
|
|
885
972
|
}
|
|
886
973
|
|
|
887
974
|
async function runTrustedTestCommand(
|
|
@@ -889,7 +976,8 @@ async function runTrustedTestCommand(
|
|
|
889
976
|
repoPath: string,
|
|
890
977
|
taskDir: string,
|
|
891
978
|
statusFile: string,
|
|
892
|
-
deadlineMs: number
|
|
979
|
+
deadlineMs: number,
|
|
980
|
+
runnerInstanceId: string,
|
|
893
981
|
): Promise<TestExecutionResult> {
|
|
894
982
|
const config = getConfig();
|
|
895
983
|
const trusted = guardTestCommand(testCommand, config, repoPath);
|
|
@@ -899,9 +987,9 @@ async function runTrustedTestCommand(
|
|
|
899
987
|
let command = parts[0];
|
|
900
988
|
let args = parts.slice(1);
|
|
901
989
|
if (process.platform === "win32" && /^(npm|npm\.cmd|pnpm|pnpm\.cmd)$/i.test(command)) {
|
|
902
|
-
const
|
|
903
|
-
command =
|
|
904
|
-
args = [
|
|
990
|
+
const packageManager = resolvePackageManagerInvocation(command, repoPath);
|
|
991
|
+
command = packageManager.command;
|
|
992
|
+
args = [...packageManager.argsPrefix, ...args];
|
|
905
993
|
}
|
|
906
994
|
const result = await runManagedProcess({
|
|
907
995
|
command,
|
|
@@ -912,6 +1000,7 @@ async function runTrustedTestCommand(
|
|
|
912
1000
|
phase: "running_tests",
|
|
913
1001
|
currentCommand: trusted,
|
|
914
1002
|
deadlineMs,
|
|
1003
|
+
runnerInstanceId,
|
|
915
1004
|
stdoutPath: join(taskDir, "test.stdout.log"),
|
|
916
1005
|
stderrPath: join(taskDir, "test.stderr.log"),
|
|
917
1006
|
});
|
|
@@ -1161,10 +1250,10 @@ function normalizeTimeout(value: unknown, config: ReturnType<typeof getConfig>):
|
|
|
1161
1250
|
function failBeforeExecution(taskId: string, taskDir: string, message: string, caughtError?: unknown): TaskRunResult {
|
|
1162
1251
|
// Phase 9: Preserve PatchWardenError structured info in error.log
|
|
1163
1252
|
const structuredError = caughtError ? errorPayload(caughtError) : { error: message };
|
|
1164
|
-
|
|
1253
|
+
atomicWriteJsonFileSync(join(taskDir, "error.log"), {
|
|
1165
1254
|
summary: message,
|
|
1166
1255
|
...structuredError,
|
|
1167
|
-
}
|
|
1256
|
+
});
|
|
1168
1257
|
const current = readStatus(join(taskDir, "status.json"));
|
|
1169
1258
|
const now = new Date().toISOString();
|
|
1170
1259
|
const verify: VerifyReport = {
|
|
@@ -1172,17 +1261,17 @@ function failBeforeExecution(taskId: string, taskDir: string, message: string, c
|
|
|
1172
1261
|
requested_commands: Array.isArray(current.verify_commands) ? current.verify_commands : [],
|
|
1173
1262
|
commands: [],
|
|
1174
1263
|
};
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1264
|
+
atomicWriteJsonFileSync(join(taskDir, "verify.json"), verify);
|
|
1265
|
+
atomicWriteFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`);
|
|
1266
|
+
atomicWriteFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`);
|
|
1267
|
+
atomicWriteFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n");
|
|
1268
|
+
atomicWriteFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n");
|
|
1269
|
+
atomicWriteJsonFileSync(join(taskDir, "file-stats.json"), {
|
|
1181
1270
|
task_id: taskId,
|
|
1182
1271
|
additions: 0,
|
|
1183
1272
|
deletions: 0,
|
|
1184
1273
|
files: [],
|
|
1185
|
-
}
|
|
1274
|
+
});
|
|
1186
1275
|
const result = {
|
|
1187
1276
|
task_id: taskId,
|
|
1188
1277
|
status: "failed",
|
|
@@ -1208,8 +1297,8 @@ function failBeforeExecution(taskId: string, taskDir: string, message: string, c
|
|
|
1208
1297
|
safe_followup_prompt: "Inspect result.json and error.log, correct the task metadata or configuration, and retry without widening repository scope.",
|
|
1209
1298
|
next_steps: ["Fix task metadata or configuration and retry the task."],
|
|
1210
1299
|
};
|
|
1211
|
-
|
|
1212
|
-
|
|
1300
|
+
atomicWriteJsonFileSync(join(taskDir, "result.json"), result);
|
|
1301
|
+
atomicWriteFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`);
|
|
1213
1302
|
updateStatus(taskDir, {
|
|
1214
1303
|
status: "failed",
|
|
1215
1304
|
phase: "failed",
|
|
@@ -1242,13 +1331,11 @@ function setTaskPhase(
|
|
|
1242
1331
|
|
|
1243
1332
|
function updateStatus(taskDir: string, patch: Record<string, unknown>): void {
|
|
1244
1333
|
const statusFile = join(taskDir, "status.json");
|
|
1245
|
-
|
|
1246
|
-
const next = { ...current, ...patch, updated_at: new Date().toISOString() };
|
|
1247
|
-
writeFileSync(statusFile, JSON.stringify(next, null, 2), "utf-8");
|
|
1334
|
+
updateTaskStatusFile(statusFile, patch);
|
|
1248
1335
|
}
|
|
1249
1336
|
|
|
1250
|
-
function readStatus(statusFile: string):
|
|
1251
|
-
return
|
|
1337
|
+
function readStatus(statusFile: string): TaskStatusRecord {
|
|
1338
|
+
return readTaskStatusFile(statusFile);
|
|
1252
1339
|
}
|
|
1253
1340
|
|
|
1254
1341
|
function gracefulKill(child: ChildProcess): void {
|
|
@@ -1262,10 +1349,13 @@ function forceKill(child: ChildProcess): void {
|
|
|
1262
1349
|
if (!child.pid) return;
|
|
1263
1350
|
try {
|
|
1264
1351
|
if (process.platform === "win32") {
|
|
1265
|
-
const
|
|
1352
|
+
const systemRoot = process.env.SystemRoot || process.env.WINDIR || "C:\\Windows";
|
|
1353
|
+
const taskkill = resolveTrustedExecutable(`${systemRoot}\\System32\\taskkill.exe`, process.cwd());
|
|
1354
|
+
const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
|
|
1266
1355
|
stdio: "ignore",
|
|
1267
1356
|
timeout: 5000,
|
|
1268
1357
|
windowsHide: true,
|
|
1358
|
+
env: buildChildEnvironment({ cwd: process.cwd() }),
|
|
1269
1359
|
});
|
|
1270
1360
|
if (result.status !== 0) child.kill("SIGKILL");
|
|
1271
1361
|
} else {
|
|
@@ -1276,10 +1366,6 @@ function forceKill(child: ChildProcess): void {
|
|
|
1276
1366
|
}
|
|
1277
1367
|
}
|
|
1278
1368
|
|
|
1279
|
-
function openStream(path?: string): WriteStream | null {
|
|
1280
|
-
return path ? createWriteStream(path, { flags: "a" }) : null;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
1369
|
function appendBounded(current: string, next: string): string {
|
|
1284
1370
|
const combined = current + next;
|
|
1285
1371
|
return combined.length <= MAX_CAPTURE_CHARS
|
|
@@ -1290,3 +1376,17 @@ function appendBounded(current: string, next: string): string {
|
|
|
1290
1376
|
function errorMessage(error: unknown): string {
|
|
1291
1377
|
return error instanceof Error ? error.message : String(error);
|
|
1292
1378
|
}
|
|
1379
|
+
|
|
1380
|
+
export function classifyAgentFailure(stderr: string): string | null {
|
|
1381
|
+
const normalized = stderr.toLowerCase();
|
|
1382
|
+
if (normalized.includes("insufficient balance") || normalized.includes("insufficient credits")) {
|
|
1383
|
+
return "provider_insufficient_balance";
|
|
1384
|
+
}
|
|
1385
|
+
if (normalized.includes("unauthorized") || normalized.includes("authentication failed") || normalized.includes("invalid api key")) {
|
|
1386
|
+
return "provider_authentication_failed";
|
|
1387
|
+
}
|
|
1388
|
+
if (normalized.includes("model not found") || normalized.includes("model access") || normalized.includes("permission denied")) {
|
|
1389
|
+
return "provider_model_unavailable";
|
|
1390
|
+
}
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|