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,251 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { atomicWriteJsonFileSync } from "./atomicFile.js";
5
+
6
+ export interface LockedJsonMutation<T extends object, R> {
7
+ next?: T;
8
+ result: R;
9
+ }
10
+
11
+ export interface LockedJsonOptions {
12
+ waitMs?: number;
13
+ corruptLockStaleMs?: number;
14
+ busyError?: () => Error;
15
+ }
16
+
17
+ const waitArray = new Int32Array(new SharedArrayBuffer(4));
18
+ const lockReleaseRetryCodes = new Set(["EACCES", "EBUSY", "ENOTEMPTY", "EPERM"]);
19
+ const lockReleaseRetryMs = 5_000;
20
+ const lockReleaseRetryDelayMs = 10;
21
+ const deadOwnerGraceMs = 250;
22
+ const lockOwnerFile = "owner.json";
23
+
24
+ interface AcquiredLock {
25
+ path: string;
26
+ owner: string;
27
+ }
28
+
29
+ interface LockRecord {
30
+ owner?: string;
31
+ pid?: number;
32
+ }
33
+
34
+ export function readJsonObjectFileSync<T extends object>(path: string): T {
35
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf-8"));
36
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
37
+ throw new Error(`Expected a JSON object in ${path}`);
38
+ }
39
+ return parsed as T;
40
+ }
41
+
42
+ export function mutateLockedJsonFileSync<T extends object, R>(
43
+ path: string,
44
+ mutation: (current: T) => LockedJsonMutation<T, R>,
45
+ options: LockedJsonOptions = {},
46
+ ): R {
47
+ return withFileLockSync(path, () => {
48
+ const current = readJsonObjectFileSync<T>(path);
49
+ const outcome = mutation(current);
50
+ if (outcome.next) atomicWriteJsonFileSync(path, outcome.next);
51
+ return outcome.result;
52
+ }, options);
53
+ }
54
+
55
+ /** Serialize an arbitrary same-file read/modify/write sequence across processes. */
56
+ export function withFileLockSync<R>(
57
+ path: string,
58
+ action: () => R,
59
+ options: LockedJsonOptions = {},
60
+ ): R {
61
+ const lock = acquireLock(path, options);
62
+ try {
63
+ return action();
64
+ } finally {
65
+ releaseLock(lock.path, lock.owner);
66
+ }
67
+ }
68
+
69
+ /** Hold the same cross-process lock for the lifetime of an async operation. */
70
+ export async function withFileLock<R>(
71
+ path: string,
72
+ action: () => Promise<R>,
73
+ options: LockedJsonOptions = {},
74
+ ): Promise<R> {
75
+ // Async callers must not use the synchronous Atomics.wait loop: a second
76
+ // request in the same Node event loop would otherwise block the first
77
+ // request from finishing its awaited mutation and releasing the lock.
78
+ const lock = await acquireLockAsync(path, options);
79
+ try {
80
+ return await action();
81
+ } finally {
82
+ releaseLock(lock.path, lock.owner);
83
+ }
84
+ }
85
+
86
+ async function acquireLockAsync(path: string, options: LockedJsonOptions): Promise<AcquiredLock> {
87
+ const lockPath = `${path}.lock`;
88
+ const owner = `${process.pid}-${randomBytes(8).toString("hex")}`;
89
+
90
+ const deadline = Date.now() + (options.waitMs ?? 2000);
91
+ while (true) {
92
+ if (tryCreateLock(lockPath, owner)) return { path: lockPath, owner };
93
+ if (
94
+ removeStaleLock(lockPath, options.corruptLockStaleMs ?? 30_000) &&
95
+ tryCreateLock(lockPath, owner)
96
+ ) {
97
+ return { path: lockPath, owner };
98
+ }
99
+ if (Date.now() >= deadline) {
100
+ throw options.busyError?.() ?? new Error(`JSON file is busy: ${path}`);
101
+ }
102
+ await new Promise<void>((resolve) => setTimeout(resolve, lockReleaseRetryDelayMs));
103
+ }
104
+ }
105
+
106
+ function acquireLock(path: string, options: LockedJsonOptions): AcquiredLock {
107
+ const lockPath = `${path}.lock`;
108
+ const owner = `${process.pid}-${randomBytes(8).toString("hex")}`;
109
+
110
+ const deadline = Date.now() + (options.waitMs ?? 2000);
111
+ while (true) {
112
+ if (tryCreateLock(lockPath, owner)) return { path: lockPath, owner };
113
+ if (
114
+ removeStaleLock(lockPath, options.corruptLockStaleMs ?? 30_000) &&
115
+ tryCreateLock(lockPath, owner)
116
+ ) {
117
+ return { path: lockPath, owner };
118
+ }
119
+ if (Date.now() >= deadline) {
120
+ throw options.busyError?.() ?? new Error(`JSON file is busy: ${path}`);
121
+ }
122
+ Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
123
+ }
124
+ }
125
+
126
+ function tryCreateLock(lockPath: string, owner: string): boolean {
127
+ try {
128
+ mkdirSync(lockPath, { mode: 0o700 });
129
+ } catch (error) {
130
+ const code = errorCode(error);
131
+ if (code === "EEXIST" || lockReleaseRetryCodes.has(code || "")) return false;
132
+ throw error;
133
+ }
134
+
135
+ try {
136
+ writeFileSync(join(lockPath, lockOwnerFile), JSON.stringify({
137
+ owner,
138
+ pid: process.pid,
139
+ created_at: new Date().toISOString(),
140
+ }), { encoding: "utf-8", flag: "wx", mode: 0o600 });
141
+ return true;
142
+ } catch (error) {
143
+ try { rmSync(lockPath, { recursive: true, force: true }); } catch { /* leave fail-closed */ }
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ function releaseLock(lockPath: string, owner: string): void {
149
+ const detachDeadline = Date.now() + lockReleaseRetryMs;
150
+ const releasePath = `${lockPath}.release-${owner}`;
151
+
152
+ // Rename the owned lock directory out of the active lock path before
153
+ // recursive cleanup. A new contender can then create lockPath immediately,
154
+ // while Windows rimraf retries cannot delete that replacement lock.
155
+ while (true) {
156
+ const currentOwner = readLock(lockPath)?.owner;
157
+ if (currentOwner !== owner) {
158
+ if (!existsSync(lockPath)) return;
159
+ if (currentOwner !== undefined) return;
160
+ if (Date.now() >= detachDeadline) {
161
+ throw new Error(`Unable to verify lock ownership before release: ${lockPath}`);
162
+ }
163
+ Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
164
+ continue;
165
+ }
166
+ try {
167
+ renameSync(lockPath, releasePath);
168
+ break;
169
+ } catch (error) {
170
+ const code = errorCode(error);
171
+ if (code === "ENOENT") return;
172
+ if (!code || !lockReleaseRetryCodes.has(code) || Date.now() >= detachDeadline) throw error;
173
+ Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
174
+ }
175
+ }
176
+
177
+ // Cleanup is no longer ownership-sensitive after the atomic detach, so give
178
+ // it an independent retry budget instead of consuming whatever remained
179
+ // from a slow antivirus-delayed rename.
180
+ const cleanupDeadline = Date.now() + lockReleaseRetryMs;
181
+ while (true) {
182
+ try {
183
+ rmSync(releasePath, { recursive: true, force: false });
184
+ return;
185
+ } catch (error) {
186
+ const code = errorCode(error);
187
+ if (code === "ENOENT") return;
188
+ if (!code || !lockReleaseRetryCodes.has(code) || Date.now() >= cleanupDeadline) throw error;
189
+ Atomics.wait(waitArray, 0, 0, lockReleaseRetryDelayMs);
190
+ }
191
+ }
192
+ }
193
+
194
+ function removeStaleLock(lockPath: string, corruptLockStaleMs: number): boolean {
195
+ const lock = readLock(lockPath);
196
+ let ageMs: number;
197
+ try {
198
+ ageMs = Date.now() - statSync(lockPath).mtimeMs;
199
+ } catch (error) {
200
+ if (errorCode(error) === "ENOENT") return true;
201
+ throw error;
202
+ }
203
+ const stale = typeof lock?.pid === "number"
204
+ ? !processIsAlive(lock.pid) && ageMs >= deadOwnerGraceMs
205
+ : ageMs >= corruptLockStaleMs;
206
+ if (!stale) return false;
207
+
208
+ // Re-read immediately before removal so one stale-lock contender cannot
209
+ // delete a replacement acquired after its first observation.
210
+ const latest = readLock(lockPath);
211
+ if (lock?.owner) {
212
+ if (latest?.owner !== lock.owner || latest.pid !== lock.pid) return false;
213
+ } else if (latest) {
214
+ return false;
215
+ }
216
+ try {
217
+ rmSync(lockPath, { recursive: true, force: false });
218
+ return true;
219
+ } catch (error) {
220
+ if (errorCode(error) === "ENOENT") return true;
221
+ if (lockReleaseRetryCodes.has(errorCode(error) || "")) return false;
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ function readLock(path: string): LockRecord | null {
227
+ try {
228
+ const metadata = statSync(path);
229
+ const recordPath = metadata.isDirectory() ? join(path, lockOwnerFile) : path;
230
+ return readJsonObjectFileSync<LockRecord>(recordPath);
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+
236
+ function processIsAlive(pid: number): boolean {
237
+ try {
238
+ process.kill(pid, 0);
239
+ return true;
240
+ } catch (error) {
241
+ // Only ESRCH proves the process does not exist. Access-denied and unknown
242
+ // platform errors must keep the lock fail-closed.
243
+ return errorCode(error) !== "ESRCH";
244
+ }
245
+ }
246
+
247
+ function errorCode(error: unknown): string | null {
248
+ return error && typeof error === "object" && "code" in error
249
+ ? String((error as NodeJS.ErrnoException).code || "")
250
+ : null;
251
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Cross-platform abstractions for PatchWarden.
3
+ *
4
+ * Centralizes platform detection, path separators, the platform-specific null
5
+ * device, and a path-containment helper that works correctly on Windows where
6
+ * forward and backward slashes may be mixed. Use these helpers instead of
7
+ * hard-coded Unix assumptions so the runtime guard and change capture behave
8
+ * consistently across Windows and Unix hosts.
9
+ */
10
+ import { sep, relative, isAbsolute } from "node:path";
11
+
12
+ export const isWindows: boolean = process.platform === "win32";
13
+
14
+ export const pathSep: string = sep;
15
+
16
+ export const nullDevice: string = isWindows ? "NUL" : "/dev/null";
17
+
18
+ export function isSamePath(left: string, right: string): boolean {
19
+ return relative(left, right) === "";
20
+ }
21
+
22
+ /**
23
+ * Returns true when `child` is located inside `parent` (or equals it is NOT
24
+ * treated as a child here — callers that need equality should check it
25
+ * separately). Uses `path.relative` so mixed Windows separators are normalized
26
+ * before comparison, unlike `String.prototype.startsWith`.
27
+ */
28
+ export function isPathChildOf(child: string, parent: string): boolean {
29
+ const rel = relative(parent, child);
30
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
31
+ }
@@ -0,0 +1,12 @@
1
+ /** Canonical JSON with object keys sorted recursively. */
2
+ export function stableJsonStringify(value: unknown): string {
3
+ if (Array.isArray(value)) {
4
+ return `[${value.map(stableJsonStringify).join(",")}]`;
5
+ }
6
+ if (value !== null && typeof value === "object") {
7
+ const entries = Object.entries(value as Record<string, unknown>)
8
+ .sort(([left], [right]) => left.localeCompare(right));
9
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJsonStringify(entry)}`).join(",")}}`;
10
+ }
11
+ return JSON.stringify(value);
12
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,3 @@
1
- export const PATCHWARDEN_VERSION = "1.5.0";
2
- export const TOOL_SCHEMA_EPOCH = "2026-07-05-v13";
1
+ export const PATCHWARDEN_VERSION = "1.6.1";
2
+ export const TOOL_SCHEMA_EPOCH = "2026-07-19-v15";
3
+ export const ARTIFACT_SCHEMA_VERSION = TOOL_SCHEMA_EPOCH;
@@ -2,10 +2,11 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { getConfig, getTasksDir, type PatchWardenConfig } from "./config.js";
4
4
 
5
- export type WatcherState = "healthy" | "stale" | "missing" | "unreadable";
5
+ export type WatcherState = "healthy" | "degraded" | "stale" | "missing" | "unreadable";
6
6
  export type PendingReason =
7
7
  | "queued_waiting_for_watcher"
8
8
  | "queued_but_watcher_stale"
9
+ | "queued_but_watcher_degraded"
9
10
  | "queued_but_watcher_missing"
10
11
  | "queued_but_watcher_unreadable"
11
12
  | "agent_running"
@@ -75,6 +76,22 @@ export function readWatcherStatus(
75
76
  if (!Number.isFinite(heartbeatMs)) throw new Error("invalid heartbeat timestamp");
76
77
  const ageMs = Math.max(0, nowMs - heartbeatMs);
77
78
  const ageSeconds = Math.round(ageMs / 1000);
79
+ if (data.status === "degraded") {
80
+ return {
81
+ status: "degraded",
82
+ available: false,
83
+ stale_after_seconds: staleAfterSeconds,
84
+ last_heartbeat_at: String(data.last_heartbeat_at),
85
+ heartbeat_age_seconds: ageSeconds,
86
+ heartbeat_pid: Number.isInteger(Number(data.pid)) ? Number(data.pid) : null,
87
+ instance_id: typeof data.instance_id === "string" ? data.instance_id : null,
88
+ launcher_pid: Number.isInteger(Number(data.launcher_pid)) ? Number(data.launcher_pid) : null,
89
+ reason: typeof data.last_error === "string" && data.last_error
90
+ ? `Watcher is degraded after repeated tick failures: ${data.last_error}`
91
+ : "Watcher is degraded after repeated tick failures. Restart the PatchWarden watcher.",
92
+ activity: null,
93
+ };
94
+ }
78
95
  const healthy = ageMs < staleAfterSeconds * 1000;
79
96
  if (healthy) {
80
97
  return {
@@ -186,6 +203,7 @@ export function derivePendingReason(
186
203
  watcher: WatcherStatusSnapshot
187
204
  ): PendingReason {
188
205
  if (task.status === "pending") {
206
+ if (watcher.status === "degraded") return "queued_but_watcher_degraded";
189
207
  if (watcher.status === "stale") return "queued_but_watcher_stale";
190
208
  if (watcher.status === "missing") return "queued_but_watcher_missing";
191
209
  if (watcher.status === "unreadable") return "queued_but_watcher_unreadable";
@@ -0,0 +1,11 @@
1
+ (function () {
2
+ if (!window.patchwardenDesktop) return;
3
+ var html = document.documentElement;
4
+ var theme = localStorage.getItem("patchwarden.desktop.theme") || "system";
5
+ var dark = theme === "dark" || (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
6
+ html.classList.add("pw-desktop", dark ? "dark" : "light");
7
+ html.dataset.theme = dark ? "dark" : "light";
8
+ html.style.backgroundColor = dark ? "#0f1413" : "#eef2f1";
9
+ var language = localStorage.getItem("patchwarden.language");
10
+ if (language === "zh-CN" || language === "en") html.lang = language;
11
+ })();
@@ -0,0 +1,36 @@
1
+ (function () {
2
+ var api = window.patchwardenDesktop;
3
+ if (!api) return;
4
+
5
+ var html = document.documentElement;
6
+ html.classList.add("pw-desktop");
7
+
8
+ var nativeFetch = window.fetch.bind(window);
9
+ window.fetch = function (input, init) {
10
+ var options = init || {};
11
+ var method = String(options.method || "GET").toUpperCase();
12
+ if (method !== "GET" || options.signal) return nativeFetch(input, options);
13
+ var controller = new AbortController();
14
+ var timer = window.setTimeout(function () { controller.abort(); }, 10000);
15
+ return nativeFetch(input, Object.assign({}, options, { signal: controller.signal })).catch(function (error) {
16
+ if (error && error.name === "AbortError") throw new Error("请求超过 10 秒,请重试");
17
+ throw error;
18
+ }).finally(function () { window.clearTimeout(timer); });
19
+ };
20
+
21
+ function applyTheme(theme) {
22
+ var dark = theme === "dark" || (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
23
+ html.dataset.theme = dark ? "dark" : "light";
24
+ html.classList.toggle("dark", dark);
25
+ html.classList.toggle("light", !dark);
26
+ localStorage.setItem("patchwarden.desktop.theme", theme || "system");
27
+ html.style.backgroundColor = dark ? "#0f1413" : "#eef2f1";
28
+ }
29
+
30
+ window.patchwardenApplyTheme = applyTheme;
31
+
32
+ api.getPreferences().then(function (preferences) { applyTheme(preferences.theme || "system"); });
33
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function () {
34
+ api.getPreferences().then(function (preferences) { if (preferences.theme === "system") applyTheme("system"); });
35
+ });
36
+ })();
package/ui/desktop.css ADDED
@@ -0,0 +1,160 @@
1
+ /* Shared responsive shell plus desktop-only theme refinements. */
2
+ .desktop-only-nav { display: none !important; }
3
+
4
+ @view-transition { navigation: auto; }
5
+ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 120ms; animation-timing-function: ease-out; }
6
+ @media (prefers-reduced-motion: reduce) {
7
+ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0s; }
8
+ }
9
+
10
+ html, body { max-width: 100%; overflow-x: hidden; }
11
+ .pw-dashboard-toolbar {
12
+ display: grid !important;
13
+ grid-template-columns: auto minmax(180px, 1fr) auto;
14
+ align-items: center;
15
+ gap: 10px;
16
+ min-width: 0;
17
+ }
18
+ .pw-toolbar-actions, .pw-toolbar-links { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
19
+ .pw-toolbar-repo { min-width: 0; }
20
+ .pw-toolbar-repo select { width: 100%; min-width: 0 !important; max-width: 100%; }
21
+ @media (min-width: 1121px) {
22
+ .pw-dashboard-toolbar { grid-template-columns: max-content minmax(160px, 1fr) max-content; }
23
+ .pw-toolbar-actions, .pw-toolbar-links { flex-wrap: nowrap; }
24
+ .pw-dashboard-toolbar .pw-toolbar-actions button,
25
+ .pw-dashboard-toolbar .pw-toolbar-links a,
26
+ .pw-dashboard-toolbar .pw-toolbar-links button { padding-left: 9px !important; padding-right: 9px !important; }
27
+ }
28
+
29
+ .pw-app-shell { display: flex; height: 100vh; margin: 0; color: var(--pw-text-primary); background: var(--pw-bg-elevated); font-family: var(--pw-font-sans); }
30
+ .pw-sidebar { display: flex; flex: 0 0 220px; flex-direction: column; height: 100vh; background: var(--pw-bg); border-right: 1px solid var(--pw-border); }
31
+ .pw-brand { display: flex; align-items: center; gap: 10px; min-height: 58px; padding: 0 18px; border-bottom: 1px solid var(--pw-border-subtle); }
32
+ .pw-brand svg { width: 21px; color: var(--pw-accent); }
33
+ .pw-sidebar nav { display: flex; flex-direction: column; gap: 4px; padding: 14px 10px; overflow-y: auto; }
34
+ .pw-sidebar nav a { display: flex; align-items: center; gap: 10px; min-height: 38px; padding: 0 10px; color: var(--pw-text-secondary); text-decoration: none; border-radius: 6px; }
35
+ .pw-sidebar nav a:hover { color: var(--pw-text-primary); background: var(--pw-bg-hover); }
36
+ .pw-sidebar nav a[data-active="true"] { color: var(--pw-accent); background: var(--pw-accent-subtle); border-left: 2px solid var(--pw-accent); }
37
+ .pw-sidebar nav svg { width: 17px; flex: none; }
38
+ .pw-page { display: flex; flex: 1; min-width: 0; flex-direction: column; height: 100vh; }
39
+ .pw-page-header { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 74px; padding: 12px 24px; background: var(--pw-bg-surface); border-bottom: 1px solid var(--pw-border); }
40
+ .pw-page-header h1 { margin: 0; font-size: 19px; letter-spacing: 0; }
41
+ .pw-page-header p { margin: 4px 0 0; color: var(--pw-text-secondary); font-size: 12px; }
42
+ .pw-page-header select { min-height: 34px; max-width: 190px; padding: 0 28px 0 10px; color: var(--pw-text-primary); background: var(--pw-bg-elevated); border: 1px solid var(--pw-border); border-radius: 6px; }
43
+ .getting-started-main { flex: 1; min-width: 0; padding: 22px 24px; overflow-y: auto; }
44
+ .readiness-list { max-width: 980px; background: var(--pw-bg-surface); border: 1px solid var(--pw-border); border-radius: 7px; }
45
+ .readiness-item { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 15px; min-height: 112px; padding: 18px; border-bottom: 1px solid var(--pw-border-subtle); }
46
+ .readiness-item:last-child { border-bottom: 0; }
47
+ .check-index { display: grid; width: 30px; height: 30px; place-items: center; color: var(--pw-text-secondary); border: 1px solid var(--pw-border); border-radius: 50%; font: 600 12px var(--pw-font-mono); }
48
+ .readiness-item[data-state="ready"] .check-index { color: var(--pw-text-inverse); background: var(--pw-state-success); border-color: var(--pw-state-success); }
49
+ .check-heading { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
50
+ .check-heading h2 { margin: 0; font-size: 14px; letter-spacing: 0; }
51
+ .readiness-item p { margin: 7px 0 0; color: var(--pw-text-secondary); font-size: 12px; line-height: 1.5; }
52
+ .readiness-item .reason-detail { color: var(--pw-state-error); }
53
+ .check-status { padding: 3px 7px; color: var(--pw-state-warning); background: var(--pw-state-warning-bg); border-radius: 4px; font-size: 11px; }
54
+ .readiness-item[data-state="ready"] .check-status { color: var(--pw-state-success); background: var(--pw-state-success-bg); }
55
+ .check-status.manual { color: var(--pw-text-secondary); background: var(--pw-bg-hover); }
56
+ .primary-command { display: none; align-items: center; justify-content: center; min-height: 36px; padding: 0 13px; color: var(--pw-text-inverse); background: var(--pw-accent); border: 1px solid var(--pw-accent); border-radius: 6px; text-decoration: none; font: 600 12px var(--pw-font-sans); cursor: pointer; white-space: nowrap; }
57
+ .readiness-item[data-primary-action="true"] .primary-command { display: inline-flex; }
58
+ .primary-command:disabled { cursor: wait; opacity: .62; }
59
+ .guide-band { max-width: 980px; margin-top: 16px; padding: 18px 0; }
60
+ .guide-band h2 { margin: 0 0 10px; font-size: 13px; }
61
+ .guide-links { display: flex; gap: 16px; flex-wrap: wrap; }
62
+ .guide-links a { color: var(--pw-accent); font-size: 12px; }
63
+ .pw-toast { position: fixed; right: 20px; bottom: 20px; max-width: min(360px, calc(100vw - 40px)); padding: 10px 13px; color: var(--pw-text-inverse); background: var(--pw-text-primary); border-radius: 6px; opacity: 0; pointer-events: none; transform: translateY(8px); transition: .18s ease; }
64
+ .pw-toast.visible { opacity: 1; transform: translateY(0); }
65
+ .pw-language-switcher { min-height: 34px; max-width: 190px; margin-left: auto; padding: 0 28px 0 10px; color: var(--pw-text-primary); background: var(--pw-bg-elevated); border: 1px solid var(--pw-border); border-radius: 6px; }
66
+ aside > .pw-language-switcher { width: calc(100% - 20px); max-width: none; margin: 10px; flex: none; }
67
+
68
+ html.pw-desktop {
69
+ --pw-bg: #eef2f1;
70
+ --pw-bg-elevated: #f7f9f8;
71
+ --pw-bg-surface: #ffffff;
72
+ --pw-bg-hover: #e6ece9;
73
+ --pw-bg-active: #dbe5e1;
74
+ --pw-border: #cad4d0;
75
+ --pw-border-subtle: #dce3e0;
76
+ --pw-border-focus: #167a58;
77
+ --pw-text-primary: #192521;
78
+ --pw-text-secondary: #52625c;
79
+ --pw-text-tertiary: #74837e;
80
+ --pw-text-inverse: #ffffff;
81
+ --pw-text-accent: #167a58;
82
+ --pw-accent: #167a58;
83
+ --pw-accent-hover: #105f45;
84
+ --pw-accent-muted: rgba(22, 122, 88, 0.11);
85
+ --pw-accent-subtle: rgba(22, 122, 88, 0.06);
86
+ --pw-code-bg: #18201e;
87
+ color-scheme: light;
88
+ }
89
+
90
+ html.pw-desktop.dark {
91
+ --pw-bg: #0f1413;
92
+ --pw-bg-elevated: #141a18;
93
+ --pw-bg-surface: #1b2321;
94
+ --pw-bg-hover: #25302d;
95
+ --pw-bg-active: #2c3935;
96
+ --pw-border: #34423e;
97
+ --pw-border-subtle: #293530;
98
+ --pw-text-primary: #edf4f1;
99
+ --pw-text-secondary: #a0afa9;
100
+ --pw-text-tertiary: #71817b;
101
+ --pw-accent: #43c798;
102
+ --pw-accent-hover: #60d8ad;
103
+ --pw-text-accent: #43c798;
104
+ --pw-accent-muted: rgba(67, 199, 152, 0.13);
105
+ --pw-accent-subtle: rgba(67, 199, 152, 0.07);
106
+ color-scheme: dark;
107
+ }
108
+
109
+ html.pw-desktop body { background: var(--pw-bg) !important; color: var(--pw-text-primary) !important; }
110
+ html.pw-desktop .desktop-only-nav { display: flex !important; }
111
+ html.pw-desktop aside { width: 220px !important; background: var(--pw-bg) !important; }
112
+ html.pw-desktop aside > div:first-child { padding-top: 17px !important; padding-bottom: 17px !important; }
113
+ html.pw-desktop header { min-height: 58px; background: var(--pw-bg-surface) !important; }
114
+ html.pw-desktop main { background: var(--pw-bg-elevated) !important; }
115
+ html.pw-desktop section,
116
+ html.pw-desktop table,
117
+ html.pw-desktop .bg-white,
118
+ html.pw-desktop .bg-gray-50,
119
+ html.pw-desktop .bg-slate-50 { background-color: var(--pw-bg-surface) !important; }
120
+ html.pw-desktop input,
121
+ html.pw-desktop select,
122
+ html.pw-desktop textarea { background-color: var(--pw-bg-elevated) !important; color: var(--pw-text-primary) !important; border-color: var(--pw-border) !important; }
123
+ html.pw-desktop .text-gray-900,
124
+ html.pw-desktop .text-slate-900 { color: var(--pw-text-primary) !important; }
125
+ html.pw-desktop .text-gray-500,
126
+ html.pw-desktop .text-gray-600,
127
+ html.pw-desktop .text-slate-500,
128
+ html.pw-desktop .text-slate-600 { color: var(--pw-text-secondary) !important; }
129
+ html.pw-desktop .border-gray-200,
130
+ html.pw-desktop .border-slate-200 { border-color: var(--pw-border) !important; }
131
+ html.pw-desktop [data-nav-key] { border-radius: 6px !important; }
132
+ html.pw-desktop [data-nav-key][data-active="true"] { border-left-width: 2px !important; }
133
+ html.pw-desktop button, html.pw-desktop select, html.pw-desktop input { border-radius: 6px !important; letter-spacing: 0; }
134
+ html.pw-desktop .rounded-lg { border-radius: 7px !important; }
135
+ html.pw-desktop .rounded-xl { border-radius: 8px !important; }
136
+ html.pw-desktop [style*="box-shadow"] { box-shadow: none !important; }
137
+ html.pw-desktop .pw-desktop-badge { color: var(--pw-accent); background: var(--pw-accent-subtle); border: 1px solid var(--pw-border); }
138
+
139
+ @media (max-width: 1050px) {
140
+ html.pw-desktop aside { width: 196px !important; }
141
+ html.pw-desktop main { padding: 18px !important; }
142
+ }
143
+
144
+ @media (max-width: 1120px) {
145
+ .pw-dashboard-toolbar { grid-template-columns: minmax(0, 1fr) auto; }
146
+ .pw-toolbar-actions { grid-column: 1 / -1; }
147
+ .pw-toolbar-links { justify-content: flex-end; }
148
+ }
149
+
150
+ @media (max-width: 980px) {
151
+ .pw-sidebar { flex-basis: 190px; }
152
+ .pw-page-header, .getting-started-main { padding-left: 18px; padding-right: 18px; }
153
+ .readiness-item { grid-template-columns: 34px minmax(0, 1fr); }
154
+ .readiness-item .primary-command { grid-column: 2; justify-self: start; }
155
+ }
156
+
157
+ @media (max-width: 760px) {
158
+ .pw-dashboard-toolbar { grid-template-columns: 1fr; }
159
+ .pw-toolbar-actions, .pw-toolbar-links { flex-wrap: wrap; justify-content: flex-start; }
160
+ }
@@ -0,0 +1,93 @@
1
+ (function () {
2
+ var api = window.patchwardenDesktop;
3
+ var i18n = window.PatchWardenI18n;
4
+ var statusData = null;
5
+ var tunnelData = null;
6
+ var coreStarting = false;
7
+ var lastCoreReady = false;
8
+ function tr(key) { return i18n ? i18n.t(key) : key; }
9
+ function item(name) { return document.querySelector('[data-check="' + name + '"]'); }
10
+ function reasonText(reason, fallback) {
11
+ if (!reason) return "";
12
+ var key = "reason." + reason; var translated = tr(key);
13
+ return translated === key ? (fallback || reason) : translated;
14
+ }
15
+ function render(name, state, message, reason, fallback) {
16
+ var root = item(name); if (!root) return;
17
+ root.dataset.state = state;
18
+ root.querySelector("[data-status]").textContent = tr(state === "ready" ? "home.ready" : state === "manual" ? "home.manual" : "home.needsAction");
19
+ root.querySelector("[data-message]").textContent = tr(message);
20
+ var detail = root.querySelector("[data-reason]"); if (detail) detail.textContent = reasonText(reason, fallback);
21
+ }
22
+ function renderLiveCheck(name, check) {
23
+ if (!check) return;
24
+ var root = item(name); if (!root) return;
25
+ var state = check.state === "ready" ? "ready" : check.state === "optional" ? "manual" : "action";
26
+ root.dataset.state = state;
27
+ root.querySelector("[data-status]").textContent = tr(state === "ready" ? "home.ready" : state === "manual" ? "home.manual" : "home.needsAction");
28
+ root.querySelector("[data-message]").textContent = check.detail || "";
29
+ var detail = root.querySelector("[data-reason]"); if (detail) detail.textContent = "";
30
+ }
31
+ function setPrimaryAction(name) {
32
+ ["workspace", "core", "tunnel", "chatgpt"].forEach(function (key) {
33
+ var root = item(key);
34
+ if (root) root.dataset.primaryAction = key === name ? "true" : "false";
35
+ });
36
+ }
37
+ function syncCoreButton() {
38
+ var button = document.getElementById("startCore");
39
+ button.disabled = coreStarting || lastCoreReady;
40
+ button.textContent = tr(coreStarting ? "home.startingCore" : lastCoreReady ? "home.coreRunning" : "home.startCore");
41
+ }
42
+ async function fetchJson(url, options) {
43
+ var response = await fetch(url, options); var body = await response.json().catch(function () { return {}; });
44
+ if (!response.ok || body.ok === false) { var error = new Error(body.error || "Request failed"); error.reasonCode = body.reason_code; error.nextSteps = body.next_steps || []; throw error; }
45
+ return body;
46
+ }
47
+ async function refresh() {
48
+ statusData = await fetchJson("/api/status");
49
+ var agents = Array.isArray(statusData.agents) ? statusData.agents : [];
50
+ var workspaceReady = !!statusData.workspace_root && agents.some(function (agent) { return agent && agent.available; });
51
+ var coreAvailable = !!(statusData.core && statusData.core.available);
52
+ var watcherReady = !!(statusData.watcher && statusData.watcher.status === "healthy");
53
+ var coreReady = coreAvailable && watcherReady;
54
+ var coreTunnel = statusData.tunnel && statusData.tunnel.core ? statusData.tunnel.core : {};
55
+ var tunnelReady = coreTunnel.ready === true;
56
+
57
+ render("workspace", workspaceReady ? "ready" : "action", workspaceReady ? "home.workspaceReady" : "home.workspaceMissing", workspaceReady ? null : "workspace_agent_missing");
58
+ render("core", coreReady ? "ready" : "action", coreReady ? "home.coreReady" : "home.coreMissing", coreReady ? null : (statusData.watcher && statusData.watcher.reason) || (statusData.core && statusData.core.reason) || "watcher_unhealthy");
59
+ render("tunnel", tunnelReady ? "ready" : "action", tunnelReady ? "home.tunnelReady" : "home.tunnelMissing", tunnelReady ? null : coreTunnel.reason_code || "tunnel_not_ready");
60
+ render("chatgpt", "manual", "home.chatgptManual", null);
61
+
62
+ lastCoreReady = coreReady;
63
+ syncCoreButton();
64
+ setPrimaryAction(!workspaceReady ? "workspace" : !coreReady ? "core" : !tunnelReady ? "tunnel" : "chatgpt");
65
+ }
66
+ async function startCore() {
67
+ coreStarting = true;
68
+ syncCoreButton();
69
+ render("core", "action", "home.coreStarting");
70
+ try {
71
+ var token = await fetchJson("/control-token.json");
72
+ await fetchJson("/api/core/start", { method: "POST", headers: { "X-PatchWarden-Control-Token": token.token } });
73
+ await refresh();
74
+ } catch (error) {
75
+ lastCoreReady = false;
76
+ var fallback = error.message + (error.nextSteps && error.nextSteps[0] ? " " + error.nextSteps[0] : "");
77
+ render("core", "action", "home.startFailed", error.reasonCode || "start_failed", fallback);
78
+ toastText(reasonText(error.reasonCode || "start_failed", fallback));
79
+ } finally {
80
+ coreStarting = false;
81
+ syncCoreButton();
82
+ }
83
+ }
84
+ function toastText(value) { var node = document.getElementById("toast"); node.textContent = value; node.classList.add("visible"); setTimeout(function () { node.classList.remove("visible"); }, 4000); }
85
+ function toast(key) { toastText(tr(key)); }
86
+ document.getElementById("startCore").addEventListener("click", startCore);
87
+ document.getElementById("copyPrompt").addEventListener("click", function () { navigator.clipboard.writeText("Call health_check and report Core readiness, Watcher health, active profile, and the discovered tool count.").then(function () { toast("home.copied"); }); });
88
+ var language = document.getElementById("language");
89
+ window.addEventListener("patchwarden:i18nready", function () { language.value = i18n.getSelectedLanguage(); void refresh(); });
90
+ language.addEventListener("change", function () { void i18n.setLanguage(language.value).then(refresh); });
91
+ window.addEventListener("patchwarden:languagechange", function () { if (statusData) void refresh(); });
92
+ if (window.lucide) window.lucide.createIcons();
93
+ })();