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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { appendFileSync, closeSync, existsSync, fstatSync, openSync, readSync, statSync, } from "node:fs";
|
|
2
|
+
import { atomicWriteFileSync } from "./atomicFile.js";
|
|
3
|
+
import { withFileLockSync } from "./lockedJsonFile.js";
|
|
4
|
+
const TRUNCATION_MARKER = Buffer.from("[earlier content truncated]\n", "utf-8");
|
|
5
|
+
export function readTextFilePrefixSync(path, maxBytes) {
|
|
6
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
7
|
+
throw new Error("maxBytes must be a positive integer");
|
|
8
|
+
}
|
|
9
|
+
const descriptor = openSync(path, "r");
|
|
10
|
+
try {
|
|
11
|
+
const totalBytes = fstatSync(descriptor).size;
|
|
12
|
+
const length = Math.min(totalBytes, maxBytes);
|
|
13
|
+
const buffer = Buffer.alloc(length);
|
|
14
|
+
let offset = 0;
|
|
15
|
+
while (offset < length) {
|
|
16
|
+
const bytesRead = readSync(descriptor, buffer, offset, length - offset, offset);
|
|
17
|
+
if (bytesRead === 0)
|
|
18
|
+
break;
|
|
19
|
+
offset += bytesRead;
|
|
20
|
+
}
|
|
21
|
+
let content = buffer.subarray(0, offset).toString("utf-8");
|
|
22
|
+
if (totalBytes > offset && content.endsWith("\uFFFD"))
|
|
23
|
+
content = content.slice(0, -1);
|
|
24
|
+
return { content, totalBytes, truncated: totalBytes > offset };
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
closeSync(descriptor);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function readTextFileTailLinesSync(path, lines, maxBytes = 1024 * 1024) {
|
|
31
|
+
if (!existsSync(path))
|
|
32
|
+
return "";
|
|
33
|
+
const boundedLines = Number.isFinite(lines)
|
|
34
|
+
? Math.max(1, Math.min(1000, Math.trunc(lines)))
|
|
35
|
+
: 100;
|
|
36
|
+
const suffix = readFileSuffix(path, maxBytes);
|
|
37
|
+
let text = suffix.content.toString("utf-8");
|
|
38
|
+
if (suffix.truncated) {
|
|
39
|
+
const firstNewline = text.indexOf("\n");
|
|
40
|
+
if (firstNewline >= 0)
|
|
41
|
+
text = text.slice(firstNewline + 1);
|
|
42
|
+
}
|
|
43
|
+
const allLines = text.split(/\r?\n/);
|
|
44
|
+
if (allLines.at(-1) === "")
|
|
45
|
+
allLines.pop();
|
|
46
|
+
return allLines.slice(-boundedLines).join("\n");
|
|
47
|
+
}
|
|
48
|
+
export function appendBoundedTextFileSync(path, content, maxBytes = 2 * 1024 * 1024) {
|
|
49
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= TRUNCATION_MARKER.length) {
|
|
50
|
+
throw new Error("maxBytes must exceed the truncation marker size");
|
|
51
|
+
}
|
|
52
|
+
withFileLockSync(path, () => {
|
|
53
|
+
const incoming = Buffer.from(content, "utf-8");
|
|
54
|
+
const existingSize = existsSync(path) ? statSync(path).size : 0;
|
|
55
|
+
if (existingSize + incoming.length <= maxBytes) {
|
|
56
|
+
appendFileSync(path, incoming, { mode: 0o600 });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const incomingSuffix = incoming.subarray(Math.max(0, incoming.length - maxBytes + TRUNCATION_MARKER.length));
|
|
60
|
+
const existingBudget = Math.max(0, maxBytes - TRUNCATION_MARKER.length - incomingSuffix.length);
|
|
61
|
+
const existingSuffix = existingBudget > 0 && existingSize > 0
|
|
62
|
+
? readFileSuffix(path, existingBudget).content
|
|
63
|
+
: Buffer.alloc(0);
|
|
64
|
+
const output = Buffer.concat([TRUNCATION_MARKER, existingSuffix, incomingSuffix]);
|
|
65
|
+
const mode = existsSync(path) ? statSync(path).mode : 0o600;
|
|
66
|
+
atomicWriteFileSync(path, output, { mode });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function readFileSuffix(path, maxBytes) {
|
|
70
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
71
|
+
throw new Error("maxBytes must be a positive integer");
|
|
72
|
+
}
|
|
73
|
+
const descriptor = openSync(path, "r");
|
|
74
|
+
try {
|
|
75
|
+
const size = fstatSync(descriptor).size;
|
|
76
|
+
const length = Math.min(size, maxBytes);
|
|
77
|
+
const buffer = Buffer.alloc(length);
|
|
78
|
+
let offset = 0;
|
|
79
|
+
while (offset < length) {
|
|
80
|
+
const bytesRead = readSync(descriptor, buffer, offset, length - offset, size - length + offset);
|
|
81
|
+
if (bytesRead === 0)
|
|
82
|
+
break;
|
|
83
|
+
offset += bytesRead;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
content: offset === length ? buffer : buffer.subarray(0, offset),
|
|
87
|
+
truncated: size > length,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
closeSync(descriptor);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface LockedJsonMutation<T extends object, R> {
|
|
2
|
+
next?: T;
|
|
3
|
+
result: R;
|
|
4
|
+
}
|
|
5
|
+
export interface LockedJsonOptions {
|
|
6
|
+
waitMs?: number;
|
|
7
|
+
corruptLockStaleMs?: number;
|
|
8
|
+
busyError?: () => Error;
|
|
9
|
+
}
|
|
10
|
+
export declare function readJsonObjectFileSync<T extends object>(path: string): T;
|
|
11
|
+
export declare function mutateLockedJsonFileSync<T extends object, R>(path: string, mutation: (current: T) => LockedJsonMutation<T, R>, options?: LockedJsonOptions): R;
|
|
12
|
+
/** Serialize an arbitrary same-file read/modify/write sequence across processes. */
|
|
13
|
+
export declare function withFileLockSync<R>(path: string, action: () => R, options?: LockedJsonOptions): R;
|
|
14
|
+
/** Hold the same cross-process lock for the lifetime of an async operation. */
|
|
15
|
+
export declare function withFileLock<R>(path: string, action: () => Promise<R>, options?: LockedJsonOptions): Promise<R>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { atomicWriteJsonFileSync } from "./atomicFile.js";
|
|
5
|
+
const waitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
6
|
+
const lockReleaseRetryCodes = new Set(["EACCES", "EBUSY", "ENOTEMPTY", "EPERM"]);
|
|
7
|
+
const lockReleaseRetryMs = 5_000;
|
|
8
|
+
const lockReleaseRetryDelayMs = 10;
|
|
9
|
+
const deadOwnerGraceMs = 250;
|
|
10
|
+
const lockOwnerFile = "owner.json";
|
|
11
|
+
export function readJsonObjectFileSync(path) {
|
|
12
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
13
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14
|
+
throw new Error(`Expected a JSON object in ${path}`);
|
|
15
|
+
}
|
|
16
|
+
return parsed;
|
|
17
|
+
}
|
|
18
|
+
export function mutateLockedJsonFileSync(path, mutation, options = {}) {
|
|
19
|
+
return withFileLockSync(path, () => {
|
|
20
|
+
const current = readJsonObjectFileSync(path);
|
|
21
|
+
const outcome = mutation(current);
|
|
22
|
+
if (outcome.next)
|
|
23
|
+
atomicWriteJsonFileSync(path, outcome.next);
|
|
24
|
+
return outcome.result;
|
|
25
|
+
}, options);
|
|
26
|
+
}
|
|
27
|
+
/** Serialize an arbitrary same-file read/modify/write sequence across processes. */
|
|
28
|
+
export function withFileLockSync(path, action, options = {}) {
|
|
29
|
+
const lock = acquireLock(path, options);
|
|
30
|
+
try {
|
|
31
|
+
return action();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
releaseLock(lock.path, lock.owner);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Hold the same cross-process lock for the lifetime of an async operation. */
|
|
38
|
+
export async function withFileLock(path, action, options = {}) {
|
|
39
|
+
// Async callers must not use the synchronous Atomics.wait loop: a second
|
|
40
|
+
// request in the same Node event loop would otherwise block the first
|
|
41
|
+
// request from finishing its awaited mutation and releasing the lock.
|
|
42
|
+
const lock = await acquireLockAsync(path, options);
|
|
43
|
+
try {
|
|
44
|
+
return await action();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
releaseLock(lock.path, lock.owner);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function acquireLockAsync(path, options) {
|
|
51
|
+
const lockPath = `${path}.lock`;
|
|
52
|
+
const owner = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
53
|
+
const deadline = Date.now() + (options.waitMs ?? 2000);
|
|
54
|
+
while (true) {
|
|
55
|
+
if (tryCreateLock(lockPath, owner))
|
|
56
|
+
return { path: lockPath, owner };
|
|
57
|
+
if (removeStaleLock(lockPath, options.corruptLockStaleMs ?? 30_000) &&
|
|
58
|
+
tryCreateLock(lockPath, owner)) {
|
|
59
|
+
return { path: lockPath, owner };
|
|
60
|
+
}
|
|
61
|
+
if (Date.now() >= deadline) {
|
|
62
|
+
throw options.busyError?.() ?? new Error(`JSON file is busy: ${path}`);
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, lockReleaseRetryDelayMs));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function acquireLock(path, options) {
|
|
68
|
+
const lockPath = `${path}.lock`;
|
|
69
|
+
const owner = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
70
|
+
const deadline = Date.now() + (options.waitMs ?? 2000);
|
|
71
|
+
while (true) {
|
|
72
|
+
if (tryCreateLock(lockPath, owner))
|
|
73
|
+
return { path: lockPath, owner };
|
|
74
|
+
if (removeStaleLock(lockPath, options.corruptLockStaleMs ?? 30_000) &&
|
|
75
|
+
tryCreateLock(lockPath, owner)) {
|
|
76
|
+
return { path: lockPath, owner };
|
|
77
|
+
}
|
|
78
|
+
if (Date.now() >= deadline) {
|
|
79
|
+
throw options.busyError?.() ?? new Error(`JSON file is busy: ${path}`);
|
|
80
|
+
}
|
|
81
|
+
Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function tryCreateLock(lockPath, owner) {
|
|
85
|
+
try {
|
|
86
|
+
mkdirSync(lockPath, { mode: 0o700 });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const code = errorCode(error);
|
|
90
|
+
if (code === "EEXIST" || lockReleaseRetryCodes.has(code || ""))
|
|
91
|
+
return false;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(join(lockPath, lockOwnerFile), JSON.stringify({
|
|
96
|
+
owner,
|
|
97
|
+
pid: process.pid,
|
|
98
|
+
created_at: new Date().toISOString(),
|
|
99
|
+
}), { encoding: "utf-8", flag: "wx", mode: 0o600 });
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
try {
|
|
104
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
catch { /* leave fail-closed */ }
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function releaseLock(lockPath, owner) {
|
|
111
|
+
const detachDeadline = Date.now() + lockReleaseRetryMs;
|
|
112
|
+
const releasePath = `${lockPath}.release-${owner}`;
|
|
113
|
+
// Rename the owned lock directory out of the active lock path before
|
|
114
|
+
// recursive cleanup. A new contender can then create lockPath immediately,
|
|
115
|
+
// while Windows rimraf retries cannot delete that replacement lock.
|
|
116
|
+
while (true) {
|
|
117
|
+
const currentOwner = readLock(lockPath)?.owner;
|
|
118
|
+
if (currentOwner !== owner) {
|
|
119
|
+
if (!existsSync(lockPath))
|
|
120
|
+
return;
|
|
121
|
+
if (currentOwner !== undefined)
|
|
122
|
+
return;
|
|
123
|
+
if (Date.now() >= detachDeadline) {
|
|
124
|
+
throw new Error(`Unable to verify lock ownership before release: ${lockPath}`);
|
|
125
|
+
}
|
|
126
|
+
Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
renameSync(lockPath, releasePath);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const code = errorCode(error);
|
|
135
|
+
if (code === "ENOENT")
|
|
136
|
+
return;
|
|
137
|
+
if (!code || !lockReleaseRetryCodes.has(code) || Date.now() >= detachDeadline)
|
|
138
|
+
throw error;
|
|
139
|
+
Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Cleanup is no longer ownership-sensitive after the atomic detach, so give
|
|
143
|
+
// it an independent retry budget instead of consuming whatever remained
|
|
144
|
+
// from a slow antivirus-delayed rename.
|
|
145
|
+
const cleanupDeadline = Date.now() + lockReleaseRetryMs;
|
|
146
|
+
while (true) {
|
|
147
|
+
try {
|
|
148
|
+
rmSync(releasePath, { recursive: true, force: false });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const code = errorCode(error);
|
|
153
|
+
if (code === "ENOENT")
|
|
154
|
+
return;
|
|
155
|
+
if (!code || !lockReleaseRetryCodes.has(code) || Date.now() >= cleanupDeadline)
|
|
156
|
+
throw error;
|
|
157
|
+
Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function removeStaleLock(lockPath, corruptLockStaleMs) {
|
|
162
|
+
const lock = readLock(lockPath);
|
|
163
|
+
let ageMs;
|
|
164
|
+
try {
|
|
165
|
+
ageMs = Date.now() - statSync(lockPath).mtimeMs;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (errorCode(error) === "ENOENT")
|
|
169
|
+
return true;
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
const stale = typeof lock?.pid === "number"
|
|
173
|
+
? !processIsAlive(lock.pid) && ageMs >= deadOwnerGraceMs
|
|
174
|
+
: ageMs >= corruptLockStaleMs;
|
|
175
|
+
if (!stale)
|
|
176
|
+
return false;
|
|
177
|
+
// Re-read immediately before removal so one stale-lock contender cannot
|
|
178
|
+
// delete a replacement acquired after its first observation.
|
|
179
|
+
const latest = readLock(lockPath);
|
|
180
|
+
if (lock?.owner) {
|
|
181
|
+
if (latest?.owner !== lock.owner || latest.pid !== lock.pid)
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
else if (latest) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
rmSync(lockPath, { recursive: true, force: false });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (errorCode(error) === "ENOENT")
|
|
193
|
+
return true;
|
|
194
|
+
if (lockReleaseRetryCodes.has(errorCode(error) || ""))
|
|
195
|
+
return false;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function readLock(path) {
|
|
200
|
+
try {
|
|
201
|
+
const metadata = statSync(path);
|
|
202
|
+
const recordPath = metadata.isDirectory() ? join(path, lockOwnerFile) : path;
|
|
203
|
+
return readJsonObjectFileSync(recordPath);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function processIsAlive(pid) {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(pid, 0);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
// Only ESRCH proves the process does not exist. Access-denied and unknown
|
|
216
|
+
// platform errors must keep the lock fail-closed.
|
|
217
|
+
return errorCode(error) !== "ESRCH";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function errorCode(error) {
|
|
221
|
+
return error && typeof error === "object" && "code" in error
|
|
222
|
+
? String(error.code || "")
|
|
223
|
+
: null;
|
|
224
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const isWindows: boolean;
|
|
2
|
+
export declare const pathSep: string;
|
|
3
|
+
export declare const nullDevice: string;
|
|
4
|
+
export declare function isSamePath(left: string, right: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Returns true when `child` is located inside `parent` (or equals it is NOT
|
|
7
|
+
* treated as a child here — callers that need equality should check it
|
|
8
|
+
* separately). Uses `path.relative` so mixed Windows separators are normalized
|
|
9
|
+
* before comparison, unlike `String.prototype.startsWith`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isPathChildOf(child: string, parent: string): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform abstractions for PatchWarden.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes platform detection, path separators, the platform-specific null
|
|
5
|
+
* device, and a path-containment helper that works correctly on Windows where
|
|
6
|
+
* forward and backward slashes may be mixed. Use these helpers instead of
|
|
7
|
+
* hard-coded Unix assumptions so the runtime guard and change capture behave
|
|
8
|
+
* consistently across Windows and Unix hosts.
|
|
9
|
+
*/
|
|
10
|
+
import { sep, relative, isAbsolute } from "node:path";
|
|
11
|
+
export const isWindows = process.platform === "win32";
|
|
12
|
+
export const pathSep = sep;
|
|
13
|
+
export const nullDevice = isWindows ? "NUL" : "/dev/null";
|
|
14
|
+
export function isSamePath(left, right) {
|
|
15
|
+
return relative(left, right) === "";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns true when `child` is located inside `parent` (or equals it is NOT
|
|
19
|
+
* treated as a child here — callers that need equality should check it
|
|
20
|
+
* separately). Uses `path.relative` so mixed Windows separators are normalized
|
|
21
|
+
* before comparison, unlike `String.prototype.startsWith`.
|
|
22
|
+
*/
|
|
23
|
+
export function isPathChildOf(child, parent) {
|
|
24
|
+
const rel = relative(parent, child);
|
|
25
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Canonical JSON with object keys sorted recursively. */
|
|
2
|
+
export function stableJsonStringify(value) {
|
|
3
|
+
if (Array.isArray(value)) {
|
|
4
|
+
return `[${value.map(stableJsonStringify).join(",")}]`;
|
|
5
|
+
}
|
|
6
|
+
if (value !== null && typeof value === "object") {
|
|
7
|
+
const entries = Object.entries(value)
|
|
8
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
9
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJsonStringify(entry)}`).join(",")}}`;
|
|
10
|
+
}
|
|
11
|
+
return JSON.stringify(value);
|
|
12
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare const PATCHWARDEN_VERSION = "1.
|
|
2
|
-
export declare const TOOL_SCHEMA_EPOCH = "2026-07-
|
|
1
|
+
export declare const PATCHWARDEN_VERSION = "1.6.1";
|
|
2
|
+
export declare const TOOL_SCHEMA_EPOCH = "2026-07-19-v15";
|
|
3
|
+
export declare const ARTIFACT_SCHEMA_VERSION = "2026-07-19-v15";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export const PATCHWARDEN_VERSION = "1.
|
|
2
|
-
export const TOOL_SCHEMA_EPOCH = "2026-07-
|
|
1
|
+
export const PATCHWARDEN_VERSION = "1.6.1";
|
|
2
|
+
export const TOOL_SCHEMA_EPOCH = "2026-07-19-v15";
|
|
3
|
+
export const ARTIFACT_SCHEMA_VERSION = TOOL_SCHEMA_EPOCH;
|
package/dist/watcherStatus.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PatchWardenConfig } from "./config.js";
|
|
2
|
-
export type WatcherState = "healthy" | "stale" | "missing" | "unreadable";
|
|
3
|
-
export type PendingReason = "queued_waiting_for_watcher" | "queued_but_watcher_stale" | "queued_but_watcher_missing" | "queued_but_watcher_unreadable" | "agent_running" | "verification_running" | "preparing" | "collecting_artifacts" | null;
|
|
2
|
+
export type WatcherState = "healthy" | "degraded" | "stale" | "missing" | "unreadable";
|
|
3
|
+
export type PendingReason = "queued_waiting_for_watcher" | "queued_but_watcher_stale" | "queued_but_watcher_degraded" | "queued_but_watcher_missing" | "queued_but_watcher_unreadable" | "agent_running" | "verification_running" | "preparing" | "collecting_artifacts" | null;
|
|
4
4
|
export interface WatcherStatusSnapshot {
|
|
5
5
|
status: WatcherState;
|
|
6
6
|
available: boolean;
|
package/dist/watcherStatus.js
CHANGED
|
@@ -45,6 +45,22 @@ export function readWatcherStatus(config = getConfig(), nowMs = Date.now()) {
|
|
|
45
45
|
throw new Error("invalid heartbeat timestamp");
|
|
46
46
|
const ageMs = Math.max(0, nowMs - heartbeatMs);
|
|
47
47
|
const ageSeconds = Math.round(ageMs / 1000);
|
|
48
|
+
if (data.status === "degraded") {
|
|
49
|
+
return {
|
|
50
|
+
status: "degraded",
|
|
51
|
+
available: false,
|
|
52
|
+
stale_after_seconds: staleAfterSeconds,
|
|
53
|
+
last_heartbeat_at: String(data.last_heartbeat_at),
|
|
54
|
+
heartbeat_age_seconds: ageSeconds,
|
|
55
|
+
heartbeat_pid: Number.isInteger(Number(data.pid)) ? Number(data.pid) : null,
|
|
56
|
+
instance_id: typeof data.instance_id === "string" ? data.instance_id : null,
|
|
57
|
+
launcher_pid: Number.isInteger(Number(data.launcher_pid)) ? Number(data.launcher_pid) : null,
|
|
58
|
+
reason: typeof data.last_error === "string" && data.last_error
|
|
59
|
+
? `Watcher is degraded after repeated tick failures: ${data.last_error}`
|
|
60
|
+
: "Watcher is degraded after repeated tick failures. Restart the PatchWarden watcher.",
|
|
61
|
+
activity: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
48
64
|
const healthy = ageMs < staleAfterSeconds * 1000;
|
|
49
65
|
if (healthy) {
|
|
50
66
|
return {
|
|
@@ -149,6 +165,8 @@ function checkRunningTaskHeartbeat(config, nowMs, staleAfterSeconds) {
|
|
|
149
165
|
}
|
|
150
166
|
export function derivePendingReason(task, watcher) {
|
|
151
167
|
if (task.status === "pending") {
|
|
168
|
+
if (watcher.status === "degraded")
|
|
169
|
+
return "queued_but_watcher_degraded";
|
|
152
170
|
if (watcher.status === "stale")
|
|
153
171
|
return "queued_but_watcher_stale";
|
|
154
172
|
if (watcher.status === "missing")
|