patchwarden 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -1,17 +1,23 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
- import { createWriteStream, existsSync, readFileSync, writeFileSync, } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { existsSync, readFileSync, } from "node:fs";
3
4
  import { isAbsolute, join, relative, resolve } from "node:path";
4
5
  import { getTasksDir, getPlansDir, getConfig, resolveWorkspaceRoot } from "../config.js";
5
6
  import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
6
7
  import { guardTestCommand, } from "../security/commandGuard.js";
7
- import { writeTaskProgress } from "../taskProgress.js";
8
- import { writeTaskRuntime } from "../taskRuntime.js";
8
+ import { writeTaskProgress } from "./taskProgress.js";
9
+ import { writeTaskRuntime } from "./taskRuntime.js";
10
+ import { claimPendingTask, readTaskStatusFile, updateTaskStatusFile, } from "./taskStatusStore.js";
9
11
  import { validateAssessmentFreshness } from "../assessments/assessmentStore.js";
12
+ import { recordAssessmentValidationFailure } from "../assessments/assessmentDiagnostics.js";
10
13
  import { buildAgentInvocation, buildExecutionPrompt } from "./agentInvocation.js";
11
14
  import { buildChangeArtifacts, captureRepoSnapshot, compareSnapshots, emptyArtifactHygiene, writeSnapshot, extractExternalDirtyFiles, findNewExternalDirtyFiles, buildArtifactManifest, groupChangedFiles, } from "./changeCapture.js";
12
15
  import { errorPayload } from "../errors.js";
13
- import { diagnoseAndroidBuild } from "../tools/androidDoctor.js";
16
+ import { ARTIFACT_SCHEMA_VERSION } from "../version.js";
17
+ import { diagnoseAndroidBuild } from "../tools/workspace/androidDoctor.js";
14
18
  import { runPostTaskCleanup } from "./postTaskCleanup.js";
19
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
20
+ import { allowedEnvironmentValues, buildChildEnvironment, redactProcessOutput, resolvePackageManagerInvocation, resolveTrustedExecutable, SecureProcessLogCapture, } from "./processSecurity.js";
15
21
  const HEARTBEAT_INTERVAL_MS = 2000;
16
22
  const GRACEFUL_KILL_MS = 2000;
17
23
  const MAX_CAPTURE_CHARS = 100_000;
