@surething/cockpit 1.0.217 → 1.0.219

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 (577) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +3 -2
  3. package/.next-prod/build-manifest.json +3 -3
  4. package/.next-prod/prerender-manifest.json +3 -3
  5. package/.next-prod/react-loadable-manifest.json +0 -1
  6. package/.next-prod/routes-manifest.json +6 -0
  7. package/.next-prod/server/app/_global-error/page.js +3 -3
  8. package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next-prod/server/app/_global-error.html +1 -1
  11. package/.next-prod/server/app/_global-error.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  14. package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  15. package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next-prod/server/app/_not-found/page.js +2 -2
  19. package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next-prod/server/app/_not-found.html +1 -1
  22. package/.next-prod/server/app/_not-found.rsc +3 -3
  23. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  24. package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
  26. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/.next-prod/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  29. package/.next-prod/server/app/api/agent/test/route.js +1 -1
  30. package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
  31. package/.next-prod/server/app/api/bash/route.js +1 -1
  32. package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
  33. package/.next-prod/server/app/api/chat/codex/route.js +2 -2
  34. package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
  35. package/.next-prod/server/app/api/chat/deepseek/route.js +2 -2
  36. package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
  37. package/.next-prod/server/app/api/chat/kimi/route.js +2 -2
  38. package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
  39. package/.next-prod/server/app/api/chat/ollama/route.js +2 -2
  40. package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
  41. package/.next-prod/server/app/api/chat/pty-input/route.js +1 -0
  42. package/.next-prod/server/app/api/chat/pty-input/route.js.nft.json +1 -0
  43. package/.next-prod/server/app/api/chat/pty-input/route_client-reference-manifest.js +1 -0
  44. package/.next-prod/server/app/api/chat/route.js +19 -3
  45. package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
  46. package/.next-prod/server/app/api/claude-stats/route.js +1 -1
  47. package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
  48. package/.next-prod/server/app/api/commands/route.js +1 -1
  49. package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
  50. package/.next-prod/server/app/api/comments/route.js +1 -1
  51. package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
  52. package/.next-prod/server/app/api/db/columns/route.js +1 -1
  53. package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
  54. package/.next-prod/server/app/api/db/connect/route.js +1 -1
  55. package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
  56. package/.next-prod/server/app/api/db/disconnect/route.js +1 -1
  57. package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
  58. package/.next-prod/server/app/api/db/export/route.js +1 -1
  59. package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
  60. package/.next-prod/server/app/api/db/query/route.js +1 -1
  61. package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
  62. package/.next-prod/server/app/api/db/schemas/route.js +1 -1
  63. package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
  64. package/.next-prod/server/app/api/dev/spans/route.js +1 -1
  65. package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
  66. package/.next-prod/server/app/api/extension/version/route.js +1 -1
  67. package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
  68. package/.next-prod/server/app/api/file/route.js +1 -1
  69. package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
  70. package/.next-prod/server/app/api/files/blame/route.js +1 -1
  71. package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
  72. package/.next-prod/server/app/api/files/clipboard/route.js +1 -1
  73. package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
  74. package/.next-prod/server/app/api/files/copy/route.js +1 -1
  75. package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
  76. package/.next-prod/server/app/api/files/delete/route.js +1 -1
  77. package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
  78. package/.next-prod/server/app/api/files/expanded/route.js +1 -1
  79. package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
  80. package/.next-prod/server/app/api/files/index/route.js +1 -1
  81. package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
  82. package/.next-prod/server/app/api/files/init/route.js +1 -1
  83. package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
  84. package/.next-prod/server/app/api/files/paste/route.js +1 -1
  85. package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
  86. package/.next-prod/server/app/api/files/read/route.js +1 -1
  87. package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
  88. package/.next-prod/server/app/api/files/readdir/route.js +1 -1
  89. package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
  90. package/.next-prod/server/app/api/files/recent/route.js +1 -1
  91. package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
  92. package/.next-prod/server/app/api/files/save/route.js +1 -1
  93. package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
  94. package/.next-prod/server/app/api/files/search/route.js +1 -1
  95. package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
  96. package/.next-prod/server/app/api/files/stat/route.js +1 -1
  97. package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
  98. package/.next-prod/server/app/api/files/text/route.js +1 -1
  99. package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
  100. package/.next-prod/server/app/api/git/branch-diff/route.js +1 -1
  101. package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
  102. package/.next-prod/server/app/api/git/branches/route.js +1 -1
  103. package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
  104. package/.next-prod/server/app/api/git/commit-diff/route.js +1 -1
  105. package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
  106. package/.next-prod/server/app/api/git/commits/route.js +1 -1
  107. package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
  108. package/.next-prod/server/app/api/git/diff/route.js +1 -1
  109. package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
  110. package/.next-prod/server/app/api/git/discard/route.js +1 -1
  111. package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
  112. package/.next-prod/server/app/api/git/stage/route.js +1 -1
  113. package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
  114. package/.next-prod/server/app/api/git/status/route.js +1 -1
  115. package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
  116. package/.next-prod/server/app/api/git/unstage/route.js +1 -1
  117. package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
  118. package/.next-prod/server/app/api/git/worktree/route.js +1 -1
  119. package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
  120. package/.next-prod/server/app/api/global-state/route.js +1 -1
  121. package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
  122. package/.next-prod/server/app/api/jupyter/load/route.js +1 -1
  123. package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
  124. package/.next-prod/server/app/api/jupyter/save/route.js +1 -1
  125. package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
  126. package/.next-prod/server/app/api/jupyter/shutdown/route.js +1 -1
  127. package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
  128. package/.next-prod/server/app/api/lsp/definition/route.js +1 -1
  129. package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
  130. package/.next-prod/server/app/api/lsp/hover/route.js +1 -1
  131. package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
  132. package/.next-prod/server/app/api/lsp/references/route.js +1 -1
  133. package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
  134. package/.next-prod/server/app/api/lsp/status/route.js +1 -1
  135. package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
  136. package/.next-prod/server/app/api/lsp/warmup/route.js +1 -1
  137. package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
  138. package/.next-prod/server/app/api/mysql/columns/route.js +2 -2
  139. package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
  140. package/.next-prod/server/app/api/mysql/connect/route.js +1 -1
  141. package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
  142. package/.next-prod/server/app/api/mysql/disconnect/route.js +1 -1
  143. package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
  144. package/.next-prod/server/app/api/mysql/export/route.js +1 -1
  145. package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
  146. package/.next-prod/server/app/api/mysql/query/route.js +1 -1
  147. package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
  148. package/.next-prod/server/app/api/mysql/schemas/route.js +2 -2
  149. package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
  150. package/.next-prod/server/app/api/neo4j/connect/route.js +1 -1
  151. package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
  152. package/.next-prod/server/app/api/neo4j/disconnect/route.js +1 -1
  153. package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
  154. package/.next-prod/server/app/api/neo4j/query/route.js +1 -1
  155. package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
  156. package/.next-prod/server/app/api/neo4j/schema/route.js +1 -1
  157. package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
  158. package/.next-prod/server/app/api/note/route.js +1 -1
  159. package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
  160. package/.next-prod/server/app/api/ollama/models/route.js +1 -1
  161. package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
  162. package/.next-prod/server/app/api/ollama/start/route.js +1 -1
  163. package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
  164. package/.next-prod/server/app/api/open-cursor/route.js +1 -1
  165. package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
  166. package/.next-prod/server/app/api/open-vscode/route.js +1 -1
  167. package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
  168. package/.next-prod/server/app/api/pick-folder/route.js +1 -1
  169. package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
  170. package/.next-prod/server/app/api/pinned-sessions/route.js +1 -1
  171. package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
  172. package/.next-prod/server/app/api/project-settings/route.js +1 -1
  173. package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
  174. package/.next-prod/server/app/api/project-state/route.js +1 -1
  175. package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
  176. package/.next-prod/server/app/api/projectGraph/affected/route.js +1 -1
  177. package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
  178. package/.next-prod/server/app/api/projectGraph/callees/route.js +1 -1
  179. package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
  180. package/.next-prod/server/app/api/projectGraph/callers/route.js +1 -1
  181. package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
  182. package/.next-prod/server/app/api/projectGraph/coedit/route.js +1 -1
  183. package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
  184. package/.next-prod/server/app/api/projectGraph/context/route.js +1 -1
  185. package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
  186. package/.next-prod/server/app/api/projectGraph/file/route.js +1 -1
  187. package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
  188. package/.next-prod/server/app/api/projectGraph/file-functions/route.js +1 -1
  189. package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
  190. package/.next-prod/server/app/api/projectGraph/impact/route.js +1 -1
  191. package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
  192. package/.next-prod/server/app/api/projectGraph/related/route.js +1 -1
  193. package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
  194. package/.next-prod/server/app/api/projectGraph/risk/route.js +1 -1
  195. package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
  196. package/.next-prod/server/app/api/projectGraph/search/route.js +1 -1
  197. package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
  198. package/.next-prod/server/app/api/projects/route.js +1 -1
  199. package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
  200. package/.next-prod/server/app/api/redis/command/route.js +1 -1
  201. package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
  202. package/.next-prod/server/app/api/redis/connect/route.js +1 -1
  203. package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
  204. package/.next-prod/server/app/api/redis/delete/route.js +1 -1
  205. package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
  206. package/.next-prod/server/app/api/redis/disconnect/route.js +1 -1
  207. package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
  208. package/.next-prod/server/app/api/redis/get/route.js +1 -1
  209. package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
  210. package/.next-prod/server/app/api/redis/keys/route.js +1 -1
  211. package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
  212. package/.next-prod/server/app/api/redis/set/route.js +1 -1
  213. package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
  214. package/.next-prod/server/app/api/review/[id]/comments/route.js +1 -1
  215. package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
  216. package/.next-prod/server/app/api/review/[id]/replies/route.js +1 -1
  217. package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
  218. package/.next-prod/server/app/api/review/[id]/route.js +1 -1
  219. package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
  220. package/.next-prod/server/app/api/review/identify/route.js +1 -1
  221. package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
  222. package/.next-prod/server/app/api/review/order/route.js +1 -1
  223. package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
  224. package/.next-prod/server/app/api/review/route.js +1 -1
  225. package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
  226. package/.next-prod/server/app/api/review/share-info/route.js +1 -1
  227. package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
  228. package/.next-prod/server/app/api/review/users/route.js +1 -1
  229. package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
  230. package/.next-prod/server/app/api/scheduled-tasks/route.js +1 -1
  231. package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
  232. package/.next-prod/server/app/api/services/config/route.js +1 -1
  233. package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
  234. package/.next-prod/server/app/api/services/scripts/route.js +1 -1
  235. package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
  236. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js +1 -1
  237. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
  238. package/.next-prod/server/app/api/session/[sessionId]/history/route.js +1 -1
  239. package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
  240. package/.next-prod/server/app/api/session-by-path/route.js +1 -1
  241. package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
  242. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js +1 -1
  243. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
  244. package/.next-prod/server/app/api/sessions/projects/route.js +1 -1
  245. package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
  246. package/.next-prod/server/app/api/sessions/route.js +1 -1
  247. package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
  248. package/.next-prod/server/app/api/settings/route.js +1 -1
  249. package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
  250. package/.next-prod/server/app/api/skills/[id]/route.js +1 -1
  251. package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
  252. package/.next-prod/server/app/api/skills/content/route.js +1 -1
  253. package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
  254. package/.next-prod/server/app/api/skills/route.js +1 -1
  255. package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
  256. package/.next-prod/server/app/api/terminal/aliases/route.js +1 -1
  257. package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
  258. package/.next-prod/server/app/api/terminal/autocomplete/route.js +1 -1
  259. package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
  260. package/.next-prod/server/app/api/terminal/bubble-order/route.js +1 -1
  261. package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
  262. package/.next-prod/server/app/api/terminal/env/route.js +1 -1
  263. package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
  264. package/.next-prod/server/app/api/terminal/history/route.js +1 -1
  265. package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
  266. package/.next-prod/server/app/api/version/route.js +1 -1
  267. package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
  268. package/.next-prod/server/app/favicon.ico/route.js +1 -1
  269. package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
  270. package/.next-prod/server/app/manifest.webmanifest/route.js +2 -2
  271. package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  272. package/.next-prod/server/app/page.js +2 -2
  273. package/.next-prod/server/app/page.js.nft.json +1 -1
  274. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  275. package/.next-prod/server/app/project/page.js +2 -2
  276. package/.next-prod/server/app/project/page.js.nft.json +1 -1
  277. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  278. package/.next-prod/server/app/review/[id]/page.js +2 -2
  279. package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
  280. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  281. package/.next-prod/server/app-paths-manifest.json +3 -2
  282. package/.next-prod/server/chunks/1921.js +131 -0
  283. package/.next-prod/server/chunks/2939.js +1 -1
  284. package/.next-prod/server/chunks/3792.js +9 -0
  285. package/.next-prod/server/chunks/3863.js +4 -1
  286. package/.next-prod/server/chunks/6244.js +1 -0
  287. package/.next-prod/server/chunks/8916.js +1 -1
  288. package/.next-prod/server/chunks/9658.js +9 -9
  289. package/.next-prod/server/middleware-build-manifest.js +1 -1
  290. package/.next-prod/server/middleware-react-loadable-manifest.js +1 -1
  291. package/.next-prod/server/pages/404.html +1 -1
  292. package/.next-prod/server/pages/500.html +1 -1
  293. package/.next-prod/server/server-reference-manifest.json +1 -1
  294. package/.next-prod/static/2xsVtkHy1Q-18e5b3DPua/_buildManifest.js +1 -0
  295. package/.next-prod/static/chunks/6321-a2227f1f162bd000.js +29 -0
  296. package/.next-prod/static/chunks/6345-d9033e1e3d07c277.js +14 -0
  297. package/.next-prod/static/chunks/app/_global-error/page-ccd4c8b73711effa.js +1 -0
  298. package/.next-prod/static/chunks/app/api/agent/test/route-ccd4c8b73711effa.js +1 -0
  299. package/.next-prod/static/chunks/app/api/bash/route-ccd4c8b73711effa.js +1 -0
  300. package/.next-prod/static/chunks/app/api/chat/codex/route-ccd4c8b73711effa.js +1 -0
  301. package/.next-prod/static/chunks/app/api/chat/deepseek/route-ccd4c8b73711effa.js +1 -0
  302. package/.next-prod/static/chunks/app/api/chat/kimi/route-ccd4c8b73711effa.js +1 -0
  303. package/.next-prod/static/chunks/app/api/chat/ollama/route-ccd4c8b73711effa.js +1 -0
  304. package/.next-prod/static/chunks/app/api/chat/pty-input/route-ccd4c8b73711effa.js +1 -0
  305. package/.next-prod/static/chunks/app/api/chat/route-ccd4c8b73711effa.js +1 -0
  306. package/.next-prod/static/chunks/app/api/claude-stats/route-ccd4c8b73711effa.js +1 -0
  307. package/.next-prod/static/chunks/app/api/commands/route-ccd4c8b73711effa.js +1 -0
  308. package/.next-prod/static/chunks/app/api/comments/route-ccd4c8b73711effa.js +1 -0
  309. package/.next-prod/static/chunks/app/api/db/columns/route-ccd4c8b73711effa.js +1 -0
  310. package/.next-prod/static/chunks/app/api/db/connect/route-ccd4c8b73711effa.js +1 -0
  311. package/.next-prod/static/chunks/app/api/db/disconnect/route-ccd4c8b73711effa.js +1 -0
  312. package/.next-prod/static/chunks/app/api/db/export/route-ccd4c8b73711effa.js +1 -0
  313. package/.next-prod/static/chunks/app/api/db/query/route-ccd4c8b73711effa.js +1 -0
  314. package/.next-prod/static/chunks/app/api/db/schemas/route-ccd4c8b73711effa.js +1 -0
  315. package/.next-prod/static/chunks/app/api/dev/spans/route-ccd4c8b73711effa.js +1 -0
  316. package/.next-prod/static/chunks/app/api/extension/version/route-ccd4c8b73711effa.js +1 -0
  317. package/.next-prod/static/chunks/app/api/file/route-ccd4c8b73711effa.js +1 -0
  318. package/.next-prod/static/chunks/app/api/files/blame/route-ccd4c8b73711effa.js +1 -0
  319. package/.next-prod/static/chunks/app/api/files/clipboard/route-ccd4c8b73711effa.js +1 -0
  320. package/.next-prod/static/chunks/app/api/files/copy/route-ccd4c8b73711effa.js +1 -0
  321. package/.next-prod/static/chunks/app/api/files/delete/route-ccd4c8b73711effa.js +1 -0
  322. package/.next-prod/static/chunks/app/api/files/expanded/route-ccd4c8b73711effa.js +1 -0
  323. package/.next-prod/static/chunks/app/api/files/index/route-ccd4c8b73711effa.js +1 -0
  324. package/.next-prod/static/chunks/app/api/files/init/route-ccd4c8b73711effa.js +1 -0
  325. package/.next-prod/static/chunks/app/api/files/paste/route-ccd4c8b73711effa.js +1 -0
  326. package/.next-prod/static/chunks/app/api/files/read/route-ccd4c8b73711effa.js +1 -0
  327. package/.next-prod/static/chunks/app/api/files/readdir/route-ccd4c8b73711effa.js +1 -0
  328. package/.next-prod/static/chunks/app/api/files/recent/route-ccd4c8b73711effa.js +1 -0
  329. package/.next-prod/static/chunks/app/api/files/save/route-ccd4c8b73711effa.js +1 -0
  330. package/.next-prod/static/chunks/app/api/files/search/route-ccd4c8b73711effa.js +1 -0
  331. package/.next-prod/static/chunks/app/api/files/stat/route-ccd4c8b73711effa.js +1 -0
  332. package/.next-prod/static/chunks/app/api/files/text/route-ccd4c8b73711effa.js +1 -0
  333. package/.next-prod/static/chunks/app/api/git/branch-diff/route-ccd4c8b73711effa.js +1 -0
  334. package/.next-prod/static/chunks/app/api/git/branches/route-ccd4c8b73711effa.js +1 -0
  335. package/.next-prod/static/chunks/app/api/git/commit-diff/route-ccd4c8b73711effa.js +1 -0
  336. package/.next-prod/static/chunks/app/api/git/commits/route-ccd4c8b73711effa.js +1 -0
  337. package/.next-prod/static/chunks/app/api/git/diff/route-ccd4c8b73711effa.js +1 -0
  338. package/.next-prod/static/chunks/app/api/git/discard/route-ccd4c8b73711effa.js +1 -0
  339. package/.next-prod/static/chunks/app/api/git/stage/route-ccd4c8b73711effa.js +1 -0
  340. package/.next-prod/static/chunks/app/api/git/status/route-ccd4c8b73711effa.js +1 -0
  341. package/.next-prod/static/chunks/app/api/git/unstage/route-ccd4c8b73711effa.js +1 -0
  342. package/.next-prod/static/chunks/app/api/git/worktree/route-ccd4c8b73711effa.js +1 -0
  343. package/.next-prod/static/chunks/app/api/global-state/route-ccd4c8b73711effa.js +1 -0
  344. package/.next-prod/static/chunks/app/api/jupyter/load/route-ccd4c8b73711effa.js +1 -0
  345. package/.next-prod/static/chunks/app/api/jupyter/save/route-ccd4c8b73711effa.js +1 -0
  346. package/.next-prod/static/chunks/app/api/jupyter/shutdown/route-ccd4c8b73711effa.js +1 -0
  347. package/.next-prod/static/chunks/app/api/lsp/definition/route-ccd4c8b73711effa.js +1 -0
  348. package/.next-prod/static/chunks/app/api/lsp/hover/route-ccd4c8b73711effa.js +1 -0
  349. package/.next-prod/static/chunks/app/api/lsp/references/route-ccd4c8b73711effa.js +1 -0
  350. package/.next-prod/static/chunks/app/api/lsp/status/route-ccd4c8b73711effa.js +1 -0
  351. package/.next-prod/static/chunks/app/api/lsp/warmup/route-ccd4c8b73711effa.js +1 -0
  352. package/.next-prod/static/chunks/app/api/mysql/columns/route-ccd4c8b73711effa.js +1 -0
  353. package/.next-prod/static/chunks/app/api/mysql/connect/route-ccd4c8b73711effa.js +1 -0
  354. package/.next-prod/static/chunks/app/api/mysql/disconnect/route-ccd4c8b73711effa.js +1 -0
  355. package/.next-prod/static/chunks/app/api/mysql/export/route-ccd4c8b73711effa.js +1 -0
  356. package/.next-prod/static/chunks/app/api/mysql/query/route-ccd4c8b73711effa.js +1 -0
  357. package/.next-prod/static/chunks/app/api/mysql/schemas/route-ccd4c8b73711effa.js +1 -0
  358. package/.next-prod/static/chunks/app/api/neo4j/connect/route-ccd4c8b73711effa.js +1 -0
  359. package/.next-prod/static/chunks/app/api/neo4j/disconnect/route-ccd4c8b73711effa.js +1 -0
  360. package/.next-prod/static/chunks/app/api/neo4j/query/route-ccd4c8b73711effa.js +1 -0
  361. package/.next-prod/static/chunks/app/api/neo4j/schema/route-ccd4c8b73711effa.js +1 -0
  362. package/.next-prod/static/chunks/app/api/note/route-ccd4c8b73711effa.js +1 -0
  363. package/.next-prod/static/chunks/app/api/ollama/models/route-ccd4c8b73711effa.js +1 -0
  364. package/.next-prod/static/chunks/app/api/ollama/start/route-ccd4c8b73711effa.js +1 -0
  365. package/.next-prod/static/chunks/app/api/open-cursor/route-ccd4c8b73711effa.js +1 -0
  366. package/.next-prod/static/chunks/app/api/open-vscode/route-ccd4c8b73711effa.js +1 -0
  367. package/.next-prod/static/chunks/app/api/pick-folder/route-ccd4c8b73711effa.js +1 -0
  368. package/.next-prod/static/chunks/app/api/pinned-sessions/route-ccd4c8b73711effa.js +1 -0
  369. package/.next-prod/static/chunks/app/api/project-settings/route-ccd4c8b73711effa.js +1 -0
  370. package/.next-prod/static/chunks/app/api/project-state/route-ccd4c8b73711effa.js +1 -0
  371. package/.next-prod/static/chunks/app/api/projectGraph/affected/route-ccd4c8b73711effa.js +1 -0
  372. package/.next-prod/static/chunks/app/api/projectGraph/callees/route-ccd4c8b73711effa.js +1 -0
  373. package/.next-prod/static/chunks/app/api/projectGraph/callers/route-ccd4c8b73711effa.js +1 -0
  374. package/.next-prod/static/chunks/app/api/projectGraph/coedit/route-ccd4c8b73711effa.js +1 -0
  375. package/.next-prod/static/chunks/app/api/projectGraph/context/route-ccd4c8b73711effa.js +1 -0
  376. package/.next-prod/static/chunks/app/api/projectGraph/file/route-ccd4c8b73711effa.js +1 -0
  377. package/.next-prod/static/chunks/app/api/projectGraph/file-functions/route-ccd4c8b73711effa.js +1 -0
  378. package/.next-prod/static/chunks/app/api/projectGraph/impact/route-ccd4c8b73711effa.js +1 -0
  379. package/.next-prod/static/chunks/app/api/projectGraph/related/route-ccd4c8b73711effa.js +1 -0
  380. package/.next-prod/static/chunks/app/api/projectGraph/risk/route-ccd4c8b73711effa.js +1 -0
  381. package/.next-prod/static/chunks/app/api/projectGraph/search/route-ccd4c8b73711effa.js +1 -0
  382. package/.next-prod/static/chunks/app/api/projects/route-ccd4c8b73711effa.js +1 -0
  383. package/.next-prod/static/chunks/app/api/redis/command/route-ccd4c8b73711effa.js +1 -0
  384. package/.next-prod/static/chunks/app/api/redis/connect/route-ccd4c8b73711effa.js +1 -0
  385. package/.next-prod/static/chunks/app/api/redis/delete/route-ccd4c8b73711effa.js +1 -0
  386. package/.next-prod/static/chunks/app/api/redis/disconnect/route-ccd4c8b73711effa.js +1 -0
  387. package/.next-prod/static/chunks/app/api/redis/get/route-ccd4c8b73711effa.js +1 -0
  388. package/.next-prod/static/chunks/app/api/redis/keys/route-ccd4c8b73711effa.js +1 -0
  389. package/.next-prod/static/chunks/app/api/redis/set/route-ccd4c8b73711effa.js +1 -0
  390. package/.next-prod/static/chunks/app/api/review/[id]/comments/route-ccd4c8b73711effa.js +1 -0
  391. package/.next-prod/static/chunks/app/api/review/[id]/replies/route-ccd4c8b73711effa.js +1 -0
  392. package/.next-prod/static/chunks/app/api/review/[id]/route-ccd4c8b73711effa.js +1 -0
  393. package/.next-prod/static/chunks/app/api/review/identify/route-ccd4c8b73711effa.js +1 -0
  394. package/.next-prod/static/chunks/app/api/review/order/route-ccd4c8b73711effa.js +1 -0
  395. package/.next-prod/static/chunks/app/api/review/route-ccd4c8b73711effa.js +1 -0
  396. package/.next-prod/static/chunks/app/api/review/share-info/route-ccd4c8b73711effa.js +1 -0
  397. package/.next-prod/static/chunks/app/api/review/users/route-ccd4c8b73711effa.js +1 -0
  398. package/.next-prod/static/chunks/app/api/scheduled-tasks/route-ccd4c8b73711effa.js +1 -0
  399. package/.next-prod/static/chunks/app/api/services/config/route-ccd4c8b73711effa.js +1 -0
  400. package/.next-prod/static/chunks/app/api/services/scripts/route-ccd4c8b73711effa.js +1 -0
  401. package/.next-prod/static/chunks/app/api/session/[sessionId]/fork/route-ccd4c8b73711effa.js +1 -0
  402. package/.next-prod/static/chunks/app/api/session/[sessionId]/history/route-ccd4c8b73711effa.js +1 -0
  403. package/.next-prod/static/chunks/app/api/session-by-path/route-ccd4c8b73711effa.js +1 -0
  404. package/.next-prod/static/chunks/app/api/sessions/projects/[encodedPath]/route-ccd4c8b73711effa.js +1 -0
  405. package/.next-prod/static/chunks/app/api/sessions/projects/route-ccd4c8b73711effa.js +1 -0
  406. package/.next-prod/static/chunks/app/api/sessions/route-ccd4c8b73711effa.js +1 -0
  407. package/.next-prod/static/chunks/app/api/settings/route-ccd4c8b73711effa.js +1 -0
  408. package/.next-prod/static/chunks/app/api/skills/[id]/route-ccd4c8b73711effa.js +1 -0
  409. package/.next-prod/static/chunks/app/api/skills/content/route-ccd4c8b73711effa.js +1 -0
  410. package/.next-prod/static/chunks/app/api/skills/route-ccd4c8b73711effa.js +1 -0
  411. package/.next-prod/static/chunks/app/api/terminal/aliases/route-ccd4c8b73711effa.js +1 -0
  412. package/.next-prod/static/chunks/app/api/terminal/autocomplete/route-ccd4c8b73711effa.js +1 -0
  413. package/.next-prod/static/chunks/app/api/terminal/bubble-order/route-ccd4c8b73711effa.js +1 -0
  414. package/.next-prod/static/chunks/app/api/terminal/env/route-ccd4c8b73711effa.js +1 -0
  415. package/.next-prod/static/chunks/app/api/terminal/history/route-ccd4c8b73711effa.js +1 -0
  416. package/.next-prod/static/chunks/app/api/version/route-ccd4c8b73711effa.js +1 -0
  417. package/.next-prod/static/chunks/app/{layout-8e3a54b794cb35b6.js → layout-a21c32c2b18fbae2.js} +1 -1
  418. package/.next-prod/static/chunks/app/manifest.webmanifest/route-ccd4c8b73711effa.js +1 -0
  419. package/.next-prod/static/chunks/app/{page-3ab0a0f28cbdc8e2.js → page-c5dc8ea6f9fc740b.js} +1 -1
  420. package/.next-prod/static/chunks/app/project/{page-3ab0a0f28cbdc8e2.js → page-c5dc8ea6f9fc740b.js} +1 -1
  421. package/.next-prod/static/chunks/next/dist/client/components/builtin/app-error-ccd4c8b73711effa.js +1 -0
  422. package/.next-prod/static/chunks/next/dist/client/components/builtin/forbidden-ccd4c8b73711effa.js +1 -0
  423. package/.next-prod/static/chunks/next/dist/client/components/builtin/not-found-ccd4c8b73711effa.js +1 -0
  424. package/.next-prod/static/chunks/next/dist/client/components/builtin/unauthorized-ccd4c8b73711effa.js +1 -0
  425. package/.next-prod/static/chunks/webpack-cb3881d9a37f80b7.js +1 -0
  426. package/.next-prod/static/css/ad6271f3f61ab845.css +1 -0
  427. package/.next-prod/trace +13 -13
  428. package/.next-prod/trace-build +1 -1
  429. package/.next-prod/types/app/api/chat/pty-input/route.ts +351 -0
  430. package/.next-prod/types/routes.d.ts +2 -1
  431. package/.next-prod/types/validator.ts +9 -0
  432. package/README.md +5 -5
  433. package/README.zh.md +5 -5
  434. package/bin/cock-browser.messages.mjs +176 -0
  435. package/bin/cock-browser.mjs +290 -18
  436. package/chrome-extension/automation.js +684 -32
  437. package/chrome-extension/manifest.json +1 -1
  438. package/chrome-extension/messages.js +45 -0
  439. package/dist/{chunk-W6G6X3FP.mjs → chunk-WOM47O75.mjs} +49 -1
  440. package/dist/httpApi.mjs +66 -6
  441. package/dist/scheduledTasks.mjs +6 -1
  442. package/dist/{server-ZBUZ24TC.mjs → server-SNB4H35J.mjs} +5 -1
  443. package/dist/wsServer.mjs +5 -2
  444. package/package.json +3 -5
  445. package/.next-prod/server/chunks/4034.js +0 -114
  446. package/.next-prod/server/chunks/8764.js +0 -26
  447. package/.next-prod/static/7pu1LXbRRLfg05VN3u39s/_buildManifest.js +0 -1
  448. package/.next-prod/static/chunks/6345-d477b8d5c682b1fb.js +0 -14
  449. package/.next-prod/static/chunks/6917-0a22d7764ca45244.js +0 -29
  450. package/.next-prod/static/chunks/app/_global-error/page-8ae56b9359db0996.js +0 -1
  451. package/.next-prod/static/chunks/app/api/agent/test/route-8ae56b9359db0996.js +0 -1
  452. package/.next-prod/static/chunks/app/api/bash/route-8ae56b9359db0996.js +0 -1
  453. package/.next-prod/static/chunks/app/api/chat/codex/route-8ae56b9359db0996.js +0 -1
  454. package/.next-prod/static/chunks/app/api/chat/deepseek/route-8ae56b9359db0996.js +0 -1
  455. package/.next-prod/static/chunks/app/api/chat/kimi/route-8ae56b9359db0996.js +0 -1
  456. package/.next-prod/static/chunks/app/api/chat/ollama/route-8ae56b9359db0996.js +0 -1
  457. package/.next-prod/static/chunks/app/api/chat/route-8ae56b9359db0996.js +0 -1
  458. package/.next-prod/static/chunks/app/api/claude-stats/route-8ae56b9359db0996.js +0 -1
  459. package/.next-prod/static/chunks/app/api/commands/route-8ae56b9359db0996.js +0 -1
  460. package/.next-prod/static/chunks/app/api/comments/route-8ae56b9359db0996.js +0 -1
  461. package/.next-prod/static/chunks/app/api/db/columns/route-8ae56b9359db0996.js +0 -1
  462. package/.next-prod/static/chunks/app/api/db/connect/route-8ae56b9359db0996.js +0 -1
  463. package/.next-prod/static/chunks/app/api/db/disconnect/route-8ae56b9359db0996.js +0 -1
  464. package/.next-prod/static/chunks/app/api/db/export/route-8ae56b9359db0996.js +0 -1
  465. package/.next-prod/static/chunks/app/api/db/query/route-8ae56b9359db0996.js +0 -1
  466. package/.next-prod/static/chunks/app/api/db/schemas/route-8ae56b9359db0996.js +0 -1
  467. package/.next-prod/static/chunks/app/api/dev/spans/route-8ae56b9359db0996.js +0 -1
  468. package/.next-prod/static/chunks/app/api/extension/version/route-8ae56b9359db0996.js +0 -1
  469. package/.next-prod/static/chunks/app/api/file/route-8ae56b9359db0996.js +0 -1
  470. package/.next-prod/static/chunks/app/api/files/blame/route-8ae56b9359db0996.js +0 -1
  471. package/.next-prod/static/chunks/app/api/files/clipboard/route-8ae56b9359db0996.js +0 -1
  472. package/.next-prod/static/chunks/app/api/files/copy/route-8ae56b9359db0996.js +0 -1
  473. package/.next-prod/static/chunks/app/api/files/delete/route-8ae56b9359db0996.js +0 -1
  474. package/.next-prod/static/chunks/app/api/files/expanded/route-8ae56b9359db0996.js +0 -1
  475. package/.next-prod/static/chunks/app/api/files/index/route-8ae56b9359db0996.js +0 -1
  476. package/.next-prod/static/chunks/app/api/files/init/route-8ae56b9359db0996.js +0 -1
  477. package/.next-prod/static/chunks/app/api/files/paste/route-8ae56b9359db0996.js +0 -1
  478. package/.next-prod/static/chunks/app/api/files/read/route-8ae56b9359db0996.js +0 -1
  479. package/.next-prod/static/chunks/app/api/files/readdir/route-8ae56b9359db0996.js +0 -1
  480. package/.next-prod/static/chunks/app/api/files/recent/route-8ae56b9359db0996.js +0 -1
  481. package/.next-prod/static/chunks/app/api/files/save/route-8ae56b9359db0996.js +0 -1
  482. package/.next-prod/static/chunks/app/api/files/search/route-8ae56b9359db0996.js +0 -1
  483. package/.next-prod/static/chunks/app/api/files/stat/route-8ae56b9359db0996.js +0 -1
  484. package/.next-prod/static/chunks/app/api/files/text/route-8ae56b9359db0996.js +0 -1
  485. package/.next-prod/static/chunks/app/api/git/branch-diff/route-8ae56b9359db0996.js +0 -1
  486. package/.next-prod/static/chunks/app/api/git/branches/route-8ae56b9359db0996.js +0 -1
  487. package/.next-prod/static/chunks/app/api/git/commit-diff/route-8ae56b9359db0996.js +0 -1
  488. package/.next-prod/static/chunks/app/api/git/commits/route-8ae56b9359db0996.js +0 -1
  489. package/.next-prod/static/chunks/app/api/git/diff/route-8ae56b9359db0996.js +0 -1
  490. package/.next-prod/static/chunks/app/api/git/discard/route-8ae56b9359db0996.js +0 -1
  491. package/.next-prod/static/chunks/app/api/git/stage/route-8ae56b9359db0996.js +0 -1
  492. package/.next-prod/static/chunks/app/api/git/status/route-8ae56b9359db0996.js +0 -1
  493. package/.next-prod/static/chunks/app/api/git/unstage/route-8ae56b9359db0996.js +0 -1
  494. package/.next-prod/static/chunks/app/api/git/worktree/route-8ae56b9359db0996.js +0 -1
  495. package/.next-prod/static/chunks/app/api/global-state/route-8ae56b9359db0996.js +0 -1
  496. package/.next-prod/static/chunks/app/api/jupyter/load/route-8ae56b9359db0996.js +0 -1
  497. package/.next-prod/static/chunks/app/api/jupyter/save/route-8ae56b9359db0996.js +0 -1
  498. package/.next-prod/static/chunks/app/api/jupyter/shutdown/route-8ae56b9359db0996.js +0 -1
  499. package/.next-prod/static/chunks/app/api/lsp/definition/route-8ae56b9359db0996.js +0 -1
  500. package/.next-prod/static/chunks/app/api/lsp/hover/route-8ae56b9359db0996.js +0 -1
  501. package/.next-prod/static/chunks/app/api/lsp/references/route-8ae56b9359db0996.js +0 -1
  502. package/.next-prod/static/chunks/app/api/lsp/status/route-8ae56b9359db0996.js +0 -1
  503. package/.next-prod/static/chunks/app/api/lsp/warmup/route-8ae56b9359db0996.js +0 -1
  504. package/.next-prod/static/chunks/app/api/mysql/columns/route-8ae56b9359db0996.js +0 -1
  505. package/.next-prod/static/chunks/app/api/mysql/connect/route-8ae56b9359db0996.js +0 -1
  506. package/.next-prod/static/chunks/app/api/mysql/disconnect/route-8ae56b9359db0996.js +0 -1
  507. package/.next-prod/static/chunks/app/api/mysql/export/route-8ae56b9359db0996.js +0 -1
  508. package/.next-prod/static/chunks/app/api/mysql/query/route-8ae56b9359db0996.js +0 -1
  509. package/.next-prod/static/chunks/app/api/mysql/schemas/route-8ae56b9359db0996.js +0 -1
  510. package/.next-prod/static/chunks/app/api/neo4j/connect/route-8ae56b9359db0996.js +0 -1
  511. package/.next-prod/static/chunks/app/api/neo4j/disconnect/route-8ae56b9359db0996.js +0 -1
  512. package/.next-prod/static/chunks/app/api/neo4j/query/route-8ae56b9359db0996.js +0 -1
  513. package/.next-prod/static/chunks/app/api/neo4j/schema/route-8ae56b9359db0996.js +0 -1
  514. package/.next-prod/static/chunks/app/api/note/route-8ae56b9359db0996.js +0 -1
  515. package/.next-prod/static/chunks/app/api/ollama/models/route-8ae56b9359db0996.js +0 -1
  516. package/.next-prod/static/chunks/app/api/ollama/start/route-8ae56b9359db0996.js +0 -1
  517. package/.next-prod/static/chunks/app/api/open-cursor/route-8ae56b9359db0996.js +0 -1
  518. package/.next-prod/static/chunks/app/api/open-vscode/route-8ae56b9359db0996.js +0 -1
  519. package/.next-prod/static/chunks/app/api/pick-folder/route-8ae56b9359db0996.js +0 -1
  520. package/.next-prod/static/chunks/app/api/pinned-sessions/route-8ae56b9359db0996.js +0 -1
  521. package/.next-prod/static/chunks/app/api/project-settings/route-8ae56b9359db0996.js +0 -1
  522. package/.next-prod/static/chunks/app/api/project-state/route-8ae56b9359db0996.js +0 -1
  523. package/.next-prod/static/chunks/app/api/projectGraph/affected/route-8ae56b9359db0996.js +0 -1
  524. package/.next-prod/static/chunks/app/api/projectGraph/callees/route-8ae56b9359db0996.js +0 -1
  525. package/.next-prod/static/chunks/app/api/projectGraph/callers/route-8ae56b9359db0996.js +0 -1
  526. package/.next-prod/static/chunks/app/api/projectGraph/coedit/route-8ae56b9359db0996.js +0 -1
  527. package/.next-prod/static/chunks/app/api/projectGraph/context/route-8ae56b9359db0996.js +0 -1
  528. package/.next-prod/static/chunks/app/api/projectGraph/file/route-8ae56b9359db0996.js +0 -1
  529. package/.next-prod/static/chunks/app/api/projectGraph/file-functions/route-8ae56b9359db0996.js +0 -1
  530. package/.next-prod/static/chunks/app/api/projectGraph/impact/route-8ae56b9359db0996.js +0 -1
  531. package/.next-prod/static/chunks/app/api/projectGraph/related/route-8ae56b9359db0996.js +0 -1
  532. package/.next-prod/static/chunks/app/api/projectGraph/risk/route-8ae56b9359db0996.js +0 -1
  533. package/.next-prod/static/chunks/app/api/projectGraph/search/route-8ae56b9359db0996.js +0 -1
  534. package/.next-prod/static/chunks/app/api/projects/route-8ae56b9359db0996.js +0 -1
  535. package/.next-prod/static/chunks/app/api/redis/command/route-8ae56b9359db0996.js +0 -1
  536. package/.next-prod/static/chunks/app/api/redis/connect/route-8ae56b9359db0996.js +0 -1
  537. package/.next-prod/static/chunks/app/api/redis/delete/route-8ae56b9359db0996.js +0 -1
  538. package/.next-prod/static/chunks/app/api/redis/disconnect/route-8ae56b9359db0996.js +0 -1
  539. package/.next-prod/static/chunks/app/api/redis/get/route-8ae56b9359db0996.js +0 -1
  540. package/.next-prod/static/chunks/app/api/redis/keys/route-8ae56b9359db0996.js +0 -1
  541. package/.next-prod/static/chunks/app/api/redis/set/route-8ae56b9359db0996.js +0 -1
  542. package/.next-prod/static/chunks/app/api/review/[id]/comments/route-8ae56b9359db0996.js +0 -1
  543. package/.next-prod/static/chunks/app/api/review/[id]/replies/route-8ae56b9359db0996.js +0 -1
  544. package/.next-prod/static/chunks/app/api/review/[id]/route-8ae56b9359db0996.js +0 -1
  545. package/.next-prod/static/chunks/app/api/review/identify/route-8ae56b9359db0996.js +0 -1
  546. package/.next-prod/static/chunks/app/api/review/order/route-8ae56b9359db0996.js +0 -1
  547. package/.next-prod/static/chunks/app/api/review/route-8ae56b9359db0996.js +0 -1
  548. package/.next-prod/static/chunks/app/api/review/share-info/route-8ae56b9359db0996.js +0 -1
  549. package/.next-prod/static/chunks/app/api/review/users/route-8ae56b9359db0996.js +0 -1
  550. package/.next-prod/static/chunks/app/api/scheduled-tasks/route-8ae56b9359db0996.js +0 -1
  551. package/.next-prod/static/chunks/app/api/services/config/route-8ae56b9359db0996.js +0 -1
  552. package/.next-prod/static/chunks/app/api/services/scripts/route-8ae56b9359db0996.js +0 -1
  553. package/.next-prod/static/chunks/app/api/session/[sessionId]/fork/route-8ae56b9359db0996.js +0 -1
  554. package/.next-prod/static/chunks/app/api/session/[sessionId]/history/route-8ae56b9359db0996.js +0 -1
  555. package/.next-prod/static/chunks/app/api/session-by-path/route-8ae56b9359db0996.js +0 -1
  556. package/.next-prod/static/chunks/app/api/sessions/projects/[encodedPath]/route-8ae56b9359db0996.js +0 -1
  557. package/.next-prod/static/chunks/app/api/sessions/projects/route-8ae56b9359db0996.js +0 -1
  558. package/.next-prod/static/chunks/app/api/sessions/route-8ae56b9359db0996.js +0 -1
  559. package/.next-prod/static/chunks/app/api/settings/route-8ae56b9359db0996.js +0 -1
  560. package/.next-prod/static/chunks/app/api/skills/[id]/route-8ae56b9359db0996.js +0 -1
  561. package/.next-prod/static/chunks/app/api/skills/content/route-8ae56b9359db0996.js +0 -1
  562. package/.next-prod/static/chunks/app/api/skills/route-8ae56b9359db0996.js +0 -1
  563. package/.next-prod/static/chunks/app/api/terminal/aliases/route-8ae56b9359db0996.js +0 -1
  564. package/.next-prod/static/chunks/app/api/terminal/autocomplete/route-8ae56b9359db0996.js +0 -1
  565. package/.next-prod/static/chunks/app/api/terminal/bubble-order/route-8ae56b9359db0996.js +0 -1
  566. package/.next-prod/static/chunks/app/api/terminal/env/route-8ae56b9359db0996.js +0 -1
  567. package/.next-prod/static/chunks/app/api/terminal/history/route-8ae56b9359db0996.js +0 -1
  568. package/.next-prod/static/chunks/app/api/version/route-8ae56b9359db0996.js +0 -1
  569. package/.next-prod/static/chunks/app/manifest.webmanifest/route-8ae56b9359db0996.js +0 -1
  570. package/.next-prod/static/chunks/next/dist/client/components/builtin/app-error-8ae56b9359db0996.js +0 -1
  571. package/.next-prod/static/chunks/next/dist/client/components/builtin/forbidden-8ae56b9359db0996.js +0 -1
  572. package/.next-prod/static/chunks/next/dist/client/components/builtin/not-found-8ae56b9359db0996.js +0 -1
  573. package/.next-prod/static/chunks/next/dist/client/components/builtin/unauthorized-8ae56b9359db0996.js +0 -1
  574. package/.next-prod/static/chunks/webpack-b618ac52d349e5ca.js +0 -1
  575. package/.next-prod/static/css/fc2730c2dbe4866e.css +0 -1
  576. /package/.next-prod/static/{7pu1LXbRRLfg05VN3u39s → 2xsVtkHy1Q-18e5b3DPua}/_ssgManifest.js +0 -0
  577. /package/.next-prod/static/chunks/{e868780c.39804e9ab925f132.js → e868780c-39804e9ab925f132.js} +0 -0
