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
@@ -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,
@@ -52,6 +52,39 @@ interface Suggestion {
52
52
  link?: string;
53
53
  }
54
54
 
55
+ export interface ConnectionSummary {
56
+ profile: string;
57
+ tool_profile: string | null;
58
+ ready: boolean;
59
+ tool_count: number | null;
60
+ tool_manifest_sha256: string | null;
61
+ tunnel_id_masked: string | null;
62
+ reconnect_guidance: string;
63
+ }
64
+
65
+ export function buildConnectionSummary(
66
+ mode: "core" | "direct",
67
+ tunnel: Record<string, unknown>,
68
+ tools: { tool_profile: string | null; tool_count: number | null; tool_manifest_sha256: string | null },
69
+ ): ConnectionSummary {
70
+ const masked = typeof tunnel.tunnel_id_masked === "string" && tunnel.tunnel_id_masked.includes("***")
71
+ ? tunnel.tunnel_id_masked
72
+ : null;
73
+ return {
74
+ profile: typeof tunnel.profile === "string"
75
+ ? tunnel.profile
76
+ : mode === "direct" ? "patchwarden-direct" : "patchwarden",
77
+ tool_profile: tools.tool_profile,
78
+ ready: tunnel.ready === true,
79
+ tool_count: tools.tool_count,
80
+ tool_manifest_sha256: tools.tool_manifest_sha256,
81
+ tunnel_id_masked: masked,
82
+ reconnect_guidance: mode === "direct"
83
+ ? "If ChatGPT still uses an older Direct Tunnel, update the connector binding and test health_check in a new chat."
84
+ : "If ChatGPT still uses an older Core Tunnel, update the connector binding and test health_check in a new chat.",
85
+ };
86
+ }
87
+
55
88
  interface StatusSnapshotForSuggestions {
56
89
  core: RuntimeHealth;
57
90
  direct: RuntimeHealth;
@@ -59,9 +92,78 @@ interface StatusSnapshotForSuggestions {
59
92
  tunnel: { core: Record<string, unknown>; direct: Record<string, unknown> };
60
93
  agents: AgentAvailability[];
61
94
  tasks: StatusTasks;
95
+ direct_profile_enabled: boolean;
96
+ }
97
+
98
+ export interface ExperienceStatus {
99
+ readiness: "ready" | "needs_action" | "blocked";
100
+ live_checks: Array<{
101
+ key: string;
102
+ label: string;
103
+ state: "ready" | "needs_action" | "blocked" | "optional";
104
+ detail: string;
105
+ }>;
106
+ historical_summary: {
107
+ stale_tasks: number;
108
+ failed_tasks: number;
109
+ note: string;
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Keep current readiness separate from historical evidence. A month-old failed
115
+ * task is useful for audit, but must not make a configured local bridge look
116
+ * unavailable on the first screen.
117
+ */
118
+ export function buildExperienceStatus(snapshot: StatusSnapshotForSuggestions): ExperienceStatus {
119
+ const coreReady = snapshot.core.available && snapshot.tunnel.core.ready === true;
120
+ const watcherReady = snapshot.watcher.status === "healthy";
121
+ const agentsReady = snapshot.agents.some((agent) => agent.available);
122
+ const directEnabled = snapshot.direct_profile_enabled;
123
+ const directReady = snapshot.direct.available && snapshot.tunnel.direct.ready === true;
124
+ const staleTasks = snapshot.tasks.stale;
125
+ const failedTasks = snapshot.tasks.tasks.filter((task) => String((task as TaskEntry).status || "").includes("failed")).length;
126
+
127
+ const live_checks: ExperienceStatus["live_checks"] = [
128
+ { key: "workspace_agents", label: "工作区与 Agent", state: agentsReady ? "ready" : "blocked", detail: agentsReady ? "工作区内至少有一个可用 Agent" : "需要配置工作区或可用 Agent" },
129
+ { key: "core", label: "Core 服务", state: coreReady ? "ready" : "needs_action", detail: coreReady ? "Core 与 Tunnel 已就绪" : "启动 Core 后会继续检查 Tunnel 与 Watcher" },
130
+ { key: "watcher", label: "任务监视器", state: watcherReady ? "ready" : "needs_action", detail: watcherReady ? "Watcher 心跳正常" : "Core 启动后 Watcher 将处理排队任务" },
131
+ { key: "direct", label: "Direct 编辑", state: directEnabled ? (directReady ? "ready" : "needs_action") : "optional", detail: directEnabled ? (directReady ? "Direct 已就绪" : "已启用但尚未就绪") : "可选高级能力,未启用不会影响 Core" },
132
+ ];
133
+
134
+ const readiness: ExperienceStatus["readiness"] = !agentsReady
135
+ ? "blocked"
136
+ : coreReady && watcherReady && (!directEnabled || directReady)
137
+ ? "ready"
138
+ : "needs_action";
139
+
140
+ return {
141
+ readiness,
142
+ live_checks,
143
+ historical_summary: {
144
+ stale_tasks: staleTasks,
145
+ failed_tasks: failedTasks,
146
+ note: "历史任务与审计记录不会影响当前实时就绪状态。",
147
+ },
148
+ };
62
149
  }
63
150
 
64
- function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
151
+ export function reconcileTunnelStatus(
152
+ status: Record<string, unknown>,
153
+ health: RuntimeHealth,
154
+ ): Record<string, unknown> {
155
+ if (!health.available) return status;
156
+ return {
157
+ ...status,
158
+ observed: true,
159
+ status: "running",
160
+ ready: true,
161
+ reason_code: "health_endpoint_ready",
162
+ health_observed: true,
163
+ };
164
+ }
165
+
166
+ export function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
65
167
  const out: Suggestion[] = [];
66
168
 
67
169
  if (!s.core.available) {
@@ -72,7 +174,7 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
72
174
  action: "/api/core/start",
73
175
  });
74
176
  }
75
- if (!s.direct.available) {
177
+ if (s.direct_profile_enabled && !s.direct.available) {
76
178
  out.push({
77
179
  code: "direct_stopped",
78
180
  severity: "warning",
@@ -81,12 +183,12 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
81
183
  });
82
184
  }
83
185
 
84
- if (s.watcher.status === "stale" || s.watcher.status === "unreadable") {
186
+ if (s.watcher.status === "degraded" || s.watcher.status === "stale" || s.watcher.status === "unreadable") {
85
187
  out.push({
86
188
  code: "watcher_stale",
87
189
  severity: "error",
88
- message: "Watcher 处于 " + s.watcher.status + " 状态,建议 Restart All",
89
- action: "/api/restart-all",
190
+ message: "Watcher 处于 " + s.watcher.status + " 状态,建议重启 Core",
191
+ action: "/api/core/restart",
90
192
  });
91
193
  }
92
194
 
@@ -101,12 +203,12 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
101
203
 
102
204
  const coreTunnelReady = !!(s.tunnel.core && s.tunnel.core.ready);
103
205
  const directTunnelReady = !!(s.tunnel.direct && s.tunnel.direct.ready);
104
- if (!coreTunnelReady || !directTunnelReady) {
206
+ if (!coreTunnelReady || (s.direct_profile_enabled && !directTunnelReady)) {
105
207
  out.push({
106
208
  code: "tunnel_not_ready",
107
209
  severity: "warning",
108
- message: "Tunnel 未就绪,建议重启 profile 或检查代理",
109
- action: "/api/restart-all",
210
+ message: "Tunnel 未就绪,建议启动 profile 或检查代理",
211
+ action: "/api/start-all",
110
212
  });
111
213
  }
112
214
 
@@ -115,7 +217,7 @@ function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[] {
115
217
  out.push({
116
218
  code: "agent_missing",
117
219
  severity: "info",
118
- message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查 opencode/claude 路径)",
220
+ message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查对应 CLI 安装与路径)",
119
221
  });
120
222
  }
121
223
 
@@ -169,7 +271,7 @@ function diffAndRecordEvents(prev: StatusSnapshotDigest, curr: StatusSnapshotDig
169
271
 
170
272
  export async function handleStatus(res: ServerResponse): Promise<void> {
171
273
  try {
172
- const [coreHealth, directHealth, watcher, tunnelCore, tunnelDirect, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
274
+ const [coreHealth, directHealth, watcher, tunnelCoreRaw, tunnelDirectRaw, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
173
275
  probeRuntimeHealth(CORE_BASE_URL).catch((err): RuntimeHealth => ({
174
276
  available: false,
175
277
  reason: errorMessage(err),
@@ -191,6 +293,8 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
191
293
  Promise.resolve(resolveWorkspaceRootSafe()),
192
294
  Promise.resolve(listTasksForStatus()),
193
295
  ]);
296
+ const tunnelCore = reconcileTunnelStatus(tunnelCoreRaw, coreHealth);
297
+ const tunnelDirect = reconcileTunnelStatus(tunnelDirectRaw, directHealth);
194
298
  const snapshotForSuggestions: StatusSnapshotForSuggestions = {
195
299
  core: coreHealth,
196
300
  direct: directHealth,
@@ -198,8 +302,10 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
198
302
  tunnel: { core: tunnelCore, direct: tunnelDirect },
199
303
  agents,
200
304
  tasks,
305
+ direct_profile_enabled: config.enableDirectProfile === true,
201
306
  };
202
307
  const suggestions = buildSuggestions(snapshotForSuggestions);
308
+ const experience = buildExperienceStatus(snapshotForSuggestions);
203
309
  const tunnelClientExe = findTunnelClientExecutable();
204
310
 
205
311
  // Diff against the previous poll to record observed state-change events.
@@ -217,15 +323,21 @@ export async function handleStatus(res: ServerResponse): Promise<void> {
217
323
  watcher,
218
324
  tunnel: { core: tunnelCore, direct: tunnelDirect },
219
325
  tools: { core: toolsCore, direct: toolsDirect },
326
+ connections: {
327
+ core: buildConnectionSummary("core", tunnelCore, toolsCore),
328
+ direct: buildConnectionSummary("direct", tunnelDirect, toolsDirect),
329
+ },
220
330
  agents,
221
331
  workspace_root: workspaceRoot,
222
332
  tasks,
223
333
  suggestions,
334
+ experience,
335
+ direct_profile_enabled: config.enableDirectProfile === true,
224
336
  setup: {
225
337
  tunnel_client: {
226
338
  available: tunnelClientExe !== null,
227
339
  path: tunnelClientExe,
228
- default_path: DEFAULT_TUNNEL_CLIENT_EXE,
340
+ source: config.tunnelClientPath && tunnelClientExe === config.tunnelClientPath ? "config" : "detected",
229
341
  },
230
342
  workspace_root: workspaceRoot,
231
343
  watcher: {
@@ -270,11 +382,15 @@ export function handleTunnelUiUrl(res: ServerResponse): void {
270
382
  });
271
383
  }
272
384
 
273
- export function handleDiagnostics(res: ServerResponse): void {
385
+ export async function handleDiagnostics(res: ServerResponse): Promise<void> {
274
386
  try {
275
387
  const watcher = readWatcherStatusSafe();
276
- const tunnelCore = readTunnelStatus(false);
277
- const tunnelDirect = readTunnelStatus(true);
388
+ const [coreHealth, directHealth] = await Promise.all([
389
+ probeRuntimeHealth(CORE_BASE_URL),
390
+ probeRuntimeHealth(DIRECT_BASE_URL),
391
+ ]);
392
+ const tunnelCore = reconcileTunnelStatus(readTunnelStatus(false), coreHealth);
393
+ const tunnelDirect = reconcileTunnelStatus(readTunnelStatus(true), directHealth);
278
394
  const toolsCore = readToolManifest(false);
279
395
  const toolsDirect = readToolManifest(true);
280
396
  const agents = listAgentsSafe();
@@ -314,6 +430,7 @@ export function handleDiagnostics(res: ServerResponse): void {
314
430
  workspace_root: workspaceRoot,
315
431
  recent_failures: recentFailures,
316
432
  direct_profile_enabled: config.enableDirectProfile ?? false,
433
+ config_identity_sha256: configIdentitySha256,
317
434
  };
318
435
 
319
436
  // 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) });