@@ -36,7 +42,25 @@ async function prepareTask(taskId) {
36
42
  const statusFile = join(taskDir, "status.json");
37
43
  if (!existsSync(statusFile))
38
44
  throw new Error(`Task not found: "${taskId}"`);
39
- const initialStatus = readStatus(statusFile);
45
+ const startedAtMs = Date.now();
46
+ const runnerInstanceId = randomUUID().replace(/-/g, "");
47
+ const claim = claimPendingTask(statusFile, {
48
+ status: "running",
49
+ phase: "preparing",
50
+ started_at: new Date(startedAtMs).toISOString(),
51
+ last_heartbeat_at: new Date(startedAtMs).toISOString(),
52
+ current_command: null,
53
+ error: null,
54
+ });
55
+ if (!claim.claimed) {
56
+ const currentStatus = String(claim.status.status || "failed");
57
+ return {
58
+ task_id: taskId,
59
+ status: currentStatus,
60
+ error: `Task is ${currentStatus}; only pending tasks can be claimed for execution.`,
61
+ };
62
+ }
63
+ const initialStatus = claim.status;
40
64
  const planId = String(initialStatus.plan_id || "");
41
65
  const agentName = String(initialStatus.agent || "");
42
66
  const rawRepoPath = String(initialStatus.resolved_repo_path || initialStatus.repo_path || wsRoot);
@@ -50,7 +74,6 @@ async function prepareTask(taskId) {
50
74
  catch (error) {
51
75
  return failBeforeExecution(taskId, taskDir, errorMessage(error), error);
52
76
  }
53
- const startedAtMs = Date.now();
54
77
  const deadlineMs = startedAtMs + timeoutSeconds * 1000;
55
78
  let repoPath;
56
79
  try {
@@ -73,7 +96,11 @@ async function prepareTask(taskId) {
73
96
  const preExecSnapshot = await captureRepoSnapshot(repoPath);
74
97
  const validation = validateAssessmentFreshness(assessmentId, preExecSnapshot);
75
98
  if (!validation.valid) {
76
- const message = `assessment validation failed: ${validation.failure_reason}. Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.`;
99
+ recordAssessmentValidationFailure(validation);
100
+ const changedFields = validation.config_change_categories?.length
101
+ ? ` Changed fields: ${validation.config_change_categories.join(", ")}.`
102
+ : "";
103
+ const message = `assessment validation failed: ${validation.failure_reason}.${changedFields} Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.`;
77
104
  return failBeforeExecution(taskId, taskDir, message);
78
105
  }
79
106
  }
@@ -82,18 +109,12 @@ async function prepareTask(taskId) {
82
109
  return failBeforeExecution(taskId, taskDir, message, error);
83
110
  }
84
111
  }
85
- updateStatus(taskDir, {
86
- status: "running",
87
- phase: "preparing",
88
- started_at: new Date(startedAtMs).toISOString(),
89
- last_heartbeat_at: new Date().toISOString(),
90
- timeout_seconds: timeoutSeconds,
91
- current_command: null,
92
- error: null,
93
- });
112
+ updateStatus(taskDir, { timeout_seconds: timeoutSeconds });
94
113
  writeTaskRuntime(taskDir, {
95
114
  task_started_at: new Date(startedAtMs).toISOString(),
96
115
  watcher_instance_id: process.env.PATCHWARDEN_WATCHER_INSTANCE_ID || undefined,
116
+ runner_pid: process.pid,
117
+ runner_instance_id: runnerInstanceId,
97
118
  });
98
119
  setTaskPhase(taskDir, "preparing", null, "Capturing pre-task repository state.");
99
120
  let beforeSnapshot;
@@ -113,12 +134,13 @@ async function prepareTask(taskId) {
113
134
  return {
114
135
  taskId, taskDir, statusFile, repoPath, wsRoot, config, plansDir, initialStatus,
115
136
  planId, agentName, testCommand, changePolicy, verifyCommands, timeoutSeconds,
116
- startedAtMs, deadlineMs, beforeSnapshot, beforeWorkspaceSnapshot, externalDirtyBaseline,
137
+ startedAtMs, deadlineMs, runnerInstanceId, beforeSnapshot, beforeWorkspaceSnapshot, externalDirtyBaseline,
117
138
  };
118
139
  }
119
140
  async function executeAgent(ctx) {
120
141
  const state = {
121
142
  agentResult: null,
143
+ agentFailureCategory: null,
122
144
  testResult: skippedTest(ctx.testCommand, ctx.repoPath, "Agent did not complete successfully."),
123
145
  verifyResults: [],
124
146
  finalStatus: "failed",
@@ -142,8 +164,11 @@ async function executeAgent(ctx) {
142
164
  phase: "executing_agent",
143
165
  currentCommand: invocation.commandLabel,
144
166
  deadlineMs: ctx.deadlineMs,
167
+ runnerInstanceId: ctx.runnerInstanceId,
145
168
  stdoutPath: join(ctx.taskDir, "stdout.log"),
146
169
  stderrPath: join(ctx.taskDir, "stderr.log"),
170
+ environmentVariableNames: invocation.environmentVariableNames,
171
+ blockedEnvironmentVariableNames: invocation.blockedEnvironmentVariableNames,
147
172
  });
148
173
  const agentResult = state.agentResult;
149
174
  if (agentResult.terminationReason === "canceled" || agentResult.terminationReason === "killed") {
@@ -153,13 +178,17 @@ async function executeAgent(ctx) {
153
178
  : "Task was canceled by user request.";
154
179
  }
155
180
  else if (agentResult.terminationReason === "timeout") {
181
+ state.finalStatus = "timeout";
156
182
  state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during agent execution.`;
157
183
  }
158
184
  else if (agentResult.spawnError) {
159
185
  state.finalError = `Agent spawn failed: ${agentResult.spawnError}`;
160
186
  }
161
187
  else if (agentResult.exitCode !== 0) {
162
- state.finalError = `Agent exited with code ${agentResult.exitCode}.`;
188
+ state.agentFailureCategory = classifyAgentFailure(agentResult.stderr);
189
+ state.finalError = state.agentFailureCategory
190
+ ? `Agent provider failure (${state.agentFailureCategory}); process exited with code ${agentResult.exitCode}.`
191
+ : `Agent exited with code ${agentResult.exitCode}.`;
163
192
  }
164
193
  }
165
194
  catch (error) {
@@ -175,7 +204,7 @@ async function runVerification(ctx, state) {
175
204
  if (ctx.verifyCommands.length > 0) {
176
205
  for (const command of ctx.verifyCommands) {
177
206
  setTaskPhase(ctx.taskDir, "running_tests", command);
178
- const verification = await runTrustedTestCommand(command, ctx.repoPath, ctx.taskDir, ctx.statusFile, ctx.deadlineMs);
207
+ const verification = await runTrustedTestCommand(command, ctx.repoPath, ctx.taskDir, ctx.statusFile, ctx.deadlineMs, ctx.runnerInstanceId);
179
208
  state.verifyResults.push(verification);
180
209
  if (verification.terminationReason || Date.now() >= ctx.deadlineMs)
181
210
  break;
@@ -190,6 +219,7 @@ async function runVerification(ctx, state) {
190
219
  : "Task was canceled during verification.";
191
220
  }
192
221
  else if (interrupted?.terminationReason === "timeout" || Date.now() >= ctx.deadlineMs) {
222
+ state.finalStatus = "timeout";
193
223
  state.finalError = `Task timed out after ${ctx.timeoutSeconds} seconds during verification.`;
194
224
  }
195
225
  else if (failedVerification) {
@@ -229,7 +259,7 @@ async function collectArtifacts(ctx, state) {
229
259
  skipped: [{ path: ".", reason: "post_task_cleanup", skip_reason: errorMessage(error) }],
230
260
  source_files_touched: 0,
231
261
  };
232
- writeFileSync(join(ctx.taskDir, "post-task-cleanup.json"), JSON.stringify(cleanupReport, null, 2), "utf-8");
262
+ atomicWriteJsonFileSync(join(ctx.taskDir, "post-task-cleanup.json"), cleanupReport);
233
263
  updateStatus(ctx.taskDir, { cleanup: cleanupReport });
234
264
  }
235
265
  }
@@ -239,24 +269,16 @@ async function collectArtifacts(ctx, state) {
239
269
  let artifactStatus = "collected";
240
270
  let artifactCollectionError = null;
241
271
  let timeoutHandle;
272
+ const artifactController = new AbortController();
242
273
  try {
243
- const collectionResult = await Promise.race([
244
- Promise.resolve().then(async () => {
245
- const afterSnapshot = await captureRepoSnapshot(ctx.repoPath);
246
- writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
247
- return await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot);
248
- }),
249
- new Promise((_, reject) => {
250
- timeoutHandle = setTimeout(() => reject(new Error("Artifact collection timed out")), ARTIFACT_COLLECTION_TIMEOUT_MS);
251
- }),
252
- ]);
253
- if (timeoutHandle)
254
- clearTimeout(timeoutHandle);
255
- changes = collectionResult;
274
+ timeoutHandle = setTimeout(() => {
275
+ artifactController.abort(new Error("Artifact collection timed out"));
276
+ }, ARTIFACT_COLLECTION_TIMEOUT_MS);
277
+ const afterSnapshot = await captureRepoSnapshot(ctx.repoPath, artifactController.signal);
278
+ writeSnapshot(ctx.taskDir, "git-after.json", afterSnapshot);
279
+ changes = await buildChangeArtifacts(ctx.repoPath, ctx.beforeSnapshot, afterSnapshot, artifactController.signal);
256
280
  }
257
281
  catch (error) {
258
- if (timeoutHandle)
259
- clearTimeout(timeoutHandle);
260
282
  state.lastCaughtError = error;
261
283
  artifactCollectionError = errorMessage(error);
262
284
  artifactStatus = artifactCollectionError.includes("timed out") ? "timeout" : "failed";
@@ -275,7 +297,7 @@ async function collectArtifacts(ctx, state) {
275
297
  unavailable_reason: `Change capture failed: ${artifactCollectionError}`,
276
298
  artifact_hygiene: emptyArtifactHygiene(),
277
299
  };
278
- writeFileSync(join(ctx.taskDir, "partial_result.md"), [
300
+ atomicWriteFileSync(join(ctx.taskDir, "partial_result.md"), [
279
301
  "# PatchWarden Partial Result",
280
302
  "",
281
303
  `Task: ${ctx.taskId}`,
@@ -295,7 +317,7 @@ async function collectArtifacts(ctx, state) {
295
317
  "## Note",
296
318
  "Artifact collection did not complete. The task result may be incomplete. Review stdout.log, stderr.log, and verify.json for details.",
297
319
  "",
298
- ].join("\n"), "utf-8");
320
+ ].join("\n"));
299
321
  if (state.finalStatus === "done_by_agent") {
300
322
  state.finalError = `Change capture failed: ${artifactCollectionError}`;
301
323
  state.finalStatus = "failed";
@@ -304,6 +326,10 @@ async function collectArtifacts(ctx, state) {
304
326
  state.finalError ||= `Change capture failed: ${artifactCollectionError}`;
305
327
  }
306
328
  }
329
+ finally {
330
+ if (timeoutHandle)
331
+ clearTimeout(timeoutHandle);
332
+ }
307
333
  const artifactCollectionFinishedAt = new Date().toISOString();
308
334
  let outOfScopeChanges = [];
309
335
  const preexistingExternalDirty = ctx.externalDirtyBaseline;
@@ -326,18 +352,18 @@ async function collectArtifacts(ctx, state) {
326
352
  ? [`Pre-existing external dirty files (not caused by this task): ${preexistingExternalDirty.length} file(s)`]
327
353
  : [];
328
354
  applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, preexistingExternalDirty);
329
- writeFileSync(join(ctx.taskDir, "git.diff"), changes.diff, "utf-8");
330
- writeFileSync(join(ctx.taskDir, "diff.patch"), changes.diff, "utf-8");
331
- writeFileSync(join(ctx.taskDir, "changed-files.json"), JSON.stringify(changes, null, 2), "utf-8");
355
+ atomicWriteFileSync(join(ctx.taskDir, "git.diff"), changes.diff);
356
+ atomicWriteFileSync(join(ctx.taskDir, "diff.patch"), changes.diff);
357
+ atomicWriteJsonFileSync(join(ctx.taskDir, "changed-files.json"), { schema_version: ARTIFACT_SCHEMA_VERSION, ...changes });
332
358
  const artifactManifest = await buildArtifactManifest(changes.changed_files, ctx.repoPath, ctx.taskId);
333
- writeFileSync(join(ctx.taskDir, "artifact_manifest.json"), JSON.stringify(artifactManifest, null, 2), "utf-8");
359
+ atomicWriteJsonFileSync(join(ctx.taskDir, "artifact_manifest.json"), artifactManifest);
334
360
  const changedFileGroups = groupChangedFiles(changes.changed_files);
335
- writeFileSync(join(ctx.taskDir, "file-stats.json"), JSON.stringify({
361
+ atomicWriteJsonFileSync(join(ctx.taskDir, "file-stats.json"), {
336
362
  task_id: ctx.taskId,
337
363
  additions: changes.additions,
338
364
  deletions: changes.deletions,
339
365
  files: changes.file_stats,
340
- }, null, 2), "utf-8");
366
+ });
341
367
  return {
342
368
  changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt,
343
369
  outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport,
@@ -348,15 +374,15 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
348
374
  if (newOutOfScopeChanges.length > 0) {
349
375
  state.finalStatus = "failed_scope_violation";
350
376
  state.finalError = `Detected ${newOutOfScopeChanges.length} new change(s) outside resolved_repo_path during task execution.`;
351
- writeFileSync(join(ctx.taskDir, "rollback-plan.json"), JSON.stringify({
377
+ atomicWriteJsonFileSync(join(ctx.taskDir, "rollback-plan.json"), {
352
378
  task_id: ctx.taskId,
353
379
  status: "review_required",
354
380
  automatic_rollback_performed: false,
355
381
  warning: "Review ownership and concurrent edits before rollback. PatchWarden did not modify or restore these files.",
356
382
  out_of_scope_changes: newOutOfScopeChanges,
357
383
  preexisting_external_dirty_files: preexistingExternalDirty,
358
- }, null, 2), "utf-8");
359
- writeFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
384
+ });
385
+ atomicWriteFileSync(join(ctx.taskDir, "rollback_scope_violation_plan.md"), [
360
386
  "# Scope Violation Rollback Plan",
361
387
  "",
362
388
  `Task: ${ctx.taskId}`,
@@ -366,7 +392,11 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
366
392
  "## New out-of-scope files (caused by this task)",
367
393
  ...newOutOfScopeChanges.map((file) => `- ${file.change}: ${file.path}`),
368
394
  "",
369
- ].join("\n"), "utf-8");
395
+ ].join("\n"));
396
+ }
397
+ else if (changes.diff_redacted) {
398
+ state.finalStatus = "failed_policy_violation";
399
+ state.finalError = "Credential-like content was detected in the task diff. PatchWarden redacted the evidence; remove the sensitive content before retrying.";
370
400
  }
371
401
  else if (ctx.changePolicy === "no_changes" && changes.changed_files.length > 0) {
372
402
  state.finalStatus = "failed_policy_violation";
@@ -375,7 +405,7 @@ function applyScopeViolationVerdict(ctx, state, changes, newOutOfScopeChanges, p
375
405
  }
376
406
  function finalizeTask(ctx, state, evidence) {
377
407
  const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, preexistingWarnings, cleanupReport, artifactManifest, changedFileGroups } = evidence;
378
- writeFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult), "utf-8");
408
+ atomicWriteFileSync(join(ctx.taskDir, "test.log"), buildTestLog(state.testResult));
379
409
  const verifyJson = buildVerifyJson(ctx.verifyCommands, state.verifyResults, ctx.repoPath);
380
410
  if (newOutOfScopeChanges.length > 0) {
381
411
  verifyJson.status = "failed";
@@ -383,11 +413,13 @@ function finalizeTask(ctx, state, evidence) {
383
413
  }
384
414
  else if (state.finalStatus === "failed_policy_violation") {
385
415
  verifyJson.status = "failed";
386
- verifyJson.failure_reason = "change_policy_violation";
416
+ verifyJson.failure_reason = changes.diff_redacted
417
+ ? "sensitive_content_violation"
418
+ : "change_policy_violation";
387
419
  }
388
- writeFileSync(join(ctx.taskDir, "verify.json"), JSON.stringify(verifyJson, null, 2), "utf-8");
389
- writeFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands), "utf-8");
390
- if (!["canceled", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus))
420
+ atomicWriteJsonFileSync(join(ctx.taskDir, "verify.json"), verifyJson);
421
+ atomicWriteFileSync(join(ctx.taskDir, "verify.log"), buildVerifyLog(verifyJson.commands));
422
+ if (!["canceled", "timeout", "done_by_agent", "failed_verification", "failed_scope_violation", "failed_policy_violation"].includes(state.finalStatus))
391
423
  state.finalStatus = "failed";
392
424
  const finalPhase = state.finalStatus === "done_by_agent" ? "done_by_agent" : state.finalStatus;
393
425
  const followup = buildFailureFollowup(state.finalStatus, state.finalError, verifyJson.commands);
@@ -422,17 +454,17 @@ function finalizeTask(ctx, state, evidence) {
422
454
  androidWarning,
423
455
  preexistingWarnings,
424
456
  });
425
- writeFileSync(join(ctx.taskDir, "result.md"), resultMd, "utf-8");
457
+ atomicWriteFileSync(join(ctx.taskDir, "result.md"), resultMd);
426
458
  const resultJson = buildResultJson({
427
459
  ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning,
428
460
  });
429
- writeFileSync(join(ctx.taskDir, "result.json"), JSON.stringify(resultJson, null, 2), "utf-8");
461
+ atomicWriteJsonFileSync(join(ctx.taskDir, "result.json"), resultJson);
430
462
  if (state.finalError) {
431
463
  const structuredError = errorPayload(state.lastCaughtError);
432
- writeFileSync(join(ctx.taskDir, "error.log"), JSON.stringify({
464
+ atomicWriteJsonFileSync(join(ctx.taskDir, "error.log"), {
433
465
  summary: state.finalError,
434
466
  ...structuredError,
435
- }, null, 2), "utf-8");
467
+ });
436
468
  }
437
469
  const finishedAt = new Date().toISOString();
438
470
  updateStatus(ctx.taskDir, {
@@ -442,6 +474,10 @@ function finalizeTask(ctx, state, evidence) {
442
474
  last_heartbeat_at: finishedAt,
443
475
  finished_at: finishedAt,
444
476
  error: state.finalError,
477
+ termination_reason: state.finalStatus === "timeout"
478
+ ? "timeout"
479
+ : state.finalStatus === "canceled" ? "canceled" : null,
480
+ agent_failure_category: state.agentFailureCategory,
445
481
  changed_files: changes.changed_files.map(({ path, change }) => ({ path, change })),
446
482
  artifact_hygiene_counts: changes.artifact_hygiene.counts,
447
483
  artifact_status: artifactStatus,
@@ -456,6 +492,8 @@ function finalizeTask(ctx, state, evidence) {
456
492
  verify_commands: ctx.verifyCommands,
457
493
  diff_available: changes.diff_available,
458
494
  diff_truncated: changes.diff_truncated,
495
+ diff_redacted: changes.diff_redacted === true,
496
+ diff_redaction_categories: changes.diff_redaction_categories ?? [],
459
497
  workspace_dirty_before: changes.workspace_dirty_before,
460
498
  workspace_dirty_after: changes.workspace_dirty_after,
461
499
  workspace_dirty: changes.workspace_dirty_after,
@@ -478,6 +516,7 @@ function buildResultJson(input) {
478
516
  const { ctx, state, evidence, verifyJson, followup, androidDiagnostic, androidWarning } = input;
479
517
  const { changes, artifactStatus, artifactCollectionError, artifactCollectionStartedAt, artifactCollectionFinishedAt, outOfScopeChanges, newOutOfScopeChanges, preexistingExternalDirty, cleanupReport, artifactManifest, changedFileGroups } = evidence;
480
518
  return {
519
+ schema_version: ARTIFACT_SCHEMA_VERSION,
481
520
  task_id: ctx.taskId,
482
521
  status: state.finalStatus,
483
522
  agent: ctx.agentName,
@@ -488,6 +527,10 @@ function buildResultJson(input) {
488
527
  template: ctx.initialStatus.template || null,
489
528
  change_policy: ctx.changePolicy,
490
529
  summary: state.finalError || "Agent execution and configured verification completed successfully.",
530
+ termination_reason: state.finalStatus === "timeout"
531
+ ? "timeout"
532
+ : state.finalStatus === "canceled" ? "canceled" : null,
533
+ agent_failure_category: state.agentFailureCategory,
491
534
  changed_files: changes.changed_files,
492
535
  changed_file_groups: {
493
536
  source_changes: changedFileGroups.source_changes.length,
@@ -527,6 +570,9 @@ function buildResultJson(input) {
527
570
  warnings: [
528
571
  ...ctx.beforeSnapshot.warnings,
529
572
  ...(changes.diff_truncated ? ["diff.patch was truncated; changed-files.json retains file evidence."] : []),
573
+ ...(changes.diff_redacted ? [
574
+ `Credential-like diff content was redacted (${(changes.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`,
575
+ ] : []),
530
576
  ...evidence.preexistingWarnings,
531
577
  ...(androidWarning ? [androidWarning] : []),
532
578
  ],
@@ -594,13 +640,22 @@ async function runManagedProcess(options) {
594
640
  if (Date.now() >= options.deadlineMs) {
595
641
  return { exitCode: null, stdout: "", stderr: "", spawnError: null, terminationReason: "timeout" };
596
642
  }
643
+ const exactRedactionValues = allowedEnvironmentValues(options.environmentVariableNames);
644
+ const logCapture = new SecureProcessLogCapture([options.stdoutPath, options.stderrPath], options.maxLogBytes);
597
645
  let child;
598
646
  try {
599
- child = spawn(options.command, options.args, {
647
+ const env = buildChildEnvironment({
648
+ cwd: options.cwd,
649
+ allowedNames: options.environmentVariableNames,
650
+ blockedNames: options.blockedEnvironmentVariableNames,
651
+ });
652
+ const command = resolveTrustedExecutable(options.command, options.cwd, { pathValue: env.PATH });
653
+ child = spawn(command, options.args, {
600
654
  cwd: options.cwd,
601
655
  stdio: ["ignore", "pipe", "pipe"],
602
656
  detached: process.platform !== "win32",
603
657
  windowsHide: true,
658
+ env,
604
659
  });
605
660
  }
606
661
  catch (error) {
@@ -611,9 +666,10 @@ async function runManagedProcess(options) {
611
666
  writeTaskRuntime(options.taskDir, {
612
667
  child_pid: child.pid,
613
668
  child_started_at: new Date().toISOString(),
669
+ runner_pid: process.pid,
670
+ runner_instance_id: options.runnerInstanceId,
671
+ child_owned_by_runner_instance_id: options.runnerInstanceId,
614
672
  });
615
- const stdoutStream = openStream(options.stdoutPath);
616
- const stderrStream = openStream(options.stderrPath);
617
673
  let stdout = "";
618
674
  let stderr = "";
619
675
  let spawnError = null;
@@ -680,12 +736,12 @@ async function runManagedProcess(options) {
680
736
  child.stdout?.on("data", (chunk) => {
681
737
  const text = chunk.toString("utf-8");
682
738
  stdout = appendBounded(stdout, text);
683
- stdoutStream?.write(text);
739
+ logCapture.append(options.stdoutPath, chunk);
684
740
  });
685
741
  child.stderr?.on("data", (chunk) => {
686
742
  const text = chunk.toString("utf-8");
687
743
  stderr = appendBounded(stderr, text);
688
- stderrStream?.write(text);
744
+ logCapture.append(options.stderrPath, chunk);
689
745
  });
690
746
  heartbeat();
691
747
  const heartbeatTimer = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
@@ -702,12 +758,6 @@ async function runManagedProcess(options) {
702
758
  };
703
759
  childExitFinish = finish;
704
760
  child.once("close", (code) => finish(code));
705
- child.once("exit", (code) => {
706
- // On Windows, "close" can lag behind "exit" when stdio pipes drain
707
- // after taskkill. Resolve on exit to avoid hanging the runner, but
708
- // still let "close" fire if it arrives first.
709
- finish(code);
710
- });
711
761
  child.once("error", (error) => {
712
762
  spawnError = error.message;
713
763
  finish(null);
@@ -716,8 +766,7 @@ async function runManagedProcess(options) {
716
766
  clearInterval(heartbeatTimer);
717
767
  if (forceTimer)
718
768
  clearTimeout(forceTimer);
719
- stdoutStream?.destroy();
720
- stderrStream?.destroy();
769
+ logCapture.flush(exactRedactionValues);
721
770
  writeTaskRuntime(options.taskDir, {
722
771
  phase: options.phase,
723
772
  last_heartbeat_at: new Date().toISOString(),
@@ -725,9 +774,17 @@ async function runManagedProcess(options) {
725
774
  runner_pid: process.pid,
726
775
  child_pid: undefined,
727
776
  });
728
- return { exitCode, stdout, stderr, spawnError, terminationReason };
777
+ const safeStdout = redactProcessOutput(stdout, exactRedactionValues);
778
+ const safeStderr = redactProcessOutput(stderr, exactRedactionValues);
779
+ return {
780
+ exitCode,
781
+ stdout: safeStdout.length > MAX_CAPTURE_CHARS ? safeStdout.slice(-MAX_CAPTURE_CHARS) : safeStdout,
782
+ stderr: safeStderr.length > MAX_CAPTURE_CHARS ? safeStderr.slice(-MAX_CAPTURE_CHARS) : safeStderr,
783
+ spawnError,
784
+ terminationReason,
785
+ };
729
786
  }
730
- async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile, deadlineMs) {
787
+ async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile, deadlineMs, runnerInstanceId) {
731
788
  const config = getConfig();
732
789
  const trusted = guardTestCommand(testCommand, config, repoPath);
733
790
  const startedAtMs = Date.now();
@@ -736,9 +793,9 @@ async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile,
736
793
  let command = parts[0];
737
794
  let args = parts.slice(1);
738
795
  if (process.platform === "win32" && /^(npm|npm\.cmd|pnpm|pnpm\.cmd)$/i.test(command)) {
739
- const shim = /^pnpm/i.test(command) ? "pnpm.cmd" : "npm.cmd";
740
- command = process.env.ComSpec || "cmd.exe";
741
- args = ["/d", "/s", "/c", shim, ...args];
796
+ const packageManager = resolvePackageManagerInvocation(command, repoPath);
797
+ command = packageManager.command;
798
+ args = [...packageManager.argsPrefix, ...args];
742
799
  }
743
800
  const result = await runManagedProcess({
744
801
  command,
@@ -749,6 +806,7 @@ async function runTrustedTestCommand(testCommand, repoPath, taskDir, statusFile,
749
806
  phase: "running_tests",
750
807
  currentCommand: trusted,
751
808
  deadlineMs,
809
+ runnerInstanceId,
752
810
  stdoutPath: join(taskDir, "test.stdout.log"),
753
811
  stderrPath: join(taskDir, "test.stderr.log"),
754
812
  });
@@ -963,10 +1021,10 @@ function normalizeTimeout(value, config) {
963
1021
  function failBeforeExecution(taskId, taskDir, message, caughtError) {
964
1022
  // Phase 9: Preserve PatchWardenError structured info in error.log
965
1023
  const structuredError = caughtError ? errorPayload(caughtError) : { error: message };
966
- writeFileSync(join(taskDir, "error.log"), JSON.stringify({
1024
+ atomicWriteJsonFileSync(join(taskDir, "error.log"), {
967
1025
  summary: message,
968
1026
  ...structuredError,
969
- }, null, 2), "utf-8");
1027
+ });
970
1028
  const current = readStatus(join(taskDir, "status.json"));
971
1029
  const now = new Date().toISOString();
972
1030
  const verify = {
@@ -974,17 +1032,17 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
974
1032
  requested_commands: Array.isArray(current.verify_commands) ? current.verify_commands : [],
975
1033
  commands: [],
976
1034
  };
977
- writeFileSync(join(taskDir, "verify.json"), JSON.stringify(verify, null, 2), "utf-8");
978
- writeFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`, "utf-8");
979
- writeFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`, "utf-8");
980
- writeFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n", "utf-8");
981
- writeFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n", "utf-8");
982
- writeFileSync(join(taskDir, "file-stats.json"), JSON.stringify({
1035
+ atomicWriteJsonFileSync(join(taskDir, "verify.json"), verify);
1036
+ atomicWriteFileSync(join(taskDir, "verify.log"), `Verification did not run: ${message}\n`);
1037
+ atomicWriteFileSync(join(taskDir, "test.log"), `(not run)\nExit code: not run\n${message}\n`);
1038
+ atomicWriteFileSync(join(taskDir, "git.diff"), "(task failed before change capture)\n");
1039
+ atomicWriteFileSync(join(taskDir, "diff.patch"), "(task failed before change capture)\n");
1040
+ atomicWriteJsonFileSync(join(taskDir, "file-stats.json"), {
983
1041
  task_id: taskId,
984
1042
  additions: 0,
985
1043
  deletions: 0,
986
1044
  files: [],
987
- }, null, 2), "utf-8");
1045
+ });
988
1046
  const result = {
989
1047
  task_id: taskId,
990
1048
  status: "failed",
@@ -1010,8 +1068,8 @@ function failBeforeExecution(taskId, taskDir, message, caughtError) {
1010
1068
  safe_followup_prompt: "Inspect result.json and error.log, correct the task metadata or configuration, and retry without widening repository scope.",
1011
1069
  next_steps: ["Fix task metadata or configuration and retry the task."],
1012
1070
  };
1013
- writeFileSync(join(taskDir, "result.json"), JSON.stringify(result, null, 2), "utf-8");
1014
- writeFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`, "utf-8");
1071
+ atomicWriteJsonFileSync(join(taskDir, "result.json"), result);
1072
+ atomicWriteFileSync(join(taskDir, "result.md"), `# PatchWarden Task Result\n\n## Status\nfailed\n\n## Summary\n${message}\n`);
1015
1073
  updateStatus(taskDir, {
1016
1074
  status: "failed",
1017
1075
  phase: "failed",
@@ -1037,12 +1095,10 @@ function setTaskPhase(taskDir, phase, currentCommand, note) {
1037
1095
  }
1038
1096
  function updateStatus(taskDir, patch) {
1039
1097
  const statusFile = join(taskDir, "status.json");
1040
- const current = readStatus(statusFile);
1041
- const next = { ...current, ...patch, updated_at: new Date().toISOString() };
1042
- writeFileSync(statusFile, JSON.stringify(next, null, 2), "utf-8");
1098
+ updateTaskStatusFile(statusFile, patch);
1043
1099
  }
1044
1100
  function readStatus(statusFile) {
1045
- return JSON.parse(readFileSync(statusFile, "utf-8"));
1101
+ return readTaskStatusFile(statusFile);
1046
1102
  }
1047
1103
  function gracefulKill(child) {
1048
1104
  try {
@@ -1058,10 +1114,13 @@ function forceKill(child) {
1058
1114
  return;
1059
1115
  try {
1060
1116
  if (process.platform === "win32") {
1061
- const result = spawnSync("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
1117
+ const systemRoot = process.env.SystemRoot || process.env.WINDIR || "C:\\Windows";
1118
+ const taskkill = resolveTrustedExecutable(`${systemRoot}\\System32\\taskkill.exe`, process.cwd());
1119
+ const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
1062
1120
  stdio: "ignore",
1063
1121
  timeout: 5000,
1064
1122
  windowsHide: true,
1123
+ env: buildChildEnvironment({ cwd: process.cwd() }),
1065
1124
  });
1066
1125
  if (result.status !== 0)
1067
1126
  child.kill("SIGKILL");
@@ -1077,9 +1136,6 @@ function forceKill(child) {
1077
1136
  catch { } // cleanup failure is safe to ignore
1078
1137
  }
1079
1138
  }
1080
- function openStream(path) {
1081
- return path ? createWriteStream(path, { flags: "a" }) : null;
1082
- }
1083
1139
  function appendBounded(current, next) {
1084
1140
  const combined = current + next;
1085
1141
  return combined.length <= MAX_CAPTURE_CHARS
@@ -1089,3 +1145,16 @@ function appendBounded(current, next) {
1089
1145
  function errorMessage(error) {
1090
1146
  return error instanceof Error ? error.message : String(error);
1091
1147
  }
1148
+ export function classifyAgentFailure(stderr) {
1149
+ const normalized = stderr.toLowerCase();
1150
+ if (normalized.includes("insufficient balance") || normalized.includes("insufficient credits")) {
1151
+ return "provider_insufficient_balance";
1152
+ }
1153
+ if (normalized.includes("unauthorized") || normalized.includes("authentication failed") || normalized.includes("invalid api key")) {
1154
+ return "provider_authentication_failed";
1155
+ }
1156
+ if (normalized.includes("model not found") || normalized.includes("model access") || normalized.includes("permission denied")) {
1157
+ return "provider_model_unavailable";
1158
+ }
1159
+ return null;
1160
+ }
@@ -7,6 +7,9 @@ export interface SimpleProcessOptions {
7
7
  maxStderrBytes?: number;
8
8
  stdoutPath?: string;
9
9
  stderrPath?: string;
10
+ environmentVariableNames?: string[];
11
+ blockedEnvironmentVariableNames?: string[];
12
+ maxLogBytes?: number;
10
13
  }
11
14
  export interface SimpleProcessResult {
12
15
  exitCode: number | null;