patchwarden 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -12,26 +12,26 @@
12
12
  import { createServer } from "node:http";
13
13
  import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
14
14
  import { logger } from "../logging.js";
15
- import { config, controlToken, errorMessage, host, PAGE_ALIASES, port, readBody, resolveTailParam, sendJson, } from "./shared.js";
15
+ import { config, controlToken, errorMessage, host, PAGE_ALIASES, port, readBody, sendJson, } from "./shared.js";
16
16
  import { recordEvent, removeStatusFile, writeStatusFile } from "./runtime.js";
17
- import { checkControlToken } from "./middleware/auth.js";
17
+ import { checkControlToken, isTrustedControlHost } from "./middleware/auth.js";
18
18
  import { serveFavicon, serveStatic } from "./middleware/static.js";
19
- import { handleTasks, handleStaleTasks, handleTaskDetail, handleTaskSafeResult, handleTaskSafeAudit, handleTaskSafeTestSummary, handleTaskSafeDiffSummary } from "./routes/tasks.js";
20
- import { handleReconcile, handleTaskAudit, handleOpenTaskFolder, handleHideStale } from "./routes/taskActions.js";
21
- import { handleDirectSessions, handleDirectSessionDetail, handleDirectSessionSafeSummary, handleDirectSessionFinalize, handleDirectSessionAudit, handleDirectSessionHide } from "./routes/sessions.js";
22
- import { handleLineages, handleLineageDetail } from "./routes/lineage.js";
23
- import { handleEvidencePacks, handleEvidencePackDetail, handleEvidencePackExport } from "./routes/evidence.js";
24
- import { handleProjectPolicy, handleReleaseStatus } from "./routes/policy.js";
25
- import { handleWorkspace, handleWorkspaceRepos, handleWorkspaceRepoStatus } from "./routes/workspace.js";
26
- import { handleManageAction, handleOpenLogsFolder } from "./routes/process.js";
27
- import { handleStatus, handleControlCenterStatus, handleEvents, handleTunnelUiUrl, handleDiagnostics } from "./routes/status.js";
28
- import { handleLogs, handleAudit, handleWarnings } from "./routes/audit.js";
19
+ import { buildRoutes } from "./routeTable.js";
29
20
  // ── Request router ────────────────────────────────────────────────
