patchwarden 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -4,7 +4,7 @@ import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
- import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
7
+ import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/catalog/toolCatalog.js";
8
8
 
9
9
  const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
10
10
  const jsonOnly = process.argv.includes("--json");
@@ -14,7 +14,10 @@ import { fileURLToPath } from "node:url";
14
14
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
15
15
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
16
16
 
17
- const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
17
+ const sourceRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
18
+ const root = process.env.PATCHWARDEN_TEST_RUNTIME_ROOT
19
+ ? resolve(process.env.PATCHWARDEN_TEST_RUNTIME_ROOT)
20
+ : sourceRoot;
18
21
  const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
19
22
  const expectedServerVersion = packageJson.version;
20
23
  const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-mcp-"));
@@ -54,6 +57,8 @@ try {
54
57
  }), "utf-8");
55
58
  writeFileSync(join(workspaceRoot, "hello.txt"), "hello from mcp smoke\n", "utf-8");
56
59
  writeFileSync(join(workspaceRoot, ".env"), "SECRET=blocked\n", "utf-8");
60
+ writeFileSync(join(workspaceRoot, "npm.cmd"), "@echo repo npm hijack executed\r\n@exit /b 99\r\n", "utf-8");
61
+ writeFileSync(join(workspaceRoot, "git.cmd"), "@echo repo git hijack executed\r\n@exit /b 98\r\n", "utf-8");
57
62
  writeFileSync(
58
63
  join(workspaceRoot, "package.json"),
59
64
  JSON.stringify({
@@ -290,6 +295,33 @@ try {
290
295
  }
291
296
  ok("create_task accepts inline plans and guarded templates");
292
297
 
298
+ const assessment = await parseToolJson("create_task", {
299
+ execution_mode: "assess_only",
300
+ template: "inspect_only",
301
+ goal: "Inspect package metadata without changing files.",
302
+ agent: "codex",
303
+ repo_path: ".",
304
+ verify_commands: [],
305
+ scope: ["package.json"],
306
+ forbidden: ["release/**"],
307
+ });
308
+ const assessmentFile = join(workspaceRoot, ".patchwarden", "assessments", assessment.assessment_id, "assessment.json");
309
+ const assessmentRecord = JSON.parse(readFileSync(assessmentFile, "utf-8"));
310
+ if (
311
+ !/^[a-f0-9]{64}$/.test(assessmentRecord.execution_config_hash || "") ||
312
+ assessmentRecord.assessment_security_snapshot_version !== "assessment-security-v2"
313
+ ) {
314
+ throw new Error(`assessment security fingerprint missing: ${JSON.stringify(assessmentRecord)}`);
315
+ }
316
+ const assessedTask = await parseToolJson("create_task", {
317
+ execution_mode: "execute",
318
+ assessment_id: assessment.assessment_id,
319
+ });
320
+ if (assessedTask.status !== "pending" || !assessedTask.task_id) {
321
+ throw new Error(`two-stage assessment did not create a pending task: ${JSON.stringify(assessedTask)}`);
322
+ }
323
+ ok("assess_only then execute preserves the versioned security fingerprint");
324
+
293
325
  const blockedAgent = await client.callTool({
294
326
  name: "create_task",
295
327
  arguments: { plan_id: plan.plan_id, agent: "missing-agent", repo_path: "." },
@@ -320,7 +352,11 @@ try {
320
352
  );
321
353
  ok("sensitive file and path escape checks reject access");
322
354
 
323
- const runner = spawnSync("node", ["dist/runner/cli.js", task.task_id], {
355
+ // The runner executes with the temporary workspace as cwd; use an absolute
356
+ // package path so the smoke test does not depend on that workspace having a
357
+ // copied `dist/` tree.
358
+ const runnerPath = resolve(root, "dist", "runner", "cli.js");
359
+ const runner = spawnSync("node", [runnerPath, task.task_id], {
324
360
  cwd: root,
325
361
  env: { ...process.env, PATCHWARDEN_CONFIG: configPath },
326
362
  encoding: "utf-8",
@@ -437,7 +473,9 @@ try {
437
473
  writeFileSync(join(directRepo, "package.json"), JSON.stringify({
438
474
  name: "direct-fixture",
439
475
  private: true,
440
- scripts: { test: 'node -e "console.log(\'ok\')"' },
476
+ // Exercise the npm-script execution path without relying on a temporary
477
+ // script file or platform-specific quoting.
478
+ scripts: { test: "node --version" },
441
479
  }, null, 2), "utf-8");
442
480
 
443
481
  writeFileSync(
@@ -517,7 +555,23 @@ try {
517
555
  })).content?.[0]?.text || "{}"
518
556
  );
519
557
  if (bundleResult.status !== "passed" || bundleResult.command_count !== 1 || bundleResult.passed_commands !== 1) {
520
- throw new Error(`run_direct_verification_bundle failed: ${JSON.stringify(bundleResult)}`);
558
+ // This repository and command are generated entirely by this smoke test,
559
+ // so a bounded log excerpt is safe and makes platform-only CI failures
560
+ // actionable without weakening the production tool's safe summary.
561
+ const sessionDir = join(workspaceRoot, ".patchwarden", "direct-sessions", sessionResult.session_id);
562
+ const verificationPath = join(sessionDir, "verification.json");
563
+ const logPath = join(sessionDir, "verification.log");
564
+ const verification = existsSync(verificationPath)
565
+ ? readFileSync(verificationPath, "utf-8").slice(-2000)
566
+ : "missing verification.json";
567
+ const fixtureLog = existsSync(logPath)
568
+ ? readFileSync(logPath, "utf-8").slice(-2000)
569
+ : "missing verification.log";
570
+ throw new Error(
571
+ `run_direct_verification_bundle failed: ${JSON.stringify(bundleResult)}; ` +
572
+ `synthetic_fixture_verification=${JSON.stringify(verification)}; ` +
573
+ `synthetic_fixture_log=${JSON.stringify(fixtureLog)}`
574
+ );
521
575
  }
522
576
  const bundleText = JSON.stringify(bundleResult).toLowerCase();
523
577
  if (bundleText.includes("stdout_tail") || bundleText.includes("stderr_tail") || bundleText.includes("verification.log")) {
@@ -1,15 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { readFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ buildChildEnvironment,
8
+ resolvePackageManagerInvocation,
9
+ } from "../../dist/runner/processSecurity.js";
4
10
 
5
- const isWindows = process.platform === "win32";
6
- const command = isWindows ? (process.env.ComSpec || "cmd.exe") : "npm";
7
- const args = isWindows
8
- ? ["/d", "/s", "/c", "npm.cmd pack --dry-run --json"]
9
- : ["pack", "--dry-run", "--json"];
10
- const result = spawnSync(command, args, {
11
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
12
+ const env = buildChildEnvironment({ cwd: root });
13
+ const invocation = resolvePackageManagerInvocation(process.platform === "win32" ? "npm.cmd" : "npm", root, {
14
+ pathValue: env.PATH,
15
+ });
16
+ const result = spawnSync(invocation.command, [...invocation.argsPrefix, "pack", "--dry-run", "--json"], {
17
+ cwd: root,
11
18
  encoding: "utf8",
19
+ env,
12
20
  shell: false,
21
+ windowsHide: true,
13
22
  });
14
23
 
15
24
  if (result.status !== 0) {
@@ -35,6 +44,8 @@ const forbidden = [
35
44
  /\.dpapi$/i,
36
45
  /^docs\/optimization-proposal\.md$/i,
37
46
  /(^|\/)kill-patchwarden\.(cmd|ps1)$/i,
47
+ /^(?:dist|src)\/test\//i,
48
+ /^docs\/archive\//i,
38
49
  ];
39
50
  const leaked = files.filter((file) => forbidden.some((pattern) => pattern.test(file)));
40
51
  if (leaked.length > 0) {
@@ -44,8 +55,10 @@ if (leaked.length > 0) {
44
55
  }
45
56
 
46
57
  const required = [
58
+ "dist/index.js",
59
+ "dist/index.d.ts",
47
60
  "PatchWarden.cmd",
48
- "PatchWarden-Desktop.cmd",
61
+ "scripts/launchers/PatchWarden-Desktop.cmd",
49
62
  "scripts/control/manage-patchwarden.ps1",
50
63
  "scripts/launchers/Start-PatchWarden-Tunnel.cmd",
51
64
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readdirSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { collectMatchingFiles } from "../lib/file-discovery.js";
6
7
 
7
8
  const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
8
9
  const root = resolve(scriptDir, "..", "..");
@@ -14,10 +15,7 @@ if (!existsSync(unitDir)) {
14
15
  process.exit(1);
15
16
  }
16
17
 
17
- const testFiles = readdirSync(unitDir)
18
- .filter((name) => name.endsWith(".test.js"))
19
- .sort()
20
- .map((name) => resolve(unitDir, name));
18
+ const testFiles = collectMatchingFiles(unitDir, (name) => name.endsWith(".test.js"));
21
19
 
22
20
  if (testFiles.length === 0) {
23
21
  console.error(`[unit-tests] No compiled unit tests found in ${unitDir}`);
@@ -36,8 +36,9 @@ try {
36
36
  }
37
37
  });
38
38
 
39
+ await runReusedPidLockScenario();
39
40
  await runExternalScenario();
40
- console.log("ok - watcher supervisor isolates environment and handles exit, stale heartbeat, retry limit, and external ownership");
41
+ console.log("ok - watcher supervisor isolates environment and handles exit, stale heartbeat, PID reuse, retry limit, and external ownership");
41
42
  } finally {
42
43
  for (const child of children) {
43
44
  if (child.exitCode === null) child.kill("SIGKILL");
@@ -45,6 +46,32 @@ try {
45
46
  rmSync(temp, { recursive: true, force: true });
46
47
  }
47
48
 
49
+ async function runReusedPidLockScenario() {
50
+ const fixture = createFixture("reused-pid-lock");
51
+ mkdirSync(dirname(fixture.lockPath), { recursive: true });
52
+ writeFileSync(fixture.lockPath, JSON.stringify({
53
+ pid: process.pid,
54
+ instance_id: "stale-reused-pid",
55
+ launcher_pid: 999999,
56
+ started_at: "2026-01-01T00:00:00.000Z",
57
+ }), "utf-8");
58
+ writeFileSync(fixture.heartbeatPath, JSON.stringify({
59
+ status: "running",
60
+ pid: process.pid,
61
+ instance_id: "stale-reused-pid",
62
+ launcher_pid: 999999,
63
+ started_at: "2026-01-01T00:00:00.000Z",
64
+ last_heartbeat_at: "2026-01-01T00:00:00.000Z",
65
+ }), "utf-8");
66
+
67
+ const result = runLauncher(fixture, "healthy", 1, 7_000);
68
+ if (result.error && result.error.code === "ETIMEDOUT") throw result.error;
69
+ const attempts = Number(readFileSync(fixture.watcherAttemptPath, "utf-8")) || 0;
70
+ if (attempts < 1 || !result.stdout.includes("Removed stale watcher lock after PID identity verification")) {
71
+ throw new Error(`Reused PID lock was not safely retired: ${JSON.stringify({ attempts, stdout: result.stdout, stderr: result.stderr })}`);
72
+ }
73
+ }
74
+
48
75
  async function runScenario(label, mode, maxRestarts, lifetimeMs, assertState) {
49
76
  const fixture = createFixture(label.replace(/[^a-z0-9]+/gi, "-"));
50
77
  const result = runLauncher(fixture, mode, maxRestarts, lifetimeMs);
@@ -60,6 +87,8 @@ async function runScenario(label, mode, maxRestarts, lifetimeMs, assertState) {
60
87
  async function runExternalScenario() {
61
88
  const fixture = createFixture("external");
62
89
  const env = fixtureEnv(fixture, "healthy", 7_000);
90
+ delete env.CONTROL_PLANE_API_KEY;
91
+ delete env.PATCHWARDEN_OWNER_TOKEN;
63
92
  const external = spawn(process.execPath, [fixture.watcherPath], {
64
93
  cwd: fixture.project,
65
94
  env: {
@@ -137,6 +166,7 @@ function createFixture(name) {
137
166
  watcherPath,
138
167
  watcherAttemptPath,
139
168
  heartbeatPath: join(workspace, ".patchwarden", "watcher-heartbeat.json"),
169
+ lockPath: join(workspace, ".patchwarden", "watcher.lock"),
140
170
  watcherStatusPath: join(localAppData, "patchwarden", "runtime", "watcher-status.json"),
141
171
  mockCmd,
142
172
  localAppData,
@@ -168,6 +198,9 @@ function fixtureEnv(fixture, mode, lifetimeMs) {
168
198
  APPDATA: fixture.appData,
169
199
  LOCALAPPDATA: fixture.localAppData,
170
200
  CONTROL_PLANE_API_KEY: "test-key",
201
+ PATCHWARDEN_OWNER_TOKEN: "test-owner-token",
202
+ PATCHWARDEN_AGENT_ENV_ALLOWLIST: "PATCHWARDEN_TEST_PROVIDER_KEY",
203
+ PATCHWARDEN_TEST_PROVIDER_KEY: "provider-key-canary",
171
204
  WATCHER_FIXTURE_MODE: mode,
172
205
  TUNNEL_FIXTURE_LIFETIME_MS: String(lifetimeMs),
173
206
  };
@@ -177,6 +210,9 @@ function watcherFixtureSource(attemptPath) {
177
210
  return `
178
211
  const fs=require('fs');const path=require('path');
179
212
  if(!process.env.XDG_CONFIG_HOME){console.error('watcher did not receive XDG_CONFIG_HOME');process.exit(12)}
213
+ if(process.env.CONTROL_PLANE_API_KEY){console.error('watcher received tunnel owner credential');process.exit(13)}
214
+ if(process.env.PATCHWARDEN_OWNER_TOKEN){console.error('watcher received HTTP owner credential');process.exit(14)}
215
+ if(process.env.PATCHWARDEN_TEST_PROVIDER_KEY!=='provider-key-canary'){console.error('watcher did not receive allow-listed provider credential');process.exit(15)}
180
216
  let attempt=0;try{attempt=Number(fs.readFileSync(${JSON.stringify(attemptPath)},'utf8'))||0}catch{}
181
217
  attempt++;fs.writeFileSync(${JSON.stringify(attemptPath)},String(attempt));
182
218
  const cfg=JSON.parse(fs.readFileSync(process.env.PATCHWARDEN_CONFIG,'utf8'));
@@ -192,6 +228,7 @@ else setInterval(write,250);
192
228
  function tunnelFixtureSource() {
193
229
  return `
194
230
  const fs=require('fs');const path=require('path');const args=process.argv.slice(2);const command=args[0]||'';
231
+ if(process.env.PATCHWARDEN_TEST_PROVIDER_KEY){console.error('allow-listed provider credential leaked into tunnel-client');process.exit(22)}
195
232
  const flag=(name)=>{const i=args.indexOf(name);return i>=0?args[i+1]:''};
196
233
  if(command==='init'){const profile=flag('--profile')||'patchwarden';const mcpCommand=flag('--mcp-command')||'';const yamlDir=path.join(process.env.APPDATA,'tunnel-client');const yamlPath=path.join(yamlDir,profile+'.yaml');fs.mkdirSync(yamlDir,{recursive:true});fs.writeFileSync(yamlPath,'mcp:\\n commands:\\n - channel: main\\n command: \"'+mcpCommand.replace(/\\\\/g,'/')+'\"\\n\\nhealth:\\n listen_addr: \"127.0.0.1:8080\"\\n');console.log('{}');process.exit(0)}
197
234
  if(command==='doctor'){console.log('{}');process.exit(0)}
@@ -6,7 +6,7 @@ $RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\runtime"
6
6
  $HealthUrlFile = Join-Path $RuntimeDirectory "tunnel-health-url.txt"
7
7
 
8
8
  $env:PATCHWARDEN_CONFIG = $ConfigPath
9
- $healthJson = node --input-type=module -e "Promise.all([import('./dist/tools/healthCheck.js'),import('./dist/tools/registry.js')]).then(([h,r])=>console.log(JSON.stringify(h.healthCheck(r.getToolCatalogSnapshot()))))" 2>$null
9
+ $healthJson = node --input-type=module -e "Promise.all([import('./dist/tools/diagnostics/healthCheck.js'),import('./dist/tools/registry.js')]).then(([h,r])=>console.log(JSON.stringify(h.healthCheck(r.getToolCatalogSnapshot()))))" 2>$null
10
10
  if ($LASTEXITCODE -ne 0 -or -not $healthJson) {
11
11
  throw "Could not load PatchWarden health information. Run npm.cmd run build first."
12
12
  }
@@ -89,6 +89,58 @@ function Read-FileLineSafe {
89
89
  }
90
90
  }
91
91
 
92
+ function Write-JsonFileAtomic {
93
+ param([string]$Path, [object]$Payload)
94
+ $directory = Split-Path -Parent $Path
95
+ New-Item -ItemType Directory -Force -Path $directory | Out-Null
96
+ $temporary = "$Path.tmp"
97
+ $Payload | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $temporary -Encoding UTF8
98
+ Move-Item -LiteralPath $temporary -Destination $Path -Force
99
+ }
100
+
101
+ function Write-BackgroundStartingState {
102
+ param($Definition)
103
+ $statusPath = Join-Path $Definition.RuntimeDirectory "tunnel-status.json"
104
+ $previous = Read-JsonFileSafe -Path $statusPath
105
+ $payload = [ordered]@{
106
+ status = "starting"
107
+ reason_code = $null
108
+ ready = $false
109
+ attempt = 0
110
+ pid = $null
111
+ checked_at = (Get-Date).ToUniversalTime().ToString("o")
112
+ next_retry_at = $null
113
+ last_error = $null
114
+ last_exit_code = $null
115
+ stdout_tail = @()
116
+ stderr_tail = @()
117
+ stdout_log = Join-Path $Definition.RuntimeDirectory "tunnel-client.stdout.log"
118
+ stderr_log = Join-Path $Definition.RuntimeDirectory "tunnel-client.stderr.log"
119
+ server_version = if ($previous) { $previous.server_version } else { $null }
120
+ schema_epoch = if ($previous) { $previous.schema_epoch } else { $null }
121
+ tool_profile = $Definition.ToolProfile
122
+ tool_count = if ($previous) { $previous.tool_count } else { $null }
123
+ tool_names = if ($previous -and $previous.tool_names) { @($previous.tool_names) } else { @() }
124
+ tool_manifest_sha256 = if ($previous) { $previous.tool_manifest_sha256 } else { $null }
125
+ tools_ready = [bool]($previous -and $previous.tools_ready)
126
+ core_tools_ready = [bool]($Definition.Key -eq "core" -and $previous -and $previous.core_tools_ready)
127
+ }
128
+ Write-JsonFileAtomic -Path $statusPath -Payload $payload
129
+ }
130
+
131
+ function Write-BackgroundSupervisorState {
132
+ param($Definition, [string]$Status, [Nullable[int]]$ProcessId, [string]$ReasonCode = $null)
133
+ $payload = [ordered]@{
134
+ status = $Status
135
+ reason_code = $ReasonCode
136
+ pid = $ProcessId
137
+ checked_at = (Get-Date).ToUniversalTime().ToString("o")
138
+ stdout_log = Join-Path $Definition.RuntimeDirectory "supervisor.stdout.log"
139
+ stderr_log = Join-Path $Definition.RuntimeDirectory "supervisor.stderr.log"
140
+ }
141
+ Write-JsonFileAtomic -Path (Join-Path $Definition.RuntimeDirectory "supervisor-status.json") -Payload $payload
142
+ }
143
+
92
144
  function Test-TunnelHealthEndpoint {
93
145
  param([string]$HealthUrl)
94
146
  if (-not $HealthUrl) { return $null }
@@ -522,20 +574,33 @@ function Start-Mode {
522
574
  if ($Background) {
523
575
  $launcherScript = Join-Path $PSScriptRoot "start-patchwarden-tunnel.ps1"
524
576
  if (-not (Test-Path -LiteralPath $launcherScript)) { throw "Launcher script not found: $launcherScript" }
577
+ $backgroundWrapper = Join-Path $PSScriptRoot "run-background-supervisor.ps1"
578
+ if (-not (Test-Path -LiteralPath $backgroundWrapper)) { throw "Background wrapper not found: $backgroundWrapper" }
525
579
  $healthListenAddr = ([Uri]$Definition.HealthBaseUrl).Authority
526
580
  $arguments = @(
527
- "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $launcherScript,
581
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $backgroundWrapper,
528
582
  "-ToolProfile", $Definition.ToolProfile,
529
583
  "-Profile", $Definition.Profile,
530
- "-HealthListenAddr", $healthListenAddr,
531
- "-NoTunnelWebUi"
584
+ "-HealthListenAddr", $healthListenAddr
532
585
  )
533
586
  if (-not $Definition.HasWatcher) { $arguments += "-SkipWatcher" }
534
587
  if ($WhatIf) {
535
588
  Write-Host "[start:$($Definition.Key)] WHAT-IF: start hidden supervisor for $($Definition.Profile)" -ForegroundColor Magenta
536
589
  return
537
590
  }
538
- $process = Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden
591
+ New-Item -ItemType Directory -Force -Path $Definition.RuntimeDirectory | Out-Null
592
+ $supervisorStdout = Join-Path $Definition.RuntimeDirectory "supervisor.stdout.log"
593
+ $supervisorStderr = Join-Path $Definition.RuntimeDirectory "supervisor.stderr.log"
594
+ Write-BackgroundStartingState -Definition $Definition
595
+ Set-Content -LiteralPath $supervisorStdout -Value "" -Encoding UTF8 -NoNewline
596
+ Set-Content -LiteralPath $supervisorStderr -Value "" -Encoding UTF8 -NoNewline
597
+ try {
598
+ $process = Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden
599
+ Write-BackgroundSupervisorState -Definition $Definition -Status "running" -ProcessId $process.Id
600
+ } catch {
601
+ Write-BackgroundSupervisorState -Definition $Definition -Status "failed" -ProcessId $null -ReasonCode "supervisor_launch_failed"
602
+ throw
603
+ }
539
604
  Write-Host "[start:$($Definition.Key)] Started hidden supervisor PID $($process.Id). Logs: $($Definition.RuntimeDirectory)" -ForegroundColor Green
540
605
  return
541
606
  }
@@ -0,0 +1,127 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)]
4
+ [ValidateSet("core", "direct")]
5
+ [string]$Mode,
6
+ [AllowEmptyString()]
7
+ [string]$TunnelId = "",
8
+ [Parameter(Mandatory = $true)]
9
+ [string]$TunnelClientExe,
10
+ [Parameter(Mandatory = $true)]
11
+ [string]$ConfigPath,
12
+ [Parameter(Mandatory = $true)]
13
+ [string]$CredentialPath,
14
+ [ValidateSet("environment", "none", "manual")]
15
+ [string]$ProxyMode = "environment",
16
+ [AllowEmptyString()]
17
+ [string]$ProxyUrl = "",
18
+ [switch]$UseSavedCredential
19
+ )
20
+
21
+ $ErrorActionPreference = "Stop"
22
+ $ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
23
+ $Profile = if ($Mode -eq "direct") { "patchwarden-direct" } else { "patchwarden" }
24
+ $ToolProfile = if ($Mode -eq "direct") { "chatgpt_direct" } else { "chatgpt_core" }
25
+ $LauncherName = if ($Mode -eq "direct") { "patchwarden-mcp-direct.cmd" } else { "patchwarden-mcp-stdio.cmd" }
26
+ $HealthListenAddr = if ($Mode -eq "direct") { "127.0.0.1:8081" } else { "127.0.0.1:8080" }
27
+ $ProfilePath = Join-Path $env:APPDATA "tunnel-client\$Profile.yaml"
28
+ $McpLauncher = (Join-Path $ProjectRoot "scripts\mcp\$LauncherName") -replace "\\", "/"
29
+ $profileBackup = "$ProfilePath.patchwarden-provision-backup-$PID"
30
+ $hadProfile = Test-Path -LiteralPath $ProfilePath
31
+ $runtimeKey = $null
32
+
33
+ function Write-Result {
34
+ param([bool]$Ok, [string]$ReasonCode, [string]$NextStep)
35
+ [ordered]@{ ok = $Ok; reason_code = $ReasonCode; next_step = $NextStep } | ConvertTo-Json -Compress
36
+ }
37
+
38
+ function Get-DoctorReason {
39
+ param([string]$Text)
40
+ $value = [string]$Text
41
+ if ($value -match '(?i)unauthor|forbidden|invalid.*(?:key|token)|authentication|401|403') { return "authentication_failed" }
42
+ if ($value -match '(?i)proxy|ECONNREFUSED|connection refused|connect.*failed|timeout|timed out') { return "proxy_unreachable" }
43
+ if ($value -match '(?i)region|country|unsupported location|not available in') { return "region_unsupported" }
44
+ return "doctor_failed"
45
+ }
46
+
47
+ try {
48
+ if (-not (Test-Path -LiteralPath $TunnelClientExe -PathType Leaf)) { throw "tunnel_client_missing" }
49
+ if (-not (Test-Path -LiteralPath $ConfigPath -PathType Leaf)) { throw "config_missing" }
50
+ if (-not (Test-Path -LiteralPath ($McpLauncher -replace "/", "\") -PathType Leaf)) { throw "mcp_launcher_missing" }
51
+ if ($UseSavedCredential) {
52
+ if (-not (Test-Path -LiteralPath $ProfilePath -PathType Leaf)) { throw "tunnel_profile_missing" }
53
+ if (-not (Test-Path -LiteralPath $CredentialPath -PathType Leaf)) { throw "tunnel_credential_missing" }
54
+ $secure = ConvertTo-SecureString (Get-Content -LiteralPath $CredentialPath -Raw -Encoding UTF8)
55
+ $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
56
+ try { $runtimeKey = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
57
+ finally { if ($bstr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } }
58
+ } else {
59
+ if ([string]::IsNullOrWhiteSpace($TunnelId)) { throw "tunnel_id_missing" }
60
+ $runtimeKey = [Console]::In.ReadLine()
61
+ }
62
+ if ([string]::IsNullOrWhiteSpace($runtimeKey)) { throw "runtime_key_missing" }
63
+ $env:CONTROL_PLANE_API_KEY = $runtimeKey
64
+ $env:PATCHWARDEN_CONFIG = $ConfigPath
65
+ $env:NO_PROXY = "localhost,127.0.0.1,::1"
66
+
67
+ $useProxy = $false
68
+ switch ($ProxyMode) {
69
+ "manual" {
70
+ $proxyUri = $null
71
+ if (-not [Uri]::TryCreate($ProxyUrl, [UriKind]::Absolute, [ref]$proxyUri) -or $proxyUri.Scheme -notin @("http", "https", "socks5")) { throw "proxy_invalid" }
72
+ if (-not [string]::IsNullOrEmpty($proxyUri.UserInfo)) { throw "proxy_credentials_forbidden" }
73
+ $env:HTTP_PROXY = $ProxyUrl; $env:HTTPS_PROXY = $ProxyUrl; $env:ALL_PROXY = $ProxyUrl
74
+ $useProxy = $true
75
+ }
76
+ "none" { Remove-Item Env:HTTP_PROXY, Env:HTTPS_PROXY, Env:ALL_PROXY -ErrorAction SilentlyContinue }
77
+ "environment" { $useProxy = -not [string]::IsNullOrWhiteSpace($env:HTTPS_PROXY) }
78
+ }
79
+
80
+ if (-not $UseSavedCredential) {
81
+ New-Item -ItemType Directory -Force -Path (Split-Path -Parent $ProfilePath) | Out-Null
82
+ if ($hadProfile) { Copy-Item -LiteralPath $ProfilePath -Destination $profileBackup -Force }
83
+ & $TunnelClientExe init --sample sample_mcp_stdio_local --profile $Profile --tunnel-id $TunnelId --mcp-command $McpLauncher --force | Out-Null
84
+ if ($LASTEXITCODE -ne 0) { throw "profile_init_failed" }
85
+
86
+ $profileText = Get-Content -LiteralPath $ProfilePath -Raw -Encoding UTF8
87
+ if ($profileText -match 'listen_addr:\s*"[^"]*"') {
88
+ $profileText = $profileText -replace '(listen_addr:\s*)"[^"]*"', "`$1`"$HealthListenAddr`""
89
+ } elseif ($profileText -match '(?m)^health:') {
90
+ $profileText = $profileText -replace '(?m)^(health:.*)$', "`$1`n listen_addr: `"$HealthListenAddr`""
91
+ } else {
92
+ $profileText = "$($profileText.TrimEnd())`n`nhealth:`n listen_addr: `"$HealthListenAddr`"`n"
93
+ }
94
+ Set-Content -LiteralPath $ProfilePath -Value $profileText -Encoding UTF8 -NoNewline
95
+ }
96
+
97
+ $doctorArgs = @("doctor", "--profile", $Profile, "--explain", "--json")
98
+ if ($useProxy) { $doctorArgs += @("--http-proxy", "env:HTTPS_PROXY") }
99
+ $doctorOutput = (& $TunnelClientExe @doctorArgs 2>&1 | Out-String)
100
+ if ($LASTEXITCODE -ne 0) { throw (Get-DoctorReason -Text $doctorOutput) }
101
+
102
+ if (-not $UseSavedCredential) {
103
+ $secure = ConvertTo-SecureString $runtimeKey -AsPlainText -Force
104
+ $encrypted = ConvertFrom-SecureString $secure
105
+ $credentialDirectory = Split-Path -Parent $CredentialPath
106
+ New-Item -ItemType Directory -Force -Path $credentialDirectory | Out-Null
107
+ $credentialTemp = "$CredentialPath.tmp-$PID"
108
+ Set-Content -LiteralPath $credentialTemp -Value $encrypted -Encoding UTF8 -NoNewline
109
+ Move-Item -LiteralPath $credentialTemp -Destination $CredentialPath -Force
110
+ }
111
+ Remove-Item -LiteralPath $profileBackup -Force -ErrorAction SilentlyContinue
112
+ Write-Result -Ok $true -ReasonCode "configured" -NextStep "start_core"
113
+ exit 0
114
+ } catch {
115
+ if ($hadProfile -and (Test-Path -LiteralPath $profileBackup)) {
116
+ Move-Item -LiteralPath $profileBackup -Destination $ProfilePath -Force
117
+ } elseif (-not $hadProfile) {
118
+ Remove-Item -LiteralPath $ProfilePath -Force -ErrorAction SilentlyContinue
119
+ }
120
+ $reason = [string]$_.Exception.Message
121
+ if ($reason -notmatch '^[a-z0-9_]+$') { $reason = "provisioning_failed" }
122
+ Write-Result -Ok $false -ReasonCode $reason -NextStep $(if ($reason -eq "authentication_failed") { "replace_runtime_key" } elseif ($reason -eq "proxy_unreachable") { "check_proxy" } else { "review_tunnel_setup" })
123
+ exit 1
124
+ } finally {
125
+ $env:CONTROL_PLANE_API_KEY = $null
126
+ $runtimeKey = $null
127
+ }
@@ -0,0 +1,41 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)]
4
+ [ValidateSet("chatgpt_core", "chatgpt_direct")]
5
+ [string]$ToolProfile,
6
+
7
+ [Parameter(Mandatory = $true)]
8
+ [ValidateSet("patchwarden", "patchwarden-direct")]
9
+ [string]$Profile,
10
+
11
+ [Parameter(Mandatory = $true)]
12
+ [ValidatePattern('^127\.0\.0\.1:\d+$')]
13
+ [string]$HealthListenAddr,
14
+
15
+ [switch]$SkipWatcher
16
+ )
17
+
18
+ $ErrorActionPreference = "Stop"
19
+ $launcherScript = Join-Path $PSScriptRoot "start-patchwarden-tunnel.ps1"
20
+ $runtimeName = if ($ToolProfile -eq "chatgpt_direct") { "runtime-direct" } else { "runtime" }
21
+ $runtimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\$runtimeName"
22
+ $stdout = Join-Path $runtimeDirectory "supervisor.stdout.log"
23
+ $stderr = Join-Path $runtimeDirectory "supervisor.stderr.log"
24
+
25
+ New-Item -ItemType Directory -Force -Path $runtimeDirectory | Out-Null
26
+ $arguments = @(
27
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $launcherScript,
28
+ "-ToolProfile", $ToolProfile,
29
+ "-Profile", $Profile,
30
+ "-HealthListenAddr", $HealthListenAddr,
31
+ "-NoTunnelWebUi"
32
+ )
33
+ if ($SkipWatcher) { $arguments += "-SkipWatcher" }
34
+
35
+ try {
36
+ & powershell.exe @arguments 1> $stdout 2> $stderr
37
+ exit $LASTEXITCODE
38
+ } catch {
39
+ $_ | Out-String | Set-Content -LiteralPath $stderr -Encoding UTF8
40
+ exit 1
41
+ }