patchwarden 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
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
  }
@@ -209,8 +221,10 @@ function normalizeConfig(config) {
209
221
  if (config.http.host !== undefined && typeof config.http.host !== "string") {
210
222
  throw new Error("http.host must be a string");
211
223
  }
212
- if (config.http.ownerTokenEnv !== undefined && typeof config.http.ownerTokenEnv !== "string") {
213
- throw new Error("http.ownerTokenEnv must be a string");
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");
214
228
  }
215
229
  }
216
230
  if (config.enableRunTaskTool !== undefined && typeof config.enableRunTaskTool !== "boolean") {
@@ -235,6 +249,44 @@ function normalizeConfig(config) {
235
249
  if (config.enableDirectProfile !== undefined && typeof config.enableDirectProfile !== "boolean") {
236
250
  throw new Error("enableDirectProfile must be a boolean");
237
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
+ }
238
290
  if (!config.directSessionsDir || typeof config.directSessionsDir !== "string") {
239
291
  throw new Error("directSessionsDir must be a non-empty string");
240
292
  }
@@ -276,6 +328,7 @@ function normalizeConfig(config) {
276
328
  }
277
329
  return {
278
330
  ...config,
331
+ agents,
279
332
  workspaceRoot: resolve(config.workspaceRoot),
280
333
  allowedTestCommands: [...new Set(config.allowedTestCommands.map((command) => command.trim()))],
281
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
+ }
@@ -8,3 +8,9 @@
8
8
  */
9
9
  import { type IncomingMessage } from "node:http";
10
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;
@@ -1,8 +1,17 @@
1
+ import { firstHeaderValue, timingSafeStringEqual } from "../../security/secretComparison.js";
2
+ import { isTrustedLoopbackHostHeader } from "../../security/loopbackHost.js";
1
3
  import { controlToken } from "../shared.js";
2
4
  export function checkControlToken(req) {
3
- const header = req.headers["x-patchwarden-control-token"];
4
- const provided = Array.isArray(header) ? header[0] : header;
5
- if (typeof provided !== "string" || provided.length === 0)
6
- return false;
7
- return provided === controlToken;
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);
8
17
  }
@@ -5,7 +5,7 @@
5
5
  * assets, css) with path-traversal protection, plus the inline SVG favicon.
6
6
  * Content types are mapped from `CONTENT_TYPES` in shared.ts.
7
7
  */
8
- import { existsSync, readFileSync, statSync } from "node:fs";
8
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
9
9
  import { extname, isAbsolute, relative, resolve } from "node:path";
10
10
  import { CONTENT_TYPES, CONTROL_CENTER_FAVICON, errorMessage, sendJson, uiRoot, } from "../shared.js";
11
11
  export function serveStatic(res, urlPath) {
@@ -45,9 +45,22 @@ export function serveStatic(res, urlPath) {
45
45
  sendJson(res, 404, { error: "Not found" });
46
46
  return;
47
47
  }
48
- const ext = extname(candidate).toLowerCase();
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();
49
62
  const contentType = CONTENT_TYPES[ext] || "application/octet-stream";
50
- const content = readFileSync(candidate);
63
+ const content = readFileSync(realCandidate);
51
64
  res.writeHead(200, { "Content-Type": contentType });
52
65
  res.end(content);
53
66
  }
@@ -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
+ }
@@ -8,11 +8,11 @@
8
8
  */
9
9
  import { existsSync, readdirSync } from "node:fs";
10
10
  import { join } from "node:path";
11
- import { listTasks } from "../../tools/listTasks.js";
11
+ import { listTasks } from "../../tools/tasks/listTasks.js";
12
12
  import { redactSensitiveContent } from "../../security/contentRedaction.js";
13
13
  import { getDirectSessionsDir, getTasksDir } from "../../config.js";
14
14
  import { classifyStaleTask, fileMtimeIso, parseReviewVerdict, readWatcherStatusSafe, } from "../runtime.js";
