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,197 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { reloadConfig } from "../../config.js";
7
- import { createTask } from "../../tools/createTask.js";
8
- import { savePlan } from "../../tools/savePlan.js";
9
- import { PatchWardenError } from "../../errors.js";
10
- // ── Helpers ───────────────────────────────────────────────────────
11
- let tempDir;
12
- let prevConfigEnv;
13
- function writeConfig(workspaceRoot) {
14
- const configPath = join(tempDir, "patchwarden.config.json");
15
- writeFileSync(configPath, JSON.stringify({
16
- workspaceRoot,
17
- tasksDir: ".patchwarden/tasks",
18
- plansDir: ".patchwarden/plans",
19
- assessmentsDir: ".patchwarden/assessments",
20
- directSessionsDir: ".patchwarden/direct-sessions",
21
- agents: {
22
- codex: { command: "codex", args: ["exec", "{prompt}"] },
23
- },
24
- allowedTestCommands: ["npm test", "npm run build"],
25
- defaultTaskTimeoutSeconds: 60,
26
- maxTaskTimeoutSeconds: 300,
27
- watcherStaleSeconds: 30,
28
- }), "utf-8");
29
- prevConfigEnv = process.env.PATCHWARDEN_CONFIG;
30
- process.env.PATCHWARDEN_CONFIG = configPath;
31
- reloadConfig();
32
- }
33
- /** Write a fresh watcher heartbeat so readWatcherStatus reports healthy. */
34
- function writeFreshHeartbeat(workspaceRoot) {
35
- const heartbeatPath = join(workspaceRoot, ".patchwarden", "watcher-heartbeat.json");
36
- mkdirSync(dirname(heartbeatPath), { recursive: true });
37
- writeFileSync(heartbeatPath, JSON.stringify({
38
- status: "running",
39
- pid: process.pid,
40
- last_heartbeat_at: new Date().toISOString(),
41
- instance_id: "test-instance",
42
- }), "utf-8");
43
- }
44
- describe("createTask", () => {
45
- beforeEach(() => {
46
- tempDir = mkdtempSync(join(tmpdir(), "pw-createtask-"));
47
- });
48
- afterEach(() => {
49
- if (prevConfigEnv === undefined)
50
- delete process.env.PATCHWARDEN_CONFIG;
51
- else
52
- process.env.PATCHWARDEN_CONFIG = prevConfigEnv;
53
- reloadConfig();
54
- rmSync(tempDir, { recursive: true, force: true });
55
- });
56
- // ── 1. inline_plan source ──────────────────────────────────────
57
- it("creates a task from inline_plan, writes status.json, and allocates a task directory", async () => {
58
- writeConfig(tempDir);
59
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
60
- writeFreshHeartbeat(tempDir);
61
- const result = await createTask({
62
- inline_plan: "## Goal\nAdd a hello-world print statement to main.ts.",
63
- agent: "codex",
64
- repo_path: "my-repo",
65
- });
66
- const out = result;
67
- assert.ok(out.task_id.startsWith("task_"), `task_id should start with "task_", got: ${out.task_id}`);
68
- assert.equal(out.status, "pending");
69
- assert.equal(out.plan_source, "inline");
70
- assert.equal(out.agent, "codex");
71
- assert.equal(out.execution_blocked, false);
72
- assert.ok(existsSync(out.path), "task directory should exist");
73
- assert.ok(existsSync(join(out.path, "status.json")), "status.json should exist");
74
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
75
- assert.equal(status.task_id, out.task_id);
76
- assert.equal(status.status, "pending");
77
- assert.equal(status.phase, "queued");
78
- assert.equal(status.agent, "codex");
79
- assert.equal(status.plan_source, "inline");
80
- assert.equal(status.repo_path, "my-repo");
81
- });
82
- // ── 2. template source ─────────────────────────────────────────
83
- it("creates a task from a built-in template (feature_small)", async () => {
84
- writeConfig(tempDir);
85
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
86
- writeFreshHeartbeat(tempDir);
87
- const result = await createTask({
88
- template: "feature_small",
89
- goal: "Add a utility function for string truncation.",
90
- agent: "codex",
91
- repo_path: "my-repo",
92
- });
93
- const out = result;
94
- assert.ok(out.task_id.startsWith("task_"));
95
- assert.equal(out.plan_source, "template");
96
- assert.equal(out.template, "feature_small");
97
- assert.ok(existsSync(join(out.path, "status.json")));
98
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
99
- assert.equal(status.template, "feature_small");
100
- assert.equal(status.plan_source, "template");
101
- });
102
- // ── 3. plan_id source ──────────────────────────────────────────
103
- it("creates a task from a previously saved plan_id", async () => {
104
- writeConfig(tempDir);
105
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
106
- writeFreshHeartbeat(tempDir);
107
- const saved = savePlan({
108
- title: "Pre-saved plan",
109
- content: "## Goal\nDo something safe and repo-local.",
110
- });
111
- const result = await createTask({
112
- plan_id: saved.plan_id,
113
- agent: "codex",
114
- repo_path: "my-repo",
115
- });
116
- const out = result;
117
- assert.equal(out.plan_id, saved.plan_id);
118
- assert.equal(out.plan_source, "saved");
119
- assert.ok(existsSync(join(out.path, "status.json")));
120
- const status = JSON.parse(readFileSync(join(out.path, "status.json"), "utf-8"));
121
- assert.equal(status.plan_id, saved.plan_id);
122
- assert.equal(status.plan_source, "saved");
123
- });
124
- // ── 4. assess_only mode ────────────────────────────────────────
125
- it("returns a risk assessment instead of creating a task in assess_only mode", async () => {
126
- writeConfig(tempDir);
127
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
128
- const result = await createTask({
129
- inline_plan: "## Goal\nAdd a print statement to main.ts.",
130
- agent: "codex",
131
- repo_path: "my-repo",
132
- execution_mode: "assess_only",
133
- });
134
- const out = result;
135
- assert.ok(out.assessment_id, "assessment_id should be present");
136
- assert.ok(out.assessment_short_id, "assessment_short_id should be present");
137
- assert.equal(out.decision, "allow");
138
- assert.equal(out.risk_level, "low");
139
- assert.ok(out.next_tool_call, "next_tool_call should be present for allow decision");
140
- assert.equal(out.next_tool_call.name, "create_task");
141
- assert.equal(out.next_tool_call.arguments.execution_mode, "execute");
142
- assert.equal(out.next_tool_call.arguments.assessment_id, out.assessment_id);
143
- // No task directory should be created in assess_only mode
144
- assert.equal("task_id" in out, false, "assess_only output should not contain task_id");
145
- const tasksDir = join(tempDir, ".patchwarden", "tasks");
146
- if (existsSync(tasksDir)) {
147
- const taskEntries = readdirSync(tasksDir).filter((name) => name.startsWith("task_"));
148
- assert.equal(taskEntries.length, 0, "no task directory should be created in assess_only mode");
149
- }
150
- });
151
- // ── 5. agent not configured ────────────────────────────────────
152
- it("throws PatchWardenError (agent_not_configured) when the agent is not registered", async () => {
153
- writeConfig(tempDir);
154
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
155
- await assert.rejects(() => createTask({
156
- inline_plan: "## Goal\nDo something.",
157
- agent: "ghost-agent",
158
- repo_path: "my-repo",
159
- }), (err) => {
160
- assert.ok(err instanceof PatchWardenError, "should throw PatchWardenError");
161
- assert.equal(err.reason, "agent_not_configured");
162
- return true;
163
- });
164
- });
165
- // ── 6. repo_path escapes workspace ─────────────────────────────
166
- it("throws workspace_path_escape when repo_path is outside workspaceRoot", async () => {
167
- writeConfig(tempDir);
168
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
169
- await assert.rejects(() => createTask({
170
- inline_plan: "## Goal\nDo something.",
171
- agent: "codex",
172
- repo_path: "../outside-workspace",
173
- }), (err) => {
174
- assert.ok(err instanceof PatchWardenError, "should throw PatchWardenError");
175
- assert.equal(err.reason, "workspace_path_escape");
176
- return true;
177
- });
178
- });
179
- // ── 7. watcher not running ─────────────────────────────────────
180
- it("returns execution_blocked: true when watcher is not running", async () => {
181
- writeConfig(tempDir);
182
- mkdirSync(join(tempDir, "my-repo"), { recursive: true });
183
- // No watcher heartbeat file → watcher status is "missing"
184
- const result = await createTask({
185
- inline_plan: "## Goal\nDo something.",
186
- agent: "codex",
187
- repo_path: "my-repo",
188
- });
189
- const out = result;
190
- assert.equal(out.execution_blocked, true);
191
- assert.equal(out.watcher.available, false);
192
- assert.equal(out.watcher.status, "missing");
193
- assert.equal(out.next_tool_call.name, "health_check");
194
- // Task is still created (queued) despite being blocked
195
- assert.ok(existsSync(join(out.path, "status.json")));
196
- });
197
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,457 +0,0 @@
1
- import { describe, it, beforeEach, afterEach } from "node:test";
2
- import { strict as assert } from "node:assert";
3
- import { mkdtempSync, rmSync, mkdirSync, writeFileSync, utimesSync, existsSync, } from "node:fs";
4
- import { join, dirname } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { diagnoseTask } from "../../tools/diagnoseTask.js";
7
- // ── Test fixtures ──────────────────────────────────────────────────
8
- function makeConfig(workspaceRoot) {
9
- return {
10
- workspaceRoot,
11
- plansDir: ".patchwarden/plans",
12
- tasksDir: ".patchwarden/tasks",
13
- assessmentsDir: ".patchwarden/assessments",
14
- assessmentTtlSeconds: 3600,
15
- agents: { codex: { command: "codex", args: ["exec", "{prompt}"] } },
16
- allowedTestCommands: ["npm test"],
17
- repoAllowedTestCommands: {},
18
- maxReadFileBytes: 200_000,
19
- defaultTaskTimeoutSeconds: 900,
20
- maxTaskTimeoutSeconds: 3600,
21
- watcherStaleSeconds: 30,
22
- directSessionsDir: ".patchwarden/direct-sessions",
23
- directSessionTtlSeconds: 3600,
24
- directMaxPatchBytes: 200_000,
25
- directMaxFileBytes: 500_000,
26
- };
27
- }
28
- /** A PID that is guaranteed to be alive (the test process itself). */
29
- const ALIVE_PID = process.pid;
30
- /** A PID that is guaranteed to not exist (well above typical PID max). */
31
- const DEAD_PID = 999999;
32
- function isoSecondsAgo(seconds) {
33
- return new Date(Date.now() - seconds * 1000).toISOString();
34
- }
35
- /** Set a file's mtime to N seconds ago so diagnose_task sees it as stale or fresh. */
36
- function ageFile(filePath, secondsAgo) {
37
- if (!existsSync(filePath))
38
- return;
39
- const targetMs = Date.now() - secondsAgo * 1000;
40
- const targetSec = targetMs / 1000;
41
- utimesSync(filePath, targetSec, targetSec);
42
- }
43
- describe("diagnoseTask", () => {
44
- let tempDir;
45
- let config;
46
- let tasksDir;
47
- let watcherHeartbeatPath;
48
- beforeEach(() => {
49
- tempDir = mkdtempSync(join(tmpdir(), "pw-diagnose-"));
50
- config = makeConfig(tempDir);
51
- tasksDir = join(tempDir, ".patchwarden", "tasks");
52
- mkdirSync(tasksDir, { recursive: true });
53
- watcherHeartbeatPath = join(dirname(tasksDir), "watcher-heartbeat.json");
54
- });
55
- afterEach(() => {
56
- rmSync(tempDir, { recursive: true, force: true });
57
- });
58
- /**
59
- * Build a task fixture with status.json, runtime.json, and optional log
60
- * files with controlled mtimes.
61
- */
62
- function buildTask(opts) {
63
- const taskId = opts.taskId;
64
- const taskDir = join(tasksDir, taskId);
65
- mkdirSync(taskDir, { recursive: true });
66
- const statusSecondsAgo = opts.createdSecondsAgo ?? 600;
67
- const status = {
68
- task_id: taskId,
69
- status: opts.status ?? "running",
70
- phase: opts.phase ?? "executing_agent",
71
- created_at: isoSecondsAgo(statusSecondsAgo),
72
- updated_at: isoSecondsAgo(statusSecondsAgo),
73
- };
74
- if (opts.heartbeatSecondsAgo !== undefined) {
75
- status.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
76
- }
77
- writeFileSync(join(taskDir, "status.json"), JSON.stringify(status, null, 2), "utf-8");
78
- const runtime = {
79
- phase: opts.phase ?? "executing_agent",
80
- };
81
- if (opts.heartbeatSecondsAgo !== undefined) {
82
- runtime.last_heartbeat_at = isoSecondsAgo(opts.heartbeatSecondsAgo);
83
- }
84
- if (opts.childPid !== undefined) {
85
- runtime.child_pid = opts.childPid;
86
- }
87
- if (opts.childStartedSecondsAgo !== undefined) {
88
- runtime.child_started_at = isoSecondsAgo(opts.childStartedSecondsAgo);
89
- }
90
- if (opts.watcherInstanceId !== undefined) {
91
- runtime.watcher_instance_id = opts.watcherInstanceId;
92
- }
93
- if (opts.taskStartedSecondsAgo !== undefined) {
94
- runtime.task_started_at = isoSecondsAgo(opts.taskStartedSecondsAgo);
95
- }
96
- runtime.current_command = null;
97
- writeFileSync(join(taskDir, "runtime.json"), JSON.stringify(runtime, null, 2), "utf-8");
98
- if (opts.stdoutContent !== undefined) {
99
- const stdoutPath = join(taskDir, "stdout.log");
100
- writeFileSync(stdoutPath, opts.stdoutContent, "utf-8");
101
- if (opts.stdoutSecondsAgo !== undefined)
102
- ageFile(stdoutPath, opts.stdoutSecondsAgo);
103
- }
104
- if (opts.stderrContent !== undefined) {
105
- const stderrPath = join(taskDir, "stderr.log");
106
- writeFileSync(stderrPath, opts.stderrContent, "utf-8");
107
- if (opts.stderrSecondsAgo !== undefined)
108
- ageFile(stderrPath, opts.stderrSecondsAgo);
109
- }
110
- if (opts.resultMd) {
111
- writeFileSync(join(taskDir, "result.md"), "# Result\nTask completed.", "utf-8");
112
- }
113
- if (opts.testLog) {
114
- writeFileSync(join(taskDir, "test.log"), "TAP version 13\n1..1\nok 1 test", "utf-8");
115
- }
116
- if (opts.gitDiff) {
117
- writeFileSync(join(taskDir, "git.diff"), "diff --git a/foo b/foo\n", "utf-8");
118
- }
119
- if (opts.artifactManifest) {
120
- writeFileSync(join(taskDir, "artifact_manifest.json"), "{}", "utf-8");
121
- }
122
- return taskDir;
123
- }
124
- /** Write a watcher heartbeat with the given instance_id and freshness. */
125
- function writeWatcherHeartbeat(instanceId, secondsAgo = 5) {
126
- const heartbeat = {
127
- last_heartbeat_at: isoSecondsAgo(secondsAgo),
128
- pid: process.pid,
129
- instance_id: instanceId,
130
- launcher_pid: process.ppid ?? null,
131
- };
132
- writeFileSync(watcherHeartbeatPath, JSON.stringify(heartbeat, null, 2), "utf-8");
133
- }
134
- // ── 1. active_running: fresh heartbeat, watcher owns, logs growing ──
135
- it("diagnoses active_running when heartbeat fresh, watcher owns, logs growing", () => {
136
- buildTask({
137
- taskId: "task-active-001",
138
- status: "running",
139
- phase: "executing_agent",
140
- heartbeatSecondsAgo: 10, // fresh (< 120s)
141
- childPid: ALIVE_PID, // alive
142
- watcherInstanceId: "watcher-A", // matches watcher
143
- taskStartedSecondsAgo: 60,
144
- stdoutContent: "Running...\n",
145
- stdoutSecondsAgo: 5, // logs growing (< 120s)
146
- stderrContent: "stderr\n",
147
- stderrSecondsAgo: 5,
148
- });
149
- writeWatcherHeartbeat("watcher-A", 3);
150
- const result = diagnoseTask({ task_id: "task-active-001" }, config);
151
- assert.equal(result.diagnosis, "active_running");
152
- assert.equal(result.confidence, "high");
153
- assert.ok(result.safe_actions.includes("leave_unchanged"));
154
- assert.equal(result.evidence.watcher_owns_task, true);
155
- assert.equal(result.evidence.child_pid_alive, true);
156
- assert.equal(result.evidence.pid_reuse_suspected, false);
157
- });
158
- // ── 2. stale_running: heartbeat stale, PID dead, no watcher ownership, logs stale ──
159
- //
160
- // This task was started directly (e.g., via run_task MCP tool) and has no
161
- // watcher_instance_id in runtime, so isWatcherOwningTask returns
162
- // "no_runtime_record" — which does NOT trigger orphaned_running. With
163
- // heartbeat/PID/logs all stale, it falls through to stale_running.
164
- it("diagnoses stale_running when heartbeat stale, PID dead, no watcher record, logs stale", () => {
165
- buildTask({
166
- taskId: "task-stale-001",
167
- status: "running",
168
- phase: "executing_agent",
169
- heartbeatSecondsAgo: 600, // stale (> 300s)
170
- childPid: DEAD_PID, // dead
171
- // No watcherInstanceId — simulates task started directly via run_task
172
- taskStartedSecondsAgo: 700,
173
- stdoutContent: "old output\n",
174
- stdoutSecondsAgo: 600, // stale (> 300s)
175
- stderrContent: "old stderr\n",
176
- stderrSecondsAgo: 600,
177
- });
178
- const result = diagnoseTask({ task_id: "task-stale-001" }, config);
179
- assert.equal(result.diagnosis, "stale_running");
180
- assert.equal(result.confidence, "high");
181
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
182
- assert.equal(result.evidence.child_pid_alive, false);
183
- assert.equal(result.evidence.watcher_owns_task, false);
184
- assert.equal(result.evidence.watcher_ownership_reason, "no_runtime_record");
185
- });
186
- // ── 3. possibly_stale_running: heartbeat possibly stale + logs stale, PID alive ──
187
- //
188
- // heartbeat is between 120-300s (possibly stale), logs are > 300s (stale),
189
- // and PID is alive but pidReuseSuspected is false because heartbeat is not
190
- // stale enough (> 300s required). This falls into possibly_stale_running.
191
- it("diagnoses possibly_stale_running when heartbeat possibly stale and logs stale", () => {
192
- buildTask({
193
- taskId: "task-posstale-001",
194
- status: "running",
195
- phase: "executing_agent",
196
- heartbeatSecondsAgo: 200, // possibly stale (> 120, <= 300)
197
- childPid: ALIVE_PID, // alive
198
- watcherInstanceId: "watcher-A",
199
- taskStartedSecondsAgo: 300,
200
- stdoutContent: "output\n",
201
- stdoutSecondsAgo: 400, // stale (> 300s)
202
- stderrContent: "stderr\n",
203
- stderrSecondsAgo: 400,
204
- });
205
- writeWatcherHeartbeat("watcher-A", 5);
206
- const result = diagnoseTask({ task_id: "task-posstale-001" }, config);
207
- // heartbeatPossiblyStale=true && logsStale=true → possibly_stale_running
208
- assert.equal(result.diagnosis, "possibly_stale_running");
209
- assert.equal(result.confidence, "medium");
210
- assert.ok(result.safe_actions.includes("leave_unchanged"));
211
- assert.equal(result.evidence.pid_reuse_suspected, false);
212
- });
213
- // ── 4. orphaned_running: watcher does not own task (instance mismatch) ──
214
- it("diagnoses orphaned_running when watcher instance_id mismatches", () => {
215
- buildTask({
216
- taskId: "task-orphan-001",
217
- status: "running",
218
- phase: "executing_agent",
219
- heartbeatSecondsAgo: 30, // fresh
220
- childPid: ALIVE_PID,
221
- watcherInstanceId: "watcher-old", // does NOT match current watcher
222
- taskStartedSecondsAgo: 60,
223
- stdoutContent: "output\n",
224
- stdoutSecondsAgo: 10,
225
- stderrContent: "stderr\n",
226
- stderrSecondsAgo: 10,
227
- });
228
- writeWatcherHeartbeat("watcher-new", 5);
229
- const result = diagnoseTask({ task_id: "task-orphan-001" }, config);
230
- assert.equal(result.diagnosis, "orphaned_running");
231
- assert.equal(result.confidence, "high");
232
- assert.ok(result.safe_actions.includes("mark_orphaned"));
233
- assert.equal(result.evidence.watcher_owns_task, false);
234
- assert.equal(result.evidence.watcher_ownership_reason, "instance_mismatch");
235
- });
236
- // ── 5. artifact_collection_stuck: in collecting_artifacts and stuck ──
237
- it("diagnoses artifact_collection_stuck when in collecting phase and signals stale", () => {
238
- buildTask({
239
- taskId: "task-stuckart-001",
240
- status: "collecting_artifacts",
241
- phase: "collecting_artifacts",
242
- heartbeatSecondsAgo: 400, // stale
243
- childPid: DEAD_PID, // dead
244
- watcherInstanceId: "watcher-A",
245
- taskStartedSecondsAgo: 500,
246
- stdoutContent: "agent done\n",
247
- stdoutSecondsAgo: 400,
248
- stderrContent: "stderr\n",
249
- stderrSecondsAgo: 400,
250
- });
251
- writeWatcherHeartbeat("watcher-A", 5);
252
- const result = diagnoseTask({ task_id: "task-stuckart-001" }, config);
253
- assert.equal(result.diagnosis, "artifact_collection_stuck");
254
- assert.equal(result.confidence, "high");
255
- assert.ok(result.safe_actions.includes("recollect_artifacts"));
256
- assert.ok(result.safe_actions.includes("mark_failed_stale"));
257
- assert.equal(result.evidence.collecting_artifacts_phase, true);
258
- });
259
- // ── 6. done_candidate: artifacts exist while status still running ──
260
- it("diagnoses done_candidate with high confidence when result.md + test.log exist", () => {
261
- buildTask({
262
- taskId: "task-donecand-001",
263
- status: "running",
264
- phase: "executing_agent",
265
- heartbeatSecondsAgo: 400,
266
- childPid: DEAD_PID,
267
- watcherInstanceId: "watcher-old",
268
- taskStartedSecondsAgo: 500,
269
- stdoutContent: "done\n",
270
- stdoutSecondsAgo: 400,
271
- stderrContent: "stderr\n",
272
- stderrSecondsAgo: 400,
273
- resultMd: true,
274
- testLog: true,
275
- gitDiff: true,
276
- });
277
- // No watcher heartbeat — orphaned
278
- const result = diagnoseTask({ task_id: "task-donecand-001" }, config);
279
- // done_candidate takes priority over orphaned_running because artifacts exist
280
- assert.equal(result.diagnosis, "done_candidate");
281
- assert.equal(result.confidence, "high");
282
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
283
- assert.equal(result.evidence.has_result_md, true);
284
- assert.equal(result.evidence.has_test_log, true);
285
- });
286
- it("diagnoses done_candidate with medium confidence when only result.md + collecting phase", () => {
287
- buildTask({
288
- taskId: "task-donecand-002",
289
- status: "collecting_artifacts",
290
- phase: "collecting_artifacts",
291
- heartbeatSecondsAgo: 30, // fresh — not stuck
292
- childPid: ALIVE_PID,
293
- watcherInstanceId: "watcher-A",
294
- taskStartedSecondsAgo: 100,
295
- stdoutContent: "done\n",
296
- stdoutSecondsAgo: 30,
297
- stderrContent: "stderr\n",
298
- stderrSecondsAgo: 30,
299
- resultMd: true,
300
- // No test.log/git.diff → medium signal
301
- });
302
- writeWatcherHeartbeat("watcher-A", 5);
303
- const result = diagnoseTask({ task_id: "task-donecand-002" }, config);
304
- assert.equal(result.diagnosis, "done_candidate");
305
- assert.equal(result.confidence, "medium");
306
- assert.ok(result.safe_actions.includes("mark_done_by_agent"));
307
- assert.ok(result.safe_actions.includes("collect_artifacts"));
308
- });
309
- // ── 7. unknown: insufficient evidence ──
310
- //
311
- // heartbeat is fresh but PID is dead and logs are stale — no single
312
- // branch matches with enough confidence, so it falls through to unknown.
313
- it("diagnoses unknown when signals conflict (fresh heartbeat but dead PID + stale logs)", () => {
314
- buildTask({
315
- taskId: "task-unknown-001",
316
- status: "running",
317
- phase: "executing_agent",
318
- heartbeatSecondsAgo: 10, // fresh — doesn't hit stale paths
319
- childPid: DEAD_PID, // dead — but heartbeat says fresh
320
- watcherInstanceId: "watcher-A",
321
- taskStartedSecondsAgo: 60,
322
- stdoutContent: "running\n",
323
- stdoutSecondsAgo: 400, // stale — but heartbeat says fresh
324
- stderrContent: "stderr\n",
325
- stderrSecondsAgo: 400,
326
- });
327
- writeWatcherHeartbeat("watcher-A", 5);
328
- const result = diagnoseTask({ task_id: "task-unknown-001" }, config);
329
- // Fresh heartbeat means not stale_running / possibly_stale_running.
330
- // Dead PID + stale logs means not active_running.
331
- // Conflicting signals → unknown.
332
- assert.equal(result.diagnosis, "unknown");
333
- assert.equal(result.confidence, "low");
334
- assert.ok(result.safe_actions.includes("leave_unchanged"));
335
- });
336
- // ── 8. PID reuse case: PID alive but everything else stale ──
337
- //
338
- // This is the conservative safety check: even though process.kill(pid, 0)
339
- // returns true, we refuse to call the task "active" because heartbeat and
340
- // logs are all stale. The PID may have been reused by the OS for an
341
- // unrelated process.
342
- it("refuses active_running on PID reuse: PID alive but heartbeat + logs stale", () => {
343
- buildTask({
344
- taskId: "task-pidreuse-001",
345
- status: "running",
346
- phase: "executing_agent",
347
- heartbeatSecondsAgo: 600, // very stale
348
- childPid: ALIVE_PID, // alive — but probably reused!
349
- watcherInstanceId: "watcher-A",
350
- taskStartedSecondsAgo: 700,
351
- stdoutContent: "old output\n",
352
- stdoutSecondsAgo: 600, // very stale
353
- stderrContent: "old stderr\n",
354
- stderrSecondsAgo: 600,
355
- });
356
- writeWatcherHeartbeat("watcher-A", 5);
357
- const result = diagnoseTask({ task_id: "task-pidreuse-001" }, config);
358
- // Must NOT be active_running — that would be the unsafe conclusion.
359
- assert.notEqual(result.diagnosis, "active_running");
360
- assert.equal(result.diagnosis, "possibly_stale_running");
361
- assert.equal(result.confidence, "medium");
362
- assert.equal(result.evidence.pid_reuse_suspected, true);
363
- assert.equal(result.evidence.child_pid_alive, true);
364
- assert.ok(result.reasons.some((r) => r.includes("PID reuse")));
365
- });
366
- // ── 9. terminal: already-terminal statuses return terminal diagnosis ──
367
- it("returns terminal diagnosis for done status", () => {
368
- buildTask({
369
- taskId: "pw-terminal-done",
370
- status: "done",
371
- phase: "completed",
372
- createdSecondsAgo: 1000,
373
- });
374
- const result = diagnoseTask({ task_id: "pw-terminal-done" }, config);
375
- assert.equal(result.diagnosis, "terminal");
376
- assert.equal(result.confidence, "high");
377
- assert.ok(result.safe_actions.includes("leave_unchanged"));
378
- });
379
- it("returns terminal diagnosis for failed_stale status (v0.7.0 status)", () => {
380
- buildTask({
381
- taskId: "pw-terminal-failed-stale",
382
- status: "failed_stale",
383
- phase: "failed_stale",
384
- createdSecondsAgo: 1000,
385
- });
386
- const result = diagnoseTask({ task_id: "pw-terminal-failed-stale" }, config);
387
- assert.equal(result.diagnosis, "terminal");
388
- });
389
- it("returns terminal diagnosis for orphaned status (v0.7.0 status)", () => {
390
- buildTask({
391
- taskId: "pw-terminal-orphaned",
392
- status: "orphaned",
393
- phase: "orphaned",
394
- createdSecondsAgo: 1000,
395
- });
396
- const result = diagnoseTask({ task_id: "pw-terminal-orphaned" }, config);
397
- assert.equal(result.diagnosis, "terminal");
398
- });
399
- it("returns terminal diagnosis for done_by_agent status (v0.7.0 status)", () => {
400
- buildTask({
401
- taskId: "pw-terminal-done-by-agent",
402
- status: "done_by_agent",
403
- phase: "done_by_agent",
404
- createdSecondsAgo: 1000,
405
- });
406
- const result = diagnoseTask({ task_id: "pw-terminal-done-by-agent" }, config);
407
- assert.equal(result.diagnosis, "terminal");
408
- });
409
- // ── include_logs flag ──
410
- it("includes redacted log tails when include_logs=true", () => {
411
- buildTask({
412
- taskId: "task-logs-001",
413
- status: "running",
414
- phase: "executing_agent",
415
- heartbeatSecondsAgo: 10,
416
- childPid: ALIVE_PID,
417
- watcherInstanceId: "watcher-A",
418
- taskStartedSecondsAgo: 60,
419
- stdoutContent: "line1\nline2 with Bearer abcdefghijklmnop123456\n",
420
- stdoutSecondsAgo: 5,
421
- stderrContent: "stderr line\n",
422
- stderrSecondsAgo: 5,
423
- });
424
- writeWatcherHeartbeat("watcher-A", 3);
425
- const result = diagnoseTask({ task_id: "task-logs-001", include_logs: true }, config);
426
- assert.ok(result.logs !== null);
427
- assert.ok(result.logs.stdout_tail !== null);
428
- assert.ok(result.logs.stderr_tail !== null);
429
- // The bearer token must be redacted
430
- assert.ok(!result.logs.stdout_tail.includes("abcdefghijklmnop123456"));
431
- assert.ok(result.logs.stdout_tail.includes("[REDACTED]"));
432
- });
433
- it("omits log tails when include_logs is false (default)", () => {
434
- buildTask({
435
- taskId: "task-nologs-001",
436
- status: "running",
437
- phase: "executing_agent",
438
- heartbeatSecondsAgo: 10,
439
- childPid: ALIVE_PID,
440
- watcherInstanceId: "watcher-A",
441
- taskStartedSecondsAgo: 60,
442
- stdoutContent: "line1\n",
443
- stdoutSecondsAgo: 5,
444
- stderrContent: "stderr\n",
445
- stderrSecondsAgo: 5,
446
- });
447
- writeWatcherHeartbeat("watcher-A", 3);
448
- const result = diagnoseTask({ task_id: "task-nologs-001" }, config);
449
- assert.equal(result.logs, null);
450
- });
451
- // ── Error case: task not found ──
452
- it("throws when task does not exist", () => {
453
- // guardReadPath throws "File not found" before the existsSync check
454
- // produces "Task not found" — both indicate the task doesn't exist.
455
- assert.throws(() => diagnoseTask({ task_id: "non-existent-task" }, config), /(Task not found|File not found)/);
456
- });
457
- });
@@ -1 +0,0 @@
1
- export {};