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
@@ -101,7 +101,79 @@ html.dark {
101
101
  .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
102
102
  .pw-spin { animation: pw-spin 1s linear infinite; }
103
103
  @keyframes pw-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
104
+ /* Toast notifications */
105
+ .pw-toast {
106
+ pointer-events: auto;
107
+ display: flex;
108
+ align-items: flex-start;
109
+ gap: 8px;
110
+ padding: 10px 12px;
111
+ border-radius: 8px;
112
+ border: 1px solid var(--pw-border);
113
+ background-color: var(--pw-bg-surface);
114
+ box-shadow: var(--pw-shadow-float);
115
+ font-family: var(--pw-font-sans);
116
+ font-size: 12px;
117
+ color: var(--pw-text-primary);
118
+ animation: pw-toast-in 200ms ease;
119
+ max-width: 360px;
120
+ }
121
+ .pw-toast.pw-toast-success { border-left: 3px solid var(--pw-state-success); }
122
+ .pw-toast.pw-toast-warning { border-left: 3px solid var(--pw-state-warning); }
123
+ .pw-toast.pw-toast-error { border-left: 3px solid var(--pw-state-error); }
124
+ .pw-toast.pw-toast-info { border-left: 3px solid var(--pw-state-info); }
125
+ .pw-toast .pw-toast-title { font-weight: 600; margin-bottom: 2px; }
126
+ .pw-toast .pw-toast-msg { color: var(--pw-text-secondary); word-break: break-word; }
127
+ .pw-toast .pw-toast-close { margin-left: auto; cursor: pointer; color: var(--pw-text-tertiary); background: transparent; border: 0; font-size: 14px; line-height: 1; padding: 0 2px; }
128
+ .pw-toast .pw-toast-close:hover { color: var(--pw-text-primary); }
129
+ @keyframes pw-toast-in {
130
+ from { opacity: 0; transform: translateX(20px); }
131
+ to { opacity: 1; transform: translateX(0); }
132
+ }
133
+ .pw-toast.pw-toast-leaving { animation: pw-toast-out 200ms ease forwards; }
134
+ @keyframes pw-toast-out {
135
+ to { opacity: 0; transform: translateX(20px); }
136
+ }
137
+ /* Quick action buttons */
138
+ .pw-quick-btn {
139
+ display: inline-flex;
140
+ align-items: center;
141
+ gap: 4px;
142
+ padding: 3px 8px;
143
+ border-radius: 4px;
144
+ border: 1px solid var(--pw-border-subtle);
145
+ background-color: var(--pw-bg-hover);
146
+ color: var(--pw-text-secondary);
147
+ cursor: pointer;
148
+ transition: background-color var(--pw-transition-fast), color var(--pw-transition-fast);
149
+ text-decoration: none;
150
+ font-size: 11px;
151
+ font-family: var(--pw-font-sans);
152
+ white-space: nowrap;
153
+ }
154
+ .pw-quick-btn:hover {
155
+ background-color: var(--pw-bg-active);
156
+ color: var(--pw-text-primary);
157
+ }
158
+ .pw-quick-btn:disabled {
159
+ opacity: 0.5;
160
+ cursor: not-allowed;
161
+ }
162
+ .pw-quick-btn.pw-quick-danger:hover {
163
+ background-color: var(--pw-state-error-bg);
164
+ color: var(--pw-state-error);
165
+ border-color: var(--pw-state-error);
166
+ }
167
+ .pw-quick-btn.pw-quick-accent {
168
+ color: var(--pw-accent);
169
+ border-color: var(--pw-accent-muted);
170
+ }
171
+ .pw-quick-btn.pw-quick-accent:hover {
172
+ background-color: var(--pw-accent-muted);
173
+ }
104
174
  </style>
175
+ <script src="/desktop-bootstrap.js"></script>
176
+ <link rel="stylesheet" href="/desktop.css">
105
177
  </head>
106
178
  <body class="flex h-screen overflow-hidden">
107
179
 
@@ -119,6 +191,10 @@ html.dark {
119
191
 
120
192
  <!-- Navigation -->
121
193
  <nav class="flex-1 flex flex-col gap-1 px-3 pt-4 overflow-y-auto">
194
+ <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);">
195
+ <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>
196
+ <span class="truncate" data-i18n="nav.gettingStarted">开始使用</span>
197
+ </a>
122
198
 
123
199
  <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);">
124
200
  <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">
@@ -174,6 +250,10 @@ html.dark {
174
250
  <span class="truncate" data-zh="日志" data-en="Logs">日志</span>
175
251
  </a>
176
252
 
253
+ <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);">
254
+ <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>
255
+ <span class="truncate" data-i18n="nav.settings">设置</span>
256
+ </a>
177
257
  </nav>
178
258
 
179
259
  <!-- Bottom Section -->
@@ -198,6 +278,12 @@ html.dark {
198
278
  Direct 会话
199
279
  </h1>
200
280
  <div class="flex items-center gap-4 shrink-0">
281
+ <select id="session-state" class="px-2 py-1.5 rounded-md text-xs font-medium" 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);">
282
+ <option value="active">进行中</option>
283
+ <option value="archive">历史记录</option>
284
+ <option value="audited">仅审计完成</option>
285
+ <option value="expired">仅已过期</option>
286
+ </select>
201
287
  <span id="lastUpdated" class="text-xs whitespace-nowrap hidden" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);"></span>
202
288
  <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);">
203
289
  <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">
