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
@@ -140,6 +140,8 @@ html.dark {
140
140
  to { transform: rotate(360deg); }
141
141
  }
142
142
  </style>
143
+ <script src="/desktop-bootstrap.js"></script>
144
+ <link rel="stylesheet" href="/desktop.css">
143
145
  </head>
144
146
  <body class="flex h-screen overflow-hidden">
145
147
 
@@ -157,6 +159,10 @@ html.dark {
157
159
 
158
160
  <!-- Navigation -->
159
161
  <nav class="flex-1 flex flex-col gap-1 px-3 pt-4 overflow-y-auto">
162
+ <a href="/pages/getting-started.html" data-nav-key="getting-started" class="desktop-only-nav flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
163
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.87 12.87 0 0 1 22 2c0 2.72-.78 7.5-6.05 11a22.35 22.35 0 0 1-3.95 2z"/></svg>
164
+ <span class="truncate" data-i18n="nav.gettingStarted">开始使用</span>
165
+ </a>
160
166
 
161
167
  <a href="/pages/dashboard.html" data-nav-key="dashboard" data-dom-id="nav-dashboard" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
162
168
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0">
@@ -212,6 +218,10 @@ html.dark {
212
218
  <span class="truncate" data-zh="日志" data-en="Logs">日志</span>
213
219
  </a>
214
220
 
221
+ <a href="/pages/settings.html" data-nav-key="settings" class="desktop-only-nav flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
222
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="shrink-0"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4V21a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1A1.7 1.7 0 0 0 9 4.6 1.7 1.7 0 0 0 10 3V2.8h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9A1.7 1.7 0 0 0 21 10h.2v4H21a1.7 1.7 0 0 0-1.6 1z"/></svg>
223
+ <span class="truncate" data-i18n="nav.settings">设置</span>
224
+ </a>
215
225
  </nav>
216
226
 
217
227
  <!-- Bottom Section -->
@@ -250,36 +260,134 @@ html.dark {
250
260
  <main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
251
261
 
252
262
  <!-- ===== Filter Bar ===== -->
253
- <div class="flex flex-wrap items-center gap-3 mb-5">
254
- <!-- Status Filter Dropdown -->
255
- <div class="relative">
256
- <select id="statusFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-secondary); font-family: var(--pw-font-sans);">
257
- <option value="" data-zh="全部状态" data-en="All Status">全部状态</option>
258
- <option value="pending">Pending</option>
259
- <option value="running">Running</option>
260
- <option value="done">Done</option>
261
- <option value="failed">Failed</option>
262
- </select>
263
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
264
- <polyline points="6 9 12 15 18 9"/>
265
- </svg>
263
+ <div class="rounded-lg mb-5 p-4" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
264
+ <div class="flex flex-wrap items-end gap-3">
265
+ <!-- Repo Path Filter -->
266
+ <div class="flex flex-col gap-1">
267
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="仓库" data-en="Repo">仓库</label>
268
+ <div class="relative">
269
+ <select id="repoFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 180px;">
270
+ <option value="" data-zh="全部仓库" data-en="All repos">全部仓库</option>
271
+ </select>
272
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
273
+ <polyline points="6 9 12 15 18 9"/>
274
+ </svg>
275
+ </div>
276
+ </div>
277
+
278
+ <!-- Status Filter -->
279
+ <div class="flex flex-col gap-1">
280
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="状态" data-en="Status">状态</label>
281
+ <div class="relative">
282
+ <select id="statusFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 140px;">
283
+ <option value="" data-zh="全部状态" data-en="All Status">全部状态</option>
284
+ <option value="running">Running</option>
285
+ <option value="pending">Pending</option>
286
+ <option value="done">Done</option>
287
+ <option value="failed">Failed</option>
288
+ <option value="failed_verification">Failed Verification</option>
289
+ <option value="failed_policy_violation">Failed Policy Violation</option>
290
+ <option value="failed_scope_violation">Failed Scope Violation</option>
291
+ <option value="needs_fix">Needs Fix</option>
292
+ <option value="ready_for_review">Ready For Review</option>
293
+ <option value="done_by_agent">Done By Agent</option>
294
+ </select>
295
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
296
+ <polyline points="6 9 12 15 18 9"/>
297
+ </svg>
298
+ </div>
299
+ </div>
300
+
301
+ <!-- Acceptance Status Filter -->
302
+ <div class="flex flex-col gap-1">
303
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验收" data-en="Acceptance">验收</label>
304
+ <div class="relative">
305
+ <select id="acceptanceFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 160px;">
306
+ <option value="" data-zh="全部验收" data-en="All Acceptance">全部验收</option>
307
+ <option value="ready_for_review">Ready For Review</option>
308
+ <option value="done_by_agent">Done By Agent</option>
309
+ <option value="needs_fix">Needs Fix</option>
310
+ <option value="failed_verification">Failed Verification</option>
311
+ <option value="failed_policy_violation">Failed Policy Violation</option>
312
+ <option value="failed_scope_violation">Failed Scope Violation</option>
313
+ </select>
314
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
315
+ <polyline points="6 9 12 15 18 9"/>
316
+ </svg>
317
+ </div>
318
+ </div>
319
+
320
+ <!-- Warning Type Filter -->
321
+ <div class="flex flex-col gap-1">
322
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="警告类型" data-en="Warning Type">警告类型</label>
323
+ <div class="relative">
324
+ <select id="warningTypeFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 180px;">
325
+ <option value="" data-zh="全部警告" data-en="All Warnings">全部警告</option>
326
+ <option value="unrecorded_command_execution">Unrecorded Command Execution</option>
327
+ <option value="artifact_hygiene">Artifact Hygiene</option>
328
+ <option value="scope_changes">Scope Changes</option>
329
+ <option value="release_publish_claim">Release Publish Claim</option>
330
+ <option value="manual_verification_required">Manual Verification Required</option>
331
+ <option value="stale_task">Stale Task</option>
332
+ <option value="failed_verification">Failed Verification</option>
333
+ </select>
334
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
335
+ <polyline points="6 9 12 15 18 9"/>
336
+ </svg>
337
+ </div>
338
+ </div>
339
+
340
+ <!-- Agent Filter -->
341
+ <div class="flex flex-col gap-1">
342
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="Agent" data-en="Agent">Agent</label>
343
+ <div class="relative">
344
+ <select id="agentFilter" class="appearance-none pl-3 pr-8 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); min-width: 140px;">
345
+ <option value="" data-zh="全部 Agent" data-en="All Agents">全部 Agent</option>
346
+ </select>
347
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--pw-text-tertiary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
348
+ <polyline points="6 9 12 15 18 9"/>
349
+ </svg>
350
+ </div>
351
+ </div>
352
+
353
+ <!-- Date From -->
354
+ <div class="flex flex-col gap-1">
355
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="更新从" data-en="Updated From">更新从</label>
356
+ <input id="dateFromFilter" type="date" class="px-3 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); color-scheme: dark;" />
357
+ </div>
358
+
359
+ <!-- Date To -->
360
+ <div class="flex flex-col gap-1">
361
+ <label class="text-xs font-medium" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="更新至" data-en="Updated To">更新至</label>
362
+ <input id="dateToFilter" type="date" class="px-3 py-1.5 rounded-lg text-xs font-medium outline-none cursor-pointer transition-colors" style="background-color: var(--pw-bg-elevated); border: 1px solid var(--pw-border-subtle); color: var(--pw-text-primary); font-family: var(--pw-font-sans); color-scheme: dark;" />
363
+ </div>
364
+
365
+ <!-- Action Buttons -->
366
+ <div class="flex items-center gap-2">
367
+ <button id="applyFiltersBtn" class="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors" style="background-color: var(--pw-accent); color: var(--pw-text-inverse); font-family: var(--pw-font-sans);">
368
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
369
+ <span data-zh="应用筛选" data-en="Apply Filters">应用筛选</span>
370
+ </button>
371
+ <button id="clearFiltersBtn" class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">
372
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
373
+ <span data-zh="清空筛选" data-en="Clear Filters">清空筛选</span>
374
+ </button>
375
+ </div>
376
+
377
+ <!-- Refresh + Count -->
378
+ <button id="refreshBtn" class="flex items-center gap-2 px-4 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors ml-auto" style="background-color: var(--pw-bg-elevated); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">
379
+ <svg id="refreshIcon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
380
+ <polyline points="23 4 23 10 17 10"/>
381
+ <polyline points="1 20 1 14 7 14"/>
382
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
383
+ </svg>
384
+ <span data-zh="刷新" data-en="Refresh">刷新</span>
385
+ </button>
386
+ </div>
387
+ <div class="flex items-center justify-between mt-3 pt-3" style="border-top: 1px solid var(--pw-border-subtle);">
388
+ <div id="filterStatusText" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">—</div>
389
+ <div id="countSummary" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">--</div>
266
390
  </div>
267
-
268
- <!-- Refresh Button -->
269
- <button id="refreshBtn" class="flex items-center gap-2 px-4 py-1.5 rounded-lg text-xs font-semibold whitespace-nowrap transition-colors" style="background-color: var(--pw-bg-surface); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">
270
- <svg id="refreshIcon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
271
- <polyline points="23 4 23 10 17 10"/>
272
- <polyline points="1 20 1 14 7 14"/>
273
- <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
274
- </svg>
275
- <span data-zh="刷新" data-en="Refresh">刷新</span>
276
- </button>
277
-
278
- <!-- Spacer -->
279
- <div class="flex-1"></div>
280
-
281
- <!-- Count Summary -->
282
- <div id="countSummary" class="text-xs" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">--</div>
283
391
  </div>
284
392
 
285
393
  <!-- ===== Task Table (border-led card) ===== -->
@@ -324,16 +432,18 @@ html.dark {
324
432
 
325
433
  <!-- Table -->
326
434
  <div id="tableWrapper" class="hidden overflow-x-auto">
327
- <table class="w-full text-left" style="min-width: 960px;">
435
+ <table class="w-full text-left" style="min-width: 1200px;">
328
436
  <thead>
329
437
  <tr style="border-bottom: 1px solid var(--pw-border);">
330
438
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="任务ID" data-en="Task ID">任务ID</th>
331
439
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="标题" data-en="Title">标题</th>
332
440
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);">Agent</th>
333
441
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="状态" data-en="Status">状态</th>
442
+ <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验收" data-en="Acceptance">验收</th>
443
+ <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="验证" data-en="Verification">验证</th>
334
444
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="阶段" data-en="Phase">阶段</th>
335
445
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="创建时间" data-en="Created">创建时间</th>
336
- <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="待因" data-en="Pending Reason">待因</th>
446
+ <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="下一步" data-en="Next Action">下一步</th>
337
447
  <th class="px-4 py-3 text-xs font-semibold whitespace-nowrap text-right" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-sans);" data-zh="操作" data-en="Actions">操作</th>
338
448
  </tr>
339
449
  </thead>
@@ -342,6 +452,20 @@ html.dark {
342
452
  </tbody>
343
453
  </table>
344
454
  </div>
455
+ <div id="loadMoreWrap" class="hidden px-4 py-3 text-center" style="border-top: 1px solid var(--pw-border-subtle);">
456
+ <button id="loadMoreBtn" class="px-3 py-1.5 rounded-md text-xs font-semibold" type="button" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);">加载更多</button>
457
+ </div>
458
+ </div>
459
+
460
+ <!-- ===== SAFE MODAL ===== -->
461
+ <div id="safe-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4" style="background-color: rgba(0,0,0,0.6);">
462
+ <div class="rounded-lg w-full max-w-2xl max-h-[80vh] flex flex-col" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); box-shadow: var(--pw-shadow-modal);">
463
+ <div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
464
+ <h3 id="safe-modal-title" class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">—</h3>
465
+ <button id="safe-modal-close" class="text-lg leading-none" style="color: var(--pw-text-tertiary);" type="button">×</button>
466
+ </div>
467
+ <div id="safe-modal-body" class="flex-1 overflow-auto px-4 py-3 text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono); white-space: pre-wrap; word-break: break-all;"></div>
468
+ </div>
345
469
  </div>
346
470
 
347
471
  </main>
@@ -418,6 +542,51 @@ html.dark {
418
542
  background-color: var(--pw-bg-active) !important;
419
543
  color: var(--pw-text-primary) !important;
420
544
  }
545
+ /* Quick action icon buttons */
546
+ .pw-quick-btn {
547
+ display: inline-flex;
548
+ align-items: center;
549
+ justify-content: center;
550
+ width: 24px;
551
+ height: 24px;
552
+ border-radius: 4px;
553
+ border: 1px solid var(--pw-border-subtle);
554
+ background-color: var(--pw-bg-hover);
555
+ color: var(--pw-text-secondary);
556
+ cursor: pointer;
557
+ transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
558
+ text-decoration: none;
559
+ vertical-align: middle;
560
+ }
561
+ .pw-quick-btn:hover {
562
+ background-color: var(--pw-bg-active);
563
+ color: var(--pw-text-primary);
564
+ filter: none;
565
+ }
566
+ /* Acceptance status badge - more prominent than status */
567
+ .pw-acceptance-badge {
568
+ font-size: var(--pw-text-sm);
569
+ font-weight: var(--pw-weight-bold);
570
+ padding: 2px 8px;
571
+ border-width: 1px;
572
+ border-style: solid;
573
+ }
574
+ #taskTableBody td:nth-child(2) {
575
+ display: -webkit-box;
576
+ -webkit-line-clamp: 2;
577
+ -webkit-box-orient: vertical;
578
+ overflow: hidden;
579
+ }
580
+ @media (max-width: 1100px) {
581
+ #tableWrapper { overflow-x: visible; padding: 10px; }
582
+ #tableWrapper table { min-width: 0 !important; display: block; }
583
+ #tableWrapper thead { display: none; }
584
+ #taskTableBody, #taskTableBody tr { display: block; }
585
+ #taskTableBody tr { margin-bottom: 10px; padding: 12px; border: 1px solid var(--pw-border-subtle); border-radius: 6px; }
586
+ #taskTableBody td { display: block; padding: 4px 0 !important; text-align: left !important; max-width: none !important; }
587
+ #taskTableBody td:nth-child(5), #taskTableBody td:nth-child(6), #taskTableBody td:nth-child(7), #taskTableBody td:nth-child(9) { display: none; }
588
+ #taskTableBody td:nth-child(10) > div { display: flex; justify-content: flex-start; flex-wrap: wrap; }
589
+ }
421
590
  </style>
