@surething/cockpit 1.0.258 → 1.0.260

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 (248) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +2 -2
  3. package/.next-prod/build-manifest.json +2 -2
  4. package/.next-prod/cockpit-build.json +1 -1
  5. package/.next-prod/prerender-manifest.json +4 -4
  6. package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next-prod/server/app/_global-error.html +1 -1
  9. package/.next-prod/server/app/_global-error.rsc +1 -1
  10. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next-prod/server/app/_not-found/page.js +1 -1
  14. package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next-prod/server/app/_not-found.html +2 -2
  17. package/.next-prod/server/app/_not-found.rsc +3 -3
  18. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
  20. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
  23. package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
  24. package/.next-prod/server/app/api/chat/codex/route.js +1 -1
  25. package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
  26. package/.next-prod/server/app/api/chat/deepseek/route.js +1 -1
  27. package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
  28. package/.next-prod/server/app/api/chat/glm/route.js +1 -1
  29. package/.next-prod/server/app/api/chat/glm/route.js.nft.json +1 -1
  30. package/.next-prod/server/app/api/chat/kimi/route.js +1 -1
  31. package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
  32. package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
  33. package/.next-prod/server/app/api/chat/route.js +1 -1
  34. package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
  35. package/.next-prod/server/app/api/chat/stop/route.js +1 -1
  36. package/.next-prod/server/app/api/chat/stop/route.js.nft.json +1 -1
  37. package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
  38. package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
  39. package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
  40. package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
  41. package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
  42. package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
  43. package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
  44. package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
  45. package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
  46. package/.next-prod/server/app/api/deepseek/balance/route.js.nft.json +1 -1
  47. package/.next-prod/server/app/api/deepseek/credentials/route.js.nft.json +1 -1
  48. package/.next-prod/server/app/api/deepseek/models/route.js.nft.json +1 -1
  49. package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
  50. package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
  51. package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
  52. package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
  53. package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
  54. package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
  55. package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
  56. package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
  57. package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
  58. package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
  59. package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
  60. package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
  61. package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
  62. package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
  63. package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
  64. package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
  65. package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
  66. package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
  67. package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
  68. package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
  69. package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
  70. package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
  71. package/.next-prod/server/app/api/git/current-branch/route.js.nft.json +1 -1
  72. package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
  73. package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
  74. package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
  75. package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
  76. package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
  77. package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
  78. package/.next-prod/server/app/api/glm/credentials/route.js.nft.json +1 -1
  79. package/.next-prod/server/app/api/glm/models/route.js +1 -1
  80. package/.next-prod/server/app/api/glm/models/route.js.nft.json +1 -1
  81. package/.next-prod/server/app/api/glm/usage/route.js +1 -1
  82. package/.next-prod/server/app/api/glm/usage/route.js.nft.json +1 -1
  83. package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
  84. package/.next-prod/server/app/api/health/route.js.nft.json +1 -1
  85. package/.next-prod/server/app/api/html-apps/[id]/route.js +1 -1
  86. package/.next-prod/server/app/api/html-apps/[id]/route.js.nft.json +1 -1
  87. package/.next-prod/server/app/api/html-apps/route.js +1 -1
  88. package/.next-prod/server/app/api/html-apps/route.js.nft.json +1 -1
  89. package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
  90. package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
  91. package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
  92. package/.next-prod/server/app/api/kimi/credentials/route.js.nft.json +1 -1
  93. package/.next-prod/server/app/api/kimi/models/route.js +1 -1
  94. package/.next-prod/server/app/api/kimi/models/route.js.nft.json +1 -1
  95. package/.next-prod/server/app/api/kimi/usage/route.js +1 -1
  96. package/.next-prod/server/app/api/kimi/usage/route.js.nft.json +1 -1
  97. package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
  98. package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
  99. package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
  100. package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
  101. package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
  102. package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
  103. package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
  104. package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
  105. package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
  106. package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
  107. package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
  108. package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
  109. package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
  110. package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
  111. package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
  112. package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
  113. package/.next-prod/server/app/api/ollama/config/route.js.nft.json +1 -1
  114. package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
  115. package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
  116. package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
  117. package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
  118. package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
  119. package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
  120. package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
  121. package/.next-prod/server/app/api/project-state/route.js +1 -1
  122. package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
  123. package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
  124. package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
  125. package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
  126. package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
  127. package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
  128. package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
  129. package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
  130. package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
  131. package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
  132. package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
  133. package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
  134. package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
  135. package/.next-prod/server/app/api/prompts/config/route.js.nft.json +1 -1
  136. package/.next-prod/server/app/api/push/public-key/route.js.nft.json +1 -1
  137. package/.next-prod/server/app/api/push/subscribe/route.js.nft.json +1 -1
  138. package/.next-prod/server/app/api/push/unsubscribe/route.js.nft.json +1 -1
  139. package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
  140. package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
  141. package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
  142. package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
  143. package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
  144. package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
  145. package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
  146. package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
  147. package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
  148. package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
  149. package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
  150. package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
  151. package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
  152. package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
  153. package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
  154. package/.next-prod/server/app/api/scheduled-tasks/route.js +2 -2
  155. package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
  156. package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
  157. package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
  158. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js +1 -1
  159. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
  160. package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
  161. package/.next-prod/server/app/api/session-by-path/route.js +1 -1
  162. package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
  163. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js +1 -1
  164. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
  165. package/.next-prod/server/app/api/sessions/projects/route.js +1 -1
  166. package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
  167. package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
  168. package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
  169. package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
  170. package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
  171. package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
  172. package/.next-prod/server/app/api/snapshots/diff/route.js.nft.json +1 -1
  173. package/.next-prod/server/app/api/snapshots/route.js.nft.json +1 -1
  174. package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
  175. package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
  176. package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
  177. package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
  178. package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
  179. package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
  180. package/.next-prod/server/app/apps/[...path]/route.js +1 -1
  181. package/.next-prod/server/app/apps/[...path]/route.js.nft.json +1 -1
  182. package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
  183. package/.next-prod/server/app/m/page.js +1 -1
  184. package/.next-prod/server/app/m/page.js.nft.json +1 -1
  185. package/.next-prod/server/app/m/page_client-reference-manifest.js +1 -1
  186. package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  187. package/.next-prod/server/app/page.js +1 -1
  188. package/.next-prod/server/app/page.js.nft.json +1 -1
  189. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  190. package/.next-prod/server/app/project/page.js +1 -1
  191. package/.next-prod/server/app/project/page.js.nft.json +1 -1
  192. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  193. package/.next-prod/server/app/review/[id]/page.js +1 -1
  194. package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
  195. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  196. package/.next-prod/server/app-paths-manifest.json +2 -2
  197. package/.next-prod/server/chunks/151.js +1 -0
  198. package/.next-prod/server/chunks/3286.js +448 -294
  199. package/.next-prod/server/chunks/{8557.js → 3641.js} +5 -5
  200. package/.next-prod/server/chunks/5750.js +16 -9
  201. package/.next-prod/server/chunks/6495.js +1 -1
  202. package/.next-prod/server/chunks/6548.js +1 -1
  203. package/.next-prod/server/chunks/816.js +2 -0
  204. package/.next-prod/server/chunks/841.js +12 -10
  205. package/.next-prod/server/chunks/9352.js +4 -4
  206. package/.next-prod/server/chunks/9830.js +1 -1
  207. package/.next-prod/server/middleware-build-manifest.js +1 -1
  208. package/.next-prod/server/pages/404.html +2 -2
  209. package/.next-prod/server/pages/500.html +1 -1
  210. package/.next-prod/server/server-reference-manifest.json +1 -1
  211. package/.next-prod/static/chunks/1157-164bcf046a0a9b15.js +32 -0
  212. package/.next-prod/static/chunks/1372-d16d68b1b03a345a.js +15 -0
  213. package/.next-prod/static/chunks/{9592-a657fb9eb995b64d.js → 4333-9e6709e96d3ec973.js} +7 -7
  214. package/.next-prod/static/chunks/{225-75b2e22f496b56a1.js → 6160-958532439d324185.js} +2 -2
  215. package/.next-prod/static/chunks/6784-09dffdb0e8ca7fb3.js +381 -0
  216. package/.next-prod/static/chunks/app/{layout-bec1cf218bc88d81.js → layout-ab52fc1a83af968b.js} +1 -1
  217. package/.next-prod/static/chunks/app/m/page-7dcf56a65528216f.js +1 -0
  218. package/.next-prod/static/chunks/app/{page-5e6dbc9731415743.js → page-5de8b6584e328a6f.js} +1 -1
  219. package/.next-prod/static/chunks/app/project/{page-5e6dbc9731415743.js → page-5de8b6584e328a6f.js} +1 -1
  220. package/.next-prod/static/css/83c134c954630942.css +1 -0
  221. package/.next-prod/trace +14 -14
  222. package/.next-prod/trace-build +1 -1
  223. package/README.md +1 -1
  224. package/README.zh.md +1 -1
  225. package/apps/devkit/app.jsx +728 -0
  226. package/apps/devkit/index.html +178 -0
  227. package/apps/devkit/md5.js +98 -0
  228. package/dist/{chunk-L5TWVHYB.mjs → chunk-B5LXMESP.mjs} +19 -9
  229. package/dist/{chunk-YEZ3U3F2.mjs → chunk-CLPJYIFC.mjs} +1 -1
  230. package/dist/{chunk-RSRPFWNL.mjs → chunk-TU3GL2HK.mjs} +1 -27
  231. package/dist/httpApi.mjs +2 -2
  232. package/dist/scheduledTasks.mjs +692 -483
  233. package/dist/{server-UZPDELUK.mjs → server-2UJDVZ32.mjs} +2 -2
  234. package/dist/wsServer.mjs +5 -4
  235. package/package.json +1 -1
  236. package/public/agent-icons/claude.svg +1 -0
  237. package/public/agent-icons/codex.svg +1 -0
  238. package/public/agent-icons/deepseek.svg +1 -0
  239. package/public/agent-icons/glm.svg +1 -0
  240. package/public/agent-icons/kimi.svg +1 -0
  241. package/public/agent-icons/ollama.svg +1 -0
  242. package/.next-prod/static/chunks/1372-cc9afa045dc4eb5e.js +0 -15
  243. package/.next-prod/static/chunks/6784-500e233d3e8f6b41.js +0 -381
  244. package/.next-prod/static/chunks/7482-8478af06d5aa0972.js +0 -25
  245. package/.next-prod/static/chunks/app/m/page-aff83d3f5f4047c5.js +0 -1
  246. package/.next-prod/static/css/2334aed02a409f57.css +0 -1
  247. /package/.next-prod/static/{ZFNy0XuBWptMqEQGi7GTy → 4Jgc-wu2w_CC8iS5G1QZf}/_buildManifest.js +0 -0
  248. /package/.next-prod/static/{ZFNy0XuBWptMqEQGi7GTy → 4Jgc-wu2w_CC8iS5G1QZf}/_ssgManifest.js +0 -0
