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
@@ -1,6 +1,65 @@
1
1
  import { type ServerResponse } from "node:http";
2
+ import { type AgentAvailability } from "../../tools/workspace/listAgents.js";
3
+ import { type WatcherStatusSnapshot } from "../../watcherStatus.js";
4
+ import { type RuntimeHealth, type StatusTasks } from "../runtime.js";
5
+ interface Suggestion {
6
+ code: string;
7
+ severity: "info" | "warning" | "error";
8
+ message: string;
9
+ action?: string;
10
+ link?: string;
11
+ }
12
+ export interface ConnectionSummary {
13
+ profile: string;
14
+ tool_profile: string | null;
15
+ ready: boolean;
16
+ tool_count: number | null;
17
+ tool_manifest_sha256: string | null;
18
+ tunnel_id_masked: string | null;
19
+ reconnect_guidance: string;
20
+ }
21
+ export declare function buildConnectionSummary(mode: "core" | "direct", tunnel: Record<string, unknown>, tools: {
22
+ tool_profile: string | null;
23
+ tool_count: number | null;
24
+ tool_manifest_sha256: string | null;
25
+ }): ConnectionSummary;
26
+ interface StatusSnapshotForSuggestions {
27
+ core: RuntimeHealth;
28
+ direct: RuntimeHealth;
29
+ watcher: WatcherStatusSnapshot;
30
+ tunnel: {
31
+ core: Record<string, unknown>;
32
+ direct: Record<string, unknown>;
33
+ };
34
+ agents: AgentAvailability[];
35
+ tasks: StatusTasks;
36
+ direct_profile_enabled: boolean;
37
+ }
38
+ export interface ExperienceStatus {
39
+ readiness: "ready" | "needs_action" | "blocked";
40
+ live_checks: Array<{
41
+ key: string;
42
+ label: string;
43
+ state: "ready" | "needs_action" | "blocked" | "optional";
44
+ detail: string;
45
+ }>;
46
+ historical_summary: {
47
+ stale_tasks: number;
48
+ failed_tasks: number;
49
+ note: string;
50
+ };
51
+ }
52
+ /**
53
+ * Keep current readiness separate from historical evidence. A month-old failed
54
+ * task is useful for audit, but must not make a configured local bridge look
55
+ * unavailable on the first screen.
56
+ */
57
+ export declare function buildExperienceStatus(snapshot: StatusSnapshotForSuggestions): ExperienceStatus;
58
+ export declare function reconcileTunnelStatus(status: Record<string, unknown>, health: RuntimeHealth): Record<string, unknown>;
59
+ export declare function buildSuggestions(s: StatusSnapshotForSuggestions): Suggestion[];
2
60
  export declare function handleStatus(res: ServerResponse): Promise<void>;
3
61
  export declare function handleControlCenterStatus(res: ServerResponse): void;
4
62
  export declare function handleEvents(res: ServerResponse, limit: number): void;
5
63
  export declare function handleTunnelUiUrl(res: ServerResponse): void;
6
- export declare function handleDiagnostics(res: ServerResponse): void;
64
+ export declare function handleDiagnostics(res: ServerResponse): Promise<void>;
65
+ export {};
@@ -12,8 +12,72 @@ import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../../version.js";
12
12
  import { redactSensitiveValue } from "../../security/contentRedaction.js";
13
13
  import { listAgentsSafe, listTasksForStatus, probeRuntimeHealth, readEvents, recordEvent, readToolManifest, readTunnelStatus, readTunnelUrl, readWatcherStatusSafe, resolveWorkspaceRootSafe, } from "../runtime.js";
14
14
  import { findTunnelClientExecutable } from "./process.js";