30
21
  async function handleRequest(req, res) {
31
- const parsedUrl = new URL(req.url || "/", `http://${req.headers.host || host}`);
22
+ res.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
23
+ res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
24
+ res.setHeader("Referrer-Policy", "no-referrer");
25
+ res.setHeader("X-Content-Type-Options", "nosniff");
26
+ res.setHeader("X-Frame-Options", "DENY");
27
+ if (!isTrustedControlHost(req, port)) {
28
+ sendJson(res, 421, { error: "Untrusted Host header" });
29
+ return;
30
+ }
31
+ const parsedUrl = new URL(req.url || "/", `http://${host}:${port}`);
32
32
  const pathname = parsedUrl.pathname;
33
33
  const method = (req.method || "GET").toUpperCase();
34
- // Static routes
34
+ // ── Static resource priority branches (not in routeTable.ts) ──
35
35
  if (method === "GET" && pathname === "/") {
36
36
  serveStatic(res, "pages/dashboard.html");
37
37
  return;
@@ -51,368 +51,39 @@ async function handleRequest(req, res) {
51
51
  }
52
52
  if (method === "GET" &&
53
53
  (pathname === "/colors_and_type.css" ||
54
+ pathname === "/desktop.css" ||
55
+ pathname === "/desktop-bridge.js" ||
56
+ pathname === "/i18n.js" ||
57
+ pathname === "/getting-started.js" ||
58
+ pathname === "/settings.js" ||
54
59
  pathname.startsWith("/pages/") ||
55
60
  pathname.startsWith("/partials/") ||
56
61
  pathname.startsWith("/vendor/"))) {
57
62
  serveStatic(res, pathname);
58
63
  return;
59
64
  }
60
- // GET API routes
61
- if (method === "GET" && pathname === "/api/status") {
62
- await handleStatus(res);
63
- return;
64
- }
65
- if (method === "GET" && pathname === "/api/tasks") {
66
- handleTasks(res, {
67
- repo_path: parsedUrl.searchParams.get("repo_path") || undefined,
68
- status: parsedUrl.searchParams.get("status") || undefined,
69
- acceptance_status: parsedUrl.searchParams.get("acceptance_status") || undefined,
70
- agent: parsedUrl.searchParams.get("agent") || undefined,
71
- warning_type: parsedUrl.searchParams.get("warning_type") || undefined,
72
- });
73
- return;
74
- }
75
- if (method === "GET" && pathname === "/api/tasks/stale") {
76
- handleStaleTasks(res);
77
- return;
78
- }
79
- if (method === "GET" && pathname === "/api/lineages") {
80
- handleLineages(res);
81
- return;
82
- }
83
- const lineageMatch = pathname.match(/^\/api\/lineages\/([^/]+)$/);
84
- if (method === "GET" && lineageMatch) {
85
- let lineageId;
86
- try {
87
- lineageId = decodeURIComponent(lineageMatch[1]);
88
- }
89
- catch {
90
- lineageId = lineageMatch[1];
91
- }
92
- handleLineageDetail(res, lineageId);
93
- return;
94
- }
95
- if (method === "GET" && pathname === "/api/project-policy") {
96
- handleProjectPolicy(res, parsedUrl.searchParams.get("repo_path") || ".");
97
- return;
98
- }
99
- if (method === "GET" && pathname === "/api/release/status") {
100
- handleReleaseStatus(res, parsedUrl.searchParams.get("repo_path") || ".");
101
- return;
102
- }
103
- if (method === "GET" && pathname === "/api/evidence-packs") {
104
- handleEvidencePacks(res);
105
- return;
106
- }
107
- const evidencePackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)$/);
108
- if (method === "GET" && evidencePackMatch) {
109
- let lineageId;
110
- try {
111
- lineageId = decodeURIComponent(evidencePackMatch[1]);
112
- }
113
- catch {
114
- lineageId = evidencePackMatch[1];
115
- }
116
- handleEvidencePackDetail(res, lineageId);
117
- return;
118
- }
119
- const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
120
- if (method === "GET" && taskMatch) {
121
- let taskId;
122
- try {
123
- taskId = decodeURIComponent(taskMatch[1]);
124
- }
125
- catch {
126
- taskId = taskMatch[1];
127
- }
128
- if (taskId === "." ||
129
- taskId === ".." ||
130
- taskId.includes("/") ||
131
- taskId.includes("\\") ||
132
- taskId.includes("\0")) {
133
- sendJson(res, 400, { error: "Invalid task id" });
134
- return;
135
- }
136
- handleTaskDetail(res, taskId);
137
- return;
138
- }
139
- // Safe, bounded views for task artifacts (no full stdout/stderr/diff).
140
- const taskSafeResultMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-result$/);
141
- if (method === "GET" && taskSafeResultMatch) {
142
- let taskId;
143
- try {
144
- taskId = decodeURIComponent(taskSafeResultMatch[1]);
145
- }
146
- catch {
147
- taskId = taskSafeResultMatch[1];
148
- }
149
- handleTaskSafeResult(res, taskId);
150
- return;
151
- }
152
- const taskSafeAuditMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-audit$/);
153
- if (method === "GET" && taskSafeAuditMatch) {
154
- let taskId;
155
- try {
156
- taskId = decodeURIComponent(taskSafeAuditMatch[1]);
157
- }
158
- catch {
159
- taskId = taskSafeAuditMatch[1];
160
- }
161
- handleTaskSafeAudit(res, taskId);
162
- return;
163
- }
164
- const taskSafeTestSummaryMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-test-summary$/);
165
- if (method === "GET" && taskSafeTestSummaryMatch) {
166
- let taskId;
167
- try {
168
- taskId = decodeURIComponent(taskSafeTestSummaryMatch[1]);
169
- }
170
- catch {
171
- taskId = taskSafeTestSummaryMatch[1];
172
- }
173
- handleTaskSafeTestSummary(res, taskId);
174
- return;
175
- }
176
- const taskSafeDiffSummaryMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/safe-diff-summary$/);
177
- if (method === "GET" && taskSafeDiffSummaryMatch) {
178
- let taskId;
179
- try {
180
- taskId = decodeURIComponent(taskSafeDiffSummaryMatch[1]);
181
- }
182
- catch {
183
- taskId = taskSafeDiffSummaryMatch[1];
184
- }
185
- handleTaskSafeDiffSummary(res, taskId);
186
- return;
187
- }
188
- // Logs: /api/logs/<category>?tail=<100|300|1000>
189
- const logsMatch = pathname.match(/^\/api\/logs\/([a-z-]+)$/);
190
- if (method === "GET" && logsMatch) {
191
- const rawCat = logsMatch[1];
192
- const category = rawCat === "core" || rawCat === "direct" || rawCat === "watcher" || rawCat === "control-center"
193
- ? rawCat
194
- : null;
195
- if (!category) {
196
- sendJson(res, 404, { error: "Unknown log category" });
197
- return;
198
- }
199
- const tail = resolveTailParam(parsedUrl.searchParams.get("tail"));
200
- handleLogs(res, category, tail);
201
- return;
202
- }
203
- if (method === "GET" && pathname === "/api/workspace") {
204
- handleWorkspace(res);
205
- return;
206
- }
207
- if (method === "GET" && pathname === "/api/workspace/repos") {
208
- handleWorkspaceRepos(res);
209
- return;
210
- }
211
- // On-demand git status for a single repo (path-traversal safe).
212
- // The repo segment is URL-decoded; traversal is rejected by guardWorkspacePath.
213
- const workspaceRepoMatch = pathname.match(/^\/api\/workspace\/([^/]+(?:\/[^/]+)*)\/status$/);
214
- if (method === "GET" && workspaceRepoMatch) {
215
- let repoParam;
216
- try {
217
- repoParam = decodeURIComponent(workspaceRepoMatch[1]);
218
- }
219
- catch {
220
- sendJson(res, 400, { error: "Invalid repo path encoding" });
221
- return;
222
- }
223
- handleWorkspaceRepoStatus(res, repoParam);
224
- return;
225
- }
226
- if (method === "GET" && pathname === "/api/direct-sessions") {
227
- handleDirectSessions(res);
228
- return;
229
- }
230
- const directSessionSummaryMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/summary$/);
231
- if (method === "GET" && directSessionSummaryMatch) {
232
- let sessionId;
233
- try {
234
- sessionId = decodeURIComponent(directSessionSummaryMatch[1]);
235
- }
236
- catch {
237
- sessionId = directSessionSummaryMatch[1];
238
- }
239
- handleDirectSessionSafeSummary(res, sessionId);
240
- return;
241
- }
242
- const directSessionMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)$/);
243
- if (method === "GET" && directSessionMatch) {
244
- let sessionId;
245
- try {
246
- sessionId = decodeURIComponent(directSessionMatch[1]);
247
- }
248
- catch {
249
- sessionId = directSessionMatch[1];
250
- }
251
- handleDirectSessionDetail(res, sessionId);
252
- return;
253
- }
254
- if (method === "GET" && pathname === "/api/audit") {
255
- handleAudit(res);
256
- return;
257
- }
258
- if (method === "GET" && pathname === "/api/warnings") {
259
- handleWarnings(res);
260
- return;
261
- }
262
- if (method === "GET" && pathname === "/api/diagnostics") {
263
- handleDiagnostics(res);
264
- return;
265
- }
266
- if (method === "GET" && pathname === "/api/tunnel-ui-url") {
267
- handleTunnelUiUrl(res);
268
- return;
269
- }
270
- if (method === "GET" && pathname === "/api/events") {
271
- const limitParam = parsedUrl.searchParams.get("limit");
272
- let limit = 100;
273
- if (limitParam !== null) {
274
- const n = parseInt(limitParam, 10);
275
- if (Number.isFinite(n) && n > 0 && n <= 1000)
276
- limit = n;
277
- }
278
- handleEvents(res, limit);
279
- return;
280
- }
281
- if (method === "GET" && pathname === "/api/control-center-status") {
282
- handleControlCenterStatus(res);
283
- return;
284
- }
285
- // POST API routes (all require control token)
65
+ // ── POST routes: drain body + verify control token before dispatch ──
66
+ // All POST routes in the table declare `requiresToken: true`; the body is
67
+ // drained up-front so HTTP keep-alive connections stay clean even when the
68
+ // route is unknown (preserving the original handleRequest behavior).
286
69
  if (method === "POST") {
287
- await readBody(req); // drain optional body
70
+ await readBody(req);
288
71
  if (!checkControlToken(req)) {
289
72
  sendJson(res, 403, { error: "Missing or invalid control token" });
290
73
  return;
291
74
  }
292
- if (pathname === "/api/start-all")
293
- return handleManageAction(res, "start", "all");
294
- if (pathname === "/api/stop-all")
295
- return handleManageAction(res, "stop", "all");
296
- if (pathname === "/api/restart-all")
297
- return handleManageAction(res, "restart", "all");
298
- if (pathname === "/api/core/start")
299
- return handleManageAction(res, "start", "core");
300
- if (pathname === "/api/core/stop")
301
- return handleManageAction(res, "stop", "core");
302
- if (pathname === "/api/direct/start")
303
- return handleManageAction(res, "start", "direct");
304
- if (pathname === "/api/direct/stop")
305
- return handleManageAction(res, "stop", "direct");
306
- if (pathname === "/api/open-logs-folder") {
307
- handleOpenLogsFolder(res);
308
- return;
309
- }
310
- // POST /api/direct-sessions/:sessionId/finalize — finalize a direct session
311
- // (must be matched BEFORE any generic /api/direct-sessions/:sessionId pattern)
312
- const finalizeDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/finalize$/);
313
- if (finalizeDirectMatch) {
314
- let sessionId;
315
- try {
316
- sessionId = decodeURIComponent(finalizeDirectMatch[1]);
317
- }
318
- catch {
319
- sessionId = finalizeDirectMatch[1];
320
- }
321
- await handleDirectSessionFinalize(res, sessionId);
322
- return;
323
- }
324
- // POST /api/direct-sessions/:sessionId/audit — audit a direct session
325
- const auditDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/audit$/);
326
- if (auditDirectMatch) {
327
- let sessionId;
328
- try {
329
- sessionId = decodeURIComponent(auditDirectMatch[1]);
330
- }
331
- catch {
332
- sessionId = auditDirectMatch[1];
333
- }
334
- handleDirectSessionAudit(res, sessionId);
335
- return;
336
- }
337
- // POST /api/direct-sessions/:sessionId/hide — hide a direct session from the list
338
- const hideDirectMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/hide$/);
339
- if (hideDirectMatch) {
340
- let sessionId;
341
- try {
342
- sessionId = decodeURIComponent(hideDirectMatch[1]);
343
- }
344
- catch {
345
- sessionId = hideDirectMatch[1];
346
- }
347
- handleDirectSessionHide(res, sessionId);
348
- return;
349
- }
350
- // POST /api/tasks/:taskId/reconcile (token already validated above)
351
- const reconcileMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/reconcile$/);
352
- if (reconcileMatch) {
353
- let taskId;
354
- try {
355
- taskId = decodeURIComponent(reconcileMatch[1]);
356
- }
357
- catch {
358
- taskId = reconcileMatch[1];
359
- }
360
- handleReconcile(res, taskId);
361
- return;
362
- }
363
- // POST /api/tasks/:taskId/audit — run audit_task safely
364
- const auditMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/audit$/);
365
- if (auditMatch) {
366
- let taskId;
367
- try {
368
- taskId = decodeURIComponent(auditMatch[1]);
369
- }
370
- catch {
371
- taskId = auditMatch[1];
372
- }
373
- handleTaskAudit(res, taskId);
374
- return;
375
- }
376
- // POST /api/tasks/:taskId/open-folder — open task folder in file explorer
377
- const openFolderMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/open-folder$/);
378
- if (openFolderMatch) {
379
- let taskId;
380
- try {
381
- taskId = decodeURIComponent(openFolderMatch[1]);
382
- }
383
- catch {
384
- taskId = openFolderMatch[1];
385
- }
386
- handleOpenTaskFolder(res, taskId);
387
- return;
388
- }
389
- // POST /api/tasks/:taskId/hide-stale — hide a stale task from the dashboard
390
- const hideStaleMatch = pathname.match(/^\/api\/tasks\/([^/]+)\/hide-stale$/);
391
- if (hideStaleMatch) {
392
- let taskId;
393
- try {
394
- taskId = decodeURIComponent(hideStaleMatch[1]);
395
- }
396
- catch {
397
- taskId = hideStaleMatch[1];
398
- }
399
- handleHideStale(res, taskId);
400
- return;
401
- }
402
- // POST /api/evidence-packs/:lineageId/export — export an evidence pack for a lineage
403
- const exportPackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)\/export$/);
404
- if (exportPackMatch) {
405
- let lineageId;
406
- try {
407
- lineageId = decodeURIComponent(exportPackMatch[1]);
408
- }
409
- catch {
410
- lineageId = exportPackMatch[1];
411
- }
412
- handleEvidencePackExport(res, lineageId);
413
- return;
414
- }
415
- sendJson(res, 404, { error: "Not found" });
75
+ }
76
+ // ── Declarative route table dispatch ──
77
+ // Filter by method → match pattern (in declaration order, so specific
78
+ // patterns win over generic `:id`) → call handler with capture-group slice.
79
+ const routes = buildRoutes(parsedUrl);
80
+ for (const route of routes) {
81
+ if (route.method !== method)
82
+ continue;
83
+ const match = pathname.match(route.pattern);
84
+ if (!match)
85
+ continue;
86
+ await route.handler(res, match.slice(1));
416
87
  return;
417
88
  }