@@ -21,24 +21,71 @@ let refCounter = 0;
21
21
  const refToElement = new Map();
22
22
  const elementToRef = new WeakMap();
23
23
 
24
+ // Snapshot epoch: incremented by clearRefs() each time `snapshot` runs.
25
+ // Refs encode the epoch as `e<N>#v<E>`, so stale refs from a previous
26
+ // snapshot are detected explicitly rather than producing a misleading
27
+ // "disconnected" message.
28
+ let snapshotEpoch = 0;
29
+
24
30
  function clearRefs() {
25
31
  refCounter = 0;
26
32
  refToElement.clear();
33
+ snapshotEpoch += 1;
27
34
  }
28
35
 
29
36
  function assignRef(el) {
37
+ // WeakMap can't be cleared, so a previously-snapshotted element returns its
38
+ // OLD ref (carrying an old epoch). Detect that and re-assign at the current
39
+ // epoch so all refs in a single snapshot share one banner version.
30
40
  const existing = elementToRef.get(el);
31
- if (existing) return existing;
32
- const ref = `e${refCounter++}`;
41
+ if (existing) {
42
+ const m = existing.match(/^e\d+#v(\d+)$/);
43
+ if (m && Number(m[1]) === snapshotEpoch) return existing;
44
+ // fall through — re-assign at the current epoch
45
+ }
46
+ const ref = `e${refCounter++}#v${snapshotEpoch}`;
33
47
  refToElement.set(ref, el);
34
48
  elementToRef.set(el, ref);
35
49
  return ref;
36
50
  }
37
51
 
52
+ // Inline stale-ref message — kept here (not in messages.js) to avoid
53
+ // runtime cross-module loading in the content-script context. Examples
54
+ // below are generic by policy; never inline business-specific selectors.
55
+ function staleRefMsg(ref, kind, currentEpoch) {
56
+ return (
57
+ `Element ref "${ref}" is stale (current snapshot v=${currentEpoch}; kind: ${kind}).\n` +
58
+ ` Refs are valid only until the next snapshot / re-render / route change.\n` +
59
+ ` Fix one of:\n` +
60
+ ` 1. Re-run \`snapshot\` to get fresh refs (banner shows v=${currentEpoch}).\n` +
61
+ ` 2. Use a CSS selector or visible text directly:\n` +
62
+ ` cockpit browser <id> click --text "Sign in"\n` +
63
+ ` cockpit browser <id> click --selector 'button[type="submit"]'\n` +
64
+ ` 3. Drop to evaluate:\n` +
65
+ ` cockpit browser <id> evaluate "(() => document.querySelector('button[aria-label=\\"Save\\"]').click())()"`
66
+ );
67
+ }
68
+
38
69
  function findByRef(ref) {
70
+ if (typeof ref !== 'string' || !ref) {
71
+ throw new Error(staleRefMsg(String(ref), 'missing', snapshotEpoch));
72
+ }
73
+ const m = ref.match(/^e(\d+)(?:#v(\d+))?$/);
74
+ if (!m) {
75
+ throw new Error(staleRefMsg(ref, 'malformed', snapshotEpoch));
76
+ }
77
+ if (m[2] === undefined) {
78
+ // Legacy `eN` form (no epoch). Per spec there is no backwards compat;
79
+ // reject explicitly so AI knows to re-snapshot rather than guessing.
80
+ throw new Error(staleRefMsg(ref, 'no-epoch (use eN#vM)', snapshotEpoch));
81
+ }
82
+ const refEpoch = Number(m[2]);
83
+ if (refEpoch !== snapshotEpoch) {
84
+ throw new Error(staleRefMsg(ref, `from v=${refEpoch}`, snapshotEpoch));
85
+ }
39
86
  const el = refToElement.get(ref);
40
87
  if (!el || !el.isConnected) {
41
- throw new Error(`Element ref "${ref}" not found or disconnected`);
88
+ throw new Error(staleRefMsg(ref, 'disconnected', snapshotEpoch));
42
89
  }
43
90
  return el;
44
91
  }
@@ -137,9 +184,16 @@ function getName(el) {
137
184
  return el.title || '';
138
185
  }
139
186
 
140
- function buildA11yTree(root = document.body, maxDepth = 12) {
187
+ function buildA11yTree(root = document.body, opts = {}) {
188
+ const {
189
+ maxDepth = 12,
190
+ filter = null, // string regex source; lines not matching are dropped
191
+ includeHiddenText = false, // surface innerText for container/anonymous nodes
192
+ } = opts;
193
+
141
194
  clearRefs();
142
195
  const lines = [];
196
+ const filterRe = filter ? safeRegex(filter) : null;
143
197
 
144
198
  function walk(el, depth) {
145
199
  if (depth > maxDepth) return;
@@ -160,6 +214,15 @@ function buildA11yTree(root = document.body, maxDepth = 12) {
160
214
  if (name) {
161
215
  const displayName = name.length > 80 ? name.slice(0, 77) + '...' : name;
162
216
  line += ` "${displayName}"`;
217
+ } else if (includeHiddenText) {
218
+ // Surface collapsed text on otherwise-nameless nodes (e.g. <summary>,
219
+ // <details>, headings with emoji + text). Useful when grep-ing on
220
+ // user-visible content rather than role+aria-label.
221
+ const inner = (el.innerText || '').trim().replace(/\s+/g, ' ');
222
+ if (inner) {
223
+ const snippet = inner.length > 60 ? inner.slice(0, 57) + '...' : inner;
224
+ line += ` …"${snippet}"`;
225
+ }
163
226
  }
164
227
 
165
228
  const extras = [];
@@ -177,6 +240,17 @@ function buildA11yTree(root = document.body, maxDepth = 12) {
177
240
  if (extras.length) line += ` [${extras.join(', ')}]`;
178
241
  line += ` [${ref}]`;
179
242
  lines.push(line);
243
+ } else if (includeHiddenText) {
244
+ // Even for "container" passthrough, emit a thin marker line if it has
245
+ // visible text — so grep can still find content collapsed by the
246
+ // container heuristic. We don't assign a ref here (container is just
247
+ // structure, not addressable).
248
+ const inner = (el.innerText || '').trim().replace(/\s+/g, ' ');
249
+ if (inner) {
250
+ const indent = ' '.repeat(depth);
251
+ const snippet = inner.length > 60 ? inner.slice(0, 57) + '...' : inner;
252
+ lines.push(`${indent}${el.tagName.toLowerCase()} …"${snippet}"`);
253
+ }
180
254
  }
181
255
 
182
256
  for (const child of el.children) {
@@ -185,7 +259,28 @@ function buildA11yTree(root = document.body, maxDepth = 12) {
185
259
  }
186
260
 
187
261
  walk(root || document.body, 0);
188
- return lines.join('\n');
262
+
263
+ let kept = lines;
264
+ if (filterRe) kept = lines.filter(l => filterRe.test(l));
265
+
266
+ // Banner is always emitted first. AI parses v=<N> to detect epoch changes
267
+ // (any cached refs from a different epoch are stale per findByRef).
268
+ const banner = (
269
+ `# a11y tree v=${snapshotEpoch} — refs valid until next snapshot\n` +
270
+ `# Text inside <details>/<summary> and unnamed container <div>/<section> is collapsed.\n` +
271
+ `# Grep on role / aria-label, NOT on user-visible emoji / text.\n` +
272
+ `# Tips: --include-hidden-text surfaces collapsed innerText; --filter <regex> reduces output.\n` +
273
+ (filterRe ? `# filter: ${filter} (${kept.length}/${lines.length} lines kept)\n` : '') +
274
+ (includeHiddenText ? `# include-hidden-text: on\n` : '')
275
+ );
276
+ return banner + kept.join('\n');
277
+ }
278
+
279
+ // Safe regex compile — falls back to a literal-match regex if the pattern
280
+ // is malformed, so a stray `?` or `(` from AI doesn't crash the snapshot.
281
+ function safeRegex(src) {
282
+ try { return new RegExp(src); }
283
+ catch { return new RegExp(src.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); }
189
284
  }
190
285
 
191
286
  // ============================================================================
@@ -283,7 +378,8 @@ const handlers = {
283
378
  back: async () => { history.back(); return { ok: true }; },
284
379
  forward: async () => { history.forward(); return { ok: true }; },
285
380
 
286
- snapshot: async () => buildA11yTree(document.body),
381
+ snapshot: async ({ filter, includeHiddenText, maxDepth } = {}) =>
382
+ buildA11yTree(document.body, { filter, includeHiddenText, maxDepth }),
287
383
 
288
384
  screenshot: async () => {
289
385
  // 1) 通知父页面(项目 iframe):切到 console view + 切到本项目 + 返回 iframe bounds
@@ -326,11 +422,33 @@ const handlers = {
326
422
  return { image: result, format: 'png' };
327
423
  },
328
424
 
329
- click: async ({ ref }) => {
330
- const el = findByRef(ref);
425
+ click: async ({ ref, text, selector, exact = false, nth = 0 }) => {
426
+ let el;
427
+ if (ref) {
428
+ el = findByRef(ref);
429
+ } else if (selector) {
430
+ const all = document.querySelectorAll(selector);
431
+ if (!all.length) throw new Error(`No element matching selector "${selector}"`);
432
+ if (nth >= all.length) throw new Error(`Only ${all.length} matches for selector "${selector}", nth=${nth} out of range`);
433
+ el = all[nth];
434
+ } else if (text) {
435
+ const candidates = Array.from(document.querySelectorAll(
436
+ 'button, a, [role="button"], [role="link"], [role="tab"], [role="menuitem"], input[type="submit"], input[type="button"]'
437
+ ));
438
+ const matches = candidates.filter(c => {
439
+ const t = (c.textContent || '').trim();
440
+ const aria = c.getAttribute('aria-label') || '';
441
+ return exact ? (t === text || aria === text) : (t.includes(text) || aria.includes(text));
442
+ });
443
+ if (!matches.length) throw new Error(`No clickable element with text "${text}" (searched button / a / role=button|link|tab|menuitem / input[type=submit|button])`);
444
+ if (nth >= matches.length) throw new Error(`Only ${matches.length} matches for text "${text}", nth=${nth} out of range`);
445
+ el = matches[nth];
446
+ } else {
447
+ throw new Error('click requires one of: ref (positional or --ref), --text, --selector');
448
+ }
331
449
  el.scrollIntoView({ block: 'nearest' });
332
450
  el.click();
333
- return { clicked: ref };
451
+ return { clicked: ref || (text ? `text:${text}` : `selector:${selector}`), nth };
334
452
  },
335
453
 
336
454
  type: async ({ ref, text, clear }) => {
@@ -356,13 +474,24 @@ const handlers = {
356
474
  return { typed: text, ref };
357
475
  },
358
476
 
359
- fill: async ({ ref, value }) => {
360
- const el = findByRef(ref);
477
+ fill: async ({ ref, selector, value }) => {
478
+ let el;
479
+ let target;
480
+ if (ref) {
481
+ el = findByRef(ref);
482
+ target = ref;
483
+ } else if (selector) {
484
+ el = document.querySelector(selector);
485
+ if (!el) throw new Error(`No element matching selector "${selector}"`);
486
+ target = `selector:${selector}`;
487
+ } else {
488
+ throw new Error('fill requires one of: ref (positional or --ref), --selector');
489
+ }
361
490
  el.focus();
362
491
  if (el.tagName === 'SELECT') {
363
492
  el.value = value;
364
493
  el.dispatchEvent(new Event('change', { bubbles: true }));
365
- return { filled: ref, value };
494
+ return { filled: target, value };
366
495
  }
367
496
  const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set
368
497
  || Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
@@ -370,7 +499,7 @@ const handlers = {
370
499
  else el.value = value;
371
500
  el.dispatchEvent(new Event('input', { bubbles: true }));
372
501
  el.dispatchEvent(new Event('change', { bubbles: true }));
373
- return { filled: ref, value };
502
+ return { filled: target, value };
374
503
  },
375
504
 
376
505
  hover: async ({ ref }) => {
@@ -422,24 +551,111 @@ const handlers = {
422
551
  return { dispatched: event, ref };
423
552
  },
424
553
 
425
- wait: async ({ text, ref: waitRef, url: waitUrl, time, timeout = 10000 }) => {
554
+ wait: async ({
555
+ text,
556
+ ref: waitRef,
557
+ url: waitUrl,
558
+ time,
559
+ selector,
560
+ state = 'visible',
561
+ networkIdle,
562
+ domStable,
563
+ quietMs = 500,
564
+ maxRequestAgeMs = 30000,
565
+ timeout = 10000,
566
+ }) => {
426
567
  const start = Date.now();
427
568
  const poll = (check) => new Promise((resolve, reject) => {
428
569
  const interval = setInterval(() => {
429
- if (check()) { clearInterval(interval); resolve(true); return; }
430
- if (Date.now() - start > timeout) { clearInterval(interval); reject(new Error(`Wait timeout after ${timeout}ms`)); }
570
+ try {
571
+ if (check()) { clearInterval(interval); resolve(true); return; }
572
+ } catch (e) { clearInterval(interval); reject(e); return; }
573
+ if (Date.now() - start > timeout) {
574
+ clearInterval(interval);
575
+ reject(new Error(`Wait timeout after ${timeout}ms`));
576
+ }
431
577
  }, 100);
432
578
  });
433
579
 
434
580
  if (time) { await new Promise(r => setTimeout(r, time)); return { waited: `${time}ms` }; }
435
- if (text) { await poll(() => document.body.textContent.includes(text)); return { waited: `text "${text}"` }; }
436
- if (waitRef) { await poll(() => refToElement.has(waitRef) && refToElement.get(waitRef).isConnected); return { waited: `ref ${waitRef}` }; }
581
+
582
+ if (text) {
583
+ await poll(() => document.body.textContent.includes(text));
584
+ return { waited: `text "${text}"`, elapsedMs: Date.now() - start };
585
+ }
586
+
587
+ if (waitRef) {
588
+ await poll(() => refToElement.has(waitRef) && refToElement.get(waitRef).isConnected);
589
+ return { waited: `ref ${waitRef}`, elapsedMs: Date.now() - start };
590
+ }
591
+
437
592
  if (waitUrl) {
438
593
  const pat = waitUrl.includes('*') ? new RegExp('^' + waitUrl.replace(/\*/g, '.*') + '$') : null;
439
594
  await poll(() => pat ? pat.test(location.href) : location.href.includes(waitUrl));
440
- return { waited: `url "${waitUrl}"` };
595
+ return { waited: `url "${waitUrl}"`, elapsedMs: Date.now() - start };
596
+ }
597
+
598
+ // F2.2 — wait for an element matching CSS selector to reach a state.
599
+ if (selector) {
600
+ const validStates = ['visible', 'hidden', 'attached', 'detached'];
601
+ if (!validStates.includes(state)) {
602
+ throw new Error(`Invalid --state "${state}"; expected one of: ${validStates.join(', ')}`);
603
+ }
604
+ await poll(() => {
605
+ const el = document.querySelector(selector);
606
+ if (state === 'attached') return !!el;
607
+ if (state === 'detached') return !el;
608
+ if (!el) return false;
609
+ const vis = isVisible(el);
610
+ return state === 'visible' ? vis : !vis;
611
+ });
612
+ return { waited: `selector "${selector}" → ${state}`, elapsedMs: Date.now() - start };
613
+ }
614
+
615
+ // F2.1 — wait for the page network to settle: 0 in-flight requests for
616
+ // `quietMs` consecutive ms. Requests older than `maxRequestAgeMs` are
617
+ // ignored (covers SSE / long-poll). Counts XHR + fetch from networkBuffer.
618
+ if (networkIdle) {
619
+ let quietSince = null;
620
+ await poll(() => {
621
+ const now = Date.now();
622
+ const inflight = networkBuffer.filter(e =>
623
+ e.status == null &&
624
+ e.startTime &&
625
+ (now - e.startTime) < maxRequestAgeMs
626
+ ).length;
627
+ if (inflight === 0) {
628
+ if (quietSince == null) quietSince = now;
629
+ return (now - quietSince) >= quietMs;
630
+ }
631
+ quietSince = null;
632
+ return false;
633
+ });
634
+ return { waited: `network-idle (quiet=${quietMs}ms)`, elapsedMs: Date.now() - start };
441
635
  }
442
- throw new Error('wait requires one of: text, ref, url, time');
636
+
637
+ // F3.3 — wait until DOM mutations stop for `quietMs` consecutive ms.
638
+ if (domStable) {
639
+ return await new Promise((resolve, reject) => {
640
+ let lastMutation = Date.now();
641
+ const obs = new MutationObserver(() => { lastMutation = Date.now(); });
642
+ obs.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true });
643
+ const tick = setInterval(() => {
644
+ const now = Date.now();
645
+ if (now - lastMutation >= quietMs) {
646
+ clearInterval(tick); obs.disconnect();
647
+ resolve({ waited: `dom-stable (quiet=${quietMs}ms)`, elapsedMs: now - start });
648
+ return;
649
+ }
650
+ if (now - start > timeout) {
651
+ clearInterval(tick); obs.disconnect();
652
+ reject(new Error(`Wait timeout after ${timeout}ms (dom still mutating)`));
653
+ }
654
+ }, 50);
655
+ });
656
+ }
657
+
658
+ throw new Error('wait requires one of: --text, --ref, --url, --time, --selector, --network-idle, --dom-stable');
443
659
  },
444
660
 
445
661
  evaluate: async ({ js, allFrames }) => {
@@ -643,22 +859,158 @@ const handlers = {
643
859
 
644
860
  assert: async (params) => {
645
861
  const failures = [];
646
- if (params.visible !== undefined) {
647
- const el = findByRef(params.ref);
862
+
863
+ // F2.3 selector-based element resolution. Either --ref OR --selector
864
+ // can locate the element; if both given, --selector wins (more stable
865
+ // across re-renders, more typical for E2E).
866
+ const resolveEl = () => {
867
+ if (params.selector) {
868
+ const el = document.querySelector(params.selector);
869
+ if (!el) throw new Error(`No element matching selector "${params.selector}"`);
870
+ return el;
871
+ }
872
+ if (params.ref) return findByRef(params.ref);
873
+ throw new Error('assert requires --ref or --selector for element-level checks');
874
+ };
875
+
876
+ const needsElement = params.visible !== undefined || params.text !== undefined ||
877
+ params.checked !== undefined || params.disabled !== undefined ||
878
+ params.attr !== undefined;
879
+ let el = null;
880
+ if (needsElement) {
881
+ try { el = resolveEl(); }
882
+ catch (e) { failures.push(e.message); }
883
+ }
884
+
885
+ if (el && params.visible !== undefined) {
648
886
  const vis = isVisible(el);
649
- if (params.visible && !vis) failures.push(`Element ${params.ref} is not visible`);
650
- if (!params.visible && vis) failures.push(`Element ${params.ref} is visible`);
887
+ if (params.visible && !vis) failures.push(`Element is not visible (${params.selector || params.ref})`);
888
+ if (!params.visible && vis) failures.push(`Element is visible (${params.selector || params.ref})`);
651
889
  }
652
- if (params.text !== undefined) {
653
- const el = findByRef(params.ref);
890
+ if (el && params.text !== undefined) {
654
891
  const actual = el.textContent?.trim() || '';
655
892
  if (!actual.includes(params.text)) failures.push(`Expected text "${params.text}", got "${actual.slice(0, 100)}"`);
656
893
  }
657
- if (params.checked !== undefined) { const el = findByRef(params.ref); if (el.checked !== params.checked) failures.push(`Expected checked=${params.checked}, got ${el.checked}`); }
658
- if (params.disabled !== undefined) { const el = findByRef(params.ref); const d = el.disabled || el.getAttribute('aria-disabled') === 'true'; if (d !== params.disabled) failures.push(`Expected disabled=${params.disabled}, got ${d}`); }
659
- if (params.url) { const pat = params.url.includes('*') ? new RegExp('^' + params.url.replace(/\*/g, '.*') + '$') : null; const m = pat ? pat.test(location.href) : location.href.includes(params.url); if (!m) failures.push(`URL "${location.href}" does not match "${params.url}"`); }
660
- if (params.title) { if (!document.title.includes(params.title)) failures.push(`Title "${document.title}" does not match "${params.title}"`); }
661
- if (params.consoleNoErrors) { const errs = consoleBuffer.filter(m => m.level === 'error'); if (errs.length) failures.push(`Found ${errs.length} console errors: ${errs.map(e => e.text).join('; ').slice(0, 200)}`); }
894
+ if (el && params.checked !== undefined) {
895
+ if (el.checked !== params.checked) failures.push(`Expected checked=${params.checked}, got ${el.checked}`);
896
+ }
897
+ if (el && params.disabled !== undefined) {
898
+ const d = el.disabled || el.getAttribute('aria-disabled') === 'true';
899
+ if (d !== params.disabled) failures.push(`Expected disabled=${params.disabled}, got ${d}`);
900
+ }
901
+ if (el && params.attr !== undefined) {
902
+ // --attr "key=value" — split on first '='
903
+ const eq = params.attr.indexOf('=');
904
+ if (eq < 0) failures.push(`--attr must be "key=value" form, got "${params.attr}"`);
905
+ else {
906
+ const k = params.attr.slice(0, eq).trim();
907
+ const v = params.attr.slice(eq + 1);
908
+ const got = el.getAttribute(k);
909
+ if (got !== v) failures.push(`Expected attr ${k}="${v}", got ${got === null ? 'null' : `"${got}"`}`);
910
+ }
911
+ }
912
+
913
+ if (params.url) {
914
+ const pat = params.url.includes('*') ? new RegExp('^' + params.url.replace(/\*/g, '.*') + '$') : null;
915
+ const m = pat ? pat.test(location.href) : location.href.includes(params.url);
916
+ if (!m) failures.push(`URL "${location.href}" does not match "${params.url}"`);
917
+ }
918
+ if (params.title) {
919
+ if (!document.title.includes(params.title)) failures.push(`Title "${document.title}" does not match "${params.title}"`);
920
+ }
921
+ if (params.consoleNoErrors) {
922
+ const errs = consoleBuffer.filter(m => m.level === 'error');
923
+ if (errs.length) failures.push(`Found ${errs.length} console errors: ${errs.map(e => e.text).join('; ').slice(0, 200)}`);
924
+ }
925
+
926
+ // F2.4 — assert a matching request occurred in networkBuffer.
927
+ // --network --method M --url U --status S [--since <epoch ms>]
928
+ // `--url` matches as a substring (or with `*` glob).
929
+ // `--status` may be a literal int, "2xx", "4xx", etc.
930
+ // `--since` filters to entries with startTime >= since.
931
+ if (params.network) {
932
+ const sinceMs = params.since ? Number(params.since) : 0;
933
+ const urlPat = params.url
934
+ ? (params.url.includes('*')
935
+ ? new RegExp('^' + params.url.replace(/\*/g, '.*') + '$')
936
+ : null)
937
+ : null;
938
+ const methodU = params.method ? params.method.toUpperCase() : null;
939
+ const statusCheck = (s) => {
940
+ if (params.status == null) return true;
941
+ const want = String(params.status);
942
+ if (/^\d+$/.test(want)) return s === Number(want);
943
+ if (/^\dxx$/.test(want)) return s >= Number(want[0]) * 100 && s < (Number(want[0]) + 1) * 100;
944
+ return false;
945
+ };
946
+ const matches = networkBuffer.filter(e =>
947
+ (!sinceMs || (e.startTime || 0) >= sinceMs) &&
948
+ (!methodU || e.method === methodU) &&
949
+ (!params.url || (urlPat ? urlPat.test(e.url) : (e.url || '').includes(params.url))) &&
950
+ statusCheck(e.status)
951
+ );
952
+ if (!matches.length) {
953
+ const desc = [
954
+ params.method ? `method=${params.method}` : null,
955
+ params.url ? `url=${params.url}` : null,
956
+ params.status != null ? `status=${params.status}` : null,
957
+ sinceMs ? `since=${sinceMs}` : null,
958
+ ].filter(Boolean).join(' ');
959
+ failures.push(`No matching request in networkBuffer (${desc}); buffer has ${networkBuffer.length} entries`);
960
+ }
961
+ }
962
+
963
+ // F2.5 — assert backend by fetch + jsonpath compare.
964
+ // --fetch <url> [--method M] [--body B] [--fetch-status N]
965
+ // [--jsonpath P --equals V | --contains V | --not-contains V]
966
+ if (params.fetch) {
967
+ const init = { method: params.fetchMethod || 'GET', credentials: 'same-origin' };
968
+ init.headers = { 'Accept': 'application/json, text/plain, */*' };
969
+ if (params.body != null) {
970
+ init.body = typeof params.body === 'string' ? params.body : JSON.stringify(params.body);
971
+ init.headers['Content-Type'] = 'application/json';
972
+ }
973
+ let r, data, parseErr;
974
+ try {
975
+ r = await fetch(params.fetch, init);
976
+ const ct = r.headers.get('content-type') || '';
977
+ data = ct.includes('json') ? await r.json().catch(() => r.text()) : await r.text();
978
+ } catch (e) { parseErr = e.message; }
979
+ if (parseErr) {
980
+ failures.push(`assert --fetch ${params.fetch}: ${parseErr}`);
981
+ } else {
982
+ if (params.fetchStatus != null && r.status !== Number(params.fetchStatus)) {
983
+ failures.push(`assert --fetch ${params.fetch}: expected status ${params.fetchStatus}, got ${r.status}`);
984
+ }
985
+ if (params.jsonpath) {
986
+ let value;
987
+ try { value = simpleJsonPath(data, params.jsonpath); }
988
+ catch (e) { failures.push(`assert --fetch jsonpath ${params.jsonpath}: ${e.message}`); }
989
+ if (params.equals !== undefined) {
990
+ const want = params.equals;
991
+ const eq = Array.isArray(value)
992
+ ? JSON.stringify(value) === JSON.stringify(want)
993
+ : value == want; // intentional loose equal for "5" == 5
994
+ if (!eq) failures.push(`assert --fetch jsonpath ${params.jsonpath}: expected ${JSON.stringify(want)}, got ${JSON.stringify(value)?.slice(0, 200)}`);
995
+ }
996
+ if (params.contains !== undefined) {
997
+ const want = params.contains;
998
+ const has = Array.isArray(value)
999
+ ? value.some(v => v == want || (typeof v === 'string' && v.includes(String(want))))
1000
+ : (typeof value === 'string' ? value.includes(String(want)) : false);
1001
+ if (!has) failures.push(`assert --fetch jsonpath ${params.jsonpath}: expected to contain ${JSON.stringify(want)}, got ${JSON.stringify(value)?.slice(0, 200)}`);
1002
+ }
1003
+ if (params.notContains !== undefined) {
1004
+ const dont = params.notContains;
1005
+ const has = Array.isArray(value)
1006
+ ? value.some(v => v == dont || (typeof v === 'string' && v.includes(String(dont))))
1007
+ : (typeof value === 'string' ? value.includes(String(dont)) : false);
1008
+ if (has) failures.push(`assert --fetch jsonpath ${params.jsonpath}: expected NOT to contain ${JSON.stringify(dont)}, got ${JSON.stringify(value)?.slice(0, 200)}`);
1009
+ }
1010
+ }
1011
+ }
1012
+ }
1013
+
662
1014
  return failures.length ? { pass: false, failures } : { pass: true };
663
1015
  },
664
1016
 
@@ -681,8 +1033,271 @@ const handlers = {
681
1033
  }
682
1034
  return { error: `Unknown metric: ${metric}` };
683
1035
  },
1036
+
1037
+ // ──────────────────────────────────────────────────────────────────────
1038
+ // F1.6 — fetch shortcut: GET/POST/… using the page's auth session.
1039
+ // Returns parsed JSON when Content-Type matches, otherwise text.
1040
+ // Optional --json '<path>' extracts a value via a tiny JSONPath subset:
1041
+ // $ whole document
1042
+ // $.key object property
1043
+ // $.a.b chained property
1044
+ // $[N] array index
1045
+ // $.a[0].b mixed
1046
+ // $[*].key map over array → array of values
1047
+ // No filters / wildcards beyond the above.
1048
+ // ──────────────────────────────────────────────────────────────────────
1049
+ fetch: async ({ url, method = 'GET', body = null, headers = null, json = null }) => {
1050
+ if (!url) throw new Error('fetch requires --url (or positional URL)');
1051
+ const init = { method, credentials: 'same-origin' };
1052
+ init.headers = { 'Accept': 'application/json, text/plain, */*' };
1053
+ if (headers && typeof headers === 'object') Object.assign(init.headers, headers);
1054
+ if (body != null) {
1055
+ if (typeof body === 'string') {
1056
+ init.body = body;
1057
+ if (!('Content-Type' in init.headers)) init.headers['Content-Type'] = 'application/json';
1058
+ } else {
1059
+ init.body = JSON.stringify(body);
1060
+ init.headers['Content-Type'] = 'application/json';
1061
+ }
1062
+ }
1063
+ const r = await fetch(url, init);
1064
+ const ct = r.headers.get('content-type') || '';
1065
+ let data;
1066
+ if (ct.includes('json')) {
1067
+ try { data = await r.json(); }
1068
+ catch { data = await r.text(); }
1069
+ } else {
1070
+ data = await r.text();
1071
+ }
1072
+ const result = { status: r.status, ok: r.ok, contentType: ct };
1073
+ if (json) {
1074
+ result.jsonpath = json;
1075
+ result.value = simpleJsonPath(data, json);
1076
+ } else {
1077
+ result.data = data;
1078
+ }
1079
+ return result;
1080
+ },
1081
+
1082
+ // ──────────────────────────────────────────────────────────────────────
1083
+ // F2.6 — submit: form.requestSubmit() works on React-controlled forms
1084
+ // where dispatching a synthetic Enter key event is ignored.
1085
+ // ──────────────────────────────────────────────────────────────────────
1086
+ submit: async ({ formSelector } = {}) => {
1087
+ let form;
1088
+ if (formSelector) {
1089
+ form = document.querySelector(formSelector);
1090
+ if (!form) throw new Error(`No element matching form selector "${formSelector}"`);
1091
+ if (form.tagName !== 'FORM') {
1092
+ // allow passing a selector to a child — climb to nearest form ancestor.
1093
+ form = form.closest('form');
1094
+ if (!form) throw new Error(`Element "${formSelector}" is not a <form> and has no <form> ancestor`);
1095
+ }
1096
+ } else {
1097
+ const active = document.activeElement;
1098
+ form = active && active.closest ? active.closest('form') : null;
1099
+ if (!form) throw new Error('submit: no --form-selector given and document.activeElement is not inside a <form>');
1100
+ }
1101
+ if (typeof form.requestSubmit === 'function') form.requestSubmit();
1102
+ else form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
1103
+ return { submitted: form.getAttribute('id') || form.getAttribute('name') || form.tagName, action: form.action || null };
1104
+ },
1105
+
1106
+ // ──────────────────────────────────────────────────────────────────────
1107
+ // F1.8 — deep health: probes the page itself. The cheap (non-blocking)
1108
+ // server-side health lives in src/lib/httpApi.ts and never round-trips
1109
+ // here. Use this only with --deep.
1110
+ // ──────────────────────────────────────────────────────────────────────
1111
+ health_deep: async () => ({
1112
+ extension: 'alive',
1113
+ page: {
1114
+ ts: Date.now(),
1115
+ readyState: document.readyState,
1116
+ url: window.location.href,
1117
+ title: document.title,
1118
+ pendingMicrotasks: 0, // best-effort; not directly observable
1119
+ snapshotEpoch,
1120
+ },
1121
+ }),
1122
+
1123
+ // ──────────────────────────────────────────────────────────────────────
1124
+ // F3.1 — reset: atomic test-isolation helper.
1125
+ // --cookies expire every cookie visible to JS for the current host
1126
+ // --storage clear localStorage + sessionStorage
1127
+ // --cache drop everything in the Cache Storage API
1128
+ // --reload reload the page after clearing (force-bypass cache)
1129
+ // Returns which steps actually ran (so AI can confirm).
1130
+ // ──────────────────────────────────────────────────────────────────────
1131
+ reset: async ({ cookies, storage, cache, reload } = {}) => {
1132
+ const cleared = [];
1133
+ const errors = [];
1134
+ if (cookies) {
1135
+ try {
1136
+ const host = location.hostname;
1137
+ const parents = [host];
1138
+ // Also try the eTLD+1 parent (e.g. "sub.example.com" → ".example.com")
1139
+ const parts = host.split('.');
1140
+ if (parts.length > 2) parents.push('.' + parts.slice(-2).join('.'));
1141
+ for (const c of document.cookie.split(';')) {
1142
+ const eq = c.indexOf('=');
1143
+ const name = (eq > -1 ? c.slice(0, eq) : c).trim();
1144
+ if (!name) continue;
1145
+ const dead = 'expires=Thu, 01 Jan 1970 00:00:00 GMT';
1146
+ document.cookie = `${name}=; ${dead}; path=/`;
1147
+ for (const d of parents) {
1148
+ document.cookie = `${name}=; ${dead}; path=/; domain=${d}`;
1149
+ }
1150
+ }
1151
+ cleared.push('cookies');
1152
+ } catch (e) { errors.push(`cookies: ${e.message}`); }
1153
+ }
1154
+ if (storage) {
1155
+ try { localStorage.clear(); sessionStorage.clear(); cleared.push('storage'); }
1156
+ catch (e) { errors.push(`storage: ${e.message}`); }
1157
+ }
1158
+ if (cache) {
1159
+ try {
1160
+ if (typeof caches !== 'undefined' && caches.keys) {
1161
+ const keys = await caches.keys();
1162
+ await Promise.all(keys.map(k => caches.delete(k)));
1163
+ cleared.push(`cache (${keys.length} stores)`);
1164
+ } else {
1165
+ errors.push('cache: Cache Storage API unavailable');
1166
+ }
1167
+ } catch (e) { errors.push(`cache: ${e.message}`); }
1168
+ }
1169
+ if (reload) {
1170
+ // Defer the reload so the result can ship back before navigation tears
1171
+ // down the iframe runtime.
1172
+ setTimeout(() => location.reload(), 50);
1173
+ cleared.push('reload (scheduled)');
1174
+ }
1175
+ if (!cleared.length && !errors.length) {
1176
+ throw new Error('reset requires at least one of: --cookies, --storage, --cache, --reload');
1177
+ }
1178
+ return errors.length ? { cleared, errors } : { cleared };
1179
+ },
1180
+
1181
+ // ──────────────────────────────────────────────────────────────────────
1182
+ // F3.2 — status: one-stop "where am I" summary for AI orientation.
1183
+ // url, title, readyState, last console error, last failed request, top
1184
+ // visible actions. Keep it cheap; this is what AI runs after a long gap.
1185
+ // ──────────────────────────────────────────────────────────────────────
1186
+ status: async () => {
1187
+ const now = Date.now();
1188
+ const lastErr = consoleBuffer.filter(m => m.level === 'error').slice(-1)[0] || null;
1189
+ const lastFailed = networkBuffer.filter(r => typeof r.status === 'number' && r.status >= 400).slice(-1)[0] || null;
1190
+ const buttons = Array.from(document.querySelectorAll('button, [role="button"], a[role="button"], input[type="submit"]'))
1191
+ .filter(b => isVisible(b))
1192
+ .slice(0, 8);
1193
+ const topActions = buttons.map(b => {
1194
+ const t = (b.textContent || '').trim().replace(/\s+/g, ' ');
1195
+ const aria = b.getAttribute('aria-label');
1196
+ const label = aria || (t.length > 32 ? t.slice(0, 29) + '...' : t);
1197
+ return label;
1198
+ }).filter(Boolean);
1199
+ return {
1200
+ url: location.href,
1201
+ title: document.title,
1202
+ readyState: document.readyState,
1203
+ lastConsoleError: lastErr ? {
1204
+ text: (lastErr.text || '').slice(0, 200),
1205
+ ageSec: Math.round((now - lastErr.timestamp) / 1000),
1206
+ } : null,
1207
+ lastFailedRequest: lastFailed ? {
1208
+ method: lastFailed.method,
1209
+ url: lastFailed.url,
1210
+ status: lastFailed.status,
1211
+ ageSec: Math.round((now - (lastFailed.startTime || now)) / 1000),
1212
+ } : null,
1213
+ topActions,
1214
+ };
1215
+ },
1216
+
1217
+ // ──────────────────────────────────────────────────────────────────────
1218
+ // F3.4 — set: unified writer for cookie / localStorage / sessionStorage.
1219
+ // (Cookies are JS-visible only; HttpOnly cannot be set from page context.)
1220
+ // --type cookie + --name --value [--domain --path --secure
1221
+ // --samesite --expires]
1222
+ // --type local-storage + --name --value
1223
+ // --type session-storage + --name --value
1224
+ // ──────────────────────────────────────────────────────────────────────
1225
+ set: async ({ type, name, value, domain, path, secure, sameSite, expires } = {}) => {
1226
+ if (!type) throw new Error('set requires --type (one of: cookie, local-storage, session-storage)');
1227
+ if (!name) throw new Error('set requires --name');
1228
+ if (value === undefined) throw new Error('set requires --value');
1229
+ const v = typeof value === 'string' ? value : JSON.stringify(value);
1230
+ if (type === 'cookie') {
1231
+ let str = `${name}=${encodeURIComponent(v)}`;
1232
+ str += `; path=${path || '/'}`;
1233
+ if (domain) str += `; domain=${domain}`;
1234
+ if (secure) str += `; secure`;
1235
+ if (sameSite) str += `; samesite=${sameSite}`;
1236
+ if (expires) str += `; expires=${expires}`;
1237
+ document.cookie = str;
1238
+ // Verify (cookies set against a different domain are silently dropped)
1239
+ const verify = document.cookie.split(';').some(c => c.trim().startsWith(`${name}=`));
1240
+ return { set: 'cookie', name, verified: verify };
1241
+ }
1242
+ if (type === 'local-storage') { localStorage.setItem(name, v); return { set: 'local-storage', name, length: v.length }; }
1243
+ if (type === 'session-storage') { sessionStorage.setItem(name, v); return { set: 'session-storage', name, length: v.length }; }
1244
+ throw new Error(`set --type must be one of: cookie, local-storage, session-storage (got "${type}")`);
1245
+ },
1246
+
1247
+ // Internal — used by CLI-side post-verify (F1.7). Cheap state probe.
1248
+ probe_state: async () => {
1249
+ // domHash: cheap-ish CRC of the visible body innerHTML length + a small sample
1250
+ const html = document.body ? document.body.innerHTML : '';
1251
+ const sample = html.length > 4000
1252
+ ? html.slice(0, 1000) + html.slice(html.length / 2, html.length / 2 + 1000) + html.slice(-1000)
1253
+ : html;
1254
+ let hash = 0;
1255
+ for (let i = 0; i < sample.length; i++) hash = ((hash << 5) - hash + sample.charCodeAt(i)) | 0;
1256
+ return {
1257
+ url: window.location.href,
1258
+ domLen: html.length,
1259
+ domHash: hash,
1260
+ lastNetworkId: networkBuffer.length ? networkBuffer[networkBuffer.length - 1].id : 0,
1261
+ ts: Date.now(),
1262
+ };
1263
+ },
684
1264
  };
685
1265
 
1266
+ // ──────────────────────────────────────────────────────────────────────
1267
+ // Simple JSONPath extractor — supports only: $, .key, [N], [*]
1268
+ // ──────────────────────────────────────────────────────────────────────
1269
+ function simpleJsonPath(data, path) {
1270
+ if (typeof path !== 'string' || !path.startsWith('$')) {
1271
+ throw new Error(`Invalid jsonpath "${path}" — must start with $`);
1272
+ }
1273
+ let rest = path.slice(1);
1274
+ let current = [data];
1275
+ while (rest.length) {
1276
+ if (rest.startsWith('.')) {
1277
+ const m = rest.match(/^\.([A-Za-z_$][\w$]*)/);
1278
+ if (!m) throw new Error(`Invalid jsonpath segment near "${rest}"`);
1279
+ const key = m[1];
1280
+ current = current.flatMap(v => (v != null && typeof v === 'object' && key in v) ? [v[key]] : []);
1281
+ rest = rest.slice(m[0].length);
1282
+ } else if (rest.startsWith('[')) {
1283
+ const m = rest.match(/^\[(\*|\d+)\]/);
1284
+ if (!m) throw new Error(`Invalid jsonpath bracket near "${rest}"`);
1285
+ if (m[1] === '*') {
1286
+ current = current.flatMap(v => Array.isArray(v) ? v : (v != null && typeof v === 'object' ? Object.values(v) : []));
1287
+ } else {
1288
+ const idx = Number(m[1]);
1289
+ current = current.flatMap(v => Array.isArray(v) && idx < v.length ? [v[idx]] : []);
1290
+ }
1291
+ rest = rest.slice(m[0].length);
1292
+ } else {
1293
+ throw new Error(`Invalid jsonpath suffix "${rest}"`);
1294
+ }
1295
+ }
1296
+ // If the path involved no wildcard, return scalar; otherwise array.
1297
+ if (path.includes('[*]')) return current;
1298
+ return current.length ? current[0] : undefined;
1299
+ }
1300
+
686
1301
  // ============================================================================
687
1302
  // 命令分发
688
1303
  // ============================================================================
@@ -695,7 +1310,17 @@ function handleCommand(event) {
695
1310
  const handler = handlers[action];
696
1311
 
697
1312
  if (!handler) {
698
- _realParent.postMessage({ type: 'cockpit:cmd-result', reqId, ok: false, error: `Unknown action: ${action}` }, '*');
1313
+ // Filter internal-use actions from "Did you mean" suggestions.
1314
+ const INTERNAL_ACTIONS = new Set(['probe_state', 'evaluate_chunk']);
1315
+ const known = Object.keys(handlers).filter(k => !k.startsWith('_') && !INTERNAL_ACTIONS.has(k));
1316
+ const suggestions = fuzzyTopK(action, known, 3);
1317
+ const hint = suggestions.length ? `\n Did you mean: ${suggestions.join(', ')}?` : '';
1318
+ _realParent.postMessage({
1319
+ type: 'cockpit:cmd-result',
1320
+ reqId,
1321
+ ok: false,
1322
+ error: `Unknown action "${action}".${hint}\n Run: cockpit browser --help-all`,
1323
+ }, '*');
699
1324
  return;
700
1325
  }
701
1326
 
@@ -704,6 +1329,33 @@ function handleCommand(event) {
704
1329
  .catch(err => _realParent.postMessage({ type: 'cockpit:cmd-result', reqId, ok: false, error: err.message || String(err) }, '*'));
705
1330
  }
706
1331
 
1332
+ // Fuzzy ranking — surfaces "evluate" → "evaluate" while filtering unrelated
1333
+ // candidates. Uses character-bag overlap ratio (multiset intersection /
1334
+ // max length); substring matches get a bonus. Threshold 0.6.
1335
+ function fuzzyTopK(input, candidates, k) {
1336
+ if (!input) return [];
1337
+ const inputLow = input.toLowerCase();
1338
+ const bag = bagCounts(inputLow);
1339
+ const scored = candidates.map(c => {
1340
+ const cl = c.toLowerCase();
1341
+ const cBag = bagCounts(cl);
1342
+ let inter = 0;
1343
+ for (const [ch, n] of bag) inter += Math.min(n, cBag.get(ch) || 0);
1344
+ const ratio = inter / Math.max(inputLow.length, cl.length);
1345
+ const startsWith = cl.startsWith(inputLow) ? 0.5 : 0;
1346
+ const contains = cl.includes(inputLow) ? 0.3 : 0;
1347
+ return { c, score: ratio + startsWith + contains };
1348
+ });
1349
+ scored.sort((a, b) => b.score - a.score);
1350
+ return scored.slice(0, k).filter(s => s.score >= 0.6).map(s => s.c);
1351
+ }
1352
+
1353
+ function bagCounts(s) {
1354
+ const m = new Map();
1355
+ for (const ch of s) m.set(ch, (m.get(ch) || 0) + 1);
1356
+ return m;
1357
+ }
1358
+
707
1359
  // ============================================================================
708
1360
  // 导出初始化函数
709
1361
  // ============================================================================