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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { getTaskFile } from "./getTaskFile.js";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { getConfig, getTasksDir } from "
|
|
5
|
-
import { guardPath } from "
|
|
6
|
-
import { redactSensitiveContent } from "
|
|
4
|
+
import { getConfig, getTasksDir } from "../../config.js";
|
|
5
|
+
import { guardPath } from "../../security/pathGuard.js";
|
|
6
|
+
import { redactSensitiveContent } from "../../security/contentRedaction.js";
|
|
7
|
+
import { readTextFilePrefixSync, readTextFileTailLinesSync, } from "../../utils/boundedFile.js";
|
|
7
8
|
export function getResult(taskId) {
|
|
8
9
|
return getTaskFile(taskId, "result.md");
|
|
9
10
|
}
|
|
@@ -20,10 +21,9 @@ export function getDiff(taskId) {
|
|
|
20
21
|
}
|
|
21
22
|
const filePath = existsSync(preferred) ? preferred : fallback;
|
|
22
23
|
guardPath(filePath, config.workspaceRoot, config.tasksDir);
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const patchHead =
|
|
26
|
-
const rawReturnedBytes = Buffer.byteLength(patchHead, "utf-8");
|
|
24
|
+
const bounded = readTextFilePrefixSync(filePath, config.maxReadFileBytes);
|
|
25
|
+
const size = bounded.totalBytes;
|
|
26
|
+
const patchHead = bounded.content;
|
|
27
27
|
const redaction = redactSensitiveContent(patchHead);
|
|
28
28
|
const content = redaction.content;
|
|
29
29
|
const diff = {
|
|
@@ -32,7 +32,7 @@ export function getDiff(taskId) {
|
|
|
32
32
|
content,
|
|
33
33
|
path: filePath,
|
|
34
34
|
available: true,
|
|
35
|
-
truncated:
|
|
35
|
+
truncated: bounded.truncated,
|
|
36
36
|
total_bytes: size,
|
|
37
37
|
returned_bytes: Buffer.byteLength(content, "utf-8"),
|
|
38
38
|
patch_head: content,
|
|
@@ -90,8 +90,7 @@ export function getTaskLogTail(taskId, file, options) {
|
|
|
90
90
|
}
|
|
91
91
|
const stat = statSync(filePath);
|
|
92
92
|
const totalBytes = stat.size;
|
|
93
|
-
const
|
|
94
|
-
const tail = raw.split("\n").slice(-maxLines).join("\n");
|
|
93
|
+
const tail = readTextFileTailLinesSync(filePath, maxLines);
|
|
95
94
|
const content = applyRedact ? redactSensitiveContent(tail) : { content: tail, redacted: false, redaction_categories: [] };
|
|
96
95
|
return {
|
|
97
96
|
task_id: taskId,
|
|
@@ -115,13 +114,15 @@ function getTaskFileTail(taskId, filename, options) {
|
|
|
115
114
|
return getTaskFile(taskId, filename);
|
|
116
115
|
}
|
|
117
116
|
const stat = statSync(filePath);
|
|
118
|
-
const
|
|
119
|
-
|
|
117
|
+
const maxBytes = options.maxBytes > 0
|
|
118
|
+
? Math.min(options.maxBytes, config.maxReadFileBytes)
|
|
119
|
+
: config.maxReadFileBytes;
|
|
120
|
+
let content;
|
|
120
121
|
if (options.tailLines > 0) {
|
|
121
|
-
content =
|
|
122
|
+
content = readTextFileTailLinesSync(filePath, Math.min(Math.max(1, options.tailLines), 1000), maxBytes);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
content =
|
|
124
|
+
else {
|
|
125
|
+
content = readTextFilePrefixSync(filePath, maxBytes).content;
|
|
125
126
|
}
|
|
126
127
|
const redaction = redactSensitiveContent(content);
|
|
127
128
|
return {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* and never installs global dependencies. It only runs `java -version` (read-only)
|
|
8
8
|
* and inspects files/directories/env vars that already exist.
|
|
9
9
|
*/
|
|
10
|
-
import { execSync } from "node:child_process";
|
|
11
10
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
12
11
|
import { join } from "node:path";
|
|
12
|
+
import { runSimpleProcessSync } from "../../runner/simpleProcess.js";
|
|
13
13
|
// ── Constants ──────────────────────────────────────────────────────
|
|
14
14
|
/**
|
|
15
15
|
* Required explanatory sentence emitted whenever the Android SDK is missing.
|
|
@@ -18,17 +18,23 @@ import { join } from "node:path";
|
|
|
18
18
|
const SDK_MISSING_REASON = "Android project exists, APK not built because Android SDK is missing.";
|
|
19
19
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
20
20
|
/**
|
|
21
|
-
* Run a shell command and return its trimmed combined stdout/stderr output.
|
|
21
|
+
* Run a shell-free command and return its trimmed combined stdout/stderr output.
|
|
22
22
|
* Returns an empty string when the command is missing or fails so callers can
|
|
23
23
|
* treat absence gracefully. A timeout guards against hanging processes.
|
|
24
24
|
*/
|
|
25
|
-
function runCmd(
|
|
25
|
+
function runCmd(command, args, cwd = process.cwd()) {
|
|
26
26
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const result = runSimpleProcessSync({
|
|
28
|
+
command,
|
|
29
|
+
args: [...args],
|
|
30
|
+
cwd,
|
|
31
|
+
timeoutMs: 8000,
|
|
32
|
+
maxStdoutBytes: 64 * 1024,
|
|
33
|
+
maxStderrBytes: 64 * 1024,
|
|
34
|
+
});
|
|
35
|
+
if (result.exitCode !== 0)
|
|
36
|
+
return "";
|
|
37
|
+
return `${result.stdout}\n${result.stderr}`.trim();
|
|
32
38
|
}
|
|
33
39
|
catch {
|
|
34
40
|
return "";
|
|
@@ -95,8 +101,8 @@ export function diagnoseAndroidBuild(repoPath) {
|
|
|
95
101
|
const sdkRoot = androidHomeEnv || androidSdkRootEnv;
|
|
96
102
|
const sdkExists = sdkRoot !== "" && isDirectory(sdkRoot);
|
|
97
103
|
// 1. java -version ------------------------------------------------------
|
|
98
|
-
// `java -version` writes to stderr;
|
|
99
|
-
const javaOutput = runCmd("java -version
|
|
104
|
+
// `java -version` normally writes to stderr; runSimpleProcessSync captures both.
|
|
105
|
+
const javaOutput = runCmd("java", ["-version"], repoPath);
|
|
100
106
|
const javaVersion = parseJavaVersion(javaOutput);
|
|
101
107
|
if (javaVersion) {
|
|
102
108
|
const major = parseJavaMajor(javaVersion);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getConfig } from "../../config.js";
|
|
2
|
+
import { PatchWardenError } from "../../errors.js";
|
|
3
|
+
import { readDirectSession, appendDirectSessionOperation, withDirectSessionMutationLock, } from "../../direct/directSessionStore.js";
|
|
4
|
+
import { guardDirectSessionActive, guardDirectWritePath, guardDirectPatchSize, } from "../../direct/directGuards.js";
|
|
5
|
+
import { applyPatchOperations, computeFileSha256, } from "../../direct/directPatch.js";
|
|
6
|
+
export function applyPatch(input) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
return withDirectSessionMutationLock(input.session_id, () => {
|
|
9
|
+
// 1. Read session and guard active (not expired, not finalized)
|
|
10
|
+
const session = readDirectSession(input.session_id);
|
|
11
|
+
guardDirectSessionActive(session);
|
|
12
|
+
// 2. Guard write path (inside repo, not sensitive, not binary,
|
|
13
|
+
// not node_modules/release/dist)
|
|
14
|
+
const resolvedPath = guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot);
|
|
15
|
+
// 3. Calculate patch size and guard against directMaxPatchBytes
|
|
16
|
+
const patchBytes = Buffer.byteLength(JSON.stringify(input.operations), "utf-8");
|
|
17
|
+
guardDirectPatchSize(patchBytes);
|
|
18
|
+
// 4. Apply against the same bytes whose hash is validated. Re-resolve the
|
|
19
|
+
// target immediately before replacement to detect link/reparse-point swaps.
|
|
20
|
+
const patchResult = applyPatchOperations(resolvedPath, input.operations, {
|
|
21
|
+
expectedSha256: input.expected_sha256,
|
|
22
|
+
maxFileBytes: config.directMaxFileBytes,
|
|
23
|
+
revalidatePath: () => guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot),
|
|
24
|
+
});
|
|
25
|
+
const beforeSha256 = patchResult.before_sha256;
|
|
26
|
+
// Re-read the file from disk to compute the authoritative after_sha256
|
|
27
|
+
const finalPath = guardDirectWritePath(input.path, session.resolved_repo_path, config.workspaceRoot);
|
|
28
|
+
const afterSha256 = computeFileSha256(finalPath);
|
|
29
|
+
if (afterSha256 !== patchResult.after_sha256) {
|
|
30
|
+
throw new PatchWardenError("direct_target_changed_after_write", `Patch target "${input.path}" changed before the operation could be recorded.`, "Inspect the file and retry only after concurrent filesystem changes stop.", true, { path: input.path, expected_sha256: patchResult.after_sha256, actual_sha256: afterSha256 });
|
|
31
|
+
}
|
|
32
|
+
// 6. Append operation record to session
|
|
33
|
+
appendDirectSessionOperation(input.session_id, {
|
|
34
|
+
index: 0,
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
path: input.path,
|
|
37
|
+
operation_type: "patch",
|
|
38
|
+
before_sha256: beforeSha256,
|
|
39
|
+
after_sha256: afterSha256,
|
|
40
|
+
operations_applied: patchResult.operations_applied,
|
|
41
|
+
bytes_changed: patchResult.bytes_changed,
|
|
42
|
+
});
|
|
43
|
+
// 7. Return result
|
|
44
|
+
return {
|
|
45
|
+
path: input.path,
|
|
46
|
+
before_sha256: beforeSha256,
|
|
47
|
+
after_sha256: afterSha256,
|
|
48
|
+
operations_applied: patchResult.operations_applied,
|
|
49
|
+
bytes_changed: patchResult.bytes_changed,
|
|
50
|
+
next_action: "Call run_verification to test the changes, or apply_patch for more edits.",
|
|
51
|
+
};
|
|
52
|
+
}, config);
|
|
53
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 参考 src/tools/auditTask.ts 的导出/返回风格。
|
|
6
6
|
*/
|
|
7
|
-
import { getConfig } from "
|
|
8
|
-
import { guardWorkspacePath } from "
|
|
9
|
-
import { discardWorktree } from "
|
|
7
|
+
import { getConfig } from "../../config.js";
|
|
8
|
+
import { guardWorkspacePath } from "../../security/pathGuard.js";
|
|
9
|
+
import { discardWorktree } from "../../goal/worktreeManager.js";
|
|
10
10
|
/**
|
|
11
11
|
* 丢弃指定 worktree:移除 worktree 目录、删除临时 branch、归档状态。
|
|
12
12
|
*
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./androidDoctor.js";
|
|
2
|
+
export * from "./applyPatch.js";
|
|
3
|
+
export * from "./discardWorktree.js";
|
|
4
|
+
export * from "./listAgents.js";
|
|
5
|
+
export * from "./listWorkspace.js";
|
|
6
|
+
export * from "./mergeWorktree.js";
|
|
7
|
+
export * from "./readWorkspaceFile.js";
|
|
8
|
+
export * from "./recommendAgentForTask.js";
|
|
9
|
+
export * from "./searchWorkspace.js";
|
|
10
|
+
export * from "./syncFile.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./androidDoctor.js";
|
|
2
|
+
export * from "./applyPatch.js";
|
|
3
|
+
export * from "./discardWorktree.js";
|
|
4
|
+
export * from "./listAgents.js";
|
|
5
|
+
export * from "./listWorkspace.js";
|
|
6
|
+
export * from "./mergeWorktree.js";
|
|
7
|
+
export * from "./readWorkspaceFile.js";
|
|
8
|
+
export * from "./recommendAgentForTask.js";
|
|
9
|
+
export * from "./searchWorkspace.js";
|
|
10
|
+
export * from "./syncFile.js";
|
|
@@ -5,6 +5,11 @@ export interface AgentAvailability {
|
|
|
5
5
|
command: string;
|
|
6
6
|
reason: string | null;
|
|
7
7
|
checked_at: string;
|
|
8
|
+
adapter: string | null;
|
|
9
|
+
model: string | null;
|
|
10
|
+
capabilities: {
|
|
11
|
+
model_override: boolean;
|
|
12
|
+
};
|
|
8
13
|
}
|
|
9
14
|
export declare function listAgents(): {
|
|
10
15
|
agents: AgentAvailability[];
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, delimiter, extname, isAbsolute, join } from "node:path";
|
|
3
|
-
import { getConfig } from "
|
|
3
|
+
import { getConfig } from "../../config.js";
|
|
4
|
+
import { sanitizeTrustedPath } from "../../runner/processSecurity.js";
|
|
4
5
|
export function listAgents() {
|
|
5
6
|
const config = getConfig();
|
|
6
7
|
const checkedAt = new Date().toISOString();
|
|
7
8
|
const agents = Object.entries(config.agents)
|
|
8
9
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
9
10
|
.map(([name, agent]) => {
|
|
10
|
-
const available = commandExists(agent.command);
|
|
11
|
+
const available = commandExists(agent.command, config.workspaceRoot);
|
|
11
12
|
return {
|
|
12
13
|
name,
|
|
13
14
|
configured: true,
|
|
@@ -15,15 +16,18 @@ export function listAgents() {
|
|
|
15
16
|
command: basename(agent.command),
|
|
16
17
|
reason: available ? null : "Configured executable was not found on disk or PATH.",
|
|
17
18
|
checked_at: checkedAt,
|
|
19
|
+
adapter: typeof agent.adapter === "string" ? agent.adapter : ["codex", "opencode"].includes(name) ? name : null,
|
|
20
|
+
model: typeof agent.model === "string" ? agent.model : null,
|
|
21
|
+
capabilities: { model_override: typeof agent.adapter === "string" || ["codex", "opencode"].includes(name) },
|
|
18
22
|
};
|
|
19
23
|
});
|
|
20
24
|
return { agents, total: agents.length };
|
|
21
25
|
}
|
|
22
|
-
function commandExists(command) {
|
|
26
|
+
function commandExists(command, workspaceRoot) {
|
|
23
27
|
if (isAbsolute(command) || command.includes("/") || command.includes("\\")) {
|
|
24
28
|
return isFile(command);
|
|
25
29
|
}
|
|
26
|
-
const pathEntries = (process.env.PATH || "").split(delimiter).filter(Boolean);
|
|
30
|
+
const pathEntries = sanitizeTrustedPath(process.env.PATH || "", workspaceRoot).split(delimiter).filter(Boolean);
|
|
27
31
|
const extensions = process.platform === "win32"
|
|
28
32
|
? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
|
|
29
33
|
: [""];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readdirSync, statSync, existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { getConfig } from "
|
|
4
|
-
import { guardPath } from "
|
|
5
|
-
import { guardSensitivePath } from "
|
|
3
|
+
import { getConfig } from "../../config.js";
|
|
4
|
+
import { guardPath } from "../../security/pathGuard.js";
|
|
5
|
+
import { guardSensitivePath } from "../../security/sensitiveGuard.js";
|
|
6
6
|
const MAX_LIST_FILES = 200;
|
|
7
7
|
export function listWorkspace(relativePath) {
|
|
8
8
|
const config = getConfig();
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 参考 src/tools/auditTask.ts 的导出/返回风格。
|
|
6
6
|
*/
|
|
7
|
-
import { getConfig } from "
|
|
8
|
-
import { guardWorkspacePath } from "
|
|
9
|
-
import { mergeWorktree } from "
|
|
7
|
+
import { getConfig } from "../../config.js";
|
|
8
|
+
import { guardWorkspacePath } from "../../security/pathGuard.js";
|
|
9
|
+
import { mergeWorktree } from "../../goal/worktreeManager.js";
|
|
10
10
|
/**
|
|
11
11
|
* 合并指定 worktree 的 branch 回 repo_path(主工作区)。
|
|
12
12
|
*
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { readFileSync, statSync, existsSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { getConfig } from "
|
|
4
|
-
import { guardReadPath } from "
|
|
5
|
-
import { guardSensitivePath } from "
|
|
6
|
-
import { redactSensitiveContent } from "
|
|
7
|
-
import { PatchWardenError } from "
|
|
8
|
-
import { readDirectSession } from "
|
|
9
|
-
import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "
|
|
10
|
-
import { resolveToolProfile } from "
|
|
3
|
+
import { getConfig } from "../../config.js";
|
|
4
|
+
import { guardReadPath } from "../../security/pathGuard.js";
|
|
5
|
+
import { guardSensitivePath } from "../../security/sensitiveGuard.js";
|
|
6
|
+
import { redactSensitiveContent } from "../../security/contentRedaction.js";
|
|
7
|
+
import { PatchWardenError } from "../../errors.js";
|
|
8
|
+
import { readDirectSession } from "../../direct/directSessionStore.js";
|
|
9
|
+
import { guardDirectSessionActive, guardDirectReadPath, isBinaryFile, guardDirectFileSize } from "../../direct/directGuards.js";
|
|
10
|
+
import { resolveToolProfile } from "../catalog/toolCatalog.js";
|
|
11
11
|
export function readWorkspaceFile(input) {
|
|
12
12
|
const config = getConfig();
|
|
13
13
|
// Support both old string API and new object API
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getConfig } from "
|
|
2
|
-
import { guardWorkspacePath } from "
|
|
3
|
-
import { routeAgent } from "
|
|
1
|
+
import { getConfig } from "../../config.js";
|
|
2
|
+
import { guardWorkspacePath } from "../../security/pathGuard.js";
|
|
3
|
+
import { routeAgent } from "../../agents/agentRouter.js";
|
|
4
4
|
export function recommendAgentForTask(input) {
|
|
5
5
|
const config = getConfig();
|
|
6
6
|
const repoPath = String(input.repo_path || "").trim();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
2
2
|
import { relative, join, extname } from "node:path";
|
|
3
|
-
import { getConfig } from "
|
|
4
|
-
import { PatchWardenError } from "
|
|
5
|
-
import { isSensitivePath } from "
|
|
6
|
-
import { validateDirectSessionFreshness, } from "
|
|
7
|
-
import { guardDirectSessionActive } from "
|
|
3
|
+
import { getConfig } from "../../config.js";
|
|
4
|
+
import { PatchWardenError } from "../../errors.js";
|
|
5
|
+
import { isSensitivePath } from "../../security/sensitiveGuard.js";
|
|
6
|
+
import { validateDirectSessionFreshness, } from "../../direct/directSessionStore.js";
|
|
7
|
+
import { guardDirectSessionActive } from "../../direct/directGuards.js";
|
|
8
8
|
// ── Constants ──────────────────────────────────────────────────────
|
|
9
9
|
const SKIP_DIRECTORIES = new Set([
|
|
10
10
|
".git",
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { PatchWardenError } from "../../errors.js";
|
|
5
|
+
import { getConfig } from "../../config.js";
|
|
6
|
+
import { guardDirectReadPath, guardDirectFileSize, guardDirectSessionActive, guardDirectWritePath, } from "../../direct/directGuards.js";
|
|
7
|
+
import { appendDirectSessionOperation, readDirectSession, withDirectSessionMutationLock, } from "../../direct/directSessionStore.js";
|
|
8
|
+
import { computeFileSha256 } from "../../direct/directPatch.js";
|
|
9
|
+
import { atomicWriteFileSync } from "../../utils/atomicFile.js";
|
|
10
|
+
import { redactSensitiveContent } from "../../security/contentRedaction.js";
|
|
11
|
+
/**
|
|
12
|
+
* Copy a file from source to target within the same session repo.
|
|
13
|
+
* Both source and target must be inside the session's repo_path.
|
|
14
|
+
*/
|
|
15
|
+
export function syncFile(sessionId, sourcePath, targetPath, options, config) {
|
|
16
|
+
const cfg = config || getConfig();
|
|
17
|
+
return withDirectSessionMutationLock(sessionId, () => {
|
|
18
|
+
const session = readDirectSession(sessionId, cfg);
|
|
19
|
+
guardDirectSessionActive(session);
|
|
20
|
+
const repoPath = session.resolved_repo_path;
|
|
21
|
+
const workspaceRoot = cfg.workspaceRoot;
|
|
22
|
+
// Guard source path — must be inside repo and not internal/sensitive/binary.
|
|
23
|
+
const resolvedSource = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
|
|
24
|
+
if (!existsSync(resolvedSource)) {
|
|
25
|
+
throw new PatchWardenError("source_file_not_found", `Source file does not exist: "${sourcePath}".`, "Ensure the source path is correct.", true, { source_path: sourcePath });
|
|
26
|
+
}
|
|
27
|
+
// Guard target path — must be inside repo, not in blocked dirs
|
|
28
|
+
const resolvedTarget = guardDirectWritePath(targetPath, repoPath, workspaceRoot);
|
|
29
|
+
const sourceMetadata = statSync(resolvedSource);
|
|
30
|
+
if (!sourceMetadata.isFile()) {
|
|
31
|
+
throw new PatchWardenError("direct_source_not_file", `Source path is not a regular file: "${sourcePath}".`, "Choose an existing text file inside the Direct session repository.", true, { source_path: sourcePath });
|
|
32
|
+
}
|
|
33
|
+
guardDirectFileSize(sourceMetadata.size, cfg);
|
|
34
|
+
// Read once, then hash the exact bytes that will be copied.
|
|
35
|
+
const sourceContent = readFileSync(resolvedSource);
|
|
36
|
+
guardDirectFileSize(sourceContent.length, cfg);
|
|
37
|
+
const sourceText = sourceContent.toString("utf-8");
|
|
38
|
+
if (!Buffer.from(sourceText, "utf-8").equals(sourceContent)) {
|
|
39
|
+
throw new PatchWardenError("unsupported_text_encoding", `Source file "${sourcePath}" is not valid UTF-8 text.`, "Use a normal task or convert the file to UTF-8 before Direct sync.", true, { source_path: sourcePath });
|
|
40
|
+
}
|
|
41
|
+
const sensitive = redactSensitiveContent(sourceText);
|
|
42
|
+
if (sensitive.redacted) {
|
|
43
|
+
throw new PatchWardenError("sensitive_content_blocked", `Source file contains credential-like content (${sensitive.redaction_categories.join(", ")}).`, "Remove the sensitive value and retry with placeholders or environment-variable references.", true, { source_path: sourcePath, redaction_categories: sensitive.redaction_categories });
|
|
44
|
+
}
|
|
45
|
+
const sourceSha256 = createHash("sha256").update(sourceContent).digest("hex");
|
|
46
|
+
if (options?.expected_source_sha256 && options.expected_source_sha256 !== sourceSha256) {
|
|
47
|
+
throw new PatchWardenError("source_hash_mismatch", `Source file hash mismatch. Expected "${options.expected_source_sha256}" but got "${sourceSha256}".`, "Re-read the source file to get the current sha256.", true, { expected_sha256: options.expected_source_sha256, actual_sha256: sourceSha256 });
|
|
48
|
+
}
|
|
49
|
+
// Get target sha256 before copy
|
|
50
|
+
let beforeTargetSha256 = null;
|
|
51
|
+
if (existsSync(resolvedTarget)) {
|
|
52
|
+
const targetMetadata = statSync(resolvedTarget);
|
|
53
|
+
if (!targetMetadata.isFile()) {
|
|
54
|
+
throw new PatchWardenError("direct_target_not_file", `Target path is not a regular file: "${targetPath}".`, "Choose a text-file target inside the Direct session repository.", true, { target_path: targetPath });
|
|
55
|
+
}
|
|
56
|
+
guardDirectFileSize(targetMetadata.size, cfg);
|
|
57
|
+
beforeTargetSha256 = computeFileSha256(resolvedTarget);
|
|
58
|
+
// Verify target sha256 if provided
|
|
59
|
+
if (options?.expected_target_sha256 && options.expected_target_sha256 !== beforeTargetSha256) {
|
|
60
|
+
throw new PatchWardenError("target_hash_mismatch", `Target file hash mismatch. Expected "${options.expected_target_sha256}" but got "${beforeTargetSha256}".`, "Re-read the target file to get the current sha256.", true, { expected_sha256: options.expected_target_sha256, actual_sha256: beforeTargetSha256 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const copiedBytes = sourceContent.length;
|
|
64
|
+
if (beforeTargetSha256 === sourceSha256) {
|
|
65
|
+
assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
|
|
66
|
+
appendSyncOperation(sessionId, sourcePath, targetPath, beforeTargetSha256, sourceSha256, 0, 0, cfg);
|
|
67
|
+
return buildSyncResult(sourcePath, targetPath, beforeTargetSha256, sourceSha256, copiedBytes, false);
|
|
68
|
+
}
|
|
69
|
+
// Create target directory if needed
|
|
70
|
+
mkdirSync(dirname(resolvedTarget), { recursive: true });
|
|
71
|
+
// Re-resolve after directory creation so a newly exposed link/reparse point
|
|
72
|
+
// cannot reuse the earlier lexical decision. Replace atomically in the
|
|
73
|
+
// validated target directory and preserve an existing file's permissions.
|
|
74
|
+
const revalidatedTarget = guardDirectWritePath(resolvedTarget, repoPath, workspaceRoot);
|
|
75
|
+
const currentTargetSha256 = existsSync(revalidatedTarget)
|
|
76
|
+
? computeFileSha256(revalidatedTarget)
|
|
77
|
+
: null;
|
|
78
|
+
if (currentTargetSha256 !== beforeTargetSha256) {
|
|
79
|
+
throw new PatchWardenError("direct_target_changed_during_sync", `Target file "${targetPath}" changed while sync_file was preparing the copy.`, "Re-read the target file and retry after concurrent filesystem changes stop.", true, {
|
|
80
|
+
path: targetPath,
|
|
81
|
+
before_sha256: beforeTargetSha256,
|
|
82
|
+
current_sha256: currentTargetSha256,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
assertSourceUnchanged(sourcePath, resolvedSource, sourceSha256, repoPath, workspaceRoot);
|
|
86
|
+
const existingMode = existsSync(revalidatedTarget) ? statSync(revalidatedTarget).mode : undefined;
|
|
87
|
+
atomicWriteFileSync(revalidatedTarget, sourceContent, { mode: existingMode });
|
|
88
|
+
const finalTarget = guardDirectWritePath(revalidatedTarget, repoPath, workspaceRoot);
|
|
89
|
+
if (finalTarget !== revalidatedTarget) {
|
|
90
|
+
throw new PatchWardenError("direct_path_changed_during_write", `Target path "${targetPath}" changed while sync_file was writing it.`, "Inspect the repository path and retry only after concurrent filesystem changes stop.", true, { path: targetPath, operation: "sync_file" });
|
|
91
|
+
}
|
|
92
|
+
// Compute after hash
|
|
93
|
+
const afterTargetSha256 = computeFileSha256(finalTarget);
|
|
94
|
+
const changed = beforeTargetSha256 !== afterTargetSha256;
|
|
95
|
+
appendSyncOperation(sessionId, sourcePath, targetPath, beforeTargetSha256, afterTargetSha256, 1, copiedBytes, cfg);
|
|
96
|
+
return buildSyncResult(sourcePath, targetPath, beforeTargetSha256, afterTargetSha256, copiedBytes, changed);
|
|
97
|
+
}, cfg);
|
|
98
|
+
}
|
|
99
|
+
function assertSourceUnchanged(sourcePath, resolvedSource, expectedSha256, repoPath, workspaceRoot) {
|
|
100
|
+
const currentPath = guardDirectReadPath(sourcePath, repoPath, workspaceRoot);
|
|
101
|
+
const samePath = process.platform === "win32"
|
|
102
|
+
? currentPath.toLowerCase() === resolvedSource.toLowerCase()
|
|
103
|
+
: currentPath === resolvedSource;
|
|
104
|
+
const currentSha256 = samePath && existsSync(currentPath)
|
|
105
|
+
? computeFileSha256(currentPath)
|
|
106
|
+
: null;
|
|
107
|
+
if (!samePath || currentSha256 !== expectedSha256) {
|
|
108
|
+
throw new PatchWardenError("direct_source_changed_during_sync", `Source file "${sourcePath}" changed while sync_file was preparing the copy.`, "Re-read the source file and retry after concurrent filesystem changes stop.", true, { source_path: sourcePath, expected_sha256: expectedSha256, actual_sha256: currentSha256 });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function appendSyncOperation(sessionId, sourcePath, targetPath, beforeSha256, afterSha256, operationsApplied, bytesChanged, config) {
|
|
112
|
+
appendDirectSessionOperation(sessionId, {
|
|
113
|
+
index: 0,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
path: targetPath,
|
|
116
|
+
source_path: sourcePath,
|
|
117
|
+
operation_type: "sync",
|
|
118
|
+
before_sha256: beforeSha256,
|
|
119
|
+
after_sha256: afterSha256,
|
|
120
|
+
operations_applied: operationsApplied,
|
|
121
|
+
bytes_changed: bytesChanged,
|
|
122
|
+
}, config);
|
|
123
|
+
}
|
|
124
|
+
function buildSyncResult(sourcePath, targetPath, beforeSha256, afterSha256, copiedBytes, changed) {
|
|
125
|
+
return {
|
|
126
|
+
source_path: sourcePath,
|
|
127
|
+
target_path: targetPath,
|
|
128
|
+
before_target_sha256: beforeSha256,
|
|
129
|
+
after_target_sha256: afterSha256,
|
|
130
|
+
source_sha256: afterSha256,
|
|
131
|
+
copied_bytes: copiedBytes,
|
|
132
|
+
changed,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface AtomicWriteOptions {
|
|
2
|
+
encoding?: BufferEncoding;
|
|
3
|
+
mode?: number;
|
|
4
|
+
}
|
|
5
|
+
/** Replace a file from a complete same-directory temporary file. */
|
|
6
|
+
export declare function atomicWriteFileSync(path: string, content: string | NodeJS.ArrayBufferView, options?: AtomicWriteOptions): void;
|
|
7
|
+
export declare function atomicWriteJsonFileSync(path: string, value: unknown, mode?: number): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
const transientWindowsRenameErrors = new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
4
|
+
const renameRetryDeadlineMs = 1_000;
|
|
5
|
+
const renameRetryDelayMs = 10;
|
|
6
|
+
const waitArray = new Int32Array(new SharedArrayBuffer(4));
|
|
7
|
+
/** Replace a file from a complete same-directory temporary file. */
|
|
8
|
+
export function atomicWriteFileSync(path, content, options = {}) {
|
|
9
|
+
const temporaryPath = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
10
|
+
try {
|
|
11
|
+
writeFileSync(temporaryPath, content, {
|
|
12
|
+
encoding: options.encoding ?? "utf-8",
|
|
13
|
+
flag: "wx",
|
|
14
|
+
...(options.mode === undefined ? {} : { mode: options.mode }),
|
|
15
|
+
});
|
|
16
|
+
replaceFileSync(temporaryPath, path);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
try {
|
|
20
|
+
rmSync(temporaryPath, { force: true });
|
|
21
|
+
}
|
|
22
|
+
catch { /* best effort */ }
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function replaceFileSync(temporaryPath, path) {
|
|
27
|
+
const deadline = Date.now() + renameRetryDeadlineMs;
|
|
28
|
+
while (true) {
|
|
29
|
+
try {
|
|
30
|
+
renameSync(temporaryPath, path);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const code = error && typeof error === "object" && "code" in error
|
|
35
|
+
? String(error.code || "")
|
|
36
|
+
: "";
|
|
37
|
+
if (process.platform !== "win32" ||
|
|
38
|
+
!transientWindowsRenameErrors.has(code) ||
|
|
39
|
+
Date.now() >= deadline) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
Atomics.wait(waitArray, 0, 0, renameRetryDelayMs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function atomicWriteJsonFileSync(path, value, mode = 0o600) {
|
|
47
|
+
atomicWriteFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { mode });
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface BoundedTextRead {
|
|
2
|
+
content: string;
|
|
3
|
+
totalBytes: number;
|
|
4
|
+
truncated: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function readTextFilePrefixSync(path: string, maxBytes: number): BoundedTextRead;
|
|
7
|
+
export declare function readTextFileTailLinesSync(path: string, lines: number, maxBytes?: number): string;
|
|
8
|
+
export declare function appendBoundedTextFileSync(path: string, content: string, maxBytes?: number): void;
|