@@ -216,6 +302,18 @@ html.dark {
216
302
  <!-- Page Content Area -->
217
303
  <main class="flex-1 overflow-y-auto p-6" style="background-color: var(--pw-bg-elevated);">
218
304
 
305
+ <div class="mb-4 flex flex-wrap items-end gap-3 rounded-lg px-4 py-3" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
306
+ <label class="grid gap-1 text-xs" style="color: var(--pw-text-tertiary);"><span data-zh="项目" data-en="Project">项目</span><select id="session-repo" aria-label="Direct 会话项目" class="px-2 py-1.5 rounded-md text-xs" style="min-width:180px;background-color:var(--pw-bg-elevated);color:var(--pw-text-primary);border:1px solid var(--pw-border-subtle);"><option value="">全部项目</option></select></label>
307
+ <label class="grid gap-1 text-xs" style="color: var(--pw-text-tertiary);"><span data-zh="开始日期" data-en="From date">开始日期</span><input id="session-date-from" type="date" aria-label="Direct 会话开始日期" class="px-2 py-1.5 rounded-md text-xs" style="background-color:var(--pw-bg-elevated);color:var(--pw-text-primary);border:1px solid var(--pw-border-subtle);"></label>
308
+ <label class="grid gap-1 text-xs" style="color: var(--pw-text-tertiary);"><span data-zh="结束日期" data-en="To date">结束日期</span><input id="session-date-to" type="date" aria-label="Direct 会话结束日期" class="px-2 py-1.5 rounded-md text-xs" style="background-color:var(--pw-bg-elevated);color:var(--pw-text-primary);border:1px solid var(--pw-border-subtle);"></label>
309
+ </div>
310
+
311
+ <section id="direct-disabled" class="hidden rounded-lg p-6 mb-4" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
312
+ <h2 class="text-base font-semibold" style="color:var(--pw-text-primary);" data-zh="Direct 是可选高级能力" data-en="Direct is an optional advanced capability">Direct 是可选高级能力</h2>
313
+ <p class="mt-2 text-sm" style="color:var(--pw-text-secondary);" data-zh="未启用不会影响 Core、Tunnel 或普通任务。需要受控的直接编辑会话时,可在设置中启用。" data-en="Leaving Direct disabled does not affect Core, Tunnel, or regular tasks. Enable it in Settings when you need controlled direct editing sessions.">未启用不会影响 Core、Tunnel 或普通任务。需要受控的直接编辑会话时,可在设置中启用。</p>
314
+ <a href="/pages/settings.html" class="inline-flex mt-4 px-3 py-2 rounded-md text-sm font-semibold" style="background-color:var(--pw-accent);color:var(--pw-text-inverse);" data-zh="前往设置" data-en="Open Settings">前往设置</a>
315
+ </section>
316
+
219
317
  <!-- ===== Error Banner ===== -->
220
318
  <div id="error-banner" class="hidden mb-5 rounded-lg px-4 py-3 flex items-start gap-3" style="background-color: var(--pw-state-error-bg); border: 1px solid var(--pw-state-error);">
221
319
  <svg class="shrink-0 mt-0.5" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--pw-state-error)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -257,10 +355,25 @@ html.dark {
257
355
 
258
356
  <!-- ===== Sessions List ===== -->
259
357
  <div id="sessions-list" class="hidden flex flex-col gap-3"></div>
358
+ <div id="sessions-more-wrap" class="hidden mt-4 text-center"><button id="sessions-more" type="button" class="px-4 py-2 rounded-md text-sm font-medium" style="background-color:var(--pw-bg-surface);color:var(--pw-text-primary);border:1px solid var(--pw-border);" data-zh="加载更多" data-en="Load more">加载更多</button></div>
260
359
 
261
360
  <!-- ===== Session Detail ===== -->
262
361
  <div id="session-detail" class="hidden flex flex-col gap-6"></div>
263
362
 
363
+ <!-- ===== Toast Container ===== -->
364
+ <div id="toast-container" class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm pointer-events-none"></div>
365
+
366
+ <!-- ===== SAFE MODAL ===== -->
367
+ <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);">
368
+ <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);">
369
+ <div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
370
+ <h3 id="safe-modal-title" class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">—</h3>
371
+ <button id="safe-modal-close" class="text-lg leading-none" style="color: var(--pw-text-tertiary);" type="button" title="关闭" aria-label="关闭结果面板">×</button>
372
+ </div>
373
+ <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>
374
+ </div>
375
+ </div>
376
+
264
377
  </main>
265
378
  </div>
266
379
 
@@ -288,6 +401,9 @@ html.dark {
288
401
  'use strict';
289
402
 
290
403
  var API_URL = '/api/direct-sessions';
404
+ var controlToken = null;
405
+ var loadedSessions = [];
406
+ var nextCursor = null;
291
407
 
292
408
  function $(id) { return document.getElementById(id); }
293
409
  function hide(el) { if (el) el.classList.add('hidden'); }
@@ -316,6 +432,13 @@ html.dark {
316
432
  + ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds());
317
433
  }
318
434
 
