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,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
copyFileSync,
|
|
4
4
|
existsSync,
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readFileSync,
|
|
@@ -16,7 +16,8 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
|
|
17
17
|
const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
18
18
|
const root = resolve(scriptDir, "..", "..");
|
|
19
|
-
const
|
|
19
|
+
const releaseRoot = resolve(root, "release");
|
|
20
|
+
const releaseDir = resolve(releaseRoot, "package");
|
|
20
21
|
const archivePath = resolve(root, "patchwarden-release.tar.gz");
|
|
21
22
|
|
|
22
23
|
// Read version from package.json for zip naming
|
|
@@ -31,11 +32,6 @@ const include = [
|
|
|
31
32
|
"src",
|
|
32
33
|
"ui",
|
|
33
34
|
"PatchWarden.cmd",
|
|
34
|
-
"PatchWarden-Control.cmd",
|
|
35
|
-
"PatchWarden-Control-Tray.cmd",
|
|
36
|
-
"PatchWarden-Desktop.cmd",
|
|
37
|
-
"Restart-PatchWarden-Control.cmd",
|
|
38
|
-
"Stop-PatchWarden.cmd",
|
|
39
35
|
".gitignore",
|
|
40
36
|
"LICENSE",
|
|
41
37
|
"README.md",
|
|
@@ -57,6 +53,8 @@ const forbidden = [
|
|
|
57
53
|
/(^|[\\/])\.env$/,
|
|
58
54
|
/\.log$/,
|
|
59
55
|
/^docs[\\/]optimization-proposal\.md$/i,
|
|
56
|
+
/^docs[\\/]archive(?:[\\/]|$)/i,
|
|
57
|
+
/^(?:dist|src)[\\/]test(?:[\\/]|$)/i,
|
|
60
58
|
/(^|[\\/])kill-patchwarden\.(cmd|ps1)$/i,
|
|
61
59
|
];
|
|
62
60
|
|
|
@@ -173,9 +171,15 @@ function readZipEntryNames(zipPath) {
|
|
|
173
171
|
return names;
|
|
174
172
|
}
|
|
175
173
|
|
|
174
|
+
const stageRelativeToRoot = toPosix(relative(root, releaseDir));
|
|
175
|
+
if (stageRelativeToRoot !== "release/package") {
|
|
176
|
+
throw new Error(`Refusing to clean non-isolated release staging path: ${stageRelativeToRoot}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
176
179
|
console.log("[pack-clean] Preparing clean release directory...");
|
|
177
180
|
rmSync(releaseDir, { recursive: true, force: true });
|
|
178
181
|
rmSync(archivePath, { force: true });
|
|
182
|
+
mkdirSync(releaseRoot, { recursive: true });
|
|
179
183
|
mkdirSync(releaseDir, { recursive: true });
|
|
180
184
|
|
|
181
185
|
for (const item of include) {
|
|
@@ -185,17 +189,26 @@ for (const item of include) {
|
|
|
185
189
|
console.log(` skip missing: ${item}`);
|
|
186
190
|
continue;
|
|
187
191
|
}
|
|
188
|
-
|
|
189
|
-
recursive: true,
|
|
190
|
-
filter(sourcePath) {
|
|
191
|
-
const rel = toPosix(relative(root, sourcePath));
|
|
192
|
-
return !isForbidden(rel);
|
|
193
|
-
},
|
|
194
|
-
});
|
|
192
|
+
copyAllowedEntry(source, target);
|
|
195
193
|
console.log(` copied: ${item}`);
|
|
196
194
|
}
|
|
197
195
|
|
|
196
|
+
// Keep an independent second boundary before the fail-closed validation. This
|
|
197
|
+
// protects package contents if a future copy implementation changes behavior.
|
|
198
|
+
pruneForbiddenEntries(releaseDir);
|
|
199
|
+
|
|
198
200
|
const releaseFiles = listFiles(releaseDir);
|
|
201
|
+
const requiredReleaseEntries = ["package.json", "README.md", "dist/index.js"];
|
|
202
|
+
const missingReleaseEntries = requiredReleaseEntries.filter(
|
|
203
|
+
(entry) => !existsSync(resolve(releaseDir, entry))
|
|
204
|
+
);
|
|
205
|
+
if (releaseFiles.length === 0 || missingReleaseEntries.length > 0) {
|
|
206
|
+
console.error("[pack-clean] Release directory is incomplete:");
|
|
207
|
+
for (const entry of missingReleaseEntries) {
|
|
208
|
+
console.error(` missing: ${entry}`);
|
|
209
|
+
}
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
199
212
|
const badReleaseEntries = releaseFiles.filter((file) =>
|
|
200
213
|
isForbidden(toPosix(relative(releaseDir, file)))
|
|
201
214
|
);
|
|
@@ -296,6 +309,38 @@ function listFiles(dir) {
|
|
|
296
309
|
return entries;
|
|
297
310
|
}
|
|
298
311
|
|
|
312
|
+
function copyAllowedEntry(source, target) {
|
|
313
|
+
const rel = toPosix(relative(root, source));
|
|
314
|
+
if (isForbidden(rel)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const stat = statSync(source);
|
|
319
|
+
if (stat.isDirectory()) {
|
|
320
|
+
mkdirSync(target, { recursive: true });
|
|
321
|
+
for (const name of readdirSync(source)) {
|
|
322
|
+
copyAllowedEntry(join(source, name), join(target, name));
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
copyFileSync(source, target);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function pruneForbiddenEntries(dir) {
|
|
331
|
+
for (const name of readdirSync(dir)) {
|
|
332
|
+
const fullPath = join(dir, name);
|
|
333
|
+
const rel = toPosix(relative(releaseDir, fullPath));
|
|
334
|
+
if (isForbidden(rel)) {
|
|
335
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (statSync(fullPath).isDirectory()) {
|
|
339
|
+
pruneForbiddenEntries(fullPath);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
299
344
|
function isForbidden(value) {
|
|
300
345
|
return forbidden.some((pattern) => pattern.test(value));
|
|
301
346
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve, relative, isAbsolute } from "node:path";
|
|
1
|
+
import { resolve, relative, isAbsolute, sep } from "node:path";
|
|
3
2
|
import { PatchWardenConfig } from "../config.js";
|
|
4
3
|
import { buildAgentInvocation, buildAssessmentPrompt } from "../runner/agentInvocation.js";
|
|
5
4
|
import { runSimpleProcessSync } from "../runner/simpleProcess.js";
|
|
6
5
|
import { captureRepoSnapshot, compareSnapshots, type RepoSnapshot, type ChangedFile } from "../runner/changeCapture.js";
|
|
7
6
|
import type { AgentAssessmentOutput, AgentAssessmentSummary } from "./assessmentStore.js";
|
|
7
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
8
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
8
9
|
|
|
9
10
|
const ASSESSMENT_MARKER = "===ASSESSMENT_JSON===";
|
|
10
11
|
|
|
@@ -21,7 +22,7 @@ export interface AgentAssessorInput {
|
|
|
21
22
|
config: PatchWardenConfig;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function runAgentAssessment(input: AgentAssessorInput): AgentAssessmentSummary {
|
|
25
|
+
export async function runAgentAssessment(input: AgentAssessorInput): Promise<AgentAssessmentSummary> {
|
|
25
26
|
const logPaths: AgentAssessmentSummary["log_paths"] = {
|
|
26
27
|
stdout: resolve(input.assessmentDir, "agent-assessment-stdout.log"),
|
|
27
28
|
stderr: resolve(input.assessmentDir, "agent-assessment-stderr.log"),
|
|
@@ -45,15 +46,17 @@ export function runAgentAssessment(input: AgentAssessorInput): AgentAssessmentSu
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
// ── 1. Build prompt and write to file for {prompt_file} support ──
|
|
48
|
-
const
|
|
49
|
+
const safeGoal = redactSensitiveContent(input.goal).content.slice(0, 10_000);
|
|
50
|
+
const safePlan = redactSensitiveContent(input.planContent).content.slice(0, 1024 * 1024);
|
|
51
|
+
const assessmentPrompt = buildAssessmentPrompt(safeGoal, safePlan, input.repoPath);
|
|
49
52
|
const promptFilePath = resolve(input.assessmentDir, "agent-assessment-prompt.md");
|
|
50
|
-
|
|
53
|
+
atomicWriteFileSync(promptFilePath, assessmentPrompt);
|
|
51
54
|
logPaths.prompt = promptFilePath;
|
|
52
55
|
|
|
53
56
|
// ── 2. Before snapshot (repo-scoped) ──
|
|
54
57
|
let repoBefore: RepoSnapshot;
|
|
55
58
|
try {
|
|
56
|
-
repoBefore = captureRepoSnapshot(input.repoPath);
|
|
59
|
+
repoBefore = await captureRepoSnapshot(input.repoPath);
|
|
57
60
|
} catch {
|
|
58
61
|
// If we can't capture before snapshot, conservatively skip agent assessment
|
|
59
62
|
emptySummary.status = "spawn_failed";
|
|
@@ -89,13 +92,15 @@ export function runAgentAssessment(input: AgentAssessorInput): AgentAssessmentSu
|
|
|
89
92
|
maxStderrBytes: Math.max(16384, Math.floor(input.maxOutputBytes / 4)),
|
|
90
93
|
stdoutPath: logPaths.stdout,
|
|
91
94
|
stderrPath: logPaths.stderr,
|
|
95
|
+
environmentVariableNames: invocation.environmentVariableNames,
|
|
96
|
+
blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
|
|
92
97
|
});
|
|
93
98
|
|
|
94
99
|
// ── 5. After snapshot + read-only violation check ──
|
|
95
100
|
let readOnlyViolation = false;
|
|
96
101
|
let violationFiles: string[] = [];
|
|
97
102
|
try {
|
|
98
|
-
const repoAfter = captureRepoSnapshot(input.repoPath);
|
|
103
|
+
const repoAfter = await captureRepoSnapshot(input.repoPath);
|
|
99
104
|
const changes = compareSnapshots(repoBefore, repoAfter);
|
|
100
105
|
if (changes.length > 0) {
|
|
101
106
|
readOnlyViolation = true;
|
|
@@ -123,11 +128,11 @@ export function runAgentAssessment(input: AgentAssessorInput): AgentAssessmentSu
|
|
|
123
128
|
stderr_truncated: result.stderrTruncated,
|
|
124
129
|
log_paths: { ...logPaths, violation: resolve(input.assessmentDir, "agent-assessment-violation.json") },
|
|
125
130
|
};
|
|
126
|
-
|
|
131
|
+
atomicWriteJsonFileSync(summary.log_paths.violation!, {
|
|
127
132
|
violation: "read_only_violation",
|
|
128
133
|
files: violationFiles,
|
|
129
134
|
exit_code: result.exitCode,
|
|
130
|
-
}
|
|
135
|
+
});
|
|
131
136
|
writeSummary(logPaths.assessment, summary);
|
|
132
137
|
return summary;
|
|
133
138
|
}
|
|
@@ -253,12 +258,16 @@ function parseAssessmentJson(stdout: string, repoPath: string): ParsedAssessment
|
|
|
253
258
|
const jsonEnd = jsonText.lastIndexOf("}");
|
|
254
259
|
if (jsonStart < 0 || jsonEnd < 0 || jsonEnd <= jsonStart) return { output: null, sanitized_reasons };
|
|
255
260
|
|
|
256
|
-
let
|
|
261
|
+
let parsedValue: unknown;
|
|
257
262
|
try {
|
|
258
|
-
|
|
263
|
+
parsedValue = JSON.parse(jsonText.slice(jsonStart, jsonEnd + 1));
|
|
259
264
|
} catch {
|
|
260
265
|
return { output: null, sanitized_reasons };
|
|
261
266
|
}
|
|
267
|
+
if (!parsedValue || typeof parsedValue !== "object" || Array.isArray(parsedValue)) {
|
|
268
|
+
return { output: null, sanitized_reasons };
|
|
269
|
+
}
|
|
270
|
+
const parsed = parsedValue as Record<string, unknown>;
|
|
262
271
|
|
|
263
272
|
// ── Strict validation ──
|
|
264
273
|
if (parsed.risk_level !== "low" && parsed.risk_level !== "medium" && parsed.risk_level !== "high") {
|
|
@@ -272,7 +281,9 @@ function parseAssessmentJson(stdout: string, repoPath: string): ParsedAssessment
|
|
|
272
281
|
|
|
273
282
|
if (!Array.isArray(parsed.reason_codes)) return { output: null, sanitized_reasons };
|
|
274
283
|
if (parsed.reason_codes.length > 50) return { output: null, sanitized_reasons };
|
|
275
|
-
const reasonCodes = parsed.reason_codes.filter((
|
|
284
|
+
const reasonCodes = parsed.reason_codes.filter((reason): reason is string =>
|
|
285
|
+
typeof reason === "string" && reason.length <= 100
|
|
286
|
+
);
|
|
276
287
|
if (reasonCodes.length !== parsed.reason_codes.length) {
|
|
277
288
|
sanitized_reasons.push("reason_codes_filtered");
|
|
278
289
|
}
|
|
@@ -287,7 +298,10 @@ function parseAssessmentJson(stdout: string, repoPath: string): ParsedAssessment
|
|
|
287
298
|
if (isAbsolute(p)) { pathsSanitized = true; continue; }
|
|
288
299
|
const resolved = resolve(repoPath, p);
|
|
289
300
|
const rel = relative(repoPath, resolved);
|
|
290
|
-
if (rel
|
|
301
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
302
|
+
pathsSanitized = true;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
291
305
|
affectedPaths.push(p);
|
|
292
306
|
}
|
|
293
307
|
if (pathsSanitized) {
|
|
@@ -297,7 +311,7 @@ function parseAssessmentJson(stdout: string, repoPath: string): ParsedAssessment
|
|
|
297
311
|
if (!Array.isArray(parsed.destructive_actions)) return { output: null, sanitized_reasons };
|
|
298
312
|
if (parsed.destructive_actions.length > 20) return { output: null, sanitized_reasons };
|
|
299
313
|
const destructiveActions = parsed.destructive_actions.filter(
|
|
300
|
-
(
|
|
314
|
+
(action): action is string => typeof action === "string" && action.length <= 200
|
|
301
315
|
);
|
|
302
316
|
|
|
303
317
|
if (typeof parsed.requires_user_confirm !== "boolean") return { output: null, sanitized_reasons };
|
|
@@ -320,5 +334,5 @@ function parseAssessmentJson(stdout: string, repoPath: string): ParsedAssessment
|
|
|
320
334
|
}
|
|
321
335
|
|
|
322
336
|
function writeSummary(path: string, summary: AgentAssessmentSummary): void {
|
|
323
|
-
|
|
337
|
+
atomicWriteJsonFileSync(path, summary);
|
|
324
338
|
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mkdirSync,
|
|
3
|
-
writeFileSync,
|
|
4
3
|
existsSync,
|
|
5
4
|
readFileSync,
|
|
6
|
-
renameSync,
|
|
7
5
|
} from "node:fs";
|
|
8
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
9
7
|
import { resolve, join } from "node:path";
|
|
10
8
|
import { getAssessmentsDir, getConfig, getRepoAllowedTestCommands, PatchWardenConfig } from "../config.js";
|
|
11
9
|
import { guardPath } from "../security/pathGuard.js";
|
|
12
10
|
import { PatchWardenError } from "../errors.js";
|
|
11
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
12
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
13
|
+
import { withFileLockSync } from "../utils/lockedJsonFile.js";
|
|
13
14
|
import { TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
14
|
-
import { getLastToolCatalogSnapshot } from "../tools/toolCatalog.js";
|
|
15
|
+
import { getLastToolCatalogSnapshot } from "../tools/catalog/toolCatalog.js";
|
|
15
16
|
import { captureRepoSnapshot, type RepoSnapshot } from "../runner/changeCapture.js";
|
|
16
17
|
import type { RiskAssessmentResult } from "../security/riskEngine.js";
|
|
17
18
|
import type { TaskTemplateName, ChangePolicy } from "../tools/taskTemplates.js";
|
|
@@ -203,7 +204,7 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
|
|
|
203
204
|
repo_path: input.repo_path,
|
|
204
205
|
resolved_repo_path: input.resolved_repo_path,
|
|
205
206
|
template: input.template || null,
|
|
206
|
-
goal: input.goal
|
|
207
|
+
goal: input.goal ? redactSensitiveContent(input.goal).content : null,
|
|
207
208
|
test_command: input.test_command || null,
|
|
208
209
|
verify_commands: input.verify_commands || [],
|
|
209
210
|
agent: input.agent,
|
|
@@ -216,7 +217,7 @@ export function createAssessment(input: AssessmentCreateInput): AssessmentRecord
|
|
|
216
217
|
agent_assessment_summary: input.agent_assessment_summary || null,
|
|
217
218
|
};
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
atomicWriteJsonFileSync(join(dir, "assessment.json"), record);
|
|
220
221
|
|
|
221
222
|
return record;
|
|
222
223
|
}
|
|
@@ -314,7 +315,7 @@ export function validateAssessmentFreshness(
|
|
|
314
315
|
* This is intentionally not registered as an MCP tool: a remote client may
|
|
315
316
|
* request confirmation, but it cannot grant confirmation to itself.
|
|
316
317
|
*/
|
|
317
|
-
export function confirmAssessment(assessmentId: string): AssessmentConfirmationResult {
|
|
318
|
+
export async function confirmAssessment(assessmentId: string): Promise<AssessmentConfirmationResult> {
|
|
318
319
|
if (!/^assessment_\d{8}_\d{6}_[0-9a-f]{32}$/.test(assessmentId)) {
|
|
319
320
|
throw new PatchWardenError(
|
|
320
321
|
"assessment_id_invalid",
|
|
@@ -323,6 +324,10 @@ export function confirmAssessment(assessmentId: string): AssessmentConfirmationR
|
|
|
323
324
|
);
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
const config = getConfig();
|
|
328
|
+
const assessmentFile = resolve(getAssessmentsDir(config), assessmentId, "assessment.json");
|
|
329
|
+
guardPath(assessmentFile, config.workspaceRoot, config.assessmentsDir);
|
|
330
|
+
|
|
326
331
|
const assessment = readAssessment(assessmentId);
|
|
327
332
|
if (assessment.decision !== "needs_confirm" || !assessment.requires_confirm) {
|
|
328
333
|
throw new PatchWardenError(
|
|
@@ -334,44 +339,51 @@ export function confirmAssessment(assessmentId: string): AssessmentConfirmationR
|
|
|
334
339
|
);
|
|
335
340
|
}
|
|
336
341
|
|
|
337
|
-
const snapshot = captureRepoSnapshot(assessment.resolved_repo_path);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
guardPath(temporaryFile, config.workspaceRoot, config.assessmentsDir);
|
|
364
|
-
writeFileSync(temporaryFile, JSON.stringify(confirmedRecord, null, 2), "utf-8");
|
|
365
|
-
renameSync(temporaryFile, assessmentFile);
|
|
342
|
+
const snapshot = await captureRepoSnapshot(assessment.resolved_repo_path);
|
|
343
|
+
|
|
344
|
+
// Re-read and write while holding one cross-process lock. Concurrent local
|
|
345
|
+
// confirmers may compute snapshots in parallel, but they cannot replace one
|
|
346
|
+
// another's confirmation metadata from a stale assessment record.
|
|
347
|
+
return withFileLockSync(assessmentFile, () => {
|
|
348
|
+
const validation = validateAssessmentFreshness(assessmentId, snapshot, {
|
|
349
|
+
allow_unconfirmed: true,
|
|
350
|
+
// A standalone CLI has no active MCP profile snapshot. The execute path
|
|
351
|
+
// still performs the full tool-manifest check before creating the task.
|
|
352
|
+
skip_tool_manifest: true,
|
|
353
|
+
});
|
|
354
|
+
if (!validation.valid || !validation.assessment) {
|
|
355
|
+
throw new PatchWardenError(
|
|
356
|
+
validation.failure_reason || "assessment_validation_failed",
|
|
357
|
+
`Assessment "${assessmentId}" cannot be confirmed: ${validation.failure_reason || "validation_failed"}.`,
|
|
358
|
+
"Run create_task with execution_mode=assess_only again, then confirm the new assessment locally."
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
if (validation.assessment.decision !== "needs_confirm" || !validation.assessment.requires_confirm) {
|
|
362
|
+
throw new PatchWardenError(
|
|
363
|
+
"assessment_confirmation_not_allowed",
|
|
364
|
+
`Assessment "${assessmentId}" is no longer eligible for local confirmation.`,
|
|
365
|
+
"Run create_task with execution_mode=assess_only again."
|
|
366
|
+
);
|
|
367
|
+
}
|
|
366
368
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
const confirmedAt = validation.assessment.confirmed_at || new Date().toISOString();
|
|
370
|
+
const confirmedRecord: AssessmentRecord = {
|
|
371
|
+
...validation.assessment,
|
|
372
|
+
confirmed: true,
|
|
373
|
+
confirmed_at: confirmedAt,
|
|
374
|
+
confirm_code: validation.assessment.confirm_code || randomBytes(8).toString("hex"),
|
|
375
|
+
};
|
|
376
|
+
atomicWriteJsonFileSync(assessmentFile, confirmedRecord);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
assessment_id: assessmentId,
|
|
380
|
+
decision: "needs_confirm",
|
|
381
|
+
confirmed: true,
|
|
382
|
+
confirmed_at: confirmedAt,
|
|
383
|
+
expires_at: confirmedRecord.expires_at,
|
|
384
|
+
next_action: "Call create_task with only execution_mode=execute and this full assessment_id.",
|
|
385
|
+
};
|
|
386
|
+
});
|
|
375
387
|
}
|
|
376
388
|
|
|
377
389
|
export function computeWorkspaceFingerprint(snapshot: RepoSnapshot): string {
|
|
@@ -10,20 +10,21 @@
|
|
|
10
10
|
import { loadConfig } from "../config.js";
|
|
11
11
|
import { confirmAssessment } from "./assessmentStore.js";
|
|
12
12
|
import { errorPayload } from "../errors.js";
|
|
13
|
+
import { logger } from "../logging.js";
|
|
13
14
|
|
|
14
15
|
loadConfig();
|
|
15
16
|
|
|
16
17
|
const assessmentId = process.argv[2];
|
|
17
18
|
if (!assessmentId || process.argv.length !== 3) {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
logger.info("Usage: patchwarden-confirm <full_assessment_id>");
|
|
20
|
+
logger.info("The display-only assessment_short_id is not accepted.");
|
|
20
21
|
process.exit(1);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
try {
|
|
24
|
-
const result = confirmAssessment(assessmentId);
|
|
25
|
+
const result = await confirmAssessment(assessmentId);
|
|
25
26
|
console.log(JSON.stringify(result, null, 2));
|
|
26
27
|
} catch (error) {
|
|
27
|
-
|
|
28
|
+
logger.error("confirm_assessment_failed", { error: errorPayload(error) });
|
|
28
29
|
process.exit(1);
|
|
29
30
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { basename, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { validateWorkspaceRoot } from "./security/workspaceRootGuard.js";
|
|
3
4
|
|
|
4
5
|
// ── Type definitions ──────────────────────────────────────────────
|
|
5
6
|
|
|
6
7
|
export interface AgentConfig {
|
|
7
8
|
command: string;
|
|
8
9
|
args: string[];
|
|
10
|
+
envAllowlist?: string[];
|
|
11
|
+
adapter?: string;
|
|
12
|
+
model?: string;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
export interface PatchWardenConfig {
|
|
@@ -23,11 +27,20 @@ export interface PatchWardenConfig {
|
|
|
23
27
|
watcherStaleSeconds: number;
|
|
24
28
|
toolProfile?: "full" | "chatgpt_core" | "chatgpt_direct" | "chatgpt_search";
|
|
25
29
|
repoAliases?: Record<string, string>;
|
|
30
|
+
httpPort?: number;
|
|
31
|
+
http?: { port?: number; host?: string; ownerTokenEnv?: string };
|
|
32
|
+
enableRunTaskTool?: boolean;
|
|
26
33
|
enableAgentAssessment?: boolean;
|
|
27
34
|
agentAssessmentTimeoutSeconds?: number;
|
|
28
35
|
agentAssessmentMaxOutputBytes?: number;
|
|
29
36
|
agentAssessmentAgentName?: string;
|
|
30
37
|
enableDirectProfile?: boolean;
|
|
38
|
+
tunnelClientPath?: string;
|
|
39
|
+
tunnelProxy?: {
|
|
40
|
+
scope: "shared" | "separate";
|
|
41
|
+
core: { mode: "environment" | "none" | "manual"; url?: string };
|
|
42
|
+
direct: { mode: "environment" | "none" | "manual"; url?: string };
|
|
43
|
+
};
|
|
31
44
|
directSessionsDir: string;
|
|
32
45
|
directSessionTtlSeconds: number;
|
|
33
46
|
directMaxPatchBytes: number;
|
|
@@ -44,16 +57,7 @@ const DEFAULT_CONFIG: PatchWardenConfig = {
|
|
|
44
57
|
tasksDir: ".patchwarden/tasks",
|
|
45
58
|
assessmentsDir: ".patchwarden/assessments",
|
|
46
59
|
assessmentTtlSeconds: 3600,
|
|
47
|
-
agents: {
|
|
48
|
-
codex: {
|
|
49
|
-
command: "codex",
|
|
50
|
-
args: ["exec", "--cd", "{repo}", "{prompt}"],
|
|
51
|
-
},
|
|
52
|
-
opencode: {
|
|
53
|
-
command: "opencode",
|
|
54
|
-
args: ["run", "{prompt}"],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
60
|
+
agents: {},
|
|
57
61
|
allowedTestCommands: [
|
|
58
62
|
"npm test",
|
|
59
63
|
"npm run test",
|
|
@@ -172,6 +176,11 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
172
176
|
if (!config.workspaceRoot || typeof config.workspaceRoot !== "string") {
|
|
173
177
|
throw new Error("workspaceRoot must be a non-empty string");
|
|
174
178
|
}
|
|
179
|
+
const workspaceValidation = validateWorkspaceRoot(config.workspaceRoot);
|
|
180
|
+
if (!workspaceValidation.ok) {
|
|
181
|
+
throw new Error(`Invalid workspaceRoot: ${workspaceValidation.reason} (${workspaceValidation.path})`);
|
|
182
|
+
}
|
|
183
|
+
config.workspaceRoot = workspaceValidation.path;
|
|
175
184
|
if (!config.plansDir || typeof config.plansDir !== "string") {
|
|
176
185
|
throw new Error("plansDir must be a non-empty string");
|
|
177
186
|
}
|
|
@@ -187,6 +196,20 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
187
196
|
if (!config.agents || typeof config.agents !== "object") {
|
|
188
197
|
throw new Error("agents must be an object");
|
|
189
198
|
}
|
|
199
|
+
const agents: Record<string, AgentConfig> = {};
|
|
200
|
+
for (const [agentName, agent] of Object.entries(config.agents)) {
|
|
201
|
+
if (!agent || typeof agent !== "object" || typeof agent.command !== "string" || !Array.isArray(agent.args)) {
|
|
202
|
+
throw new Error(`agents["${agentName}"] must define command and args`);
|
|
203
|
+
}
|
|
204
|
+
const envAllowlist = agent.envAllowlist ?? [];
|
|
205
|
+
if (!Array.isArray(envAllowlist) || envAllowlist.some((name) => typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) {
|
|
206
|
+
throw new Error(`agents["${agentName}"].envAllowlist must contain only environment variable names`);
|
|
207
|
+
}
|
|
208
|
+
const blockedNames = new Set(["CONTROL_PLANE_API_KEY", "PATCHWARDEN_OWNER_TOKEN", config.http?.ownerTokenEnv?.toUpperCase()]);
|
|
209
|
+
const blocked = envAllowlist.find((name) => blockedNames.has(name.toUpperCase()));
|
|
210
|
+
if (blocked) throw new Error(`agents["${agentName}"].envAllowlist cannot include reserved variable "${blocked}"`);
|
|
211
|
+
agents[agentName] = { ...agent, args: [...agent.args], envAllowlist: [...new Set(envAllowlist)] };
|
|
212
|
+
}
|
|
190
213
|
if (!Array.isArray(config.allowedTestCommands)) {
|
|
191
214
|
throw new Error("allowedTestCommands must be an array");
|
|
192
215
|
}
|
|
@@ -233,6 +256,46 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
233
256
|
) {
|
|
234
257
|
throw new Error('toolProfile must be "full", "chatgpt_core", "chatgpt_direct", or "chatgpt_search"');
|
|
235
258
|
}
|
|
259
|
+
if (config.repoAliases !== undefined) {
|
|
260
|
+
if (typeof config.repoAliases !== "object" || config.repoAliases === null || Array.isArray(config.repoAliases)) {
|
|
261
|
+
throw new Error("repoAliases must be an object mapping alias names to repository paths");
|
|
262
|
+
}
|
|
263
|
+
for (const [alias, target] of Object.entries(config.repoAliases)) {
|
|
264
|
+
if (typeof target !== "string") {
|
|
265
|
+
throw new Error(`repoAliases["${alias}"] must be a string`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (config.httpPort !== undefined) {
|
|
270
|
+
if (typeof config.httpPort !== "number" || !Number.isInteger(config.httpPort) || config.httpPort < 1 || config.httpPort > 65535) {
|
|
271
|
+
throw new Error("httpPort must be an integer from 1 to 65535");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (config.http !== undefined) {
|
|
275
|
+
if (typeof config.http !== "object" || config.http === null || Array.isArray(config.http)) {
|
|
276
|
+
throw new Error("http must be an object");
|
|
277
|
+
}
|
|
278
|
+
if (config.http.port !== undefined) {
|
|
279
|
+
if (typeof config.http.port !== "number" || !Number.isInteger(config.http.port) || config.http.port < 1 || config.http.port > 65535) {
|
|
280
|
+
throw new Error("http.port must be an integer from 1 to 65535");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (config.http.host !== undefined && typeof config.http.host !== "string") {
|
|
284
|
+
throw new Error("http.host must be a string");
|
|
285
|
+
}
|
|
286
|
+
if (
|
|
287
|
+
config.http.ownerTokenEnv !== undefined
|
|
288
|
+
&& (
|
|
289
|
+
typeof config.http.ownerTokenEnv !== "string"
|
|
290
|
+
|| !/^[A-Za-z_][A-Za-z0-9_]*$/.test(config.http.ownerTokenEnv)
|
|
291
|
+
)
|
|
292
|
+
) {
|
|
293
|
+
throw new Error("http.ownerTokenEnv must be a valid environment variable name");
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (config.enableRunTaskTool !== undefined && typeof config.enableRunTaskTool !== "boolean") {
|
|
297
|
+
throw new Error("enableRunTaskTool must be a boolean");
|
|
298
|
+
}
|
|
236
299
|
if (config.enableAgentAssessment !== undefined && typeof config.enableAgentAssessment !== "boolean") {
|
|
237
300
|
throw new Error("enableAgentAssessment must be a boolean");
|
|
238
301
|
}
|
|
@@ -252,6 +315,36 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
252
315
|
if (config.enableDirectProfile !== undefined && typeof config.enableDirectProfile !== "boolean") {
|
|
253
316
|
throw new Error("enableDirectProfile must be a boolean");
|
|
254
317
|
}
|
|
318
|
+
if (config.tunnelClientPath !== undefined) {
|
|
319
|
+
if (typeof config.tunnelClientPath !== "string" || !isAbsolute(config.tunnelClientPath)) {
|
|
320
|
+
throw new Error("tunnelClientPath must be an absolute path");
|
|
321
|
+
}
|
|
322
|
+
if (basename(config.tunnelClientPath).toLowerCase() !== "tunnel-client.exe") {
|
|
323
|
+
throw new Error("tunnelClientPath must point to tunnel-client.exe");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (config.tunnelProxy !== undefined) {
|
|
327
|
+
if (!config.tunnelProxy || typeof config.tunnelProxy !== "object" || !["shared", "separate"].includes(config.tunnelProxy.scope)) {
|
|
328
|
+
throw new Error("tunnelProxy.scope must be shared or separate");
|
|
329
|
+
}
|
|
330
|
+
for (const key of ["core", "direct"] as const) {
|
|
331
|
+
const endpoint = config.tunnelProxy[key];
|
|
332
|
+
if (!endpoint || typeof endpoint !== "object" || !["environment", "none", "manual"].includes(endpoint.mode)) {
|
|
333
|
+
throw new Error(`tunnelProxy.${key}.mode must be environment, none, or manual`);
|
|
334
|
+
}
|
|
335
|
+
if (endpoint.mode === "manual") {
|
|
336
|
+
if (typeof endpoint.url !== "string") throw new Error(`tunnelProxy.${key}.url is required in manual mode`);
|
|
337
|
+
let parsed: URL;
|
|
338
|
+
try { parsed = new URL(endpoint.url); } catch { throw new Error(`tunnelProxy.${key}.url must be a valid URL`); }
|
|
339
|
+
if (!["http:", "https:", "socks5:"].includes(parsed.protocol)) {
|
|
340
|
+
throw new Error(`tunnelProxy.${key}.url must use http, https, or socks5`);
|
|
341
|
+
}
|
|
342
|
+
if (parsed.username || parsed.password) throw new Error(`tunnelProxy.${key}.url must not contain credentials`);
|
|
343
|
+
} else if (endpoint.url !== undefined) {
|
|
344
|
+
throw new Error(`tunnelProxy.${key}.url is only allowed in manual mode`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
255
348
|
if (!config.directSessionsDir || typeof config.directSessionsDir !== "string") {
|
|
256
349
|
throw new Error("directSessionsDir must be a non-empty string");
|
|
257
350
|
}
|
|
@@ -294,6 +387,7 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
294
387
|
|
|
295
388
|
return {
|
|
296
389
|
...config,
|
|
390
|
+
agents,
|
|
297
391
|
workspaceRoot: resolve(config.workspaceRoot),
|
|
298
392
|
allowedTestCommands: [...new Set(config.allowedTestCommands.map((command) => command.trim()))],
|
|
299
393
|
repoAllowedTestCommands,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { buildChildEnvironment, resolveTrustedExecutable } from "../runner/processSecurity.js";
|
|
4
|
+
|
|
5
|
+
const FILE_MANAGER_ENVIRONMENT = [
|
|
6
|
+
"DISPLAY",
|
|
7
|
+
"WAYLAND_DISPLAY",
|
|
8
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
9
|
+
"XDG_RUNTIME_DIR",
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
/** Launch a trusted platform file manager without inheriting ambient secrets. */
|
|
13
|
+
export function launchFileManager(target: string, cwd: string): boolean {
|
|
14
|
+
try {
|
|
15
|
+
const env = buildChildEnvironment({ cwd, allowedNames: FILE_MANAGER_ENVIRONMENT });
|
|
16
|
+
const requested = fileManagerCommand(env);
|
|
17
|
+
const command = resolveTrustedExecutable(requested, cwd, { pathValue: env.PATH });
|
|
18
|
+
const child = spawn(command, [target], {
|
|
19
|
+
cwd,
|
|
20
|
+
detached: true,
|
|
21
|
+
env,
|
|
22
|
+
stdio: "ignore",
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
});
|
|
25
|
+
child.on("error", () => { /* opening a convenience viewer is best effort */ });
|
|
26
|
+
child.unref();
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fileManagerCommand(env: NodeJS.ProcessEnv): string {
|
|
34
|
+
if (process.platform === "win32") {
|
|
35
|
+
const windowsRoot = env.SystemRoot || env.WINDIR;
|
|
36
|
+
return windowsRoot ? join(windowsRoot, "explorer.exe") : "explorer.exe";
|
|
37
|
+
}
|
|
38
|
+
if (process.platform === "darwin") return "open";
|
|
39
|
+
return "xdg-open";
|
|
40
|
+
}
|