15
- import { config, controlCenterStatusPath, CORE_BASE_URL, DEFAULT_TUNNEL_CLIENT_EXE, DIRECT_BASE_URL, errorMessage, readJsonFileSafe, sendJson, } from "../shared.js";
16
- function buildSuggestions(s) {
15
+ import { config, configIdentitySha256, controlCenterStatusPath, CORE_BASE_URL, DIRECT_BASE_URL, errorMessage, readJsonFileSafe, sendJson, } from "../shared.js";
16
+ export function buildConnectionSummary(mode, tunnel, tools) {
17
+ const masked = typeof tunnel.tunnel_id_masked === "string" && tunnel.tunnel_id_masked.includes("***")
18
+ ? tunnel.tunnel_id_masked
19
+ : null;
20
+ return {
21
+ profile: typeof tunnel.profile === "string"
22
+ ? tunnel.profile
23
+ : mode === "direct" ? "patchwarden-direct" : "patchwarden",
24
+ tool_profile: tools.tool_profile,
25
+ ready: tunnel.ready === true,
26
+ tool_count: tools.tool_count,
27
+ tool_manifest_sha256: tools.tool_manifest_sha256,
28
+ tunnel_id_masked: masked,
29
+ reconnect_guidance: mode === "direct"
30
+ ? "If ChatGPT still uses an older Direct Tunnel, update the connector binding and test health_check in a new chat."
31
+ : "If ChatGPT still uses an older Core Tunnel, update the connector binding and test health_check in a new chat.",
32
+ };
33
+ }
34
+ /**
35
+ * Keep current readiness separate from historical evidence. A month-old failed
36
+ * task is useful for audit, but must not make a configured local bridge look
37
+ * unavailable on the first screen.
38
+ */
39
+ export function buildExperienceStatus(snapshot) {
40
+ const coreReady = snapshot.core.available && snapshot.tunnel.core.ready === true;
41
+ const watcherReady = snapshot.watcher.status === "healthy";
42
+ const agentsReady = snapshot.agents.some((agent) => agent.available);
43
+ const directEnabled = snapshot.direct_profile_enabled;
44
+ const directReady = snapshot.direct.available && snapshot.tunnel.direct.ready === true;
45
+ const staleTasks = snapshot.tasks.stale;
46
+ const failedTasks = snapshot.tasks.tasks.filter((task) => String(task.status || "").includes("failed")).length;
47
+ const live_checks = [
48
+ { key: "workspace_agents", label: "工作区与 Agent", state: agentsReady ? "ready" : "blocked", detail: agentsReady ? "工作区内至少有一个可用 Agent" : "需要配置工作区或可用 Agent" },
49
+ { key: "core", label: "Core 服务", state: coreReady ? "ready" : "needs_action", detail: coreReady ? "Core 与 Tunnel 已就绪" : "启动 Core 后会继续检查 Tunnel 与 Watcher" },
50
+ { key: "watcher", label: "任务监视器", state: watcherReady ? "ready" : "needs_action", detail: watcherReady ? "Watcher 心跳正常" : "Core 启动后 Watcher 将处理排队任务" },
51
+ { key: "direct", label: "Direct 编辑", state: directEnabled ? (directReady ? "ready" : "needs_action") : "optional", detail: directEnabled ? (directReady ? "Direct 已就绪" : "已启用但尚未就绪") : "可选高级能力,未启用不会影响 Core" },
52
+ ];
53
+ const readiness = !agentsReady
54
+ ? "blocked"
55
+ : coreReady && watcherReady && (!directEnabled || directReady)
56
+ ? "ready"
57
+ : "needs_action";
58
+ return {
59
+ readiness,
60
+ live_checks,
61
+ historical_summary: {
62
+ stale_tasks: staleTasks,
63
+ failed_tasks: failedTasks,
64
+ note: "历史任务与审计记录不会影响当前实时就绪状态。",
65
+ },
66
+ };
67
+ }
68
+ export function reconcileTunnelStatus(status, health) {
69
+ if (!health.available)
70
+ return status;
71
+ return {
72
+ ...status,
73
+ observed: true,
74
+ status: "running",
75
+ ready: true,
76
+ reason_code: "health_endpoint_ready",
77
+ health_observed: true,
78
+ };
79
+ }
80
+ export function buildSuggestions(s) {
17
81
  const out = [];
18
82
  if (!s.core.available) {
19
83
  out.push({
@@ -23,7 +87,7 @@ function buildSuggestions(s) {
23
87
  action: "/api/core/start",
24
88
  });
25
89
  }
26
- if (!s.direct.available) {
90
+ if (s.direct_profile_enabled && !s.direct.available) {
27
91
  out.push({
28
92
  code: "direct_stopped",
29
93
  severity: "warning",
@@ -31,12 +95,12 @@ function buildSuggestions(s) {
31
95
  action: "/api/direct/start",
32
96
  });
33
97
  }
34
- if (s.watcher.status === "stale" || s.watcher.status === "unreadable") {
98
+ if (s.watcher.status === "degraded" || s.watcher.status === "stale" || s.watcher.status === "unreadable") {
35
99
  out.push({
36
100
  code: "watcher_stale",
37
101
  severity: "error",
38
- message: "Watcher 处于 " + s.watcher.status + " 状态,建议 Restart All",
39
- action: "/api/restart-all",
102
+ message: "Watcher 处于 " + s.watcher.status + " 状态,建议重启 Core",
103
+ action: "/api/core/restart",
40
104
  });
41
105
  }
42
106
  if (s.tasks.stale > 0) {
@@ -49,12 +113,12 @@ function buildSuggestions(s) {
49
113
  }
50
114
  const coreTunnelReady = !!(s.tunnel.core && s.tunnel.core.ready);
51
115
  const directTunnelReady = !!(s.tunnel.direct && s.tunnel.direct.ready);
52
- if (!coreTunnelReady || !directTunnelReady) {
116
+ if (!coreTunnelReady || (s.direct_profile_enabled && !directTunnelReady)) {
53
117
  out.push({
54
118
  code: "tunnel_not_ready",
55
119
  severity: "warning",
56
- message: "Tunnel 未就绪,建议重启 profile 或检查代理",
57
- action: "/api/restart-all",
120
+ message: "Tunnel 未就绪,建议启动 profile 或检查代理",
121
+ action: "/api/start-all",
58
122
  });
59
123
  }
60
124
  const missingAgents = s.agents.filter((a) => !a.available);
@@ -62,7 +126,7 @@ function buildSuggestions(s) {
62
126
  out.push({
63
127
  code: "agent_missing",
64
128
  severity: "info",
65
- message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查 opencode/claude 路径)",
129
+ message: "Agent 未就绪:" + missingAgents.map((a) => a.name).join(", ") + "(请检查对应 CLI 安装与路径)",
66
130
  });
67
131
  }
68
132
  return out;
@@ -101,7 +165,7 @@ function diffAndRecordEvents(prev, curr) {
101
165
  // ── Handlers ──────────────────────────────────────────────────────
102
166
  export async function handleStatus(res) {
103
167
  try {
104
- const [coreHealth, directHealth, watcher, tunnelCore, tunnelDirect, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
168
+ const [coreHealth, directHealth, watcher, tunnelCoreRaw, tunnelDirectRaw, toolsCore, toolsDirect, agents, workspaceRoot, tasks] = await Promise.all([
105
169
  probeRuntimeHealth(CORE_BASE_URL).catch((err) => ({
106
170
  available: false,
107
171
  reason: errorMessage(err),
@@ -123,6 +187,8 @@ export async function handleStatus(res) {
123
187
  Promise.resolve(resolveWorkspaceRootSafe()),
124
188
  Promise.resolve(listTasksForStatus()),
125
189
  ]);
190
+ const tunnelCore = reconcileTunnelStatus(tunnelCoreRaw, coreHealth);
191
+ const tunnelDirect = reconcileTunnelStatus(tunnelDirectRaw, directHealth);
126
192
  const snapshotForSuggestions = {
127
193
  core: coreHealth,
128
194
  direct: directHealth,
@@ -130,8 +196,10 @@ export async function handleStatus(res) {
130
196
  tunnel: { core: tunnelCore, direct: tunnelDirect },
131
197
  agents,
132
198
  tasks,
199
+ direct_profile_enabled: config.enableDirectProfile === true,
133
200
  };
134
201
  const suggestions = buildSuggestions(snapshotForSuggestions);
202
+ const experience = buildExperienceStatus(snapshotForSuggestions);
135
203
  const tunnelClientExe = findTunnelClientExecutable();
136
204
  // Diff against the previous poll to record observed state-change events.
137
205
  // This is the only place that observes Core/Direct/watcher/task transitions
@@ -147,15 +215,21 @@ export async function handleStatus(res) {
147
215
  watcher,
148
216
  tunnel: { core: tunnelCore, direct: tunnelDirect },
149
217
  tools: { core: toolsCore, direct: toolsDirect },
218
+ connections: {
219
+ core: buildConnectionSummary("core", tunnelCore, toolsCore),
220
+ direct: buildConnectionSummary("direct", tunnelDirect, toolsDirect),
221
+ },
150
222
  agents,
151
223
  workspace_root: workspaceRoot,
152
224
  tasks,
153
225
  suggestions,
226
+ experience,
227
+ direct_profile_enabled: config.enableDirectProfile === true,
154
228
  setup: {
155
229
  tunnel_client: {
156
230
  available: tunnelClientExe !== null,
157
231
  path: tunnelClientExe,
158
- default_path: DEFAULT_TUNNEL_CLIENT_EXE,
232
+ source: config.tunnelClientPath && tunnelClientExe === config.tunnelClientPath ? "config" : "detected",
159
233
  },
160
234
  workspace_root: workspaceRoot,
161
235
  watcher: {
@@ -197,11 +271,15 @@ export function handleTunnelUiUrl(res) {
197
271
  direct: readTunnelUrl(true),
198
272
  });
199
273
  }
200
- export function handleDiagnostics(res) {
274
+ export async function handleDiagnostics(res) {
201
275
  try {
202
276
  const watcher = readWatcherStatusSafe();
203
- const tunnelCore = readTunnelStatus(false);
204
- const tunnelDirect = readTunnelStatus(true);
277
+ const [coreHealth, directHealth] = await Promise.all([
278
+ probeRuntimeHealth(CORE_BASE_URL),
279
+ probeRuntimeHealth(DIRECT_BASE_URL),
280
+ ]);
281
+ const tunnelCore = reconcileTunnelStatus(readTunnelStatus(false), coreHealth);
282
+ const tunnelDirect = reconcileTunnelStatus(readTunnelStatus(true), directHealth);
205
283
  const toolsCore = readToolManifest(false);
206
284
  const toolsDirect = readToolManifest(true);
207
285
  const agents = listAgentsSafe();
@@ -239,6 +317,7 @@ export function handleDiagnostics(res) {
239
317
  workspace_root: workspaceRoot,
240
318
  recent_failures: recentFailures,
241
319
  direct_profile_enabled: config.enableDirectProfile ?? false,
320
+ config_identity_sha256: configIdentitySha256,
242
321
  };
243
322
  // Redact any sensitive content that may have leaked into string fields.
244
323
  const redacted = redactSensitiveValue(diagnostics);
@@ -7,13 +7,15 @@
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
- import { auditTask } from "../../tools/auditTask.js";
12
+ import { auditTask } from "../../tools/diagnostics/auditTask.js";
14
13
  import { getTasksDir } from "../../config.js";
15
- import { classifyStaleTask, isValidTaskId, readHiddenStaleIds, readWatcherStatusSafe, recordEvent, TERMINAL_TASK_STATUSES, writeHiddenStaleIds, } from "../runtime.js";
16
- import { config, errorMessage, readJsonFileSafe, sendJson } from "../shared.js";
14
+ import { classifyStaleTask, isValidTaskId, readHiddenStaleIds, reconstructTaskEntry, readWatcherStatusSafe, recordEvent, TERMINAL_TASK_STATUSES, writeHiddenStaleIds, } from "../runtime.js";
15
+ import { launchFileManager } from "../fileManager.js";
16
+ import { config, errorMessage, guardControlPath, readJsonFileSafe, sendJson } from "../shared.js";
17
+ import { atomicWriteJsonFileSync } from "../../utils/atomicFile.js";
18
+ import { mutateTaskStatus } from "../../runner/taskStatusStore.js";
17
19
  /**
18
20
  * Reconcile a stale task. Does NOT delete the task. Reads the task files,
19
21
  * decides whether it is safe to mark the task as stale/archived, writes a
@@ -27,8 +29,8 @@ export function handleReconcile(res, taskId) {
27
29
  return;
28
30
  }
29
31
  const tasksDir = getTasksDir(config);
30
- const taskDir = join(tasksDir, taskId);
31
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
32
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
33
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
32
34
  sendJson(res, 404, { error: "Task not found" });
33
35
  return;
34
36
  }
@@ -37,33 +39,7 @@ export function handleReconcile(res, taskId) {
37
39
  const statusData = readJsonFileSafe(statusPath) ?? {};
38
40
  const runtimeData = readJsonFileSafe(runtimePath) ?? {};
39
41
  const watcher = readWatcherStatusSafe();
40
- const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
41
- const taskStatus = String(statusData.status || "pending");
42
- const taskAcceptanceStatus = taskStatus === "done_by_agent"
43
- ? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE.includes(statusData.acceptance_status) ? statusData.acceptance_status : "pending")
44
- : null;
45
- const taskEntry = {
46
- task_id: taskId,
47
- plan_id: String(statusData.plan_id || ""),
48
- title: "",
49
- agent: String(statusData.agent || ""),
50
- status: taskStatus,
51
- phase: String(runtimeData.phase || statusData.phase || "queued"),
52
- acceptance_status: taskAcceptanceStatus,
53
- created_at: String(statusData.created_at || ""),
54
- updated_at: String(statusData.updated_at || ""),
55
- workspace_root: String(statusData.workspace_root || config.workspaceRoot),
56
- repo_path: String(statusData.repo_path || "."),
57
- resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
58
- test_command: String(statusData.test_command || ""),
59
- verify_commands: Array.isArray(statusData.verify_commands) ? statusData.verify_commands : [],
60
- error: statusData.error ? String(statusData.error) : null,
61
- last_heartbeat_at: String(runtimeData.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
62
- current_command: runtimeData.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
63
- timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
64
- pending_reason: null,
65
- watcher_status: watcher.status,
66
- };
42
+ const taskEntry = reconstructTaskEntry(taskId, statusData, runtimeData, watcher);
67
43
  const cls = classifyStaleTask(taskEntry, watcher);
68
44
  const isTerminal = TERMINAL_TASK_STATUSES.has(taskEntry.status);
69
45
  // Safe to mark stale/archived when:
@@ -104,7 +80,7 @@ export function handleReconcile(res, taskId) {
104
80
  };
105
81
  // Write the reconcile record artifact.
106
82
  try {
107
- writeFileSync(join(taskDir, "reconcile.json"), JSON.stringify(reconcileRecord, null, 2), "utf-8");
83
+ atomicWriteJsonFileSync(join(taskDir, "reconcile.json"), reconcileRecord);
108
84
  }
109
85
  catch (writeErr) {
110
86
  sendJson(res, 500, { error: `Failed to write reconcile record: ${errorMessage(writeErr)}` });
@@ -112,13 +88,18 @@ export function handleReconcile(res, taskId) {
112
88
  }
113
89
  // Annotate status.json with reconcile metadata (do not mutate status enum).
114
90
  if (safe) {
115
- const annotated = {
116
- ...statusData,
91
+ const annotation = {
117
92
  reconcile_state: decision === "marked_archived" ? "archived" : "stale",
118
93
  reconciled_at: reconciledAt,
119
94
  };
120
95
  try {
121
- writeFileSync(statusPath, JSON.stringify(annotated, null, 2), "utf-8");
96
+ mutateTaskStatus(statusPath, (current) => {
97
+ if (current.status !== taskEntry.status) {
98
+ throw new Error("Task status changed while reconcile was being evaluated; retry.");
99
+ }
100
+ const next = { ...current, ...annotation, updated_at: new Date().toISOString() };
101
+ return { next, result: next };
102
+ });
122
103
  }
123
104
  catch (writeErr) {
124
105
  sendJson(res, 500, { error: `Failed to annotate status.json: ${errorMessage(writeErr)}` });
@@ -150,8 +131,8 @@ export function handleTaskAudit(res, taskId) {
150
131
  return;
151
132
  }
152
133
  const tasksDir = getTasksDir(config);
153
- const taskDir = join(tasksDir, taskId);
154
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
134
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
135
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
155
136
  sendJson(res, 404, { error: "Task not found" });
156
137
  return;
157
138
  }
@@ -179,29 +160,12 @@ export function handleOpenTaskFolder(res, taskId) {
179
160
  return;
180
161
  }
181
162
  const tasksDir = getTasksDir(config);
182
- const taskDir = join(tasksDir, taskId);
183
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
163
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
164
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
184
165
  sendJson(res, 404, { error: "Task not found" });
185
166
  return;
186
167
  }
187
- let cmd;
188
- if (process.platform === "win32") {
189
- cmd = "explorer.exe";
190
- }
191
- else if (process.platform === "darwin") {
192
- cmd = "open";
193
- }
194
- else {
195
- cmd = "xdg-open";
196
- }
197
- try {
198
- const child = spawn(cmd, [taskDir], { detached: true, stdio: "ignore" });
199
- child.on("error", () => { });
200
- child.unref();
201
- }
202
- catch {
203
- /* ignore */
204
- }
168
+ launchFileManager(taskDir, tasksDir);
205
169
  sendJson(res, 200, { ok: true, path: taskDir });
206
170
  }
207
171
  catch (err) {
@@ -5,6 +5,10 @@ export interface TaskFilters {
5
5
  acceptance_status?: string;
6
6
  agent?: string;
7
7
  warning_type?: string;
8
+ date_from?: string;
9
+ date_to?: string;
10
+ limit?: number;
11
+ cursor?: string;
8
12
  }
9
13
  export declare function handleTasks(res: ServerResponse, filters?: TaskFilters): void;
10
14
  export declare function deriveStaleReasonCode(staleReasons: string[]): string;
@@ -9,17 +9,18 @@
9
9
  */
10
10
  import { existsSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
- import { listTasks } from "../../tools/listTasks.js";
13
- import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../../tools/safeViews.js";
12
+ import { listTasks } from "../../tools/tasks/listTasks.js";
13
+ import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../../tools/diagnostics/safeViews.js";
14
14
  import { getTasksDir } from "../../config.js";
15
- import { augmentTaskWithStale, classifyStaleTask, fileMtimeIso, isValidTaskId, parseReviewVerdict, readWatcherStatusSafe, } from "../runtime.js";
16
- import { config, errorMessage, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
15
+ import { augmentTaskWithStale, classifyStaleTask, fileMtimeIso, isValidTaskId, parseReviewVerdict, reconstructTaskEntry, readWatcherStatusSafe, } from "../runtime.js";
16
+ import { config, errorMessage, guardControlPath, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
17
17
  export function handleTasks(res, filters) {
18
18
  try {
19
19
  const result = listTasks({ limit: 100 });
20
20
  const watcher = result.watcher;
21
21
  const now = Date.now();
22
22
  let augmented = result.tasks.map((t) => augmentTaskWithStale(t, watcher, now));
23
+ const facetSource = augmented.slice();
23
24
  // Apply optional filters from query params.
24
25
  if (filters) {
25
26
  if (filters.repo_path) {
@@ -46,23 +47,59 @@ export function handleTasks(res, filters) {
46
47
  }
47
48
  if (filters.warning_type) {
48
49
  const wt = filters.warning_type;
49
- if (wt === "stale") {
50
+ if (wt === "stale" || wt === "stale_task") {
50
51
  augmented = augmented.filter((t) => t.is_stale);
51
52
  }
52
53
  else if (wt === "error") {
53
54
  augmented = augmented.filter((t) => t.error !== null && t.error !== "");
54
55
  }
56
+ else if (wt === "failed_verification") {
57
+ augmented = augmented.filter((t) => t.status === wt);
58
+ }
55
59
  else {
56
- // Treat as a specific stale_reason token to match against.
57
- augmented = augmented.filter((t) => Array.isArray(t.stale_reasons) && t.stale_reasons.includes(wt));
60
+ augmented = augmented.filter((t) => t.status === wt || t.acceptance_status === wt ||
61
+ (Array.isArray(t.stale_reasons) && t.stale_reasons.includes(wt)));
58
62
  }
59
63
  }
64
+ if (filters.date_from || filters.date_to) {
65
+ const from = filters.date_from ? Date.parse(`${filters.date_from}T00:00:00`) : Number.NEGATIVE_INFINITY;
66
+ const to = filters.date_to ? Date.parse(`${filters.date_to}T23:59:59`) : Number.POSITIVE_INFINITY;
67
+ augmented = augmented.filter((task) => {
68
+ const timestamp = Date.parse(String(task.updated_at || task.created_at || ""));
69
+ return Number.isFinite(timestamp) && timestamp >= from && timestamp <= to;
70
+ });
71
+ }
60
72
  }
73
+ augmented.sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")) || String(b.task_id).localeCompare(String(a.task_id)));
61
74
  const staleCount = augmented.filter((t) => t.is_stale).length;
75
+ const limit = Math.max(10, Math.min(filters?.limit || 25, 100));
76
+ const offset = filters?.cursor && /^\d+$/.test(filters.cursor) ? Number(filters.cursor) : 0;
77
+ const page = augmented.slice(offset, offset + limit);
78
+ const nextCursor = offset + page.length < augmented.length ? String(offset + page.length) : null;
79
+ const facets = {
80
+ repos: [...new Set(facetSource.map((task) => String(task.repo_path || ".")))].sort(),
81
+ statuses: [...new Set(facetSource.map((task) => String(task.status || "unknown")))].sort(),
82
+ agents: [...new Set(facetSource.map((task) => String(task.agent || "unknown")))].sort(),
83
+ };
62
84
  sendJson(res, 200, {
63
- tasks: augmented,
85
+ tasks: page,
64
86
  total: augmented.length,
65
- returned: augmented.length,
87
+ returned: page.length,
88
+ nextCursor,
89
+ next_cursor: nextCursor,
90
+ filters: {
91
+ applied: {
92
+ repo_path: filters?.repo_path || null,
93
+ status: filters?.status || null,
94
+ acceptance_status: filters?.acceptance_status || null,
95
+ agent: filters?.agent || null,
96
+ warning_type: filters?.warning_type || null,
97
+ date_from: filters?.date_from || null,
98
+ date_to: filters?.date_to || null,
99
+ },
100
+ options: facets,
101
+ },
102
+ facets,
66
103
  watcher,
67
104
  stale_count: staleCount,
68
105
  });
@@ -72,6 +109,10 @@ export function handleTasks(res, filters) {
72
109
  tasks: [],
73
110
  total: 0,
74
111
  returned: 0,
112
+ nextCursor: null,
113
+ next_cursor: null,
114
+ filters: { applied: {}, options: { repos: [], statuses: [], agents: [] } },
115
+ facets: { repos: [], statuses: [], agents: [] },
75
116
  watcher: null,
76
117
  stale_count: 0,
77
118
  error: errorMessage(err),
@@ -140,9 +181,13 @@ export function handleStaleTasks(res) {
140
181
  }
141
182
  export function handleTaskDetail(res, taskId) {
142
183
  try {
184
+ if (!isValidTaskId(taskId)) {
185
+ sendJson(res, 400, { error: "Invalid task id" });
186
+ return;
187
+ }
143
188
  const tasksDir = getTasksDir(config);
144
- const taskDir = join(tasksDir, taskId);
145
- if (!existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
189
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
190
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
146
191
  sendJson(res, 404, { error: "Task not found" });
147
192
  return;
148
193
  }
@@ -187,33 +232,7 @@ export function handleTaskDetail(res, taskId) {
187
232
  let stale = null;
188
233
  if (statusData) {
189
234
  const watcher = readWatcherStatusSafe();
190
- const VALID_ACCEPTANCE2 = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
191
- const taskStatus2 = String(statusData.status || "pending");
192
- const taskAcceptanceStatus2 = taskStatus2 === "done_by_agent"
193
- ? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE2.includes(statusData.acceptance_status) ? statusData.acceptance_status : "pending")
194
- : null;
195
- const entry = {
196
- task_id: taskId,
197
- plan_id: String(statusData.plan_id || ""),
198
- title: "",
199
- agent: String(statusData.agent || ""),
200
- status: taskStatus2,
201
- phase: String(runtimeData?.phase || statusData.phase || "queued"),
202
- acceptance_status: taskAcceptanceStatus2,
203
- created_at: String(statusData.created_at || ""),
204
- updated_at: String(statusData.updated_at || ""),
205
- workspace_root: String(statusData.workspace_root || config.workspaceRoot),
206
- repo_path: String(statusData.repo_path || "."),
207
- resolved_repo_path: String(statusData.resolved_repo_path || statusData.repo_path || config.workspaceRoot),
208
- test_command: String(statusData.test_command || ""),
209
- verify_commands: Array.isArray(statusData.verify_commands) ? statusData.verify_commands : [],
210
- error: statusData.error ? String(statusData.error) : null,
211
- last_heartbeat_at: String(runtimeData?.last_heartbeat_at || statusData.last_heartbeat_at || statusData.updated_at || ""),
212
- current_command: runtimeData?.current_command === undefined ? null : String(runtimeData.current_command || "") || null,
213
- timeout_seconds: Number(statusData.timeout_seconds) || config.defaultTaskTimeoutSeconds,
214
- pending_reason: null,
215
- watcher_status: watcher.status,
216
- };
235
+ const entry = reconstructTaskEntry(taskId, statusData, runtimeData ?? {}, watcher);
217
236
  stale = classifyStaleTask(entry, watcher);
218
237
  }
219
238
  sendJson(res, 200, {
@@ -246,12 +265,7 @@ export function handleTaskSafeResult(res, taskId) {
246
265
  sendJson(res, 400, { error: "Invalid task id" });
247
266
  return;
248
267
  }
249
- try {
250
- sendJson(res, 200, safeResult(taskId, { max_items: 12 }));
251
- }
252
- catch (err) {
253
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
254
- }
268
+ sendJson(res, 200, safeResult(taskId, { max_items: 12 }));
255
269
  }
256
270
  catch (err) {
257
271
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
@@ -263,12 +277,7 @@ export function handleTaskSafeAudit(res, taskId) {
263
277
  sendJson(res, 400, { error: "Invalid task id" });
264
278
  return;
265
279
  }
266
- try {
267
- sendJson(res, 200, safeAudit(taskId, { max_items: 12 }));
268
- }
269
- catch (err) {
270
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
271
- }
280
+ sendJson(res, 200, safeAudit(taskId, { max_items: 12 }));
272
281
  }
273
282
  catch (err) {
274
283
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
@@ -280,12 +289,7 @@ export function handleTaskSafeTestSummary(res, taskId) {
280
289
  sendJson(res, 400, { error: "Invalid task id" });
281
290
  return;
282
291
  }
283
- try {
284
- sendJson(res, 200, safeTestSummary(taskId));
285
- }
286
- catch (err) {
287
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
288
- }
292
+ sendJson(res, 200, safeTestSummary(taskId));
289
293
  }
290
294
  catch (err) {
291
295
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
@@ -297,12 +301,7 @@ export function handleTaskSafeDiffSummary(res, taskId) {
297
301
  sendJson(res, 400, { error: "Invalid task id" });
298
302
  return;
299
303
  }
300
- try {
301
- sendJson(res, 200, safeDiffSummary(taskId, { max_items: 12 }));
302
- }
303
- catch (err) {
304
- sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
305
- }
304
+ sendJson(res, 200, safeDiffSummary(taskId, { max_items: 12 }));
306
305
  }
307
306
  catch (err) {
308
307
  sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });