patchwarden 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * Control Center routes — process lifecycle proxy (/api/start-all,
3
+ * /api/stop-all, /api/restart-all, /api/core/*, /api/direct/*).
4
+ *
5
+ * Proxies start/stop/restart actions to scripts/control/manage-patchwarden.ps1
6
+ * and exposes open-logs-folder. Start/restart actions run a preflight that
7
+ * verifies the tunnel-client executable and launcher files exist before
8
+ * launching, so the non-interactive web UI never deadlocks on a missing
9
+ * dependency. All endpoints are POST routes gated by the control token.
10
+ */
11
+ import { existsSync, readFileSync, statSync } from "node:fs";
12
+ import { delimiter, extname, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { spawn } from "node:child_process";
15
+ import { buildChildEnvironment, redactProcessOutput, resolveTrustedExecutable, sanitizeTrustedPath, } from "../../runner/processSecurity.js";
16
+ import { recordEvent } from "../runtime.js";
17
+ import { launchFileManager } from "../fileManager.js";
18
+ import { config, CORE_BASE_URL, DIRECT_BASE_URL, errorMessage, getRuntimeRoot, manageScriptPath, projectRoot, sendJson, } from "../shared.js";
19
+ const MAX_MANAGE_OUTPUT_CHARS = 128 * 1024;
20
+ const MANAGE_ALLOWED_ENVIRONMENT = [
21
+ "PATCHWARDEN_CONFIG",
22
+ "PATCHWARDEN_TUNNEL_ID",
23
+ "PATCHWARDEN_CREDENTIAL_PATH",
24
+ "PATCHWARDEN_TUNNEL_CLIENT_EXE",
25
+ "TUNNEL_CLIENT_EXE",
26
+ "OPENCODE_BIN_DIR",
27
+ "HTTP_PROXY",
28
+ "HTTPS_PROXY",
29
+ "ALL_PROXY",
30
+ "NO_PROXY",
31
+ ];
32
+ function runManageAction(action, mode) {
33
+ return new Promise((resolveP, rejectP) => {
34
+ let child;
35
+ let exactValues = [];
36
+ try {
37
+ const env = buildManageEnvironment();
38
+ exactValues = manageSensitiveValues(env);
39
+ const command = resolveTrustedExecutable("powershell.exe", projectRoot, { pathValue: env.PATH });
40
+ child = spawn(command, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", manageScriptPath, action, mode, "-Background"], { cwd: projectRoot, windowsHide: true, env });
41
+ }
42
+ catch (err) {
43
+ rejectP(err);
44
+ return;
45
+ }
46
+ let stdout = "";
47
+ let stderr = "";
48
+ let settled = false;
49
+ const timer = setTimeout(() => {
50
+ if (settled)
51
+ return;
52
+ settled = true;
53
+ try {
54
+ child.kill();
55
+ }
56
+ catch { /* ignore */ }
57
+ rejectP(new Error(`manage-patchwarden.ps1 timed out after 60s (action=${action}, mode=${mode})`));
58
+ }, 60_000);
59
+ child.stdout.on("data", (d) => {
60
+ stdout = appendBoundedManageOutput(stdout, d.toString("utf-8"));
61
+ });
62
+ child.stderr.on("data", (d) => {
63
+ stderr = appendBoundedManageOutput(stderr, d.toString("utf-8"));
64
+ });
65
+ child.on("error", (err) => {
66
+ if (settled)
67
+ return;
68
+ settled = true;
69
+ clearTimeout(timer);
70
+ rejectP(err);
71
+ });
72
+ child.on("close", (code) => {
73
+ if (settled)
74
+ return;
75
+ settled = true;
76
+ clearTimeout(timer);
77
+ resolveP({
78
+ exitCode: code ?? -1,
79
+ stdout: redactProcessOutput(stdout, exactValues),
80
+ stderr: redactProcessOutput(stderr, exactValues),
81
+ });
82
+ });
83
+ });
84
+ }
85
+ export function resolveManageProfiles(mode, action, directEnabled) {
86
+ if (mode === "core" || mode === "direct")
87
+ return { selected: [mode], skipped: [] };
88
+ if ((action === "start" || action === "restart") && !directEnabled)
89
+ return { selected: ["core"], skipped: ["direct"] };
90
+ return { selected: ["core", "direct"], skipped: [] };
91
+ }
92
+ function selectedControlModes(mode, action = "status") {
93
+ return resolveManageProfiles(mode, action, config.enableDirectProfile === true).selected;
94
+ }
95
+ function skippedControlModes(mode, action) {
96
+ return resolveManageProfiles(mode, action, config.enableDirectProfile === true).skipped;
97
+ }
98
+ function effectiveManageMode(modes) {
99
+ return modes.length === 1 ? modes[0] : "all";
100
+ }
101
+ function launcherPathForMode(mode) {
102
+ const launcherName = mode === "direct" ? "Start-PatchWarden-Direct-Tunnel.cmd" : "Start-PatchWarden-Tunnel.cmd";
103
+ return join(projectRoot, "scripts", "launchers", launcherName);
104
+ }
105
+ function findExecutableOnPath(fileName) {
106
+ const pathValue = sanitizeTrustedPath(process.env.PATH || "", projectRoot);
107
+ const extensions = process.platform === "win32"
108
+ ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
109
+ : [""];
110
+ for (const entry of pathValue.split(delimiter)) {
111
+ if (!entry)
112
+ continue;
113
+ const direct = join(entry, fileName);
114
+ if (isRegularFile(direct))
115
+ return direct;
116
+ if (process.platform === "win32" && !extname(fileName)) {
117
+ for (const ext of extensions) {
118
+ const candidate = join(entry, fileName + ext.toLowerCase());
119
+ if (isRegularFile(candidate))
120
+ return candidate;
121
+ }
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ function findTunnelClientExecutable() {
127
+ if (process.env.PATCHWARDEN_CONTROL_FORCE_MISSING_TUNNEL_CLIENT === "1")
128
+ return null;
129
+ const configured = config.tunnelClientPath;
130
+ if (configured && isRegularFile(configured))
131
+ return configured;
132
+ const explicit = process.env.PATCHWARDEN_TUNNEL_CLIENT_EXE || process.env.TUNNEL_CLIENT_EXE;
133
+ if (explicit && isRegularFile(explicit))
134
+ return explicit;
135
+ const fromPath = findExecutableOnPath("tunnel-client.exe") ?? findExecutableOnPath("tunnel-client");
136
+ if (fromPath)
137
+ return fromPath;
138
+ const candidates = [
139
+ process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, "PatchWarden", "tunnel-client.exe") : null,
140
+ process.env.APPDATA ? join(process.env.APPDATA, "tunnel-client", "tunnel-client.exe") : null,
141
+ join(homedir(), "tunnel-client", "tunnel-client.exe"),
142
+ ].filter((v) => typeof v === "string" && v.length > 0);
143
+ for (const candidate of candidates) {
144
+ if (isRegularFile(candidate))
145
+ return candidate;
146
+ }
147
+ return null;
148
+ }
149
+ export function buildManageEnvironment() {
150
+ const agentEnvironmentNames = [...new Set(Object.values(config.agents)
151
+ .flatMap((agent) => agent.envAllowlist ?? []))];
152
+ const env = buildChildEnvironment({
153
+ cwd: projectRoot,
154
+ allowedNames: [...MANAGE_ALLOWED_ENVIRONMENT, ...agentEnvironmentNames],
155
+ blockedNames: [config.http?.ownerTokenEnv || "PATCHWARDEN_OWNER_TOKEN"],
156
+ });
157
+ const lifecycleNames = new Set(MANAGE_ALLOWED_ENVIRONMENT.map((name) => name.toUpperCase()));
158
+ const providerOnlyNames = agentEnvironmentNames.filter((name) => !lifecycleNames.has(name.toUpperCase()));
159
+ env.PATCHWARDEN_AGENT_ENV_ALLOWLIST = providerOnlyNames.join(";");
160
+ const tunnelClient = findTunnelClientExecutable();
161
+ if (tunnelClient) {
162
+ env.PATCHWARDEN_TUNNEL_CLIENT_EXE = tunnelClient;
163
+ env.TUNNEL_CLIENT_EXE = tunnelClient;
164
+ }
165
+ const proxy = config.tunnelProxy;
166
+ const core = proxy?.core || { mode: "environment" };
167
+ const direct = proxy?.scope === "separate" ? proxy.direct : core;
168
+ env.PATCHWARDEN_TUNNEL_PROXY_MODE = core.mode;
169
+ env.PATCHWARDEN_TUNNEL_PROXY_URL = core.mode === "manual" ? core.url || "" : "";
170
+ env.PATCHWARDEN_DIRECT_PROXY_MODE = direct.mode;
171
+ env.PATCHWARDEN_DIRECT_PROXY_URL = direct.mode === "manual" ? direct.url || "" : "";
172
+ return env;
173
+ }
174
+ // Re-exported so the status route can surface tunnel-client availability in
175
+ // /api/status without duplicating the discovery logic.
176
+ export { findTunnelClientExecutable };
177
+ function profilePathForMode(mode) {
178
+ if (!process.env.APPDATA)
179
+ return null;
180
+ return join(process.env.APPDATA, "tunnel-client", mode === "direct" ? "patchwarden-direct.yaml" : "patchwarden.yaml");
181
+ }
182
+ function credentialPath() {
183
+ return process.env.APPDATA ? join(process.env.APPDATA, "patchwarden", "control-plane-api-key.dpapi") : null;
184
+ }
185
+ function preflightManageAction(action, mode) {
186
+ if (action !== "start" && action !== "restart")
187
+ return null;
188
+ const selectedModes = selectedControlModes(mode, action);
189
+ const missingLaunchers = selectedModes
190
+ .map((m) => ({ mode: m, path: launcherPathForMode(m) }))
191
+ .filter((entry) => !existsSync(entry.path));
192
+ const tunnelClient = findTunnelClientExecutable();
193
+ const missingProfiles = selectedModes
194
+ .map((m) => ({ mode: m, path: profilePathForMode(m) }))
195
+ .filter((entry) => !entry.path || !existsSync(entry.path));
196
+ const savedCredential = credentialPath();
197
+ const missing = [];
198
+ if (!tunnelClient)
199
+ missing.push("tunnel-client.exe");
200
+ for (const entry of missingLaunchers)
201
+ missing.push(`${entry.mode} launcher`);
202
+ for (const entry of missingProfiles)
203
+ missing.push(`${entry.mode} profile`);
204
+ if (!savedCredential || !existsSync(savedCredential))
205
+ missing.push("tunnel runtime credential");
206
+ if (missing.length === 0)
207
+ return null;
208
+ const reasonCode = !tunnelClient
209
+ ? "tunnel_client_missing"
210
+ : missingLaunchers.length > 0
211
+ ? "launcher_missing"
212
+ : missingProfiles.length > 0
213
+ ? "tunnel_profile_missing"
214
+ : "tunnel_credential_missing";
215
+ return {
216
+ status: 409,
217
+ body: {
218
+ ok: false,
219
+ action,
220
+ mode,
221
+ reason_code: reasonCode,
222
+ error: "Control Center preflight failed. Start/restart from the Web UI is non-interactive, so required runtime dependencies must be available before launching.",
223
+ missing,
224
+ next_steps: [
225
+ "在桌面设置中自动检测或选择 tunnel-client.exe,也可设置 PATCHWARDEN_TUNNEL_CLIENT_EXE。",
226
+ "在桌面设置中配置 Tunnel ID 和专用 runtime API key,并完成 doctor 验证。",
227
+ "确认 scripts/launchers 下的启动器存在,然后重试。",
228
+ ],
229
+ },
230
+ };
231
+ }
232
+ function preflightDirectProfile(action, mode) {
233
+ if ((action !== "start" && action !== "restart") || mode !== "direct")
234
+ return null;
235
+ if (config.enableDirectProfile === true)
236
+ return null;
237
+ return {
238
+ status: 409,
239
+ body: {
240
+ ok: false,
241
+ action,
242
+ mode,
243
+ reason_code: "direct_profile_disabled",
244
+ error: "Direct profile 尚未启用。请先在桌面设置中启用 Direct,再启动对应隧道。",
245
+ next_steps: ["打开 设置 -> MCP 与隧道", "启用 Direct profile 并保存", "重新启动 Direct"],
246
+ },
247
+ };
248
+ }
249
+ function readRuntimeState(mode, fileName) {
250
+ try {
251
+ return JSON.parse(readFileSync(join(getRuntimeRoot(mode === "direct"), fileName), "utf8").replace(/^\uFEFF/, ""));
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ }
257
+ function readRuntimeLog(mode, fileName) {
258
+ try {
259
+ const value = readFileSync(join(getRuntimeRoot(mode === "direct"), fileName), "utf8");
260
+ return value.length > 64_000 ? value.slice(-64_000) : value;
261
+ }
262
+ catch {
263
+ return "";
264
+ }
265
+ }
266
+ export function classifySupervisorFailure(diagnosticText) {
267
+ if (/unsupported_country_region_territory|unsupported.{0,30}(country|region|territory)/i.test(diagnosticText))
268
+ return "unsupported_region";
269
+ if (/\b(401|403)\b|unauthori[sz]ed|invalid.{0,30}(api.?key|credential)|api.?key.{0,30}(invalid|missing)/i.test(diagnosticText))
270
+ return "auth_failed";
271
+ if (/proxy|ECONNREFUSED|ENETUNREACH|ETIMEDOUT|could not resolve|name resolution|connection (failed|refused)/i.test(diagnosticText))
272
+ return "proxy_unreachable";
273
+ if (/tool manifest preflight|tool_manifest_(check_failed|invalid)|mcp-manifest-check/i.test(diagnosticText))
274
+ return "tool_manifest_check_failed";
275
+ if (/spawn EPERM|access (is )?denied|permission denied/i.test(diagnosticText))
276
+ return "supervisor_permission_denied";
277
+ if (/watcher.{0,40}(failed|unhealthy|missing|exited)/i.test(diagnosticText))
278
+ return "watcher_unhealthy";
279
+ if (/profile.{0,30}(missing|invalid)|config.{0,30}(missing|invalid)|launcher.{0,30}(missing|not found)/i.test(diagnosticText))
280
+ return "config_error";
281
+ return "supervisor_exited";
282
+ }
283
+ function processIsAlive(pid) {
284
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
285
+ return false;
286
+ try {
287
+ process.kill(pid, 0);
288
+ return true;
289
+ }
290
+ catch (err) {
291
+ return err.code === "EPERM";
292
+ }
293
+ }
294
+ function inspectSupervisor(mode, startedAt) {
295
+ const state = readRuntimeState(mode, "supervisor-status.json");
296
+ const checkedAt = typeof state?.checked_at === "string" ? Date.parse(state.checked_at) : 0;
297
+ const fresh = Number.isFinite(checkedAt) && checkedAt >= startedAt - 2000;
298
+ if (!fresh)
299
+ return { terminal: false, reasonCode: null };
300
+ const explicitlyFailed = state?.status === "failed";
301
+ if (!explicitlyFailed && processIsAlive(state?.pid))
302
+ return { terminal: false, reasonCode: null };
303
+ const diagnostics = `${readRuntimeLog(mode, "supervisor.stderr.log")}\n${readRuntimeLog(mode, "supervisor.stdout.log")}`;
304
+ const recordedReason = typeof state?.reason_code === "string" ? state.reason_code : null;
305
+ return { terminal: true, reasonCode: recordedReason || classifySupervisorFailure(diagnostics) };
306
+ }
307
+ async function probeReady(baseUrl) {
308
+ const controller = new AbortController();
309
+ const timer = setTimeout(() => controller.abort(), 1500);
310
+ try {
311
+ const response = await fetch(`${baseUrl}/readyz`, { signal: controller.signal, cache: "no-store" });
312
+ return response.ok;
313
+ }
314
+ catch {
315
+ return false;
316
+ }
317
+ finally {
318
+ clearTimeout(timer);
319
+ }
320
+ }
321
+ async function inspectStartup(mode, startedAt) {
322
+ const tunnel = readRuntimeState(mode, "tunnel-status.json");
323
+ const checkedAt = typeof tunnel?.checked_at === "string" ? Date.parse(tunnel.checked_at) : 0;
324
+ const fresh = Number.isFinite(checkedAt) && checkedAt >= startedAt - 2000;
325
+ const endpointReady = await probeReady(mode === "direct" ? DIRECT_BASE_URL : CORE_BASE_URL);
326
+ const watcher = mode === "core" ? readRuntimeState(mode, "watcher-status.json") : null;
327
+ const watcherReady = mode === "direct" || watcher?.status === "healthy";
328
+ const ready = endpointReady && tunnel?.ready !== false && watcherReady;
329
+ const terminalStatuses = new Set(["failed", "error", "stopped", "restart_limit_reached"]);
330
+ const tunnelTerminal = fresh && terminalStatuses.has(String(tunnel?.status || "")) && tunnel?.reason_code !== "stopped_by_manager";
331
+ const supervisor = inspectSupervisor(mode, startedAt);
332
+ const terminal = tunnelTerminal || supervisor.terminal;
333
+ const reasonCode = tunnelTerminal
334
+ ? (typeof tunnel?.reason_code === "string" ? tunnel.reason_code : "startup_failed")
335
+ : supervisor.reasonCode;
336
+ return {
337
+ mode,
338
+ ready,
339
+ endpoint_ready: endpointReady,
340
+ watcher_ready: watcherReady,
341
+ status: tunnel?.status || "starting",
342
+ reason_code: reasonCode,
343
+ error: tunnel?.last_error || null,
344
+ terminal,
345
+ };
346
+ }
347
+ async function waitForStartup(modes, startedAt) {
348
+ const configured = Number(process.env.PATCHWARDEN_CONTROL_START_TIMEOUT_MS || "30000");
349
+ const timeoutMs = Number.isFinite(configured) ? Math.min(Math.max(configured, 1000), 120000) : 30000;
350
+ const deadline = Date.now() + timeoutMs;
351
+ let profiles = [];
352
+ while (Date.now() < deadline) {
353
+ profiles = await Promise.all(modes.map((mode) => inspectStartup(mode, startedAt)));
354
+ if (profiles.every((profile) => profile.ready === true))
355
+ return { ok: true, profiles };
356
+ const failed = profiles.find((profile) => profile.terminal === true);
357
+ if (failed) {
358
+ const reasonCode = typeof failed.reason_code === "string" ? failed.reason_code : "supervisor_exited";
359
+ return { ok: false, profiles, reasonCode };
360
+ }
361
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
362
+ }
363
+ profiles = await Promise.all(modes.map((mode) => inspectStartup(mode, startedAt)));
364
+ return { ok: false, profiles, reasonCode: "startup_timeout" };
365
+ }
366
+ export async function handleManageAction(res, action, mode) {
367
+ try {
368
+ const directPreflight = preflightDirectProfile(action, mode);
369
+ if (directPreflight) {
370
+ recordEvent("manage." + mode + "." + action + ".profile_disabled", { reason_code: "direct_profile_disabled" });
371
+ sendJson(res, directPreflight.status, directPreflight.body);
372
+ return;
373
+ }
374
+ const selectedModes = selectedControlModes(mode, action);
375
+ const skippedModes = skippedControlModes(mode, action);
376
+ const managedMode = effectiveManageMode(selectedModes);
377
+ const preflight = preflightManageAction(action, mode);
378
+ if (preflight) {
379
+ recordEvent("manage." + mode + "." + action + ".preflight_failed", {
380
+ missing: preflight.body.missing,
381
+ });
382
+ sendJson(res, preflight.status, {
383
+ ...preflight.body,
384
+ started_profiles: [],
385
+ skipped_profiles: skippedModes,
386
+ });
387
+ return;
388
+ }
389
+ const startedAt = Date.now();
390
+ const result = await runManageAction(action, managedMode);
391
+ if (result.exitCode !== 0) {
392
+ recordEvent("manage." + mode + "." + action, { exit_code: result.exitCode, ok: false });
393
+ sendJson(res, 500, {
394
+ ok: false,
395
+ action,
396
+ mode,
397
+ started_profiles: [],
398
+ skipped_profiles: skippedModes,
399
+ reason_code: "manager_failed",
400
+ error: result.stderr.trim() || result.stdout.trim() || `manage-patchwarden.ps1 exited with code ${result.exitCode}`,
401
+ });
402
+ return;
403
+ }
404
+ if (action === "start" || action === "restart") {
405
+ const startup = await waitForStartup(selectedModes, startedAt);
406
+ if (!startup.ok) {
407
+ const timeout = startup.reasonCode === "startup_timeout";
408
+ recordEvent("manage." + mode + "." + action + ".not_ready", { reason_code: startup.reasonCode, profiles: startup.profiles });
409
+ sendJson(res, timeout ? 504 : 502, {
410
+ ok: false,
411
+ action,
412
+ mode,
413
+ started_profiles: selectedModes,
414
+ skipped_profiles: skippedModes,
415
+ reason_code: startup.reasonCode,
416
+ error: timeout
417
+ ? "启动命令已执行,但服务未在等待时间内通过就绪检查。"
418
+ : "隧道 supervisor 在服务就绪前退出。",
419
+ profiles: startup.profiles,
420
+ next_steps: ["检查设置中的 tunnel-client 与代理", "运行健康检查", "查看本次 Core / Direct 日志"],
421
+ });
422
+ return;
423
+ }
424
+ }
425
+ recordEvent("manage." + mode + "." + action, {
426
+ exit_code: result.exitCode,
427
+ ok: result.exitCode === 0,
428
+ });
429
+ sendJson(res, 200, {
430
+ ok: result.exitCode === 0,
431
+ exitCode: result.exitCode,
432
+ stdout: result.stdout,
433
+ stderr: result.stderr,
434
+ verified: action === "start" || action === "restart",
435
+ started_profiles: action === "start" || action === "restart" ? selectedModes : [],
436
+ skipped_profiles: skippedModes,
437
+ reason_code: skippedModes.length > 0 ? "direct_profile_disabled" : null,
438
+ });
439
+ }
440
+ catch (err) {
441
+ recordEvent("manage." + mode + "." + action + ".failed", { error: errorMessage(err) });
442
+ sendJson(res, 500, { error: errorMessage(err) });
443
+ }
444
+ }
445
+ export function handleOpenLogsFolder(res) {
446
+ try {
447
+ const target = getRuntimeRoot(false);
448
+ launchFileManager(target, projectRoot);
449
+ sendJson(res, 200, { ok: true, path: target });
450
+ }
451
+ catch (err) {
452
+ sendJson(res, 500, { error: errorMessage(err) });
453
+ }
454
+ }
455
+ function appendBoundedManageOutput(current, next) {
456
+ if (current.length >= MAX_MANAGE_OUTPUT_CHARS)
457
+ return current;
458
+ return current + next.slice(0, MAX_MANAGE_OUTPUT_CHARS - current.length);
459
+ }
460
+ function manageSensitiveValues(env) {
461
+ const agentEnvironmentValues = Object.values(config.agents)
462
+ .flatMap((agent) => agent.envAllowlist ?? [])
463
+ .map((name) => findEnvironmentValue(env, name));
464
+ const values = [
465
+ env.HTTP_PROXY,
466
+ env.HTTPS_PROXY,
467
+ env.ALL_PROXY,
468
+ env.PATCHWARDEN_TUNNEL_PROXY_URL,
469
+ env.PATCHWARDEN_DIRECT_PROXY_URL,
470
+ ...agentEnvironmentValues,
471
+ ].filter((value) => typeof value === "string" && Buffer.byteLength(value, "utf-8") >= 8);
472
+ return [...new Set(values)].sort((left, right) => right.length - left.length);
473
+ }
474
+ function findEnvironmentValue(env, name) {
475
+ if (process.platform !== "win32")
476
+ return env[name];
477
+ const match = Object.keys(env).find((key) => key.toUpperCase() === name.toUpperCase());
478
+ return match ? env[match] : undefined;
479
+ }
480
+ function isRegularFile(path) {
481
+ try {
482
+ return statSync(path).isFile();
483
+ }
484
+ catch {
485
+ return false;
486
+ }
487
+ }
@@ -0,0 +1,30 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export interface DirectSessionFilters {
3
+ state?: "active" | "archive" | "finalized" | "audited" | "expired";
4
+ repo_path?: string;
5
+ date_from?: string;
6
+ date_to?: string;
7
+ limit?: number;
8
+ cursor?: string;
9
+ }
10
+ export declare function handleDirectSessions(res: ServerResponse, filters?: DirectSessionFilters): void;
11
+ export declare function handleDirectSessionDetail(res: ServerResponse, sessionId: string): void;
12
+ export declare function handleDirectSessionSafeSummary(res: ServerResponse, sessionId: string): void;
13
+ /**
14
+ * Finalize a direct session via safeFinalizeDirectSession. Returns the safe
15
+ * summary on success, or { error } on failure with HTTP 200 (fault tolerance:
16
+ * the UI always gets a JSON body it can render).
17
+ */
18
+ export declare function handleDirectSessionFinalize(res: ServerResponse, sessionId: string): Promise<void>;
19
+ /**
20
+ * Audit a direct session via safeAuditDirectSession. Returns the safe audit
21
+ * result on success, or { error } on failure (HTTP 200 for fault tolerance).
22
+ */
23
+ export declare function handleDirectSessionAudit(res: ServerResponse, sessionId: string): void;
24
+ /**
25
+ * Hide a direct session from the dashboard list. The session itself is NOT
26
+ * deleted or modified — only the control-center's local
27
+ * hidden-direct-session-ids.json state file is updated. Requires control token
28
+ * (enforced by the POST router).
29
+ */
30
+ export declare function handleDirectSessionHide(res: ServerResponse, sessionId: string): void;