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
@@ -10,7 +10,7 @@ import { existsSync, readdirSync, statSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { type ServerResponse } from "node:http";
12
12
  import { getDirectSessionsDir } from "../../config.js";
13
- import { safeAuditDirectSession, safeDirectSummary, safeFinalizeDirectSession } from "../../tools/safeViews.js";
13
+ import { safeAuditDirectSession, safeDirectSummary, safeFinalizeDirectSession } from "../../tools/diagnostics/safeViews.js";
14
14
  import {
15
15
  fileMtimeIso,
16
16
  isValidDirectSessionId,
@@ -18,7 +18,7 @@ import {
18
18
  recordEvent,
19
19
  writeHiddenDirectSessionIds,
20
20
  } from "../runtime.js";
21
- import { config, errorMessage, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
21
+ import { config, errorMessage, guardControlPath, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
22
22
 
23
23
  interface DirectSessionSummary {
24
24
  session_id: string;
@@ -91,19 +91,28 @@ function readDirectSessionSummary(sessionDir: string, sessionId: string): Direct
91
91
  };
92
92
  }
93
93
 
94
- export function handleDirectSessions(res: ServerResponse): void {
94
+ export interface DirectSessionFilters {
95
+ state?: "active" | "archive" | "finalized" | "audited" | "expired";
96
+ repo_path?: string;
97
+ date_from?: string;
98
+ date_to?: string;
99
+ limit?: number;
100
+ cursor?: string;
101
+ }
102
+
103
+ export function handleDirectSessions(res: ServerResponse, filters?: DirectSessionFilters): void {
95
104
  try {
96
105
  const sessionsDir = getDirectSessionsDir(config);
97
106
  if (!existsSync(sessionsDir)) {
98
107
  // Directory missing -> empty list, never 500.
99
- sendJson(res, 200, { sessions: [], total: 0, reason: null });
108
+ sendJson(res, 200, emptyDirectSessionResponse(filters));
100
109
  return;
101
110
  }
102
111
  let entries: import("node:fs").Dirent[] = [];
103
112
  try {
104
113
  entries = readdirSync(sessionsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
105
114
  } catch (err) {
106
- sendJson(res, 200, { sessions: [], total: 0, reason: errorMessage(err) });
115
+ sendJson(res, 200, { ...emptyDirectSessionResponse(filters), reason: errorMessage(err) });
107
116
  return;
108
117
  }
109
118
  // Filter out sessions hidden from the dashboard via POST .../hide.
@@ -111,32 +120,92 @@ export function handleDirectSessions(res: ServerResponse): void {
111
120
  const summaries: DirectSessionSummary[] = [];
112
121
  for (const entry of entries) {
113
122
  if (hiddenIds.has(entry.name)) continue;
114
- const summary = readDirectSessionSummary(join(sessionsDir, entry.name), entry.name);
123
+ const sessionDir = guardControlPath(join(sessionsDir, entry.name), config.directSessionsDir);
124
+ if (!sessionDir) continue;
125
+ const summary = readDirectSessionSummary(sessionDir, entry.name);
115
126
  if (summary) summaries.push(summary);
116
127
  }
117
- // Sort by created_at descending.
128
+ // Sort by created_at descending, then apply archive filters and pagination.
118
129
  summaries.sort((a, b) => b.created_at.localeCompare(a.created_at));
119
- sendJson(res, 200, { sessions: summaries, total: summaries.length, reason: null });
130
+ const now = Date.now();
131
+ let filtered = summaries;
132
+ if (filters?.state) {
133
+ filtered = filtered.filter((session) => {
134
+ const expired = Boolean(session.expires_at) && Date.parse(session.expires_at) < now;
135
+ if (filters.state === "archive") return session.finalized || session.audited || expired;
136
+ if (filters.state === "expired") return expired;
137
+ if (filters.state === "audited") return session.audited && !expired;
138
+ if (filters.state === "finalized") return session.finalized && !session.audited && !expired;
139
+ return !session.finalized && !expired;
140
+ });
141
+ }
142
+ if (filters?.repo_path) filtered = filtered.filter((session) => session.repo_path === filters.repo_path);
143
+ if (filters?.date_from || filters?.date_to) {
144
+ const from = filters.date_from ? Date.parse(`${filters.date_from}T00:00:00`) : Number.NEGATIVE_INFINITY;
145
+ const to = filters.date_to ? Date.parse(`${filters.date_to}T23:59:59`) : Number.POSITIVE_INFINITY;
146
+ filtered = filtered.filter((session) => {
147
+ const timestamp = Date.parse(session.created_at);
148
+ return Number.isFinite(timestamp) && timestamp >= from && timestamp <= to;
149
+ });
150
+ }
151
+ const limit = Math.max(10, Math.min(filters?.limit || 30, 100));
152
+ const offset = filters?.cursor && /^\d+$/.test(filters.cursor) ? Number(filters.cursor) : 0;
153
+ const sessions = filtered.slice(offset, offset + limit);
154
+ const nextCursor = offset + sessions.length < filtered.length ? String(offset + sessions.length) : null;
155
+ const repos = [...new Set(summaries.map((session) => session.repo_path).filter(Boolean))].sort();
156
+ sendJson(res, 200, {
157
+ sessions,
158
+ total: filtered.length,
159
+ returned: sessions.length,
160
+ nextCursor,
161
+ next_cursor: nextCursor,
162
+ filters: {
163
+ applied: {
164
+ state: filters?.state || null,
165
+ repo_path: filters?.repo_path || null,
166
+ date_from: filters?.date_from || null,
167
+ date_to: filters?.date_to || null,
168
+ },
169
+ options: { repos, states: ["active", "archive", "finalized", "audited", "expired"] },
170
+ },
171
+ direct_profile_enabled: config.enableDirectProfile === true,
172
+ reason: null,
173
+ });
120
174
  } catch (err) {
121
- sendJson(res, 200, { sessions: [], total: 0, reason: errorMessage(err) });
175
+ sendJson(res, 200, { ...emptyDirectSessionResponse(filters), reason: errorMessage(err) });
122
176
  }
123
177
  }
124
178
 
179
+ function emptyDirectSessionResponse(filters?: DirectSessionFilters): Record<string, unknown> {
180
+ return {
181
+ sessions: [],
182
+ total: 0,
183
+ returned: 0,
184
+ nextCursor: null,
185
+ next_cursor: null,
186
+ filters: {
187
+ applied: {
188
+ state: filters?.state || null,
189
+ repo_path: filters?.repo_path || null,
190
+ date_from: filters?.date_from || null,
191
+ date_to: filters?.date_to || null,
192
+ },
193
+ options: { repos: [], states: ["active", "archive", "finalized", "audited", "expired"] },
194
+ },
195
+ direct_profile_enabled: config.enableDirectProfile === true,
196
+ reason: null,
197
+ };
198
+ }
199
+
125
200
  export function handleDirectSessionDetail(res: ServerResponse, sessionId: string): void {
126
201
  try {
127
- if (
128
- sessionId === "." ||
129
- sessionId === ".." ||
130
- sessionId.includes("/") ||
131
- sessionId.includes("\\") ||
132
- sessionId.includes("\0")
133
- ) {
202
+ if (!isValidDirectSessionId(sessionId)) {
134
203
  sendJson(res, 400, { error: "Invalid session id" });
135
204
  return;
136
205
  }
137
206
  const sessionsDir = getDirectSessionsDir(config);
138
- const sessionDir = join(sessionsDir, sessionId);
139
- if (!existsSync(sessionDir) || !statSync(sessionDir).isDirectory()) {
207
+ const sessionDir = guardControlPath(join(sessionsDir, sessionId), config.directSessionsDir);
208
+ if (!sessionDir || !existsSync(sessionDir) || !statSync(sessionDir).isDirectory()) {
140
209
  sendJson(res, 404, { error: "Direct session not found" });
141
210
  return;
142
211
  }
@@ -158,13 +227,7 @@ export function handleDirectSessionDetail(res: ServerResponse, sessionId: string
158
227
 
159
228
  export function handleDirectSessionSafeSummary(res: ServerResponse, sessionId: string): void {
160
229
  try {
161
- if (
162
- sessionId === "." ||
163
- sessionId === ".." ||
164
- sessionId.includes("/") ||
165
- sessionId.includes("\\") ||
166
- sessionId.includes("\0")
167
- ) {
230
+ if (!isValidDirectSessionId(sessionId)) {
168
231
  sendJson(res, 400, { error: "Invalid session id" });
169
232
  return;
170
233
  }
@@ -9,8 +9,8 @@
9
9
  */
10
10
  import { existsSync } from "node:fs";
11
11
  import { type ServerResponse } from "node:http";
12
- import { type AgentAvailability } from "../../tools/listAgents.js";
13
- import { type TaskEntry } from "../../tools/listTasks.js";
12
+ import { type AgentAvailability } from "../../tools/workspace/listAgents.js";
13
+ import { type TaskEntry } from "../../tools/tasks/listTasks.js";
14
14
  import { type WatcherStatusSnapshot } from "../../watcherStatus.js";
15
15
  import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
16
16
  import { redactSensitiveValue } from "../../security/contentRedaction.js";
@@ -33,9 +33,9 @@ import {
33
33
  import { findTunnelClientExecutable } from "./process.js";
34
34
  import {
35
35
  config,
36
+ configIdentitySha256,
36
37
  controlCenterStatusPath,
37
38
  CORE_BASE_URL,
38
- DEFAULT_TUNNEL_CLIENT_EXE,
39
39
  DIRECT_BASE_URL,
40
40
  errorMessage,
41
41
  readJsonFileSafe,
@@ -59,9 +59,78 @@ interface StatusSnapshotForSuggestions {
59
59
  tunnel: { core: Record<string, unknown>; direct: Record<string, unknown> };
60
60
  agents: AgentAvailability[];
61
61
  tasks: StatusTasks;
62
+ direct_profile_enabled: boolean;
62
63
  }
63
64
 
64
- function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
65
+ export interface ExperienceStatus {
66
+ readiness: "ready" | "needs_action" | "blocked";
67
+ live_checks: Array<{
68
+ key: string;
69
+ label: string;
70
+ state: "ready" | "needs_action" | "blocked" | "optional";
71
+ detail: string;
72
+ }>;
73
+ historical_summary: {
74
+ stale_tasks: number;
75
+ failed_tasks: number;
76
+ note: string;
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Keep current readiness separate from historical evidence. A month-old failed
82
+ * task is useful for audit, but must not make a configured local bridge look
83
+ * unavailable on the first screen.
84
+ */
85
+ export function buildExperienceStatus(snapshot: StatusSnapshotForSuggestions): ExperienceStatus {
86
+ const coreReady = snapshot.core.available && snapshot.tunnel.core.ready === true;
87
+ const watcherReady = snapshot.watcher.status === "healthy";
88
+ const agentsReady = snapshot.agents.some((agent) => agent.available);
89
+ const directEnabled = snapshot.direct_profile_enabled;
90
+ const directReady = snapshot.direct.available && snapshot.tunnel.direct.ready === true;
91
+ const staleTasks = snapshot.tasks.stale;
92
+ const failedTasks = snapshot.tasks.tasks.filter((task) => String((task as TaskEntry).status || "").includes("failed")).length;
93
+
94
+ const live_checks: ExperienceStatus["live_checks"] = [
95
+ { key: "workspace_agents", label: "工作区与 Agent", state: agentsReady ? "ready" : "blocked", detail: agentsReady ? "工作区内至少有一个可用 Agent" : "需要配置工作区或可用 Agent" },
96
+ { key: "core", label: "Core 服务", state: coreReady ? "ready" : "needs_action", detail: coreReady ? "Core 与 Tunnel 已就绪" : "启动 Core 后会继续检查 Tunnel 与 Watcher" },
97
+ { key: "watcher", label: "任务监视器", state: watcherReady ? "ready" : "needs_action", detail: watcherReady ? "Watcher 心跳正常" : "Core 启动后 Watcher 将处理排队任务" },
98
+ { key: "direct", label: "Direct 编辑", state: directEnabled ? (directReady ? "ready" : "needs_action") : "optional", detail: directEnabled ? (directReady ? "Direct 已就绪" : "已启用但尚未就绪") : "可选高级能力,未启用不会影响 Core" },
99
+ ];
100
+
101
+ const readiness: ExperienceStatus["readiness"] = !agentsReady
102
+ ? "blocked"
103
+ : coreReady && watcherReady && (!directEnabled || directReady)
104
+ ? "ready"
105
+ : "needs_action";
106
+
107
+ return {
108
+ readiness,
109
+ live_checks,
110
+ historical_summary: {
111
+ stale_tasks: staleTasks,
112
+ failed_tasks: failedTasks,
113
+ note: "历史任务与审计记录不会影响当前实时就绪状态。",
114
+ },
115
+ };
116
+ }
117
+
118
+ export function reconcileTunnelStatus(
119
+ status: Record<string, unknown>,
120
+ health: RuntimeHealth,
121
+ ): Record<string, unknown> {
122
+ if (!health.available) return status;
123
+ return {
124
+ ...status,
125
+ observed: true,
126
+ status: "running",
127
+ ready: true,
128
+ reason_code: "health_endpoint_ready",
129
+ health_observed: true,
130
+ };
131
+ }
132
+
133
+ export function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
65
134
  const out: Suggestion[] = [];
66
135
 
67
136
  if (!s.core.available) {
@@ -72,7 +141,7 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
72
141
  action: "/api/core/start",
73
142
  });
74
143
  }
75
- if (!s.direct.available) {
144
+ if (s.direct_profile_enabled && !s.direct.available) {
76
145
  out.push({
77
146
  code: "direct_stopped",
78
147
  severity: "warning",
@@ -81,12 +150,12 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
81
150
  });
82
151
  }
83
152
 
84
- if (s.watcher.status === "stale" || s.watcher.status === "unreadable") {
153
+ if (s.watcher.status === "degraded" || s.watcher.status === "stale" || s.watcher.status === "unreadable") {
85
154
  out.push({
86
155
  code: "watcher_stale",
87
156
  severity: "error",
88
- message: "Watcher 处于 " + s.watcher.status + " 状态,建议 Restart All",
89
- action: "/api/restart-all",
157
+ message: "Watcher 处于 " + s.watcher.status + " 状态,建议重启 Core",
158
+ action: "/api/core/restart",
90
159
  });
91
160
  }
92
161
 
@@ -101,12 +170,12 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
101
170
 
102
171
  const coreTunnelReady = !!(s.tunnel.core && s.tunnel.core.ready);
103
172
  const directTunnelReady = !!(s.tunnel.direct && s.tunnel.direct.ready);
104
- if (!coreTunnelReady || !directTunnelReady) {
173
+ if (!coreTunnelReady || (s.direct_profile_enabled && !directTunnelReady)) {
105
174
  out.push({
106
175
  code: "tunnel_not_ready",
107
176
  severity: "warning",
108
- message: "Tunnel 未就绪,建议重启 profile 或检查代理",
109
- action: "/api/restart-all",
177
+ message: "Tunnel 未就绪,建议启动 profile 或检查代理",
178
+ action: "/api/start-all",
110
179
  });
111
180
  }
112
181
 
@@ -115,7 +184,7 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
115
184
  out.push({
116
185
  code: "agent_missing",
117
186
  severity: "info",
118
- message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查 opencode/claude 路径)",
187
+ message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查对应 CLI 安装与路径)",
119
188
  });
120
189
  }
121
190
 
@@ -169,7 +238,7 @@ function diffAndRecordEvents(prev: StatusSnapshotDigest, curr: StatusSnapshotDig
169
238
 
170
239
  export async function handleStatus(res: ServerResponse): Promise<void> {
171
240
  try {
172
- const [coreHealth, directHealth, watcher, tunnelCore, tunnelDirect, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
241
+ const [coreHealth, directHealth, watcher, tunnelCoreRaw, tunnelDirectRaw, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
173
242
  probeRuntimeHealth(CORE_BASE_URL).catch((err): RuntimeHealth => ({
174
243
  available: false,
175
244
  reason: errorMessage(err),
@@ -191,6 +260,8 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
191
260
  Promise.resolve(resolveWorkspaceRootSafe()),
192
261
  Promise.resolve(listTasksForStatus()),
193
262
  ]);
263
+ const tunnelCore = reconcileTunnelStatus(tunnelCoreRaw, coreHealth);
264
+ const tunnelDirect = reconcileTunnelStatus(tunnelDirectRaw, directHealth);
194
265
  const snapshotForSuggestions: StatusSnapshotForSuggestions = {
195
266
  core: coreHealth,
196
267
  direct: directHealth,
@@ -198,8 +269,10 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
198
269
  tunnel: { core: tunnelCore, direct: tunnelDirect },
199
270
  agents,
200
271
  tasks,
272
+ direct_profile_enabled: config.enableDirectProfile === true,
201
273
  };
202
274
  const suggestions = buildSuggestions(snapshotForSuggestions);
275
+ const experience = buildExperienceStatus(snapshotForSuggestions);
203
276
  const tunnelClientExe = findTunnelClientExecutable();
204
277
 
205
278
  // Diff against the previous poll to record observed state-change events.
@@ -221,11 +294,13 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
221
294
  workspace_root: workspaceRoot,
222
295
  tasks,
223
296
  suggestions,
297
+ experience,
298
+ direct_profile_enabled: config.enableDirectProfile === true,
224
299
  setup: {
225
300
  tunnel_client: {
226
301
  available: tunnelClientExe !== null,
227
302
  path: tunnelClientExe,
228
- default_path: DEFAULT_TUNNEL_CLIENT_EXE,
303
+ source: config.tunnelClientPath && tunnelClientExe === config.tunnelClientPath ? "config" : "detected",
229
304
  },
230
305
  workspace_root: workspaceRoot,
231
306
  watcher: {
@@ -270,11 +345,15 @@ export function handleTunnelUiUrl(res: ServerResponse): void {
270
345
  });
271
346
  }
272
347
 
273
- export function handleDiagnostics(res: ServerResponse): void {
348
+ export async function handleDiagnostics(res: ServerResponse): Promise<void> {
274
349
  try {
275
350
  const watcher = readWatcherStatusSafe();
276
- const tunnelCore = readTunnelStatus(false);
277
- const tunnelDirect = readTunnelStatus(true);
351
+ const [coreHealth, directHealth] = await Promise.all([
352
+ probeRuntimeHealth(CORE_BASE_URL),
353
+ probeRuntimeHealth(DIRECT_BASE_URL),
354
+ ]);
355
+ const tunnelCore = reconcileTunnelStatus(readTunnelStatus(false), coreHealth);
356
+ const tunnelDirect = reconcileTunnelStatus(readTunnelStatus(true), directHealth);
278
357
  const toolsCore = readToolManifest(false);
279
358
  const toolsDirect = readToolManifest(true);
280
359
  const agents = listAgentsSafe();
@@ -314,6 +393,7 @@ export function handleDiagnostics(res: ServerResponse): void {
314
393
  workspace_root: workspaceRoot,
315
394
  recent_failures: recentFailures,
316
395
  direct_profile_enabled: config.enableDirectProfile ?? false,
396
+ config_identity_sha256: configIdentitySha256,
317
397
  };
318
398
 
319
399
  // Redact any sensitive content that may have leaked into string fields.
@@ -7,24 +7,25 @@
7
7
  * dashboard. None of them delete task files; they annotate or launch external
8
8
  * viewers.
9
9
  */
10
- import { existsSync, statSync, writeFileSync } from "node:fs";
10
+ import { existsSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
- import { spawn } from "node:child_process";
13
12
  import { type ServerResponse } from "node:http";
14
- import { type TaskEntry } from "../../tools/listTasks.js";
15
- import type { AcceptanceStatus } from "../../tools/createTask.js";
16
- import { auditTask } from "../../tools/auditTask.js";
13
+ import { auditTask } from "../../tools/diagnostics/auditTask.js";
17
14
  import { getTasksDir } from "../../config.js";
18
15
  import {
19
16
  classifyStaleTask,
20
17
  isValidTaskId,
21
18
  readHiddenStaleIds,
19
+ reconstructTaskEntry,
22
20
  readWatcherStatusSafe,
23
21
  recordEvent,
24
22
  TERMINAL_TASK_STATUSES,
25
23
  writeHiddenStaleIds,
26
24
  } from "../runtime.js";
27
- import { config, errorMessage, readJsonFileSafe, sendJson } from "../shared.js";
25
+ import { launchFileManager } from "../fileManager.js";
26
+ import { config, errorMessage, guardControlPath, readJsonFileSafe, sendJson } from "../shared.js";
27
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
28
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
28
29
 
29
30
  /**
30
31
  * Reconcile a stale task. Does NOT delete the task. Reads the task files,
@@ -39,8 +40,8 @@ export function handleReconcile(res: ServerResponse, taskId: string): void {
39
40
  return;
40
41
  }
41
42
  const tasksDir = getTasksDir(config);
42
- const taskDir = join(tasksDir, taskId);
43
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
43
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
44
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
44
45
  sendJson(res, 404, { error: "Task not found" });
45
46
  return;
46
47
  }
@@ -51,33 +52,7 @@ export function handleReconcile(res: ServerResponse, taskId: string): void {
51
52
  const runtimeData = readJsonFileSafe<Record<string, unknown>>(runtimePath) ?? {};
52
53
 
53
54
  const watcher = readWatcherStatusSafe();
54
- const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
55
- const taskStatus = String(statusData.status || "pending");
56
- const taskAcceptanceStatus = taskStatus === "done_by_agent"
57
- ? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE.includes(statusData.acceptance_status) ? (statusData.acceptance_status as AcceptanceStatus) : "pending" as AcceptanceStatus)
58
- : null;
59
- const taskEntry: TaskEntry = {
60
- task_id: taskId,
61
- plan_id: String(statusData.plan_id || ""),
62
- title: "",
63
- agent: String(statusData.agent || ""),
64
- status: taskStatus as TaskEntry["status"],
65
- phase: String(runtimeData.phase || statusData.phase || "queued") as TaskEntry["phase"],
66
- acceptance_status: taskAcceptanceStatus,
67
- created_at: String(statusData.created_at || ""),
68
- updated_at: String(statusData.updated_at || ""),
69
- workspace_root: String(statusData.workspace_root || config.workspaceRoot),
70
- repo_path: String(statusData.repo_path || "."),
71
- resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
72
- test_command: String(statusData.test_command || ""),
73
- verify_commands: Array.isArray(statusData.verify_commands) ? (statusData.verify_commands as string[]) : [],
74
- error: statusData.error ? String(statusData.error) : null,
75
- last_heartbeat_at: String(runtimeData.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
76
- current_command: runtimeData.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
77
- timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
78
- pending_reason: null,
79
- watcher_status: watcher.status,
80
- };
55
+ const taskEntry = reconstructTaskEntry(taskId, statusData, runtimeData, watcher);
81
56
 
82
57
  const cls = classifyStaleTask(taskEntry, watcher);
83
58
  const isTerminal = TERMINAL_TASK_STATUSES.has(taskEntry.status);
@@ -123,7 +98,7 @@ export function handleReconcile(res: ServerResponse, taskId: string): void {
123
98
 
124
99
  // Write the reconcile record artifact.
125
100
  try {
126
- writeFileSync(join(taskDir, "reconcile.json"), JSON.stringify(reconcileRecord, null, 2), "utf-8");
101
+ atomicWriteJsonFileSync(join(taskDir, "reconcile.json"), reconcileRecord);
127
102
  } catch (writeErr) {
128
103
  sendJson(res, 500, { error: `Failed to write reconcile record: ${errorMessage(writeErr)}` });
129
104
  return;
@@ -131,13 +106,18 @@ export function handleReconcile(res: ServerResponse, taskId: string): void {
131
106
 
132
107
  // Annotate status.json with reconcile metadata (do not mutate status enum).
133
108
  if (safe) {
134
- const annotated = {
135
- ...statusData,
109
+ const annotation = {
136
110
  reconcile_state: decision === "marked_archived" ? "archived" : "stale",
137
111
  reconciled_at: reconciledAt,
138
112
  };
139
113
  try {
140
- writeFileSync(statusPath, JSON.stringify(annotated, null, 2), "utf-8");
114
+ mutateTaskStatus(statusPath, (current) => {
115
+ if (current.status !== taskEntry.status) {
116
+ throw new Error("Task status changed while reconcile was being evaluated; retry.");
117
+ }
118
+ const next = { ...current, ...annotation, updated_at: new Date().toISOString() };
119
+ return { next, result: next };
120
+ });
141
121
  } catch (writeErr) {
142
122
  sendJson(res, 500, { error: `Failed to annotate status.json: ${errorMessage(writeErr)}` });
143
123
  return;
@@ -169,8 +149,8 @@ export function handleTaskAudit(res: ServerResponse, taskId: string): void {
169
149
  return;
170
150
  }
171
151
  const tasksDir = getTasksDir(config);
172
- const taskDir = join(tasksDir, taskId);
173
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
152
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
153
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
174
154
  sendJson(res, 404, { error: "Task not found" });
175
155
  return;
176
156
  }
@@ -198,26 +178,12 @@ export function handleOpenTaskFolder(res: ServerResponse, taskId: string): void
198
178
  return;
199
179
  }
200
180
  const tasksDir = getTasksDir(config);
201
- const taskDir = join(tasksDir, taskId);
202
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
181
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
182
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
203
183
  sendJson(res, 404, { error: "Task not found" });
204
184
  return;
205
185
  }
206
- let cmd: string;
207
- if (process.platform === "win32") {
208
- cmd = "explorer.exe";
209
- } else if (process.platform === "darwin") {
210
- cmd = "open";
211
- } else {
212
- cmd = "xdg-open";
213
- }
214
- try {
215
- const child = spawn(cmd, [taskDir], { detached: true, stdio: "ignore" });
216
- child.on("error", () => { /* ignore spawn errors */ });
217
- child.unref();
218
- } catch {
219
- /* ignore */
220
- }
186
+ launchFileManager(taskDir, tasksDir);
221
187
  sendJson(res, 200, { ok: true, path: taskDir });
222
188
  } catch (err) {
223
189
  sendJson(res, 500, { error: errorMessage(err) });