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.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -1,6 +1,51 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { existsSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { chmodSync, existsSync, lstatSync, readdirSync, rmSync } from "node:fs";
3
3
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { platform } from "node:os";
5
+ import { buildGitEnvironment, resolveTrustedExecutable } from "./processSecurity.js";
6
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
7
+ const isWindows = platform() === "win32";
8
+ /**
9
+ * Remove a file or directory, working around Node.js fs.rmSync issues on Windows
10
+ * where recursive directory deletion silently fails (Node v22+).
11
+ * On Windows, clears read-only attributes before one bounded native retry.
12
+ */
13
+ function safeRemoveSync(targetPath) {
14
+ rmSync(targetPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
15
+ if (isWindows && existsSync(targetPath)) {
16
+ makeWritableSync(targetPath);
17
+ rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 150 });
18
+ }
19
+ if (existsSync(targetPath)) {
20
+ throw new Error(`Cleanup path still exists after bounded retries: ${targetPath}`);
21
+ }
22
+ }
23
+ function makeWritableSync(targetPath) {
24
+ let stat;
25
+ try {
26
+ stat = lstatSync(targetPath);
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ if (stat.isSymbolicLink())
32
+ return;
33
+ try {
34
+ chmodSync(targetPath, stat.isDirectory() ? 0o700 : 0o600);
35
+ }
36
+ catch { /* best effort */ }
37
+ if (!stat.isDirectory())
38
+ return;
39
+ let entries;
40
+ try {
41
+ entries = readdirSync(targetPath);
42
+ }
43
+ catch {
44
+ return;
45
+ }
46
+ for (const entry of entries)
47
+ makeWritableSync(join(targetPath, entry));
48
+ }
4
49
  const EXCLUDED_DIRS = new Set([".git", ".patchwarden", ".venv", "node_modules", "samples", "docs"]);
5
50
  const CLEANUP_DIRS = new Set(["__pycache__", "dist", "release_packages", "temp_files"]);
6
51
  const CLEANUP_FILES = [/\.pyc$/i];
@@ -28,7 +73,7 @@ export function runPostTaskCleanup(repoPath, taskDir) {
28
73
  continue;
29
74
  }
30
75
  try {
31
- rmSync(candidate.path, { recursive: true, force: true });
76
+ safeRemoveSync(candidate.path);
32
77
  report.removed.push({ path: rel, reason: candidate.reason });
33
78
  }
34
79
  catch (error) {
@@ -39,7 +84,7 @@ export function runPostTaskCleanup(repoPath, taskDir) {
39
84
  });
40
85
  }
41
86
  }
42
- writeFileSync(join(taskDir, "post-task-cleanup.json"), JSON.stringify(report, null, 2), "utf-8");
87
+ atomicWriteJsonFileSync(join(taskDir, "post-task-cleanup.json"), report);
43
88
  return report;
44
89
  }