422
591
 
423
592
  <script>
@@ -434,6 +603,11 @@ html.dark {
434
603
  done: { color: 'success', text: '已完成' },
435
604
  failed: { color: 'error', text: '失败' },
436
605
  failed_scope_violation: { color: 'error', text: '范围违规' },
606
+ failed_policy_violation: { color: 'error', text: '策略违规' },
607
+ failed_verification: { color: 'error', text: '验证失败' },
608
+ needs_fix: { color: 'warning', text: '需修复' },
609
+ ready_for_review: { color: 'info', text: '待审核' },
610
+ done_by_agent: { color: 'success', text: 'Agent 完成' },
437
611
  canceled: { color: 'muted', text: '已取消' },
438
612
  timeout: { color: 'muted', text: '超时' }
439
613
  };
@@ -448,7 +622,20 @@ html.dark {
448
622
  };
449
623
 
450
624
  var allTasks = [];
451
- var currentFilter = '';
625
+ var nextCursor = null;
626
+ var totalTasks = 0;
627
+ // currentFilters holds the active filter values for all dimensions.
628
+ // repo_path / status / acceptance_status / agent are sent to the backend.
629
+ // warning_type / date_from / date_to are applied client-side.
630
+ var currentFilters = {
631
+ repo_path: '',
632
+ status: '',
633
+ acceptance_status: '',
634
+ agent: '',
635
+ warning_type: '',
636
+ date_from: '',
637
+ date_to: ''
638
+ };
452
639
 
