patchwarden 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Control Center routes — mutating task actions (POST /api/tasks/:id/*).
3
+ *
4
+ * These handlers are invoked only after the server router has validated the
5
+ * control token. They cover reconcile, on-demand audit_task, opening the task
6
+ * folder in the host file explorer, and hiding a stale task from the
7
+ * dashboard. None of them delete task files; they annotate or launch external
8
+ * viewers.
9
+ */
10
+ import { existsSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { auditTask } from "../../tools/diagnostics/auditTask.js";
13
+ import { getTasksDir } from "../../config.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";
19
+ /**
20
+ * Reconcile a stale task. Does NOT delete the task. Reads the task files,
21
+ * decides whether it is safe to mark the task as stale/archived, writes a
22
+ * reconcile record, and (when safe) annotates status.json with reconcile
23
+ * metadata. The task status enum is never changed — only metadata is added.
24
+ */
25
+ export function handleReconcile(res, taskId) {
26
+ try {
27
+ if (!isValidTaskId(taskId)) {
28
+ sendJson(res, 400, { error: "Invalid task id" });
29
+ return;
30
+ }
31
+ const tasksDir = getTasksDir(config);
32
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
33
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
34
+ sendJson(res, 404, { error: "Task not found" });
35
+ return;
36
+ }
37
+ const statusPath = join(taskDir, "status.json");
38
+ const runtimePath = join(taskDir, "runtime.json");
39
+ const statusData = readJsonFileSafe(statusPath) ?? {};
40
+ const runtimeData = readJsonFileSafe(runtimePath) ?? {};
41
+ const watcher = readWatcherStatusSafe();
42
+ const taskEntry = reconstructTaskEntry(taskId, statusData, runtimeData, watcher);
43
+ const cls = classifyStaleTask(taskEntry, watcher);
44
+ const isTerminal = TERMINAL_TASK_STATUSES.has(taskEntry.status);
45
+ // Safe to mark stale/archived when:
46
+ // - terminal status -> archive (already finished)
47
+ // - stale AND watcher is not actively driving it (no current_command OR watcher not healthy)
48
+ let decision;
49
+ let safe = false;
50
+ if (isTerminal) {
51
+ decision = "marked_archived";
52
+ safe = true;
53
+ }
54
+ else if (cls.is_stale &&
55
+ (taskEntry.current_command === null || taskEntry.current_command === "" || watcher.status !== "healthy")) {
56
+ decision = "marked_stale";
57
+ safe = true;
58
+ }
59
+ else {
60
+ decision = "no_action";
61
+ safe = false;
62
+ }
63
+ const reconciledAt = new Date().toISOString();
64
+ const reconcileRecord = {
65
+ task_id: taskId,
66
+ reconciled_at: reconciledAt,
67
+ decision,
68
+ safe,
69
+ previous_status: taskEntry.status,
70
+ previous_phase: taskEntry.phase,
71
+ is_stale: cls.is_stale,
72
+ stale_reasons: cls.stale_reasons,
73
+ watcher_status: watcher.status,
74
+ watcher_last_heartbeat_at: watcher.last_heartbeat_at,
75
+ task_last_heartbeat_at: taskEntry.last_heartbeat_at || null,
76
+ task_current_command: taskEntry.current_command,
77
+ notes: decision === "no_action"
78
+ ? "Task does not currently qualify for safe reconcile (still actively running or watcher is healthy)."
79
+ : "Task annotated with reconcile metadata; original status preserved. No files were deleted.",
80
+ };
81
+ // Write the reconcile record artifact.
82
+ try {
83
+ atomicWriteJsonFileSync(join(taskDir, "reconcile.json"), reconcileRecord);
84
+ }
85
+ catch (writeErr) {
86
+ sendJson(res, 500, { error: `Failed to write reconcile record: ${errorMessage(writeErr)}` });
87
+ return;
88
+ }
89
+ // Annotate status.json with reconcile metadata (do not mutate status enum).
90
+ if (safe) {
91
+ const annotation = {
92
+ reconcile_state: decision === "marked_archived" ? "archived" : "stale",
93
+ reconciled_at: reconciledAt,
94
+ };
95
+ try {
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
+ });
103
+ }
104
+ catch (writeErr) {
105
+ sendJson(res, 500, { error: `Failed to annotate status.json: ${errorMessage(writeErr)}` });
106
+ return;
107
+ }
108
+ }
109
+ recordEvent("task.reconciled", {
110
+ task_id: taskId,
111
+ decision,
112
+ safe,
113
+ previous_status: taskEntry.status,
114
+ is_stale: cls.is_stale,
115
+ });
116
+ sendJson(res, 200, reconcileRecord);
117
+ }
118
+ catch (err) {
119
+ sendJson(res, 500, { error: errorMessage(err) });
120
+ }
121
+ }
122
+ /**
123
+ * Run audit_task for a task. Only safe to delegate when the task directory
124
+ * exists and the task is in a terminal state (auditing a running task mid-flight
125
+ * would race with the watcher writing artifacts).
126
+ */
127
+ export function handleTaskAudit(res, taskId) {
128
+ try {
129
+ if (!isValidTaskId(taskId)) {
130
+ sendJson(res, 400, { error: "Invalid task id" });
131
+ return;
132
+ }
133
+ const tasksDir = getTasksDir(config);
134
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
135
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
136
+ sendJson(res, 404, { error: "Task not found" });
137
+ return;
138
+ }
139
+ const statusData = readJsonFileSafe(join(taskDir, "status.json"));
140
+ const taskStatus = statusData ? String(statusData.status || "") : "";
141
+ if (!TERMINAL_TASK_STATUSES.has(taskStatus)) {
142
+ sendJson(res, 409, {
143
+ error: "Task is not in a terminal state; audit_task can only run safely after completion.",
144
+ status: taskStatus || "unknown",
145
+ });
146
+ return;
147
+ }
148
+ const output = auditTask(taskId);
149
+ recordEvent("task.audited", { task_id: taskId, previous_status: taskStatus });
150
+ sendJson(res, 200, { ok: true, audit: output });
151
+ }
152
+ catch (err) {
153
+ sendJson(res, 500, { error: errorMessage(err) });
154
+ }
155
+ }
156
+ export function handleOpenTaskFolder(res, taskId) {
157
+ try {
158
+ if (!isValidTaskId(taskId)) {
159
+ sendJson(res, 400, { error: "Invalid task id" });
160
+ return;
161
+ }
162
+ const tasksDir = getTasksDir(config);
163
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
164
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
165
+ sendJson(res, 404, { error: "Task not found" });
166
+ return;
167
+ }
168
+ launchFileManager(taskDir, tasksDir);
169
+ sendJson(res, 200, { ok: true, path: taskDir });
170
+ }
171
+ catch (err) {
172
+ sendJson(res, 500, { error: errorMessage(err) });
173
+ }
174
+ }
175
+ /**
176
+ * Hide a stale task from the dashboard's stale-task view. The task itself is
177
+ * NOT deleted or modified — only the control-center's local hidden-stale-ids
178
+ * state file is updated. Requires control token (enforced by the POST router).
179
+ */
180
+ export function handleHideStale(res, taskId) {
181
+ try {
182
+ if (!isValidTaskId(taskId)) {
183
+ sendJson(res, 400, { error: "Invalid task id" });
184
+ return;
185
+ }
186
+ const ids = readHiddenStaleIds();
187
+ if (!ids.includes(taskId)) {
188
+ ids.push(taskId);
189
+ writeHiddenStaleIds(ids);
190
+ }
191
+ recordEvent("task.hide_stale", { task_id: taskId });
192
+ sendJson(res, 200, { ok: true, hidden: taskId });
193
+ }
194
+ catch (err) {
195
+ sendJson(res, 500, { error: errorMessage(err) });
196
+ }
197
+ }
@@ -0,0 +1,24 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export interface TaskFilters {
3
+ repo_path?: string;
4
+ status?: string;
5
+ acceptance_status?: string;
6
+ agent?: string;
7
+ warning_type?: string;
8
+ date_from?: string;
9
+ date_to?: string;
10
+ limit?: number;
11
+ cursor?: string;
12
+ }
13
+ export declare function handleTasks(res: ServerResponse, filters?: TaskFilters): void;
14
+ export declare function deriveStaleReasonCode(staleReasons: string[]): string;
15
+ export declare function staleExplanationFor(reasonCode: string): {
16
+ explanation: string;
17
+ next_action: string;
18
+ };
19
+ export declare function handleStaleTasks(res: ServerResponse): void;
20
+ export declare function handleTaskDetail(res: ServerResponse, taskId: string): void;
21
+ export declare function handleTaskSafeResult(res: ServerResponse, taskId: string): void;
22
+ export declare function handleTaskSafeAudit(res: ServerResponse, taskId: string): void;
23
+ export declare function handleTaskSafeTestSummary(res: ServerResponse, taskId: string): void;
24
+ export declare function handleTaskSafeDiffSummary(res: ServerResponse, taskId: string): void;
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Control Center routes — task queries (GET /api/tasks/*).
3
+ *
4
+ * Hosts the read-only task endpoints: list/filter tasks, stale-task
5
+ * classification with explanations, task detail, and the safe bounded views
6
+ * (safe-result / safe-audit / safe-test-summary / safe-diff-summary) that
7
+ * never expose full stdout/stderr/diff content. Mutating task actions live in
8
+ * taskActions.ts.
9
+ */
10
+ import { existsSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { listTasks } from "../../tools/tasks/listTasks.js";
13
+ import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../../tools/diagnostics/safeViews.js";
14
+ import { getTasksDir } from "../../config.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
+ export function handleTasks(res, filters) {
18
+ try {
19
+ const result = listTasks({ limit: 100 });
20
+ const watcher = result.watcher;
21
+ const now = Date.now();
22
+ let augmented = result.tasks.map((t) => augmentTaskWithStale(t, watcher, now));
23
+ const facetSource = augmented.slice();
24
+ // Apply optional filters from query params.
25
+ if (filters) {
26
+ if (filters.repo_path) {
27
+ const filterRepo = filters.repo_path.trim().replace(/\\/g, "/");
28
+ augmented = augmented.filter((t) => {
29
+ const taskRepo = String(t.repo_path || ".").replace(/\\/g, "/");
30
+ const taskResolved = String(t.resolved_repo_path || "").replace(/\\/g, "/");
31
+ return taskRepo === filterRepo || taskResolved === filterRepo;
32
+ });
33
+ }
34
+ if (filters.status) {
35
+ augmented = augmented.filter((t) => t.status === filters.status);
36
+ }
37
+ if (filters.acceptance_status) {
38
+ augmented = augmented.filter((t) => {
39
+ // Tasks without acceptance status only match the "pending" filter when
40
+ // status is done_by_agent; null acceptance never matches other values.
41
+ return t.acceptance_status === filters.acceptance_status;
42
+ });
43
+ }
44
+ if (filters.agent) {
45
+ const filterAgent = filters.agent.toLowerCase();
46
+ augmented = augmented.filter((t) => String(t.agent || "").toLowerCase() === filterAgent);
47
+ }
48
+ if (filters.warning_type) {
49
+ const wt = filters.warning_type;
50
+ if (wt === "stale" || wt === "stale_task") {
51
+ augmented = augmented.filter((t) => t.is_stale);
52
+ }
53
+ else if (wt === "error") {
54
+ augmented = augmented.filter((t) => t.error !== null && t.error !== "");
55
+ }
56
+ else if (wt === "failed_verification") {
57
+ augmented = augmented.filter((t) => t.status === wt);
58
+ }
59
+ else {
60
+ augmented = augmented.filter((t) => t.status === wt || t.acceptance_status === wt ||
61
+ (Array.isArray(t.stale_reasons) && t.stale_reasons.includes(wt)));
62
+ }
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
+ }
72
+ }
73
+ augmented.sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")) || String(b.task_id).localeCompare(String(a.task_id)));
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
+ };
84
+ sendJson(res, 200, {
85
+ tasks: page,
86
+ total: 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,
103
+ watcher,
104
+ stale_count: staleCount,
105
+ });
106
+ }
107
+ catch (err) {
108
+ sendJson(res, 200, {
109
+ tasks: [],
110
+ total: 0,
111
+ returned: 0,
112
+ nextCursor: null,
113
+ next_cursor: null,
114
+ filters: { applied: {}, options: { repos: [], statuses: [], agents: [] } },
115
+ facets: { repos: [], statuses: [], agents: [] },
116
+ watcher: null,
117
+ stale_count: 0,
118
+ error: errorMessage(err),
119
+ });
120
+ }
121
+ }
122
+ export function deriveStaleReasonCode(staleReasons) {
123
+ if (staleReasons.length === 0)
124
+ return "unknown";
125
+ if (staleReasons.some((r) => r.includes("heartbeat_stale")))
126
+ return "heartbeat_stale";
127
+ if (staleReasons.some((r) => r.includes("config") || r.includes("assessment")))
128
+ return "assessment_stale_config";
129
+ if (staleReasons.some((r) => r.includes("runtime") || r.includes("missing")))
130
+ return "runtime_missing";
131
+ return staleReasons[0];
132
+ }
133
+ export function staleExplanationFor(reasonCode) {
134
+ switch (reasonCode) {
135
+ case "assessment_stale_config":
136
+ return {
137
+ explanation: "配置或 tool manifest 已变化,旧 assessment 已过期,需要重新评估或重新创建任务。",
138
+ next_action: "reconcile or recreate task",
139
+ };
140
+ case "heartbeat_stale":
141
+ return {
142
+ explanation: "任务心跳已过期,watcher 可能未运行或任务已僵死,建议 reconcile 或 kill。",
143
+ next_action: "reconcile or kill task",
144
+ };
145
+ case "runtime_missing":
146
+ return {
147
+ explanation: "任务运行时文件缺失,可能被外部清理,建议重新创建任务。",
148
+ next_action: "recreate task",
149
+ };
150
+ default:
151
+ return {
152
+ explanation: "任务状态异常",
153
+ next_action: "review task",
154
+ };
155
+ }
156
+ }
157
+ export function handleStaleTasks(res) {
158
+ try {
159
+ const result = listTasks({ limit: 100 });
160
+ const watcher = result.watcher;
161
+ const now = Date.now();
162
+ const staleTasks = result.tasks
163
+ .map((t) => augmentTaskWithStale(t, watcher, now))
164
+ .filter((t) => t.is_stale)
165
+ .map((t) => {
166
+ const reasonCode = deriveStaleReasonCode(t.stale_reasons);
167
+ const { explanation, next_action } = staleExplanationFor(reasonCode);
168
+ return { ...t, reason_code: reasonCode, explanation, next_action };
169
+ });
170
+ sendJson(res, 200, {
171
+ stale_tasks: staleTasks,
172
+ total: staleTasks.length,
173
+ watcher,
174
+ stale_threshold_seconds: config.watcherStaleSeconds,
175
+ reason: null,
176
+ });
177
+ }
178
+ catch (err) {
179
+ sendJson(res, 200, { stale_tasks: [], total: 0, reason: errorMessage(err) });
180
+ }
181
+ }
182
+ export function handleTaskDetail(res, taskId) {
183
+ try {
184
+ if (!isValidTaskId(taskId)) {
185
+ sendJson(res, 400, { error: "Invalid task id" });
186
+ return;
187
+ }
188
+ const tasksDir = getTasksDir(config);
189
+ const taskDir = guardControlPath(join(tasksDir, taskId), config.tasksDir);
190
+ if (!taskDir || !existsSync(taskDir) || !statSync(taskDir).isDirectory()) {
191
+ sendJson(res, 404, { error: "Task not found" });
192
+ return;
193
+ }
194
+ const statusData = readJsonFileSafe(join(taskDir, "status.json"));
195
+ const runtimeData = readJsonFileSafe(join(taskDir, "runtime.json"));
196
+ const resultData = readJsonFileSafe(join(taskDir, "result.json"));
197
+ const auditData = readJsonFileSafe(join(taskDir, "audit.json"));
198
+ const verifyData = readJsonFileSafe(join(taskDir, "verify.json"));
199
+ const changedFiles = readJsonFileSafe(join(taskDir, "changed-files.json"));
200
+ const fileStats = readJsonFileSafe(join(taskDir, "file-stats.json"));
201
+ const reconcileData = readJsonFileSafe(join(taskDir, "reconcile.json"));
202
+ // independent-review.md is the primary audit artifact (written by audit_task)
203
+ const reviewPath = join(taskDir, "independent-review.md");
204
+ let independentReview = { verdict: null, content: null };
205
+ if (existsSync(reviewPath)) {
206
+ const content = readTextFileSafe(reviewPath) ?? "";
207
+ independentReview = { verdict: parseReviewVerdict(content), content };
208
+ }
209
+ // Verification summary from verify.json
210
+ const verificationSummary = verifyData
211
+ ? {
212
+ status: verifyData.status ?? null,
213
+ commands: Array.isArray(verifyData.commands) ? verifyData.commands : null,
214
+ checked_at: fileMtimeIso(join(taskDir, "verify.json")),
215
+ }
216
+ : null;
217
+ // Warnings / errors collected from status.error, result.warnings, error.log
218
+ const warnings = [];
219
+ const errors = [];
220
+ if (statusData && statusData.error)
221
+ errors.push(String(statusData.error));
222
+ if (resultData && Array.isArray(resultData.warnings)) {
223
+ for (const w of resultData.warnings)
224
+ warnings.push(String(w));
225
+ }
226
+ if (resultData && resultData.error)
227
+ errors.push(String(resultData.error));
228
+ const errorLog = readJsonFileSafe(join(taskDir, "error.log"));
229
+ if (errorLog && errorLog.message)
230
+ errors.push(String(errorLog.message));
231
+ // Stale classification (best-effort, using watcher snapshot)
232
+ let stale = null;
233
+ if (statusData) {
234
+ const watcher = readWatcherStatusSafe();
235
+ const entry = reconstructTaskEntry(taskId, statusData, runtimeData ?? {}, watcher);
236
+ stale = classifyStaleTask(entry, watcher);
237
+ }
238
+ sendJson(res, 200, {
239
+ task_id: taskId,
240
+ status: statusData,
241
+ runtime: runtimeData,
242
+ result: resultData,
243
+ audit: auditData,
244
+ independent_review: independentReview,
245
+ diff_patch: readTextFileSafe(join(taskDir, "diff.patch")),
246
+ test_log: readTextFileSafe(join(taskDir, "test.log")) ?? readTextFileSafe(join(taskDir, "test-log.txt")),
247
+ verify_log: readTextFileSafe(join(taskDir, "verify.log")),
248
+ changed_files: changedFiles,
249
+ file_stats: fileStats,
250
+ verification_summary: verificationSummary,
251
+ warnings,
252
+ errors,
253
+ stale,
254
+ reconcile: reconcileData,
255
+ task_dir: taskDir,
256
+ });
257
+ }
258
+ catch (err) {
259
+ sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
260
+ }
261
+ }
262
+ export function handleTaskSafeResult(res, taskId) {
263
+ try {
264
+ if (!isValidTaskId(taskId)) {
265
+ sendJson(res, 400, { error: "Invalid task id" });
266
+ return;
267
+ }
268
+ sendJson(res, 200, safeResult(taskId, { max_items: 12 }));
269
+ }
270
+ catch (err) {
271
+ sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
272
+ }
273
+ }
274
+ export function handleTaskSafeAudit(res, taskId) {
275
+ try {
276
+ if (!isValidTaskId(taskId)) {
277
+ sendJson(res, 400, { error: "Invalid task id" });
278
+ return;
279
+ }
280
+ sendJson(res, 200, safeAudit(taskId, { max_items: 12 }));
281
+ }
282
+ catch (err) {
283
+ sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
284
+ }
285
+ }
286
+ export function handleTaskSafeTestSummary(res, taskId) {
287
+ try {
288
+ if (!isValidTaskId(taskId)) {
289
+ sendJson(res, 400, { error: "Invalid task id" });
290
+ return;
291
+ }
292
+ sendJson(res, 200, safeTestSummary(taskId));
293
+ }
294
+ catch (err) {
295
+ sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
296
+ }
297
+ }
298
+ export function handleTaskSafeDiffSummary(res, taskId) {
299
+ try {
300
+ if (!isValidTaskId(taskId)) {
301
+ sendJson(res, 400, { error: "Invalid task id" });
302
+ return;
303
+ }
304
+ sendJson(res, 200, safeDiffSummary(taskId, { max_items: 12 }));
305
+ }
306
+ catch (err) {
307
+ sendJson(res, 200, { task_id: taskId, error: errorMessage(err) });
308
+ }
309
+ }
@@ -0,0 +1,15 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export declare function handleWorkspace(res: ServerResponse): void;
3
+ /**
4
+ * Lists first-level subdirectories of the workspace root and, for each one,
5
+ * reads package.json (if present) to expose name/version. Read-only and
6
+ * path-bounded: only direct children of workspaceRoot are inspected.
7
+ */
8
+ export declare function handleWorkspaceRepos(res: ServerResponse): void;
9
+ /**
10
+ * On-demand `git status --short` for a single repo under workspaceRoot.
11
+ * The repo parameter is resolved against workspaceRoot and must stay inside it;
12
+ * any path traversal attempt is rejected with 400. This intentionally does NOT
13
+ * run a full workspace scan — only the repo the user clicked is inspected.
14
+ */
15
+ export declare function handleWorkspaceRepoStatus(res: ServerResponse, repoParam: string): void;