418
89
  sendJson(res, 404, { error: "Not found" });
@@ -4,6 +4,8 @@ import { type PatchWardenConfig } from "../config.js";
4
4
  export declare const projectRoot: string;
5
5
  export declare const uiRoot: string;
6
6
  export declare const manageScriptPath: string;
7
+ export declare const activeConfigPath: string;
8
+ export declare const configIdentitySha256: string;
7
9
  export declare const PAGE_ALIASES: Record<string, string>;
8
10
  export declare function createFallbackConfig(): PatchWardenConfig;
9
11
  export declare let config: PatchWardenConfig;
@@ -13,7 +15,6 @@ export declare const port: number;
13
15
  export declare const host = "127.0.0.1";
14
16
  export declare const CORE_BASE_URL: string;
15
17
  export declare const DIRECT_BASE_URL: string;
16
- export declare const DEFAULT_TUNNEL_CLIENT_EXE = "D:\\ai_agent\\tunnel-client-v0.0.9--context-conduit-topaz-windows-amd64\\tunnel-client.exe";
17
18
  export declare const CONTROL_CENTER_FAVICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" rx=\"14\" fill=\"#0a0e14\"/>\n <path d=\"M32 52s17-8 17-27V14L32 8 15 14v11c0 19 17 27 17 27z\" fill=\"#111820\" stroke=\"#2dd4a8\" stroke-width=\"4\" stroke-linejoin=\"round\"/>\n <path d=\"M24 32l6 6 12-14\" fill=\"none\" stroke=\"#2dd4a8\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>";