453
640
  // ---- DOM helpers ----
454
641
  function $(id) { return document.getElementById(id); }
@@ -488,12 +675,131 @@ html.dark {
488
675
  return PHASE_LABELS[phase] || phase || '—';
489
676
  }
490
677
 
678
+ function shortId(id) {
679
+ if (!id) return '—';
680
+ var s = String(id);
681
+ return s.length > 22 ? s.slice(0, 10) + '…' + s.slice(-8) : s;
682
+ }
683
+
684
+ // ---- Safe summary + clipboard helpers ----
685
+ async function fetchJson(url) {
686
+ var res = await fetch(url);
687
+ if (!res.ok) throw new Error('HTTP ' + res.status + ' · ' + url);
688
+ return res.json();
689
+ }
690
+
691
+ function showToast(kind, title, msg) {
692
+ var container = document.getElementById('toast-container');
693
+ if (!container) {
694
+ container = document.createElement('div');
695
+ container.id = 'toast-container';
696
+ container.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm pointer-events-none';
697
+ document.body.appendChild(container);
698
+ }
699
+ var el = document.createElement('div');
700
+ var borderColors = {
701
+ success: 'var(--pw-state-success)',
702
+ error: 'var(--pw-state-error)',
703
+ warning: 'var(--pw-state-warning)',
704
+ info: 'var(--pw-state-info)'
705
+ };
706
+ el.className = 'pointer-events-auto flex items-start gap-2 px-3 py-2 rounded-lg text-xs';
707
+ el.style.cssText = 'background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); border-left: 3px solid ' + (borderColors[kind] || borderColors.info) + '; color: var(--pw-text-primary); box-shadow: var(--pw-shadow-float); max-width: 320px;';
708
+ el.innerHTML = '<div class="min-w-0 flex-1"><div style="font-weight:600;">' + escapeHtml(title || '') + '</div>' + (msg ? '<div style="color: var(--pw-text-secondary);">' + escapeHtml(msg) + '</div>' : '') + '</div>';
709
+ container.appendChild(el);
710
+ setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 4000);
711
+ }
712
+
713
+ function copyToClipboard(text, label) {
714
+ if (!text) return;
715
+ if (navigator.clipboard && navigator.clipboard.writeText) {
716
+ navigator.clipboard.writeText(text).then(function () {
717
+ showToast('success', '已复制', (label || '内容') + '已复制到剪贴板');
718
+ }).catch(function () {
719
+ showToast('error', '复制失败', '请手动复制');
720
+ });
721
+ } else {
722
+ showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
723
+ }
724
+ }
725
+
726
+ function showSafeModal(title, data) {
727
+ var modal = $('safe-modal');
728
+ var titleEl = $('safe-modal-title');
729
+ var bodyEl = $('safe-modal-body');
730
+ if (!modal || !titleEl || !bodyEl) return;
731
+ titleEl.textContent = title || '—';
732
+ var content;
733
+ if (data && typeof data === 'object') {
734
+ content = JSON.stringify(data, null, 2);
735
+ } else {
736
+ content = String(data || '');
737
+ }
738
+ bodyEl.textContent = content;
739
+ modal.classList.remove('hidden');
740
+ }
741
+
742
+ function closeSafeModal() {
743
+ var modal = $('safe-modal');
744
+ if (modal) modal.classList.add('hidden');
745
+ }
746
+
747
+ async function fetchSafeSummary(taskId, kind, title) {
748
+ if (!taskId) return;
749
+ try {
750
+ var data = await fetchJson('/api/tasks/' + encodeURIComponent(taskId) + '/' + kind);
751
+ showSafeModal(title, data);
752
+ } catch (err) {
753
+ showToast('error', '加载失败', title + ': ' + err.message);
754
+ }
755
+ }
756
+
757
+ // ---- Status priority ----
758
+ function primaryStatus(task) {
759
+ if (!task) return { key: '', label: '—', color: 'muted' };
760
+ var priority = [
761
+ 'failed_policy_violation',
762
+ 'failed_scope_violation',
763
+ 'failed_verification',
764
+ 'needs_fix',
765
+ 'ready_for_review',
766
+ 'done_by_agent',
767
+ 'running',
768
+ 'pending'
769
+ ];
770
+ var status = task.status || '';
771
+ var acceptance = task.acceptance_status || '';
772
+ var statusIdx = priority.indexOf(status);
773
+ var acceptanceIdx = priority.indexOf(acceptance);
774
+ var chosen = '';
775
+ if (statusIdx >= 0 && acceptanceIdx >= 0) {
776
+ chosen = statusIdx <= acceptanceIdx ? status : acceptance;
777
+ } else if (statusIdx >= 0) {
778
+ chosen = status;
779
+ } else if (acceptanceIdx >= 0) {
780
+ chosen = acceptance;
781
+ } else {
782
+ chosen = acceptance || status || '';
783
+ }
784
+ var meta = getStatusMeta(chosen || status);
785
+ return { key: chosen, label: meta.text, color: meta.color };
786
+ }
787
+
788
+ function verificationHeadline(t) {
789
+ if (!t) return '—';
790
+ if (t.verification && t.verification.headline) return t.verification.headline;
791
+ if (t.safe_result && t.safe_result.verification && t.safe_result.verification.headline) return t.safe_result.verification.headline;
792
+ if (t.verification_headline) return t.verification_headline;
793
+ return '—';
794
+ }
795
+
491
796
  // ---- State rendering ----