15
- import { config, errorMessage, findLatestLog, getControlCenterLogDir, getRuntimeRoot, readFileTail, readJsonFileSafe, readTextFileSafe, sendJson, } from "../shared.js";
15
+ import { config, errorMessage, findLatestLog, guardControlPath, getControlCenterLogDir, getRuntimeRoot, readFileTail, readJsonFileSafe, readTextFileSafe, sendJson, } from "../shared.js";
16
16
  export function handleLogs(res, category, tailLines) {
17
17
  try {
18
18
  let dir;
@@ -50,6 +50,8 @@ export function handleLogs(res, category, tailLines) {
50
50
  stderr: "",
51
51
  category,
52
52
  tail: tailLines,
53
+ historical_snapshot: true,
54
+ redacted: true,
53
55
  reason: "log file not found",
54
56
  });
55
57
  return;
@@ -58,10 +60,10 @@ export function handleLogs(res, category, tailLines) {
58
60
  const stderrRaw = stderrExists ? readFileTail(stderrPath, tailLines) : "";
59
61
  const stdout = redactSensitiveContent(stdoutRaw).content;
60
62
  const stderr = redactSensitiveContent(stderrRaw).content;
61
- sendJson(res, 200, { stdout, stderr, category, tail: tailLines, reason: null });
63
+ sendJson(res, 200, { stdout, stderr, category, tail: tailLines, historical_snapshot: true, redacted: true, reason: null });
62
64
  }
63
65
  catch (err) {
64
- sendJson(res, 200, { stdout: "", stderr: "", category, tail: tailLines, reason: errorMessage(err) });
66
+ sendJson(res, 200, { stdout: "", stderr: "", category, tail: tailLines, historical_snapshot: true, redacted: true, reason: errorMessage(err) });
65
67
  }
66
68
  }
67
69
  export function handleAudit(res) {
@@ -79,7 +81,9 @@ export function handleAudit(res) {
79
81
  taskEntries = [];
80
82
  }
81
83
  for (const entry of taskEntries) {
82
- const taskDir = join(tasksDir, entry.name);
84
+ const taskDir = guardControlPath(join(tasksDir, entry.name), config.tasksDir);
85
+ if (!taskDir)
86
+ continue;
83
87
  // independent-review.md
84
88
  const reviewFile = join(taskDir, "independent-review.md");
85
89
  if (existsSync(reviewFile)) {
@@ -118,7 +122,10 @@ export function handleAudit(res) {
118
122
  sessionEntries = [];
119
123
  }
120
124
  for (const entry of sessionEntries) {
121
- const auditFile = join(sessionsDir, entry.name, "audit.json");
125
+ const sessionDir = guardControlPath(join(sessionsDir, entry.name), config.directSessionsDir);
126
+ if (!sessionDir)
127
+ continue;
128
+ const auditFile = join(sessionDir, "audit.json");
122
129
  if (!existsSync(auditFile))
123
130
  continue;
124
131
  const data = readJsonFileSafe(auditFile);
@@ -181,7 +188,10 @@ export function handleWarnings(res) {
181
188
  for (const task of taskList) {
182
189
  const taskId = task.task_id;
183
190
  // 1. Read audit.json for warning strings
184
- const auditFile = join(tasksDir, taskId, "audit.json");
191
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
192
+ if (!taskDir)
193
+ continue;
194
+ const auditFile = join(taskDir, "audit.json");
185
195
  const audit = readJsonFileSafe(auditFile);
186
196
  if (audit) {
187
197
  const warningTexts = [];
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { existsSync, readdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
- import { exportTaskEvidencePack, listEvidencePacks, readEvidencePack, } from "../../tools/evidencePack.js";
10
+ import { exportTaskEvidencePack, listEvidencePacks, readEvidencePack, } from "../../tools/tasks/evidencePack.js";
11
11
  import { errorMessage, sendJson, config } from "../shared.js";
12
12
  function augmentEvidencePackSummary(pack) {
13
13
  const jsonExists = existsSync(pack.files.json);
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { existsSync, readdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
- import { toSafeTaskLineage } from "../../tools/taskLineage.js";
10
+ import { toSafeTaskLineage } from "../../tools/tasks/taskLineage.js";
11
11
  import { config, errorMessage, readJsonFileSafeUnder, sendJson } from "../shared.js";
12
12
  function augmentLineageSummary(safe, record) {
13
13
  const directSessions = safe.tasks.direct_sessions;
@@ -1,5 +1,12 @@
1
1
  import { type ServerResponse } from "node:http";
2
+ type ControlMode = "core" | "direct";
3
+ export declare function resolveManageProfiles(mode: string, action: string, directEnabled: boolean): {
4
+ selected: ControlMode[];
5
+ skipped: ControlMode[];
6
+ };
2
7
  declare function findTunnelClientExecutable(): string | null;
8
+ export declare function buildManageEnvironment(): NodeJS.ProcessEnv;
3
9
  export { findTunnelClientExecutable };
10
+ export declare function classifySupervisorFailure(diagnosticText: string): string;
4
11
  export declare function handleManageAction(res: ServerResponse, action: string, mode: string): Promise<void>;
5
12
  export declare function handleOpenLogsFolder(res: ServerResponse): void;