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
@@ -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.1";
2
- export const TOOL_SCHEMA_EPOCH = "2026-07-05-v13";
1
+ export const PATCHWARDEN_VERSION = "1.6.2";
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
+ })();