435
+ function shortId(sid) {
436
+ if (!sid) return '—';
437
+ var s = String(sid);
438
+ if (s.length <= 20) return s;
439
+ return s.slice(0, 12) + '…' + s.slice(-6);
440
+ }
441
+
319
442
  function showError(msg) {
320
443
  var banner = $('error-banner');
321
444
  var message = $('error-message');
@@ -340,6 +463,7 @@ html.dark {
340
463
 
341
464
  function showLoading() {
342
465
  show($('loading-state'));
466
+ hide($('direct-disabled'));
343
467
  hide($('empty-state'));
344
468
  hide($('sessions-list'));
345
469
  hide($('session-detail'));
@@ -349,6 +473,8 @@ html.dark {
349
473
  show($('empty-state'));
350
474
  hide($('sessions-list'));
351
475
  hide($('session-detail'));
476
+ hide($('direct-disabled'));
477
+ hide($('sessions-more-wrap'));
352
478
  var rEl = $('empty-reason');
353
479
  if (rEl) rEl.textContent = reason || '';
354
480
  }
@@ -357,12 +483,24 @@ html.dark {
357
483
  hide($('empty-state'));
358
484
  show($('sessions-list'));
359
485
  hide($('session-detail'));
486
+ hide($('direct-disabled'));
360
487
  }
361
488
  function showDetail() {
362
489
  hide($('loading-state'));
363
490
  hide($('empty-state'));
364
491
  hide($('sessions-list'));
365
492
  show($('session-detail'));
493
+ hide($('direct-disabled'));
494
+ hide($('sessions-more-wrap'));
495
+ }
496
+
497
+ function showDirectDisabled() {
498
+ hide($('loading-state'));
499
+ hide($('empty-state'));
500
+ hide($('sessions-list'));
501
+ hide($('session-detail'));
502
+ hide($('sessions-more-wrap'));
503
+ show($('direct-disabled'));
366
504
  }
367
505
 
368
506
  function verdictColor(v) {
@@ -377,7 +515,8 @@ html.dark {
377
515
  success: { fg: 'var(--pw-state-success)', bg: 'var(--pw-state-success-bg)' },
378
516
  warning: { fg: 'var(--pw-state-warning)', bg: 'var(--pw-state-warning-bg)' },
379
517
  error: { fg: 'var(--pw-state-error)', bg: 'var(--pw-state-error-bg)' },
380
- muted: { fg: 'var(--pw-text-tertiary)', bg: 'var(--pw-bg-hover)' }
518
+ muted: { fg: 'var(--pw-text-tertiary)', bg: 'var(--pw-bg-hover)' },
519
+ info: { fg: 'var(--pw-state-info)', bg: 'var(--pw-state-info-bg)' }
381
520
  };
382
521
 
383
522
  function badgeHtml(text, color) {
@@ -385,7 +524,131 @@ html.dark {
385
524
  return '<span class="text-xs px-2 py-0.5 rounded-md whitespace-nowrap" style="color: ' + c.fg + '; background-color: ' + c.bg + ';">' + escapeHtml(text) + '</span>';
386
525
  }
387
526
 
388
- // ---------- Render: sessions list ----------
527
+ // ---------- Toast notifications ----------
528
+ function showToast(kind, title, msg) {
529
+ var container = $('toast-container');
530
+ if (!container) return;
531
+ var el = document.createElement('div');
532
+ el.className = 'pw-toast pw-toast-' + (kind || 'info');
533
+ el.innerHTML =
534
+ '<div class="min-w-0 flex-1">'
535
+ + '<div class="pw-toast-title">' + escapeHtml(title || '') + '</div>'
536
+ + (msg ? '<div class="pw-toast-msg">' + escapeHtml(msg) + '</div>' : '')
537
+ + '</div>'
538
+ + '<button type="button" class="pw-toast-close" title="关闭" aria-label="关闭通知">×</button>';
539
+ var closeBtn = el.querySelector('.pw-toast-close');
540
+ if (closeBtn) closeBtn.addEventListener('click', function () { dismissToast(el); });
541
+ container.appendChild(el);
542
+ setTimeout(function () { dismissToast(el); }, 8000);
543
+ }
544
+
545
+ function dismissToast(el) {
546
+ if (!el || el.classList.contains('pw-toast-leaving')) return;
547
+ el.classList.add('pw-toast-leaving');
548
+ setTimeout(function () {
549
+ if (el.parentNode) el.parentNode.removeChild(el);
550
+ }, 220);
551
+ }
552
+
553
+ // ---------- Clipboard + Safe Modal ----------
554
+ function copyToClipboard(text, label) {
555
+ if (!text) return;
556
+ if (navigator.clipboard && navigator.clipboard.writeText) {
557
+ navigator.clipboard.writeText(text).then(function () {
558
+ showToast('success', '已复制', (label || '内容') + '已复制到剪贴板');
559
+ }).catch(function () {
560
+ showToast('error', '复制失败', '请手动复制');
561
+ });
562
+ } else {
563
+ showToast('warning', '复制不支持', '浏览器不支持剪贴板 API');
564
+ }
565
+ }
566
+
567
+ function showSafeModal(title, data) {
568
+ var modal = $('safe-modal');
569
+ var titleEl = $('safe-modal-title');
570
+ var bodyEl = $('safe-modal-body');
571
+ if (!modal || !titleEl || !bodyEl) return;
572
+ titleEl.textContent = title || '—';
573
+ var content;
574
+ if (data && typeof data === 'object') {
575
+ content = JSON.stringify(data, null, 2);
576
+ } else {
577
+ content = String(data || '');
578
+ }
579
+ bodyEl.textContent = content;
580
+ modal.classList.remove('hidden');
581
+ }
582
+
583
+ function closeSafeModal() {
584
+ var modal = $('safe-modal');
585
+ if (modal) modal.classList.add('hidden');
586
+ }
587
+
588
+ // ---------- Control token + POST actions ----------
589
+ async function loadToken() {
590
+ try {
591
+ const res = await fetch('/control-token.json');
592
+ if (!res.ok) throw new Error('HTTP ' + res.status);
593
+ const data = await res.json();
594
+ controlToken = data.token || null;
595
+ } catch (err) {
596
+ controlToken = null;
597
+ console.error('loadToken failed:', err);
598
+ }
599
+ }
600
+
601
+ async function postAction(url) {
602
+ if (!controlToken) await loadToken();
603
+ const res = await fetch(url, {
604
+ method: 'POST',
605
+ headers: { 'X-PatchWarden-Control-Token': controlToken || '' }
606
+ });
607
+ let data = null;
608
+ try { data = await res.json(); } catch (_) { /* ignore */ }
609
+ if (!res.ok) {
610
+ const msg = (data && (data.error || data.reason || data.message)) || ('HTTP ' + res.status);
611
+ throw new Error(msg);
612
+ }
613
+ return data;
614
+ }
615
+
616
+ // ---------- Session classification ----------
617
+ function classifySession(s) {
618
+ var nowMs = Date.now();
619
+ var expiresMs = s.expires_at ? Date.parse(s.expires_at) : NaN;
620
+ var expired = Number.isFinite(expiresMs) && expiresMs < nowMs;
621
+ if (expired) return 'expired';
622
+ if (s.audited) return 'audited';
623
+ if (s.finalized) return 'finalized';
624
+ return 'active';
625
+ }
626
+
627
+ function verificationStatusText(vs) {
628
+ if (!vs || typeof vs !== 'object') return '—';
629
+ // verification_summary is the last run object: { command, exit_code, passed, timed_out, ... }
630
+ if (vs.passed === true) return 'passed';
631
+ if (vs.timed_out === true) return 'timed_out';
632
+ if (vs.passed === false) return 'failed';
633
+ if (vs.exit_code != null) return String(vs.exit_code);
634
+ return '—';
635
+ }
636
+
637
+ function verificationStatusColor(vs) {
638
+ if (!vs || typeof vs !== 'object') return 'muted';
639
+ if (vs.passed === true) return 'success';
640
+ if (vs.timed_out === true || vs.passed === false) return 'error';
641
+ return 'muted';
642
+ }
643
+
644
+ // ---------- Render: sessions list (grouped) ----------
645
+ var GROUP_LABELS = {
646
+ active: { zh: '进行中', en: 'Active', color: 'info' },
647
+ finalized: { zh: '已 Finalize', en: 'Finalized', color: 'success' },
648
+ audited: { zh: '已审计', en: 'Audited', color: 'success' },
649
+ expired: { zh: '已过期', en: 'Expired', color: 'warning' }
650
+ };
651
+
389
652
  function renderSessions(sessions) {
390
653
  var listEl = $('sessions-list');
391
654
  listEl.innerHTML = '';
@@ -394,69 +657,299 @@ html.dark {
394
657
  return;
395
658
  }
396
659
 
397
- var html = '';
660
+ var groups = { active: [], finalized: [], audited: [], expired: [] };
398
661
  for (var i = 0; i < sessions.length; i++) {
399
662
  var s = sessions[i] || {};
400
- var sid = s.session_id || '—';
401
- var finalized = !!s.finalized;
402
- var audited = !!s.audited;
403
- var changedTotal = s.changed_files_total != null ? s.changed_files_total : '—';
404
- var decision = s.audit_decision || null;
405
- var decisionBadge = decision ? badgeHtml(decision, verdictColor(decision)) : '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover);">not audited</span>';
406
- var stateBadge = finalized
407
- ? badgeHtml('finalized', 'success')
408
- : badgeHtml('open', 'warning');
409
-
410
- var titleText = s.title || s.repo_path || sid;
411
-
412
- html +=
413
- '<div class="rounded-lg p-4 cursor-pointer transition-colors session-card" data-session-id="' + escapeHtml(sid) + '" '
414
- + 'onmouseover="this.style.backgroundColor=\'var(--pw-bg-hover)\'" '
415
- + 'onmouseout="this.style.backgroundColor=\'var(--pw-bg-surface)\'" '
416
- + 'style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">'
417
- + '<div class="flex items-start justify-between gap-3 mb-3">'
418
- + '<div class="min-w-0 flex-1">'
419
- + '<div class="flex items-center gap-2 mb-1">'
420
- + '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(titleText) + '</h3>'
421
- + '</div>'
422
- + '<code class="text-xs break-all" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);">' + escapeHtml(sid) + '</code>'
423
- + '</div>'
424
- + '<div class="flex items-center gap-1.5 shrink-0 flex-wrap justify-end">'
425
- + stateBadge + decisionBadge
426
- + '</div>'
427
- + '</div>'
428
- + '<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs">'
429
- + '<div class="flex flex-col gap-0.5 min-w-0">'
430
- + '<span class="text-xs" style="color: var(--pw-text-tertiary);">repo_path</span>'
431
- + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(s.repo_path || '—') + '</span>'
432
- + '</div>'
433
- + '<div class="flex flex-col gap-0.5 min-w-0">'
434
- + '<span class="text-xs" style="color: var(--pw-text-tertiary);">changed_files_total</span>'
435
- + '<span class="truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(changedTotal)) + '</span>'
436
- + '</div>'
437
- + '<div class="flex flex-col gap-0.5 min-w-0">'
438
- + '<span class="text-xs" style="color: var(--pw-text-tertiary);">created_at</span>'
439
- + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.created_at)) + '</span>'
440
- + '</div>'
441
- + '<div class="flex flex-col gap-0.5 min-w-0">'
442
- + '<span class="text-xs" style="color: var(--pw-text-tertiary);">expires_at</span>'
443
- + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.expires_at)) + '</span>'
444
- + '</div>'
445
- + '</div>'
446
- + '</div>';
663
+ var cls = classifySession(s);
664
+ groups[cls].push(s);
665
+ }
666
+
667
+ var html = '';
668
+ // Active section (expanded)
669
+ if (groups.active.length > 0) {
670
+ html += renderGroupHeader('active', groups.active.length, false);
671
+ html += renderSessionCards(groups.active, false);
672
+ }
673
+ // Finalized section (expanded)
674
+ if (groups.finalized.length > 0) {
675
+ html += renderGroupHeader('finalized', groups.finalized.length, false);
676
+ html += renderSessionCards(groups.finalized, false);
677
+ }
678
+ // Audited section (expanded)
679
+ if (groups.audited.length > 0) {
680
+ html += renderGroupHeader('audited', groups.audited.length, false);
681
+ html += renderSessionCards(groups.audited, false);
682
+ }
683
+ // Expired section (collapsed via <details>)
684
+ if (groups.expired.length > 0) {
685
+ html += renderExpiredGroup(groups.expired);
447
686
  }
687
+
448
688
  listEl.innerHTML = html;
449
689
  showList();
690
+ wireActionButtons();
691
+ wireSessionCards();
692
+ }
693
+
694
+ function renderGroupHeader(key, count, isExpired) {
695
+ var meta = GROUP_LABELS[key] || GROUP_LABELS.active;
696
+ var c = COLOR_VARS[meta.color] || COLOR_VARS.muted;
697
+ return '<div class="flex items-center gap-2 mt-2 mb-1 px-1">'
698
+ + '<span class="w-2 h-2 rounded-full" style="background-color: ' + c.fg + ';"></span>'
699
+ + '<h2 class="text-sm font-semibold" data-zh="' + escapeHtml(meta.zh) + '" data-en="' + escapeHtml(meta.en) + '" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(meta.zh) + '</h2>'
700
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">(' + count + ')</span>'
701
+ + '</div>';
702
+ }
703
+
704
+ function renderExpiredGroup(sessions) {
705
+ var meta = GROUP_LABELS.expired;
706
+ var c = COLOR_VARS[meta.color] || COLOR_VARS.muted;
707
+ var headerHtml =
708
+ '<summary class="flex items-center gap-2 cursor-pointer px-3 py-2" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans;">'
709
+ + '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0"><polyline points="6 9 12 15 18 9"/></svg>'
710
+ + '<span class="w-2 h-2 rounded-full" style="background-color: ' + c.fg + ';"></span>'
711
+ + '<span class="text-sm font-semibold" data-zh="' + escapeHtml(meta.zh) + '" data-en="' + escapeHtml(meta.en) + '">' + escapeHtml(meta.zh) + '</span>'
712
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">(' + sessions.length + ')</span>'
713
+ + '<span class="ml-auto text-xs" style="color: var(--pw-text-tertiary);">点击展开 / 折叠</span>'
714
+ + '</summary>';
715
+ return '<details class="rounded-lg mt-2" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">'
716
+ + headerHtml
717
+ + '<div class="px-3 pb-3 pt-1 flex flex-col gap-3">'
718
+ + renderSessionCards(sessions, true)
719
+ + '</div>'
720
+ + '</details>';
721
+ }
722
+
723
+ function renderSessionCards(sessions, isExpired) {
724
+ var html = '';
725
+ for (var i = 0; i < sessions.length; i++) {
726
+ html += renderSessionCard(sessions[i] || {}, isExpired);
727
+ }
728
+ return html;
729
+ }
730
+
731
+ function renderSessionCard(s, isExpired) {
732
+ var sid = s.session_id || '—';
733
+ var finalized = !!s.finalized;
734
+ var audited = !!s.audited;
735
+ var changedTotal = s.changed_files_total != null ? s.changed_files_total : '—';
736
+ var decision = s.audit_decision || null;
737
+ var decisionBadge = decision
738
+ ? badgeHtml(decision, verdictColor(decision))
739
+ : '<span class="text-xs px-2 py-0.5 rounded-md" style="color: var(--pw-text-tertiary); background-color: var(--pw-bg-hover);">未审计</span>';
740
+ var stateBadge = finalized
741
+ ? badgeHtml('已完成', 'success')
742
+ : (isExpired ? badgeHtml('已过期', 'warning') : badgeHtml('进行中', 'warning'));
743
+ var auditedBadge = audited ? badgeHtml('已审计', 'success') : '';
744
+ var titleText = s.title || s.repo_path || sid;
745
+
746
+ // Verification status summary
747
+ var vs = s.verification_summary || null;
748
+ var vsText = verificationStatusText(vs);
749
+ var vsColor = verificationStatusColor(vs);
750
+ var vsBadge = badgeHtml(vsText, vsColor);
751
+
752
+ // Quick action buttons
753
+ var actions = '';
754
+ // safe_direct_summary
755
+ actions += quickBtnHtml('summary', '查看摘要', 'View summary', sid, '查看脱敏后的安全摘要', 'View the redacted safe summary', false, false);
756
+ // run_direct_verification_bundle — informational button (links to tasks page)
757
+ actions += quickBtnHtml('verify-bundle', '创建验证任务', 'Create verification task', sid, '前往任务页创建受控验证任务', 'Open Tasks to create a controlled verification task', false, false);
758
+ // safe_finalize_direct_session (disabled if already finalized)
759
+ actions += quickBtnHtml('finalize', '完成会话', 'Finalize session', sid, '完成并生成会话摘要', 'Finalize and generate the session summary', finalized, true);
760
+ // safe_audit_direct_session (disabled if already audited)
761
+ actions += quickBtnHtml('audit', '运行审计', 'Run audit', sid, '运行安全审计', 'Run the security audit', audited, true);
762
+ // copy session_id
763
+ actions += quickBtnHtml('copy', '复制 ID', 'Copy ID', sid, '复制会话 ID', 'Copy session ID', false, false);
764
+ // hide (expired only)
765
+ if (isExpired) {
766
+ actions += quickBtnHtml('hide', '隐藏记录', 'Hide record', sid, '仅从列表隐藏,不删除证据', 'Hide from the list without deleting evidence', false, false, true);
767
+ }
768
+
769
+ return '<div class="rounded-lg p-4 transition-colors session-card" role="button" tabindex="0" aria-label="查看 Direct 会话 ' + escapeHtml(shortId(sid)) + '" data-session-id="' + escapeHtml(sid) + '" '
770
+ + 'onmouseover="this.style.backgroundColor=\'var(--pw-bg-hover)\'" '
771
+ + 'onmouseout="this.style.backgroundColor=\'var(--pw-bg-surface)\'" '
772
+ + 'style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border); cursor: pointer;">'
773
+ + '<div class="flex items-start justify-between gap-3 mb-3">'
774
+ + '<div class="min-w-0 flex-1">'
775
+ + '<div class="flex items-center gap-2 mb-1">'
776
+ + '<h3 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-sans);">' + escapeHtml(titleText) + '</h3>'
777
+ + '</div>'
778
+ + '<div class="flex items-center gap-2 min-w-0">'
779
+ + '<code class="text-xs truncate" style="color: var(--pw-text-tertiary); font-family: var(--pw-font-mono);" title="' + escapeHtml(sid) + '">' + escapeHtml(shortId(sid)) + '</code>'
780
+ + '</div>'
781
+ + '</div>'
782
+ + '<div class="flex items-center gap-1.5 shrink-0 flex-wrap justify-end">'
783
+ + stateBadge + auditedBadge + decisionBadge
784
+ + '</div>'
785
+ + '</div>'
786
+ + '<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs mb-3">'
787
+ + '<div class="flex flex-col gap-0.5 min-w-0">'
788
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">repo_path</span>'
789
+ + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);" title="' + escapeHtml(s.repo_path || '') + '">' + escapeHtml(s.repo_path || '—') + '</span>'
790
+ + '</div>'
791
+ + '<div class="flex flex-col gap-0.5 min-w-0">'
792
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">changed_files_total</span>'
793
+ + '<span class="truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(changedTotal)) + '</span>'
794
+ + '</div>'
795
+ + '<div class="flex flex-col gap-0.5 min-w-0">'
796
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">created_at</span>'
797
+ + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.created_at)) + '</span>'
798
+ + '</div>'
799
+ + '<div class="flex flex-col gap-0.5 min-w-0">'
800
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">expires_at</span>'
801
+ + '<span class="truncate" style="color: var(--pw-text-secondary); font-family: var(--pw-font-mono);">' + escapeHtml(fmtTimeShort(s.expires_at)) + '</span>'
802
+ + '</div>'
803
+ + '</div>'
804
+ + '<div class="flex items-center gap-2 mb-3 text-xs">'
805
+ + '<span class="text-xs" style="color: var(--pw-text-tertiary);">verification:</span>'
806
+ + vsBadge
807
+ + '</div>'
808
+ + '<div class="flex items-center gap-1.5 flex-wrap" onclick="event.stopPropagation();">'
809
+ + actions
810
+ + '</div>'
811
+ + '</div>';
812
+ }
813
+
814
+ function quickBtnHtml(action, labelZh, labelEn, sid, titleZh, titleEn, disabled, accent, danger) {
815
+ var cls = 'pw-quick-btn';
816
+ if (accent) cls += ' pw-quick-accent';
817
+ if (danger) cls += ' pw-quick-danger';
818
+ var disAttr = disabled ? ' disabled' : '';
819
+ return '<button type="button" class="' + cls + '" data-action="' + escapeHtml(action) + '" data-session-id="' + escapeHtml(sid) + '" title="' + escapeHtml(titleZh) + '" aria-label="' + escapeHtml(titleZh) + '" data-zh="' + escapeHtml(labelZh) + '" data-en="' + escapeHtml(labelEn) + '"' + disAttr + '>'
820
+ + escapeHtml(labelZh)
821
+ + '</button>';
822
+ }
823
+
824
+ // ---------- Action button wiring ----------
825
+ function wireActionButtons() {
826
+ var btns = document.querySelectorAll('#sessions-list .pw-quick-btn[data-action]');
827
+ for (var i = 0; i < btns.length; i++) {
828
+ var btn = btns[i];
829
+ // Avoid double-binding on re-render.
830
+ if (btn.__pwBound) continue;
831
+ btn.__pwBound = true;
832
+ btn.addEventListener('click', function (ev) {
833
+ ev.preventDefault();
834
+ ev.stopPropagation();
835
+ handleActionClick(ev.currentTarget);
836
+ });
837
+ }
838
+ }
450
839
 
