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
|
@@ -2,17 +2,20 @@ import {
|
|
|
2
2
|
existsSync,
|
|
3
3
|
readdirSync,
|
|
4
4
|
readFileSync,
|
|
5
|
-
writeFileSync,
|
|
6
|
-
renameSync,
|
|
7
|
-
unlinkSync,
|
|
8
5
|
mkdirSync,
|
|
9
6
|
statSync,
|
|
10
7
|
type Dirent,
|
|
11
8
|
} from "node:fs";
|
|
12
9
|
import { join, resolve, dirname } from "node:path";
|
|
13
|
-
import { getTasksDir, getConfig, type PatchWardenConfig } from "
|
|
10
|
+
import { getTasksDir, getConfig, type PatchWardenConfig } from "../../config.js";
|
|
14
11
|
import { diagnoseTask, type DiagnosisType, type DiagnosisConfidence, type SafeAction } from "./diagnoseTask.js";
|
|
15
|
-
import { syncSubgoalOnTaskDone, readTaskGoalMeta } from "
|
|
12
|
+
import { syncSubgoalOnTaskDone, readTaskGoalMeta } from "../../goal/subgoalSync.js";
|
|
13
|
+
import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
|
|
14
|
+
import { writeTaskRuntime } from "../../runner/taskRuntime.js";
|
|
15
|
+
import type { TaskPhase } from "./createTask.js";
|
|
16
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
|
|
17
|
+
import { appendBoundedTextFileSync } from "../../utils/boundedFile.js";
|
|
18
|
+
import { isActiveTaskStatus } from "./taskStates.js";
|
|
16
19
|
|
|
17
20
|
// ── v0.7.0: reconcile_tasks types ──────────────────────────────────
|
|
18
21
|
|
|
@@ -22,6 +25,7 @@ export interface ReconcileTasksInput {
|
|
|
22
25
|
max_age_minutes?: number;
|
|
23
26
|
mode?: ReconcileMode;
|
|
24
27
|
include_done_candidates?: boolean;
|
|
28
|
+
task_ids?: string[];
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export interface ReconcileTaskReport {
|
|
@@ -33,7 +37,7 @@ export interface ReconcileTaskReport {
|
|
|
33
37
|
reasons: string[];
|
|
34
38
|
safe_actions: SafeAction[];
|
|
35
39
|
age_seconds: number | null;
|
|
36
|
-
action_taken: "left_unchanged" | "marked_failed_stale" | "marked_orphaned" | "marked_done_by_agent";
|
|
40
|
+
action_taken: "left_unchanged" | "marked_failed_stale" | "marked_orphaned" | "marked_done_by_agent" | "marked_canceled" | "marked_timed_out";
|
|
37
41
|
previous_status: string | null;
|
|
38
42
|
new_status: string | null;
|
|
39
43
|
applied_at: string | null;
|
|
@@ -75,6 +79,9 @@ export function reconcileTasks(
|
|
|
75
79
|
? Math.min(input.max_age_minutes, 24 * 60)
|
|
76
80
|
: DEFAULT_MAX_AGE_MINUTES;
|
|
77
81
|
const includeDoneCandidates = input.include_done_candidates !== false; // default true
|
|
82
|
+
const requestedTaskIds = input.task_ids?.length
|
|
83
|
+
? new Set(input.task_ids.filter((taskId) => /^task[-_][a-zA-Z0-9_-]+$/.test(taskId)))
|
|
84
|
+
: null;
|
|
78
85
|
const maxAgeSeconds = maxAgeMinutes * 60;
|
|
79
86
|
|
|
80
87
|
const tasksDir = getTasksDir(config);
|
|
@@ -118,8 +125,9 @@ export function reconcileTasks(
|
|
|
118
125
|
const taskDirs = entries.filter((e) => e.isDirectory());
|
|
119
126
|
|
|
120
127
|
for (const entry of taskDirs) {
|
|
121
|
-
scanned += 1;
|
|
122
128
|
const taskId = entry.name;
|
|
129
|
+
if (requestedTaskIds && !requestedTaskIds.has(taskId)) continue;
|
|
130
|
+
scanned += 1;
|
|
123
131
|
const taskDir = resolve(tasksDir, taskId);
|
|
124
132
|
const statusFile = join(taskDir, "status.json");
|
|
125
133
|
if (!existsSync(statusFile)) continue;
|
|
@@ -133,18 +141,17 @@ export function reconcileTasks(
|
|
|
133
141
|
|
|
134
142
|
const statusStr = typeof statusData.status === "string" ? statusData.status : "unknown";
|
|
135
143
|
|
|
136
|
-
//
|
|
144
|
+
// Scan every persisted non-terminal lifecycle spelling, including legacy
|
|
145
|
+
// phase-as-status records left by older Watchers.
|
|
137
146
|
const isCandidate =
|
|
138
|
-
statusStr
|
|
139
|
-
statusStr === "collecting_artifacts" ||
|
|
147
|
+
isActiveTaskStatus(statusStr) ||
|
|
140
148
|
(includeDoneCandidates && statusStr === "done_by_agent");
|
|
141
149
|
if (!isCandidate) continue;
|
|
142
150
|
|
|
143
|
-
//
|
|
151
|
+
// Use the oldest reliable timestamp for ordinary stale detection. Explicit
|
|
152
|
+
// cancel, persisted deadline, and Watcher-instance mismatch bypass this
|
|
153
|
+
// age gate so a restart cannot extend the task's total budget.
|
|
144
154
|
const ageSeconds = taskAgeSeconds(taskDir, statusData, nowMs);
|
|
145
|
-
if (ageSeconds !== null && ageSeconds < maxAgeSeconds) continue;
|
|
146
|
-
|
|
147
|
-
candidates += 1;
|
|
148
155
|
|
|
149
156
|
// Diagnose the task
|
|
150
157
|
let diagnosis;
|
|
@@ -153,6 +160,14 @@ export function reconcileTasks(
|
|
|
153
160
|
} catch {
|
|
154
161
|
continue; // diagnosis failed — skip
|
|
155
162
|
}
|
|
163
|
+
const cancellationRequested = statusData.cancel_requested === true || statusStr === "cancel_requested";
|
|
164
|
+
const deadlineExceeded = taskDeadlineExceeded(statusData, nowMs);
|
|
165
|
+
const urgentRecovery = cancellationRequested
|
|
166
|
+
|| deadlineExceeded
|
|
167
|
+
|| diagnosis.diagnosis === "orphaned_running";
|
|
168
|
+
if (ageSeconds !== null && ageSeconds < maxAgeSeconds && !urgentRecovery) continue;
|
|
169
|
+
|
|
170
|
+
candidates += 1;
|
|
156
171
|
|
|
157
172
|
const report: ReconcileTaskReport = {
|
|
158
173
|
task_id: taskId,
|
|
@@ -188,7 +203,9 @@ export function reconcileTasks(
|
|
|
188
203
|
//
|
|
189
204
|
// Anything else is left_unchanged and recorded for audit.
|
|
190
205
|
|
|
191
|
-
|
|
206
|
+
const deterministicRecovery = !diagnosis.evidence.watcher_owns_task
|
|
207
|
+
&& (cancellationRequested || deadlineExceeded);
|
|
208
|
+
if (mode === "safe_fix" && (diagnosis.confidence === "high" || deterministicRecovery)) {
|
|
192
209
|
if (diagnosis.evidence.watcher_owns_task) {
|
|
193
210
|
// Hard rule: do not touch tasks the live watcher still owns.
|
|
194
211
|
skippedActiveWatcher += 1;
|
|
@@ -206,10 +223,9 @@ export function reconcileTasks(
|
|
|
206
223
|
report.applied_by = "reconcile_tasks";
|
|
207
224
|
reconciled += 1;
|
|
208
225
|
} else {
|
|
209
|
-
// Diagnosis type does not map to a safe_fix action — leave unchanged
|
|
210
226
|
report.reasons = [
|
|
211
227
|
...report.reasons,
|
|
212
|
-
`safe_fix skipped:
|
|
228
|
+
`safe_fix skipped: ${fixResult.skip_reason || "task state could not be updated safely"}`,
|
|
213
229
|
];
|
|
214
230
|
skippedLowConfidence += 1;
|
|
215
231
|
}
|
|
@@ -254,8 +270,13 @@ interface SafeFixResult {
|
|
|
254
270
|
previous_status: string;
|
|
255
271
|
new_status: string;
|
|
256
272
|
applied_at: string;
|
|
273
|
+
skip_reason: string | null;
|
|
257
274
|
}
|
|
258
275
|
|
|
276
|
+
type SafeFixMutationOutcome =
|
|
277
|
+
| { applied: true }
|
|
278
|
+
| { applied: false; reason: string };
|
|
279
|
+
|
|
259
280
|
function applySafeFix(
|
|
260
281
|
taskDir: string,
|
|
261
282
|
taskId: string,
|
|
@@ -280,7 +301,14 @@ function applySafeFix(
|
|
|
280
301
|
let newStatus: string | null = null;
|
|
281
302
|
let actionTaken: ReconcileTaskReport["action_taken"] = "left_unchanged";
|
|
282
303
|
|
|
283
|
-
|
|
304
|
+
const cancellationRequested = currentStatus.cancel_requested === true || previousStatus === "cancel_requested";
|
|
305
|
+
if (!evidence.watcher_owns_task && cancellationRequested) {
|
|
306
|
+
newStatus = "canceled";
|
|
307
|
+
actionTaken = "marked_canceled";
|
|
308
|
+
} else if (!evidence.watcher_owns_task && taskDeadlineExceeded(currentStatus)) {
|
|
309
|
+
newStatus = "timeout";
|
|
310
|
+
actionTaken = "marked_timed_out";
|
|
311
|
+
} else switch (diagnosis) {
|
|
284
312
|
case "stale_running":
|
|
285
313
|
newStatus = "failed_stale";
|
|
286
314
|
actionTaken = "marked_failed_stale";
|
|
@@ -306,6 +334,7 @@ function applySafeFix(
|
|
|
306
334
|
previous_status: previousStatus,
|
|
307
335
|
new_status: previousStatus,
|
|
308
336
|
applied_at: new Date().toISOString(),
|
|
337
|
+
skip_reason: `diagnosis "${diagnosis}" has no automatic safe_fix action`,
|
|
309
338
|
};
|
|
310
339
|
}
|
|
311
340
|
|
|
@@ -313,75 +342,114 @@ function applySafeFix(
|
|
|
313
342
|
const statusFile = join(taskDir, "status.json");
|
|
314
343
|
const backupFile = join(taskDir, "status.json.bak");
|
|
315
344
|
|
|
316
|
-
//
|
|
345
|
+
// Revalidate and update under the shared task status lock. Any heartbeat or
|
|
346
|
+
// ownership-related status change after diagnosis makes this repair stale.
|
|
317
347
|
try {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
applied: false,
|
|
324
|
-
action_taken: "left_unchanged",
|
|
325
|
-
previous_status: previousStatus,
|
|
326
|
-
new_status: previousStatus,
|
|
327
|
-
applied_at: appliedAt,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
348
|
+
const outcome = mutateTaskStatus<SafeFixMutationOutcome>(statusFile, (current) => {
|
|
349
|
+
if (JSON.stringify(current) !== JSON.stringify(currentStatus)) {
|
|
350
|
+
return { result: { applied: false as const, reason: "task status changed after diagnosis" } };
|
|
351
|
+
}
|
|
330
352
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
const lockedPreviousStatus = typeof current.status === "string" ? current.status : "unknown";
|
|
354
|
+
const next: Record<string, unknown> = {
|
|
355
|
+
...current,
|
|
356
|
+
status: newStatus,
|
|
357
|
+
phase: newStatus,
|
|
358
|
+
updated_at: appliedAt,
|
|
359
|
+
last_heartbeat_at: appliedAt,
|
|
360
|
+
finished_at: appliedAt,
|
|
361
|
+
previous_status: lockedPreviousStatus,
|
|
362
|
+
diagnosis: {
|
|
363
|
+
type: diagnosis,
|
|
364
|
+
confidence: "high" as DiagnosisConfidence,
|
|
365
|
+
applied_by: "reconcile_tasks",
|
|
366
|
+
applied_at: appliedAt,
|
|
367
|
+
reasons,
|
|
368
|
+
evidence: {
|
|
369
|
+
heartbeat_age_seconds: evidence.heartbeat_age_seconds,
|
|
370
|
+
stdout_age_seconds: evidence.stdout_age_seconds,
|
|
371
|
+
child_pid: evidence.child_pid,
|
|
372
|
+
child_pid_alive: evidence.child_pid_alive,
|
|
373
|
+
watcher_instance_id: evidence.watcher_instance_id,
|
|
374
|
+
current_watcher_instance_id: evidence.current_watcher_instance_id,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
process_cleanup_required: !evidence.watcher_owns_task,
|
|
378
|
+
process_cleanup_reason: !evidence.watcher_owns_task
|
|
379
|
+
? "The previous runner is no longer owned by this Watcher; child-process termination could not be confirmed and no untrusted PID was killed."
|
|
380
|
+
: null,
|
|
381
|
+
};
|
|
357
382
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
383
|
+
if (newStatus === "canceled") {
|
|
384
|
+
next.canceled_at = appliedAt;
|
|
385
|
+
next.cancel_reason = "Cancellation converged after the original task runner stopped responding.";
|
|
386
|
+
next.termination_reason = "canceled";
|
|
387
|
+
next.error_code = "runner_lost_during_cancel";
|
|
388
|
+
} else if (actionTaken === "marked_timed_out") {
|
|
389
|
+
next.error = `Task exceeded its configured timeout of ${Number(current.timeout_seconds)} seconds after the original runner stopped responding.`;
|
|
390
|
+
next.termination_reason = "timeout";
|
|
391
|
+
next.error_code = "task_timeout";
|
|
392
|
+
} else if (diagnosis === "artifact_collection_stuck") {
|
|
393
|
+
next.error = "Artifact collection was interrupted and could not be safely resumed after the original runner stopped responding.";
|
|
394
|
+
next.error_code = "artifact_collection_interrupted";
|
|
395
|
+
} else if (diagnosis === "orphaned_running") {
|
|
396
|
+
next.error = "The original task runner was lost and its child process ownership could not be re-established.";
|
|
397
|
+
next.error_code = "agent_lost";
|
|
398
|
+
} else if (diagnosis === "stale_running") {
|
|
399
|
+
next.error = "The task runner heartbeat expired and no current Runner ownership could be proven.";
|
|
400
|
+
next.error_code = "agent_lost";
|
|
401
|
+
}
|
|
363
402
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
writeFileSync(tmpFile, JSON.stringify(next, null, 2), "utf-8");
|
|
368
|
-
renameSync(tmpFile, statusFile);
|
|
369
|
-
} catch {
|
|
370
|
-
// Best-effort cleanup of temp file
|
|
371
|
-
try {
|
|
372
|
-
if (existsSync(tmpFile)) {
|
|
373
|
-
unlinkSync(tmpFile);
|
|
403
|
+
if (newStatus === "done_by_agent") {
|
|
404
|
+
next.acceptance_status = "pending";
|
|
405
|
+
next.legacy_status = "done";
|
|
374
406
|
}
|
|
375
|
-
|
|
407
|
+
|
|
408
|
+
// The backup is created while the same lock protects the exact record
|
|
409
|
+
// being replaced, so it cannot capture an unrelated newer state.
|
|
410
|
+
if (!existsSync(backupFile)) atomicWriteFileSync(backupFile, readFileSync(statusFile, "utf-8"));
|
|
411
|
+
return {
|
|
412
|
+
next,
|
|
413
|
+
result: { applied: true as const },
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
if (!outcome.applied) {
|
|
417
|
+
return {
|
|
418
|
+
applied: false,
|
|
419
|
+
action_taken: "left_unchanged",
|
|
420
|
+
previous_status: previousStatus,
|
|
421
|
+
new_status: previousStatus,
|
|
422
|
+
applied_at: appliedAt,
|
|
423
|
+
skip_reason: outcome.reason,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
376
427
|
return {
|
|
377
428
|
applied: false,
|
|
378
429
|
action_taken: "left_unchanged",
|
|
379
430
|
previous_status: previousStatus,
|
|
380
431
|
new_status: previousStatus,
|
|
381
432
|
applied_at: appliedAt,
|
|
433
|
+
skip_reason: "task status could not be locked, backed up, and updated",
|
|
382
434
|
};
|
|
383
435
|
}
|
|
384
436
|
|
|
437
|
+
if (newStatus !== "done_by_agent") {
|
|
438
|
+
writeRecoveryArtifacts(taskDir, taskId, currentStatus, newStatus, actionTaken, appliedAt);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Keep the persisted runtime view terminal as well. get_task_status merges
|
|
442
|
+
// runtime.json over status.json, so leaving an old executing phase here
|
|
443
|
+
// would make a successfully reconciled task appear active again.
|
|
444
|
+
writeTaskRuntime(taskDir, {
|
|
445
|
+
phase: newStatus as TaskPhase,
|
|
446
|
+
current_command: null,
|
|
447
|
+
last_heartbeat_at: appliedAt,
|
|
448
|
+
child_pid: undefined,
|
|
449
|
+
child_started_at: undefined,
|
|
450
|
+
child_owned_by_runner_instance_id: undefined,
|
|
451
|
+
});
|
|
452
|
+
|
|
385
453
|
// v0.8.0: 当状态变为 done_by_agent 时,同步关联 subgoal 状态(running → done_by_agent)
|
|
386
454
|
if (newStatus === "done_by_agent") {
|
|
387
455
|
const goalMeta = readTaskGoalMeta(taskDir);
|
|
@@ -396,9 +464,95 @@ function applySafeFix(
|
|
|
396
464
|
previous_status: previousStatus,
|
|
397
465
|
new_status: newStatus,
|
|
398
466
|
applied_at: appliedAt,
|
|
467
|
+
skip_reason: null,
|
|
399
468
|
};
|
|
400
469
|
}
|
|
401
470
|
|
|
471
|
+
function writeRecoveryArtifacts(
|
|
472
|
+
taskDir: string,
|
|
473
|
+
taskId: string,
|
|
474
|
+
status: Record<string, unknown>,
|
|
475
|
+
newStatus: string,
|
|
476
|
+
actionTaken: ReconcileTaskReport["action_taken"],
|
|
477
|
+
recoveredAt: string,
|
|
478
|
+
): void {
|
|
479
|
+
const timeoutSeconds = Number(status.timeout_seconds);
|
|
480
|
+
const summary = actionTaken === "marked_timed_out"
|
|
481
|
+
? `Task exceeded its configured timeout of ${timeoutSeconds} seconds after the original runner stopped responding.`
|
|
482
|
+
: actionTaken === "marked_canceled"
|
|
483
|
+
? "Cancellation converged after the original task runner stopped responding."
|
|
484
|
+
: `Task was reconciled to ${newStatus} after the original runner stopped responding.`;
|
|
485
|
+
const errorCode = actionTaken === "marked_timed_out"
|
|
486
|
+
? "task_timeout"
|
|
487
|
+
: actionTaken === "marked_canceled"
|
|
488
|
+
? "runner_lost_during_cancel"
|
|
489
|
+
: newStatus === "orphaned"
|
|
490
|
+
? "agent_lost"
|
|
491
|
+
: newStatus === "failed_stale" && String(status.phase || status.status) === "collecting_artifacts"
|
|
492
|
+
? "artifact_collection_interrupted"
|
|
493
|
+
: "agent_lost";
|
|
494
|
+
const requestedCommands = Array.isArray(status.verify_commands)
|
|
495
|
+
? status.verify_commands.map(String)
|
|
496
|
+
: [];
|
|
497
|
+
const artifacts = {
|
|
498
|
+
"verify.json": {
|
|
499
|
+
status: "failed",
|
|
500
|
+
requested_commands: requestedCommands,
|
|
501
|
+
commands: [],
|
|
502
|
+
failure_reason: "runner_recovery",
|
|
503
|
+
error_code: errorCode,
|
|
504
|
+
},
|
|
505
|
+
"result.json": {
|
|
506
|
+
task_id: taskId,
|
|
507
|
+
status: newStatus,
|
|
508
|
+
agent: status.agent || "",
|
|
509
|
+
repo_path: status.repo_path || "",
|
|
510
|
+
resolved_repo_path: status.resolved_repo_path || "",
|
|
511
|
+
summary,
|
|
512
|
+
artifact_status: "partial",
|
|
513
|
+
changed_files: [],
|
|
514
|
+
diff_available: false,
|
|
515
|
+
verify_status: "failed",
|
|
516
|
+
failure_reason: "runner_recovery",
|
|
517
|
+
error_code: errorCode,
|
|
518
|
+
termination_reason: actionTaken === "marked_timed_out"
|
|
519
|
+
? "timeout"
|
|
520
|
+
: actionTaken === "marked_canceled"
|
|
521
|
+
? "canceled"
|
|
522
|
+
: "runner_stale",
|
|
523
|
+
recovered_at: recoveredAt,
|
|
524
|
+
warnings: ["The original runner stopped before complete change and verification evidence could be collected."],
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
for (const [name, value] of Object.entries(artifacts)) {
|
|
528
|
+
const path = join(taskDir, name);
|
|
529
|
+
if (!existsSync(path)) atomicWriteJsonFileSync(path, value);
|
|
530
|
+
}
|
|
531
|
+
const textArtifacts: Record<string, string> = {
|
|
532
|
+
"result.md": `# PatchWarden Task Result\n\n## Status\n${newStatus}\n\n## Summary\n${summary}\n\n## Artifact Status\npartial\n`,
|
|
533
|
+
"test.log": `(not run)\nExit code: not run\n${summary}\n`,
|
|
534
|
+
"git.diff": `(unavailable: ${summary})\n`,
|
|
535
|
+
"diff.patch": `(unavailable: ${summary})\n`,
|
|
536
|
+
};
|
|
537
|
+
for (const [name, content] of Object.entries(textArtifacts)) {
|
|
538
|
+
const path = join(taskDir, name);
|
|
539
|
+
if (!existsSync(path)) atomicWriteFileSync(path, content);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function taskDeadlineExceeded(status: Record<string, unknown>, nowMs = Date.now()): boolean {
|
|
544
|
+
const timeoutSeconds = Number(status.timeout_seconds);
|
|
545
|
+
const startedAt = typeof status.started_at === "string"
|
|
546
|
+
? Date.parse(status.started_at)
|
|
547
|
+
: typeof status.created_at === "string"
|
|
548
|
+
? Date.parse(status.created_at)
|
|
549
|
+
: NaN;
|
|
550
|
+
return Number.isFinite(startedAt)
|
|
551
|
+
&& Number.isFinite(timeoutSeconds)
|
|
552
|
+
&& timeoutSeconds > 0
|
|
553
|
+
&& nowMs >= startedAt + timeoutSeconds * 1000;
|
|
554
|
+
}
|
|
555
|
+
|
|
402
556
|
// ── reconcile.log writer ───────────────────────────────────────────
|
|
403
557
|
|
|
404
558
|
function writeReconcileLog(
|
|
@@ -428,11 +582,8 @@ function writeReconcileLog(
|
|
|
428
582
|
evidence: report.evidence_summary,
|
|
429
583
|
}));
|
|
430
584
|
}
|
|
431
|
-
// Append — do not overwrite history
|
|
432
|
-
const existing = existsSync(logPath) ? readFileSync(logPath, "utf-8") : "";
|
|
433
|
-
const nextContent = existing + (existing && !existing.endsWith("\n") ? "\n" : "") + lines.join("\n") + "\n";
|
|
434
585
|
try {
|
|
435
|
-
|
|
586
|
+
appendBoundedTextFileSync(logPath, lines.join("\n") + "\n");
|
|
436
587
|
} catch {
|
|
437
588
|
// If we cannot write the log, the status change still happened —
|
|
438
589
|
// the status.json itself contains the diagnosis audit fields.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getTasksDir, getConfig } from "../../config.js";
|
|
4
|
+
import { guardReadPath } from "../../security/pathGuard.js";
|
|
5
|
+
import { createTask } from "./createTask.js";
|
|
6
|
+
import type { ChangePolicy } from "../taskTemplates.js";
|
|
7
|
+
|
|
8
|
+
export async function retryTask(taskId: string) {
|
|
9
|
+
const config = getConfig();
|
|
10
|
+
const tasksDir = getTasksDir(config);
|
|
11
|
+
const taskDir = join(tasksDir, taskId);
|
|
12
|
+
const statusFile = join(taskDir, "status.json");
|
|
13
|
+
|
|
14
|
+
guardReadPath(statusFile, config.workspaceRoot, config.tasksDir);
|
|
15
|
+
|
|
16
|
+
if (!existsSync(statusFile)) {
|
|
17
|
+
throw new Error(`Task not found: "${taskId}"`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const parsed: unknown = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
21
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22
|
+
throw new Error(`Task "${taskId}" has an invalid status record.`);
|
|
23
|
+
}
|
|
24
|
+
const data = parsed as Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
if (typeof data.plan_id !== "string" || !data.plan_id ||
|
|
27
|
+
typeof data.agent !== "string" || !data.agent) {
|
|
28
|
+
throw new Error(`Task "${taskId}" is missing plan_id or agent. Cannot retry.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const priorRetryCount = typeof data.retry_count === "number" &&
|
|
32
|
+
Number.isSafeInteger(data.retry_count) && data.retry_count >= 0
|
|
33
|
+
? data.retry_count
|
|
34
|
+
: 0;
|
|
35
|
+
const planSource = data.plan_source === "inline" || data.plan_source === "template"
|
|
36
|
+
? data.plan_source
|
|
37
|
+
: "saved";
|
|
38
|
+
const changePolicy: ChangePolicy = data.change_policy === "no_changes"
|
|
39
|
+
? "no_changes"
|
|
40
|
+
: "repo_scoped_changes";
|
|
41
|
+
|
|
42
|
+
// Create a new task with the same parameters
|
|
43
|
+
const newTask = await createTask({
|
|
44
|
+
plan_id: data.plan_id,
|
|
45
|
+
agent: data.agent,
|
|
46
|
+
repo_path: typeof data.repo_path === "string" ? data.repo_path : undefined,
|
|
47
|
+
test_command: typeof data.test_command === "string" ? data.test_command : undefined,
|
|
48
|
+
verify_commands: Array.isArray(data.verify_commands)
|
|
49
|
+
? data.verify_commands.filter((value): value is string => typeof value === "string")
|
|
50
|
+
: undefined,
|
|
51
|
+
timeout_seconds: typeof data.timeout_seconds === "number" ? data.timeout_seconds : undefined,
|
|
52
|
+
retry_metadata: {
|
|
53
|
+
retry_of: taskId,
|
|
54
|
+
retry_count: priorRetryCount + 1,
|
|
55
|
+
plan_source: planSource,
|
|
56
|
+
template: typeof data.template === "string" ? data.template : null,
|
|
57
|
+
change_policy: changePolicy,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
original_task_id: taskId,
|
|
63
|
+
new_task_id: newTask.task_id,
|
|
64
|
+
plan_id: newTask.plan_id,
|
|
65
|
+
agent: newTask.agent,
|
|
66
|
+
status: newTask.status,
|
|
67
|
+
message: `New task created from retry of ${taskId}. Original task is unchanged.`,
|
|
68
|
+
};
|
|
69
|
+
}
|