45
90
  function collectCandidates(root) {
@@ -85,10 +130,12 @@ function hasTrackedGitContent(root, rel) {
85
130
  if (!isGitWorktree(root))
86
131
  return false;
87
132
  try {
88
- const output = execFileSync("git", ["ls-files", "--", rel], {
133
+ const env = buildGitEnvironment(root);
134
+ const output = execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["ls-files", "--", rel], {
89
135
  cwd: root,
90
136
  encoding: "utf-8",
91
137
  windowsHide: true,
138
+ env,
92
139
  });
93
140
  return output.trim().length > 0;
94
141
  }
@@ -99,20 +146,24 @@ function hasTrackedGitContent(root, rel) {
99
146
  function isIgnoredOrUntracked(root, rel) {
100
147
  if (!isGitWorktree(root))
101
148
  return true;
149
+ const env = buildGitEnvironment(root);
150
+ const git = resolveTrustedExecutable("git", root, { pathValue: env.PATH });
102
151
  try {
103
- execFileSync("git", ["check-ignore", "-q", "--", rel], {
152
+ execFileSync(git, ["check-ignore", "-q", "--", rel], {
104
153
  cwd: root,
105
154
  stdio: "ignore",
106
155
  windowsHide: true,
156
+ env,
107
157
  });
108
158
  return true;
109
159
  }
110
160
  catch {
111
161
  try {
112
- const output = execFileSync("git", ["ls-files", "--others", "--exclude-standard", "--", rel], {
162
+ const output = execFileSync(git, ["ls-files", "--others", "--exclude-standard", "--", rel], {
113
163
  cwd: root,
114
164
  encoding: "utf-8",
115
165
  windowsHide: true,
166
+ env,
116
167
  });
117
168
  return output.trim().length > 0 || !existsSync(resolve(root, rel));
118
169
  }
@@ -123,10 +174,12 @@ function isIgnoredOrUntracked(root, rel) {
123
174
  }
124
175
  function isGitWorktree(root) {
125
176
  try {
126
- return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
177
+ const env = buildGitEnvironment(root);
178
+ return execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["rev-parse", "--is-inside-work-tree"], {
127
179
  cwd: root,
128
180
  encoding: "utf-8",
129
181
  windowsHide: true,
182
+ env,
130
183
  }).trim() === "true";
131
184
  }
132
185
  catch {
@@ -0,0 +1,56 @@
1
+ export declare const DEFAULT_MAX_PROCESS_LOG_BYTES: number;
2
+ export interface ChildEnvironmentOptions {
3
+ cwd: string;
4
+ allowedNames?: readonly string[];
5
+ blockedNames?: readonly string[];
6
+ sourceEnvironment?: NodeJS.ProcessEnv;
7
+ platform?: NodeJS.Platform;
8
+ }
9
+ export interface ExecutableResolutionOptions {
10
+ platform?: NodeJS.Platform;
11
+ pathValue?: string;
12
+ pathExtValue?: string;
13
+ fileExists?: (path: string) => boolean;
14
+ }
15
+ export interface ResolvedProcessCommand {
16
+ command: string;
17
+ argsPrefix: string[];
18
+ }
19
+ export interface PreparedProcessCommand {
20
+ command: string;
21
+ args: string[];
22
+ }
23
+ /**
24
+ * Build the environment for a task-owned child. Provider credentials are not
25
+ * ambient capability: an agent receives them only when their variable names
26
+ * are explicitly allow-listed in trusted local configuration.
27
+ */
28
+ export declare function buildChildEnvironment(options: ChildEnvironmentOptions): NodeJS.ProcessEnv;
29
+ /** Build a non-interactive Git environment that disables repo-defined hooks and fsmonitor. */
30
+ export declare function buildGitEnvironment(cwd: string): NodeJS.ProcessEnv;
31
+ /** Resolve a Windows bare command before spawn so CreateProcess never searches cwd. */
32
+ export declare function resolveTrustedExecutable(command: string, cwd: string, options?: ExecutableResolutionOptions): string;
33
+ /**
34
+ * Resolve Windows package-manager shims to their JavaScript CLI entry point.
35
+ * This keeps npm/npx/pnpm execution shell-free while still supporting their
36
+ * normal .cmd installations.
37
+ */
38
+ export declare function resolvePackageManagerInvocation(managerCommand: string, cwd: string, options?: ExecutableResolutionOptions): ResolvedProcessCommand;
39
+ /** Replace the one supported Windows cmd wrapper with a native Node CLI. */
40
+ export declare function prepareShellFreeCommand(command: string, args: readonly string[], cwd: string, options?: ExecutableResolutionOptions): PreparedProcessCommand;
41
+ /** Parse the intentionally simple allow-listed command grammar without a shell. */
42
+ export declare function resolveTrustedCommandLine(commandLine: string, cwd: string, options?: ExecutableResolutionOptions): ResolvedProcessCommand;
43
+ export declare function sanitizeTrustedPath(pathValue: string, cwd: string, platform?: NodeJS.Platform): string;
44
+ export declare function allowedEnvironmentValues(names: readonly string[] | undefined, sourceEnvironment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string[];
45
+ export declare function redactProcessOutput(input: string, exactValues?: readonly string[]): string;
46
+ /** Captures complete process logs up to one auditable, redacted byte budget. */
47
+ export declare class SecureProcessLogCapture {
48
+ private readonly chunks;
49
+ private readonly maxBytes;
50
+ private readonly existingBytes;
51
+ private capturedBytes;
52
+ private truncatedPath;
53
+ constructor(paths: Array<string | undefined>, maxBytes?: number);
54
+ append(path: string | undefined, chunk: Buffer | string): void;
55
+ flush(exactValues?: readonly string[]): void;
56
+ }
@@ -0,0 +1,373 @@
1
+ import { appendFileSync, existsSync, statSync } from "node:fs";
2
+ import { delimiter, isAbsolute, relative, resolve, sep, win32 } from "node:path";
3
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
4
+ const WINDOWS_BASE_ENVIRONMENT = [
5
+ "SystemRoot",
6
+ "WINDIR",
7
+ "ComSpec",
8
+ "PATHEXT",
9
+ "PATH",
10
+ "TEMP",
11
+ "TMP",
12
+ "USERPROFILE",
13
+ "HOMEDRIVE",
14
+ "HOMEPATH",
15
+ "APPDATA",
16
+ "LOCALAPPDATA",
17
+ "PROGRAMDATA",
18
+ "ProgramFiles",
19
+ "ProgramFiles(x86)",
20
+ "ProgramW6432",
21
+ "HOME",
22
+ "XDG_CONFIG_HOME",
23
+ "XDG_CACHE_HOME",
24
+ "XDG_DATA_HOME",
25
+ "JAVA_HOME",
26
+ "ANDROID_HOME",
27
+ "ANDROID_SDK_ROOT",
28
+ "CARGO_HOME",
29
+ "RUSTUP_HOME",
30
+ "GOPATH",
31
+ "GOROOT",
32
+ "PYTHONHOME",
33
+ "VIRTUAL_ENV",
34
+ ];
35
+ const POSIX_BASE_ENVIRONMENT = [
36
+ "PATH",
37
+ "HOME",
38
+ "TMPDIR",
39
+ "USER",
40
+ "LOGNAME",
41
+ "SHELL",
42
+ "LANG",
43
+ "LC_ALL",
44
+ "LC_CTYPE",
45
+ "TERM",
46
+ "COLORTERM",
47
+ "NO_COLOR",
48
+ "FORCE_COLOR",
49
+ "XDG_CONFIG_HOME",
50
+ "XDG_CACHE_HOME",
51
+ "XDG_DATA_HOME",
52
+ ];
53
+ const ALWAYS_BLOCKED_ENVIRONMENT = new Set([
54
+ "CONTROL_PLANE_API_KEY",
55
+ "PATCHWARDEN_OWNER_TOKEN",
56
+ ]);
57
+ const WINDOWS_EXECUTABLE_EXTENSIONS = [".COM", ".EXE", ".BAT", ".CMD"];
58
+ const LOG_TRUNCATION_MARKER = "\n[PATCHWARDEN LOG TRUNCATED]\n";
59
+ export const DEFAULT_MAX_PROCESS_LOG_BYTES = 2 * 1024 * 1024;
60
+ /**
61
+ * Build the environment for a task-owned child. Provider credentials are not
62
+ * ambient capability: an agent receives them only when their variable names
63
+ * are explicitly allow-listed in trusted local configuration.
64
+ */
65
+ export function buildChildEnvironment(options) {
66
+ const source = options.sourceEnvironment ?? process.env;
67
+ const platform = options.platform ?? process.platform;
68
+ const blocked = new Set([
69
+ ...ALWAYS_BLOCKED_ENVIRONMENT,
70
+ ...(options.blockedNames ?? []).map((name) => name.toUpperCase()),
71
+ ]);
72
+ const requested = [
73
+ ...(platform === "win32" ? WINDOWS_BASE_ENVIRONMENT : POSIX_BASE_ENVIRONMENT),
74
+ ...(options.allowedNames ?? []),
75
+ ];
76
+ const environment = {};
77
+ for (const name of requested) {
78
+ if (!isEnvironmentVariableName(name)) {
79
+ throw new Error(`Invalid child environment variable name: "${name}"`);
80
+ }
81
+ if (blocked.has(name.toUpperCase()))
82
+ continue;
83
+ const entry = findEnvironmentEntry(source, name, platform);
84
+ if (entry && entry[1] !== undefined)
85
+ environment[entry[0]] = entry[1];
86
+ }
87
+ if (platform === "win32") {
88
+ // The MCP SDK's safe default environment includes SystemRoot and PATH but
89
+ // intentionally omits ComSpec and PATHEXT. npm 10 can start in that
90
+ // environment, but its lifecycle shell then exits before running even a
91
+ // simple Node command. Reconstruct only the standard Windows process
92
+ // plumbing from the already inherited OS root; never copy extra ambient
93
+ // variables or task-controlled values.
94
+ const systemRoot = findEnvironmentEntry(environment, "SystemRoot", platform)?.[1]
95
+ ?? findEnvironmentEntry(environment, "WINDIR", platform)?.[1];
96
+ if (!findEnvironmentEntry(environment, "ComSpec", platform) && systemRoot && win32.isAbsolute(systemRoot)) {
97
+ environment.ComSpec = win32.join(systemRoot, "System32", "cmd.exe");
98
+ }
99
+ if (!findEnvironmentEntry(environment, "PATHEXT", platform)) {
100
+ environment.PATHEXT = ".COM;.EXE;.BAT;.CMD";
101
+ }
102
+ }
103
+ const pathEntry = findEnvironmentEntry(environment, "PATH", platform);
104
+ if (pathEntry) {
105
+ environment[pathEntry[0]] = sanitizeTrustedPath(pathEntry[1] || "", options.cwd, platform);
106
+ }
107
+ return environment;
108
+ }
109
+ /** Build a non-interactive Git environment that disables repo-defined hooks and fsmonitor. */
110
+ export function buildGitEnvironment(cwd) {
111
+ const environment = buildChildEnvironment({ cwd });
112
+ environment.GIT_OPTIONAL_LOCKS = "0";
113
+ environment.GIT_TERMINAL_PROMPT = "0";
114
+ environment.GCM_INTERACTIVE = "Never";
115
+ environment.GIT_CONFIG_COUNT = "2";
116
+ environment.GIT_CONFIG_KEY_0 = "core.fsmonitor";
117
+ environment.GIT_CONFIG_VALUE_0 = "false";
118
+ environment.GIT_CONFIG_KEY_1 = "core.hooksPath";
119
+ environment.GIT_CONFIG_VALUE_1 = "/dev/null";
120
+ return environment;
121
+ }
122
+ /** Resolve a Windows bare command before spawn so CreateProcess never searches cwd. */
123
+ export function resolveTrustedExecutable(command, cwd, options = {}) {
124
+ const platform = options.platform ?? process.platform;
125
+ if (platform !== "win32" || win32.isAbsolute(command))
126
+ return command;
127
+ if (!command || /[\\/]/.test(command)) {
128
+ throw new Error(`Executable must be an absolute path or bare command name: "${command}"`);
129
+ }
130
+ const trustedPath = sanitizeTrustedPath(options.pathValue ?? process.env.PATH ?? "", cwd, platform);
131
+ const roots = trustedPath.split(win32.delimiter).filter(Boolean);
132
+ const extension = win32.extname(command);
133
+ const configuredExtensions = (options.pathExtValue ?? process.env.PATHEXT ?? "")
134
+ .split(win32.delimiter)
135
+ .map((value) => value.trim().toUpperCase())
136
+ .filter((value) => WINDOWS_EXECUTABLE_EXTENSIONS.includes(value));
137
+ const extensions = extension
138
+ ? [""]
139
+ : [...new Set([...configuredExtensions, ...WINDOWS_EXECUTABLE_EXTENSIONS])];
140
+ const fileExists = options.fileExists ?? existsSync;
141
+ for (const root of roots) {
142
+ for (const suffix of extensions) {
143
+ const candidate = win32.resolve(root, `${command}${suffix}`);
144
+ if (isPathInside(candidate, cwd, "win32"))
145
+ continue;
146
+ if (fileExists(candidate))
147
+ return candidate;
148
+ }
149
+ }
150
+ throw new Error(`Executable not found in trusted PATH: "${command}"`);
151
+ }
152
+ /**
153
+ * Resolve Windows package-manager shims to their JavaScript CLI entry point.
154
+ * This keeps npm/npx/pnpm execution shell-free while still supporting their
155
+ * normal .cmd installations.
156
+ */
157
+ export function resolvePackageManagerInvocation(managerCommand, cwd, options = {}) {
158
+ const platform = options.platform ?? process.platform;
159
+ if (platform !== "win32")
160
+ return { command: managerCommand, argsPrefix: [] };
161
+ const manager = win32.basename(managerCommand).replace(/\.(?:cmd|bat|exe|com)$/i, "").toLowerCase();
162
+ if (!new Set(["npm", "npx", "pnpm"]).has(manager)) {
163
+ throw new Error(`Unsupported package manager command: "${managerCommand}"`);
164
+ }
165
+ const resolved = resolveTrustedExecutable(managerCommand, cwd, options);
166
+ if (/\.(?:exe|com)$/i.test(resolved))
167
+ return { command: resolved, argsPrefix: [] };
168
+ const fileExists = options.fileExists ?? existsSync;
169
+ const roots = [];
170
+ let current = win32.dirname(resolved);
171
+ for (let depth = 0; depth < 5; depth++) {
172
+ roots.push(current);
173
+ const parent = win32.dirname(current);
174
+ if (parent === current)
175
+ break;
176
+ current = parent;
177
+ }
178
+ const relativeCliPaths = manager === "npm"
179
+ ? ["node_modules\\npm\\bin\\npm-cli.js"]
180
+ : manager === "npx"
181
+ ? ["node_modules\\npm\\bin\\npx-cli.js"]
182
+ : [
183
+ "node_modules\\pnpm\\bin\\pnpm.cjs",
184
+ "node_modules\\pnpm\\bin\\pnpm.mjs",
185
+ "node\\node_modules\\pnpm\\bin\\pnpm.cjs",
186
+ "node\\node_modules\\pnpm\\bin\\pnpm.mjs",
187
+ ];
188
+ for (const root of roots) {
189
+ for (const relativeCli of relativeCliPaths) {
190
+ const cliPath = win32.join(root, relativeCli);
191
+ if (fileExists(cliPath))
192
+ return { command: process.execPath, argsPrefix: [cliPath] };
193
+ }
194
+ }
195
+ throw new Error(`Native CLI entry point not found for trusted package-manager shim: "${resolved}"`);
196
+ }
197
+ /** Replace the one supported Windows cmd wrapper with a native Node CLI. */
198
+ export function prepareShellFreeCommand(command, args, cwd, options = {}) {
199
+ const platform = options.platform ?? process.platform;
200
+ if (platform !== "win32") {
201
+ return { command, args: [...args] };
202
+ }
203
+ // Node cannot reliably execute a .cmd shim with shell:false on Windows.
204
+ // Resolve direct package-manager invocations to their JavaScript CLI too,
205
+ // so callers do not need to manufacture a cmd.exe /c wrapper.
206
+ if (/^(?:npm|npx|pnpm)(?:\.cmd)?$/i.test(win32.basename(command))) {
207
+ const resolved = resolvePackageManagerInvocation(command, cwd, options);
208
+ return { command: resolved.command, args: [...resolved.argsPrefix, ...args] };
209
+ }
210
+ if (!/^cmd(?:\.exe)?$/i.test(win32.basename(command))) {
211
+ return { command, args: [...args] };
212
+ }
213
+ const commandIndex = args.findIndex((arg) => /^\/c$/i.test(arg));
214
+ const prefix = commandIndex >= 0 ? args.slice(0, commandIndex) : [];
215
+ const manager = commandIndex >= 0 ? args[commandIndex + 1] : undefined;
216
+ if (commandIndex < 0
217
+ || prefix.some((arg) => !/^\/(?:d|s)$/i.test(arg))
218
+ || !manager
219
+ || !/^(?:npm|npx|pnpm)(?:\.cmd)?$/i.test(manager)) {
220
+ throw new Error("Windows command shells are not allowed for task-owned child processes");
221
+ }
222
+ const resolved = resolvePackageManagerInvocation(manager, cwd, options);
223
+ return {
224
+ command: resolved.command,
225
+ args: [...resolved.argsPrefix, ...args.slice(commandIndex + 2)],
226
+ };
227
+ }
228
+ /** Parse the intentionally simple allow-listed command grammar without a shell. */
229
+ export function resolveTrustedCommandLine(commandLine, cwd, options = {}) {
230
+ const parts = commandLine.trim().split(/\s+/).filter(Boolean);
231
+ if (parts.length === 0)
232
+ throw new Error("Command must not be empty");
233
+ if (parts.some((part) => /[\x00-\x1f"'|&;<>()`$]/.test(part))) {
234
+ throw new Error(`Command contains unsupported shell syntax: "${commandLine}"`);
235
+ }
236
+ if (/^(?:npm|npx|pnpm)(?:\.cmd)?$/i.test(parts[0])) {
237
+ const manager = resolvePackageManagerInvocation(parts[0], cwd, options);
238
+ return { command: manager.command, argsPrefix: [...manager.argsPrefix, ...parts.slice(1)] };
239
+ }
240
+ return {
241
+ command: resolveTrustedExecutable(parts[0], cwd, options),
242
+ argsPrefix: parts.slice(1),
243
+ };
244
+ }
245
+ export function sanitizeTrustedPath(pathValue, cwd, platform = process.platform) {
246
+ const pathApi = platform === "win32" ? win32 : { delimiter, isAbsolute, relative, resolve, sep };
247
+ const seen = new Set();
248
+ const entries = [];
249
+ for (const rawEntry of pathValue.split(pathApi.delimiter)) {
250
+ const unquoted = rawEntry.trim().replace(/^"|"$/g, "");
251
+ if (!unquoted || !pathApi.isAbsolute(unquoted))
252
+ continue;
253
+ const absolute = pathApi.resolve(unquoted);
254
+ if (isPathInside(absolute, cwd, platform))
255
+ continue;
256
+ const comparable = platform === "win32" ? absolute.toLowerCase() : absolute;
257
+ if (seen.has(comparable))
258
+ continue;
259
+ seen.add(comparable);
260
+ entries.push(absolute);
261
+ }
262
+ return entries.join(pathApi.delimiter);
263
+ }
264
+ export function allowedEnvironmentValues(names, sourceEnvironment = process.env, platform = process.platform) {
265
+ const values = new Set();
266
+ for (const name of names ?? []) {
267
+ if (ALWAYS_BLOCKED_ENVIRONMENT.has(name.toUpperCase()))
268
+ continue;
269
+ const value = findEnvironmentEntry(sourceEnvironment, name, platform)?.[1];
270
+ if (value && Buffer.byteLength(value, "utf-8") >= 8)
271
+ values.add(value);
272
+ }
273
+ return [...values].sort((left, right) => right.length - left.length);
274
+ }
275
+ export function redactProcessOutput(input, exactValues = []) {
276
+ let content = redactSensitiveContent(input).content;
277
+ for (const value of exactValues) {
278
+ if (value)
279
+ content = content.split(value).join("[REDACTED ENV VALUE]");
280
+ }
281
+ return content;
282
+ }
283
+ /** Captures complete process logs up to one auditable, redacted byte budget. */
284
+ export class SecureProcessLogCapture {
285
+ chunks = new Map();
286
+ maxBytes;
287
+ existingBytes;
288
+ capturedBytes = 0;
289
+ truncatedPath = null;
290
+ constructor(paths, maxBytes = DEFAULT_MAX_PROCESS_LOG_BYTES) {
291
+ if (!Number.isInteger(maxBytes) || maxBytes <= 0) {
292
+ throw new Error("maxLogBytes must be a positive integer");
293
+ }
294
+ this.maxBytes = maxBytes;
295
+ const uniquePaths = [...new Set(paths.filter((path) => Boolean(path)))];
296
+ this.existingBytes = uniquePaths.reduce((total, path) => total + fileSize(path), 0);
297
+ }
298
+ append(path, chunk) {
299
+ if (!path)
300
+ return;
301
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8");
302
+ const remaining = Math.max(0, this.maxBytes - this.existingBytes - this.capturedBytes);
303
+ const accepted = data.subarray(0, remaining);
304
+ if (accepted.length > 0) {
305
+ const chunks = this.chunks.get(path) ?? [];
306
+ chunks.push(accepted);
307
+ this.chunks.set(path, chunks);
308
+ this.capturedBytes += accepted.length;
309
+ }
310
+ if (accepted.length < data.length)
311
+ this.truncatedPath = path;
312
+ }
313
+ flush(exactValues = []) {
314
+ if (this.chunks.size === 0 && !this.truncatedPath)
315
+ return;
316
+ const remaining = Math.max(0, this.maxBytes - this.existingBytes);
317
+ const entries = [...this.chunks.entries()];
318
+ if (this.truncatedPath && !this.chunks.has(this.truncatedPath)) {
319
+ entries.push([this.truncatedPath, []]);
320
+ }
321
+ const markerBudget = this.truncatedPath
322
+ ? Math.min(remaining, Buffer.byteLength(LOG_TRUNCATION_MARKER, "utf-8"))
323
+ : 0;
324
+ let contentRemaining = remaining - markerBudget;
325
+ for (const [path, chunks] of entries) {
326
+ const redacted = redactProcessOutput(Buffer.concat(chunks).toString("utf-8"), exactValues);
327
+ const content = utf8Prefix(redacted, contentRemaining);
328
+ contentRemaining -= Buffer.byteLength(content, "utf-8");
329
+ const marker = path === this.truncatedPath
330
+ ? utf8Prefix(LOG_TRUNCATION_MARKER, markerBudget)
331
+ : "";
332
+ const output = content + marker;
333
+ if (output) {
334
+ try {
335
+ appendFileSync(path, output, "utf-8");
336
+ }
337
+ catch { /* evidence logging is best effort */ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+ function findEnvironmentEntry(environment, name, platform) {
343
+ if (platform !== "win32") {
344
+ return Object.prototype.hasOwnProperty.call(environment, name) ? [name, environment[name]] : undefined;
345
+ }
346
+ const match = Object.keys(environment).find((key) => key.toUpperCase() === name.toUpperCase());
347
+ return match ? [match, environment[match]] : undefined;
348
+ }
349
+ function isEnvironmentVariableName(name) {
350
+ return typeof name === "string" && name.length > 0 && !/[=\x00]/.test(name);
351
+ }
352
+ function isPathInside(candidate, parent, platform) {
353
+ const pathApi = platform === "win32" ? win32 : { relative, isAbsolute, sep };
354
+ const rel = pathApi.relative(parent, candidate);
355
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(rel));
356
+ }
357
+ function fileSize(path) {
358
+ try {
359
+ return statSync(path).size;
360
+ }
361
+ catch {
362
+ return 0;
363
+ }
364
+ }
365
+ function utf8Prefix(value, maxBytes) {
366
+ const encoded = Buffer.from(value, "utf-8");
367
+ if (encoded.length <= maxBytes)
368
+ return value;
369
+ let prefix = encoded.subarray(0, maxBytes).toString("utf-8").replace(/\uFFFD$/u, "");
370
+ while (Buffer.byteLength(prefix, "utf-8") > maxBytes)
371
+ prefix = prefix.slice(0, -1);
372
+ return prefix;
373
+ }
@@ -1,4 +1,4 @@
1
- import type { TaskStatus } from "../tools/createTask.js";
1
+ import type { TaskStatus } from "../tools/tasks/createTask.js";
2
2
  interface TaskRunResult {
3
3
  task_id: string;
4
4
  status: TaskStatus;