patchwarden 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +194 -26
- package/README.md +160 -22
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +24 -15
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +44 -36
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +22 -0
- package/dist/config.js +98 -11
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +16 -0
- package/dist/control/middleware/auth.js +17 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +78 -0
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +287 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +12 -0
- package/dist/control/routes/process.js +487 -0
- package/dist/control/routes/sessions.d.ts +30 -0
- package/dist/control/routes/sessions.js +287 -0
- package/dist/control/routes/status.d.ts +51 -0
- package/dist/control/routes/status.js +307 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +197 -0
- package/dist/control/routes/tasks.d.ts +24 -0
- package/dist/control/routes/tasks.js +309 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +220 -0
- package/dist/control/runtime.d.ts +92 -0
- package/dist/control/runtime.js +425 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +150 -0
- package/dist/control/shared.d.ts +59 -0
- package/dist/control/shared.js +341 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2197
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +72 -37
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +614 -373
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +197 -0
- package/dist/goal/goalStatus.d.ts +8 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +221 -0
- package/dist/goal/subgoalSync.js +13 -10
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +126 -44
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +13 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +14 -6
- package/dist/runner/changeCapture.js +287 -81
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +60 -7
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +1 -1
- package/dist/runner/runTask.js +339 -287
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +52 -38
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +24 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +265 -54
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +24 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +280 -273
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +283 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +116 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +92 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +46 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +10 -14
- package/dist/tools/registry.js +47 -1651
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
- package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
- package/dist/tools/tasks/evidencePack.js +387 -0
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
- package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
- package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
- package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +48 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +1648 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +23 -0
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/desktop-app.md +175 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +168 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +82 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +97 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/examples/config.example.json +4 -2
- package/package.json +28 -11
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +447 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +39 -33
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +30 -4
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +9 -0
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
- package/scripts/generate-demo-gif.py +320 -0
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +29 -15
- package/src/assessments/assessmentStore.ts +55 -43
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +105 -11
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +35 -0
- package/src/control/middleware/static.ts +84 -0
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +329 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +505 -0
- package/src/control/routes/sessions.ts +314 -0
- package/src/control/routes/status.ts +405 -0
- package/src/control/routes/taskActions.ts +214 -0
- package/src/control/routes/tasks.ts +331 -0
- package/src/control/routes/workspace.ts +234 -0
- package/src/control/runtime.ts +508 -0
- package/src/control/server.ts +165 -0
- package/src/control/shared.ts +360 -0
- package/src/controlCenter.ts +7 -2347
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +100 -51
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +766 -492
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +263 -0
- package/src/goal/goalStatus.ts +25 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +15 -10
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +130 -44
- package/src/index.ts +7 -4
- package/src/logging.ts +20 -4
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +342 -80
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +55 -7
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +441 -292
- package/src/runner/simpleProcess.ts +68 -33
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
- package/src/runner/taskStatusStore.ts +54 -0
- package/src/runner/watch.ts +290 -57
- package/src/security/contentRedaction.ts +31 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +276 -273
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +379 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
- package/src/tools/dispatch/directDispatch.ts +168 -0
- package/src/tools/dispatch/goalDispatch.ts +128 -0
- package/src/tools/dispatch/releaseDispatch.ts +66 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +54 -1858
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- package/src/tools/tasks/evidencePack.ts +504 -0
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +58 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +204 -3
- package/ui/pages/dashboard.html +937 -79
- package/ui/pages/direct-sessions.html +609 -60
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +105 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +468 -438
- package/ui/pages/tasks.html +648 -65
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +245 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -213
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -130
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -190
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -109
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/evidencePack.js +0 -168
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -297
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -142
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -243
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -150
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/evidencePack.ts +0 -205
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
- /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
package/dist/runner/runTask.js
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { getTasksDir, getPlansDir, getConfig, resolveWorkspaceRoot } from "../config.js";
|
|
5
5
|
import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
6
6
|
import { guardTestCommand, } from "../security/commandGuard.js";
|
|
7
|
-
import { writeTaskProgress } from "
|
|
8
|
-
import { writeTaskRuntime } from "
|
|
7
|
+
import { writeTaskProgress } from "./taskProgress.js";
|
|
8
|
+
import { writeTaskRuntime } from "./taskRuntime.js";
|
|
9
|
+
import { claimPendingTask, readTaskStatusFile, updateTaskStatusFile, } from "./taskStatusStore.js";
|
|
9
10
|
import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
|
|
10
11
|
import { buildAgentInvocation, buildExecutionPrompt } from "./agentInvocation.js";
|
|
11
12
|
import { buildChangeArtifacts, captureRepoSnapshot, compareSnapshots, emptyArtifactHygiene, writeSnapshot, extractExternalDirtyFiles, findNewExternalDirtyFiles, buildArtifactManifest, groupChangedFiles, } from "./changeCapture.js";
|
|
12
13
|
import { errorPayload } from "../errors.js";
|
|
13
|
-
import {
|
|
14
|
+
import { ARTIFACT_SCHEMA_VERSION } from "../version.js";
|
|
15
|
+
import { diagnoseAndroidBuild } from "../tools/workspace/androidDoctor.js";
|
|
14
16
|
import { runPostTaskCleanup } from "./postTaskCleanup.js";
|
|
17
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
18
|
+
import { allowedEnvironmentValues, buildChildEnvironment, redactProcessOutput, resolvePackageManagerInvocation, resolveTrustedExecutable, SecureProcessLogCapture, } from "./processSecurity.js";
|
|
15
19
|
const HEARTBEAT_INTERVAL_MS = 2000;
|
|
16
20
|
const GRACEFUL_KILL_MS = 2000;
|
|
17
21
|
const MAX_CAPTURE_CHARS = 100_000;
|
|
18
22
|
const ARTIFACT_COLLECTION_TIMEOUT_MS = 60_000;
|
|
19
23
|
export async function runTask(taskId) {
|
|
24
|
+
const prepared = await prepareTask(taskId);
|
|
25
|
+
if (!("taskDir" in prepared))
|
|
26
|
+
return prepared;
|
|
27
|
+
const ctx = prepared;
|
|
28
|
+
const state = await executeAgent(ctx);
|
|
29
|
+
await runVerification(ctx, state);
|
|
30
|
+
const evidence = await collectArtifacts(ctx, state);
|
|
31
|
+
return finalizeTask(ctx, state, evidence);
|
|
32
|
+
}
|
|
33
|
+
async function prepareTask(taskId) {
|
|
20
34
|
const config = getConfig();
|
|
21
35
|
const tasksDir = getTasksDir(config);
|
|
22
36
|
const plansDir = getPlansDir(config);
|
|
@@ -26,7 +40,24 @@ export async function runTask(taskId) {
|
|
|
26
40
|
const statusFile = join(taskDir, "status.json");
|
|
27
41
|
if (!existsSync(statusFile))
|
|
28
42
|
throw new Error(`Task not found: "${taskId}"`);
|
|
29
|
-
const
|
|
43
|
+
const startedAtMs = Date.now();
|
|
44
|
+
const claim = claimPendingTask(statusFile, {
|
|
45
|
+
status: "running",
|
|
46
|
+
phase: "preparing",
|
|
47
|
+
started_at: new Date(startedAtMs).toISOString(),
|
|
48
|
+
last_heartbeat_at: new Date(startedAtMs).toISOString(),
|
|
49
|
+
current_command: null,
|
|
50
|
+
error: null,
|
|
51
|
+
});
|
|
52
|
+
if (!claim.claimed) {
|
|
53
|
+
const currentStatus = String(claim.status.status || "failed");
|
|
54
|
+
return {
|
|
55
|
+
task_id: taskId,
|
|
56
|
+
status: currentStatus,
|
|
57
|
+
error: `Task is ${currentStatus}; only pending tasks can be claimed for execution.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const initialStatus = claim.status;
|
|
30
61
|
const planId = String(initialStatus.plan_id || "");
|
|
31
62
|
const agentName = String(initialStatus.agent || "");
|
|
32
63
|
const rawRepoPath = String(initialStatus.resolved_repo_path || initialStatus.repo_path || wsRoot);
|
|
@@ -40,7 +71,6 @@ export async function runTask(taskId) {
|
|
|
40
71
|
catch (error) {
|
|
41
72
|
return failBeforeExecution(taskId, taskDir, errorMessage(error), error);
|
|
42
73
|
}
|
|
43
|
-
const startedAtMs = Date.now();
|
|
44
74
|
const deadlineMs = startedAtMs + timeoutSeconds * 1000;
|
|
45
75
|
let repoPath;
|
|
46
76
|
try {
|
|
@@ -57,11 +87,10 @@ export async function runTask(taskId) {
|
|
|
57
87
|
const message = `verification metadata validation failed: ${errorMessage(error)}`;
|
|
58
88
|
return failBeforeExecution(taskId, taskDir, message, error);
|
|
59
89
|
}
|
|
60
|
-
// ── Assessment freshness revalidation before execution ──
|
|
61
90
|
const assessmentId = String(initialStatus.assessment_id || "");
|
|
62
91
|
if (assessmentId) {
|
|
63
92
|
try {
|
|
64
|
-
const preExecSnapshot = captureRepoSnapshot(repoPath);
|
|
93
|
+
const preExecSnapshot = await captureRepoSnapshot(repoPath);
|
|
65
94
|
const validation = validateAssessmentFreshness(assessmentId, preExecSnapshot);
|
|
66
95
|
if (!validation.valid) {
|
|
67
96
|
const message = `assessment validation failed: ${validation.failure_reason}. Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.`;
|
|
@@ -73,19 +102,7 @@ export async function runTask(taskId) {
|
|
|
73
102
|
return failBeforeExecution(taskId, taskDir, message, error);
|
|
74
103
|
}
|
|
75
104
|
}
|
|
76
|
-
updateStatus(taskDir, {
|
|
77
|
-
status: "running",
|
|
78
|
-
phase: "preparing",
|
|
79
|
-
started_at: new Date(startedAtMs).toISOString(),
|
|
80
|
-
last_heartbeat_at: new Date().toISOString(),
|
|
81
|
-
timeout_seconds: timeoutSeconds,
|
|
82
|
-
current_command: null,
|
|
83
|
-
error: null,
|
|
84
|
-
});
|
|
85
|
-
// v0.7.0: record task_started_at and watcher_instance_id so diagnose_task
|
|
86
|
-
// can detect PID reuse and orphaned tasks. watcher_instance_id comes from
|
|
87
|
-
// the watcher process env; when run_task MCP tool is used directly, this
|
|
88
|
-
// field is intentionally left undefined so ownership cannot be falsely claimed.
|
|
105
|
+
updateStatus(taskDir, { timeout_seconds: timeoutSeconds });
|
|
89
106
|
writeTaskRuntime(taskDir, {
|
|
90
107
|
task_started_at: new Date(startedAtMs).toISOString(),
|
|
91
108
|
watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || undefined,
|
|
@@ -95,156 +112,160 @@ export async function runTask(taskId) {
|
|
|
95
112
|
let beforeWorkspaceSnapshot;
|
|
96
113
|
let externalDirtyBaseline = [];
|
|
97
114
|
try {
|
|
98
|
-
beforeSnapshot = captureRepoSnapshot(repoPath);
|
|
99
|
-
beforeWorkspaceSnapshot = repoPath === wsRoot ? beforeSnapshot : captureRepoSnapshot(wsRoot);
|
|
115
|
+
beforeSnapshot = await captureRepoSnapshot(repoPath);
|
|
116
|
+
beforeWorkspaceSnapshot = repoPath === wsRoot ? beforeSnapshot : await captureRepoSnapshot(wsRoot);
|
|
100
117
|
writeSnapshot(taskDir, "git-before.json", beforeSnapshot);
|
|
101
118
|
writeSnapshot(taskDir, "workspace-before.json", beforeWorkspaceSnapshot);
|
|
102
|
-
// Phase 4: Record external dirty files as baseline before task execution
|
|
103
119
|
externalDirtyBaseline = extractExternalDirtyFiles(beforeWorkspaceSnapshot, repoPath, wsRoot);
|
|
104
120
|
}
|
|
105
121
|
catch (error) {
|
|
106
122
|
const message = `Pre-task snapshot failed: ${errorMessage(error)}`;
|
|
107
123
|
return failBeforeExecution(taskId, taskDir, message, error);
|
|
108
124
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
return {
|
|
126
|
+
taskId, taskDir, statusFile, repoPath, wsRoot, config, plansDir, initialStatus,
|
|
127
|
+
planId, agentName, testCommand, changePolicy, verifyCommands, timeoutSeconds,
|
|
128
|
+
startedAtMs, deadlineMs, beforeSnapshot, beforeWorkspaceSnapshot, externalDirtyBaseline,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async function executeAgent(ctx) {
|
|
132
|
+
const state = {
|
|
133
|
+
agentResult: null,
|
|
134
|
+
testResult: skippedTest(ctx.testCommand, ctx.repoPath, "Agent did not complete successfully."),
|
|
135
|
+
verifyResults: [],
|
|
136
|
+
finalStatus: "failed",
|
|
137
|
+
finalError: null,
|
|
138
|
+
lastCaughtError: null,
|
|
139
|
+
};
|
|
115
140
|
try {
|
|
116
|
-
const planFile = resolve(plansDir, planId, "plan.md");
|
|
141
|
+
const planFile = resolve(ctx.plansDir, ctx.planId, "plan.md");
|
|
117
142
|
if (!existsSync(planFile))
|
|
118
|
-
throw new Error(`Plan not found: "${planId}". Save the plan first.`);
|
|
143
|
+
throw new Error(`Plan not found: "${ctx.planId}". Save the plan first.`);
|
|
119
144
|
const planContent = readFileSync(planFile, "utf-8");
|
|
120
|
-
const prompt = buildExecutionPrompt(planContent, repoPath, testCommand);
|
|
121
|
-
const invocation = buildAgentInvocation(agentName, repoPath, prompt, config);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
agentResult = await runManagedProcess({
|
|
145
|
+
const prompt = buildExecutionPrompt(planContent, ctx.repoPath, ctx.testCommand);
|
|
146
|
+
const invocation = buildAgentInvocation(ctx.agentName, ctx.repoPath, prompt, ctx.config);
|
|
147
|
+
setTaskPhase(ctx.taskDir, "executing_agent", invocation.commandLabel);
|
|
148
|
+
state.agentResult = await runManagedProcess({
|
|
125
149
|
command: invocation.command,
|
|
126
150
|
args: invocation.args,
|
|
127
|
-
cwd: repoPath,
|
|
128
|
-
taskDir,
|
|
129
|
-
statusFile,
|
|
151
|
+
cwd: ctx.repoPath,
|
|
152
|
+
taskDir: ctx.taskDir,
|
|
153
|
+
statusFile: ctx.statusFile,
|
|
130
154
|
phase: "executing_agent",
|
|
131
|
-
currentCommand:
|
|
132
|
-
deadlineMs,
|
|
133
|
-
stdoutPath: join(taskDir, "stdout.log"),
|
|
134
|
-
stderrPath: join(taskDir, "stderr.log"),
|
|
155
|
+
currentCommand: invocation.commandLabel,
|
|
156
|
+
deadlineMs: ctx.deadlineMs,
|
|
157
|
+
stdoutPath: join(ctx.taskDir, "stdout.log"),
|
|
158
|
+
stderrPath: join(ctx.taskDir, "stderr.log"),
|
|
159
|
+
environmentVariableNames: invocation.environmentVariableNames,
|
|
160
|
+
blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
|
|
135
161
|
});
|
|
162
|
+
const agentResult = state.agentResult;
|
|
136
163
|
if (agentResult.terminationReason === "canceled" || agentResult.terminationReason === "killed") {
|
|
137
|
-
finalStatus = "canceled";
|
|
138
|
-
finalError = agentResult.terminationReason === "killed"
|
|
164
|
+
state.finalStatus = "canceled";
|
|
165
|
+
state.finalError = agentResult.terminationReason === "killed"
|
|
139
166
|
? "Task was terminated by kill_task."
|
|
140
167
|
: "Task was canceled by user request.";
|
|
141
168
|
}
|
|
142
169
|
else if (agentResult.terminationReason === "timeout") {
|
|
143
|
-
finalError = `Task timed out after ${timeoutSeconds} seconds during agent execution.`;
|
|
170
|
+
state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during agent execution.`;
|
|
144
171
|
}
|
|
145
172
|
else if (agentResult.spawnError) {
|
|
146
|
-
finalError = `Agent spawn failed: ${agentResult.spawnError}`;
|
|
173
|
+
state.finalError = `Agent spawn failed: ${agentResult.spawnError}`;
|
|
147
174
|
}
|
|
148
175
|
else if (agentResult.exitCode !== 0) {
|
|
149
|
-
finalError = `Agent exited with code ${agentResult.exitCode}.`;
|
|
176
|
+
state.finalError = `Agent exited with code ${agentResult.exitCode}.`;
|
|
150
177
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
state.lastCaughtError = error;
|
|
181
|
+
state.finalError = errorMessage(error);
|
|
182
|
+
}
|
|
183
|
+
return state;
|
|
184
|
+
}
|
|
185
|
+
async function runVerification(ctx, state) {
|
|
186
|
+
if (state.finalError !== null)
|
|
187
|
+
return state;
|
|
188
|
+
try {
|
|
189
|
+
if (ctx.verifyCommands.length > 0) {
|
|
190
|
+
for (const command of ctx.verifyCommands) {
|
|
191
|
+
setTaskPhase(ctx.taskDir, "running_tests", command);
|
|
192
|
+
const verification = await runTrustedTestCommand(command, ctx.repoPath, ctx.taskDir, ctx.statusFile, ctx.deadlineMs);
|
|
193
|
+
state.verifyResults.push(verification);
|
|
194
|
+
if (verification.terminationReason || Date.now() >= ctx.deadlineMs)
|
|
157
195
|
break;
|
|
158
196
|
}
|
|
159
|
-
testResult = verifyResults[0] || skippedTest(testCommand, repoPath, "No verification command ran.");
|
|
160
|
-
const interrupted = verifyResults.find((result) => result.terminationReason);
|
|
161
|
-
const failedVerification = verifyResults.find((result) => result.spawnError || result.exitCode !== 0);
|
|
197
|
+
state.testResult = state.verifyResults[0] || skippedTest(ctx.testCommand, ctx.repoPath, "No verification command ran.");
|
|
198
|
+
const interrupted = state.verifyResults.find((result) => result.terminationReason);
|
|
199
|
+
const failedVerification = state.verifyResults.find((result) => result.spawnError || result.exitCode !== 0);
|
|
162
200
|
if (interrupted?.terminationReason === "canceled" || interrupted?.terminationReason === "killed") {
|
|
163
|
-
finalStatus = "canceled";
|
|
164
|
-
finalError = interrupted.terminationReason === "killed"
|
|
201
|
+
state.finalStatus = "canceled";
|
|
202
|
+
state.finalError = interrupted.terminationReason === "killed"
|
|
165
203
|
? "Task was terminated by kill_task during verification."
|
|
166
204
|
: "Task was canceled during verification.";
|
|
167
205
|
}
|
|
168
|
-
else if (interrupted?.terminationReason === "timeout" || Date.now() >= deadlineMs) {
|
|
169
|
-
finalError = `Task timed out after ${timeoutSeconds} seconds during verification.`;
|
|
206
|
+
else if (interrupted?.terminationReason === "timeout" || Date.now() >= ctx.deadlineMs) {
|
|
207
|
+
state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during verification.`;
|
|
170
208
|
}
|
|
171
209
|
else if (failedVerification) {
|
|
172
|
-
finalStatus = "failed_verification";
|
|
173
|
-
finalError = failedVerification.spawnError
|
|
174
|
-
? `Verification command
|
|
175
|
-
: `Verification command
|
|
210
|
+
state.finalStatus = "failed_verification";
|
|
211
|
+
state.finalError = failedVerification.spawnError
|
|
212
|
+
? `Verification command "${failedVerification.command}" could not start: ${failedVerification.spawnError}`
|
|
213
|
+
: `Verification command "${failedVerification.command}" exited with code ${failedVerification.exitCode}.`;
|
|
176
214
|
}
|
|
177
|
-
else if (verifyResults.length === verifyCommands.length) {
|
|
178
|
-
finalStatus = "done_by_agent";
|
|
215
|
+
else if (state.verifyResults.length === ctx.verifyCommands.length) {
|
|
216
|
+
state.finalStatus = "done_by_agent";
|
|
179
217
|
}
|
|
180
218
|
else {
|
|
181
|
-
finalError = "Verification did not complete all configured commands.";
|
|
219
|
+
state.finalError = "Verification did not complete all configured commands.";
|
|
182
220
|
}
|
|
183
221
|
}
|
|
184
222
|
else {
|
|
185
|
-
testResult = skippedTest(testCommand, repoPath, "No verification command configured.");
|
|
186
|
-
finalStatus = "done_by_agent";
|
|
223
|
+
state.testResult = skippedTest(ctx.testCommand, ctx.repoPath, "No verification command configured.");
|
|
224
|
+
state.finalStatus = "done_by_agent";
|
|
187
225
|
}
|
|
188
226
|
}
|
|
189
227
|
catch (error) {
|
|
190
|
-
lastCaughtError = error;
|
|
191
|
-
finalError = errorMessage(error);
|
|
228
|
+
state.lastCaughtError = error;
|
|
229
|
+
state.finalError = errorMessage(error);
|
|
192
230
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
199
|
-
if (finalStatus !== "canceled") {
|
|
231
|
+
return state;
|
|
232
|
+
}
|
|
233
|
+
async function collectArtifacts(ctx, state) {
|
|
234
|
+
let cleanupReport = { enabled: true, removed: [], skipped: [], source_files_touched: 0 };
|
|
235
|
+
if (state.finalStatus !== "canceled") {
|
|
200
236
|
try {
|
|
201
|
-
cleanupReport = runPostTaskCleanup(repoPath, taskDir);
|
|
202
|
-
updateStatus(taskDir, { cleanup: cleanupReport });
|
|
237
|
+
cleanupReport = runPostTaskCleanup(ctx.repoPath, ctx.taskDir);
|
|
238
|
+
updateStatus(ctx.taskDir, { cleanup: cleanupReport });
|
|
203
239
|
}
|
|
204
240
|
catch (error) {
|
|
205
241
|
cleanupReport = {
|
|
206
|
-
enabled: true,
|
|
207
|
-
removed: [],
|
|
242
|
+
enabled: true, removed: [],
|
|
208
243
|
skipped: [{ path: ".", reason: "post_task_cleanup", skip_reason: errorMessage(error) }],
|
|
209
244
|
source_files_touched: 0,
|
|
210
245
|
};
|
|
211
|
-
|
|
212
|
-
updateStatus(taskDir, { cleanup: cleanupReport });
|
|
246
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "post-task-cleanup.json"), cleanupReport);
|
|
247
|
+
updateStatus(ctx.taskDir, { cleanup: cleanupReport });
|
|
213
248
|
}
|
|
214
249
|
}
|
|
215
|
-
setTaskPhase(taskDir, "collecting_artifacts", null, "Capturing post-task state and writing reports.");
|
|
250
|
+
setTaskPhase(ctx.taskDir, "collecting_artifacts", null, "Capturing post-task state and writing reports.");
|
|
216
251
|
const artifactCollectionStartedAt = new Date().toISOString();
|
|
217
252
|
let changes;
|
|
218
253
|
let artifactStatus = "collected";
|
|
219
254
|
let artifactCollectionError = null;
|
|
220
255
|
let timeoutHandle;
|
|
256
|
+
const artifactController = new AbortController();
|
|
221
257
|
try {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}),
|
|
229
|
-
new Promise((_, reject) => {
|
|
230
|
-
timeoutHandle = setTimeout(() => reject(new Error("Artifact collection timed out")), ARTIFACT_COLLECTION_TIMEOUT_MS);
|
|
231
|
-
}),
|
|
232
|
-
]);
|
|
233
|
-
if (timeoutHandle)
|
|
234
|
-
clearTimeout(timeoutHandle);
|
|
235
|
-
changes = collectionResult;
|
|
258
|
+
timeoutHandle = setTimeout(() => {
|
|
259
|
+
artifactController.abort(new Error("Artifact collection timed out"));
|
|
260
|
+
}, ARTIFACT_COLLECTION_TIMEOUT_MS);
|
|
261
|
+
const afterSnapshot = await captureRepoSnapshot(ctx.repoPath, artifactController.signal);
|
|
262
|
+
writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
|
|
263
|
+
changes = await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot, artifactController.signal);
|
|
236
264
|
}
|
|
237
265
|
catch (error) {
|
|
238
|
-
|
|
239
|
-
clearTimeout(timeoutHandle);
|
|
240
|
-
lastCaughtError = error;
|
|
266
|
+
state.lastCaughtError = error;
|
|
241
267
|
artifactCollectionError = errorMessage(error);
|
|
242
|
-
|
|
243
|
-
artifactStatus = "timeout";
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
artifactStatus = "failed";
|
|
247
|
-
}
|
|
268
|
+
artifactStatus = artifactCollectionError.includes("timed out") ? "timeout" : "failed";
|
|
248
269
|
changes = {
|
|
249
270
|
changed_files: [],
|
|
250
271
|
diff: `(change capture failed: ${artifactCollectionError})\n`,
|
|
@@ -254,17 +275,16 @@ export async function runTask(taskId) {
|
|
|
254
275
|
additions: 0,
|
|
255
276
|
deletions: 0,
|
|
256
277
|
file_stats: [],
|
|
257
|
-
workspace_dirty_before: beforeSnapshot.workspace_dirty,
|
|
258
|
-
workspace_dirty_after: beforeSnapshot.workspace_dirty,
|
|
278
|
+
workspace_dirty_before: ctx.beforeSnapshot.workspace_dirty,
|
|
279
|
+
workspace_dirty_after: ctx.beforeSnapshot.workspace_dirty,
|
|
259
280
|
patch_mode: "hash_only",
|
|
260
281
|
unavailable_reason: `Change capture failed: ${artifactCollectionError}`,
|
|
261
282
|
artifact_hygiene: emptyArtifactHygiene(),
|
|
262
283
|
};
|
|
263
|
-
|
|
264
|
-
writeFileSync(join(taskDir, "partial_result.md"), [
|
|
284
|
+
atomicWriteFileSync(join(ctx.taskDir, "partial_result.md"), [
|
|
265
285
|
"# PatchWarden Partial Result",
|
|
266
286
|
"",
|
|
267
|
-
`Task: ${taskId}`,
|
|
287
|
+
`Task: ${ctx.taskId}`,
|
|
268
288
|
"",
|
|
269
289
|
"## Artifact Collection Status",
|
|
270
290
|
artifactStatus,
|
|
@@ -273,108 +293,121 @@ export async function runTask(taskId) {
|
|
|
273
293
|
artifactCollectionError,
|
|
274
294
|
"",
|
|
275
295
|
"## Agent Status",
|
|
276
|
-
agentResult ? `Exit code: ${agentResult.exitCode}` : "Agent did not run",
|
|
296
|
+
state.agentResult ? `Exit code: ${state.agentResult.exitCode}` : "Agent did not run",
|
|
277
297
|
"",
|
|
278
298
|
"## Verification Status",
|
|
279
|
-
verifyResults.length > 0 ? verifyResults.map((r) => `- ${r.command}: exit ${r.exitCode}`).join("\n") : "No verification ran",
|
|
299
|
+
state.verifyResults.length > 0 ? state.verifyResults.map((r) => `- ${r.command}: exit ${r.exitCode}`).join("\n") : "No verification ran",
|
|
280
300
|
"",
|
|
281
301
|
"## Note",
|
|
282
302
|
"Artifact collection did not complete. The task result may be incomplete. Review stdout.log, stderr.log, and verify.json for details.",
|
|
283
303
|
"",
|
|
284
|
-
].join("\n")
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
finalStatus = "failed";
|
|
304
|
+
].join("\n"));
|
|
305
|
+
if (state.finalStatus === "done_by_agent") {
|
|
306
|
+
state.finalError = `Change capture failed: ${artifactCollectionError}`;
|
|
307
|
+
state.finalStatus = "failed";
|
|
289
308
|
}
|
|
290
309
|
else {
|
|
291
|
-
finalError ||= `Change capture failed: ${artifactCollectionError}`;
|
|
310
|
+
state.finalError ||= `Change capture failed: ${artifactCollectionError}`;
|
|
292
311
|
}
|
|
293
312
|
}
|
|
313
|
+
finally {
|
|
314
|
+
if (timeoutHandle)
|
|
315
|
+
clearTimeout(timeoutHandle);
|
|
316
|
+
}
|
|
294
317
|
const artifactCollectionFinishedAt = new Date().toISOString();
|
|
295
318
|
let outOfScopeChanges = [];
|
|
296
|
-
|
|
319
|
+
const preexistingExternalDirty = ctx.externalDirtyBaseline;
|
|
297
320
|
let newOutOfScopeChanges = [];
|
|
298
321
|
try {
|
|
299
|
-
const afterWorkspaceSnapshot = repoPath === wsRoot ? captureRepoSnapshot(repoPath) : captureRepoSnapshot(wsRoot);
|
|
300
|
-
writeSnapshot(taskDir, "workspace-after.json", afterWorkspaceSnapshot);
|
|
301
|
-
const allExternalDirty = extractExternalDirtyFiles(afterWorkspaceSnapshot, repoPath, wsRoot);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Boolean(file.old_path && !isPathInside(resolve(wsRoot, file.old_path), repoPath)));
|
|
322
|
+
const afterWorkspaceSnapshot = ctx.repoPath === ctx.wsRoot ? await captureRepoSnapshot(ctx.repoPath) : await captureRepoSnapshot(ctx.wsRoot);
|
|
323
|
+
writeSnapshot(ctx.taskDir, "workspace-after.json", afterWorkspaceSnapshot);
|
|
324
|
+
const allExternalDirty = extractExternalDirtyFiles(afterWorkspaceSnapshot, ctx.repoPath, ctx.wsRoot);
|
|
325
|
+
newOutOfScopeChanges = findNewExternalDirtyFiles(ctx.externalDirtyBaseline, allExternalDirty);
|
|
326
|
+
outOfScopeChanges = compareSnapshots(ctx.beforeWorkspaceSnapshot, afterWorkspaceSnapshot)
|
|
327
|
+
.filter((file) => !isPathInside(resolve(ctx.wsRoot, file.path), ctx.repoPath) ||
|
|
328
|
+
Boolean(file.old_path && !isPathInside(resolve(ctx.wsRoot, file.old_path), ctx.repoPath)));
|
|
307
329
|
}
|
|
308
330
|
catch (error) {
|
|
309
|
-
finalError ||= `Workspace scope capture failed: ${errorMessage(error)}`;
|
|
310
|
-
if (finalStatus === "done_by_agent")
|
|
311
|
-
finalStatus = "failed";
|
|
331
|
+
state.finalError ||= `Workspace scope capture failed: ${errorMessage(error)}`;
|
|
332
|
+
if (state.finalStatus === "done_by_agent")
|
|
333
|
+
state.finalStatus = "failed";
|
|
312
334
|
}
|
|
313
|
-
// Phase 4: Pre-existing external dirty files are warnings, not failures
|
|
314
335
|
const preexistingWarnings = preexistingExternalDirty.length > 0
|
|
315
336
|
? [`Pre-existing external dirty files (not caused by this task): ${preexistingExternalDirty.length} file(s)`]
|
|
316
337
|
: [];
|
|
338
|
+
applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty);
|
|
339
|
+
atomicWriteFileSync(join(ctx.taskDir, "git.diff"), changes.diff);
|
|
340
|
+
atomicWriteFileSync(join(ctx.taskDir, "diff.patch"), changes.diff);
|
|
341
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "changed-files.json"), { schema_version: ARTIFACT_SCHEMA_VERSION, ...changes });
|
|
342
|
+
const artifactManifest = await buildArtifactManifest(changes.changed_files, ctx.repoPath, ctx.taskId);
|
|
343
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "artifact_manifest.json"), artifactManifest);
|
|
344
|
+
const changedFileGroups = groupChangedFiles(changes.changed_files);
|
|
345
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "file-stats.json"), {
|
|
346
|
+
task_id: ctx.taskId,
|
|
347
|
+
additions: changes.additions,
|
|
348
|
+
deletions: changes.deletions,
|
|
349
|
+
files: changes.file_stats,
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt,
|
|
353
|
+
outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport,
|
|
354
|
+
artifactManifest, changedFileGroups,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty) {
|
|
317
358
|
if (newOutOfScopeChanges.length > 0) {
|
|
318
|
-
finalStatus = "failed_scope_violation";
|
|
319
|
-
finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
|
|
320
|
-
|
|
321
|
-
task_id: taskId,
|
|
359
|
+
state.finalStatus = "failed_scope_violation";
|
|
360
|
+
state.finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
|
|
361
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "rollback-plan.json"), {
|
|
362
|
+
task_id: ctx.taskId,
|
|
322
363
|
status: "review_required",
|
|
323
364
|
automatic_rollback_performed: false,
|
|
324
365
|
warning: "Review ownership and concurrent edits before rollback. PatchWarden did not modify or restore these files.",
|
|
325
366
|
out_of_scope_changes: newOutOfScopeChanges,
|
|
326
367
|
preexisting_external_dirty_files: preexistingExternalDirty,
|
|
327
|
-
}
|
|
328
|
-
|
|
368
|
+
});
|
|
369
|
+
atomicWriteFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
|
|
329
370
|
"# Scope Violation Rollback Plan",
|
|
330
371
|
"",
|
|
331
|
-
`Task: ${taskId}`,
|
|
372
|
+
`Task: ${ctx.taskId}`,
|
|
332
373
|
"",
|
|
333
374
|
"PatchWarden did not automatically roll back any file. Review concurrent or user-owned edits before acting.",
|
|
334
375
|
"",
|
|
335
376
|
"## New out-of-scope files (caused by this task)",
|
|
336
377
|
...newOutOfScopeChanges.map((file) => `- ${file.change}: ${file.path}`),
|
|
337
378
|
"",
|
|
338
|
-
].join("\n")
|
|
379
|
+
].join("\n"));
|
|
339
380
|
}
|
|
340
|
-
else if (
|
|
341
|
-
finalStatus = "failed_policy_violation";
|
|
342
|
-
finalError =
|
|
381
|
+
else if (changes.diff_redacted) {
|
|
382
|
+
state.finalStatus = "failed_policy_violation";
|
|
383
|
+
state.finalError = "Credential-like content was detected in the task diff. PatchWarden redacted the evidence; remove the sensitive content before retrying.";
|
|
343
384
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const changedFileGroups =
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
additions: changes.additions,
|
|
354
|
-
deletions: changes.deletions,
|
|
355
|
-
files: changes.file_stats,
|
|
356
|
-
}, null, 2), "utf-8");
|
|
357
|
-
writeFileSync(join(taskDir, "test.log"), buildTestLog(testResult), "utf-8");
|
|
358
|
-
const verifyJson = buildVerifyJson(verifyCommands, verifyResults, repoPath);
|
|
359
|
-
// Phase 4: Use newOutOfScopeChanges (not outOfScopeChanges) for verify_status
|
|
360
|
-
// to stay consistent with finalStatus. Pre-existing external dirty files
|
|
361
|
-
// that didn't change during the task should NOT cause verify_status to fail.
|
|
385
|
+
else if (ctx.changePolicy === "no_changes" && changes.changed_files.length > 0) {
|
|
386
|
+
state.finalStatus = "failed_policy_violation";
|
|
387
|
+
state.finalError = `Task policy requires no repository changes, but detected ${changes.changed_files.length} change(s).`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function finalizeTask(ctx, state, evidence) {
|
|
391
|
+
const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport, artifactManifest, changedFileGroups } = evidence;
|
|
392
|
+
atomicWriteFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult));
|
|
393
|
+
const verifyJson = buildVerifyJson(ctx.verifyCommands, state.verifyResults, ctx.repoPath);
|
|
362
394
|
if (newOutOfScopeChanges.length > 0) {
|
|
363
395
|
verifyJson.status = "failed";
|
|
364
396
|
verifyJson.failure_reason = "scope_violation";
|
|
365
397
|
}
|
|
366
|
-
else if (finalStatus === "failed_policy_violation") {
|
|
398
|
+
else if (state.finalStatus === "failed_policy_violation") {
|
|
367
399
|
verifyJson.status = "failed";
|
|
368
|
-
verifyJson.failure_reason =
|
|
400
|
+
verifyJson.failure_reason = changes.diff_redacted
|
|
401
|
+
? "sensitive_content_violation"
|
|
402
|
+
: "change_policy_violation";
|
|
369
403
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(finalStatus))
|
|
373
|
-
finalStatus = "failed";
|
|
374
|
-
const finalPhase = finalStatus === "done_by_agent" ? "done_by_agent" : finalStatus;
|
|
375
|
-
const followup = buildFailureFollowup(finalStatus, finalError, verifyJson.commands);
|
|
376
|
-
|
|
377
|
-
const androidDiagnostic = diagnoseAndroidBuild(repoPath);
|
|
404
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "verify.json"), verifyJson);
|
|
405
|
+
atomicWriteFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands));
|
|
406
|
+
if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus))
|
|
407
|
+
state.finalStatus = "failed";
|
|
408
|
+
const finalPhase = state.finalStatus === "done_by_agent" ? "done_by_agent" : state.finalStatus;
|
|
409
|
+
const followup = buildFailureFollowup(state.finalStatus, state.finalError, verifyJson.commands);
|
|
410
|
+
const androidDiagnostic = diagnoseAndroidBuild(ctx.repoPath);
|
|
378
411
|
let androidWarning = null;
|
|
379
412
|
if (androidDiagnostic.status !== "skip") {
|
|
380
413
|
if (androidDiagnostic.status === "fail") {
|
|
@@ -388,13 +421,13 @@ export async function runTask(taskId) {
|
|
|
388
421
|
}
|
|
389
422
|
}
|
|
390
423
|
const resultMd = buildResultMarkdown({
|
|
391
|
-
taskId,
|
|
392
|
-
planId,
|
|
393
|
-
agent: agentName,
|
|
394
|
-
status: finalStatus,
|
|
395
|
-
error: finalError,
|
|
396
|
-
agentResult,
|
|
397
|
-
testResult,
|
|
424
|
+
taskId: ctx.taskId,
|
|
425
|
+
planId: ctx.planId,
|
|
426
|
+
agent: ctx.agentName,
|
|
427
|
+
status: state.finalStatus,
|
|
428
|
+
error: state.finalError,
|
|
429
|
+
agentResult: state.agentResult,
|
|
430
|
+
testResult: state.testResult,
|
|
398
431
|
verify: verifyJson,
|
|
399
432
|
changes,
|
|
400
433
|
outOfScopeChanges,
|
|
@@ -405,18 +438,75 @@ export async function runTask(taskId) {
|
|
|
405
438
|
androidWarning,
|
|
406
439
|
preexistingWarnings,
|
|
407
440
|
});
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
441
|
+
atomicWriteFileSync(join(ctx.taskDir, "result.md"), resultMd);
|
|
442
|
+
const resultJson = buildResultJson({
|
|
443
|
+
ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning,
|
|
444
|
+
});
|
|
445
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "result.json"), resultJson);
|
|
446
|
+
if (state.finalError) {
|
|
447
|
+
const structuredError = errorPayload(state.lastCaughtError);
|
|
448
|
+
atomicWriteJsonFileSync(join(ctx.taskDir, "error.log"), {
|
|
449
|
+
summary: state.finalError,
|
|
450
|
+
...structuredError,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const finishedAt = new Date().toISOString();
|
|
454
|
+
updateStatus(ctx.taskDir, {
|
|
455
|
+
status: state.finalStatus,
|
|
456
|
+
phase: finalPhase,
|
|
457
|
+
current_command: null,
|
|
458
|
+
last_heartbeat_at: finishedAt,
|
|
459
|
+
finished_at: finishedAt,
|
|
460
|
+
error: state.finalError,
|
|
461
|
+
changed_files: changes.changed_files.map(({ path, change }) => ({ path, change })),
|
|
462
|
+
artifact_hygiene_counts: changes.artifact_hygiene.counts,
|
|
463
|
+
artifact_status: artifactStatus,
|
|
464
|
+
artifact_collection_error: artifactCollectionError,
|
|
465
|
+
artifact_collection_started_at: artifactCollectionStartedAt,
|
|
466
|
+
artifact_collection_finished_at: artifactCollectionFinishedAt,
|
|
467
|
+
cleanup: cleanupReport,
|
|
468
|
+
out_of_scope_changes: outOfScopeChanges,
|
|
469
|
+
new_out_of_scope_changes: newOutOfScopeChanges,
|
|
470
|
+
preexisting_external_dirty_files: preexistingExternalDirty,
|
|
471
|
+
verify_status: verifyJson.status,
|
|
472
|
+
verify_commands: ctx.verifyCommands,
|
|
473
|
+
diff_available: changes.diff_available,
|
|
474
|
+
diff_truncated: changes.diff_truncated,
|
|
475
|
+
diff_redacted: changes.diff_redacted === true,
|
|
476
|
+
diff_redaction_categories: changes.diff_redaction_categories ?? [],
|
|
477
|
+
workspace_dirty_before: changes.workspace_dirty_before,
|
|
478
|
+
workspace_dirty_after: changes.workspace_dirty_after,
|
|
479
|
+
workspace_dirty: changes.workspace_dirty_after,
|
|
480
|
+
acceptance_status: state.finalStatus === "done_by_agent" ? "pending" : null,
|
|
481
|
+
});
|
|
482
|
+
writeTaskRuntime(ctx.taskDir, {
|
|
483
|
+
phase: finalPhase,
|
|
484
|
+
current_command: null,
|
|
485
|
+
last_heartbeat_at: finishedAt,
|
|
486
|
+
runner_pid: process.pid,
|
|
487
|
+
child_pid: undefined,
|
|
488
|
+
});
|
|
489
|
+
writeTaskProgress(ctx.taskDir, finalPhase, {
|
|
490
|
+
heartbeatAt: finishedAt,
|
|
491
|
+
note: state.finalError || `Task finished with status ${state.finalStatus}.`,
|
|
492
|
+
});
|
|
493
|
+
return { task_id: ctx.taskId, status: state.finalStatus, error: state.finalError };
|
|
494
|
+
}
|
|
495
|
+
function buildResultJson(input) {
|
|
496
|
+
const { ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning } = input;
|
|
497
|
+
const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, cleanupReport, artifactManifest, changedFileGroups } = evidence;
|
|
498
|
+
return {
|
|
499
|
+
schema_version: ARTIFACT_SCHEMA_VERSION,
|
|
500
|
+
task_id: ctx.taskId,
|
|
501
|
+
status: state.finalStatus,
|
|
502
|
+
agent: ctx.agentName,
|
|
503
|
+
workspace_root: ctx.wsRoot,
|
|
504
|
+
repo_path: ctx.initialStatus.repo_path,
|
|
505
|
+
resolved_repo_path: ctx.repoPath,
|
|
506
|
+
plan_source: ctx.initialStatus.plan_source || "saved",
|
|
507
|
+
template: ctx.initialStatus.template || null,
|
|
508
|
+
change_policy: ctx.changePolicy,
|
|
509
|
+
summary: state.finalError || "Agent execution and configured verification completed successfully.",
|
|
420
510
|
changed_files: changes.changed_files,
|
|
421
511
|
changed_file_groups: {
|
|
422
512
|
source_changes: changedFileGroups.source_changes.length,
|
|
@@ -454,68 +544,24 @@ export async function runTask(taskId) {
|
|
|
454
544
|
...(artifactStatus !== "collected" ? ["partial_result.md"] : []),
|
|
455
545
|
],
|
|
456
546
|
warnings: [
|
|
457
|
-
...beforeSnapshot.warnings,
|
|
547
|
+
...ctx.beforeSnapshot.warnings,
|
|
458
548
|
...(changes.diff_truncated ? ["diff.patch was truncated; changed-files.json retains file evidence."] : []),
|
|
459
|
-
...
|
|
549
|
+
...(changes.diff_redacted ? [
|
|
550
|
+
`Credential-like diff content was redacted (${(changes.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`,
|
|
551
|
+
] : []),
|
|
552
|
+
...evidence.preexistingWarnings,
|
|
460
553
|
...(androidWarning ? [androidWarning] : []),
|
|
461
554
|
],
|
|
462
|
-
errors: finalError ? [finalError] : [],
|
|
463
|
-
known_issues: finalError ? [finalError] : [],
|
|
555
|
+
errors: state.finalError ? [state.finalError] : [],
|
|
556
|
+
known_issues: state.finalError ? [state.finalError] : [],
|
|
464
557
|
failure_reason: followup.failure_reason,
|
|
465
558
|
failed_command: followup.failed_command,
|
|
466
559
|
suggested_next_action: followup.suggested_next_action,
|
|
467
560
|
safe_followup_prompt: followup.safe_followup_prompt,
|
|
468
|
-
next_steps: finalStatus === "done_by_agent"
|
|
561
|
+
next_steps: state.finalStatus === "done_by_agent"
|
|
469
562
|
? ["Review get_task_summary and audit_task before accepting the work."]
|
|
470
563
|
: ["Resolve the reported failure before accepting the work."],
|
|
471
|
-
}
|
|
472
|
-
if (finalError) {
|
|
473
|
-
// Phase 9: Preserve PatchWardenError structured info in error.log
|
|
474
|
-
const structuredError = errorPayload(lastCaughtError);
|
|
475
|
-
writeFileSync(join(taskDir, "error.log"), JSON.stringify({
|
|
476
|
-
summary: finalError,
|
|
477
|
-
...structuredError,
|
|
478
|
-
}, null, 2), "utf-8");
|
|
479
|
-
}
|
|
480
|
-
const finishedAt = new Date().toISOString();
|
|
481
|
-
updateStatus(taskDir, {
|
|
482
|
-
status: finalStatus,
|
|
483
|
-
phase: finalPhase,
|
|
484
|
-
current_command: null,
|
|
485
|
-
last_heartbeat_at: finishedAt,
|
|
486
|
-
finished_at: finishedAt,
|
|
487
|
-
error: finalError,
|
|
488
|
-
changed_files: changes.changed_files.map(({ path, change }) => ({ path, change })),
|
|
489
|
-
artifact_hygiene_counts: changes.artifact_hygiene.counts,
|
|
490
|
-
artifact_status: artifactStatus,
|
|
491
|
-
artifact_collection_error: artifactCollectionError,
|
|
492
|
-
artifact_collection_started_at: artifactCollectionStartedAt,
|
|
493
|
-
artifact_collection_finished_at: artifactCollectionFinishedAt,
|
|
494
|
-
cleanup: cleanupReport,
|
|
495
|
-
out_of_scope_changes: outOfScopeChanges,
|
|
496
|
-
new_out_of_scope_changes: newOutOfScopeChanges,
|
|
497
|
-
preexisting_external_dirty_files: preexistingExternalDirty,
|
|
498
|
-
verify_status: verifyJson.status,
|
|
499
|
-
verify_commands: verifyCommands,
|
|
500
|
-
diff_available: changes.diff_available,
|
|
501
|
-
diff_truncated: changes.diff_truncated,
|
|
502
|
-
workspace_dirty_before: changes.workspace_dirty_before,
|
|
503
|
-
workspace_dirty_after: changes.workspace_dirty_after,
|
|
504
|
-
workspace_dirty: changes.workspace_dirty_after,
|
|
505
|
-
acceptance_status: finalStatus === "done_by_agent" ? "pending" : null,
|
|
506
|
-
});
|
|
507
|
-
writeTaskRuntime(taskDir, {
|
|
508
|
-
phase: finalPhase,
|
|
509
|
-
current_command: null,
|
|
510
|
-
last_heartbeat_at: finishedAt,
|
|
511
|
-
runner_pid: process.pid,
|
|
512
|
-
child_pid: undefined,
|
|
513
|
-
});
|
|
514
|
-
writeTaskProgress(taskDir, finalPhase, {
|
|
515
|
-
heartbeatAt: finishedAt,
|
|
516
|
-
note: finalError || `Task finished with status ${finalStatus}.`,
|
|
517
|
-
});
|
|
518
|
-
return { task_id: taskId, status: finalStatus, error: finalError };
|
|
564
|
+
};
|
|
519
565
|
}
|
|
520
566
|
function buildFailureFollowup(status, error, commands) {
|
|
521
567
|
const failed = commands.find((command) => command.status !== "passed");
|
|
@@ -570,13 +616,22 @@ async function runManagedProcess(options) {
|
|
|
570
616
|
if (Date.now() >= options.deadlineMs) {
|
|
571
617
|
return { exitCode: null, stdout: "", stderr: "", spawnError: null, terminationReason: "timeout" };
|
|
572
618
|
}
|
|
619
|
+
const exactRedactionValues = allowedEnvironmentValues(options.environmentVariableNames);
|
|
620
|
+
const logCapture = new SecureProcessLogCapture([options.stdoutPath, options.stderrPath], options.maxLogBytes);
|
|
573
621
|
let child;
|
|
574
622
|
try {
|
|
575
|
-
|
|
623
|
+
const env = buildChildEnvironment({
|
|
624
|
+
cwd: options.cwd,
|
|
625
|
+
allowedNames: options.environmentVariableNames,
|
|
626
|
+
blockedNames: options.blockedEnvironmentVariableNames,
|
|
627
|
+
});
|
|
628
|
+
const command = resolveTrustedExecutable(options.command, options.cwd, { pathValue: env.PATH });
|
|
629
|
+
child = spawn(command, options.args, {
|
|
576
630
|
cwd: options.cwd,
|
|
577
631
|
stdio: ["ignore", "pipe", "pipe"],
|
|
578
632
|
detached: process.platform !== "win32",
|
|
579
633
|
windowsHide: true,
|
|
634
|
+
env,
|
|
580
635
|
});
|
|
581
636
|
}
|
|
582
637
|
catch (error) {
|
|
@@ -588,8 +643,6 @@ async function runManagedProcess(options) {
|
|
|
588
643
|
child_pid: child.pid,
|
|
589
644
|
child_started_at: new Date().toISOString(),
|
|
590
645
|
});
|
|
591
|
-
const stdoutStream = openStream(options.stdoutPath);
|
|
592
|
-
const stderrStream = openStream(options.stderrPath);
|
|
593
646
|
let stdout = "";
|
|
594
647
|
let stderr = "";
|
|
595
648
|
let spawnError = null;
|
|
@@ -643,11 +696,11 @@ async function runManagedProcess(options) {
|
|
|
643
696
|
try {
|
|
644
697
|
forceKill(child);
|
|
645
698
|
}
|
|
646
|
-
catch { }
|
|
699
|
+
catch { } // cleanup failure is safe to ignore
|
|
647
700
|
try {
|
|
648
701
|
child.kill("SIGKILL");
|
|
649
702
|
}
|
|
650
|
-
catch { }
|
|
703
|
+
catch { } // cleanup failure is safe to ignore
|
|
651
704
|
// Resolve the exit promise via the shared finish handle
|
|
652
705
|
if (childExitFinish)
|
|
653
706
|
childExitFinish(null);
|
|
@@ -656,12 +709,12 @@ async function runManagedProcess(options) {
|
|
|
656
709
|
child.stdout?.on("data", (chunk) => {
|
|
657
710
|
const text = chunk.toString("utf-8");
|
|
658
711
|
stdout = appendBounded(stdout, text);
|
|
659
|
-
|
|
712
|
+
logCapture.append(options.stdoutPath, chunk);
|
|
660
713
|
});
|
|
661
714
|
child.stderr?.on("data", (chunk) => {
|
|
662
715
|
const text = chunk.toString("utf-8");
|
|
663
716
|
stderr = appendBounded(stderr, text);
|
|
664
|
-
|
|
717
|
+
logCapture.append(options.stderrPath, chunk);
|
|
665
718
|
});
|
|
666
719
|
heartbeat();
|
|
667
720
|
const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
|
|
@@ -678,12 +731,6 @@ async function runManagedProcess(options) {
|
|
|
678
731
|
};
|
|
679
732
|
childExitFinish = finish;
|
|
680
733
|
child.once("close", (code) => finish(code));
|
|
681
|
-
child.once("exit", (code) => {
|
|
682
|
-
// On Windows, "close" can lag behind "exit" when stdio pipes drain
|
|
683
|
-
// after taskkill. Resolve on exit to avoid hanging the runner, but
|
|
684
|
-
// still let "close" fire if it arrives first.
|
|
685
|
-
finish(code);
|
|
686
|
-
});
|
|
687
734
|
child.once("error", (error) => {
|
|
688
735
|
spawnError = error.message;
|
|
689
736
|
finish(null);
|
|
@@ -692,8 +739,7 @@ async function runManagedProcess(options) {
|
|
|
692
739
|
clearInterval(heartbeatTimer);
|
|
693
740
|
if (forceTimer)
|
|
694
741
|
clearTimeout(forceTimer);
|
|
695
|
-
|
|
696
|
-
stderrStream?.destroy();
|
|
742
|
+
logCapture.flush(exactRedactionValues);
|
|
697
743
|
writeTaskRuntime(options.taskDir, {
|
|
698
744
|
phase: options.phase,
|
|
699
745
|
last_heartbeat_at: new Date().toISOString(),
|
|
@@ -701,7 +747,15 @@ async function runManagedProcess(options) {
|
|
|
701
747
|
runner_pid: process.pid,
|
|
702
748
|
child_pid: undefined,
|
|
703
749
|
});
|
|
704
|
-
|
|
750
|
+
const safeStdout = redactProcessOutput(stdout, exactRedactionValues);
|
|
751
|
+
const safeStderr = redactProcessOutput(stderr, exactRedactionValues);
|
|
752
|
+
return {
|
|
753
|
+
exitCode,
|
|
754
|
+
stdout: safeStdout.length > MAX_CAPTURE_CHARS ? safeStdout.slice(-MAX_CAPTURE_CHARS) : safeStdout,
|
|
755
|
+
stderr: safeStderr.length > MAX_CAPTURE_CHARS ? safeStderr.slice(-MAX_CAPTURE_CHARS) : safeStderr,
|
|
756
|
+
spawnError,
|
|
757
|
+
terminationReason,
|
|
758
|
+
};
|
|
705
759
|
}
|
|
706
760
|
async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile, deadlineMs) {
|
|
707
761
|
const config = getConfig();
|
|
@@ -712,9 +766,9 @@ async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile,
|
|
|
712
766
|
let command = parts[0];
|
|
713
767
|
let args = parts.slice(1);
|
|
714
768
|
if (process.platform === "win32" && /^(npm|npm\.cmd|pnpm|pnpm\.cmd)$/i.test(command)) {
|
|
715
|
-
const
|
|
716
|
-
command =
|
|
717
|
-
args = [
|
|
769
|
+
const packageManager = resolvePackageManagerInvocation(command, repoPath);
|
|
770
|
+
command = packageManager.command;
|
|
771
|
+
args = [...packageManager.argsPrefix, ...args];
|
|
718
772
|
}
|
|
719
773
|
const result = await runManagedProcess({
|
|
720
774
|
command,
|
|
@@ -939,10 +993,10 @@ function normalizeTimeout(value, config) {
|
|
|
939
993
|
function failBeforeExecution(taskId, taskDir, message, caughtError) {
|
|
940
994
|
// Phase 9: Preserve PatchWardenError structured info in error.log
|
|
941
995
|
const structuredError = caughtError ? errorPayload(caughtError) : { error: message };
|
|
942
|
-
|
|
996
|
+
atomicWriteJsonFileSync(join(taskDir, "error.log"), {
|
|
943
997
|
summary: message,
|
|
944
998
|
...structuredError,
|
|
945
|
-
}
|
|
999
|
+
});
|
|
946
1000
|
const current = readStatus(join(taskDir, "status.json"));
|
|
947
1001
|
const now = new Date().toISOString();
|
|
948
1002
|
const verify = {
|
|
@@ -950,17 +1004,17 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
|
|
|
950
1004
|
requested_commands: Array.isArray(current.verify_commands) ? current.verify_commands : [],
|
|
951
1005
|
commands: [],
|
|
952
1006
|
};
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1007
|
+
atomicWriteJsonFileSync(join(taskDir, "verify.json"), verify);
|
|
1008
|
+
atomicWriteFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`);
|
|
1009
|
+
atomicWriteFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`);
|
|
1010
|
+
atomicWriteFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n");
|
|
1011
|
+
atomicWriteFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n");
|
|
1012
|
+
atomicWriteJsonFileSync(join(taskDir, "file-stats.json"), {
|
|
959
1013
|
task_id: taskId,
|
|
960
1014
|
additions: 0,
|
|
961
1015
|
deletions: 0,
|
|
962
1016
|
files: [],
|
|
963
|
-
}
|
|
1017
|
+
});
|
|
964
1018
|
const result = {
|
|
965
1019
|
task_id: taskId,
|
|
966
1020
|
status: "failed",
|
|
@@ -986,8 +1040,8 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
|
|
|
986
1040
|
safe_followup_prompt: "Inspect result.json and error.log, correct the task metadata or configuration, and retry without widening repository scope.",
|
|
987
1041
|
next_steps: ["Fix task metadata or configuration and retry the task."],
|
|
988
1042
|
};
|
|
989
|
-
|
|
990
|
-
|
|
1043
|
+
atomicWriteJsonFileSync(join(taskDir, "result.json"), result);
|
|
1044
|
+
atomicWriteFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`);
|
|
991
1045
|
updateStatus(taskDir, {
|
|
992
1046
|
status: "failed",
|
|
993
1047
|
phase: "failed",
|
|
@@ -1013,12 +1067,10 @@ function setTaskPhase(taskDir, phase, currentCommand, note) {
|
|
|
1013
1067
|
}
|
|
1014
1068
|
function updateStatus(taskDir, patch) {
|
|
1015
1069
|
const statusFile = join(taskDir, "status.json");
|
|
1016
|
-
|
|
1017
|
-
const next = { ...current, ...patch, updated_at: new Date().toISOString() };
|
|
1018
|
-
writeFileSync(statusFile, JSON.stringify(next, null, 2), "utf-8");
|
|
1070
|
+
updateTaskStatusFile(statusFile, patch);
|
|
1019
1071
|
}
|
|
1020
1072
|
function readStatus(statusFile) {
|
|
1021
|
-
return
|
|
1073
|
+
return readTaskStatusFile(statusFile);
|
|
1022
1074
|
}
|
|
1023
1075
|
function gracefulKill(child) {
|
|
1024
1076
|
try {
|
|
@@ -1027,17 +1079,20 @@ function gracefulKill(child) {
|
|
|
1027
1079
|
else
|
|
1028
1080
|
child.kill("SIGTERM");
|
|
1029
1081
|
}
|
|
1030
|
-
catch { }
|
|
1082
|
+
catch { } // cleanup failure is safe to ignore
|
|
1031
1083
|
}
|
|
1032
1084
|
function forceKill(child) {
|
|
1033
1085
|
if (!child.pid)
|
|
1034
1086
|
return;
|
|
1035
1087
|
try {
|
|
1036
1088
|
if (process.platform === "win32") {
|
|
1037
|
-
const
|
|
1089
|
+
const systemRoot = process.env.SystemRoot || process.env.WINDIR || "C:\\Windows";
|
|
1090
|
+
const taskkill = resolveTrustedExecutable(`${systemRoot}\\System32\\taskkill.exe`, process.cwd());
|
|
1091
|
+
const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
|
|
1038
1092
|
stdio: "ignore",
|
|
1039
1093
|
timeout: 5000,
|
|
1040
1094
|
windowsHide: true,
|
|
1095
|
+
env: buildChildEnvironment({ cwd: process.cwd() }),
|
|
1041
1096
|
});
|
|
1042
1097
|
if (result.status !== 0)
|
|
1043
1098
|
child.kill("SIGKILL");
|
|
@@ -1050,12 +1105,9 @@ function forceKill(child) {
|
|
|
1050
1105
|
try {
|
|
1051
1106
|
child.kill("SIGKILL");
|
|
1052
1107
|
}
|
|
1053
|
-
catch { }
|
|
1108
|
+
catch { } // cleanup failure is safe to ignore
|
|
1054
1109
|
}
|
|
1055
1110
|
}
|
|
1056
|
-
function openStream(path) {
|
|
1057
|
-
return path ? createWriteStream(path, { flags: "a" }) : null;
|
|
1058
|
-
}
|
|
1059
1111
|
function appendBounded(current, next) {
|
|
1060
1112
|
const combined = current + next;
|
|
1061
1113
|
return combined.length <= MAX_CAPTURE_CHARS
|