18
19
  export declare function errorMessage(err: unknown): string;
19
20
  export declare function getRuntimeRoot(direct: boolean): string;
@@ -30,6 +31,29 @@ export declare function readJsonFileSafe<T = unknown>(filePath: string): T | nul
30
31
  export declare function isPathInside(root: string, target: string): boolean;
31
32
  export declare function readJsonFileSafeUnder<T = unknown>(root: string, relPath: string): T | null;
32
33
  export declare function readTextFileSafe(filePath: string): string | null;
34
+ /** Resolve a Control Center artifact path and reject symlink/junction escapes. */
35
+ export declare function guardControlPath(path: string, allowedPrefix: string): string | null;
33
36
  export declare function readFileTail(filePath: string, lines?: number): string;
34
37
  export declare function findLatestLog(dir: string, pattern: RegExp): string | null;
38
+ /**
39
+ * Declarative route descriptor consumed by `routeTable.ts` and dispatched by
40
+ * `server.ts`'s `handleRequest`. The handler receives the slice of path
41
+ * capture groups `[match[1], match[2], ...]` so wrappers can decode them via
42
+ * `decodeParam`. POST routes default to `requiresToken: true`.
43
+ */
44
+ export interface Route {
45
+ method: "GET" | "POST";
46
+ pattern: RegExp;
47
+ handler: (res: ServerResponse, params: string[]) => Promise<void> | void;
48
+ requiresToken?: boolean;
49
+ }
50
+ /**
51
+ * Safely decode a path capture group, falling back to the raw value if the
52
+ * percent-encoding is malformed. Accepts the raw `RegExpMatchArray` or the
53
+ * sliced `string[]` params passed to a Route handler — both are readable by
54
+ * numeric index. Note: under `lib: es2024+` `RegExpMatchArray` is a tuple
55
+ * `[string, ...string[]]`, so we widen the parameter to `readonly string[]` to
56
+ * accept both shapes without forcing callers to cast.
57
+ */
58
+ export declare function decodeParam(match: readonly string[] | RegExpMatchArray | null, index: number): string;
35
59
  export { delimiter, extname, join, resolve, isAbsolute, relative };