@@ -14,18 +14,15 @@ import {
14
14
  stashCodexRollout,
15
15
  stashTranscript,
16
16
  updateGlobalState
17
- } from "./chunk-L5TWVHYB.mjs";
17
+ } from "./chunk-B5LXMESP.mjs";
18
18
  import {
19
19
  AgentError,
20
20
  AppRuntime,
21
21
  COCKPIT_DIR,
22
22
  DEEPSEEK_CREDENTIALS_FILE,
23
- DEEPSEEK_DIR,
24
23
  Effect_exports,
25
24
  GLM_CREDENTIALS_FILE,
26
- GLM_DIR,
27
25
  KIMI_CREDENTIALS_FILE,
28
- KIMI_DIR,
29
26
  SCHEDULED_TASKS_FILE,
30
27
  SETTINGS_FILE,
31
28
  SKILLS_FILE,
@@ -37,11 +34,8 @@ import {
37
34
  getBuiltinSessionsRoot,
38
35
  getClaudeSessionPath,
39
36
  getDeepseekBuiltinSessionPath,
40
- getDeepseekSessionPath,
41
37
  getGlmBuiltinSessionPath,
42
- getGlmSessionPath,
43
38
  getKimiBuiltinSessionPath,
44
- getKimiSessionPath,
45
39
  getOllamaSessionPath,
46
40
  getSessionFilePath,
47
41
  mutateJsonFile,
@@ -51,7 +45,7 @@ import {
51
45
  settlePendingRecord,
52
46
  withFileLock,
53
47
  writeJsonFile
54
- } from "./chunk-RSRPFWNL.mjs";
48
+ } from "./chunk-TU3GL2HK.mjs";
55
49
  import {
56
50
  __export
57
51
  } from "./chunk-7P6ASYW6.mjs";
@@ -64,6 +58,17 @@ function defaultRegionForLanguage(language) {
64
58
  return language === "en" ? "global" : "cn";
65
59
  }
66
60
 
61
+ // packages/shared/utils/src/outputProgress.ts
62
+ function estimateOutputUnits(text) {
63
+ let cjk = 0;
64
+ let other = 0;
65
+ for (const ch of text) {
66
+ if (/[\u3040-\u30ff\u3400-\u9fff\uf900-\ufaff\u{20000}-\u{2a6df}\uac00-\ud7af]/u.test(ch)) cjk += 1;
67
+ else other += 1;
68
+ }
69
+ return Math.max(1, Math.ceil(cjk + other / 4));
70
+ }
71
+
67
72
  // packages/feature/agent/src/shared/toolMutation.ts
68
73
  var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
69
74
  "Read",
@@ -535,7 +540,7 @@ description: "\u5B8C\u6574\u4EE3\u7801\u5BA1\u67E5:\u9759\u6001 + \u52A8\u6001\u
535
540
 
536
541
  subagent prompt \u6A21\u677F:\`\u8BFB\u672C skill (cr/SKILL.md),\u53EA\u5BF9 <\u4F60\u90A3\u5757(Part A \u5168\u90E8 / \u67D0\u4E2A\u52A8\u6001\u5207\u7247)> \u5E94\u7528\u5BF9\u5E94 Part,\u81EA\u884C triage,\u6309\u7EDF\u4E00\u683C\u5F0F\u8F93\u51FA findings\`\u3002
537
542
 
538
- **Synthesis(\u4E3B\u4F1A\u8BDD,\u53EA\u6574\u7406\u4E0D\u91CD\u5224)**:\u6C47\u603B\u5404 subagent \u7684 findings \u2192 \u53BB\u91CD(\u540C\u6839\u56E0\u6807"\u5171\u72AF")\u2192 \u5F71\u54CD \xD7 \u6982\u7387\u6392\u5E8F \u2192 \u4E00\u4EFD\u62A5\u544A + \u68AF\u5EA6\u56FE\u3002**\u7981\u6B62\u7528\u4E3B\u4F1A\u8BDD\u7684\u5F00\u53D1\u4E0A\u4E0B\u6587\u7ED9\u4EFB\u4F55 finding \u6D17\u767D / \u964D\u7EA7**\u2014\u2014subagent \u600E\u4E48\u5224\u5C31\u600E\u4E48\u6536\u3002\u8981\u8D28\u7591\u67D0\u6761,**\u91CD\u65B0 spawn \u4E00\u4E2A\u5E72\u51C0 subagent \u590D\u6838**,\u800C\u4E0D\u662F\u4E3B\u4F1A\u8BDD\u81EA\u5DF1\u62CD\u3002
543
+ **Synthesis(\u4E3B\u4F1A\u8BDD,\u53EA\u6574\u7406\u4E0D\u91CD\u5224)**:\u6C47\u603B\u5404 subagent \u7684 findings \u2192 \u53BB\u91CD(\u540C\u6839\u56E0\u6807"\u5171\u72AF")\u2192 \u5F71\u54CD \xD7 \u6982\u7387\u6392\u5E8F \u2192 \u4E00\u4EFD\u62A5\u544A + \u68AF\u5EA6\u56FE\u3002**\u7981\u6B62\u7528\u4E3B\u4F1A\u8BDD\u7684\u5F00\u53D1\u4E0A\u4E0B\u6587\u7ED9\u4EFB\u4F55 finding \u6D17\u767D / \u964D\u7EA7**\u2014\u2014subagent \u600E\u4E48\u5224\u5C31\u600E\u4E48\u6536\u3002\u8981\u8D28\u7591\u67D0\u6761,**\u91CD\u65B0 spawn \u4E00\u4E2A\u5E72\u51C0 subagent \u590D\u6838**,\u800C\u4E0D\u662F\u4E3B\u4F1A\u8BDD\u81EA\u5DF1\u62CD\u3002\u4E3B\u4F1A\u8BDD\u53EF\u4EE5\u628A\u7ED3\u679C\u6574\u7406\u6210\u77ED\u8868,\u4F46**\u5C55\u5F00\u8BE6\u60C5\u5FC5\u987B\u4FDD\u7559 subagent \u7684\u5173\u952E\u8BC1\u636E\u5BC6\u5EA6**:\u52A8\u6001 finding \u7684\u72B6\u6001\u56FE / \u65F6\u95F4\u7EBF / \u53D8\u66F4\u8F68\u8FF9 / \u53CD\u4F8B\u8DEF\u5F84,\u9759\u6001 finding \u7684\u4E09\u89D2\u6821\u9A8C\u8BC1\u636E,\u90FD\u8981\u642C\u5230\u5BF9\u5E94\u7F16\u53F7\u8BE6\u60C5\u91CC\u3002\u5141\u8BB8\u53BB\u91CD\u548C\u5220\u6389\u91CD\u590D\u63AA\u8F9E,\u4E0D\u5141\u8BB8\u628A subagent \u7684\u8BE6\u7EC6\u6A21\u578B\u538B\u6210\u4E00\u53E5\u6458\u8981\u3002
539
544
 
540
545
  > \u53D6\u820D:\u9ED8\u8BA4 2-subagent \u65E2\u9694\u79BB\u5F00\u53D1\u6C61\u67D3\u3001\u53C8\u6D88\u6389\u9759\u6001\u644A\u8584\u52A8\u6001;hard \u7684 per-slice fan-out \u662F\u989D\u5916\u6DF1\u5EA6(\u5B9E\u6D4B\u628A image/video \u6316\u5230 \u{1F534} + \u591A subagent \u72EC\u7ACB\u6536\u655B\u63D0\u7F6E\u4FE1\u5EA6),\u8D35\u4E14\u6162,\u7531\u7528\u6237\u663E\u5F0F\u9009\u7528(\u4E0D\u81EA\u52A8\u5347\u6863)\u3002
541
546
 
@@ -625,22 +630,31 @@ correctness \u8FD9\u4E2A"\u672A\u77E5\u70B9",\u9760 **\u610F\u56FE / \u8F93\u516
625
630
 
626
631
  ---
627
632
 
628
- ## \u4EA7\u51FA findings(\u76F4\u89C2\u4F18\u5148)
633
+ ## \u4EA7\u51FA findings(\u7B80\u8868 + \u8BE6\u60C5)
634
+
635
+ \u4E25\u91CD\u5EA6 = **\u5F71\u54CD \xD7 \u6982\u7387**,\u76F4\u63A5\u7ED9\u4E58\u79EF\u7ED3\u8BBA,\u522B\u8BA9\u8BFB\u8005\u81EA\u5DF1\u6362\u7B97\u3002\u4E24\u8DEF findings \u5408\u5E76\u53BB\u91CD\u540E,\u5148\u8F93\u51FA\u4E00\u5F20**\u77ED\u5217\u8868\u683C**\u7528\u4E8E\u5FEB\u901F\u626B\u63CF,\u518D\u6309\u7F16\u53F7\u5C55\u5F00\u8BC1\u636E\u3002\u4E0D\u8981\u628A\u957F\u8BC1\u636E\u585E\u8FDB\u8868\u683C cell,\u5426\u5219\u804A\u5929\u7A97\u53E3\u4F1A\u628A\u5217\u6324\u6210\u96BE\u8BFB\u7684\u7A84\u5217\u3002
636
+
637
+ | # | \u4E25\u91CD\u5EA6 | \u4F4D\u7F6E | \u95EE\u9898\u540E\u679C | \u5904\u7406\u65B9\u5411 |
638
+ |---|---|---|---|---|
639
+ | 1 | \u{1F534}/\u{1F7E1}/\u26AA | \`file:line\` | \u4E0D\u4FEE\u4F1A\u53D1\u751F\u4EC0\u4E48(\u4E00\u53E5\u5927\u767D\u8BDD\u540E\u679C,\u4E0D\u5E26\u672F\u8BED) | \u4E00\u53E5\u8BDD\u8BF4\u660E\u4FEE\u590D\u65B9\u5411 |
629
640
 
630
- \u4E25\u91CD\u5EA6 = **\u5F71\u54CD \xD7 \u6982\u7387**,\u76F4\u63A5\u7ED9\u4E58\u79EF\u7ED3\u8BBA,\u522B\u8BA9\u8BFB\u8005\u81EA\u5DF1\u6362\u7B97\u3002\u4E24\u8DEF findings \u5408\u5E76\u53BB\u91CD,\u7EDF\u4E00\u683C\u5F0F:
641
+ \u7136\u540E\u6309\u8868\u683C\u7F16\u53F7\u5C55\u5F00:
631
642
 
632
643
  \`\`\`
633
- \u{1F534}|\u{1F7E1}|\u26AA <\u4F4D\u7F6E(file:line)> \u2014 \u4E0D\u4FEE\u4F1A\u53D1\u751F\u4EC0\u4E48(\u4E00\u53E5\u5927\u767D\u8BDD\u540E\u679C,\u4E0D\u5E26\u672F\u8BED)
634
- \u5F71\u54CD: \u574F\u5230\u4EC0\u4E48\u7A0B\u5EA6 + \u574F\u7ED9\u8C01(\u5177\u4F53:\u54EA\u7C7B\u7528\u6237 / \u79DF\u6237 / \u8C03\u7528\u65B9,\u800C\u975E\u6CDB\u6307"\u7528\u6237")
635
- \u6982\u7387: \u591A\u5927\u6982\u7387\u53D1\u751F + \u53D6\u51B3\u4E8E\u4EC0\u4E48
636
- \u8BC1\u636E: \u9759\u6001 \u2192 \u4E09\u89D2\u6821\u9A8C\u91CC\u54EA\u4E2A\u53C2\u7167\u5BF9\u4E0D\u4E0A(\u610F\u56FE / \u8F93\u5165\u57DF / \u5468\u906D);\u52A8\u6001 \u2192 \u6A21\u578B\u8BC1\u636E(\u6307\u56DE\u72B6\u6001\u56FE / \u65F6\u95F4\u7EBF\u54EA\u4E00\u5904)
637
- \u6B63\u89E3: \u5E94\u8BE5\u662F\u4EC0\u4E48 / \u88AB\u7834\u574F\u7684\u4E0D\u53D8\u91CF(\u672F\u8BED\u653E\u8FD9\u91CC)
644
+ ### #1 <\u4E00\u53E5\u8BDD\u540E\u679C>
645
+ \u5F71\u54CD: \u574F\u5230\u4EC0\u4E48\u7A0B\u5EA6 + \u574F\u7ED9\u8C01(\u5177\u4F53:\u54EA\u7C7B\u7528\u6237 / \u79DF\u6237 / \u8C03\u7528\u65B9,\u800C\u975E\u6CDB\u6307"\u7528\u6237")
646
+ \u6982\u7387: \u591A\u5927\u6982\u7387\u53D1\u751F + \u53D6\u51B3\u4E8E\u4EC0\u4E48;\u82E5\u5F85\u9A8C\u8BC1,\u5199\u660E\u5982\u4F55\u5347\u964D\u6863
647
+ \u8BC1\u636E: \u9759\u6001 \u2192 \u4E09\u89D2\u6821\u9A8C\u91CC\u54EA\u4E2A\u53C2\u7167\u5BF9\u4E0D\u4E0A(\u610F\u56FE / \u8F93\u5165\u57DF / \u5468\u906D);\u52A8\u6001 \u2192 \u4FDD\u7559 subagent \u7ED9\u51FA\u7684\u72B6\u6001\u56FE / \u65F6\u95F4\u7EBF / \u53D8\u66F4\u8F68\u8FF9 / \u53CD\u4F8B\u8DEF\u5F84,\u4E0D\u8981\u53EA\u6458\u8981\u6210\u4E00\u53E5
648
+ \u6B63\u89E3: \u5E94\u8BE5\u662F\u4EC0\u4E48 / \u88AB\u7834\u574F\u7684\u4E0D\u53D8\u91CF(\u672F\u8BED\u653E\u8FD9\u91CC)
638
649
  \`\`\`
639
650
 
640
- - \u{1F534} \u9AD8\u6982\u7387 \xD7 \u5927\u5F71\u54CD(\u5361\u53D1\u5E03) | \u{1F7E1} \u8BE5\u4FEE\u4F46\u4E0D\u6D41\u8840 | \u26AA \u987A\u624B\u3002\u964D\u5E8F\u8F93\u51FA\u3002
641
- - \u4E00\u53E5\u8BDD\u7ED3\u8BBA\u8BF4**\u540E\u679C**,\u4E0D\u8BF4\u6280\u672F\u539F\u56E0(\u539F\u56E0\u653E \`\u6B63\u89E3\`)\u3002
642
- - \u6982\u7387\u53D6\u51B3\u4E8E\u5F85\u9A8C\u8BC1\u56E0\u5B50\u5C31\u663E\u5F0F\u5199,\u5E76\u5199\u660E\u5982\u4F55\u5347\u964D\u6863;\u522B\u628A"\u5F85\u9A8C\u8BC1"\u4F2A\u88C5\u6210"\u5DF2\u786E\u8BA4"\u3002
643
- - \u591A\u6761\u540C\u6839\u56E0 \u2192 \u6807 **"\u5171\u72AF"** \u5408\u5E76\u6210\u4E00\u6761,\u522B\u540C\u4E00\u95EE\u9898\u62A5\u4E24\u904D(\u5C24\u5176\u9759\u6001/\u52A8\u6001\u5BF9\u540C\u4E00\u884C\u5404\u770B\u4E00\u9762\u65F6)\u3002
651
+ - \u{1F534} \u9AD8\u6982\u7387 \xD7 \u5927\u5F71\u54CD(\u5361\u53D1\u5E03) | \u{1F7E1} \u8BE5\u4FEE\u4F46\u4E0D\u6D41\u8840 | \u26AA \u987A\u624B\u3002\u77ED\u5217\u8868\u683C\u6309\u4E25\u91CD\u5EA6\u964D\u5E8F\u8F93\u51FA\u3002
652
+ - \`\u95EE\u9898\u540E\u679C\` \u53EA\u5199**\u540E\u679C**,\u4E0D\u5199\u6280\u672F\u539F\u56E0(\u539F\u56E0\u653E\u5C55\u5F00\u8BE6\u60C5\u7684 \`\u8BC1\u636E\` / \`\u6B63\u89E3\`)\u3002
653
+ - \`\u4F4D\u7F6E\` \u7528\u53EF\u5B9A\u4F4D\u7684 \`file:line\`;\u5982\u679C\u662F\u5408\u5E76\u540E\u7684\u5171\u72AF finding,\u8868\u683C\u7528\u6700\u6838\u5FC3\u4F4D\u7F6E,\u5728\u5C55\u5F00\u8BE6\u60C5\u7684 \`\u8BC1\u636E\` \u4E2D\u8865\u5145\u5176\u5B83\u4F4D\u7F6E\u3002
654
+ - \`\u5904\u7406\u65B9\u5411\` \u5728\u77ED\u8868\u91CC\u53EA\u5199\u4E00\u53E5,\u5B8C\u6574 \`\u6B63\u89E3\` \u653E\u5C55\u5F00\u8BE6\u60C5\u3002
655
+ - \u5C55\u5F00\u8BE6\u60C5\u662F\u4FE1\u606F\u5B8C\u6574\u9762:\u5982\u679C subagent \u5DF2\u7ED9\u51FA\u52A8\u6001\u6A21\u578B\u3001\u65F6\u95F4\u7EBF\u3001\u8DEF\u5F84\u7F16\u53F7\u6216\u5173\u952E\u4EE3\u7801\u4F4D\u7F6E,\u6700\u7EC8\u62A5\u544A\u5FC5\u987B\u4FDD\u7559\u8FD9\u4E9B\u7EC6\u8282;\u77ED\u8868\u53EF\u4EE5\u7B80,\u8BE6\u60C5\u4E0D\u80FD\u628A\u8BC1\u636E\u538B\u6CA1\u3002
656
+ - \u591A\u6761\u540C\u6839\u56E0 \u2192 \u5728 \`\u95EE\u9898\u540E\u679C\` \u6216\u5C55\u5F00\u8BE6\u60C5\u7684 \`\u8BC1\u636E\` \u6807 **"\u5171\u72AF"** \u5E76\u5408\u5E76\u6210\u4E00\u6761,\u522B\u540C\u4E00\u95EE\u9898\u62A5\u4E24\u904D(\u5C24\u5176\u9759\u6001/\u52A8\u6001\u5BF9\u540C\u4E00\u884C\u5404\u770B\u4E00\u9762\u65F6)\u3002
657
+ - \u8868\u683C\u540E\u53EA\u5C55\u5F00\u6BCF\u6761 finding \u4E00\u6B21;\u4E0D\u8981\u518D\u91CD\u590D\u4E00\u5F20 7 \u5217\u957F\u8868\u3002
644
658
  - \u6536\u5C3E\u7ED9\u4E00\u5F20 **\u5F71\u54CD \xD7 \u6982\u7387** \u68AF\u5EA6\u56FE:
645
659
 
646
660
  \`\`\`
@@ -681,7 +695,7 @@ Three tiers by scale (**all enter at least one clean subagent**). **You pick bet
681
695
 
682
696
  Subagent prompt template: \`Read this skill (cr/SKILL.md); apply only the matching Part to <your chunk (all of Part A / one dynamic slice)>, triage it yourself, and output findings in the unified format\`.
683
697
 
684
- **Synthesis (main session, organize only \u2014 never re-judge)**: gather each subagent's findings \u2192 dedup (mark same-root-cause as "accomplices") \u2192 sort by impact \xD7 probability \u2192 one report + gradient chart. **It is forbidden to use the main session's dev context to whitewash / downgrade any finding** \u2014 take what the subagent judged as-is. To dispute one, **spawn a fresh clean subagent to re-check**, not the main session deciding on its own.
698
+ **Synthesis (main session, organize only \u2014 never re-judge)**: gather each subagent's findings \u2192 dedup (mark same-root-cause as "accomplices") \u2192 sort by impact \xD7 probability \u2192 one report + gradient chart. **It is forbidden to use the main session's dev context to whitewash / downgrade any finding** \u2014 take what the subagent judged as-is. To dispute one, **spawn a fresh clean subagent to re-check**, not the main session deciding on its own. The main session may organize the result into a short summary table, but the **expanded details must preserve the subagent's key evidence density**: for dynamic findings, carry over the state diagram / timeline / change trajectory / counterexample path; for static findings, carry over the triangulation evidence. Deduplication and wording cleanup are allowed; compressing a detailed subagent model into a one-line summary is not.
685
699
 
686
700
  > Trade-off: the default 2-subagent both isolates dev pollution and removes static-dilutes-dynamic; hard's per-slice fan-out is extra depth (in practice it dug image/video up to \u{1F534}, and multiple independently-converging subagents raised confidence), expensive and slow, selected explicitly by the user (never auto-escalated).
687
701
 
@@ -771,22 +785,31 @@ These two diagrams are the evidence \u2014 many bugs (a 6-second gap, a key crea
771
785
 
772
786
  ---
773
787
 
774
- ## Produce findings (intuitive first)
788
+ ## Produce findings (summary table + details)
775
789
 
776
- Severity = **impact \xD7 probability**; give the product conclusion directly, don't make the reader do the math. Merge and dedup findings from both tracks, unified format:
790
+ Severity = **impact \xD7 probability**; give the product conclusion directly, don't make the reader do the math. Merge and dedup findings from both tracks. First output a **short summary table** for scanning, then expand each finding by number. Do not put long evidence into table cells; chat windows squeeze wide Markdown tables into unreadable narrow columns.
791
+
792
+ | # | Severity | Location | Consequence | Direction |
793
+ |---|---|---|---|---|
794
+ | 1 | \u{1F534}/\u{1F7E1}/\u26AA | \`file:line\` | What happens if unfixed (one plain-language consequence, no jargon) | One-sentence repair direction |
795
+
796
+ Then expand by table number:
777
797
 
778
798
  \`\`\`
779
- \u{1F534}|\u{1F7E1}|\u26AA <location (file:line)> \u2014 what happens if unfixed (one plain-language consequence, no jargon)
780
- Impact: how bad + to whom (specific: which users / tenants / callers, not a vague "users")
781
- Probability: how likely + what it depends on
782
- Evidence: static \u2192 which reference fails in triangulation (intent / input domain / surroundings); dynamic \u2192 model evidence (point back to which spot on the state diagram / timeline)
783
- Fix: what it should be / the broken invariant (jargon goes here)
799
+ ### #1 <one-line consequence>
800
+ Impact: how bad + to whom (specific: which users / tenants / callers, not a vague "users")
801
+ Probability: how likely + what it depends on; if uncertain, say how to raise/lower the tier
802
+ Evidence: static \u2192 which triangulation reference fails (intent / input domain / surroundings); dynamic \u2192 preserve the subagent's state diagram / timeline / change trajectory / counterexample path, not a one-line summary
803
+ Fix: what it should be / the broken invariant (jargon goes here)
784
804
  \`\`\`
785
805
 
786
- - \u{1F534} high probability \xD7 big impact (blocks release) | \u{1F7E1} should fix but not bleeding | \u26AA nice-to-have. Output in descending order.
787
- - The one-line conclusion states the **consequence**, not the technical cause (cause goes in \`Fix\`).
788
- - If probability depends on a to-be-verified factor, say so explicitly and write how to raise/lower the tier; don't disguise "to be verified" as "confirmed".
789
- - Multiple same-root-cause \u2192 mark as **"accomplices"** and merge into one; don't report the same problem twice (especially when static/dynamic each see one side of the same line).
806
+ - \u{1F534} high probability \xD7 big impact (blocks release) | \u{1F7E1} should fix but not bleeding | \u26AA nice-to-have. Sort summary table rows in descending severity.
807
+ - \`Consequence\` states the **outcome**, not the technical cause (cause goes in the expanded \`Evidence\` / \`Fix\`).
808
+ - \`Location\` must be a precise \`file:line\`; for a merged accomplice finding, use the core location in the table and mention other locations in the expanded \`Evidence\`.
809
+ - Keep \`Direction\` in the summary table to one sentence; put the complete \`Fix\` in the expanded detail.
810
+ - Expanded details are the complete information surface: if a subagent provided a dynamic model, timeline, path labels, or key code locations, the final report must carry those details forward; the summary table may be short, the detail must not erase evidence.
811
+ - Multiple same-root-cause \u2192 mark as **"accomplices"** in \`Consequence\` or expanded \`Evidence\` and merge into one finding; don't report the same problem twice (especially when static/dynamic each see one side of the same line).
812
+ - Expand each finding only once after the table; don't also emit a 7-column long table.
790
813
  - Close with an **impact \xD7 probability** gradient chart:
791
814
 
792
815
  \`\`\`
@@ -1170,349 +1193,485 @@ Finally emit [Overall recap: end-to-end interaction verification + total deliver
1170
1193
  // packages/feature/agent/src/server/lib/htmlPrompt.ts
1171
1194
  var HTML_PROMPT_ZH = `---
1172
1195
  name: html
1173
- description: "\u751F\u6210\u53EF\u4EA4\u4E92\u7684 HTML \u5E94\u7528\uFF1A\u5185\u7F6E cockpit bash SDK\uFF0C\u6267\u884C\u547D\u4EE4\u53D6/\u6539\u6570\u636E"
1174
- argument-hint: "\u63CF\u8FF0\u4F60\u60F3\u8981\u7684 HTML \u5E94\u7528"
1196
+ description: "\u751F\u6210\u53EF\u4EA4\u4E92\u7684\u672C\u5730 React \u5E94\u7528\uFF1A\u5185\u7F6E cockpit bash SDK\uFF0C\u6267\u884C\u547D\u4EE4\u53D6/\u6539\u6570\u636E"
1197
+ argument-hint: "\u63CF\u8FF0\u4F60\u60F3\u8981\u7684\u5E94\u7528"
1175
1198
  ---
1176
1199
 
1177
- # \u751F\u6210 Cockpit HTML \u5E94\u7528
1200
+ # \u751F\u6210 Cockpit \u5E94\u7528\uFF08\u672C\u5730 React \u9875\u9762\uFF09
1178
1201
 
1179
- \u4E00\u4E2A\u80FD\u53D6/\u6539\u6570\u636E\u7684 HTML \u5E94\u7528\uFF1A\u9884\u89C8\u65F6\u6CE8\u5165\u4E86\u5168\u5C40 \`window.cockpit\` SDK \u2014\u2014 \u672C\u8D28\u5C31\u662F Bash
1180
- \u5DE5\u5177\u66B4\u9732\u7ED9\u9875\u9762\uFF0C\u9875\u9762\u6309\u94AE\u80FD \`curl\`\u3001\u8BFB\u5199\u6587\u4EF6\u3001\u8DD1\u811A\u672C\u3002
1202
+ \u4E00\u4E2A**\u672C\u5730\u5E94\u7528**\uFF0C\u4E0D\u662F\u4E00\u6B21\u6027\u9875\u9762\uFF1A\u9884\u89C8\u65F6\u6CE8\u5165\u4E86\u5168\u5C40 \`window.cockpit\` SDK \u2014\u2014 \u672C\u8D28\u5C31\u662F Bash \u5DE5\u5177
1203
+ \u66B4\u9732\u7ED9\u9875\u9762\uFF0C\u6309\u94AE\u80FD \`curl\`\u3001\u8BFB\u5199\u6587\u4EF6\u3001\u8DD1\u811A\u672C\uFF1B\u6570\u636E\u843D\u76D8\u5728 app \u76EE\u5F55\u91CC\uFF0C\u91CD\u5F00\u5373\u7528\u3002
1181
1204
 
1182
- **\u9ED8\u8BA4\u7528 React \u5199**\uFF08\u4E0B\u9762\u7684\u672C\u5730\u96F6\u6784\u5EFA\u6808\uFF0Cproductized\u3001AI \u6700\u719F\uFF0C\u9002\u5408\u6709\u72B6\u6001 / \u591A\u89C6\u56FE /
1183
- \u8868\u5355\uFF09\uFF1B\u53EA\u6709**\u6781\u7B80\u5355\u7684\u5355\u89C6\u56FE\u3001\u6216\u7EAF\u9759\u6001\u4E00\u6B21\u6027\u9875**\u624D\u9000\u56DE\u5355\u6587\u4EF6\u5185\u8054 HTML\u3002\u4E24\u79CD\u6A21\u5F0F\u5171\u7528\u4E0B\u9762
1184
- \u8FD9\u5957\u5951\u7EA6\u3002
1205
+ \u96F6\u6784\u5EFA\uFF1AReact / Babel \u4ECE\u540C\u6E90\u7684 \`/html-lib\` \u52A0\u8F7D\uFF0C\u4E0D\u6253\u5305\u3001\u4E0D\u88C5\u4F9D\u8D56\u3001\u79BB\u7EBF\u53EF\u7528\u3002
1185
1206
 
1186
- ## \u6D41\u7A0B\uFF1A\u5148\u5BF9\u9F50\uFF0C\u786E\u8BA4\u540E\u518D\u5199\uFF08\u91CD\u8981\uFF09
1207
+ ## \u4E00\u3001\u6D41\u7A0B\uFF1A\u5148\u5BF9\u9F50\uFF0C\u786E\u8BA4\u540E\u518D\u5199\uFF08\u91CD\u8981\uFF09
1187
1208
 
1188
1209
  **\u522B\u4E00\u4E0A\u6765\u5C31\u5199\u6587\u4EF6**\uFF0C\u5206\u4E09\u6B65\uFF1A
1189
1210
 
1190
- 1. **\u5BF9\u9F50\u9700\u6C42** \u2014\u2014 \u5148\u590D\u8FF0\u4F60\u5BF9\u8FD9\u4E2A HTML \u5E94\u7528\u7684\u7406\u89E3\uFF08\u505A\u4EC0\u4E48\u3001\u7528 React \u8FD8\u662F\u5355\u6587\u4EF6\u3001\u6570\u636E\u4ECE\u54EA\u6765\u3001
1191
- \u5173\u952E\u529F\u80FD\u4E0E\u4EA4\u4E92\uFF09\u3002\u4EC5\u5F53\u9700\u6C42\u6709\u6B67\u4E49\u6216\u5B58\u5728\u591A\u79CD\u5408\u7406\u89E3\u8BFB\u65F6\u624D\u56DE\u95EE\uFF1B\u5DF2\u7ECF\u6E05\u695A\u5C31\u76F4\u63A5\u7ED9\u4E00\u6BB5\u7B80\u77ED
1192
- \u7684\u7406\u89E3\u6458\u8981\uFF0C\u4E0D\u5FC5\u9010\u6761\u8FFD\u95EE\u3002
1193
- 2. **\u544A\u77E5\u5B58\u653E\u76EE\u5F55** \u2014\u2014 \u6309\u4E0B\u9762\u300C\u5B58\u653E\u4F4D\u7F6E\u300D\u89C4\u5219\u5B9A\u51FA \`<\u76EE\u5F55>/<name>/\`\uFF0C\u660E\u786E\u544A\u8BC9\u7528\u6237\u51C6\u5907\u5B58\u54EA\u3001
1194
- \u53EB\u4EC0\u4E48\u540D\uFF1B\u7528\u6237\u53EF\u5426\u51B3\u6216\u6539\u76EE\u5F55 / \u6539\u540D\u3002
1195
- 3. **\u786E\u8BA4\u540E\u624D\u5199** \u2014\u2014 \u7B49\u7528\u6237\u660E\u786E\u540C\u610F\uFF08\u786E\u8BA4 / \u5F00\u59CB / \u5199\u5427 / go \u7B49\u4EFB\u610F\u80AF\u5B9A\u8868\u793A\uFF09\u518D \`Write\`
1196
- \u843D\u76D8\u3002\u786E\u8BA4\u4E4B\u524D\u53EA\u8BA8\u8BBA\u3001\u4E0D\u5199\u6587\u4EF6\u3002
1197
-
1198
- \u5373\u4FBF\u7528\u6237\u5728 \`/html\` \u540E\u5DF2\u7ECF\u628A\u9700\u6C42\u548C\u76EE\u5F55\u90FD\u5199\u6E05\u695A\u4E86\uFF0C\u4E5F\u8981\u5148\u505C\u4E00\u8F6E\uFF0C\u7ED9\u51FA\u300C\u7406\u89E3 + \u76EE\u5F55 + \u4E00\u53E5
1199
- \u300E\u786E\u8BA4\u5C31\u5F00\u59CB\u5199?\u300F\u300D\u7B49\u5F85\u786E\u8BA4\u2014\u2014\u4F46\u8FD9\u4E00\u8F6E\u8981**\u8F7B\u91CF**\uFF0C\u522B\u53CD\u590D\u8FFD\u95EE\u3002
1200
-
1201
- ## \u5951\u7EA6\uFF1Awindow.cockpit\uFF08\u4E24\u6A21\u5F0F\u5171\u7528\uFF0C\u53EA\u6B64\u4E00\u5904\uFF09
1211
+ 1. **\u5BF9\u9F50\u9700\u6C42** \u2014\u2014 \u5148\u590D\u8FF0\u4F60\u5BF9\u8FD9\u4E2A\u5E94\u7528\u7684\u7406\u89E3\uFF08\u505A\u4EC0\u4E48\u3001\u6570\u636E\u4ECE\u54EA\u6765\u3001\u5173\u952E\u529F\u80FD\u4E0E\u4EA4\u4E92\uFF09\u3002\u4EC5\u5F53\u9700\u6C42\u6709
1212
+ \u6B67\u4E49\u6216\u5B58\u5728\u591A\u79CD\u5408\u7406\u89E3\u8BFB\u65F6\u624D\u56DE\u95EE\uFF1B\u5DF2\u7ECF\u6E05\u695A\u5C31\u76F4\u63A5\u7ED9\u4E00\u6BB5\u7B80\u77ED\u7684\u7406\u89E3\u6458\u8981\uFF0C\u4E0D\u5FC5\u9010\u6761\u8FFD\u95EE\u3002
1213
+ 2. **\u544A\u77E5\u5B58\u653E\u76EE\u5F55** \u2014\u2014 \u5B9A\u51FA \`<\u76EE\u5F55>/<name>/\`\uFF0C\u660E\u786E\u544A\u8BC9\u7528\u6237\u51C6\u5907\u5B58\u54EA\u3001\u53EB\u4EC0\u4E48\u540D\uFF1B\u7528\u6237\u53EF\u5426\u51B3\u6216\u6539\uFF1A
1214
+ - \`<name>\` **\u4E00\u5F8B\u7531\u4F60\u6309\u9700\u6C42\u8D77\u540D**\uFF08\u77ED\u6A2A\u7EBF\u5C0F\u5199\uFF0C\u4E0E \`cockpit-name\` \u4E00\u81F4\uFF09\uFF0C\u7528\u6237\u53EA\u63CF\u8FF0\u9700\u6C42\u3001
1215
+ \u4E0D\u8D1F\u8D23\u8D77\u540D\u3002
1216
+ - \u7528\u6237\u5728\u9700\u6C42\u91CC\u7ED9\u4E86\u76EE\u5F55 \u2192 \`<\u7528\u6237\u7ED9\u7684\u76EE\u5F55>/<name>/\`\uFF1B\u6CA1\u7ED9 \u2192 **\u5F53\u524D\u804A\u5929\u5DE5\u4F5C\u76EE\u5F55**\uFF08\u672C\u6B21\u4F1A\u8BDD\u7684
1217
+ cwd\uFF09\u4E0B\u7684 \`<name>/\`\u3002
1218
+ - \u4E00\u4E2A app \u7684\u6240\u6709\u6587\u4EF6\u90FD\u5728\u8FD9\u4E2A\u76EE\u5F55\u91CC\uFF0C\u6570\u636E\u6309\u7B2C\u56DB\u8282\u5206\u5230 \`cache/\` \`state/\` \`out/\`\u3002\u522B\u628A\u6587\u4EF6
1219
+ \u6563\u843D\u5230\u522B\u5904\uFF0C\u4E5F\u522B\u81EA\u9020 \`.cockpit-apps\` \u4E4B\u7C7B\u76EE\u5F55\u3002
1220
+ 3. **\u786E\u8BA4\u540E\u624D\u5199** \u2014\u2014 \u7B49\u7528\u6237\u660E\u786E\u540C\u610F\uFF08\u786E\u8BA4 / \u5F00\u59CB / \u5199\u5427 / go \u7B49\u4EFB\u610F\u80AF\u5B9A\u8868\u793A\uFF09\u518D \`Write\` \u843D\u76D8\u3002
1221
+ \u786E\u8BA4\u4E4B\u524D\u53EA\u8BA8\u8BBA\u3001\u4E0D\u5199\u6587\u4EF6\u3002
1202
1222
 
1203
- \u9875\u9762\u52A0\u8F7D\u65F6 SDK \u5DF2\u5C31\u7EEA\uFF0C\u65E0\u9700\u5F15\u5165\u4EFB\u4F55\u5E93\uFF1A
1204
-
1205
- - \`cockpit.cwd: string\` \u2014\u2014 \u5F53\u524D\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF0C\u76F8\u5BF9\u8DEF\u5F84\u547D\u4EE4\u9ED8\u8BA4\u5728\u6B64\u6267\u884C\u3002
1206
- - \`cockpit.bash(command, opts?)\` \u2014\u2014 \u6267\u884C\u4E00\u6761 bash \u547D\u4EE4\uFF08\`command\` \u662F\u6574\u6761 shell \u4E32\uFF09\uFF1A
1207
- - **\u524D\u53F0**\uFF08\u9ED8\u8BA4\uFF0C\u77ED/\u79BB\u6563\u547D\u4EE4\uFF09\u2192 \`Promise<{ stdout, stderr, exitCode }>\`\uFF0C\`await\` \u540E\u4E00\u6B21\u6027\u62FF\u5168\u90E8\u8F93\u51FA\u3002
1208
- - **\u540E\u53F0**\uFF08\`opts.background: true\`\uFF0C\u957F/\u5B9E\u65F6\u547D\u4EE4\uFF09\u2192 \`{ kill() }\`\uFF0C\u7ECF\u56DE\u8C03\u6D41\u5F0F\u8F93\u51FA\uFF1A
1209
- \`opts = { background:true, cwd?, onOutput?, onStderr?, onExit?, onError? }\`\u3002\u5927/\u6301\u7EED\u8F93\u51FA\u5FC5\u987B\u7528\u540E\u53F0\uFF08\u524D\u53F0\u5168\u7F13\u5B58\u5728\u5185\u5B58\uFF09\u3002
1210
-
1211
- \u5199\u8FD9\u7C7B\u9875\u9762\u5FC5\u987B\u5B88\u7684\u51E0\u6761\uFF1A
1212
-
1213
- 1. **\u6570\u636E\u53EA\u8D70 \`cockpit.bash\`**\uFF08\`curl\` \u53D6\u3001\`curl -X POST\` \u6539\u3001\u6216\u8BFB\u5199\u6587\u4EF6\uFF09\uFF0C**\u7EDD\u4E0D\u7528
1214
- \`fetch(\u5916\u90E8URL)\`** \u2014\u2014 \u540C\u6E90\u6C99\u7BB1\u4F1A CORS \u62E6\u622A\u3002
1215
- 2. **\u4E24\u7C7B\u5931\u8D25\u90FD\u8981\u5904\u7406**\uFF08\u524D\u53F0\uFF09\uFF1A\`try/catch\` \u6293\u5230\u7684**\u629B\u5F02\u5E38 = spawn/\u57FA\u5EFA\u5931\u8D25**\uFF08\u574F\u8DEF\u5F84\u3001
1216
- \u8FDE\u63A5\u65AD\uFF09\uFF1B\u6CA1\u629B\u4F46 **\`exitCode !== 0\` = \u547D\u4EE4\u8DD1\u4E86\u4F46\u5931\u8D25**\uFF0C\u5C55\u793A \`stderr\` \u6216 stdout \u91CC\u7684\u9519\u8BEF
1217
- \u4F53\uFF0C\u522B\u53EA\u5199"\u5931\u8D25"\u3002\u540E\u53F0\u5BF9\u5E94 \`onError\`\uFF08\u57FA\u5EFA\uFF09+ \`onExit\` \u975E\u96F6 \`code\`\uFF08\u547D\u4EE4\uFF09\u3002
1218
- 3. **\`command\` \u662F shell \u4E32**\uFF1A\u52A8\u6001\u503C/\u7528\u6237\u8F93\u5165\u62FC\u8FDB\u53BB\u524D\u5FC5\u987B\u6821\u9A8C\u6216\u8F6C\u4E49\uFF08shell \u5143\u5B57\u7B26\u4F1A\u6CE8\u5165\uFF09\uFF1B
1219
- \u80FD\u7528\u56FA\u5B9A\u547D\u4EE4\u5C31\u522B\u88F8\u62FC\u3002
1220
- 4. **\u8D44\u6E90\u5F15\u7528**\uFF1A\u540C\u7EA7\u76F8\u5BF9\uFF08\`./app.jsx\`\u3001\`./style.css\`\u3001\u56FE\u7247\uFF09\u548C CDN \u7EDD\u5BF9 URL \u90FD\u80FD\u52A0\u8F7D\uFF1B\u6839
1221
- \u76F8\u5BF9\uFF08\`/assets/x.css\`\uFF09\u4F1A 404\u3002
1222
-
1223
- meta \u5934\uFF08\u52A1\u5FC5\u5199\u4E0A\uFF0C\u4F9B HTML \u9762\u677F\u505A\u5361\u7247\u3001console \`/name\` \u6253\u5F00\uFF09\uFF1A
1224
-
1225
- \`\`\`html
1226
- <title>\u9875\u9762\u6807\u9898</title>
1227
- <meta name="cockpit-name" content="short-name"> <!-- /name \u77ED\u540D\uFF0C\u552F\u4E00\u3001\u53EA\u7528\u5B57\u6BCD\u6570\u5B57-_ -->
1228
- <meta name="description" content="\u4E00\u53E5\u8BDD\u8BF4\u660E">
1229
- <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji \u6216\u56FE\u6807 url\uFF0C\u53EF\u9009 -->
1230
- <meta name="cockpit-theme" content="auto"> <!-- \u542F\u7528\u53EF\u62D6\u52A8\u7684\u4EAE/\u6697\u5207\u6362\u6309\u94AE\uFF1Bauto=\u9996\u6B21\u8DDF\u968F\u7CFB\u7EDF\uFF0C\u53EF\u586B light/dark -->
1231
- \`\`\`
1223
+ \u5373\u4FBF\u7528\u6237\u5728 \`/html\` \u540E\u5DF2\u7ECF\u628A\u9700\u6C42\u548C\u76EE\u5F55\u90FD\u5199\u6E05\u695A\u4E86\uFF0C\u4E5F\u8981\u5148\u505C\u4E00\u8F6E\uFF0C\u7ED9\u51FA\u300C\u7406\u89E3 + \u76EE\u5F55 + \u4E00\u53E5\u300E\u786E\u8BA4
1224
+ \u5C31\u5F00\u59CB\u5199?\u300F\u300D\u7B49\u5F85\u786E\u8BA4\u2014\u2014\u4F46\u8FD9\u4E00\u8F6E\u8981**\u8F7B\u91CF**\uFF0C\u522B\u53CD\u590D\u8FFD\u95EE\u3002
1232
1225
 
1233
- ## \u9ED8\u8BA4\uFF1AReact\uFF08\u672C\u5730\u96F6\u6784\u5EFA\u6808\uFF09
1226
+ ## \u4E8C\u3001\u9AA8\u67B6\uFF1A\u4E09\u4E2A\u6587\u4EF6\uFF0C\u7167\u6284
1234
1227
 
1235
- \u5916\u58F3\u56FA\u5B9A\u8FD9\u6837\u5199\uFF0C\u903B\u8F91\u62C6\u5230\u540C\u7EA7 \`./app.jsx\`\uFF1A
1228
+ \`index.html\` \u2014\u2014 \u5916\u58F3\u56FA\u5B9A\u8FD9\u6837\u5199\uFF0C\u522B\u81EA\u7531\u53D1\u6325\uFF1A
1236
1229
 
1237
1230
  \`\`\`html
1238
- <div id="root"></div>
1239
- <script src="/html-lib/react.production.min.js"></script>
1240
- <script src="/html-lib/react-dom.production.min.js"></script>
1241
- <script src="/html-lib/babel.min.js"></script>
1242
- <script>
1243
- (async () => {
1244
- const src = await (await fetch('./app.jsx')).text();
1245
- // \u5FC5\u987B classic runtime\uFF1A\u9ED8\u8BA4 automatic \u4F1A\u6CE8\u5165 import react/jsx-runtime\uFF0C
1246
- // \u4F5C\u4E3A\u666E\u901A\u811A\u672C\u6CE8\u5165\u4F1A\u62A5 "Cannot use import statement outside a module"
1247
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1248
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1249
- })();
1250
- </script>
1231
+ <!doctype html>
1232
+ <html>
1233
+ <head>
1234
+ <meta charset="utf-8">
1235
+ <title>\u9875\u9762\u6807\u9898</title>
1236
+ <meta name="cockpit-name" content="short-name"> <!-- /name \u77ED\u540D\uFF0C\u552F\u4E00\u3001\u53EA\u7528\u5B57\u6BCD\u6570\u5B57-_ -->
1237
+ <meta name="description" content="\u4E00\u53E5\u8BDD\u8BF4\u660E">
1238
+ <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji \u6216\u56FE\u6807 url\uFF0C\u53EF\u9009 -->
1239
+ <meta name="cockpit-theme" content="auto"> <!-- \u4EAE/\u6697\u5207\u6362\uFF0C\u89C1\u7B2C\u4E94\u8282 -->
1240
+ <link rel="stylesheet" href="/html-lib/theme.css">
1241
+ </head>
1242
+ <body>
1243
+ <div id="root"></div>
1244
+ <script src="/html-lib/react.production.min.js"></script>
1245
+ <script src="/html-lib/react-dom.production.min.js"></script>
1246
+ <script src="/html-lib/babel.min.js"></script>
1247
+ <script>
1248
+ (async () => {
1249
+ const src = await (await fetch('./app.jsx')).text();
1250
+ // \u5FC5\u987B classic runtime\uFF1A\u9ED8\u8BA4 automatic \u4F1A\u6CE8\u5165 import react/jsx-runtime\uFF0C
1251
+ // \u4F5C\u4E3A\u666E\u901A\u811A\u672C\u6CE8\u5165\u4F1A\u62A5 "Cannot use import statement outside a module"
1252
+ const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1253
+ const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1254
+ })();
1255
+ </script>
1256
+ </body>
1257
+ </html>
1251
1258
  \`\`\`
1252
1259
 
1253
- - \`/html-lib\` \u662F Cockpit \u672C\u5730\u6258\u7BA1\uFF08\u540C\u6E90\u3001\u79BB\u7EBF\u53EF\u7528\uFF0C\u65E0\u9700 CDN\uFF09\u3002
1254
- - \`app.jsx\` \u7528\u5168\u5C40 \`React\`\uFF08\`const { useState } = React;\`\uFF09\uFF0C\u672B\u5C3E
1255
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`\u3002
1256
- - \u6837\u5F0F\u9ED8\u8BA4\u5957 **Cockpit \u4E3B\u9898**\uFF1A\`<link rel="stylesheet" href="/html-lib/theme.css">\`\uFF0C\u76F4\u63A5\u7528
1257
- \u8BED\u4E49\u53D8\u91CF\uFF08\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` / \`--muted-foreground\` /
1258
- \`--border\` / \`--destructive\`\uFF1B\u534A\u900F\u660E\u7528 \`hsl(var(--green-9) / .12)\`\uFF09\u3002\u4E0A\u9762 meta \u5934\u91CC\u7684
1259
- \`cockpit-theme\` \u8BA9\u9884\u89C8\u5E26\u4E0A\u4E00\u4E2A\u6D6E\u52A8\u4EAE/\u6697\u5207\u6362\u6309\u94AE\uFF08\u9ED8\u8BA4\u53F3\u4E0A\u89D2\uFF0C\u7528\u6237\u53EF\u62D6\u52A8\u5438\u9644\u5230\u4EFB\u610F\u4E00\u89D2\uFF0C
1260
- \u6240\u4EE5\u4E0D\u5FC5\u4E3A\u5B83\u5728\u53F3\u4E0A\u89D2\u7559\u7A7A\u4F4D\uFF09\uFF1B**\u9ED8\u8BA4\u8DDF\u968F Cockpit \u5BBF\u4E3B\u4E3B\u9898**\uFF08Cockpit \u5916\u6253\u5F00\u65F6\u6309
1261
- meta \u7684 auto/light/dark\uFF09\uFF0C\u624B\u52A8\u5207\u6362\u540E**\u6309 app \u8BB0\u4F4F\u9009\u62E9\uFF08\u8DE8\u5237\u65B0\uFF0C\u4F18\u5148\u4E8E\u8DDF\u968F\uFF09**\uFF0C\u4E5F\u53EF\u8C03
1262
- \`cockpit.toggleTheme()\`\u3002**\u522B\u81EA\u62CD\u8C03\u8272\u677F\u3001\u522B\u4E0A Tailwind**\uFF1Bapp \u7279\u6709\u7EC6\u8282\u518D\u8865\u4E00\u5C0F\u6BB5 \`<style>\`\u3002
1260
+ meta \u5934\u56DB\u884C\u90FD\u8981\u5199\uFF1AHTML \u9762\u677F\u9760\u5B83\u505A\u5361\u7247\uFF0Cconsole \u9760 \`cockpit-name\` \u7528 \`/name\` \u6253\u5F00\u3002
1263
1261
 
1264
- React \u91CC\u53D6\u6570\u540C\u6837\u8D70 \`cockpit.bash\`\uFF1A
1262
+ \`app.jsx\` \u2014\u2014 \u5168\u5C40 \`React\`\uFF0C\u672B\u5C3E render\u3002**\u6253\u5F00\u53EA\u8BFB\u7F13\u5B58\uFF0C\u70B9\u5237\u65B0\u624D\u56DE\u6E90**\uFF1A
1265
1263
 
1266
1264
  \`\`\`jsx
1265
+ const { useState, useEffect, useCallback } = React;
1266
+
1267
1267
  function App() {
1268
- const [stars, setStars] = React.useState(null);
1269
- const load = async () => {
1270
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1271
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : '\u51FA\u9519');
1272
- };
1273
- return <button onClick={load}>\u2B50 {stars ?? '\u52A0\u8F7D'}</button>;
1268
+ const [data, setData] = useState(null);
1269
+ const [busy, setBusy] = useState(false);
1270
+ const [err, setErr] = useState(null);
1271
+
1272
+ const load = useCallback(async (refresh) => {
1273
+ setBusy(true); setErr(null);
1274
+ try {
1275
+ const arg = btoa(JSON.stringify({ refresh: !!refresh })); // \u7ED3\u6784\u5316\u5165\u53C2\u8D70 base64
1276
+ const { stdout, stderr, exitCode } = await cockpit.bash(\`node ./api.mjs \${arg}\`);
1277
+ if (exitCode !== 0) throw new Error(stderr || '\u811A\u672C\u5931\u8D25'); // \u547D\u4EE4\u8DD1\u4E86\u4F46\u5931\u8D25
1278
+ const d = JSON.parse(stdout);
1279
+ if (d.error && !d.stale) throw new Error(d.error);
1280
+ setData(d);
1281
+ if (d.stale) setErr(d.error); // \u6709\u65E7\u6570\u636E\u53EF\u663E\u793A
1282
+ } catch (e) {
1283
+ setErr(e.message); // \u629B\u5F02\u5E38 = spawn/\u57FA\u5EFA\u5931\u8D25\u3002\u5DF2\u6709 data \u5C31\u7559\u7740\uFF0C\u522B\u6E05\u6210\u9519\u8BEF\u9875
1284
+ } finally { setBusy(false); }
1285
+ }, []);
1286
+
1287
+ useEffect(() => { load(false); }, [load]); // \u6253\u5F00\uFF1A\u53EA\u8BFB\u7F13\u5B58\uFF0C0 \u6B21\u56DE\u6E90
1288
+
1289
+ if (!data) return <div>{err ?? '\u52A0\u8F7D\u4E2D\u2026'}</div>;
1290
+ return (
1291
+ <div>
1292
+ <span>{data.cached ? '\u7F13\u5B58' : '\u5DF2\u66F4\u65B0'} \xB7 {new Date(data.fetchedAt).toLocaleString('zh-CN')}</span>
1293
+ <button disabled={busy} onClick={() => load(true)}>{busy ? '\u5237\u65B0\u4E2D\u2026' : '\u5237\u65B0'}</button>
1294
+ {err && <em>\u5237\u65B0\u5931\u8D25\uFF0C\u4E0B\u9762\u662F\u65E7\u6570\u636E\uFF1A{err}</em>}
1295
+ {/* \u6E32\u67D3 data */}
1296
+ </div>
1297
+ );
1274
1298
  }
1275
- \`\`\`
1276
1299
 
1277
- ## \u540E\u7AEF\uFF1A\u590D\u6742\u903B\u8F91\u5199\u6210\u811A\u672C\uFF08CGI \u98CE\u683C\uFF0C\u4E24\u6A21\u5F0F\u901A\u7528\uFF09
1300
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
1301
+ \`\`\`
1278
1302
 
1279
- \u547D\u4EE4\u5F88\u77ED\u5C31\u5185\u8054\u5728 \`cockpit.bash("...")\`\uFF1B\u903B\u8F91\u590D\u6742\uFF08\u591A\u6B65\u3001\u89E3\u6790\u3001\u5FAA\u73AF\u3001\u8981\u5E93\u3001\u5199\u5E93\uFF09\u65F6\uFF0C\u628A\u540E\u7AEF
1280
- \u5199\u6210\u540C\u76EE\u5F55**\u811A\u672C\u6587\u4EF6**\uFF0C\`cockpit.bash\` \u8C03\u5B83\u2014\u2014HTML/JSX \u7BA1\u5C55\u793A\uFF0C\u811A\u672C\u5F53\u540E\u7AEF\u5904\u7406\u5668\u3002
1303
+ \`api.mjs\` \u2014\u2014 \u540E\u7AEF\u5904\u7406\u5668\uFF1A\u53D6\u6570\u3001\u89E3\u6790\u3001\u843D\u76D8\u90FD\u5728\u8FD9\u91CC\uFF0Cstdout \u5410\u4E00\u4E2A JSON\uFF1A
1281
1304
 
1282
- - **\u663E\u5F0F\u89E3\u91CA\u5668**\uFF1A\`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` / \`bash ./api.sh\`
1283
- \uFF08\u514D \`chmod +x\`\uFF09\uFF1B\`node\`/\`python3\` \u62FF\u4E0D\u51C6\u5C31\u5148 \`command -v\` \u63A2\u6D4B\uFF0C\u9000\u56DE shell\u3002
1284
- - **\u4F20\u53C2\u9632\u6CE8\u5165**\uFF1A\u7ED3\u6784\u5316\u5165\u53C2\u7528 **base64** \u4F20\uFF08\u524D\u7AEF \`btoa(...)\`\u3001\u811A\u672C
1285
- \`Buffer.from(arg,'base64')\`\uFF09\uFF0C\u522B\u62FC\u5B57\u7B26\u4E32\uFF1B\u5199\u5E93\u7528**\u53C2\u6570\u5316**\u8BED\u53E5\u3002
1286
- - \u811A\u672C\u7528 stdout \u8FD4\u56DE JSON\uFF0C\u524D\u7AEF \`JSON.parse\`\u3002\u5148 \`Write\` \u51FA\u811A\u672C\uFF0C\u518D\u5728\u9875\u9762\u8C03\u5B83\u3002
1305
+ \`\`\`js
1306
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
1307
+ import { join, dirname } from 'node:path';
1308
+ import { fileURLToPath } from 'node:url';
1287
1309
 
1288
- ## \u9000\u56DE\uFF1A\u5355\u6587\u4EF6 HTML\uFF08\u6781\u7B80 / \u7EAF\u9759\u6001\u4E00\u6B21\u6027\u9875\uFF09
1310
+ const DIR = dirname(fileURLToPath(import.meta.url));
1311
+ const CACHE = join(DIR, 'cache/data.json');
1312
+ const VERSION = 1; // payload \u7ED3\u6784\u4E00\u6539\u5C31 bump\uFF0C\u5426\u5219\u8001\u7F13\u5B58\u5582\u7ED9\u65B0\u524D\u7AEF
1289
1313
 
1290
- \u4E0D\u9700\u8981\u72B6\u6001\u5C31\u522B\u4E0A React\uFF0C\u76F4\u63A5\u5185\u8054\uFF1B\u53D6\u6570\u4E0E\u9519\u8BEF\u5904\u7406\u540C\u4E0A\u3002
1314
+ const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
1291
1315
 
1292
- \u524D\u53F0\uFF08\u70B9\u6309\u94AE\u62C9\u6570\u636E\uFF09\uFF1A
1316
+ function readCache() {
1317
+ if (!existsSync(CACHE)) return null;
1318
+ const c = JSON.parse(readFileSync(CACHE, 'utf8'));
1319
+ return c.v === VERSION ? c : null; // \u7248\u672C\u4E0D\u7B26 = \u4F5C\u5E9F\uFF0C\u5F53\u6CA1\u6709
1320
+ }
1321
+ function writeCache(payload) {
1322
+ mkdirSync(dirname(CACHE), { recursive: true });
1323
+ const tmp = \`\${CACHE}.\${process.pid}.tmp\`; // \u539F\u5B50\u5199\uFF1A\u4E2D\u9014\u88AB\u6740\u4E0D\u4F1A\u7559\u4E0B\u534A\u4E2A JSON
1324
+ writeFileSync(tmp, JSON.stringify({ v: VERSION, fetchedAt: new Date().toISOString(), payload }));
1325
+ renameSync(tmp, CACHE);
1326
+ }
1293
1327
 
1294
- \`\`\`html
1295
- <button id="load">\u52A0\u8F7D</button>
1296
- <pre id="out"></pre>
1297
- <script>
1298
- document.getElementById('load').onclick = async () => {
1299
- try {
1300
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1301
- if (exitCode !== 0) { out.textContent = '\u547D\u4EE4\u5931\u8D25: ' + stderr; return; }
1302
- out.textContent = '\u2B50 ' + JSON.parse(stdout).stargazers_count;
1303
- } catch (e) {
1304
- out.textContent = '\u6267\u884C\u51FA\u9519: ' + e.message; // spawn / \u57FA\u5EFA\u5931\u8D25
1305
- }
1306
- };
1307
- </script>
1328
+ try {
1329
+ const hit = args.refresh ? null : readCache();
1330
+ if (hit) {
1331
+ process.stdout.write(JSON.stringify({ ...hit.payload, cached: true, fetchedAt: hit.fetchedAt }));
1332
+ } else {
1333
+ const payload = await fetchFresh(args); // \u2190 \u4F60\u7684\u53D6\u6570\u903B\u8F91\uFF08curl / \u67E5\u5E93 / \u8DD1\u547D\u4EE4\uFF09
1334
+ writeCache(payload);
1335
+ process.stdout.write(JSON.stringify({ ...payload, cached: false, fetchedAt: new Date().toISOString() }));
1336
+ }
1337
+ } catch (e) {
1338
+ const hit = readCache(); // \u5237\u65B0\u5931\u8D25\uFF1A\u56DE\u843D\u65E7\u7F13\u5B58\u5E76\u6807\u6CE8\uFF0C\u522B\u8BA9\u9875\u9762\u7A7A\u6389
1339
+ process.stdout.write(JSON.stringify(hit
1340
+ ? { ...hit.payload, cached: true, stale: true, error: e.message, fetchedAt: hit.fetchedAt }
1341
+ : { error: e.message }));
1342
+ }
1308
1343
  \`\`\`
1309
1344
 
1310
- \u540E\u53F0\uFF08\u5B9E\u65F6\u65E5\u5FD7\uFF09\uFF1A
1345
+ - \u4E00\u6761\u547D\u4EE4\u5C31\u591F\u3001\u6570\u636E\u4E5F\u4E0D\u503C\u5F97\u7559\uFF08\u7EAF\u67E5\u770B\u5F53\u524D\u72B6\u6001\uFF09\u65F6\uFF0C\`api.mjs\` \u53EF\u4EE5\u7701\u6389\uFF0C\u76F4\u63A5
1346
+ \`cockpit.bash("curl -s \u2026")\`\uFF1B\u4F46\u53EA\u8981\u6570\u636E\u8981\u7559\u4E0B\u6765\uFF0C\u5C31\u7528\u8FD9\u4E2A\u9AA8\u67B6\uFF0C\u522B\u81EA\u5DF1\u53D1\u660E\u4E00\u5957\u3002
1347
+ - \u6362\u8BED\u8A00\u540C\u7406\uFF1A\`python3 ./api.py\` / \`bash ./api.sh\`\uFF08\u663E\u5F0F\u89E3\u91CA\u5668\uFF0C\u514D \`chmod +x\`\uFF09\uFF1B\`node\` /
1348
+ \`python3\` \u62FF\u4E0D\u51C6\u5C31\u5148 \`command -v\` \u63A2\u6D4B\uFF0C\u9000\u56DE shell\u3002
1349
+ - \u7F13\u5B58 key \u8981\u542B**\u5F71\u54CD\u7ED3\u679C\u7684\u53C2\u6570**\uFF08\u65F6\u95F4\u8303\u56F4\u3001\u6293\u53D6\u6DF1\u5EA6\u2026\uFF09\uFF1A\u628A \`cache/data.json\` \u6362\u6210
1350
+ \`cache/<param>.json\` \u6216\u5728\u6587\u4EF6\u91CC\u6309 key \u5206\u6876\uFF0C\u5426\u5219\u4F1A\u62FF 7 \u5929\u7684\u7F13\u5B58\u53BB\u6E32\u67D3 30 \u5929\u7684\u89C6\u56FE\u3002
1311
1351
 
1312
- \`\`\`html
1313
- <pre id="log"></pre>
1314
- <script>
1315
- const box = document.getElementById('log');
1316
- const h = cockpit.bash("tail -f ./build.log", {
1317
- background: true,
1318
- onOutput: c => { box.textContent += c; },
1319
- onExit: code => { box.textContent += '\\n[\u9000\u51FA ' + code + ']'; },
1320
- onError: msg => { box.textContent += '\\n[\u9519\u8BEF ' + msg + ']'; },
1321
- });
1322
- // \u9700\u8981\u65F6 h.kill()
1323
- </script>
1324
- \`\`\`
1352
+ ## \u4E09\u3001\u5951\u7EA6\uFF1Awindow.cockpit
1325
1353
 
1326
- ## \u5B58\u653E\u4F4D\u7F6E
1354
+ \u9875\u9762\u52A0\u8F7D\u65F6 SDK \u5DF2\u5C31\u7EEA\uFF0C\u65E0\u9700\u5F15\u5165\u4EFB\u4F55\u5E93\uFF1A
1327
1355
 
1328
- \u4E00\u4E2A app \u7684\u6240\u6709\u6587\u4EF6\uFF08\`index.html\` + \`app.jsx\` + \`api.mjs\` \u2026\uFF09\u653E\u8FDB\u540C\u4E00\u4E2A \`<name>/\`
1329
- \u5B50\u76EE\u5F55\u3002\`<name>\` **\u4E00\u5F8B\u7531\u4F60\u6309\u9700\u6C42\u8D77\u540D**\uFF08\u77ED\u6A2A\u7EBF\u5C0F\u5199\uFF0C\u4E0E \`cockpit-name\` \u4E00\u81F4\uFF09\uFF0C\u7528\u6237\u53EA
1330
- \u63CF\u8FF0\u9700\u6C42\u3001\u4E0D\u8D1F\u8D23\u8D77\u540D\u3002\u76EE\u6807\u76EE\u5F55\u6309\u4E0B\u9762\u89C4\u5219\u786E\u5B9A\uFF1A
1356
+ - \`cockpit.cwd: string\` \u2014\u2014 \u5F53\u524D\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF0C\u76F8\u5BF9\u8DEF\u5F84\u547D\u4EE4\u9ED8\u8BA4\u5728\u6B64\u6267\u884C\u3002
1357
+ - \`cockpit.bash(command, opts?)\` \u2014\u2014 \u6267\u884C\u4E00\u6761 bash \u547D\u4EE4\uFF08\`command\` \u662F\u6574\u6761 shell \u4E32\uFF09\uFF1A
1358
+ - **\u524D\u53F0**\uFF08\u9ED8\u8BA4\uFF0C\u77ED/\u79BB\u6563\u547D\u4EE4\uFF09\u2192 \`Promise<{ stdout, stderr, exitCode }>\`\uFF0C\`await\` \u540E\u4E00\u6B21\u62FF\u5168\u90E8\u8F93\u51FA\u3002
1359
+ - **\u540E\u53F0**\uFF08\`opts.background: true\`\uFF0C\u957F/\u5B9E\u65F6\u547D\u4EE4\uFF09\u2192 \`{ kill() }\`\uFF0C\u7ECF\u56DE\u8C03\u6D41\u5F0F\u8F93\u51FA\u3002
1360
+ \u5927/\u6301\u7EED\u8F93\u51FA**\u5FC5\u987B**\u7528\u540E\u53F0\u2014\u2014\u524D\u53F0\u4F1A\u628A\u8F93\u51FA\u5168\u7F13\u5B58\u5728\u5185\u5B58\u91CC\u3002
1361
+
1362
+ \`\`\`js
1363
+ const h = cockpit.bash("npm run build", {
1364
+ background: true,
1365
+ onOutput: c => append(c),
1366
+ onStderr: c => append(c),
1367
+ onExit: code => append('\\n[\u9000\u51FA ' + code + ']'), // \u547D\u4EE4\u8DD1\u5B8C\u4E86\uFF0Ccode \u975E\u96F6 = \u5931\u8D25
1368
+ onError: msg => append('\\n[\u9519\u8BEF ' + msg + ']'), // spawn / \u57FA\u5EFA\u5931\u8D25
1369
+ });
1370
+ // \u7EC4\u4EF6\u5378\u8F7D\u6216\u7528\u6237\u4E2D\u6B62\u65F6 h.kill()
1371
+ \`\`\`
1331
1372
 
1332
- - \u7528\u6237\u5728\u9700\u6C42\u91CC\u7ED9\u4E86\u76EE\u5F55 \u2192 \u5B58\u5230 \`<\u7528\u6237\u7ED9\u7684\u76EE\u5F55>/<name>/\`
1333
- - \u7528\u6237\u6CA1\u7ED9 \u2192 \u5B58\u5230**\u5F53\u524D\u804A\u5929\u5DE5\u4F5C\u76EE\u5F55**\uFF08\u672C\u6B21\u4F1A\u8BDD\u7684 cwd\uFF09\u4E0B\u7684 \`<name>/\`
1373
+ \u5FC5\u5B88\u7684\u51E0\u6761\uFF1A
1334
1374
 
1335
- \u522B\u628A\u6587\u4EF6\u6563\u843D\u5230\u522B\u5904\u6216\u81EA\u9020 \`.cockpit-apps\` \u4E4B\u7C7B\u76EE\u5F55\u3002\u8FD9\u4E2A\u76EE\u5F55\u5C31\u662F\u4E0A\u9762\u300C\u6D41\u7A0B\u300D\u7B2C 2 \u6B65\u8981\u544A\u77E5\u7528\u6237\u7684\uFF1B**\u7528\u6237\u786E\u8BA4\u540E**\u518D\u7528 \`Write\` \u5199\u51FA\uFF0C\u7528\u6237\u70B9\u51FB
1336
- \u5373\u53EF\u9884\u89C8\u5E76\u4EA4\u4E92\u3002`;
1337
- var HTML_PROMPT_EN = `---
1338
- name: html
1339
- description: "Generate an interactive small app with the built-in cockpit bash SDK to fetch/update data"
1340
- argument-hint: "describe the small app you want"
1341
- ---
1375
+ 1. **\u6570\u636E\u53EA\u8D70 \`cockpit.bash\`**\uFF08\`curl\` \u53D6\u3001\`curl -X POST\` \u6539\u3001\u6216\u8BFB\u5199\u6587\u4EF6\uFF09\uFF0C**\u7EDD\u4E0D\u7528
1376
+ \`fetch(\u5916\u90E8URL)\`** \u2014\u2014 \u540C\u6E90\u6C99\u7BB1\u4F1A CORS \u62E6\u622A\u3002\uFF08\`fetch('./app.jsx')\` \u8FD9\u79CD\u540C\u76EE\u5F55\u76F8\u5BF9\u8BF7\u6C42\u6CA1\u95EE\u9898\u3002\uFF09
1377
+ 2. **\u4E24\u7C7B\u5931\u8D25\u90FD\u8981\u5904\u7406**\uFF1A\`try/catch\` \u6293\u5230\u7684**\u629B\u5F02\u5E38 = spawn/\u57FA\u5EFA\u5931\u8D25**\uFF08\u574F\u8DEF\u5F84\u3001\u8FDE\u63A5\u65AD\uFF09\uFF1B\u6CA1\u629B\u4F46
1378
+ **\`exitCode !== 0\` = \u547D\u4EE4\u8DD1\u4E86\u4F46\u5931\u8D25**\uFF0C\u8981\u5C55\u793A \`stderr\` \u6216 stdout \u91CC\u7684\u9519\u8BEF\u4F53\uFF0C\u522B\u53EA\u5199"\u5931\u8D25"\u3002
1379
+ \u540E\u53F0\u5BF9\u5E94 \`onError\`\uFF08\u57FA\u5EFA\uFF09+ \`onExit\` \u975E\u96F6 \`code\`\uFF08\u547D\u4EE4\uFF09\u3002
1380
+ 3. **\`command\` \u662F shell \u4E32**\uFF1A\u52A8\u6001\u503C/\u7528\u6237\u8F93\u5165\u62FC\u8FDB\u53BB\u524D\u5FC5\u987B\u6821\u9A8C\u6216\u8F6C\u4E49\uFF08shell \u5143\u5B57\u7B26\u4F1A\u6CE8\u5165\uFF09\uFF1B\u7ED3\u6784\u5316
1381
+ \u5165\u53C2\u4E00\u5F8B\u8D70 base64\uFF08\u89C1\u9AA8\u67B6\uFF09\uFF0C\u522B\u62FC\u5B57\u7B26\u4E32\uFF1B\u5199\u5E93\u7528**\u53C2\u6570\u5316**\u8BED\u53E5\u3002
1382
+ 4. **\u8D44\u6E90\u5F15\u7528**\uFF1A\u540C\u7EA7\u76F8\u5BF9\uFF08\`./app.jsx\`\u3001\`./style.css\`\u3001\u56FE\u7247\uFF09\u548C CDN \u7EDD\u5BF9 URL \u90FD\u80FD\u52A0\u8F7D\uFF1B\u6839\u76F8\u5BF9
1383
+ \uFF08\`/assets/x.css\`\uFF09\u4F1A 404 \u2014\u2014 \u670D\u52A1\u7AEF\u53EA\u6258\u7BA1 \`/html-lib/*\` \u548C \`/apps/*\`\u3002
1342
1384
 
1343
- # Build a Cockpit small app
1385
+ ## \u56DB\u3001\u672C\u5730\u6570\u636E\uFF1A\u4E09\u7C7B\u76EE\u5F55
1344
1386
 
1345
- A small app that can fetch/update data: the preview injects a global
1346
- \`window.cockpit\` SDK \u2014 essentially the Bash tool exposed to the page, so buttons can
1347
- \`curl\`, read/write files, and run scripts.
1387
+ \u6570\u636E\u8981\u843D\u76D8\u3001\u8981\u91CD\u5F00\u5373\u7528\u2014\u2014**\u522B\u6BCF\u6B21\u6253\u5F00\u90FD\u91CD\u65B0\u62C9\u4E00\u904D**\u3002\u6240\u6709\u6301\u4E45\u5316\u8D70 \`api.mjs\`\uFF0Capp \u76EE\u5F55\u4E0B\u6309
1388
+ \u300C\u80FD\u4E0D\u80FD\u91CD\u5EFA\u300D\u5206\u4E09\u7C7B\uFF0C\u522B\u5E73\u94FA\u5728\u4E00\u8D77\uFF1A
1348
1389
 
1349
- **Default to React** (the local zero-build stack below \u2014 productized, most AI-familiar,
1350
- ideal for stateful / multi-view / form apps); fall back to single-file inline HTML only
1351
- for a **trivial single view or a purely static one-off**. Both modes share the one
1352
- contract below.
1390
+ - \`cache/\` \u2014\u2014 \u80FD\u91CD\u5EFA\u7684\uFF08\u63A5\u53E3\u8FD4\u56DE\u3001\u539F\u59CB dump\u3001\u6D3E\u751F\u7D22\u5F15\uFF09\u3002\u5220\u4E86\u53EA\u662F\u6162\u3002**\u6574\u76EE\u5F55\u5199\u8FDB \`.gitignore\`**\u3002
1391
+ - \`state/\` \u2014\u2014 \u91CD\u5EFA\u4E0D\u51FA\u6765\u7684\uFF08\u6BCF\u65E5\u5FEB\u7167\u3001\u7528\u6237\u624B\u5DE5\u7EF4\u62A4\u7684\u6E05\u5355\uFF09\u3002\u6E90\u63A5\u53E3\u7ED9\u4E0D\u4E86\u7B2C\u4E8C\u6B21\uFF0C\u5220\u4E86\u6C38\u4E45\u4E22\u3002**\u5165\u5E93**\u3002
1392
+ - \`out/\` \u2014\u2014 \u7ED9\u4EBA\u770B\u7684\u4EA7\u7269\uFF08\u62A5\u544A\u3001\u5BFC\u51FA\uFF09\uFF0C\u6309\u65E5\u671F\u547D\u540D\uFF0C\u7559\u6863\u3002
1353
1393
 
1354
- ## Flow: align first, write only after confirmation (important)
1394
+ \u5206\u7C7B\u7684\u610F\u4E49\u5168\u5728 git \u7B56\u7565\u4E0A\uFF1A\u76EE\u5F55\u5206\u4E86\u4F46 \`cache/\` \u6CA1 ignore\u3001\`state/\` \u6CA1\u5165\u5E93\uFF0C\u7B49\u4E8E\u6CA1\u5206\u3002
1355
1395
 
1356
- **Don't write files up front.** Three steps:
1396
+ \u9AA8\u67B6\u91CC\u5DF2\u7ECF\u505A\u6389\u7684\uFF08\u7248\u672C\u53F7\u3001\u539F\u5B50\u5199\u3001\`refresh\` \u53C2\u6570\u3001\u53D6\u6570\u65F6\u95F4\u6233\u3001\u5931\u8D25\u56DE\u843D\u65E7\u6570\u636E\uFF09\u4E0D\u518D\u91CD\u590D\uFF0C\u5269\u4E0B\u8FD9\u4E9B
1397
+ \u8981\u4F60\u81EA\u5DF1\u5B88\uFF1A
1357
1398
 
1358
- 1. **Align on the requirement** \u2014 restate your understanding of the app (what it does,
1359
- React vs single-file, where data comes from, key features/interactions). Ask back only
1360
- when the requirement is ambiguous or has several reasonable readings; if it's already
1361
- clear, just give a short understanding summary \u2014 don't interrogate.
1362
- 2. **Tell the storage directory** \u2014 resolve \`<dir>/<name>/\` per the "Where to store" rules
1363
- below and tell the user exactly where it'll go and what it's named; the user may reject
1364
- or change the directory / name.
1365
- 3. **Write only after confirmation** \u2014 \`Write\` the files only once the user clearly agrees
1366
- (confirm / start / go / "write it" \u2014 any affirmative). Until then, only discuss \u2014 write
1367
- nothing to disk.
1399
+ 1. **\u4F1A\u589E\u957F\u7684\u76EE\u5F55\u5FC5\u987B\u6709\u4E0A\u9650\uFF0C\u5E76\u5728\u811A\u672C\u91CC\u6267\u884C**\uFF08\u7559 N \u6761 / \u8D85 X MB \u5C31\u6E05\uFF09\u3002\u53EA\u5199\u5728\u6CE8\u91CA\u91CC\u7684\u4E0A\u9650\u7B49\u4E8E
1400
+ \u6CA1\u6709\uFF0C\u6CA1\u4EBA\u770B\u7BA1\u7684 dump \u76EE\u5F55\u6DA8\u5230 GB \u7EA7\u662F\u5E38\u6001\u3002
1401
+ 2. **\u843D\u76D8\u6587\u4EF6\u91CC\u4E0D\u5199\u7EDD\u5BF9\u8DEF\u5F84**\uFF08\u5B58\u76F8\u5BF9 app \u76EE\u5F55\u7684\u8DEF\u5F84\uFF09\uFF0C\u5426\u5219\u76EE\u5F55\u4E00\u632A\u7D22\u5F15\u5168\u5E9F\uFF1B\u542B\u90AE\u7BB1\u7B49\u9690\u79C1\u7684\u843D\u76D8
1402
+ \u4E00\u5F8B ignore\u3002
1403
+ 3. **\u7559\u4E00\u4E2A\u6E05\u7A7A\u5165\u53E3**\uFF08\`api.mjs\` \u52A0\u4E2A \`clear_cache\` \u6A21\u5F0F + \u754C\u9762\u6309\u94AE\uFF09\uFF0C\u5426\u5219\u7528\u6237\u53EA\u80FD\u53BB\u6587\u4EF6\u7BA1\u7406\u5668\u91CC
1404
+ \u731C\u54EA\u4E2A\u6587\u4EF6\u80FD\u5220\u3002
1405
+ 4. \u8981\u7528\u522B\u7684 app \u7684\u51ED\u636E / \u5DE5\u5177\u811A\u672C\u65F6\u8D70 \`../<app>/\` \u76F8\u5BF9\u5F15\u7528\uFF0C\u5E76**\u5148\u63A2\u6D4B\u5B58\u5728\u518D\u7528**\uFF1B\u5931\u8D25\u7ED9\u4E00\u53E5\u7167\u7740\u505A
1406
+ \u5C31\u80FD\u4FEE\u7684\u63D0\u793A\uFF08\u300C\u53BB xxx \u91CD\u65B0\u767B\u5F55\u300D\uFF09\uFF0C\u522B\u628A\u539F\u59CB\u62A5\u9519\u7529\u7ED9\u7528\u6237\u3002
1368
1407
 
1369
- Even if the user already spelled out the requirement and directory in the \`/html\` call,
1370
- still stop once to present "understanding + directory + a 'shall I start writing?'" and
1371
- wait \u2014 but keep this round **lightweight**, don't re-interrogate.
1408
+ ## \u4E94\u3001\u4E3B\u9898
1372
1409
 
1373
- ## Contract: window.cockpit (shared by both modes, stated once)
1410
+ \u6837\u5F0F\u9ED8\u8BA4\u5957 **Cockpit \u4E3B\u9898**\uFF08\`index.html\` \u91CC\u5DF2\u7ECF link \u4E86 \`/html-lib/theme.css\`\uFF09\u3002**\u914D\u8272\u52A8\u624B\u524D\u5148\u62C9
1411
+ \u4E00\u6B21\u8FD9\u4EFD\u6587\u4EF6**\uFF0C\u5B83\u662F\u53D8\u91CF\u7684\u552F\u4E00\u771F\u6E90\uFF0C\u522B\u51ED\u8BB0\u5FC6\u731C\u53D8\u91CF\u540D\uFF1A
1374
1412
 
1375
- The SDK is ready on load \u2014 no library to import:
1413
+ \`\`\`bash
1414
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
1415
+ \`\`\`
1376
1416
 
1377
- - \`cockpit.cwd: string\` \u2014 directory of the current file; relative-path commands run here.
1378
- - \`cockpit.bash(command, opts?)\` \u2014 run one bash command (\`command\` is a raw shell string):
1379
- - **Foreground** (default, short/discrete) \u2192 \`Promise<{ stdout, stderr, exitCode }>\`; \`await\` for the full output at once.
1380
- - **Background** (\`opts.background: true\`, long/live) \u2192 \`{ kill() }\`, streaming via callbacks:
1381
- \`opts = { background:true, cwd?, onOutput?, onStderr?, onExit?, onError? }\`. Use background for large/continuous output (foreground buffers it all in memory).
1417
+ - \`:root\` \u6BB5\u662F\u4EAE\u8272\u53D6\u503C\u3001\`.dark\` \u6BB5\u662F\u6697\u8272\u3002\u539F\u59CB\u8272\u9636\uFF08\`--slate-*\` / \`--teal-*\` / \`--red-*\` \u2026\uFF09\u5B58\u7684\u662F
1418
+ HSL \u5206\u91CF\uFF0C\u6240\u4EE5\u534A\u900F\u660E\u76F4\u63A5\u5199 \`hsl(var(--green-9) / .12)\`\u3002
1419
+ - \u524D\u666F\u8272\u6210\u5BF9\u53D6\uFF08\`--card\`/\`--card-foreground\` \u2026\uFF09\uFF0C**\u53EA\u6709 \`--brand\` \u662F\u5751**\uFF1A\`--brand-foreground\` \u662F
1420
+ \u540C\u8272\u7CFB\u8272\u8C03\uFF0C\u538B\u5728 \`--brand\` \u586B\u5145\u4E0A\u4EC5 1.5:1\uFF1B**brand \u586B\u5145\u4E0A\u7684\u6587\u5B57\u6539\u7528 \`var(--background)\`**\u3002
1421
+ - \`cockpit-theme\` meta \u8BA9\u9884\u89C8\u5E26\u4E00\u4E2A\u6D6E\u52A8\u4EAE/\u6697\u5207\u6362\u6309\u94AE\uFF08\u9ED8\u8BA4\u53F3\u4E0A\u89D2\uFF0C\u7528\u6237\u53EF\u62D6\u52A8\u5438\u9644\u5230\u4EFB\u610F\u4E00\u89D2\uFF0C\u6240\u4EE5
1422
+ \u4E0D\u5FC5\u4E3A\u5B83\u5728\u53F3\u4E0A\u89D2\u7559\u7A7A\u4F4D\uFF09\uFF1B**\u9ED8\u8BA4\u8DDF\u968F Cockpit \u5BBF\u4E3B\u4E3B\u9898**\uFF08Cockpit \u5916\u6253\u5F00\u65F6\u6309 meta \u7684
1423
+ auto/light/dark\uFF09\uFF0C\u624B\u52A8\u5207\u6362\u540E**\u6309 app \u8BB0\u4F4F\u9009\u62E9\uFF08\u8DE8\u5237\u65B0\uFF0C\u4F18\u5148\u4E8E\u8DDF\u968F\uFF09**\uFF0C\u4E5F\u53EF\u8C03
1424
+ \`cockpit.toggleTheme()\`\u3002
1425
+ - **\u522B\u81EA\u62CD\u8C03\u8272\u677F\u3001\u522B\u4E0A Tailwind**\uFF1Bapp \u7279\u6709\u7EC6\u8282\u518D\u8865\u4E00\u5C0F\u6BB5 \`<style>\`\u3002`;
1426
+ var HTML_PROMPT_EN = `---
1427
+ name: html
1428
+ description: "Generate an interactive local React app with the built-in cockpit bash SDK to fetch/update data"
1429
+ argument-hint: "describe the app you want"
1430
+ ---
1382
1431
 
1383
- Rules every such page must follow:
1432
+ # Build a Cockpit app (a local React page)
1384
1433
 
1385
- 1. **Data only through \`cockpit.bash\`** (\`curl\` to read, \`curl -X POST\` to write, or
1386
- read/write files) \u2014 **never \`fetch(externalURL)\`**: the same-origin sandbox blocks it via CORS.
1387
- 2. **Handle both failure modes** (foreground): a **throw** caught by \`try/catch\` = spawn/infra
1388
- failure (bad path, dropped connection); no throw but **\`exitCode !== 0\` = the command ran
1389
- and failed** \u2014 show \`stderr\` or the error body in stdout, don't just say "failed". Background:
1390
- \`onError\` (infra) + a non-zero \`code\` in \`onExit\` (command).
1391
- 3. **\`command\` is a shell string**: validate or escape any dynamic/user input before
1392
- interpolating it (shell metacharacters inject); prefer a fixed command.
1393
- 4. **Resource refs**: relative siblings (\`./app.jsx\`, \`./style.css\`, images) and absolute
1394
- CDN URLs load; root-relative (\`/assets/x.css\`) 404s.
1434
+ A **local application**, not a one-off page: the preview injects a global \`window.cockpit\`
1435
+ SDK \u2014 essentially the Bash tool exposed to the page, so buttons can \`curl\`, read/write files
1436
+ and run scripts; data lands on disk inside the app directory and is there on reopen.
1395
1437
 
1396
- Meta head (always include \u2014 the HTML panel renders cards from it, the console opens it via \`/name\`):
1438
+ Zero build: React / Babel load from the same-origin \`/html-lib\` \u2014 no bundler, no deps, works offline.
1397
1439
 
1398
- \`\`\`html
1399
- <title>Page title</title>
1400
- <meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ only -->
1401
- <meta name="description" content="one line about the page">
1402
- <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji or icon url, optional -->
1403
- <meta name="cockpit-theme" content="auto"> <!-- enable the draggable light/dark toggle; auto = first load follows the OS, or light/dark -->
1404
- \`\`\`
1440
+ ## 1. Flow: align first, write only after confirmation (important)
1405
1441
 
1406
- ## Default: React (local zero-build stack)
1442
+ **Don't write files up front.** Three steps:
1407
1443
 
1408
- Use this fixed shell, with the logic in a sibling \`./app.jsx\`:
1444
+ 1. **Align on the requirement** \u2014 restate your understanding of the app (what it does, where
1445
+ data comes from, key features/interactions). Ask back only when the requirement is ambiguous
1446
+ or has several reasonable readings; if it's already clear, just give a short summary \u2014 don't
1447
+ interrogate.
1448
+ 2. **Tell the storage directory** \u2014 resolve \`<dir>/<name>/\` and tell the user exactly where it
1449
+ goes and what it's called; they may reject or change it:
1450
+ - **You always pick \`<name>\`** from the request (lowercase kebab-case, matching
1451
+ \`cockpit-name\`) \u2014 the user only describes the app, they don't name it.
1452
+ - The user gave a directory \u2192 \`<the given directory>/<name>/\`; none given \u2192 \`<name>/\` under
1453
+ the **current chat working directory** (this session's cwd).
1454
+ - Every file of the app lives in that one directory, with data split into \`cache/\`
1455
+ \`state/\` \`out/\` per section 4. Don't scatter files elsewhere or invent a \`.cockpit-apps\`.
1456
+ 3. **Write only after confirmation** \u2014 \`Write\` the files once the user clearly agrees (confirm /
1457
+ start / go / "write it" \u2014 any affirmative). Until then, only discuss.
1458
+
1459
+ Even if the user already spelled out requirement and directory in the \`/html\` call, still stop
1460
+ once to present "understanding + directory + a 'shall I start writing?'" and wait \u2014 but keep that
1461
+ round **lightweight**, don't re-interrogate.
1462
+
1463
+ ## 2. Skeleton: three files, copy them
1464
+
1465
+ \`index.html\` \u2014 use this fixed shell, don't improvise:
1409
1466
 
1410
1467
  \`\`\`html
1411
- <div id="root"></div>
1412
- <script src="/html-lib/react.production.min.js"></script>
1413
- <script src="/html-lib/react-dom.production.min.js"></script>
1414
- <script src="/html-lib/babel.min.js"></script>
1415
- <script>
1416
- (async () => {
1417
- const src = await (await fetch('./app.jsx')).text();
1418
- // MUST use classic runtime: the default automatic runtime injects an import of
1419
- // react/jsx-runtime, which \u2014 appended as a plain script \u2014 throws
1420
- // "Cannot use import statement outside a module".
1421
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1422
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1423
- })();
1424
- </script>
1468
+ <!doctype html>
1469
+ <html>
1470
+ <head>
1471
+ <meta charset="utf-8">
1472
+ <title>Page title</title>
1473
+ <meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ -->
1474
+ <meta name="description" content="one line about the page">
1475
+ <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji or icon url, optional -->
1476
+ <meta name="cockpit-theme" content="auto"> <!-- light/dark toggle, see section 5 -->
1477
+ <link rel="stylesheet" href="/html-lib/theme.css">
1478
+ </head>
1479
+ <body>
1480
+ <div id="root"></div>
1481
+ <script src="/html-lib/react.production.min.js"></script>
1482
+ <script src="/html-lib/react-dom.production.min.js"></script>
1483
+ <script src="/html-lib/babel.min.js"></script>
1484
+ <script>
1485
+ (async () => {
1486
+ const src = await (await fetch('./app.jsx')).text();
1487
+ // MUST use classic runtime: the default automatic runtime injects an import of
1488
+ // react/jsx-runtime, which \u2014 appended as a plain script \u2014 throws
1489
+ // "Cannot use import statement outside a module".
1490
+ const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1491
+ const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1492
+ })();
1493
+ </script>
1494
+ </body>
1495
+ </html>
1425
1496
  \`\`\`
1426
1497
 
1427
- - \`/html-lib\` is hosted locally by Cockpit (same-origin, offline, no CDN).
1428
- - \`app.jsx\` uses the global \`React\` (\`const { useState } = React;\`) and ends with
1429
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`.
1430
- - Style with the **Cockpit theme** by default: \`<link rel="stylesheet" href="/html-lib/theme.css">\`,
1431
- then use the semantic vars (\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` /
1432
- \`--muted-foreground\` / \`--border\` / \`--destructive\`; translucency via \`hsl(var(--green-9) / .12)\`).
1433
- The \`cockpit-theme\` meta above gives the preview a floating light/dark toggle (top-right by
1434
- default, draggable to any corner \u2014 so don't reserve top-right space for it); it **follows the
1435
- Cockpit host theme by default** (outside Cockpit, the meta's auto/light/dark decides), and a manual
1436
- toggle is **remembered per app across reloads and overrides the host**, or call \`cockpit.toggleTheme()\`
1437
- from your own UI. Don't invent a palette or add Tailwind; add a small \`<style>\` for app-specific bits.
1498
+ All four meta lines matter: the HTML panel renders cards from them, and the console opens the
1499
+ app via \`cockpit-name\` (\`/name\`).
1438
1500
 
1439
- Fetch data from React the same way, via \`cockpit.bash\`:
1501
+ \`app.jsx\` \u2014 global \`React\`, render at the end. **Opening reads the cache; only Refresh goes out**:
1440
1502
 
1441
1503
  \`\`\`jsx
1504
+ const { useState, useEffect, useCallback } = React;
1505
+
1442
1506
  function App() {
1443
- const [stars, setStars] = React.useState(null);
1444
- const load = async () => {
1445
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1446
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : 'error');
1447
- };
1448
- return <button onClick={load}>\u2B50 {stars ?? 'load'}</button>;
1507
+ const [data, setData] = useState(null);
1508
+ const [busy, setBusy] = useState(false);
1509
+ const [err, setErr] = useState(null);
1510
+
1511
+ const load = useCallback(async (refresh) => {
1512
+ setBusy(true); setErr(null);
1513
+ try {
1514
+ const arg = btoa(JSON.stringify({ refresh: !!refresh })); // structured args as base64
1515
+ const { stdout, stderr, exitCode } = await cockpit.bash(\`node ./api.mjs \${arg}\`);
1516
+ if (exitCode !== 0) throw new Error(stderr || 'script failed'); // ran, but failed
1517
+ const d = JSON.parse(stdout);
1518
+ if (d.error && !d.stale) throw new Error(d.error);
1519
+ setData(d);
1520
+ if (d.stale) setErr(d.error); // old data is still shown
1521
+ } catch (e) {
1522
+ setErr(e.message); // a throw = spawn/infra failure. Keep existing data on screen
1523
+ } finally { setBusy(false); }
1524
+ }, []);
1525
+
1526
+ useEffect(() => { load(false); }, [load]); // on open: cache only, zero requests
1527
+
1528
+ if (!data) return <div>{err ?? 'Loading\u2026'}</div>;
1529
+ return (
1530
+ <div>
1531
+ <span>{data.cached ? 'cached' : 'updated'} \xB7 {new Date(data.fetchedAt).toLocaleString()}</span>
1532
+ <button disabled={busy} onClick={() => load(true)}>{busy ? 'Refreshing\u2026' : 'Refresh'}</button>
1533
+ {err && <em>Refresh failed, showing older data: {err}</em>}
1534
+ {/* render data */}
1535
+ </div>
1536
+ );
1449
1537
  }
1450
- \`\`\`
1451
1538
 
1452
- ## Backend: write complex logic as a script (CGI-style, both modes)
1539
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
1540
+ \`\`\`
1453
1541
 
1454
- Inline short commands in \`cockpit.bash("...")\`. For complex logic (multi-step, parsing,
1455
- loops, a library, DB writes), write the backend as a **script file** next to the page and
1456
- call it \u2014 the HTML/JSX is the frontend, the script is the backend handler.
1542
+ \`api.mjs\` \u2014 the backend handler: fetching, parsing and persistence live here; one JSON on stdout:
1457
1543
 
1458
- - **Explicit interpreter**: \`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` /
1459
- \`bash ./api.sh\` (avoids \`chmod +x\`); probe \`node\`/\`python3\` with \`command -v\` if unsure,
1460
- and fall back to shell.
1461
- - **Pass args safely**: send structured input as **base64** (\`btoa(...)\` on the page,
1462
- \`Buffer.from(arg,'base64')\` in the script) rather than string-building; use **parameterized**
1463
- statements for DB writes.
1464
- - The script returns JSON on stdout; the frontend does \`JSON.parse\`. \`Write\` the script first, then call it.
1544
+ \`\`\`js
1545
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
1546
+ import { join, dirname } from 'node:path';
1547
+ import { fileURLToPath } from 'node:url';
1465
1548
 
1466
- ## Fallback: single-file HTML (trivial / static one-off)
1549
+ const DIR = dirname(fileURLToPath(import.meta.url));
1550
+ const CACHE = join(DIR, 'cache/data.json');
1551
+ const VERSION = 1; // bump whenever the payload shape changes, or old cache feeds a new UI
1467
1552
 
1468
- No state? Skip React and inline it. Same data/error handling as above.
1553
+ const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
1469
1554
 
1470
- Foreground (button fetches data):
1555
+ function readCache() {
1556
+ if (!existsSync(CACHE)) return null;
1557
+ const c = JSON.parse(readFileSync(CACHE, 'utf8'));
1558
+ return c.v === VERSION ? c : null; // version mismatch = void it, act as if empty
1559
+ }
1560
+ function writeCache(payload) {
1561
+ mkdirSync(dirname(CACHE), { recursive: true });
1562
+ const tmp = \`\${CACHE}.\${process.pid}.tmp\`; // atomic: a kill mid-write leaves no half JSON
1563
+ writeFileSync(tmp, JSON.stringify({ v: VERSION, fetchedAt: new Date().toISOString(), payload }));
1564
+ renameSync(tmp, CACHE);
1565
+ }
1471
1566
 
1472
- \`\`\`html
1473
- <button id="load">Load</button>
1474
- <pre id="out"></pre>
1475
- <script>
1476
- document.getElementById('load').onclick = async () => {
1477
- try {
1478
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1479
- if (exitCode !== 0) { out.textContent = 'command failed: ' + stderr; return; }
1480
- out.textContent = '\u2B50 ' + JSON.parse(stdout).stargazers_count;
1481
- } catch (e) {
1482
- out.textContent = 'exec error: ' + e.message; // spawn / infra failure
1483
- }
1484
- };
1485
- </script>
1567
+ try {
1568
+ const hit = args.refresh ? null : readCache();
1569
+ if (hit) {
1570
+ process.stdout.write(JSON.stringify({ ...hit.payload, cached: true, fetchedAt: hit.fetchedAt }));
1571
+ } else {
1572
+ const payload = await fetchFresh(args); // \u2190 your fetching logic (curl / DB / commands)
1573
+ writeCache(payload);
1574
+ process.stdout.write(JSON.stringify({ ...payload, cached: false, fetchedAt: new Date().toISOString() }));
1575
+ }
1576
+ } catch (e) {
1577
+ const hit = readCache(); // failed refresh: fall back to the old cache, labelled
1578
+ process.stdout.write(JSON.stringify(hit
1579
+ ? { ...hit.payload, cached: true, stale: true, error: e.message, fetchedAt: hit.fetchedAt }
1580
+ : { error: e.message }));
1581
+ }
1486
1582
  \`\`\`
1487
1583
 
1488
- Background (live log):
1584
+ - When one command is enough and the data isn't worth keeping (just viewing current state), skip
1585
+ \`api.mjs\` and call \`cockpit.bash("curl -s \u2026")\` directly. The moment data should survive a
1586
+ reopen, use this skeleton rather than inventing your own.
1587
+ - Same for other languages: \`python3 ./api.py\` / \`bash ./api.sh\` (explicit interpreter avoids
1588
+ \`chmod +x\`); probe \`node\` / \`python3\` with \`command -v\` if unsure and fall back to shell.
1589
+ - The cache key must include **every parameter that changes the result** (time range, fetch depth
1590
+ \u2026): use \`cache/<param>.json\` or bucket by key inside the file \u2014 otherwise a 7-day cache renders
1591
+ in the 30-day view.
1489
1592
 
1490
- \`\`\`html
1491
- <pre id="log"></pre>
1492
- <script>
1493
- const box = document.getElementById('log');
1494
- const h = cockpit.bash("tail -f ./build.log", {
1495
- background: true,
1496
- onOutput: c => { box.textContent += c; },
1497
- onExit: code => { box.textContent += '\\n[exit ' + code + ']'; },
1498
- onError: msg => { box.textContent += '\\n[error ' + msg + ']'; },
1499
- });
1500
- // h.kill() when done
1501
- </script>
1502
- \`\`\`
1593
+ ## 3. Contract: window.cockpit
1503
1594
 
1504
- ## Where to store it
1595
+ The SDK is ready on load \u2014 no library to import:
1505
1596
 
1506
- Put all of an app's files (\`index.html\` + \`app.jsx\` + \`api.mjs\` \u2026) in a single
1507
- \`<name>/\` subdirectory. **You always pick \`<name>\`** from the request (lowercase
1508
- kebab-case, matching \`cockpit-name\`) \u2014 the user only describes the app, they don't
1509
- name it. Resolve the target directory as follows:
1597
+ - \`cockpit.cwd: string\` \u2014 directory of the current file; relative-path commands run here.
1598
+ - \`cockpit.bash(command, opts?)\` \u2014 run one bash command (\`command\` is a raw shell string):
1599
+ - **Foreground** (default, short/discrete) \u2192 \`Promise<{ stdout, stderr, exitCode }>\`; \`await\` for the full output at once.
1600
+ - **Background** (\`opts.background: true\`, long/live) \u2192 \`{ kill() }\`, streaming via callbacks.
1601
+ Large/continuous output **must** use background \u2014 foreground buffers it all in memory.
1602
+
1603
+ \`\`\`js
1604
+ const h = cockpit.bash("npm run build", {
1605
+ background: true,
1606
+ onOutput: c => append(c),
1607
+ onStderr: c => append(c),
1608
+ onExit: code => append('\\n[exit ' + code + ']'), // command finished; non-zero = failed
1609
+ onError: msg => append('\\n[error ' + msg + ']'), // spawn / infra failure
1610
+ });
1611
+ // h.kill() on unmount or when the user aborts
1612
+ \`\`\`
1510
1613
 
1511
- - The user gave a directory in the request \u2192 store under \`<the given directory>/<name>/\`.
1512
- - The user gave none \u2192 store under the **current chat working directory** (this session's cwd), i.e. \`<cwd>/<name>/\`.
1614
+ Rules every page must follow:
1615
+
1616
+ 1. **Data only through \`cockpit.bash\`** (\`curl\` to read, \`curl -X POST\` to write, or read/write
1617
+ files) \u2014 **never \`fetch(externalURL)\`**: the same-origin sandbox blocks it via CORS.
1618
+ (\`fetch('./app.jsx')\` and other same-directory relative requests are fine.)
1619
+ 2. **Handle both failure modes**: a **throw** caught by \`try/catch\` = spawn/infra failure (bad
1620
+ path, dropped connection); no throw but **\`exitCode !== 0\` = the command ran and failed** \u2014 show
1621
+ \`stderr\` or the error body in stdout, don't just say "failed". Background: \`onError\` (infra) +
1622
+ a non-zero \`code\` in \`onExit\` (command).
1623
+ 3. **\`command\` is a shell string**: validate or escape any dynamic/user input before interpolating
1624
+ it (shell metacharacters inject); pass structured input as base64 (see the skeleton) instead of
1625
+ string-building; use **parameterized** statements for DB writes.
1626
+ 4. **Resource refs**: relative siblings (\`./app.jsx\`, \`./style.css\`, images) and absolute CDN URLs
1627
+ load; root-relative (\`/assets/x.css\`) 404s \u2014 the server only hosts \`/html-lib/*\` and \`/apps/*\`.
1628
+
1629
+ ## 4. Local data: three directories
1630
+
1631
+ Data must land on disk and be there on reopen \u2014 **don't refetch everything on every open**. All
1632
+ persistence goes through \`api.mjs\`; inside the app directory, split it by "can this be rebuilt?"
1633
+ into three directories instead of one flat pile:
1634
+
1635
+ - \`cache/\` \u2014 rebuildable (API responses, raw dumps, derived indexes). Deleting it only costs time. **Gitignore the whole directory.**
1636
+ - \`state/\` \u2014 NOT rebuildable (daily snapshots, hand-maintained lists). The source can't hand it to you twice; deleted means gone. **Commit it.**
1637
+ - \`out/\` \u2014 artifacts for humans (reports, exports), date-named, kept.
1638
+
1639
+ The split only pays off through the git policy: directories separated but \`cache/\` not ignored and
1640
+ \`state/\` not committed means nothing was separated.
1641
+
1642
+ What the skeleton already handles (versioning, atomic writes, the \`refresh\` flag, the fetch
1643
+ timestamp, stale fallback) isn't repeated here. What's left is on you:
1644
+
1645
+ 1. **Any growing directory needs a cap enforced in the script** (keep N entries / prune past X MB).
1646
+ A cap that lives only in a comment is not a cap \u2014 unattended dump directories reach GB scale.
1647
+ 2. **Never write absolute paths into a file on disk** (store paths relative to the app directory),
1648
+ or moving the directory voids the index; anything holding private data (emails \u2026) is gitignored.
1649
+ 3. **Provide a clear/reset entry point** (a \`clear_cache\` mode in \`api.mjs\` + a button), or the
1650
+ user's only option is guessing which file is safe to delete in a file manager.
1651
+ 4. To use another app's credentials / helper scripts, reference them as \`../<app>/\` and **probe
1652
+ that they exist first**; on failure give one actionable line ("log in again at xxx") rather than
1653
+ dumping the raw error.
1654
+
1655
+ ## 5. Theme
1656
+
1657
+ Style with the **Cockpit theme** by default (\`index.html\` already links \`/html-lib/theme.css\`).
1658
+ **Fetch that file once before you pick any colour** \u2014 it is the single source of truth for the
1659
+ variables, so don't guess names from memory:
1660
+
1661
+ \`\`\`bash
1662
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
1663
+ \`\`\`
1513
1664
 
1514
- Don't scatter files elsewhere or invent a directory like \`.cockpit-apps\`. This directory is what step 2 of the flow above tells the user; **after the user confirms**,
1515
- \`Write\` the files; the user clicks to preview and interact.`;
1665
+ - \`:root\` holds the light values, \`.dark\` the dark ones. The raw scales (\`--slate-*\` / \`--teal-*\` /
1666
+ \`--red-*\` \u2026) store HSL components, so translucency is just \`hsl(var(--green-9) / .12)\`.
1667
+ - Take foregrounds in pairs (\`--card\`/\`--card-foreground\` \u2026), **except \`--brand\`**:
1668
+ \`--brand-foreground\` is a tint of the same hue and hits only 1.5:1 on a \`--brand\` fill \u2014 **use
1669
+ \`var(--background)\` for text on a brand fill**.
1670
+ - The \`cockpit-theme\` meta gives the preview a floating light/dark toggle (top-right by default,
1671
+ draggable to any corner \u2014 so don't reserve top-right space for it); it **follows the Cockpit host
1672
+ theme by default** (outside Cockpit, the meta's auto/light/dark decides), and a manual toggle is
1673
+ **remembered per app across reloads and overrides the host**, or call \`cockpit.toggleTheme()\`.
1674
+ - **Don't invent a palette or add Tailwind**; add a small \`<style>\` for app-specific bits.`;
1516
1675
 
1517
1676
  // packages/feature/agent/src/server/lib/newBranchPrompt.ts
1518
1677
  var NEW_BRANCH_PROMPT_ZH = `---
@@ -2374,7 +2533,7 @@ function follow(ctx) {
2374
2533
  else ctx.signal.addEventListener("abort", () => ac.abort(), { once: true });
2375
2534
  return ac;
2376
2535
  }
2377
- async function runSdkLoop(ctx, buildOptions2) {
2536
+ async function runSdkLoop(ctx, buildOptions) {
2378
2537
  const hasImages = !!(ctx.images && ctx.images.length > 0);
2379
2538
  const content = [];
2380
2539
  if (ctx.images) {
@@ -2416,7 +2575,7 @@ async function runSdkLoop(ctx, buildOptions2) {
2416
2575
  })();
2417
2576
  const attemptAbort = follow(ctx);
2418
2577
  const resume = isRetry ? ctx.currentKey() : ctx.sessionId;
2419
- const options = isRetry ? { ...buildOptions2(attemptAbort, resume, true), resume } : buildOptions2(attemptAbort, ctx.sessionId, false);
2578
+ const options = isRetry ? { ...buildOptions(attemptAbort, resume, true), resume } : buildOptions(attemptAbort, ctx.sessionId, false);
2420
2579
  const response = query({ prompt: input, options });
2421
2580
  let receivedResult = false;
2422
2581
  for await (const message of response) {
@@ -2446,6 +2605,39 @@ async function runSdkLoop(ctx, buildOptions2) {
2446
2605
  // packages/feature/agent/src/server/engines/claude.ts
2447
2606
  var PLAN_EDIT_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
2448
2607
  var isPlanFilePath = (p) => /(^|\/)\.claude\/plans\//.test(p);
2608
+ var CLAUDE_SDK_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
2609
+ var CLAUDE_ULTRACODE_MODELS = /* @__PURE__ */ new Set(["claude-fable-5", "claude-opus-5", "claude-opus-4-8"]);
2610
+ var CLAUDE_XHIGH_MODELS = /* @__PURE__ */ new Set(["claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5"]);
2611
+ function resolveClaudeModel(ctx) {
2612
+ const model = typeof ctx.params.model === "string" && ctx.params.model.trim() ? ctx.params.model.trim() : void 0;
2613
+ if (!model) return void 0;
2614
+ return ctx.params.claudeContextWindow === "1m" ? `${model}[1m]` : model;
2615
+ }
2616
+ function resolveClaudeEffort(ctx) {
2617
+ const model = typeof ctx.params.model === "string" ? ctx.params.model.trim() : "";
2618
+ if (model === "claude-haiku-4-5") return void 0;
2619
+ const effort = typeof ctx.params.claudeEffort === "string" ? ctx.params.claudeEffort : void 0;
2620
+ if (!effort || effort === "ultrathink") return void 0;
2621
+ if (effort === "ultracode") return "xhigh";
2622
+ if (effort === "xhigh" && !CLAUDE_XHIGH_MODELS.has(model)) return "max";
2623
+ if (effort === "max" && model === "claude-sonnet-4-6") return "high";
2624
+ return CLAUDE_SDK_EFFORTS.has(effort) ? effort : void 0;
2625
+ }
2626
+ function resolveClaudeSettings(ctx) {
2627
+ const settings = {};
2628
+ const model = typeof ctx.params.model === "string" ? ctx.params.model.trim() : "";
2629
+ if (ctx.params.claudeFastMode === true) settings.fastMode = true;
2630
+ if (ctx.params.claudeThinking !== void 0) settings.alwaysThinkingEnabled = ctx.params.claudeThinking;
2631
+ if (ctx.params.claudeEffort === "ultracode" && CLAUDE_ULTRACODE_MODELS.has(model)) settings.ultracode = true;
2632
+ return Object.keys(settings).length > 0 ? settings : void 0;
2633
+ }
2634
+ function withClaudePromptEffort(ctx) {
2635
+ if (ctx.params.claudeEffort !== "ultrathink") return ctx;
2636
+ const prompt = ctx.prompt ?? "";
2637
+ if (/\bultrathink\b/i.test(prompt)) return ctx;
2638
+ return { ...ctx, prompt: `Ultrathink:
2639
+ ${prompt}` };
2640
+ }
2449
2641
  function planPermission(toolName, input, opts) {
2450
2642
  if (toolName === "ExitPlanMode") {
2451
2643
  return {
@@ -2472,6 +2664,9 @@ function planPermission(toolName, input, opts) {
2472
2664
  function buildClaudeOptions(ctx, independent) {
2473
2665
  const { permissionMode } = ctx.params;
2474
2666
  const isPlan = permissionMode === "plan";
2667
+ const model = resolveClaudeModel(ctx);
2668
+ const effort = resolveClaudeEffort(ctx);
2669
+ const settings = resolveClaudeSettings(ctx);
2475
2670
  return (abort, resume, isRetry) => ({
2476
2671
  // Independent task: resume is what makes the CLI load the transcript, so the first
2477
2672
  // attempt drops it and names the session explicitly instead — same file on disk, empty
@@ -2479,6 +2674,9 @@ function buildClaudeOptions(ctx, independent) {
2479
2674
  // turn (the history is stashed away), which is exactly what the retry needs.
2480
2675
  ...independent && !isRetry ? { sessionId: resume } : resume && { resume },
2481
2676
  ...ctx.cwd && { cwd: ctx.cwd },
2677
+ ...model ? { model } : {},
2678
+ ...effort ? { effort } : {},
2679
+ ...settings ? { settings } : {},
2482
2680
  settingSources: ["user", "project", "local"],
2483
2681
  // Permission mode: 'plan' (read-only) when requested, else skip all permission checks.
2484
2682
  permissionMode: isPlan ? "plan" : "bypassPermissions",
@@ -2500,15 +2698,16 @@ var claudeSpec = {
2500
2698
  name: "claude",
2501
2699
  runner: {
2502
2700
  async run(ctx) {
2503
- const independent = ctx.params.noHistory === true && !!ctx.sessionId;
2701
+ const runCtx = withClaudePromptEffort(ctx);
2702
+ const independent = runCtx.params.noHistory === true && !!runCtx.sessionId;
2504
2703
  if (!independent) {
2505
- await runSdkLoop(ctx, buildClaudeOptions(ctx, false));
2704
+ await runSdkLoop(runCtx, buildClaudeOptions(runCtx, false));
2506
2705
  return;
2507
2706
  }
2508
- const sessionPath = getClaudeSessionPath(ctx.cwd, ctx.sessionId);
2707
+ const sessionPath = getClaudeSessionPath(runCtx.cwd, runCtx.sessionId);
2509
2708
  const stashed = stashTranscript(sessionPath);
2510
2709
  try {
2511
- await runSdkLoop(ctx, buildClaudeOptions(ctx, true));
2710
+ await runSdkLoop(runCtx, buildClaudeOptions(runCtx, true));
2512
2711
  } finally {
2513
2712
  if (stashed) mergeStashedTranscript(sessionPath);
2514
2713
  }
@@ -16841,6 +17040,16 @@ ${summary}`;
16841
17040
  }
