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
package/dist/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
- import { isAbsolute, relative, resolve, sep } from "node:path";
2
+ import { basename, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { validateWorkspaceRoot } from "./security/workspaceRootGuard.js";
3
4
  // ── Defaults ──────────────────────────────────────────────────────
4
5
  const DEFAULT_CONFIG = {
5
6
  workspaceRoot: process.cwd(),
@@ -7,16 +8,7 @@ const DEFAULT_CONFIG = {
7
8
  tasksDir: ".patchwarden/tasks",
8
9
  assessmentsDir: ".patchwarden/assessments",
9
10
  assessmentTtlSeconds: 3600,
10
- agents: {
11
- codex: {
12
- command: "codex",
13
- args: ["exec", "--cd", "{repo}", "{prompt}"],
14
- },
15
- opencode: {
16
- command: "opencode",
17
- args: ["run", "{prompt}"],
18
- },
19
- },
11
+ agents: {},
20
12
  allowedTestCommands: [
21
13
  "npm test",
22
14
  "npm run test",
@@ -123,6 +115,11 @@ function normalizeConfig(config) {
123
115
  if (!config.workspaceRoot || typeof config.workspaceRoot !== "string") {
124
116
  throw new Error("workspaceRoot must be a non-empty string");
125
117
  }
118
+ const workspaceValidation = validateWorkspaceRoot(config.workspaceRoot);
119
+ if (!workspaceValidation.ok) {
120
+ throw new Error(`Invalid workspaceRoot: ${workspaceValidation.reason} (${workspaceValidation.path})`);
121
+ }
122
+ config.workspaceRoot = workspaceValidation.path;
126
123
  if (!config.plansDir || typeof config.plansDir !== "string") {
127
124
  throw new Error("plansDir must be a non-empty string");
128
125
  }
@@ -138,6 +135,21 @@ function normalizeConfig(config) {
138
135
  if (!config.agents || typeof config.agents !== "object") {
139
136
  throw new Error("agents must be an object");
140
137
  }
138
+ const agents = {};
139
+ for (const [agentName, agent] of Object.entries(config.agents)) {
140
+ if (!agent || typeof agent !== "object" || typeof agent.command !== "string" || !Array.isArray(agent.args)) {
141
+ throw new Error(`agents["${agentName}"] must define command and args`);
142
+ }
143
+ const envAllowlist = agent.envAllowlist ?? [];
144
+ if (!Array.isArray(envAllowlist) || envAllowlist.some((name) => typeof name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))) {
145
+ throw new Error(`agents["${agentName}"].envAllowlist must contain only environment variable names`);
146
+ }
147
+ const blockedNames = new Set(["CONTROL_PLANE_API_KEY", "PATCHWARDEN_OWNER_TOKEN", config.http?.ownerTokenEnv?.toUpperCase()]);
148
+ const blocked = envAllowlist.find((name) => blockedNames.has(name.toUpperCase()));
149
+ if (blocked)
150
+ throw new Error(`agents["${agentName}"].envAllowlist cannot include reserved variable "${blocked}"`);
151
+ agents[agentName] = { ...agent, args: [...agent.args], envAllowlist: [...new Set(envAllowlist)] };
152
+ }
141
153
  if (!Array.isArray(config.allowedTestCommands)) {
142
154
  throw new Error("allowedTestCommands must be an array");
143
155
  }
@@ -182,6 +194,42 @@ function normalizeConfig(config) {
182
194
  config.toolProfile !== "chatgpt_search") {
183
195
  throw new Error('toolProfile must be "full", "chatgpt_core", "chatgpt_direct", or "chatgpt_search"');
184
196
  }
197
+ if (config.repoAliases !== undefined) {
198
+ if (typeof config.repoAliases !== "object" || config.repoAliases === null || Array.isArray(config.repoAliases)) {
199
+ throw new Error("repoAliases must be an object mapping alias names to repository paths");
200
+ }
201
+ for (const [alias, target] of Object.entries(config.repoAliases)) {
202
+ if (typeof target !== "string") {
203
+ throw new Error(`repoAliases["${alias}"] must be a string`);
204
+ }
205
+ }
206
+ }
207
+ if (config.httpPort !== undefined) {
208
+ if (typeof config.httpPort !== "number" || !Number.isInteger(config.httpPort) || config.httpPort < 1 || config.httpPort > 65535) {
209
+ throw new Error("httpPort must be an integer from 1 to 65535");
210
+ }
211
+ }
212
+ if (config.http !== undefined) {
213
+ if (typeof config.http !== "object" || config.http === null || Array.isArray(config.http)) {
214
+ throw new Error("http must be an object");
215
+ }
216
+ if (config.http.port !== undefined) {
217
+ if (typeof config.http.port !== "number" || !Number.isInteger(config.http.port) || config.http.port < 1 || config.http.port > 65535) {
218
+ throw new Error("http.port must be an integer from 1 to 65535");
219
+ }
220
+ }
221
+ if (config.http.host !== undefined && typeof config.http.host !== "string") {
222
+ throw new Error("http.host must be a string");
223
+ }
224
+ if (config.http.ownerTokenEnv !== undefined
225
+ && (typeof config.http.ownerTokenEnv !== "string"
226
+ || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(config.http.ownerTokenEnv))) {
227
+ throw new Error("http.ownerTokenEnv must be a valid environment variable name");
228
+ }
229
+ }
230
+ if (config.enableRunTaskTool !== undefined && typeof config.enableRunTaskTool !== "boolean") {
231
+ throw new Error("enableRunTaskTool must be a boolean");
232
+ }
185
233
  if (config.enableAgentAssessment !== undefined && typeof config.enableAgentAssessment !== "boolean") {
186
234
  throw new Error("enableAgentAssessment must be a boolean");
187
235
  }
@@ -201,6 +249,44 @@ function normalizeConfig(config) {
201
249
  if (config.enableDirectProfile !== undefined && typeof config.enableDirectProfile !== "boolean") {
202
250
  throw new Error("enableDirectProfile must be a boolean");
203
251
  }
252
+ if (config.tunnelClientPath !== undefined) {
253
+ if (typeof config.tunnelClientPath !== "string" || !isAbsolute(config.tunnelClientPath)) {
254
+ throw new Error("tunnelClientPath must be an absolute path");
255
+ }
256
+ if (basename(config.tunnelClientPath).toLowerCase() !== "tunnel-client.exe") {
257
+ throw new Error("tunnelClientPath must point to tunnel-client.exe");
258
+ }
259
+ }
260
+ if (config.tunnelProxy !== undefined) {
261
+ if (!config.tunnelProxy || typeof config.tunnelProxy !== "object" || !["shared", "separate"].includes(config.tunnelProxy.scope)) {
262
+ throw new Error("tunnelProxy.scope must be shared or separate");
263
+ }
264
+ for (const key of ["core", "direct"]) {
265
+ const endpoint = config.tunnelProxy[key];
266
+ if (!endpoint || typeof endpoint !== "object" || !["environment", "none", "manual"].includes(endpoint.mode)) {
267
+ throw new Error(`tunnelProxy.${key}.mode must be environment, none, or manual`);
268
+ }
269
+ if (endpoint.mode === "manual") {
270
+ if (typeof endpoint.url !== "string")
271
+ throw new Error(`tunnelProxy.${key}.url is required in manual mode`);
272
+ let parsed;
273
+ try {
274
+ parsed = new URL(endpoint.url);
275
+ }
276
+ catch {
277
+ throw new Error(`tunnelProxy.${key}.url must be a valid URL`);
278
+ }
279
+ if (!["http:", "https:", "socks5:"].includes(parsed.protocol)) {
280
+ throw new Error(`tunnelProxy.${key}.url must use http, https, or socks5`);
281
+ }
282
+ if (parsed.username || parsed.password)
283
+ throw new Error(`tunnelProxy.${key}.url must not contain credentials`);
284
+ }
285
+ else if (endpoint.url !== undefined) {
286
+ throw new Error(`tunnelProxy.${key}.url is only allowed in manual mode`);
287
+ }
288
+ }
289
+ }
204
290
  if (!config.directSessionsDir || typeof config.directSessionsDir !== "string") {
205
291
  throw new Error("directSessionsDir must be a non-empty string");
206
292
  }
@@ -242,6 +328,7 @@ function normalizeConfig(config) {
242
328
  }
243
329
  return {
244
330
  ...config,
331
+ agents,
245
332
  workspaceRoot: resolve(config.workspaceRoot),
246
333
  allowedTestCommands: [...new Set(config.allowedTestCommands.map((command) => command.trim()))],
247
334
  repoAllowedTestCommands,
@@ -0,0 +1,2 @@
1
+ /** Launch a trusted platform file manager without inheriting ambient secrets. */
2
+ export declare function launchFileManager(target: string, cwd: string): boolean;
@@ -0,0 +1,39 @@
1
+ import { spawn } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { buildChildEnvironment, resolveTrustedExecutable } from "../runner/processSecurity.js";
4
+ const FILE_MANAGER_ENVIRONMENT = [
5
+ "DISPLAY",
6
+ "WAYLAND_DISPLAY",
7
+ "DBUS_SESSION_BUS_ADDRESS",
8
+ "XDG_RUNTIME_DIR",
9
+ ];
10
+ /** Launch a trusted platform file manager without inheriting ambient secrets. */
11
+ export function launchFileManager(target, cwd) {
12
+ try {
13
+ const env = buildChildEnvironment({ cwd, allowedNames: FILE_MANAGER_ENVIRONMENT });
14
+ const requested = fileManagerCommand(env);
15
+ const command = resolveTrustedExecutable(requested, cwd, { pathValue: env.PATH });
16
+ const child = spawn(command, [target], {
17
+ cwd,
18
+ detached: true,
19
+ env,
20
+ stdio: "ignore",
21
+ windowsHide: true,
22
+ });
23
+ child.on("error", () => { });
24
+ child.unref();
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ function fileManagerCommand(env) {
32
+ if (process.platform === "win32") {
33
+ const windowsRoot = env.SystemRoot || env.WINDIR;
34
+ return windowsRoot ? join(windowsRoot, "explorer.exe") : "explorer.exe";
35
+ }
36
+ if (process.platform === "darwin")
37
+ return "open";
38
+ return "xdg-open";
39
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Control Center auth middleware — control token validation.
3
+ *
4
+ * POST routes are gated behind an in-memory control token generated at boot.
5
+ * The token is exposed read-only via GET /control-token.json (handled by the
6
+ * server router) so the local dashboard can read it, but every mutating
7
+ * action must pass through `checkControlToken` first.
8
+ */
9
+ import { type IncomingMessage } from "node:http";
10
+ export declare function checkControlToken(req: IncomingMessage): boolean;
11
+ /**
12
+ * Reject non-loopback Host headers before exposing the browser-readable
13
+ * control token. Binding to 127.0.0.1 alone does not prevent DNS rebinding.
14
+ */
15
+ export declare function isTrustedControlHost(req: IncomingMessage, expectedPort: number): boolean;
16
+ export declare function isTrustedControlHostHeader(value: string | string[] | undefined, expectedPort: number): boolean;
@@ -0,0 +1,17 @@
1
+ import { firstHeaderValue, timingSafeStringEqual } from "../../security/secretComparison.js";
2
+ import { isTrustedLoopbackHostHeader } from "../../security/loopbackHost.js";
3
+ import { controlToken } from "../shared.js";
4
+ export function checkControlToken(req) {
5
+ const provided = firstHeaderValue(req.headers["x-patchwarden-control-token"]);
6
+ return provided.length > 0 && timingSafeStringEqual(provided, controlToken);
7
+ }
8
+ /**
9
+ * Reject non-loopback Host headers before exposing the browser-readable
10
+ * control token. Binding to 127.0.0.1 alone does not prevent DNS rebinding.
11
+ */
12
+ export function isTrustedControlHost(req, expectedPort) {
13
+ return isTrustedControlHostHeader(req.headers.host, expectedPort);
14
+ }
15
+ export function isTrustedControlHostHeader(value, expectedPort) {
16
+ return isTrustedLoopbackHostHeader(value, expectedPort);
17
+ }
@@ -0,0 +1,3 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export declare function serveStatic(res: ServerResponse, urlPath: string): void;
3
+ export declare function serveFavicon(res: ServerResponse): void;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Control Center static file middleware.
3
+ *
4
+ * Serves the bundled `ui/` directory (dashboard pages, partials, vendor
5
+ * assets, css) with path-traversal protection, plus the inline SVG favicon.
6
+ * Content types are mapped from `CONTENT_TYPES` in shared.ts.
7
+ */
8
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
9
+ import { extname, isAbsolute, relative, resolve } from "node:path";
10
+ import { CONTENT_TYPES, CONTROL_CENTER_FAVICON, errorMessage, sendJson, uiRoot, } from "../shared.js";
11
+ export function serveStatic(res, urlPath) {
12
+ let candidate = "";
13
+ try {
14
+ const normalized = urlPath.startsWith("/") ? urlPath.slice(1) : urlPath;
15
+ let decoded;
16
+ try {
17
+ decoded = decodeURIComponent(normalized);
18
+ }
19
+ catch {
20
+ sendJson(res, 400, { error: "Invalid path encoding" });
21
+ return;
22
+ }
23
+ if (decoded.includes("\0")) {
24
+ sendJson(res, 400, { error: "Invalid path" });
25
+ return;
26
+ }
27
+ const segments = decoded.split("/").filter(Boolean);
28
+ if (segments.some((s) => s === "..")) {
29
+ sendJson(res, 403, { error: "Forbidden" });
30
+ return;
31
+ }
32
+ candidate = resolve(uiRoot, ...segments);
33
+ const rel = relative(uiRoot, candidate);
34
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
35
+ sendJson(res, 403, { error: "Forbidden" });
36
+ return;
37
+ }
38
+ }
39
+ catch (err) {
40
+ sendJson(res, 500, { error: errorMessage(err) });
41
+ return;
42
+ }
43
+ try {
44
+ if (!existsSync(candidate) || !statSync(candidate).isFile()) {
45
+ sendJson(res, 404, { error: "Not found" });
46
+ return;
47
+ }
48
+ // Resolve links before reading. Lexical `..` checks alone do not stop a
49
+ // symlink or junction inside `ui/` from pointing outside the bundle.
50
+ const realRoot = realpathSync(uiRoot);
51
+ const realCandidate = realpathSync(candidate);
52
+ const realRelative = relative(realRoot, realCandidate);
53
+ if (realRelative === "" || realRelative.startsWith("..") || isAbsolute(realRelative)) {
54
+ sendJson(res, 403, { error: "Forbidden" });
55
+ return;
56
+ }
57
+ if (!statSync(realCandidate).isFile()) {
58
+ sendJson(res, 404, { error: "Not found" });
59
+ return;
60
+ }
61
+ const ext = extname(realCandidate).toLowerCase();
62
+ const contentType = CONTENT_TYPES[ext] || "application/octet-stream";
63
+ const content = readFileSync(realCandidate);
64
+ res.writeHead(200, { "Content-Type": contentType });
65
+ res.end(content);
66
+ }
67
+ catch (err) {
68
+ sendJson(res, 500, { error: errorMessage(err) });
69
+ }
70
+ }
71
+ export function serveFavicon(res) {
72
+ res.writeHead(200, {
73
+ "Content-Type": "image/svg+xml; charset=utf-8",
74
+ "Cache-Control": "public, max-age=86400",
75
+ "X-Content-Type-Options": "nosniff",
76
+ });
77
+ res.end(CONTROL_CENTER_FAVICON);
78
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * PatchWarden Control Center — declarative route table.
3
+ *
4
+ * Exports `buildRoutes(parsedUrl)` which returns the full `Route[]` array
5
+ * consumed by `server.ts`'s `handleRequest`. Routes that need query-string
6
+ * parameters (e.g. `/api/tasks`, `/api/logs/:category`) close over `parsedUrl`
7
+ * via the `query` helper, so each request gets its own self-contained table
8
+ * without module-scoped request state. This keeps concurrent requests safe.
9
+ *
10
+ * Ordering rules:
11
+ * - GET routes first, POST routes second.
12
+ * - Within each method, more specific patterns precede generic `:id`
13
+ * patterns (e.g. `/api/tasks/stale` before `/api/tasks/:id`).
14
+ * - Static asset routes (`/`, `/favicon.ico`, CSS/JS, `/pages/`,
15
+ * `/partials/`, `/vendor/`) and `/control-token.json` are NOT in this
16
+ * table — they remain priority branches in `server.ts`.
17
+ */
18
+ import { type Route } from "./shared.js";
19
+ /**
20
+ * Build the per-request `Route[]` table. `parsedUrl` is captured by closures
21
+ * so handlers that need query parameters can read `searchParams` without any
22
+ * module-scoped state.
23
+ */
24
+ export declare function buildRoutes(parsedUrl: URL): Route[];
@@ -0,0 +1,263 @@
1
+ /**
2
+ * PatchWarden Control Center — declarative route table.
3
+ *
4
+ * Exports `buildRoutes(parsedUrl)` which returns the full `Route[]` array
5
+ * consumed by `server.ts`'s `handleRequest`. Routes that need query-string
6
+ * parameters (e.g. `/api/tasks`, `/api/logs/:category`) close over `parsedUrl`
7
+ * via the `query` helper, so each request gets its own self-contained table
8
+ * without module-scoped request state. This keeps concurrent requests safe.
9
+ *
10
+ * Ordering rules:
11
+ * - GET routes first, POST routes second.
12
+ * - Within each method, more specific patterns precede generic `:id`
13
+ * patterns (e.g. `/api/tasks/stale` before `/api/tasks/:id`).
14
+ * - Static asset routes (`/`, `/favicon.ico`, CSS/JS, `/pages/`,
15
+ * `/partials/`, `/vendor/`) and `/control-token.json` are NOT in this
16
+ * table — they remain priority branches in `server.ts`.
17
+ */
18
+ import { decodeParam, resolveTailParam, sendJson } from "./shared.js";
19
+ import { isValidTaskId } from "./runtime.js";
20
+ import { handleTasks, handleStaleTasks, handleTaskDetail, handleTaskSafeResult, handleTaskSafeAudit, handleTaskSafeTestSummary, handleTaskSafeDiffSummary, } from "./routes/tasks.js";
21
+ import { handleReconcile, handleTaskAudit, handleOpenTaskFolder, handleHideStale, } from "./routes/taskActions.js";
22
+ import { handleDirectSessions, handleDirectSessionDetail, handleDirectSessionSafeSummary, handleDirectSessionFinalize, handleDirectSessionAudit, handleDirectSessionHide, } from "./routes/sessions.js";
23
+ import { handleLineages, handleLineageDetail } from "./routes/lineage.js";
24
+ import { handleEvidencePacks, handleEvidencePackDetail, handleEvidencePackExport, } from "./routes/evidence.js";
25
+ import { handleProjectPolicy, handleReleaseStatus } from "./routes/policy.js";
26
+ import { handleWorkspace, handleWorkspaceRepos, handleWorkspaceRepoStatus, } from "./routes/workspace.js";
27
+ import { handleManageAction, handleOpenLogsFolder } from "./routes/process.js";
28
+ import { handleStatus, handleControlCenterStatus, handleEvents, handleTunnelUiUrl, handleDiagnostics, } from "./routes/status.js";
29
+ import { handleLogs, handleAudit, handleWarnings } from "./routes/audit.js";
30
+ const KNOWN_LOG_CATEGORIES = new Set([
31
+ "core",
32
+ "direct",
33
+ "watcher",
34
+ "control-center",
35
+ ]);
36
+ /**
37
+ * Build the per-request `Route[]` table. `parsedUrl` is captured by closures
38
+ * so handlers that need query parameters can read `searchParams` without any
39
+ * module-scoped state.
40
+ */
41
+ export function buildRoutes(parsedUrl) {
42
+ const query = parsedUrl.searchParams;
43
+ // ── GET routes (no token required) ──────────────────────────────
44
+ const getRoutes = [
45
+ { method: "GET", pattern: /^\/api\/status$/, handler: (res) => handleStatus(res) },
46
+ {
47
+ method: "GET",
48
+ pattern: /^\/api\/tasks$/,
49
+ handler: (res) => handleTasks(res, {
50
+ repo_path: query.get("repo_path") || undefined,
51
+ status: query.get("status") || undefined,
52
+ acceptance_status: query.get("acceptance_status") || undefined,
53
+ agent: query.get("agent") || undefined,
54
+ warning_type: query.get("warning_type") || undefined,
55
+ date_from: query.get("date_from") || undefined,
56
+ date_to: query.get("date_to") || undefined,
57
+ limit: query.get("limit") ? Number(query.get("limit")) : undefined,
58
+ cursor: query.get("cursor") || undefined,
59
+ }),
60
+ },
61
+ // /api/tasks/stale MUST precede /api/tasks/:id (stale matches the :id pattern).
62
+ { method: "GET", pattern: /^\/api\/tasks\/stale$/, handler: (res) => handleStaleTasks(res) },
63
+ { method: "GET", pattern: /^\/api\/lineages$/, handler: (res) => handleLineages(res) },
64
+ {
65
+ method: "GET",
66
+ pattern: /^\/api\/lineages\/([^/]+)$/,
67
+ handler: (res, p) => handleLineageDetail(res, decodeParam(p, 0)),
68
+ },
69
+ {
70
+ method: "GET",
71
+ pattern: /^\/api\/project-policy$/,
72
+ handler: (res) => handleProjectPolicy(res, query.get("repo_path") || "."),
73
+ },
74
+ {
75
+ method: "GET",
76
+ pattern: /^\/api\/release\/status$/,
77
+ handler: (res) => handleReleaseStatus(res, query.get("repo_path") || "."),
78
+ },
79
+ { method: "GET", pattern: /^\/api\/evidence-packs$/, handler: (res) => handleEvidencePacks(res) },
80
+ {
81
+ method: "GET",
82
+ pattern: /^\/api\/evidence-packs\/([^/]+)$/,
83
+ handler: (res, p) => handleEvidencePackDetail(res, decodeParam(p, 0)),
84
+ },
85
+ {
86
+ method: "GET",
87
+ pattern: /^\/api\/tasks\/([^/]+)$/,
88
+ handler: (res, p) => {
89
+ const taskId = decodeParam(p, 0);
90
+ if (!isValidTaskId(taskId)) {
91
+ sendJson(res, 400, { error: "Invalid task id" });
92
+ return;
93
+ }
94
+ handleTaskDetail(res, taskId);
95
+ },
96
+ },
97
+ // Safe, bounded views for task artifacts (no full stdout/stderr/diff).
98
+ {
99
+ method: "GET",
100
+ pattern: /^\/api\/tasks\/([^/]+)\/safe-result$/,
101
+ handler: (res, p) => handleTaskSafeResult(res, decodeParam(p, 0)),
102
+ },
103
+ {
104
+ method: "GET",
105
+ pattern: /^\/api\/tasks\/([^/]+)\/safe-audit$/,
106
+ handler: (res, p) => handleTaskSafeAudit(res, decodeParam(p, 0)),
107
+ },
108
+ {
109
+ method: "GET",
110
+ pattern: /^\/api\/tasks\/([^/]+)\/safe-test-summary$/,
111
+ handler: (res, p) => handleTaskSafeTestSummary(res, decodeParam(p, 0)),
112
+ },
113
+ {
114
+ method: "GET",
115
+ pattern: /^\/api\/tasks\/([^/]+)\/safe-diff-summary$/,
116
+ handler: (res, p) => handleTaskSafeDiffSummary(res, decodeParam(p, 0)),
117
+ },
118
+ // /api/logs/<category>?tail=<100|300|1000>
119
+ {
120
+ method: "GET",
121
+ pattern: /^\/api\/logs\/([a-z-]+)$/,
122
+ handler: (res, p) => {
123
+ const rawCat = p[0] || "";
124
+ const category = KNOWN_LOG_CATEGORIES.has(rawCat)
125
+ ? rawCat
126
+ : null;
127
+ if (!category) {
128
+ sendJson(res, 404, { error: "Unknown log category" });
129
+ return;
130
+ }
131
+ const tail = resolveTailParam(query.get("tail"));
132
+ handleLogs(res, category, tail);
133
+ },
134
+ },
135
+ { method: "GET", pattern: /^\/api\/workspace$/, handler: (res) => handleWorkspace(res) },
136
+ // /api/workspace/repos MUST precede /api/workspace/:repo+/status (no segment conflict,
137
+ // but preserved for ordering clarity).
138
+ { method: "GET", pattern: /^\/api\/workspace\/repos$/, handler: (res) => handleWorkspaceRepos(res) },
139
+ {
140
+ method: "GET",
141
+ pattern: /^\/api\/workspace\/([^/]+(?:\/[^/]+)*)\/status$/,
142
+ handler: (res, p) => {
143
+ const raw = p[0] || "";
144
+ let repoParam;
145
+ try {
146
+ repoParam = decodeURIComponent(raw);
147
+ }
148
+ catch {
149
+ sendJson(res, 400, { error: "Invalid repo path encoding" });
150
+ return;
151
+ }
152
+ handleWorkspaceRepoStatus(res, repoParam);
153
+ },
154
+ },
155
+ {
156
+ method: "GET",
157
+ pattern: /^\/api\/direct-sessions$/,
158
+ handler: (res) => handleDirectSessions(res, {
159
+ state: (query.get("state") || undefined),
160
+ repo_path: query.get("repo_path") || undefined,
161
+ date_from: query.get("date_from") || undefined,
162
+ date_to: query.get("date_to") || undefined,
163
+ limit: query.get("limit") ? Number(query.get("limit")) : undefined,
164
+ cursor: query.get("cursor") || undefined,
165
+ }),
166
+ },
167
+ // /api/direct-sessions/:id/summary MUST precede /api/direct-sessions/:id.
168
+ {
169
+ method: "GET",
170
+ pattern: /^\/api\/direct-sessions\/([^/]+)\/summary$/,
171
+ handler: (res, p) => handleDirectSessionSafeSummary(res, decodeParam(p, 0)),
172
+ },
173
+ {
174
+ method: "GET",
175
+ pattern: /^\/api\/direct-sessions\/([^/]+)$/,
176
+ handler: (res, p) => handleDirectSessionDetail(res, decodeParam(p, 0)),
177
+ },
178
+ { method: "GET", pattern: /^\/api\/audit$/, handler: (res) => handleAudit(res) },
179
+ { method: "GET", pattern: /^\/api\/warnings$/, handler: (res) => handleWarnings(res) },
180
+ { method: "GET", pattern: /^\/api\/diagnostics$/, handler: (res) => handleDiagnostics(res) },
181
+ { method: "GET", pattern: /^\/api\/tunnel-ui-url$/, handler: (res) => handleTunnelUiUrl(res) },
182
+ {
183
+ method: "GET",
184
+ pattern: /^\/api\/events$/,
185
+ handler: (res) => {
186
+ const limitParam = query.get("limit");
187
+ let limit = 100;
188
+ if (limitParam !== null) {
189
+ const n = parseInt(limitParam, 10);
190
+ if (Number.isFinite(n) && n > 0 && n <= 1000)
191
+ limit = n;
192
+ }
193
+ handleEvents(res, limit);
194
+ },
195
+ },
196
+ {
197
+ method: "GET",
198
+ pattern: /^\/api\/control-center-status$/,
199
+ handler: (res) => handleControlCenterStatus(res),
200
+ },
201
+ ];
202
+ // ── POST routes (requiresToken: true) ──────────────────────────
203
+ const postRoutes = [
204
+ { method: "POST", pattern: /^\/api\/start-all$/, requiresToken: true, handler: (res) => handleManageAction(res, "start", "all") },
205
+ { method: "POST", pattern: /^\/api\/stop-all$/, requiresToken: true, handler: (res) => handleManageAction(res, "stop", "all") },
206
+ { method: "POST", pattern: /^\/api\/restart-all$/, requiresToken: true, handler: (res) => handleManageAction(res, "restart", "all") },
207
+ { method: "POST", pattern: /^\/api\/core\/start$/, requiresToken: true, handler: (res) => handleManageAction(res, "start", "core") },
208
+ { method: "POST", pattern: /^\/api\/core\/stop$/, requiresToken: true, handler: (res) => handleManageAction(res, "stop", "core") },
209
+ { method: "POST", pattern: /^\/api\/direct\/start$/, requiresToken: true, handler: (res) => handleManageAction(res, "start", "direct") },
210
+ { method: "POST", pattern: /^\/api\/direct\/stop$/, requiresToken: true, handler: (res) => handleManageAction(res, "stop", "direct") },
211
+ { method: "POST", pattern: /^\/api\/open-logs-folder$/, requiresToken: true, handler: (res) => handleOpenLogsFolder(res) },
212
+ // /api/direct-sessions/:id/finalize MUST precede any generic /api/direct-sessions/:id pattern.
213
+ {
214
+ method: "POST",
215
+ pattern: /^\/api\/direct-sessions\/([^/]+)\/finalize$/,
216
+ requiresToken: true,
217
+ handler: (res, p) => handleDirectSessionFinalize(res, decodeParam(p, 0)),
218
+ },
219
+ {
220
+ method: "POST",
221
+ pattern: /^\/api\/direct-sessions\/([^/]+)\/audit$/,
222
+ requiresToken: true,
223
+ handler: (res, p) => handleDirectSessionAudit(res, decodeParam(p, 0)),
224
+ },
225
+ {
226
+ method: "POST",
227
+ pattern: /^\/api\/direct-sessions\/([^/]+)\/hide$/,
228
+ requiresToken: true,
229
+ handler: (res, p) => handleDirectSessionHide(res, decodeParam(p, 0)),
230
+ },
231
+ {
232
+ method: "POST",
233
+ pattern: /^\/api\/tasks\/([^/]+)\/reconcile$/,
234
+ requiresToken: true,
235
+ handler: (res, p) => handleReconcile(res, decodeParam(p, 0)),
236
+ },
237
+ {
238
+ method: "POST",
239
+ pattern: /^\/api\/tasks\/([^/]+)\/audit$/,
240
+ requiresToken: true,
241
+ handler: (res, p) => handleTaskAudit(res, decodeParam(p, 0)),
242
+ },
243
+ {
244
+ method: "POST",
245
+ pattern: /^\/api\/tasks\/([^/]+)\/open-folder$/,
246
+ requiresToken: true,
247
+ handler: (res, p) => handleOpenTaskFolder(res, decodeParam(p, 0)),
248
+ },
249
+ {
250
+ method: "POST",
251
+ pattern: /^\/api\/tasks\/([^/]+)\/hide-stale$/,
252
+ requiresToken: true,
253
+ handler: (res, p) => handleHideStale(res, decodeParam(p, 0)),
254
+ },
255
+ {
256
+ method: "POST",
257
+ pattern: /^\/api\/evidence-packs\/([^/]+)\/export$/,
258
+ requiresToken: true,
259
+ handler: (res, p) => handleEvidencePackExport(res, decodeParam(p, 0)),
260
+ },
261
+ ];
262
+ return [...getRoutes, ...postRoutes];
263
+ }
@@ -0,0 +1,15 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export type LogCategory = "core" | "direct" | "watcher" | "control-center";
3
+ export declare function handleLogs(res: ServerResponse, category: LogCategory, tailLines: number): void;
4
+ export declare function handleAudit(res: ServerResponse): void;
5
+ /**
6
+ * Aggregate warnings from multiple sources:
7
+ * - audit.json warnings/fail_checks/possible_false_positives/manual_verification_required
8
+ * - stale task classification (heartbeat stale, collecting_artifacts stale, etc.)
9
+ * - task status=failed_verification
10
+ *
11
+ * Each warning type is grouped into a bucket with affected task IDs. The
12
+ * handler is fault-tolerant: any per-task read failure is skipped, and a
13
+ * top-level failure returns an empty warnings array (never 500).
14
+ */
15
+ export declare function handleWarnings(res: ServerResponse): void;