@@ -10,15 +10,33 @@
10
10
  import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
11
11
  import { delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import { randomUUID } from "node:crypto";
13
+ import { createHash, randomUUID } from "node:crypto";
14
14
  import { homedir } from "node:os";
15
15
  import { loadConfig } from "../config.js";
16
16
  import { logger } from "../logging.js";
17
+ import { readTextFilePrefixSync, readTextFileTailLinesSync } from "../utils/boundedFile.js";
18
+ import { redactSensitiveContent, redactSensitiveValue } from "../security/contentRedaction.js";
19
+ import { guardPath } from "../security/pathGuard.js";
17
20
  // ── Paths ─────────────────────────────────────────────────────────
18
21
  export const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
19
22
  export const uiRoot = join(projectRoot, "ui");
20
23
  export const manageScriptPath = join(projectRoot, "scripts", "control", "manage-patchwarden.ps1");
24
+ function resolveActiveConfigPath() {
25
+ if (process.env.PATCHWARDEN_CONFIG)
26
+ return resolve(process.env.PATCHWARDEN_CONFIG);
27
+ for (const name of ["patchwarden.config.json", ".patchwarden.json"]) {
28
+ const candidate = resolve(process.cwd(), name);
29
+ if (existsSync(candidate))
30
+ return candidate;
31
+ }
32
+ return resolve(process.cwd(), "patchwarden.config.json");
33
+ }
34
+ export const activeConfigPath = resolveActiveConfigPath();
35
+ export const configIdentitySha256 = createHash("sha256")
36
+ .update(process.platform === "win32" ? activeConfigPath.toLowerCase() : activeConfigPath)
37
+ .digest("hex");
21
38
  export const PAGE_ALIASES = {
39
+ "/getting-started.html": "pages/getting-started.html",
22
40
  "/dashboard.html": "pages/dashboard.html",
23
41
  "/tasks.html": "pages/tasks.html",
24
42
  "/workspace.html": "pages/workspace.html",
@@ -26,11 +44,15 @@ export const PAGE_ALIASES = {
26
44
  "/task-detail.html": "pages/task-detail.html",
27
45
  "/direct-sessions.html": "pages/direct-sessions.html",
28
46
  "/logs.html": "pages/logs.html",
47
+ "/settings.html": "pages/settings.html",
29
48
  };
30
49
  // ── Config (fault-tolerant bootstrap) ─────────────────────────────
31
50
  export function createFallbackConfig() {
32
51
  return {
33
- workspaceRoot: process.cwd(),
52
+ // Fault-tolerant dashboard mode must not widen a rejected workspace to the
53
+ // caller's cwd (which could be a home or drive root). The package root is
54
+ // bounded and all execution capabilities remain disabled below.
55
+ workspaceRoot: projectRoot,
34
56
  plansDir: ".patchwarden/plans",
35
57
  tasksDir: ".patchwarden/tasks",
36
58
  assessmentsDir: ".patchwarden/assessments",
@@ -90,7 +112,6 @@ export const host = "127.0.0.1";
90
112
  // not depend on the real 8080/8081 ports being free on the host.
91
113
  export const CORE_BASE_URL = process.env.PATCHWARDEN_CORE_URL || "http://127.0.0.1:8080";
92
114
  export const DIRECT_BASE_URL = process.env.PATCHWARDEN_DIRECT_URL || "http://127.0.0.1:8081";
93
- export const DEFAULT_TUNNEL_CLIENT_EXE = "D:\\ai_agent\\tunnel-client-v0.0.9--context-conduit-topaz-windows-amd64\\tunnel-client.exe";
94
115
  export const CONTROL_CENTER_FAVICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
95
116
  <rect width="64" height="64" rx="14" fill="#0a0e14"/>
96
117
  <path d="M32 52s17-8 17-27V14L32 8 15 14v11c0 19 17 27 17 27z" fill="#111820" stroke="#2dd4a8" stroke-width="4" stroke-linejoin="round"/>
@@ -207,8 +228,12 @@ export function readJsonFileSafe(filePath) {
207
228
  if (!existsSync(filePath))
208
229
  return null;
209
230
  try {
231
+ const size = statSync(filePath).size;
232
+ if (size > Math.max(1, config.maxReadFileBytes))
233
+ return null;
210
234
  const raw = readFileSync(filePath, "utf-8");
211
- return JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
235
+ const parsed = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
236
+ return redactSensitiveValue(parsed).value;
212
237
  }
213
238
  catch {
214
239
  return null;
@@ -238,20 +263,29 @@ export function readTextFileSafe(filePath) {
238
263
  if (!existsSync(filePath))
239
264
  return null;
240
265
  try {
241
- return readFileSync(filePath, "utf-8");
266
+ const bounded = readTextFilePrefixSync(filePath, Math.max(1, config.maxReadFileBytes));
267
+ return redactSensitiveContent(bounded.content).content;
268
+ }
269
+ catch {
270
+ return null;
271
+ }
272
+ }
273
+ /** Resolve a Control Center artifact path and reject symlink/junction escapes. */
274
+ export function guardControlPath(path, allowedPrefix) {
275
+ try {
276
+ return guardPath(path, config.workspaceRoot, allowedPrefix);
242
277
  }
243
278
  catch {
244
279
  return null;
245
280
  }
246
281
  }
247
282
  export function readFileTail(filePath, lines = 100) {
248
- if (!existsSync(filePath))
283
+ try {
284
+ return readTextFileTailLinesSync(filePath, lines);
285
+ }
286
+ catch {
249
287
  return "";
250
- const content = readFileSync(filePath, "utf-8");
251
- const allLines = content.split(/\r?\n/);
252
- if (allLines.length > 0 && allLines[allLines.length - 1] === "")
253
- allLines.pop();
254
- return allLines.slice(-lines).join("\n");
288
+ }
255
289
  }
256
290
  export function findLatestLog(dir, pattern) {
257
291
  if (!existsSync(dir))
@@ -282,6 +316,25 @@ export function findLatestLog(dir, pattern) {
282
316
  }
283
317
  return join(dir, latestName);
284
318
  }
319
+ /**
320
+ * Safely decode a path capture group, falling back to the raw value if the
321
+ * percent-encoding is malformed. Accepts the raw `RegExpMatchArray` or the
322
+ * sliced `string[]` params passed to a Route handler — both are readable by
323
+ * numeric index. Note: under `lib: es2024+` `RegExpMatchArray` is a tuple
324
+ * `[string, ...string[]]`, so we widen the parameter to `readonly string[]` to
325
+ * accept both shapes without forcing callers to cast.
326
+ */
327
+ export function decodeParam(match, index) {
328
+ if (!match)
329
+ return "";
330
+ const raw = match[index] || "";
331
+ try {
332
+ return decodeURIComponent(raw);
333
+ }
334
+ catch {
335
+ return raw;
336
+ }
337
+ }
285
338
  // Re-export widely-used node primitives so route modules have a single import
286
339
  // surface for path/delimiter helpers that are also needed alongside the shared
287
340
  // helpers above.