492
797
  function showLoading() {
493
798
  show($('loadingState'));
494
799
  hide($('errorState'));
495
800
  hide($('emptyState'));
496
801
  hide($('tableWrapper'));
802
+ hide($('loadMoreWrap'));
497
803
  $('countSummary').textContent = '--';
498
804
  }
499
805
 
@@ -511,6 +817,7 @@ html.dark {
511
817
  hide($('errorState'));
512
818
  show($('emptyState'));
513
819
  hide($('tableWrapper'));
820
+ hide($('loadMoreWrap'));
514
821
  }
515
822
 
516
823
  function showTable() {
@@ -541,52 +848,156 @@ html.dark {
541
848
 
542
849
  // ---- Row rendering ----
543
850
  function renderRow(task, idx) {
851
+ var taskId = task.task_id || '';
544
852
  var meta = getStatusMeta(task.status);
545
853
  var style = badgeStyle(meta.color);
546
854
  var pulseClass = (task.status === 'running') ? ' pw-pulse-badge' : '';
547
- var truncatedId = truncate(task.task_id, 12);
855
+ var truncatedId = truncate(taskId, 12);
548
856
  var createdAt = formatTime(task.created_at);
549
857
  var phaseLabel = getPhaseLabel(task.phase);
550
858
  var altBg = (idx % 2 === 1) ? ' background-color: var(--pw-bg-hover);' : '';
859
+ var detailHref = '/pages/task-detail.html?id=' + encodeURIComponent(taskId);
860
+
861
+ // Skip status badge if redundant with acceptance (done + done_by_agent)
862
+ var skipStatusBadge = (task.status === 'done' && task.acceptance_status === 'done_by_agent');
863
+
864
+ // Acceptance badge (more prominent)
865
+ var accCell;
866
+ if (task.acceptance_status) {
867
+ var accMeta = getStatusMeta(task.acceptance_status);
868
+ var accStyle = badgeStyle(accMeta.color);
869
+ accCell = '<span class="inline-flex items-center gap-1.5 pw-acceptance-badge rounded-md whitespace-nowrap" style="' + accStyle + ' font-family: var(--pw-font-sans); border-color: ' + getAccentColor(accMeta.color) + ';">'
870
+ + '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
871
+ + escapeHtml(accMeta.text)
872
+ + '</span>';
873
+ } else {
874
+ accCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
875
+ }
876
+
877
+ // Verification headline
878
+ var headline = verificationHeadline(task);
879
+ var verifyCell = '<span class="inline-block truncate" style="max-width: 180px; color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" title="' + escapeAttr(headline) + '">' + escapeHtml(truncate(headline, 30)) + '</span>';
551
880
 
552
- var pendingCell;
553
- if (task.pending_reason) {
554
- pendingCell = '<span class="inline-block truncate" style="max-width: 180px; color: var(--pw-state-warning); font-family: var(--pw-font-sans);" title="'
555
- + escapeAttr(task.pending_reason) + '">'
556
- + escapeHtml(truncate(task.pending_reason, 28))
557
- + '</span>';
881
+ // Next action cell
882
+ var nextActionCell;
883
+ if (task.next_action) {
884
+ nextActionCell = '<span class="inline-block truncate" style="max-width: 160px; color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" title="' + escapeAttr(task.next_action) + '">' + escapeHtml(truncate(task.next_action, 28)) + '</span>';
885
+ } else if (task.pending_reason) {
886
+ nextActionCell = '<span class="inline-block truncate" style="max-width: 160px; color: var(--pw-state-warning); font-family: var(--pw-font-sans);" title="' + escapeAttr(task.pending_reason) + '">' + escapeHtml(truncate(task.pending_reason, 28)) + '</span>';
558
887
  } else {
559
- pendingCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
888
+ nextActionCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
560
889
  }
561
890
 
562
- var detailHref = '/pages/task-detail.html?id=' + encodeURIComponent(task.task_id || '');
891
+ // Status cell (skip badge if redundant)
892
+ var statusCell;
893
+ if (skipStatusBadge) {
894
+ statusCell = '<span style="color: var(--pw-text-tertiary);">—</span>';
895
+ } else {
896
+ statusCell = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium whitespace-nowrap' + pulseClass + '" style="' + style + ' font-family: var(--pw-font-sans);" data-status="' + escapeAttr(task.status || '') + '">'
897
+ + '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
898
+ + escapeHtml(meta.text)
899
+ + '</span>';
900
+ }
901
+
902
+ // Quick action buttons
903
+ var quickActions =
904
+ '<button class="pw-quick-btn" data-safe-action="safe-result" data-task-id="' + escapeAttr(taskId) + '" title="查看安全结果" aria-label="查看安全结果" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></button>'
905
+ + '<button class="pw-quick-btn" data-safe-action="safe-audit" data-task-id="' + escapeAttr(taskId) + '" title="查看安全审计" aria-label="查看安全审计" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></button>'
906
+ + '<button class="pw-quick-btn" data-safe-action="safe-test-summary" data-task-id="' + escapeAttr(taskId) + '" title="safe test summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg></button>'
907
+ + '<button class="pw-quick-btn" data-safe-action="safe-diff-summary" data-task-id="' + escapeAttr(taskId) + '" title="safe diff summary" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg></button>'
908
+ + '<button class="pw-quick-btn" data-copy-id="' + escapeAttr(taskId) + '" title="复制 task_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
909
+ + '<a href="' + detailHref + '" class="pw-quick-btn" title="查看详情"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>';
563
910
 
564
911
  return ''
565
912
  + '<tr class="transition-colors" style="border-bottom: 1px solid var(--pw-border-subtle);' + altBg + '">'
566
- + '<td class="px-4 py-3 text-xs whitespace-nowrap" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeAttr(task.task_id || '') + '">' + escapeHtml(truncatedId) + '</td>'
567
- + '<td class="px-4 py-3 text-sm" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); max-width: 320px;">' + escapeHtml(task.title || '—') + '</td>'
568
- + '<td class="px-4 py-3">'
569
- + '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium whitespace-nowrap" style="' + agentBadgeStyle(task.agent) + ' font-family: var(--pw-font-mono);">' + escapeHtml(task.agent || '—') + '</span>'
913
+ + '<td class="px-4 py-3 whitespace-nowrap">'
914
+ + '<div class="flex items-center gap-1">'
915
+ + '<span class="text-xs" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeAttr(taskId) + '">' + escapeHtml(truncatedId) + '</span>'
916
+ + '<button class="pw-quick-btn" data-copy-id="' + escapeAttr(taskId) + '" title="复制 task_id" type="button"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
917
+ + '</div>'
570
918
  + '</td>'
919
+ + '<td class="px-4 py-3 text-sm" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans); max-width: 280px;">' + escapeHtml(task.title || '—') + '</td>'
571
920
  + '<td class="px-4 py-3">'
572
- + '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium whitespace-nowrap' + pulseClass + '" style="' + style + ' font-family: var(--pw-font-sans);" data-status="' + escapeAttr(task.status || '') + '">'
573
- + '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: currentColor;"></span>'
574
- + escapeHtml(meta.text)
575
- + '</span>'
921
+ + '<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium whitespace-nowrap" style="' + agentBadgeStyle(task.agent) + ' font-family: var(--pw-font-mono);">' + escapeHtml(task.agent || '') + '</span>'
576
922
  + '</td>'
923
+ + '<td class="px-4 py-3">' + statusCell + '</td>'
924
+ + '<td class="px-4 py-3">' + accCell + '</td>'
925
+ + '<td class="px-4 py-3 text-xs">' + verifyCell + '</td>'
577
926
  + '<td class="px-4 py-3 text-xs whitespace-nowrap" style="color: var(--pw-text-secondary); font-family: var(--pw-font-sans);" data-phase="' + escapeAttr(task.phase || '') + '">' + escapeHtml(phaseLabel) + '</td>'
578
927
  + '<td class="px-4 py-3 text-xs whitespace-nowrap" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeAttr(task.created_at || '') + '">' + escapeHtml(createdAt) + '</td>'
579
- + '<td class="px-4 py-3 text-xs" style="font-family: var(--pw-font-sans);">' + pendingCell + '</td>'
928
+ + '<td class="px-4 py-3 text-xs" style="font-family: var(--pw-font-sans);">' + nextActionCell + '</td>'
580
929
  + '<td class="px-4 py-3 text-right">'
581
- + '<a href="' + detailHref + '" class="inline-block px-3 py-1 rounded-md text-xs font-medium whitespace-nowrap transition-colors" style="background-color: var(--pw-bg-hover); color: var(--pw-text-secondary); border: 1px solid var(--pw-border-subtle); font-family: var(--pw-font-sans);" data-zh="查看详情" data-en="View Details">查看详情</a>'
930
+ + '<div class="inline-flex items-center gap-1">' + quickActions + '</div>'
582
931
  + '</td>'
583
932
  + '</tr>';
584
933
  }
585
934
 
935
+ function getAccentColor(color) {
936
+ switch (color) {
937
+ case 'success': return 'var(--pw-state-success)';
938
+ case 'info': return 'var(--pw-state-info)';
939
+ case 'warning': return 'var(--pw-state-warning)';
940
+ case 'error': return 'var(--pw-state-error)';
941
+ default: return 'var(--pw-text-secondary)';
942
+ }
943
+ }
944
+
945
+ // ---- Client-side filter helpers ----
946
+ // Extracts warning-type-related signals from a task for client-side filtering.
947
+ function taskMatchesWarningType(task, warningType) {
948
+ if (!warningType) return true;
949
+ if (!task) return false;
950
+ // Check warnings array (most precise)
951
+ if (Array.isArray(task.warnings)) {
952
+ for (var i = 0; i < task.warnings.length; i++) {
953
+ var w = task.warnings[i] || {};
954
+ if (w.type === warningType || w.code === warningType || w.kind === warningType) return true;
955
+ }
956
+ }
957
+ // Check stale_task flag
958
+ if (warningType === 'stale_task' && task.is_stale === true) return true;
959
+ // Check failed_verification mapping
960
+ if (warningType === 'failed_verification' &&
961
+ (task.status === 'failed_verification' || task.acceptance_status === 'failed_verification')) return true;
962
+ // Check status / acceptance_status containing the warning type
963
+ if (task.status === warningType || task.acceptance_status === warningType) return true;
964
+ // Check verification headline (loose contains for manual_verification_required, scope_changes, etc.)
965
+ var headline = verificationHeadline(task);
966
+ if (headline && headline.toLowerCase().indexOf(warningType.replace(/_/g, ' ')) >= 0) return true;
967
+ return false;
968
+ }
969
+
970
+ function taskMatchesDateRange(task, dateFrom, dateTo) {
971
+ if (!dateFrom && !dateTo) return true;
972
+ if (!task) return false;
973
+ var ts = task.updated_at || task.created_at;
974
+ if (!ts) return false;
975
+ var d = new Date(ts);
976
+ if (isNaN(d.getTime())) return false;
977
+ if (dateFrom) {
978
+ var from = new Date(dateFrom + 'T00:00:00');
979
+ if (d < from) return false;
980
+ }
981
+ if (dateTo) {
982
+ var to = new Date(dateTo + 'T23:59:59');
983
+ if (d > to) return false;
984
+ }
985
+ return true;
986
+ }
987
+
988
+ function applyClientSideFilters(tasks) {
989
+ var out = tasks.slice();
990
+ if (currentFilters.warning_type) {
991
+ out = out.filter(function (t) { return taskMatchesWarningType(t, currentFilters.warning_type); });
992
+ }
993
+ if (currentFilters.date_from || currentFilters.date_to) {
994
+ out = out.filter(function (t) { return taskMatchesDateRange(t, currentFilters.date_from, currentFilters.date_to); });
995
+ }
996
+ return out;
997
+ }
998
+
586
999
  function renderTasks() {
587
- var filtered = currentFilter
588
- ? allTasks.filter(function (t) { return t && t.status === currentFilter; })
589
- : allTasks.slice();
1000
+ var filtered = applyClientSideFilters(allTasks);
590
1001
 
591
1002
  // Sort by created_at desc (newest first). Falls back gracefully if missing.
592
1003
  filtered.sort(function (a, b) {
@@ -606,13 +1017,18 @@ html.dark {
606
1017
  }
607
1018
  $('taskTableBody').innerHTML = html;
608
1019
  showTable();
1020
+ wireQuickActions();
609
1021
  }
1022
+ var more = $('loadMoreWrap');
1023
+ if (more) nextCursor ? show(more) : hide(more);
610
1024
 
611
1025
  // Count summary
612
- if (currentFilter) {
613
- $('countSummary').textContent = '显示 ' + filtered.length + ' / ' + allTasks.length + ' 条';
1026
+ var hasFilters = currentFilters.repo_path || currentFilters.status || currentFilters.acceptance_status ||
1027
+ currentFilters.agent || currentFilters.warning_type || currentFilters.date_from || currentFilters.date_to;
1028
+ if (hasFilters) {
1029
+ $('countSummary').textContent = '显示 ' + filtered.length + ' / ' + totalTasks + ' 条';
614
1030
  } else {
615
- $('countSummary').textContent = ' ' + allTasks.length + ' 条';
1031
+ $('countSummary').textContent = '已加载 ' + filtered.length + ' / ' + totalTasks + ' 条';
616
1032
  }
617
1033
  }
618
1034
 
@@ -634,6 +1050,68 @@ html.dark {
634
1050
  el.innerHTML = '<span class="shrink-0 w-1.5 h-1.5 rounded-full" style="background-color: ' + dotColor + ';"></span><span>Watcher: ' + escapeHtml(status) + '</span>';
635
1051
  }
636
1052
 
1053
+ // ---- Filter query building ----
1054
+ // Builds a query string from the backend-supported filter dimensions.
1055
+ function buildFilterQueryString() {
1056
+ var params = [];
1057
+ if (currentFilters.repo_path) {
1058
+ params.push('repo_path=' + encodeURIComponent(currentFilters.repo_path));
1059
+ }
1060
+ if (currentFilters.status) {
1061
+ params.push('status=' + encodeURIComponent(currentFilters.status));
1062
+ }
1063
+ if (currentFilters.acceptance_status) {
1064
+ params.push('acceptance_status=' + encodeURIComponent(currentFilters.acceptance_status));
1065
+ }
1066
+ if (currentFilters.agent) {
1067
+ params.push('agent=' + encodeURIComponent(currentFilters.agent));
1068
+ }
1069
+ if (currentFilters.warning_type) params.push('warning_type=' + encodeURIComponent(currentFilters.warning_type));
1070
+ if (currentFilters.date_from) params.push('date_from=' + encodeURIComponent(currentFilters.date_from));
1071
+ if (currentFilters.date_to) params.push('date_to=' + encodeURIComponent(currentFilters.date_to));
1072
+ params.push('limit=50');
1073
+ return params.length ? ('?' + params.join('&')) : '';
1074
+ }
1075
+
1076
+ // Reads current dropdown/input values into currentFilters.
1077
+ function readFiltersFromUI() {
1078
+ currentFilters.repo_path = $('repoFilter') ? $('repoFilter').value : '';
1079
+ currentFilters.status = $('statusFilter') ? $('statusFilter').value : '';
1080
+ currentFilters.acceptance_status = $('acceptanceFilter') ? $('acceptanceFilter').value : '';
1081
+ currentFilters.agent = $('agentFilter') ? $('agentFilter').value : '';
1082
+ currentFilters.warning_type = $('warningTypeFilter') ? $('warningTypeFilter').value : '';
1083
+ currentFilters.date_from = $('dateFromFilter') ? $('dateFromFilter').value : '';
1084
+ currentFilters.date_to = $('dateToFilter') ? $('dateToFilter').value : '';
1085
+ }
1086
+
1087
+ // Resets all filter UI inputs to empty.
1088
+ function clearFilterUI() {
1089
+ var ids = ['repoFilter', 'statusFilter', 'acceptanceFilter', 'agentFilter', 'warningTypeFilter', 'dateFromFilter', 'dateToFilter'];
1090
+ ids.forEach(function (id) {
1091
+ var el = $(id);
1092
+ if (el) el.value = '';
1093
+ });
1094
+ currentFilters = {
1095
+ repo_path: '', status: '', acceptance_status: '', agent: '',
1096
+ warning_type: '', date_from: '', date_to: ''
1097
+ };
1098
+ }
1099
+
1100
+ // Updates the small status text describing active filters.
1101
+ function updateFilterStatusText() {
1102
+ var el = $('filterStatusText');
1103
+ if (!el) return;
1104
+ var parts = [];
1105
+ if (currentFilters.repo_path) parts.push('repo: ' + currentFilters.repo_path);
1106
+ if (currentFilters.status) parts.push('status: ' + currentFilters.status);
1107
+ if (currentFilters.acceptance_status) parts.push('acceptance: ' + currentFilters.acceptance_status);
1108
+ if (currentFilters.agent) parts.push('agent: ' + currentFilters.agent);
1109
+ if (currentFilters.warning_type) parts.push('warning: ' + currentFilters.warning_type);
1110
+ if (currentFilters.date_from) parts.push('from: ' + currentFilters.date_from);
1111
+ if (currentFilters.date_to) parts.push('to: ' + currentFilters.date_to);
1112
+ el.textContent = parts.length ? ('已应用筛选 · ' + parts.join(' · ')) : '无筛选';
1113
+ }
1114
+
637
1115
  // ---- API call ----
638
1116
  function setRefreshing(isRefreshing) {
639
1117
  var icon = $('refreshIcon');
@@ -645,10 +1123,12 @@ html.dark {
645
1123
  }
646
1124
  }
647
1125
 
648
- function fetchTasks() {
649
- showLoading();
1126
+ function fetchTasks(cursor, append) {
1127
+ if (!append) showLoading();
650
1128
  setRefreshing(true);
651
- fetch(API_URL, {
1129
+ var query = buildFilterQueryString();
1130
+ var url = API_URL + query + (query ? '&' : '?') + (cursor ? 'cursor=' + encodeURIComponent(cursor) : '');
1131
+ fetch(url, {
652
1132
  method: 'GET',
653
1133
  headers: { 'Accept': 'application/json' },
654
1134
  cache: 'no-store'
@@ -662,9 +1142,13 @@ html.dark {
662
1142
  return res.json();
663
1143
  })
664
1144
  .then(function (data) {
665
- allTasks = (data && Array.isArray(data.tasks)) ? data.tasks : [];
1145
+ var page = (data && Array.isArray(data.tasks)) ? data.tasks : [];
1146
+ allTasks = append ? allTasks.concat(page) : page;
1147
+ nextCursor = data && (data.nextCursor || data.next_cursor) ? (data.nextCursor || data.next_cursor) : null;
1148
+ totalTasks = data && typeof data.total === 'number' ? data.total : allTasks.length;
666
1149
  renderWatcher(data && data.watcher);
667
1150
  renderTasks();
1151
+ updateFilterStatusText();
668
1152
  })
669
1153
  .catch(function (err) {
670
1154
  var msg = (err && err.message) ? err.message : '未知错误';
@@ -679,24 +1163,123 @@ html.dark {
679
1163
  });
680
1164
  }
681
1165
 
1166
+ // ---- Dropdown population ----
1167
+ async function loadRepos() {
1168
+ var select = $('repoFilter');
1169
+ if (!select) return;
1170
+ try {
1171
+ var data = await fetchJson('/api/workspace/repos');
1172
+ var repos = Array.isArray(data.repos) ? data.repos : [];
1173
+ var html = '<option value="" data-zh="全部仓库" data-en="All repos">全部仓库</option>';
1174
+ repos.forEach(function (r) {
1175
+ var label = r.name + (r.has_package_json && r.version ? ' (v' + r.version + ')' : '');
1176
+ var val = r.path || r.name;
1177
+ html += '<option value="' + escapeAttr(val) + '">' + escapeHtml(label) + '</option>';
1178
+ });
1179
+ select.innerHTML = html;
1180
+ } catch (err) {
1181
+ // keep default option
1182
+ console.error('loadRepos failed:', err);
1183
+ }
1184
+ }
1185
+
1186
+ async function loadAgents() {
1187
+ var select = $('agentFilter');
1188
+ if (!select) return;
1189
+ try {
1190
+ var data = await fetchJson('/api/status');
1191
+ var agents = (data && Array.isArray(data.agents)) ? data.agents : [];
1192
+ var html = '<option value="" data-zh="全部 Agent" data-en="All Agents">全部 Agent</option>';
1193
+ agents.forEach(function (a) {
1194
+ if (a && a.name) {
1195
+ html += '<option value="' + escapeAttr(a.name) + '">' + escapeHtml(a.name) + (a.available ? '' : ' (unavailable)') + '</option>';
1196
+ }
1197
+ });
1198
+ select.innerHTML = html;
1199
+ } catch (err) {
1200
+ // keep default option
1201
+ console.error('loadAgents failed:', err);
1202
+ }
1203
+ }
1204
+
682
1205
  // ---- Event wiring ----
1206
+ // Status filter: apply immediately on change (keeps original UX)
683
1207
  $('statusFilter').addEventListener('change', function (e) {
684
- currentFilter = e.target.value;
685
- renderTasks();
1208
+ readFiltersFromUI();
1209
+ fetchTasks();
1210
+ });
1211
+
1212
+ // Apply Filters button: reads all filter values and refetches
1213
+ $('applyFiltersBtn').addEventListener('click', function () {
1214
+ readFiltersFromUI();
1215
+ fetchTasks();
1216
+ });
1217
+
1218
+ // Clear Filters button: resets all filters and refetches
1219
+ $('clearFiltersBtn').addEventListener('click', function () {
1220
+ clearFilterUI();
1221
+ fetchTasks();
686
1222
  });
687
1223
 
688
1224
  $('refreshBtn').addEventListener('click', fetchTasks);
1225
+ $('loadMoreBtn').addEventListener('click', function () { if (nextCursor) fetchTasks(nextCursor, true); });
689
1226
 
690
1227
  var retryBtn = $('retryBtn');
691
1228
  if (retryBtn) {
692
1229
  retryBtn.addEventListener('click', fetchTasks);
693
1230
  }
694
1231
 
695
- // Initial load
696
- fetchTasks();
1232
+ // Safe modal close handlers
1233
+ var modalClose = $('safe-modal-close');
1234
+ if (modalClose) {
1235
+ modalClose.addEventListener('click', function () { closeSafeModal(); });
1236
+ }
1237
+ var safeModal = $('safe-modal');
1238
+ if (safeModal) {
1239
+ safeModal.addEventListener('click', function (e) {
1240
+ if (e.target === safeModal) closeSafeModal();
1241
+ });
1242
+ }
1243
+
1244
+ // ---- Quick action button wiring (called after render) ----
1245
+ var SAFE_TITLE_MAP = {
1246
+ 'safe-result': 'Safe Result',
1247
+ 'safe-audit': 'Safe Audit',
1248
+ 'safe-test-summary': 'Safe Test Summary',
1249
+ 'safe-diff-summary': 'Safe Diff Summary'
1250
+ };
1251
+
1252
+ function wireQuickActions() {
1253
+ var tbody = $('taskTableBody');
1254
+ if (!tbody) return;
1255
+ tbody.querySelectorAll('[data-safe-action]').forEach(function (btn) {
1256
+ btn.addEventListener('click', function (e) {
1257
+ e.preventDefault();
1258
+ e.stopPropagation();
1259
+ var action = btn.getAttribute('data-safe-action');
1260
+ var tid = btn.getAttribute('data-task-id');
1261
+ fetchSafeSummary(tid, action, SAFE_TITLE_MAP[action] || action);
1262
+ });
1263
+ });
1264
+ tbody.querySelectorAll('[data-copy-id]').forEach(function (btn) {
1265
+ btn.addEventListener('click', function (e) {
1266
+ e.preventDefault();
1267
+ e.stopPropagation();
1268
+ copyToClipboard(btn.getAttribute('data-copy-id'), 'task_id');
1269
+ });
1270
+ });
1271
+ }
1272
+
1273
+ // Initial load: populate filter dropdowns, then fetch tasks
1274
+ // Dropdowns load in parallel (best-effort); tasks fetch runs regardless.
1275
+ Promise.allSettled([loadRepos(), loadAgents()]).finally(function () {
1276
+ fetchTasks();
1277
+ });
697
1278
  })();
698
1279
  </script>
699
1280
 
700
1281
  <script>lucide.createIcons();</script>
1282
+ <script src="/i18n.js"></script>
1283
+ <script src="/desktop-bridge.js"></script>
701
1284
  </body>
702
1285
  </html>