451
- var cards = listEl.querySelectorAll('.session-card');
840
+ function wireSessionCards() {
841
+ var cards = $('sessions-list').querySelectorAll('.session-card');
452
842
  for (var j = 0; j < cards.length; j++) {
453
- cards[j].addEventListener('click', function (ev) {
843
+ var card = cards[j];
844
+ if (card.__pwBound) continue;
845
+ card.__pwBound = true;
846
+ card.addEventListener('click', function (ev) {
847
+ // Ignore clicks that originated on a button/details summary.
848
+ if (ev.target.closest('.pw-quick-btn') || ev.target.closest('summary')) return;
849
+ var sid2 = ev.currentTarget.getAttribute('data-session-id');
850
+ if (sid2) loadSessionDetail(sid2);
851
+ });
852
+ card.addEventListener('keydown', function (ev) {
853
+ if (ev.key !== 'Enter' && ev.key !== ' ') return;
854
+ if (ev.target.closest('.pw-quick-btn') || ev.target.closest('summary')) return;
855
+ ev.preventDefault();
454
856
  var sid2 = ev.currentTarget.getAttribute('data-session-id');
455
857
  if (sid2) loadSessionDetail(sid2);
456
858
  });
457
859
  }
458
860
  }
459
861
 
862
+ function handleActionClick(btn) {
863
+ var action = btn.getAttribute('data-action');
864
+ var sid = btn.getAttribute('data-session-id');
865
+ if (!action || !sid) return;
866
+ if (action === 'summary') {
867
+ loadSafeSummary(sid);
868
+ } else if (action === 'finalize') {
869
+ finalizeSession(sid, btn);
870
+ } else if (action === 'audit') {
871
+ auditSession(sid, btn);
872
+ } else if (action === 'copy') {
873
+ copyToClipboard(sid, 'session_id');
874
+ } else if (action === 'hide') {
875
+ hideSession(sid, btn);
876
+ } else if (action === 'verify-bundle') {
877
+ // Informational: navigate to tasks page where verification tasks are created.
878
+ showToast('info', '跳转任务页', 'run_direct_verification_bundle 需在任务页发起');
879
+ setTimeout(function () { window.location.href = '/pages/tasks.html'; }, 600);
880
+ }
881
+ }
882
+
883
+ function loadSafeSummary(sessionId) {
884
+ var url = '/api/direct-sessions/' + encodeURIComponent(sessionId) + '/summary';
885
+ fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' })
886
+ .then(function (res) { return res.json(); })
887
+ .then(function (data) {
888
+ showSafeModal('safe_direct_summary · ' + shortId(sessionId), data);
889
+ })
890
+ .catch(function (err) {
891
+ showToast('error', '加载失败', err.message || '未知错误');
892
+ });
893
+ }
894
+
895
+ function withBusy(btn, fn) {
896
+ var orig = btn.innerHTML;
897
+ btn.disabled = true;
898
+ btn.innerHTML = '<span class="inline-block w-3 h-3 align-middle border-2 border-current border-t-transparent rounded-full pw-spin"></span>';
899
+ Promise.resolve()
900
+ .then(fn)
901
+ .catch(function (err) {
902
+ showToast('error', '操作失败', err.message || '未知错误');
903
+ })
904
+ .finally(function () {
905
+ btn.disabled = false;
906
+ btn.innerHTML = orig;
907
+ });
908
+ }
909
+
910
+ function finalizeSession(sessionId, btn) {
911
+ withBusy(btn, function () {
912
+ return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/finalize')
913
+ .then(function (data) {
914
+ if (data && data.error) {
915
+ showToast('error', 'Finalize 失败', data.error);
916
+ return;
917
+ }
918
+ showToast('success', '已 Finalize', '会话 ' + shortId(sessionId) + ' 已完成 finalize');
919
+ loadSessions();
920
+ });
921
+ });
922
+ }
923
+
924
+ function auditSession(sessionId, btn) {
925
+ withBusy(btn, function () {
926
+ return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/audit')
927
+ .then(function (data) {
928
+ if (data && data.error) {
929
+ showToast('error', '审计失败', data.error);
930
+ return;
931
+ }
932
+ var decision = (data && data.decision) || '完成';
933
+ showToast('success', '审计完成', '会话 ' + shortId(sessionId) + ' 审计结果: ' + decision);
934
+ loadSessions();
935
+ });
936
+ });
937
+ }
938
+
939
+ function hideSession(sessionId, btn) {
940
+ withBusy(btn, function () {
941
+ return postAction('/api/direct-sessions/' + encodeURIComponent(sessionId) + '/hide')
942
+ .then(function (data) {
943
+ if (data && data.error) {
944
+ showToast('error', '隐藏失败', data.error);
945
+ return;
946
+ }
947
+ showToast('success', '已隐藏', '会话 ' + shortId(sessionId) + ' 已从列表隐藏');
948
+ loadSessions();
949
+ });
950
+ });
951
+ }
952
+
460
953
  // ---------- Render: session detail ----------
461
954
  function renderSessionDetail(data) {
462
955
  var el = $('session-detail');
@@ -473,7 +966,7 @@ html.dark {
473
966
  var vs = s.verification_summary || null;
474
967
  var vsHtml = '—';
475
968
  if (vs && typeof vs === 'object') {
476
- var vsStatus = vs.status || vs.verdict || '—';
969
+ var vsStatus = vs.status || vs.verdict || verificationStatusText(vs) || '—';
477
970
  var vsCmds = Array.isArray(vs.commands) ? vs.commands.map(function (c) { return JSON.stringify(c); }).join(' ') : '';
478
971
  vsHtml = '<span style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(String(vsStatus)) + '</span>'
479
972
  + (vsCmds ? '<div class="mt-1 text-xs break-all" style="color: var(--pw-text-tertiary);">' + escapeHtml(vsCmds) + '</div>' : '');
@@ -569,11 +1062,29 @@ html.dark {
569
1062
  }
570
1063
 
571
1064
  // ---------- Network ----------
572
- function loadSessions() {
1065
+ function updateRepoOptions(data) {
1066
+ var select = $('session-repo');
1067
+ if (!select) return;
1068
+ var selected = select.value;
1069
+ var repos = data && data.filters && data.filters.options && Array.isArray(data.filters.options.repos) ? data.filters.options.repos : [];
1070
+ select.innerHTML = '<option value="">全部项目</option>' + repos.map(function (repo) { return '<option value="' + escapeHtml(repo) + '">' + escapeHtml(repo) + '</option>'; }).join('');
1071
+ select.value = repos.indexOf(selected) >= 0 ? selected : '';
1072
+ }
1073
+
1074
+ function loadSessions(cursor, append) {
573
1075
  clearError();
574
- showLoading();
1076
+ if (!append) showLoading();
575
1077
  setRefreshing(true);
576
- fetch(API_URL, { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' })
1078
+ var state = $('session-state') ? $('session-state').value : 'active';
1079
+ var params = ['state=' + encodeURIComponent(state), 'limit=50'];
1080
+ var repo = $('session-repo') ? $('session-repo').value : '';
1081
+ var dateFrom = $('session-date-from') ? $('session-date-from').value : '';
1082
+ var dateTo = $('session-date-to') ? $('session-date-to').value : '';
1083
+ if (repo) params.push('repo_path=' + encodeURIComponent(repo));
1084
+ if (dateFrom) params.push('date_from=' + encodeURIComponent(dateFrom));
1085
+ if (dateTo) params.push('date_to=' + encodeURIComponent(dateTo));
1086
+ if (cursor) params.push('cursor=' + encodeURIComponent(cursor));
1087
+ fetch(API_URL + '?' + params.join('&'), { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' })
577
1088
  .then(function (res) {
578
1089
  if (!res.ok) {
579
1090
  return res.text().then(function (body) {
@@ -584,7 +1095,23 @@ html.dark {
584
1095
  })
585
1096
  .then(function (data) {
586
1097
  data = data || {};
587
- renderSessions(data.sessions || []);
1098
+ updateRepoOptions(data);
1099
+ if (data.direct_profile_enabled === false) {
1100
+ loadedSessions = append ? loadedSessions.concat(data.sessions || []) : (data.sessions || []);
1101
+ nextCursor = data.nextCursor || data.next_cursor || null;
1102
+ if (loadedSessions.length) {
1103
+ renderSessions(loadedSessions);
1104
+ show($('direct-disabled'));
1105
+ nextCursor ? show($('sessions-more-wrap')) : hide($('sessions-more-wrap'));
1106
+ } else {
1107
+ showDirectDisabled();
1108
+ }
1109
+ } else {
1110
+ loadedSessions = append ? loadedSessions.concat(data.sessions || []) : (data.sessions || []);
1111
+ nextCursor = data.nextCursor || data.next_cursor || null;
1112
+ renderSessions(loadedSessions);
1113
+ nextCursor ? show($('sessions-more-wrap')) : hide($('sessions-more-wrap'));
1114
+ }
588
1115
  setLastUpdated();
589
1116
  })
590
1117
  .catch(function (err) {
@@ -640,13 +1167,35 @@ html.dark {
640
1167
 
641
1168
  // ---------- Event wiring ----------
642
1169
  var refreshBtn = $('refreshBtn');
643
- if (refreshBtn) refreshBtn.addEventListener('click', loadSessions);
1170
+ if (refreshBtn) refreshBtn.addEventListener('click', function () { loadSessions(null, false); });
644
1171
  var retryBtn = $('retry-btn');
645
- if (retryBtn) retryBtn.addEventListener('click', loadSessions);
1172
+ if (retryBtn) retryBtn.addEventListener('click', function () { loadSessions(null, false); });
1173
+ var stateSelect = $('session-state');
1174
+ if (stateSelect) stateSelect.addEventListener('change', function () { loadSessions(null, false); });
1175
+ ['session-repo', 'session-date-from', 'session-date-to'].forEach(function (id) {
1176
+ var input = $(id);
1177
+ if (input) input.addEventListener('change', function () { loadSessions(null, false); });
1178
+ });
1179
+ var moreBtn = $('sessions-more');
1180
+ if (moreBtn) moreBtn.addEventListener('click', function () { if (nextCursor) loadSessions(nextCursor, true); });
1181
+
1182
+ var modalClose = $('safe-modal-close');
1183
+ if (modalClose) modalClose.addEventListener('click', closeSafeModal);
1184
+ // Click on modal backdrop closes it.
1185
+ var safeModal = $('safe-modal');
1186
+ if (safeModal) safeModal.addEventListener('click', function (ev) {
1187
+ if (ev.target === safeModal) closeSafeModal();
1188
+ });
1189
+ // ESC closes modal.
1190
+ document.addEventListener('keydown', function (ev) {
1191
+ if (ev.key === 'Escape') closeSafeModal();
1192
+ });
646
1193
 
647
1194
  // Initial load
648
- loadSessions();
1195
+ loadSessions(null, false);
649
1196
  })();
650
1197
  </script>
1198
+ <script src="/i18n.js"></script>
1199
+ <script src="/desktop-bridge.js"></script>
651
1200
  </body>
652
1201
  </html>