16842
17041
 
16843
17042
  // packages/feature/agent/src/server/engines/builtinAgent/stream.ts
17043
+ function emitUsageUpdate(outputTokens, safeEnqueue) {
17044
+ safeEnqueue(
17045
+ `data: ${JSON.stringify({
17046
+ type: "usage_update",
17047
+ output_tokens: Math.max(0, Math.round(outputTokens))
17048
+ })}
17049
+
17050
+ `
17051
+ );
17052
+ }
16844
17053
  function formatToolError(error48) {
16845
17054
  if (error48 instanceof Error) return error48.message || String(error48);
16846
17055
  if (typeof error48 === "string") return error48;
@@ -16852,12 +17061,15 @@ function formatToolError(error48) {
16852
17061
  }
16853
17062
  async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16854
17063
  let text = "";
17064
+ let progressOutputTokens = 0;
16855
17065
  const toolCalls = [];
16856
17066
  const pendingToolCalls = /* @__PURE__ */ new Map();
16857
17067
  for await (const part of fullStream) {
16858
17068
  switch (part.type) {
16859
17069
  case "text-delta": {
16860
17070
  text += part.text;
17071
+ progressOutputTokens += estimateOutputUnits(part.text);
17072
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16861
17073
  safeEnqueue(
16862
17074
  `data: ${JSON.stringify({
16863
17075
  type: "stream_event",
@@ -16871,12 +17083,16 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16871
17083
  );
16872
17084
  break;
16873
17085
  }
17086
+ case "finish-step":
17087
+ break;
16874
17088
  case "tool-call": {
16875
17089
  if (text) {
16876
17090
  opts?.onTextFlush?.(text);
16877
17091
  text = "";
16878
17092
  }
16879
17093
  const input = part.input || {};
17094
+ progressOutputTokens += estimateOutputUnits(`${part.toolName} ${JSON.stringify(input)}`);
17095
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16880
17096
  opts?.onToolCall?.(part.toolCallId, part.toolName, input);
16881
17097
  safeEnqueue(
16882
17098
  `data: ${JSON.stringify({
@@ -16897,6 +17113,8 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16897
17113
  }
16898
17114
  case "tool-result": {
16899
17115
  const content = String(part.output);
17116
+ progressOutputTokens += estimateOutputUnits(content);
17117
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16900
17118
  opts?.onToolResult?.(part.toolCallId, content, false);
16901
17119
  safeEnqueue(
16902
17120
  `data: ${JSON.stringify({
@@ -16918,6 +17136,8 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16918
17136
  // unusable session. Every tool call must close with a result line, success or not.
16919
17137
  case "tool-error": {
16920
17138
  const content = formatToolError(part.error);
17139
+ progressOutputTokens += estimateOutputUnits(content);
17140
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16921
17141
  opts?.onToolResult?.(part.toolCallId, content, true);
16922
17142
  safeEnqueue(
16923
17143
  `data: ${JSON.stringify({
@@ -16941,8 +17161,6 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16941
17161
  // the normal failure path (orchestrator → run `error` event → UI).
16942
17162
  case "error":
16943
17163
  throw new Error(formatProviderError(part.error), { cause: part.error });
16944
- case "finish":
16945
- break;
16946
17164
  default:
16947
17165
  break;
16948
17166
  }
@@ -17129,12 +17347,12 @@ function resolveEndpoints(p, saved, language) {
17129
17347
  const id = saved.region || p.regions.defaultFor(language);
17130
17348
  return p.regions.byId[id] ?? p.endpoints;
17131
17349
  }
17132
- function resolveBuiltinModel(requested, saved, fallback) {
17350
+ function resolveModel(requested, saved, fallback) {
17133
17351
  if (typeof requested === "string" && requested.trim()) return requested.trim();
17134
17352
  if (saved && saved.trim()) return saved.trim();
17135
17353
  return fallback;
17136
17354
  }
17137
- function buildBuiltinConfig(p, apiKey, endpoints) {
17355
+ function buildConfig(p, apiKey, endpoints) {
17138
17356
  return {
17139
17357
  sessionsRoot: getBuiltinSessionsRoot(p.name),
17140
17358
  defaultModel: p.defaultModel,
@@ -17142,37 +17360,6 @@ function buildBuiltinConfig(p, apiKey, endpoints) {
17142
17360
  ...p.builtinTemperature !== void 0 && { temperature: p.builtinTemperature }
17143
17361
  };
17144
17362
  }
17145
- function buildEnv(opts) {
17146
- const { provider: p, apiKey, model, settings, endpoints } = opts;
17147
- const wire = p.sdkWireModel ? p.sdkWireModel(model) : model;
17148
- return sanitizedSpawnEnv({
17149
- ANTHROPIC_AUTH_TOKEN: void 0,
17150
- ANTHROPIC_BASE_URL: endpoints.anthropicBaseUrl,
17151
- ANTHROPIC_API_KEY: apiKey,
17152
- // both providers accept this as x-api-key
17153
- ANTHROPIC_MODEL: wire,
17154
- ANTHROPIC_SMALL_FAST_MODEL: p.smallFastModel ?? wire,
17155
- CLAUDE_CONFIG_DIR: p.configDir,
17156
- ...p.disablePromptCaching && { DISABLE_PROMPT_CACHING: "1" },
17157
- CLAUDE_CODE_USE_BEDROCK: "0",
17158
- CLAUDE_CODE_USE_VERTEX: "0",
17159
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
17160
- ...p.sdkEnv?.(model, settings)
17161
- });
17162
- }
17163
- function buildOptions(ctx, env) {
17164
- return (abort, resume) => ({
17165
- ...resume && { resume },
17166
- ...ctx.cwd && { cwd: ctx.cwd },
17167
- settingSources: ["user", "project", "local"],
17168
- permissionMode: "bypassPermissions",
17169
- allowDangerouslySkipPermissions: true,
17170
- includePartialMessages: true,
17171
- abortController: abort,
17172
- env
17173
- });
17174
- }
17175
- var isBuiltinMode = (params) => params.mode === "builtin";
17176
17363
  function makeAnthropicCompatSpec(p) {
17177
17364
  return {
17178
17365
  name: p.name,
@@ -17188,14 +17375,13 @@ function makeAnthropicCompatSpec(p) {
17188
17375
  error: `${p.label} API key is not configured. Open the ${p.label} picker in the chat header to set one.`
17189
17376
  };
17190
17377
  }
17191
- const requested = typeof params.model === "string" ? params.model : void 0;
17192
- if (isBuiltinMode(params)) {
17193
- const textCheck = requireTextPrompt(params);
17194
- if (!textCheck.ok) return textCheck;
17195
- params.model = resolveBuiltinModel(requested, saved.builtinModel, p.defaultModel);
17196
- return { ok: true };
17197
- }
17198
- params.model = p.resolveSdkModel(requested, saved.model);
17378
+ const textCheck = requireTextPrompt(params);
17379
+ if (!textCheck.ok) return textCheck;
17380
+ params.model = resolveModel(
17381
+ typeof params.model === "string" ? params.model : void 0,
17382
+ saved.builtinModel,
17383
+ p.defaultModel
17384
+ );
17199
17385
  return { ok: true };
17200
17386
  },
17201
17387
  runner: {
@@ -17204,13 +17390,7 @@ function makeAnthropicCompatSpec(p) {
17204
17390
  const settings = await readSettings();
17205
17391
  const saved = settings.engines?.[p.name] ?? {};
17206
17392
  const endpoints = resolveEndpoints(p, saved, settings.language);
17207
- if (isBuiltinMode(ctx.params)) {
17208
- await runBuiltinAgent(ctx, buildBuiltinConfig(p, apiKey, endpoints));
17209
- return;
17210
- }
17211
- const model = typeof ctx.params.model === "string" ? ctx.params.model : p.defaultModel;
17212
- const env = buildEnv({ provider: p, apiKey, model, settings: saved, endpoints });
17213
- await runSdkLoop(ctx, buildOptions(ctx, env));
17393
+ await runBuiltinAgent(ctx, buildConfig(p, apiKey, endpoints));
17214
17394
  },
17215
17395
  resolveTitle: (cwd, sessionId) => getSessionTitle(cwd, sessionId)
17216
17396
  }
@@ -17218,28 +17398,14 @@ function makeAnthropicCompatSpec(p) {
17218
17398
  }
17219
17399
 
17220
17400
  // packages/feature/agent/src/server/engines/deepseek.ts
17221
- var DEEPSEEK_BASE_URL = "https://api.deepseek.com/anthropic";
17222
- var DEFAULT_MODEL = "deepseek-v4-pro";
17223
- var SMALL_FAST_MODEL = "deepseek-v4-flash";
17224
- var ALLOWED_MODELS = /* @__PURE__ */ new Set(["deepseek-v4-flash", "deepseek-v4-pro"]);
17225
17401
  var DEEPSEEK_OPENAI_BASE_URL = "https://api.deepseek.com/v1";
17402
+ var DEFAULT_MODEL = "deepseek-v4-pro";
17226
17403
  var deepseekProvider = {
17227
17404
  name: "deepseek",
17228
17405
  label: "DeepSeek",
17229
- endpoints: { anthropicBaseUrl: DEEPSEEK_BASE_URL, openAiBaseUrl: DEEPSEEK_OPENAI_BASE_URL },
17230
- configDir: DEEPSEEK_DIR,
17406
+ endpoints: { openAiBaseUrl: DEEPSEEK_OPENAI_BASE_URL },
17231
17407
  defaultModel: DEFAULT_MODEL,
17232
- apiKey: deepseekApiKey,
17233
- smallFastModel: SMALL_FAST_MODEL,
17234
- disablePromptCaching: true,
17235
- // DeepSeek runs its own server-side prefix KV cache
17236
- // SDK mode is whitelist-gated: with no listing API to validate against, an unknown id
17237
- // would only fail at the first chat turn.
17238
- resolveSdkModel(requested, saved) {
17239
- if (typeof requested === "string" && ALLOWED_MODELS.has(requested)) return requested;
17240
- if (saved && ALLOWED_MODELS.has(saved)) return saved;
17241
- return DEFAULT_MODEL;
17242
- }
17408
+ apiKey: deepseekApiKey
17243
17409
  };
17244
17410
  var deepseekSpec = makeAnthropicCompatSpec(deepseekProvider);
17245
17411
 
@@ -17262,72 +17428,25 @@ var ollamaSpec = {
17262
17428
  };
17263
17429
 
17264
17430
  // packages/feature/agent/src/server/engines/kimi.ts
17265
- var KIMI_BASE_URL = "https://api.kimi.com/coding/";
17266
17431
  var KIMI_OPENAI_BASE_URL = "https://api.kimi.com/coding/v1";
17267
17432
  var DEFAULT_MODEL3 = "kimi-for-coding";
17268
- var CONTEXT_TOKENS_FALLBACK = {
17269
- k3: 1048576,
17270
- "k3-256k": 262144,
17271
- "kimi-for-coding": 262144,
17272
- "kimi-for-coding-highspeed": 262144
17273
- };
17274
- var DEFAULT_CONTEXT_TOKENS = 262144;
17275
- function kimiWireModel(model) {
17276
- return model === "k3" ? "k3[1m]" : model;
17277
- }
17278
17433
  var kimiProvider = {
17279
17434
  name: "kimi",
17280
17435
  label: "Kimi",
17281
- endpoints: { anthropicBaseUrl: KIMI_BASE_URL, openAiBaseUrl: KIMI_OPENAI_BASE_URL },
17282
- configDir: KIMI_DIR,
17436
+ endpoints: { openAiBaseUrl: KIMI_OPENAI_BASE_URL },
17283
17437
  defaultModel: DEFAULT_MODEL3,
17284
17438
  apiKey: kimiApiKey,
17285
- sdkWireModel: kimiWireModel,
17286
17439
  // Kimi's coding models are thinking-only and reject any temperature but 1 with
17287
17440
  // `invalid temperature: only 1 is allowed for this model`, which fails the turn before a
17288
17441
  // token streams. Omit the field and take the provider default.
17289
- builtinTemperature: null,
17290
- // Kimi Code publishes no small/fast tier — the official Claude Code config points every
17291
- // model slot at the selected model, so ANTHROPIC_SMALL_FAST_MODEL falls through to it.
17292
- // Both protocols list live models (GET /coding/v1/models), so unlike DeepSeek there is
17293
- // nothing to whitelist against: pass the id through and let the API reject unknowns.
17294
- resolveSdkModel(requested, saved) {
17295
- if (typeof requested === "string" && requested.trim()) return requested.trim();
17296
- if (saved && saved.trim()) return saved.trim();
17297
- return DEFAULT_MODEL3;
17298
- },
17299
- sdkEnv(model, settings) {
17300
- const wire = kimiWireModel(model);
17301
- const contextTokens = settings.modelContextTokens ?? CONTEXT_TOKENS_FALLBACK[model] ?? DEFAULT_CONTEXT_TOKENS;
17302
- return {
17303
- // Kimi serves one model per key tier, so every alias the SDK may route to
17304
- // (subagents, haiku-class helpers) has to resolve to the same id — otherwise the
17305
- // SDK asks for `claude-3-5-haiku` and gets a 404.
17306
- ANTHROPIC_DEFAULT_FABLE_MODEL: wire,
17307
- ANTHROPIC_DEFAULT_OPUS_MODEL: wire,
17308
- ANTHROPIC_DEFAULT_SONNET_MODEL: wire,
17309
- ANTHROPIC_DEFAULT_HAIKU_MODEL: wire,
17310
- CLAUDE_CODE_SUBAGENT_MODEL: wire,
17311
- // Without these the SDK compacts against Claude's default window and throws away
17312
- // most of the 256K/1M context this plan pays for.
17313
- CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(contextTokens),
17314
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(contextTokens),
17315
- ...settings.modelEffort ? { CLAUDE_CODE_EFFORT_LEVEL: settings.modelEffort } : {}
17316
- };
17317
- }
17442
+ builtinTemperature: null
17318
17443
  };
17319
17444
  var kimiSpec = makeAnthropicCompatSpec(kimiProvider);
17320
17445
 
17321
17446
  // packages/feature/agent/src/server/engines/glm.ts
17322
17447
  var GLM_REGIONS = {
17323
- cn: {
17324
- anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
17325
- openAiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4"
17326
- },
17327
- global: {
17328
- anthropicBaseUrl: "https://api.z.ai/api/anthropic",
17329
- openAiBaseUrl: "https://api.z.ai/api/coding/paas/v4"
17330
- }
17448
+ cn: { openAiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
17449
+ global: { openAiBaseUrl: "https://api.z.ai/api/coding/paas/v4" }
17331
17450
  };
17332
17451
  var GLM_REGION_IDS = Object.keys(GLM_REGIONS);
17333
17452
  var DEFAULT_MODEL4 = "glm-5.2";
@@ -17340,34 +17459,10 @@ var glmProvider = {
17340
17459
  // Shared with the picker so the UI cannot claim one host while runs go to another.
17341
17460
  defaultFor: defaultRegionForLanguage
17342
17461
  },
17343
- configDir: GLM_DIR,
17344
17462
  defaultModel: DEFAULT_MODEL4,
17345
- apiKey: glmApiKey,
17346
- // GLM's /models reports bare ids no context window, no display name — so unlike Kimi
17347
- // there is nothing to whitelist against and nothing to derive a context limit from.
17348
- // Pass the id through and let the API reject unknowns.
17349
- resolveSdkModel(requested, saved) {
17350
- if (typeof requested === "string" && requested.trim()) return requested.trim();
17351
- if (saved && saved.trim()) return saved.trim();
17352
- return DEFAULT_MODEL4;
17353
- },
17354
- sdkEnv(model) {
17355
- return {
17356
- // GLM serves one model per request, so every alias the SDK may route to has to
17357
- // resolve to the same id — otherwise it asks for `claude-3-5-haiku` and gets a 404.
17358
- ANTHROPIC_DEFAULT_FABLE_MODEL: model,
17359
- ANTHROPIC_DEFAULT_OPUS_MODEL: model,
17360
- ANTHROPIC_DEFAULT_SONNET_MODEL: model,
17361
- ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
17362
- CLAUDE_CODE_SUBAGENT_MODEL: model
17363
- };
17364
- }
17365
- // No CLAUDE_CODE_MAX_CONTEXT_TOKENS: GLM publishes no per-model context metadata, and a
17366
- // hardcoded table would silently rot as models ship. The SDK default is close enough to
17367
- // GLM's 200K that guessing is not worth the staleness.
17368
- //
17369
- // No builtinTemperature override either: temperature 0 with tools is accepted here
17370
- // (verified), unlike Kimi's thinking-only models.
17463
+ apiKey: glmApiKey
17464
+ // No builtinTemperature override: temperature 0 with tools is accepted here (verified),
17465
+ // unlike Kimi's thinking-only models.
17371
17466
  };
17372
17467
  var glmSpec = makeAnthropicCompatSpec(glmProvider);
17373
17468
 
@@ -17393,6 +17488,8 @@ var CODEX_TOOL_NAMES = {
17393
17488
  var CODEX_MCP_NAMESPACE_PREFIX = "mcp__";
17394
17489
  var codexMcpToolName = (server, tool2) => `${CODEX_MCP_NAMESPACE_PREFIX}${server}__${tool2}`;
17395
17490
  var CODEX_SPAWN_FN_NAME = "spawn_agent";
17491
+ var CODEX_PATCH_FN_NAME = "apply_patch";
17492
+ var CODEX_EXEC_SCRIPT_FN_NAME = "exec";
17396
17493
  function codexTodoInput(items) {
17397
17494
  return {
17398
17495
  todos: (items ?? []).flatMap(
@@ -17423,6 +17520,80 @@ function codexMcpResultText(item) {
17423
17520
  if (item.result === null || item.result === void 0) return "";
17424
17521
  return typeof item.result === "string" ? item.result : JSON.stringify(item.result, null, 2);
17425
17522
  }
17523
+ var JS_STRING_LITERAL_RE = /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/g;
17524
+ var JS_SIMPLE_ESCAPES = {
17525
+ n: "\n",
17526
+ r: "\r",
17527
+ t: " ",
17528
+ b: "\b",
17529
+ f: "\f",
17530
+ v: "\v",
17531
+ "0": "\0"
17532
+ };
17533
+ function decodeJsStringLiteral(literal2) {
17534
+ if (literal2.startsWith('"')) {
17535
+ try {
17536
+ return JSON.parse(literal2);
17537
+ } catch {
17538
+ }
17539
+ }
17540
+ return literal2.slice(1, -1).replace(
17541
+ /\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[\s\S])/g,
17542
+ (_full, esc2) => {
17543
+ if (esc2.startsWith("u{")) return String.fromCodePoint(parseInt(esc2.slice(2, -1), 16));
17544
+ if (esc2.startsWith("u") || esc2.startsWith("x")) return String.fromCharCode(parseInt(esc2.slice(1), 16));
17545
+ return JS_SIMPLE_ESCAPES[esc2] ?? esc2;
17546
+ }
17547
+ );
17548
+ }
17549
+ function extractPatchLiteral(script) {
17550
+ JS_STRING_LITERAL_RE.lastIndex = 0;
17551
+ let m;
17552
+ while (m = JS_STRING_LITERAL_RE.exec(script)) {
17553
+ const value = decodeJsStringLiteral(m[0]);
17554
+ if (value.includes("*** Begin Patch")) return value;
17555
+ }
17556
+ return null;
17557
+ }
17558
+ function extractExecArgs(script) {
17559
+ const call = script.indexOf("tools.exec_command(");
17560
+ if (call === -1) return null;
17561
+ const open = script.indexOf("{", call);
17562
+ if (open === -1) return null;
17563
+ let depth = 0;
17564
+ let quote = "";
17565
+ for (let i = open; i < script.length; i++) {
17566
+ const ch = script[i];
17567
+ if (quote) {
17568
+ if (ch === "\\") i++;
17569
+ else if (ch === quote) quote = "";
17570
+ continue;
17571
+ }
17572
+ if (ch === '"' || ch === "'" || ch === "`") {
17573
+ quote = ch;
17574
+ continue;
17575
+ }
17576
+ if (ch === "{") depth++;
17577
+ else if (ch === "}" && --depth === 0) {
17578
+ try {
17579
+ const parsed = JSON.parse(script.slice(open, i + 1));
17580
+ return parsed && typeof parsed === "object" ? parsed : null;
17581
+ } catch {
17582
+ return null;
17583
+ }
17584
+ }
17585
+ }
17586
+ return null;
17587
+ }
17588
+ function parseCodexExecScript(script) {
17589
+ const patch = extractPatchLiteral(script);
17590
+ if (patch) return { kind: "patch", patch };
17591
+ const args = extractExecArgs(script);
17592
+ if (args) {
17593
+ return { kind: "exec", command: typeof args.cmd === "string" ? args.cmd : "", args };
17594
+ }
17595
+ return { kind: "unknown" };
17596
+ }
17426
17597
  var CODEX_AGENT_DONE = /* @__PURE__ */ new Set(["completed", "errored", "interrupted", "not_found", "failed"]);
17427
17598
  function parseCodexSpawnInput(args, extra) {
17428
17599
  const prompt = typeof args.message === "string" ? args.message : "";
@@ -17515,8 +17686,12 @@ function parseCallLines(text, exec2, patch, spawns, mcps) {
17515
17686
  } catch {
17516
17687
  }
17517
17688
  exec2.push({ callId: p.call_id, cmd });
17518
- } else if (p.type === "custom_tool_call" && p.name === "apply_patch") {
17689
+ } else if (p.type === "custom_tool_call" && p.name === CODEX_PATCH_FN_NAME) {
17519
17690
  patch.push({ callId: p.call_id, files: parsePatchFiles(p.input || "") });
17691
+ } else if (p.type === "custom_tool_call" && p.name === CODEX_EXEC_SCRIPT_FN_NAME) {
17692
+ const script = parseCodexExecScript(p.input || "");
17693
+ if (script.kind === "exec") exec2.push({ callId: p.call_id, cmd: script.command });
17694
+ else if (script.kind === "patch") patch.push({ callId: p.call_id, files: parsePatchFiles(script.patch) });
17520
17695
  } else if (p.type === "function_call" && p.name && p.namespace?.startsWith(CODEX_MCP_NAMESPACE_PREFIX)) {
17521
17696
  mcps.push({
17522
17697
  callId: p.call_id,
@@ -17643,7 +17818,9 @@ function createCodexEventAdapter(ctx) {
17643
17818
  const { sessionId } = ctx;
17644
17819
  let terminated = false;
17645
17820
  let failure = null;
17821
+ let progressOutputTokens = 0;
17646
17822
  const pendingToolCalls = /* @__PURE__ */ new Map();
17823
+ const progressItems = /* @__PURE__ */ new Set();
17647
17824
  let rolloutPath = null;
17648
17825
  let execBase = 0;
17649
17826
  let patchBase = 0;
@@ -17814,6 +17991,14 @@ function createCodexEventAdapter(ctx) {
17814
17991
  }
17815
17992
  emitAgentReports(item);
17816
17993
  };
17994
+ const emitOutputProgress = (text, key) => {
17995
+ if (key) {
17996
+ if (progressItems.has(key)) return;
17997
+ progressItems.add(key);
17998
+ }
17999
+ progressOutputTokens += estimateOutputUnits(text);
18000
+ ctx.emit({ type: "usage_update", output_tokens: progressOutputTokens });
18001
+ };
17817
18002
  const handle = (event) => {
17818
18003
  if (terminated) return;
17819
18004
  switch (event.type) {
@@ -17829,12 +18014,15 @@ function createCodexEventAdapter(ctx) {
17829
18014
  const item = event.item;
17830
18015
  if (!item) break;
17831
18016
  if (item.type === "agent_message" && item.text) {
18017
+ emitOutputProgress(item.text);
17832
18018
  ctx.emit({ type: "assistant", message: { content: [{ type: "text", text: item.text }] } });
17833
18019
  }
17834
18020
  if (item.type === "error" && (item.message || item.text)) {
18021
+ emitOutputProgress(item.message || item.text || "");
17835
18022
  ctx.emit({ type: "error", error: item.message || item.text });
17836
18023
  }
17837
18024
  if (item.type === "reasoning" && item.text) {
18025
+ emitOutputProgress(item.text);
17838
18026
  ctx.emit({
17839
18027
  type: "assistant",
17840
18028
  message: { content: [{ type: "text", text: `<details><summary>Reasoning</summary>
@@ -17847,11 +18035,13 @@ ${item.text}
17847
18035
  if (item.type === "command_execution") {
17848
18036
  const toolUseId = resolveExecToolUseId(item);
17849
18037
  if (!pendingToolCalls.has(toolUseId)) {
18038
+ emitOutputProgress(item.command || CODEX_TOOL_NAMES.bash, `command-start:${toolUseId}`);
17850
18039
  ctx.emit({
17851
18040
  type: "assistant",
17852
18041
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command || "" } }] }
17853
18042
  });
17854
18043
  }
18044
+ emitOutputProgress(item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`, `command-result:${toolUseId}`);
17855
18045
  ctx.emit({
17856
18046
  type: "user",
17857
18047
  message: { content: [{ tool_use_id: toolUseId, content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})` }] }
@@ -17859,17 +18049,28 @@ ${item.text}
17859
18049
  pendingToolCalls.delete(toolUseId);
17860
18050
  }
17861
18051
  if (item.type === "collab_tool_call") handleCollabItem(item);
17862
- if (item.type === "mcp_tool_call") emitMcpCall(item, true);
17863
- if (item.type === "web_search") emitWebSearch(item);
17864
- if (item.type === "todo_list") emitTodoList(item);
18052
+ if (item.type === "mcp_tool_call") {
18053
+ emitOutputProgress(`${item.server || "mcp"} ${item.tool || ""} ${JSON.stringify(item.result ?? item.error ?? "")}`);
18054
+ emitMcpCall(item, true);
18055
+ }
18056
+ if (item.type === "web_search") {
18057
+ emitOutputProgress(JSON.stringify(item.action || item.query || "web_search"));
18058
+ emitWebSearch(item);
18059
+ }
18060
+ if (item.type === "todo_list") {
18061
+ emitOutputProgress(JSON.stringify(item.items || []));
18062
+ emitTodoList(item);
18063
+ }
17865
18064
  if (item.type === "file_change") {
17866
18065
  const toolUseId = resolvePatchToolUseId(item);
17867
18066
  if (!pendingToolCalls.has(toolUseId)) {
18067
+ emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
17868
18068
  ctx.emit({
17869
18069
  type: "assistant",
17870
18070
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
17871
18071
  });
17872
18072
  }
18073
+ emitOutputProgress(patchResultText(item), `patch-result:${toolUseId}`);
17873
18074
  ctx.emit({
17874
18075
  type: "user",
17875
18076
  message: { content: [{ tool_use_id: toolUseId, content: patchResultText(item) }] }
@@ -17884,6 +18085,7 @@ ${item.text}
17884
18085
  if (item?.type === "command_execution" && item.command) {
17885
18086
  const toolUseId = resolveExecToolUseId(item);
17886
18087
  pendingToolCalls.set(toolUseId, toolUseId);
18088
+ emitOutputProgress(item.command, `command-start:${toolUseId}`);
17887
18089
  ctx.emit({
17888
18090
  type: "assistant",
17889
18091
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command } }] }
@@ -17892,6 +18094,7 @@ ${item.text}
17892
18094
  if (item?.type === "file_change" && (item.changes?.length ?? 0) > 0) {
17893
18095
  const toolUseId = resolvePatchToolUseId(item);
17894
18096
  pendingToolCalls.set(toolUseId, toolUseId);
18097
+ emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
17895
18098
  ctx.emit({
17896
18099
  type: "assistant",
17897
18100
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
@@ -17949,14 +18152,30 @@ function codexSdkInput(prompt, imageFiles) {
17949
18152
  ];
17950
18153
  }
17951
18154
  function codexThreadOptions(ctx) {
18155
+ const effort = resolveCodexReasoningEffort(ctx.params.codexReasoningEffort);
17952
18156
  return {
17953
18157
  ...ctx.params.model ? { model: ctx.params.model } : {},
18158
+ ...effort ? { modelReasoningEffort: effort } : {},
17954
18159
  ...ctx.cwd ? { workingDirectory: ctx.cwd } : {},
17955
18160
  sandboxMode: "danger-full-access",
17956
18161
  approvalPolicy: "never",
17957
18162
  skipGitRepoCheck: true
17958
18163
  };
17959
18164
  }
18165
+ function resolveCodexReasoningEffort(value) {
18166
+ switch (value) {
18167
+ case "minimal":
18168
+ case "low":
18169
+ case "medium":
18170
+ case "high":
18171
+ case "xhigh":
18172
+ case "max":
18173
+ case "ultra":
18174
+ return value;
18175
+ default:
18176
+ return void 0;
18177
+ }
18178
+ }
17960
18179
  async function runCodexSdk(ctx) {
17961
18180
  const imageFiles = ctx.images && ctx.images.length > 0 ? writeImagesToTemp(ctx.images) : [];
17962
18181
  const adapter = createCodexEventAdapter(ctx);
@@ -18023,6 +18242,7 @@ var en_default2 = {
18023
18242
  cancel: "Cancel",
18024
18243
  delete: "Delete",
18025
18244
  close: "Close",
18245
+ minimize: "Minimize",
18026
18246
  copy: "Copy",
18027
18247
  edit: "Edit",
18028
18248
  file: "File",
@@ -18091,8 +18311,9 @@ var en_default2 = {
18091
18311
  searchPlaceholder: "Search HTML apps...",
18092
18312
  pathLabel: "Absolute path to the .html file",
18093
18313
  invalid: "Invalid",
18314
+ builtin: "Built-in",
18094
18315
  noDescription: "No description",
18095
- emptyNoApps: "No HTML apps yet.",
18316
+ emptyNoApps: "No custom HTML apps yet.",
18096
18317
  emptyCreatePrefix: "Use",
18097
18318
  emptyCreateSuffix: "in chat to create a new app.",
18098
18319
  addExisting: "add an existing HTML file",
@@ -18139,7 +18360,6 @@ var en_default2 = {
18139
18360
  unstagedNFiles: "Unstaged {{count}} files",
18140
18361
  discardFailed: "Discard failed",
18141
18362
  discardedNFiles: "Discarded changes in {{count}} files",
18142
- stagedAllFiles: "All files staged",
18143
18363
  deletedFile: "File deleted",
18144
18364
  discardedChanges: "Changes discarded",
18145
18365
  pastedFile: "Pasted: {{name}}",
@@ -18481,10 +18701,11 @@ var en_default2 = {
18481
18701
  startConversation: "Start a conversation",
18482
18702
  loadMoreHistory: "Load more...",
18483
18703
  scrollUpForMore: "\u2191 Scroll up to load more history",
18484
- claudeThinking: "Claude is thinking...",
18485
18704
  jumpToStart: "Jump to start",
18486
18705
  jumpToLatest: "Jump to latest",
18487
18706
  copyMessage: "Copy message",
18707
+ copyImage: "Copy image",
18708
+ copiedImage: "Image copied",
18488
18709
  forkSession: "Fork session from here",
18489
18710
  excerptTurn: "Excerpt this turn to a new session",
18490
18711
  imageN: "Image {{index}}",
@@ -18509,7 +18730,6 @@ var en_default2 = {
18509
18730
  placeholder: "Type a message, Enter to send (Shift+Enter for new line, paste images; start a line with / (main session) or @ (subagent) for commands, multiple allowed)",
18510
18731
  placeholderDisabled: "Generating... you can keep typing (ESC to cancel; if stuck, cancel and resend 'continue')",
18511
18732
  imageSizeLimit: "Image size exceeds limit (max 5MB), current: {{size}}MB",
18512
- stageAll: "Stage all files (git add -A)",
18513
18733
  viewGitChanges: "View Git changes",
18514
18734
  viewAllComments: "View all comments",
18515
18735
  userMessages: "User messages",
@@ -19225,6 +19445,7 @@ var zh_default = {
19225
19445
  cancel: "\u53D6\u6D88",
19226
19446
  delete: "\u5220\u9664",
19227
19447
  close: "\u5173\u95ED",
19448
+ minimize: "\u6700\u5C0F\u5316",
19228
19449
  copy: "\u590D\u5236",
19229
19450
  edit: "\u7F16\u8F91",
19230
19451
  file: "\u6587\u4EF6",
@@ -19293,8 +19514,9 @@ var zh_default = {
19293
19514
  searchPlaceholder: "\u641C\u7D22 HTML \u5E94\u7528...",
19294
19515
  pathLabel: ".html \u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84",
19295
19516
  invalid: "\u65E0\u6548",
19517
+ builtin: "\u5185\u7F6E",
19296
19518
  noDescription: "\u65E0\u63CF\u8FF0",
19297
- emptyNoApps: "\u8FD8\u6CA1\u6709 HTML \u5E94\u7528\u3002",
19519
+ emptyNoApps: "\u8FD8\u6CA1\u6709\u81EA\u5B9A\u4E49 HTML \u5E94\u7528\u3002",
19298
19520
  emptyCreatePrefix: "\u5728\u804A\u5929\u91CC\u4F7F\u7528",
19299
19521
  emptyCreateSuffix: "\u521B\u5EFA\u65B0\u5E94\u7528\u3002",
19300
19522
  addExisting: "\u6DFB\u52A0\u5DF2\u6709 HTML \u6587\u4EF6",
@@ -19341,7 +19563,6 @@ var zh_default = {
19341
19563
  unstagedNFiles: "\u5DF2\u53D6\u6D88\u6682\u5B58 {{count}} \u4E2A\u6587\u4EF6",
19342
19564
  discardFailed: "\u653E\u5F03\u53D8\u66F4\u5931\u8D25",
19343
19565
  discardedNFiles: "\u5DF2\u653E\u5F03 {{count}} \u4E2A\u6587\u4EF6\u7684\u53D8\u66F4",
19344
- stagedAllFiles: "\u5DF2\u6682\u5B58\u6240\u6709\u6587\u4EF6",
19345
19566
  deletedFile: "\u5DF2\u5220\u9664\u6587\u4EF6",
19346
19567
  discardedChanges: "\u5DF2\u653E\u5F03\u53D8\u66F4",
19347
19568
  pastedFile: "\u5DF2\u7C98\u8D34: {{name}}",
@@ -19683,10 +19904,11 @@ var zh_default = {
19683
19904
  startConversation: "\u5F00\u59CB\u5BF9\u8BDD\u5427",
19684
19905
  loadMoreHistory: "\u52A0\u8F7D\u66F4\u591A...",
19685
19906
  scrollUpForMore: "\u2191 \u5411\u4E0A\u6EDA\u52A8\u52A0\u8F7D\u66F4\u591A\u5386\u53F2",
19686
- claudeThinking: "Claude \u6B63\u5728\u601D\u8003...",
19687
19907
  jumpToStart: "\u8DF3\u8F6C\u5230\u5F00\u59CB",
19688
19908
  jumpToLatest: "\u8DF3\u8F6C\u5230\u6700\u65B0",
19689
19909
  copyMessage: "\u590D\u5236\u6D88\u606F",
19910
+ copyImage: "\u590D\u5236\u56FE\u7247",
19911
+ copiedImage: "\u5DF2\u590D\u5236\u56FE\u7247",
19690
19912
  forkSession: "\u4ECE\u6B64\u5904\u5206\u53C9\u4F1A\u8BDD",
19691
19913
  excerptTurn: "\u6458\u5F55\u672C\u8F6E\u5230\u65B0\u4F1A\u8BDD",
19692
19914
  imageN: "\u56FE\u7247 {{index}}",
@@ -19711,7 +19933,6 @@ var zh_default = {
19711
19933
  placeholder: "\u8F93\u5165\u6D88\u606F\uFF0CEnter \u53D1\u9001\uFF08Shift+Enter \u6362\u884C\uFF0C\u53EF\u7C98\u8D34\u56FE\u7247\uFF1B\u884C\u9996 /\uFF08\u4E3B\u4F1A\u8BDD\uFF09\u6216 @\uFF08\u5B50\u4EE3\u7406\uFF09\u5524\u8D77\u547D\u4EE4\uFF0C\u652F\u6301\u591A\u884C\u591A\u547D\u4EE4\uFF09",
19712
19934
  placeholderDisabled: "\u751F\u6210\u4E2D... \u53EF\u7EE7\u7EED\u8F93\u5165 (ESC \u53D6\u6D88;\u5361\u4F4F\u592A\u4E45\u53EF\u53D6\u6D88\u540E\u91CD\u53D1\u300C\u7EE7\u7EED\u300D)",
19713
19935
  imageSizeLimit: "\u56FE\u7247\u5927\u5C0F\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u5927 5MB\uFF09\uFF0C\u5F53\u524D\u5927\u5C0F: {{size}}MB",
19714
- stageAll: "\u6682\u5B58\u6240\u6709\u6587\u4EF6 (git add -A)",
19715
19936
  viewGitChanges: "\u67E5\u770B Git \u53D8\u66F4",
19716
19937
  viewAllComments: "\u67E5\u770B\u6240\u6709\u8BC4\u8BBA",
19717
19938
  userMessages: "\u7528\u6237\u6D88\u606F\u5217\u8868",
@@ -20497,8 +20718,7 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20497
20718
  if (!spec) {
20498
20719
  throw new Error(`no engine spec for ${engine}`);
20499
20720
  }
20500
- const builtinLoop = isBuiltinLoopSession(engine, task);
20501
- const noHistory = await readSessionNoHistory(task, engine, builtinLoop);
20721
+ const noHistory = await readSessionNoHistory(task, engine);
20502
20722
  const outcome = await dispatchChat(spec, {
20503
20723
  prompt: buildTaskPrompt(task),
20504
20724
  // Omit sessionId to start a brand-new session when the resume target is gone;
@@ -20507,7 +20727,6 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20507
20727
  cwd: task.cwd,
20508
20728
  engine,
20509
20729
  ...task.model && { model: task.model },
20510
- ...builtinLoop && { mode: "builtin" },
20511
20730
  ...noHistory && { noHistory: true }
20512
20731
  });
20513
20732
  if (!outcome.ok) {
@@ -20542,29 +20761,19 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20542
20761
  })
20543
20762
  });
20544
20763
  var BUILTIN_LOOP_PATHS = {
20764
+ ollama: getOllamaSessionPath,
20545
20765
  deepseek: getDeepseekBuiltinSessionPath,
20546
20766
  kimi: getKimiBuiltinSessionPath,
20547
20767
  glm: getGlmBuiltinSessionPath
20548
20768
  };
20549
- var SDK_STORE_PATHS = {
20550
- deepseek: getDeepseekSessionPath,
20551
- kimi: getKimiSessionPath,
20552
- glm: getGlmSessionPath
20553
- };
20554
- function isBuiltinLoopSession(engine, task) {
20555
- const resolve = BUILTIN_LOOP_PATHS[engine];
20556
- return !!resolve && existsSync3(resolve(task.cwd, task.sessionId));
20557
- }
20558
- async function readSessionNoHistory(task, engine, builtinLoop) {
20559
- if (engine !== "ollama" && !builtinLoop && engine !== "claude" && engine !== "codex") return false;
20769
+ async function readSessionNoHistory(task, engine) {
20770
+ if (!BUILTIN_LOOP_PATHS[engine] && engine !== "claude" && engine !== "codex") return false;
20560
20771
  const state = await readJsonFile(getSessionFilePath(task.cwd), {});
20561
20772
  return state.noHistories?.[task.sessionId] === true;
20562
20773
  }
20563
20774
  function sessionPathFor(engine, task) {
20564
- if (engine === "ollama") return getOllamaSessionPath(task.cwd, task.sessionId);
20565
- if (isBuiltinLoopSession(engine, task)) return BUILTIN_LOOP_PATHS[engine](task.cwd, task.sessionId);
20566
- const sdkStorePath = SDK_STORE_PATHS[engine];
20567
- if (sdkStorePath) return sdkStorePath(task.cwd, task.sessionId);
20775
+ const builtinPath = BUILTIN_LOOP_PATHS[engine];
20776
+ if (builtinPath) return builtinPath(task.cwd, task.sessionId);
20568
20777
  if (engine === "codex") return findCodexSessionPath(task.sessionId);
20569
20778
  return getClaudeSessionPath(task.cwd, task.sessionId);
20570
20779
  }