patchwarden 1.5.1 → 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 (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -3,7 +3,10 @@
3
3
  [string]$Profile = "patchwarden",
4
4
  [ValidateSet("chatgpt_core", "chatgpt_direct")]
5
5
  [string]$ToolProfile = "chatgpt_core",
6
- [string]$ProxyUrl = $(if ($env:HTTPS_PROXY) { $env:HTTPS_PROXY } else { "http://127.0.0.1:7892" }),
6
+ [ValidateSet("", "environment", "none", "manual")]
7
+ [string]$ProxyMode = "",
8
+ [AllowEmptyString()]
9
+ [string]$ProxyUrl = "",
7
10
  [string]$TunnelClientExe = $(if ($env:TUNNEL_CLIENT_EXE) { $env:TUNNEL_CLIENT_EXE } else { $env:PATCHWARDEN_TUNNEL_CLIENT_EXE }),
8
11
  [string]$OpencodeBin = $env:OPENCODE_BIN_DIR,
9
12
  [string]$ConfigPath = $env:PATCHWARDEN_CONFIG,
@@ -22,6 +25,47 @@
22
25
 
23
26
  $ErrorActionPreference = "Stop"
24
27
 
28
+ if ([string]::IsNullOrWhiteSpace($ProxyMode)) {
29
+ $ProxyMode = if ($ToolProfile -eq "chatgpt_direct" -and $env:PATCHWARDEN_DIRECT_PROXY_MODE) {
30
+ $env:PATCHWARDEN_DIRECT_PROXY_MODE
31
+ } elseif ($env:PATCHWARDEN_TUNNEL_PROXY_MODE) {
32
+ $env:PATCHWARDEN_TUNNEL_PROXY_MODE
33
+ } else {
34
+ "environment"
35
+ }
36
+ }
37
+ if ([string]::IsNullOrWhiteSpace($ProxyUrl)) {
38
+ $ProxyUrl = if ($ToolProfile -eq "chatgpt_direct" -and $env:PATCHWARDEN_DIRECT_PROXY_URL) {
39
+ $env:PATCHWARDEN_DIRECT_PROXY_URL
40
+ } else {
41
+ $env:PATCHWARDEN_TUNNEL_PROXY_URL
42
+ }
43
+ }
44
+
45
+ $script:UseProxyArgument = $false
46
+ switch ($ProxyMode) {
47
+ "manual" {
48
+ if ([string]::IsNullOrWhiteSpace($ProxyUrl)) { throw "Manual proxy mode requires ProxyUrl." }
49
+ $proxyUri = $null
50
+ if (-not [Uri]::TryCreate($ProxyUrl, [UriKind]::Absolute, [ref]$proxyUri) -or $proxyUri.Scheme -notin @("http", "https", "socks5")) {
51
+ throw "ProxyUrl must use http, https, or socks5."
52
+ }
53
+ if (-not [string]::IsNullOrEmpty($proxyUri.UserInfo)) { throw "ProxyUrl must not contain credentials." }
54
+ $env:HTTP_PROXY = $ProxyUrl
55
+ $env:HTTPS_PROXY = $ProxyUrl
56
+ $env:ALL_PROXY = $ProxyUrl
57
+ $script:UseProxyArgument = $true
58
+ }
59
+ "none" {
60
+ Remove-Item Env:HTTP_PROXY, Env:HTTPS_PROXY, Env:ALL_PROXY -ErrorAction SilentlyContinue
61
+ }
62
+ "environment" {
63
+ $script:UseProxyArgument = -not [string]::IsNullOrWhiteSpace($env:HTTPS_PROXY)
64
+ }
65
+ default { throw "Unsupported proxy mode: $ProxyMode" }
66
+ }
67
+ $env:NO_PROXY = "localhost,127.0.0.1,::1"
68
+
25
69
  if ([string]::IsNullOrWhiteSpace($HealthListenAddr)) {
26
70
  if ($Profile -eq "patchwarden-direct" -or $ToolProfile -eq "chatgpt_direct") {
27
71
  $HealthListenAddr = "127.0.0.1:8081"
@@ -58,12 +102,35 @@ $script:WatcherManaged = $false
58
102
  $script:WatcherRestartAttempts = 0
59
103
  $script:WatcherHealthySince = $null
60
104
  $script:WatcherRestartExhausted = $false
61
- $DefaultTunnelClientPath = "D:\ai_agent\tunnel-client-v0.0.9--context-conduit-topaz-windows-amd64\tunnel-client.exe"
62
-
63
105
  if ($ToolProfile -eq "chatgpt_direct") {
64
106
  $SkipWatcher = $true
65
107
  }
66
108
 
109
+ $script:AgentEnvironmentNames = @(
110
+ ([string]$env:PATCHWARDEN_AGENT_ENV_ALLOWLIST -split ';') |
111
+ Where-Object { $_ -match '^[A-Za-z_][A-Za-z0-9_]*$' } |
112
+ Select-Object -Unique
113
+ )
114
+
115
+ function Suspend-AgentProviderEnvironment {
116
+ $saved = @{}
117
+ foreach ($name in $script:AgentEnvironmentNames) {
118
+ $item = Get-Item -LiteralPath "Env:$name" -ErrorAction SilentlyContinue
119
+ if ($null -ne $item) {
120
+ $saved[$name] = [string]$item.Value
121
+ Remove-Item -LiteralPath "Env:$name" -ErrorAction SilentlyContinue
122
+ }
123
+ }
124
+ return $saved
125
+ }
126
+
127
+ function Restore-AgentProviderEnvironment {
128
+ param([hashtable]$Saved)
129
+ foreach ($name in $Saved.Keys) {
130
+ Set-Item -LiteralPath "Env:$name" -Value $Saved[$name]
131
+ }
132
+ }
133
+
67
134
  function Assert-File {
68
135
  param([string]$Path, [string]$Name)
69
136
  if (-not (Test-Path -LiteralPath $Path)) {
@@ -230,9 +297,17 @@ function Get-DiagnosticCode {
230
297
  }
231
298
 
232
299
  function Invoke-TunnelDoctor {
233
- $output = (& $TunnelClientExe doctor --profile $Profile --explain --json --http-proxy env:HTTPS_PROXY 2>&1 | Out-String)
300
+ $doctorArguments = @("doctor", "--profile", $Profile, "--explain", "--json")
301
+ if ($script:UseProxyArgument) { $doctorArguments += @("--http-proxy", "env:HTTPS_PROXY") }
302
+ $savedAgentEnvironment = Suspend-AgentProviderEnvironment
303
+ try {
304
+ $output = (& $TunnelClientExe @doctorArguments 2>&1 | Out-String)
305
+ $exitCode = $LASTEXITCODE
306
+ } finally {
307
+ Restore-AgentProviderEnvironment -Saved $savedAgentEnvironment
308
+ }
234
309
  return [pscustomobject]@{
235
- ExitCode = $LASTEXITCODE
310
+ ExitCode = $exitCode
236
311
  Output = $output
237
312
  ReasonCode = Get-DiagnosticCode $output
238
313
  }
@@ -306,11 +381,15 @@ function Start-OwnedWatcherProcess {
306
381
  $previousWatcherLauncherPid = $env:PATCHWARDEN_WATCHER_LAUNCHER_PID
307
382
  $previousXdgConfigHome = $env:XDG_CONFIG_HOME
308
383
  $previousPath = $env:PATH
384
+ $previousControlPlaneApiKey = $env:CONTROL_PLANE_API_KEY
385
+ $previousOwnerToken = $env:PATCHWARDEN_OWNER_TOKEN
309
386
  try {
310
387
  $env:PATCHWARDEN_WATCHER_INSTANCE_ID = $script:WatcherInstanceId
311
388
  $env:PATCHWARDEN_WATCHER_LAUNCHER_PID = [string]$PID
312
389
  $env:XDG_CONFIG_HOME = $OpencodeConfigHome
313
390
  if ($OpencodeBin) { $env:PATH = "$OpencodeBin;$env:PATH" }
391
+ Remove-Item Env:CONTROL_PLANE_API_KEY -ErrorAction SilentlyContinue
392
+ Remove-Item Env:PATCHWARDEN_OWNER_TOKEN -ErrorAction SilentlyContinue
314
393
  $script:WatcherProcess = Start-Process -FilePath $node `
315
394
  -ArgumentList @((Join-Path $ProjectRoot "dist\runner\watch.js")) `
316
395
  -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden `
@@ -319,6 +398,8 @@ function Start-OwnedWatcherProcess {
319
398
  if ($null -eq $previousWatcherInstanceId) { Remove-Item Env:PATCHWARDEN_WATCHER_INSTANCE_ID -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_WATCHER_INSTANCE_ID = $previousWatcherInstanceId }
320
399
  if ($null -eq $previousWatcherLauncherPid) { Remove-Item Env:PATCHWARDEN_WATCHER_LAUNCHER_PID -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_WATCHER_LAUNCHER_PID = $previousWatcherLauncherPid }
321
400
  if ($null -eq $previousXdgConfigHome) { Remove-Item Env:XDG_CONFIG_HOME -ErrorAction SilentlyContinue } else { $env:XDG_CONFIG_HOME = $previousXdgConfigHome }
401
+ if ($null -eq $previousControlPlaneApiKey) { Remove-Item Env:CONTROL_PLANE_API_KEY -ErrorAction SilentlyContinue } else { $env:CONTROL_PLANE_API_KEY = $previousControlPlaneApiKey }
402
+ if ($null -eq $previousOwnerToken) { Remove-Item Env:PATCHWARDEN_OWNER_TOKEN -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_OWNER_TOKEN = $previousOwnerToken }
322
403
  $env:PATH = $previousPath
323
404
  }
324
405
  Write-WatcherStatus -Status "starting"
@@ -368,7 +449,12 @@ function Invoke-WatcherSupervisorTick {
368
449
 
369
450
  function Get-TunnelHealth {
370
451
  if (-not (Test-Path -LiteralPath $HealthUrlFile) -or -not (Test-Path -LiteralPath $PidFile)) { return $null }
371
- $output = (& $TunnelClientExe health --json --url-file $HealthUrlFile --pid-file $PidFile 2>&1 | Out-String)
452
+ $savedAgentEnvironment = Suspend-AgentProviderEnvironment
453
+ try {
454
+ $output = (& $TunnelClientExe health --json --url-file $HealthUrlFile --pid-file $PidFile 2>&1 | Out-String)
455
+ } finally {
456
+ Restore-AgentProviderEnvironment -Saved $savedAgentEnvironment
457
+ }
372
458
  try { return $output | ConvertFrom-Json } catch { return $null }
373
459
  }
374
460
 
@@ -505,8 +591,7 @@ if (-not $TunnelClientExe) {
505
591
 
506
592
  # Search common installation locations
507
593
  $commonPaths = @(
508
- $DefaultTunnelClientPath
509
- Join-Path $env:LOCALAPPDATA "patchwarden\tunnel-client.exe"
594
+ Join-Path $env:LOCALAPPDATA "PatchWarden\tunnel-client.exe"
510
595
  Join-Path $env:APPDATA "tunnel-client\tunnel-client.exe"
511
596
  Join-Path $env:USERPROFILE "tunnel-client\tunnel-client.exe"
512
597
  )
@@ -525,11 +610,8 @@ if (-not $TunnelClientExe) {
525
610
  Write-Host "[input] tunnel-client.exe not found on PATH or common locations." -ForegroundColor Yellow
526
611
  Write-Host " Please provide the full path."
527
612
  Write-Host ""
528
- Write-Host " To search for it manually, try one of these commands:" -ForegroundColor Cyan
529
- Write-Host " Get-ChildItem -Path `$env:LOCALAPPDATA -Recurse -Filter tunnel-client.exe -ErrorAction SilentlyContinue" -ForegroundColor Gray
530
- Write-Host " Get-ChildItem -Path `$env:APPDATA -Recurse -Filter tunnel-client.exe -ErrorAction SilentlyContinue" -ForegroundColor Gray
531
- Write-Host " Get-ChildItem -Path C:\ -Recurse -Filter tunnel-client.exe -ErrorAction SilentlyContinue" -ForegroundColor Gray
532
- Write-Host " Or download it from: https://platform.openai.com/docs/guides/mcp-connector" -ForegroundColor Cyan
613
+ Write-Host " Desktop users can open Settings -> MCP and tunnel -> Auto detect / Choose file." -ForegroundColor Cyan
614
+ Write-Host " The desktop guide explains trusted downloads and SHA256 verification." -ForegroundColor Cyan
533
615
  Write-Host ""
534
616
  $TunnelClientExe = Read-Host "Path to tunnel-client.exe"
535
617
  }
@@ -556,7 +638,7 @@ if (-not $TunnelId -and (Test-Path -LiteralPath $ProfilePath)) {
556
638
  $profileContent = Get-Content -LiteralPath $ProfilePath -Raw -Encoding UTF8
557
639
  if ($profileContent -match 'tunnel_id:\s*"([^"]+)"') {
558
640
  $TunnelId = $matches[1]
559
- Write-Host "[detect] Tunnel ID read from profile $Profile`: $TunnelId" -ForegroundColor Green
641
+ Write-Host "[detect] Tunnel ID read from profile $Profile." -ForegroundColor Green
560
642
  }
561
643
  } catch {
562
644
  Write-Host "[warn] Could not read Tunnel ID from $ProfilePath`: $($_.Exception.Message)" -ForegroundColor Yellow
@@ -580,8 +662,15 @@ if (-not (Test-Path -LiteralPath (Join-Path $ProjectRoot "dist\index.js"))) {
580
662
  New-Item -ItemType Directory -Force -Path $RuntimeDirectory | Out-Null
581
663
  $env:PATCHWARDEN_CONFIG = $ConfigPath
582
664
  Write-Host "[manifest] Verifying the exact $ToolProfile stdio MCP tool catalog..."
583
- $manifestOutput = (& node (Join-Path $ProjectRoot "scripts\checks\mcp-manifest-check.js") --profile $ToolProfile --json 2>&1 | Out-String).Trim()
584
- if ($LASTEXITCODE -ne 0) {
665
+ $previousErrorActionPreference = $ErrorActionPreference
666
+ try {
667
+ $ErrorActionPreference = "Continue"
668
+ $manifestOutput = (& node (Join-Path $ProjectRoot "scripts\checks\mcp-manifest-check.js") --profile $ToolProfile --json 2>&1 | Out-String).Trim()
669
+ $manifestExitCode = $LASTEXITCODE
670
+ } finally {
671
+ $ErrorActionPreference = $previousErrorActionPreference
672
+ }
673
+ if ($manifestExitCode -ne 0) {
585
674
  Write-TunnelStatus -Status "stopped" -ReasonCode "tool_manifest_check_failed" -LastError "The tunnel MCP tool manifest preflight failed."
586
675
  throw "Tool manifest preflight failed: $manifestOutput"
587
676
  }
@@ -601,10 +690,6 @@ Write-Host "[config] health listen addr: $HealthListenAddr"
601
690
  Set-SecretEnvIfMissing
602
691
 
603
692
  $env:PATCHWARDEN_CONFIG = $ConfigPath
604
- $env:HTTP_PROXY = $ProxyUrl
605
- $env:HTTPS_PROXY = $ProxyUrl
606
- $env:ALL_PROXY = $ProxyUrl
607
- $env:NO_PROXY = "localhost,127.0.0.1,::1"
608
693
 
609
694
  $profileNeedsInit = $true
610
695
  if (Test-Path -LiteralPath $ProfilePath) {
@@ -614,12 +699,17 @@ if (Test-Path -LiteralPath $ProfilePath) {
614
699
 
615
700
  if ($profileNeedsInit) {
616
701
  Write-Host "[init] Creating tunnel-client profile: $Profile"
617
- & $TunnelClientExe init `
618
- --sample sample_mcp_stdio_local `
619
- --profile $Profile `
620
- --tunnel-id $TunnelId `
621
- --mcp-command $McpStdioLauncherForTunnel `
622
- --force
702
+ $savedAgentEnvironment = Suspend-AgentProviderEnvironment
703
+ try {
704
+ & $TunnelClientExe init `
705
+ --sample sample_mcp_stdio_local `
706
+ --profile $Profile `
707
+ --tunnel-id $TunnelId `
708
+ --mcp-command $McpStdioLauncherForTunnel `
709
+ --force
710
+ } finally {
711
+ Restore-AgentProviderEnvironment -Saved $savedAgentEnvironment
712
+ }
623
713
  Set-ProfileHealthListenAddr -ProfilePath $ProfilePath -HealthListenAddr $HealthListenAddr
624
714
  } else {
625
715
  Set-ProfileHealthListenAddr -ProfilePath $ProfilePath -HealthListenAddr $HealthListenAddr
@@ -628,7 +718,7 @@ if ($profileNeedsInit) {
628
718
  $env:PATCHWARDEN_TUNNEL_STATUS_FILE = $StatusFile
629
719
  if (-not $SkipWatcher) { Start-PatchWardenWatcher }
630
720
 
631
- Write-Host "[doctor] Checking tunnel-client profile through the configured proxy..."
721
+ Write-Host "[doctor] Checking tunnel-client profile (proxy mode: $ProxyMode)..."
632
722
  $preflightAttempt = 0
633
723
  $preflightMaxRetries = 3
634
724
  $doctor = Invoke-TunnelDoctor
@@ -665,8 +755,7 @@ Write-Host "[run-config] health url file: $HealthUrlFile"
665
755
  Write-Host "[run-config] pid file: $PidFile"
666
756
  Write-Host "[run-config] stdout log: $StdoutLogFile"
667
757
  Write-Host "[run-config] stderr log: $StderrLogFile"
668
- Write-Host "[run-config] HTTPS_PROXY: $env:HTTPS_PROXY"
669
- Write-Host "[run-config] HTTP_PROXY: $env:HTTP_PROXY"
758
+ Write-Host "[run-config] proxy mode: $ProxyMode"
670
759
  Write-Host "[run-config] CONTROL_PLANE_API_KEY: $(if ($env:CONTROL_PLANE_API_KEY) { 'set' } else { 'missing' })"
671
760
  Write-Host "[run-config] cwd: $ProjectRoot"
672
761
  Write-Host ""
@@ -681,18 +770,23 @@ try {
681
770
  Write-TunnelStatus -Status "starting" -ReasonCode $null -Ready $false -Attempt $attempt
682
771
  $runArguments = @(
683
772
  "run", "--profile", $Profile,
684
- "--http-proxy", "env:HTTPS_PROXY",
685
773
  "--health.url-file", $HealthUrlFile,
686
774
  "--pid.file", $PidFile,
687
775
  "--log.format", "json"
688
776
  )
777
+ if ($script:UseProxyArgument) { $runArguments += @("--http-proxy", "env:HTTPS_PROXY") }
689
778
  if ($openUi) { $runArguments += "--open-web-ui"; $openUi = $false }
690
779
  $argumentLine = ($runArguments | ForEach-Object { Quote-ProcessArgument $_ }) -join " "
691
780
  Write-Host "[run-config] command: tunnel-client $($runArguments -join ' ')"
692
781
  $redirectedCommand = '""' + $TunnelClientExe + '" ' + $argumentLine + ' 1>"' + $StdoutLogFile + '" 2>"' + $StderrLogFile + '""'
693
782
  $processArgumentLine = '/d /s /c ' + $redirectedCommand
694
- $script:TunnelProcess = Start-Process -FilePath $env:ComSpec -ArgumentList $processArgumentLine `
695
- -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden
783
+ $savedAgentEnvironment = Suspend-AgentProviderEnvironment
784
+ try {
785
+ $script:TunnelProcess = Start-Process -FilePath $env:ComSpec -ArgumentList $processArgumentLine `
786
+ -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden
787
+ } finally {
788
+ Restore-AgentProviderEnvironment -Saved $savedAgentEnvironment
789
+ }
696
790
  Write-TunnelStatus -Status "connecting" -ReasonCode $null -Ready $false -Attempt $attempt -ProcessId $script:TunnelProcess.Id
697
791
  Write-Host "[run] supervised command PID $($script:TunnelProcess.Id), attempt $attempt"
698
792
 
@@ -1,11 +1,11 @@
1
1
  @echo off
2
2
  setlocal
3
- cd /d "%~dp0"
3
+ cd /d "%~dp0..\.."
4
4
  if /i "%~1"=="--foreground" (
5
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\control\control-center-tray.ps1" %*
5
+ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\control\control-center-tray.ps1" %*
6
6
  set EXITCODE=%ERRORLEVEL%
7
7
  endlocal & exit /b %EXITCODE%
8
8
  )
9
9
 
10
- start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0scripts\control\control-center-tray.ps1" %*
10
+ start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0..\control\control-center-tray.ps1" %*
11
11
  endlocal & exit /b 0
@@ -0,0 +1,6 @@
1
+ @echo off
2
+ setlocal
3
+ cd /d "%~dp0..\.."
4
+ powershell -ExecutionPolicy Bypass -NoProfile -File "%~dp0..\control\start-control-center.ps1" %*
5
+ set EXITCODE=%ERRORLEVEL%
6
+ endlocal & exit /b %EXITCODE%
@@ -1,5 +1,5 @@
1
1
  @echo off
2
2
  setlocal
3
- cd /d "%~dp0"
4
- start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0scripts\control\control-center-tray.ps1" %*
3
+ cd /d "%~dp0..\.."
4
+ start "" powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "%~dp0..\control\control-center-tray.ps1" %*
5
5
  endlocal & exit /b 0
@@ -0,0 +1,6 @@
1
+ @echo off
2
+ setlocal
3
+ cd /d "%~dp0..\.."
4
+ powershell -ExecutionPolicy Bypass -NoProfile -File "%~dp0..\control\restart-control-center.ps1" %*
5
+ set EXITCODE=%ERRORLEVEL%
6
+ endlocal & exit /b %EXITCODE%
@@ -1,7 +1,7 @@
1
1
  @echo off
2
2
  setlocal
3
- cd /d "%~dp0"
4
- powershell -ExecutionPolicy Bypass -NoProfile -File "%~dp0scripts\control\stop-patchwarden.ps1" %*
3
+ cd /d "%~dp0..\.."
4
+ powershell -ExecutionPolicy Bypass -NoProfile -File "%~dp0..\control\stop-patchwarden.ps1" %*
5
5
  set EXITCODE=%ERRORLEVEL%
6
6
  if not "%EXITCODE%"=="0" (
7
7
  echo.
@@ -0,0 +1,11 @@
1
+ import { rmSync } from "node:fs";
2
+ import { basename, dirname, resolve } from "node:path";
3
+
4
+ export function cleanGeneratedOutput(projectRoot, outputPath) {
5
+ const root = resolve(projectRoot);
6
+ const output = resolve(outputPath);
7
+ if (dirname(output) !== root || basename(output).toLowerCase() !== "dist") {
8
+ throw new Error(`Refusing to clean non-dist output: ${output}`);
9
+ }
10
+ rmSync(output, { recursive: true, force: true });
11
+ }
@@ -0,0 +1,12 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ export function collectMatchingFiles(directory, predicate) {
5
+ return readdirSync(directory, { withFileTypes: true })
6
+ .sort((left, right) => left.name.localeCompare(right.name))
7
+ .flatMap((entry) => {
8
+ const path = resolve(directory, entry.name);
9
+ if (entry.isDirectory()) return collectMatchingFiles(path, predicate);
10
+ return entry.isFile() && predicate(entry.name, path) ? [path] : [];
11
+ });
12
+ }
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { basename, join, resolve } from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
10
+ const desktopRoot = join(root, "desktop");
11
+ const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
12
+ const argv = process.argv.slice(2);
13
+ const requireClean = argv.includes("--require-clean");
14
+ const skipUiSmoke = argv.includes("--skip-ui-smoke");
15
+
16
+ function option(name) {
17
+ const index = argv.indexOf(name);
18
+ return index >= 0 ? argv[index + 1] : null;
19
+ }
20
+
21
+ function json(path) {
22
+ return JSON.parse(readFileSync(path, "utf8"));
23
+ }
24
+
25
+ function sha256(value) {
26
+ return createHash("sha256").update(value).digest("hex");
27
+ }
28
+
29
+ function directoryStats(directory) {
30
+ let files = 0;
31
+ let bytes = 0;
32
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
33
+ const path = join(directory, entry.name);
34
+ if (entry.isDirectory()) {
35
+ const nested = directoryStats(path);
36
+ files += nested.files;
37
+ bytes += nested.bytes;
38
+ } else if (entry.isFile()) {
39
+ files += 1;
40
+ bytes += statSync(path).size;
41
+ }
42
+ }
43
+ return { files, bytes };
44
+ }
45
+
46
+ function spawnSpec(command, args) {
47
+ if (process.platform === "win32" && /\.cmd$/i.test(command)) {
48
+ return { command: process.env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", command, ...args] };
49
+ }
50
+ return { command, args };
51
+ }
52
+
53
+ function capture(command, args, cwd = root) {
54
+ const spec = spawnSpec(command, args);
55
+ const result = spawnSync(spec.command, spec.args, { cwd, encoding: "utf8", windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
56
+ if (result.status !== 0) throw new Error(`${command} ${args.join(" ")} failed: ${result.stderr || result.stdout || result.error?.message || "unknown error"}`);
57
+ return String(result.stdout || "").trim();
58
+ }
59
+
60
+ const rootPackage = json(join(root, "package.json"));
61
+ const rootLock = json(join(root, "package-lock.json"));
62
+ const desktopPackage = json(join(desktopRoot, "package.json"));
63
+ const desktopLock = json(join(desktopRoot, "package-lock.json"));
64
+ const versionSource = readFileSync(join(root, "src", "version.ts"), "utf8");
65
+ const sourceVersion = versionSource.match(/PATCHWARDEN_VERSION\s*=\s*"([^"]+)"/)?.[1] || null;
66
+ const versions = [rootPackage.version, rootLock.version, rootLock.packages?.[""]?.version, desktopPackage.version, desktopLock.version, desktopLock.packages?.[""]?.version, sourceVersion];
67
+ if (versions.some((version) => version !== rootPackage.version)) throw new Error(`Version mismatch: ${versions.join(", ")}`);
68
+ if (process.platform !== "win32") throw new Error("Desktop preflight requires Windows.");
69
+
70
+ const gitStatus = capture("git", ["status", "--porcelain=v1", "-z"]);
71
+ const changedFileCount = gitStatus ? gitStatus.split("\0").filter(Boolean).length : 0;
72
+ if (requireClean && changedFileCount > 0) throw new Error(`Release preflight requires a clean worktree; found ${changedFileCount} changed paths.`);
73
+
74
+ const stamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
75
+ const defaultOutput = join(root, "release", `desktop-preflight-${rootPackage.version}-${stamp}`);
76
+ const outputRoot = resolve(option("--output") || defaultOutput);
77
+ if (existsSync(outputRoot)) throw new Error(`Preflight output already exists: ${outputRoot}`);
78
+ mkdirSync(outputRoot, { recursive: true });
79
+
80
+ const report = {
81
+ schema_version: "patchwarden-desktop-preflight-v1",
82
+ status: "running",
83
+ started_at: new Date().toISOString(),
84
+ finished_at: null,
85
+ version: rootPackage.version,
86
+ output_directory: outputRoot,
87
+ source: {
88
+ branch: capture("git", ["branch", "--show-current"]),
89
+ head: capture("git", ["rev-parse", "HEAD"]),
90
+ dirty: changedFileCount > 0,
91
+ changed_path_count: changedFileCount,
92
+ status_sha256: sha256(gitStatus),
93
+ tracked_diff_sha256: sha256(capture("git", ["diff", "--binary", "HEAD"])),
94
+ },
95
+ environment: {
96
+ platform: process.platform,
97
+ arch: process.arch,
98
+ node: process.version,
99
+ npm: capture(npmCommand, ["--version"]),
100
+ electron: desktopPackage.devDependencies.electron,
101
+ electron_builder: desktopPackage.devDependencies["electron-builder"],
102
+ },
103
+ options: { require_clean: requireClean, skip_ui_smoke: skipUiSmoke },
104
+ checks: [],
105
+ package: null,
106
+ runtime_manifest: null,
107
+ ui_smoke: null,
108
+ artifact: null,
109
+ error: null,
110
+ };
111
+
112
+ function saveReport() {
113
+ writeFileSync(join(outputRoot, "preflight-report.json"), `${JSON.stringify(report, null, 2)}\n`, "utf8");
114
+ const lines = [
115
+ `# PatchWarden Desktop ${report.version} preflight`,
116
+ "",
117
+ `- Status: ${report.status}`,
118
+ `- Source: ${report.source.branch} @ ${report.source.head}`,
119
+ `- Dirty baseline: ${report.source.dirty} (${report.source.changed_path_count} paths, status SHA256 ${report.source.status_sha256})`,
120
+ `- Node/npm: ${report.environment.node} / ${report.environment.npm}`,
121
+ `- Electron: ${report.environment.electron}`,
122
+ `- Output: ${report.output_directory}`,
123
+ "",
124
+ "## Checks",
125
+ "",
126
+ ...report.checks.map((check) => `- ${check.ok ? "PASS" : "FAIL"} ${check.name} (${check.duration_ms} ms)`),
127
+ "",
128
+ report.runtime_manifest ? `Packaged runtime: ${report.runtime_manifest.tool_profile}, ${report.runtime_manifest.tool_count} tools, manifest SHA256 ${report.runtime_manifest.tool_manifest_sha256}.` : "Packaged runtime: not verified.",
129
+ report.ui_smoke ? `UI smoke: ${report.ui_smoke.ok ? "passed" : "failed"}.` : "UI smoke: not run.",
130
+ report.error ? `\nError: ${report.error}` : "",
131
+ ];
132
+ writeFileSync(join(outputRoot, "preflight-report.md"), `${lines.join("\n")}\n`, "utf8");
133
+ }
134
+
135
+ function run(name, command, args, cwd = root) {
136
+ const started = Date.now();
137
+ const spec = spawnSpec(command, args);
138
+ const result = spawnSync(spec.command, spec.args, { cwd, encoding: "utf8", windowsHide: true, maxBuffer: 128 * 1024 * 1024 });
139
+ const stdout = String(result.stdout || "");
140
+ const stderr = String(result.stderr || "");
141
+ if (stdout) process.stdout.write(stdout);
142
+ if (stderr) process.stderr.write(stderr);
143
+ const check = { name, ok: result.status === 0, exit_code: result.status, duration_ms: Date.now() - started };
144
+ report.checks.push(check);
145
+ saveReport();
146
+ if (!check.ok) throw new Error(`${name} failed with exit code ${result.status ?? "unknown"}: ${stderr || result.error?.message || "see command output"}`);
147
+ return stdout;
148
+ }
149
+
150
+ let isolatedRoot = null;
151
+ try {
152
+ run("root clean build", npmCommand, ["run", "build"]);
153
+ run("build output contract", npmCommand, ["run", "check:build-output"]);
154
+ const unitOutput = run("root unit tests", npmCommand, ["run", "test:unit"]);
155
+ const desktopOutput = run("desktop tests", npmCommand, ["run", "desktop:test"]);
156
+ const packageOutput = run("npm package surface", npmCommand, ["run", "verify:package"]);
157
+ run("desktop staging", npmCommand, ["run", "desktop:stage"]);
158
+ run("Electron directory package", npmCommand, ["exec", "electron-builder", "--", "--win", "dir", "--x64", `--config.directories.output=${outputRoot}`], desktopRoot);
159
+
160
+ report.package = {
161
+ npm_file_count: Number(packageOutput.match(/OK:\s+(\d+) package files/)?.[1] || 0),
162
+ root_unit_tests: Number([...unitOutput.matchAll(/tests\s+(\d+)/g)].at(-1)?.[1] || 0),
163
+ desktop_tests: Number([...desktopOutput.matchAll(/tests\s+(\d+)/g)].at(-1)?.[1] || 0),
164
+ };
165
+
166
+ const unpackedRoot = join(outputRoot, "win-unpacked");
167
+ const executable = join(unpackedRoot, "PatchWarden.exe");
168
+ const packagedCore = join(unpackedRoot, "resources", "core");
169
+ if (!existsSync(executable) || !existsSync(packagedCore)) throw new Error("Electron directory package is missing its executable or core runtime.");
170
+ for (const forbidden of ["dist/test", "src/test", "docs/archive", "scripts/checks/doctor-smoke.js"]) {
171
+ if (existsSync(join(packagedCore, forbidden))) throw new Error(`Packaged core contains development-only path: ${forbidden}`);
172
+ }
173
+ const packagedChecks = readdirSync(join(packagedCore, "scripts", "checks")).sort();
174
+ if (JSON.stringify(packagedChecks) !== JSON.stringify(["mcp-manifest-check.js"])) {
175
+ throw new Error(`Packaged core has unexpected check scripts: ${packagedChecks.join(", ")}`);
176
+ }
177
+ const coreStats = directoryStats(packagedCore);
178
+ report.package.packaged_core_file_count = coreStats.files;
179
+ report.package.packaged_core_size_bytes = coreStats.bytes;
180
+
181
+ isolatedRoot = join(tmpdir(), `patchwarden-preflight-${process.pid}-${Date.now()}`);
182
+ cpSync(packagedCore, isolatedRoot, { recursive: true });
183
+ const manifestOutput = run("isolated packaged runtime manifest", process.execPath, ["scripts/checks/mcp-manifest-check.js", "--profile", "chatgpt_core"], isolatedRoot);
184
+ report.runtime_manifest = JSON.parse(manifestOutput);
185
+ if (!report.runtime_manifest.ok || report.runtime_manifest.server_version !== rootPackage.version || report.runtime_manifest.tool_count !== 26) {
186
+ throw new Error("Packaged runtime manifest did not match the 1.6.1 26-tool contract.");
187
+ }
188
+
189
+ if (!skipUiSmoke) {
190
+ const smokeOutput = run("unpacked desktop UI smoke", process.execPath, ["scripts/smoke-unpacked.mjs", "--exe", executable, "--output", join(outputRoot, "ui-smoke")], desktopRoot);
191
+ report.ui_smoke = JSON.parse(smokeOutput.trim().split(/\r?\n/).at(-1));
192
+ if (!report.ui_smoke.ok) throw new Error("Unpacked desktop UI smoke did not pass.");
193
+ }
194
+
195
+ report.artifact = {
196
+ name: basename(executable),
197
+ size_bytes: readFileSync(executable).length,
198
+ sha256: sha256(readFileSync(executable)),
199
+ };
200
+ report.status = "passed";
201
+ } catch (error) {
202
+ report.status = "failed";
203
+ report.error = error instanceof Error ? error.message : String(error);
204
+ throw error;
205
+ } finally {
206
+ if (isolatedRoot) rmSync(isolatedRoot, { recursive: true, force: true });
207
+ report.finished_at = new Date().toISOString();
208
+ saveReport();
209
+ }
210
+
211
+ console.log(`[desktop-preflight] PASS: ${outputRoot}`);