@surething/cockpit 1.0.259 → 1.0.261

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 (249) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +1 -1
  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 +1 -1
  197. package/.next-prod/server/chunks/3286.js +406 -270
  198. package/.next-prod/server/chunks/{9004.js → 3641.js} +2 -2
  199. package/.next-prod/server/chunks/516.js +1 -0
  200. package/.next-prod/server/chunks/5750.js +16 -10
  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/{2653-3f8bfa49727caa72.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-fef0d44c94fcb415.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-824be00a023f34d7.js → page-5de8b6584e328a6f.js} +1 -1
  219. package/.next-prod/static/chunks/app/project/{page-824be00a023f34d7.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 +730 -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 +685 -462
  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/server/chunks/151.js +0 -1
  243. package/.next-prod/static/chunks/1372-d94b8f9ed5cd2713.js +0 -15
  244. package/.next-prod/static/chunks/6784-ec12c61b77c64dc0.js +0 -381
  245. package/.next-prod/static/chunks/7482-3ffe7da02d3e7f45.js +0 -26
  246. package/.next-prod/static/chunks/app/m/page-aff83d3f5f4047c5.js +0 -1
  247. package/.next-prod/static/css/a82580b2376d7cae.css +0 -1
  248. /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_buildManifest.js +0 -0
  249. /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_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",
@@ -1188,349 +1193,485 @@ Finally emit [Overall recap: end-to-end interaction verification + total deliver
1188
1193
  // packages/feature/agent/src/server/lib/htmlPrompt.ts
1189
1194
  var HTML_PROMPT_ZH = `---
1190
1195
  name: html
1191
- 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"
1192
- 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"
1193
1198
  ---
1194
1199
 
1195
- # \u751F\u6210 Cockpit HTML \u5E94\u7528
1200
+ # \u751F\u6210 Cockpit \u5E94\u7528\uFF08\u672C\u5730 React \u9875\u9762\uFF09
1196
1201
 
1197
- \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
1198
- \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
1199
1204
 
1200
- **\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 /
1201
- \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
1202
- \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
1203
1206
 
1204
- ## \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
1205
1208
 
1206
1209
  **\u522B\u4E00\u4E0A\u6765\u5C31\u5199\u6587\u4EF6**\uFF0C\u5206\u4E09\u6B65\uFF1A
1207
1210
 
1208
- 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
1209
- \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
1210
- \u7684\u7406\u89E3\u6458\u8981\uFF0C\u4E0D\u5FC5\u9010\u6761\u8FFD\u95EE\u3002
1211
- 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
1212
- \u53EB\u4EC0\u4E48\u540D\uFF1B\u7528\u6237\u53EF\u5426\u51B3\u6216\u6539\u76EE\u5F55 / \u6539\u540D\u3002
1213
- 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\`
1214
- \u843D\u76D8\u3002\u786E\u8BA4\u4E4B\u524D\u53EA\u8BA8\u8BBA\u3001\u4E0D\u5199\u6587\u4EF6\u3002
1215
-
1216
- \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
1217
- \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
1218
-
1219
- ## \u5951\u7EA6\uFF1Awindow.cockpit\uFF08\u4E24\u6A21\u5F0F\u5171\u7528\uFF0C\u53EA\u6B64\u4E00\u5904\uFF09
1220
-
1221
- \u9875\u9762\u52A0\u8F7D\u65F6 SDK \u5DF2\u5C31\u7EEA\uFF0C\u65E0\u9700\u5F15\u5165\u4EFB\u4F55\u5E93\uFF1A
1222
-
1223
- - \`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
1224
- - \`cockpit.bash(command, opts?)\` \u2014\u2014 \u6267\u884C\u4E00\u6761 bash \u547D\u4EE4\uFF08\`command\` \u662F\u6574\u6761 shell \u4E32\uFF09\uFF1A
1225
- - **\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
1226
- - **\u540E\u53F0**\uFF08\`opts.background: true\`\uFF0C\u957F/\u5B9E\u65F6\u547D\u4EE4\uFF09\u2192 \`{ kill() }\`\uFF0C\u7ECF\u56DE\u8C03\u6D41\u5F0F\u8F93\u51FA\uFF1A
1227
- \`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
1228
-
1229
- \u5199\u8FD9\u7C7B\u9875\u9762\u5FC5\u987B\u5B88\u7684\u51E0\u6761\uFF1A
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
1230
1222
 
1231
- 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
1232
- \`fetch(\u5916\u90E8URL)\`** \u2014\u2014 \u540C\u6E90\u6C99\u7BB1\u4F1A CORS \u62E6\u622A\u3002
1233
- 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
1234
- \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
1235
- \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
1236
- 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
1237
- \u80FD\u7528\u56FA\u5B9A\u547D\u4EE4\u5C31\u522B\u88F8\u62FC\u3002
1238
- 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
1239
- \u76F8\u5BF9\uFF08\`/assets/x.css\`\uFF09\u4F1A 404\u3002
1240
-
1241
- meta \u5934\uFF08\u52A1\u5FC5\u5199\u4E0A\uFF0C\u4F9B HTML \u9762\u677F\u505A\u5361\u7247\u3001console \`/name\` \u6253\u5F00\uFF09\uFF1A
1242
-
1243
- \`\`\`html
1244
- <title>\u9875\u9762\u6807\u9898</title>
1245
- <meta name="cockpit-name" content="short-name"> <!-- /name \u77ED\u540D\uFF0C\u552F\u4E00\u3001\u53EA\u7528\u5B57\u6BCD\u6570\u5B57-_ -->
1246
- <meta name="description" content="\u4E00\u53E5\u8BDD\u8BF4\u660E">
1247
- <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji \u6216\u56FE\u6807 url\uFF0C\u53EF\u9009 -->
1248
- <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 -->
1249
- \`\`\`
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
1250
1225
 
1251
- ## \u9ED8\u8BA4\uFF1AReact\uFF08\u672C\u5730\u96F6\u6784\u5EFA\u6808\uFF09
1226
+ ## \u4E8C\u3001\u9AA8\u67B6\uFF1A\u4E09\u4E2A\u6587\u4EF6\uFF0C\u7167\u6284
1252
1227
 
1253
- \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
1254
1229
 
1255
1230
  \`\`\`html
1256
- <div id="root"></div>
1257
- <script src="/html-lib/react.production.min.js"></script>
1258
- <script src="/html-lib/react-dom.production.min.js"></script>
1259
- <script src="/html-lib/babel.min.js"></script>
1260
- <script>
1261
- (async () => {
1262
- const src = await (await fetch('./app.jsx')).text();
1263
- // \u5FC5\u987B classic runtime\uFF1A\u9ED8\u8BA4 automatic \u4F1A\u6CE8\u5165 import react/jsx-runtime\uFF0C
1264
- // \u4F5C\u4E3A\u666E\u901A\u811A\u672C\u6CE8\u5165\u4F1A\u62A5 "Cannot use import statement outside a module"
1265
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1266
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1267
- })();
1268
- </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>
1269
1258
  \`\`\`
1270
1259
 
1271
- - \`/html-lib\` \u662F Cockpit \u672C\u5730\u6258\u7BA1\uFF08\u540C\u6E90\u3001\u79BB\u7EBF\u53EF\u7528\uFF0C\u65E0\u9700 CDN\uFF09\u3002
1272
- - \`app.jsx\` \u7528\u5168\u5C40 \`React\`\uFF08\`const { useState } = React;\`\uFF09\uFF0C\u672B\u5C3E
1273
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`\u3002
1274
- - \u6837\u5F0F\u9ED8\u8BA4\u5957 **Cockpit \u4E3B\u9898**\uFF1A\`<link rel="stylesheet" href="/html-lib/theme.css">\`\uFF0C\u76F4\u63A5\u7528
1275
- \u8BED\u4E49\u53D8\u91CF\uFF08\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` / \`--muted-foreground\` /
1276
- \`--border\` / \`--destructive\`\uFF1B\u534A\u900F\u660E\u7528 \`hsl(var(--green-9) / .12)\`\uFF09\u3002\u4E0A\u9762 meta \u5934\u91CC\u7684
1277
- \`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
1278
- \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
1279
- 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
1280
- \`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
1281
1261
 
1282
- 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
1283
1263
 
1284
1264
  \`\`\`jsx
1265
+ const { useState, useEffect, useCallback } = React;
1266
+
1285
1267
  function App() {
1286
- const [stars, setStars] = React.useState(null);
1287
- const load = async () => {
1288
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1289
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : '\u51FA\u9519');
1290
- };
1291
- 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
+ );
1292
1298
  }
1293
- \`\`\`
1294
1299
 
1295
- ## \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
+ \`\`\`
1296
1302
 
1297
- \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
1298
- \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
1299
1304
 
1300
- - **\u663E\u5F0F\u89E3\u91CA\u5668**\uFF1A\`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` / \`bash ./api.sh\`
1301
- \uFF08\u514D \`chmod +x\`\uFF09\uFF1B\`node\`/\`python3\` \u62FF\u4E0D\u51C6\u5C31\u5148 \`command -v\` \u63A2\u6D4B\uFF0C\u9000\u56DE shell\u3002
1302
- - **\u4F20\u53C2\u9632\u6CE8\u5165**\uFF1A\u7ED3\u6784\u5316\u5165\u53C2\u7528 **base64** \u4F20\uFF08\u524D\u7AEF \`btoa(...)\`\u3001\u811A\u672C
1303
- \`Buffer.from(arg,'base64')\`\uFF09\uFF0C\u522B\u62FC\u5B57\u7B26\u4E32\uFF1B\u5199\u5E93\u7528**\u53C2\u6570\u5316**\u8BED\u53E5\u3002
1304
- - \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';
1305
1309
 
1306
- ## \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
1307
1313
 
1308
- \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() || '{}');
1309
1315
 
1310
- \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
+ }
1311
1327
 
1312
- \`\`\`html
1313
- <button id="load">\u52A0\u8F7D</button>
1314
- <pre id="out"></pre>
1315
- <script>
1316
- document.getElementById('load').onclick = async () => {
1317
- try {
1318
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1319
- if (exitCode !== 0) { out.textContent = '\u547D\u4EE4\u5931\u8D25: ' + stderr; return; }
1320
- out.textContent = '\u2B50 ' + JSON.parse(stdout).stargazers_count;
1321
- } catch (e) {
1322
- out.textContent = '\u6267\u884C\u51FA\u9519: ' + e.message; // spawn / \u57FA\u5EFA\u5931\u8D25
1323
- }
1324
- };
1325
- </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
+ }
1326
1343
  \`\`\`
1327
1344
 
1328
- \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
1329
1351
 
1330
- \`\`\`html
1331
- <pre id="log"></pre>
1332
- <script>
1333
- const box = document.getElementById('log');
1334
- const h = cockpit.bash("tail -f ./build.log", {
1335
- background: true,
1336
- onOutput: c => { box.textContent += c; },
1337
- onExit: code => { box.textContent += '\\n[\u9000\u51FA ' + code + ']'; },
1338
- onError: msg => { box.textContent += '\\n[\u9519\u8BEF ' + msg + ']'; },
1339
- });
1340
- // \u9700\u8981\u65F6 h.kill()
1341
- </script>
1342
- \`\`\`
1352
+ ## \u4E09\u3001\u5951\u7EA6\uFF1Awindow.cockpit
1343
1353
 
1344
- ## \u5B58\u653E\u4F4D\u7F6E
1354
+ \u9875\u9762\u52A0\u8F7D\u65F6 SDK \u5DF2\u5C31\u7EEA\uFF0C\u65E0\u9700\u5F15\u5165\u4EFB\u4F55\u5E93\uFF1A
1345
1355
 
1346
- \u4E00\u4E2A app \u7684\u6240\u6709\u6587\u4EF6\uFF08\`index.html\` + \`app.jsx\` + \`api.mjs\` \u2026\uFF09\u653E\u8FDB\u540C\u4E00\u4E2A \`<name>/\`
1347
- \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
1348
- \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
+ \`\`\`
1349
1372
 
1350
- - \u7528\u6237\u5728\u9700\u6C42\u91CC\u7ED9\u4E86\u76EE\u5F55 \u2192 \u5B58\u5230 \`<\u7528\u6237\u7ED9\u7684\u76EE\u5F55>/<name>/\`
1351
- - \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
1352
1374
 
1353
- \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
1354
- \u5373\u53EF\u9884\u89C8\u5E76\u4EA4\u4E92\u3002`;
1355
- var HTML_PROMPT_EN = `---
1356
- name: html
1357
- description: "Generate an interactive small app with the built-in cockpit bash SDK to fetch/update data"
1358
- argument-hint: "describe the small app you want"
1359
- ---
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
1360
1384
 
1361
- # Build a Cockpit small app
1385
+ ## \u56DB\u3001\u672C\u5730\u6570\u636E\uFF1A\u4E09\u7C7B\u76EE\u5F55
1362
1386
 
1363
- A small app that can fetch/update data: the preview injects a global
1364
- \`window.cockpit\` SDK \u2014 essentially the Bash tool exposed to the page, so buttons can
1365
- \`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
1366
1389
 
1367
- **Default to React** (the local zero-build stack below \u2014 productized, most AI-familiar,
1368
- ideal for stateful / multi-view / form apps); fall back to single-file inline HTML only
1369
- for a **trivial single view or a purely static one-off**. Both modes share the one
1370
- 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
1371
1393
 
1372
- ## 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
1373
1395
 
1374
- **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
1375
1398
 
1376
- 1. **Align on the requirement** \u2014 restate your understanding of the app (what it does,
1377
- React vs single-file, where data comes from, key features/interactions). Ask back only
1378
- when the requirement is ambiguous or has several reasonable readings; if it's already
1379
- clear, just give a short understanding summary \u2014 don't interrogate.
1380
- 2. **Tell the storage directory** \u2014 resolve \`<dir>/<name>/\` per the "Where to store" rules
1381
- below and tell the user exactly where it'll go and what it's named; the user may reject
1382
- or change the directory / name.
1383
- 3. **Write only after confirmation** \u2014 \`Write\` the files only once the user clearly agrees
1384
- (confirm / start / go / "write it" \u2014 any affirmative). Until then, only discuss \u2014 write
1385
- 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
1386
1407
 
1387
- Even if the user already spelled out the requirement and directory in the \`/html\` call,
1388
- still stop once to present "understanding + directory + a 'shall I start writing?'" and
1389
- wait \u2014 but keep this round **lightweight**, don't re-interrogate.
1408
+ ## \u4E94\u3001\u4E3B\u9898
1390
1409
 
1391
- ## 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
1392
1412
 
1393
- The SDK is ready on load \u2014 no library to import:
1413
+ \`\`\`bash
1414
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
1415
+ \`\`\`
1394
1416
 
1395
- - \`cockpit.cwd: string\` \u2014 directory of the current file; relative-path commands run here.
1396
- - \`cockpit.bash(command, opts?)\` \u2014 run one bash command (\`command\` is a raw shell string):
1397
- - **Foreground** (default, short/discrete) \u2192 \`Promise<{ stdout, stderr, exitCode }>\`; \`await\` for the full output at once.
1398
- - **Background** (\`opts.background: true\`, long/live) \u2192 \`{ kill() }\`, streaming via callbacks:
1399
- \`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
+ ---
1400
1431
 
1401
- Rules every such page must follow:
1432
+ # Build a Cockpit app (a local React page)
1402
1433
 
1403
- 1. **Data only through \`cockpit.bash\`** (\`curl\` to read, \`curl -X POST\` to write, or
1404
- read/write files) \u2014 **never \`fetch(externalURL)\`**: the same-origin sandbox blocks it via CORS.
1405
- 2. **Handle both failure modes** (foreground): a **throw** caught by \`try/catch\` = spawn/infra
1406
- failure (bad path, dropped connection); no throw but **\`exitCode !== 0\` = the command ran
1407
- and failed** \u2014 show \`stderr\` or the error body in stdout, don't just say "failed". Background:
1408
- \`onError\` (infra) + a non-zero \`code\` in \`onExit\` (command).
1409
- 3. **\`command\` is a shell string**: validate or escape any dynamic/user input before
1410
- interpolating it (shell metacharacters inject); prefer a fixed command.
1411
- 4. **Resource refs**: relative siblings (\`./app.jsx\`, \`./style.css\`, images) and absolute
1412
- 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.
1413
1437
 
1414
- 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.
1415
1439
 
1416
- \`\`\`html
1417
- <title>Page title</title>
1418
- <meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ only -->
1419
- <meta name="description" content="one line about the page">
1420
- <meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji or icon url, optional -->
1421
- <meta name="cockpit-theme" content="auto"> <!-- enable the draggable light/dark toggle; auto = first load follows the OS, or light/dark -->
1422
- \`\`\`
1440
+ ## 1. Flow: align first, write only after confirmation (important)
1423
1441
 
1424
- ## Default: React (local zero-build stack)
1442
+ **Don't write files up front.** Three steps:
1425
1443
 
1426
- 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:
1427
1466
 
1428
1467
  \`\`\`html
1429
- <div id="root"></div>
1430
- <script src="/html-lib/react.production.min.js"></script>
1431
- <script src="/html-lib/react-dom.production.min.js"></script>
1432
- <script src="/html-lib/babel.min.js"></script>
1433
- <script>
1434
- (async () => {
1435
- const src = await (await fetch('./app.jsx')).text();
1436
- // MUST use classic runtime: the default automatic runtime injects an import of
1437
- // react/jsx-runtime, which \u2014 appended as a plain script \u2014 throws
1438
- // "Cannot use import statement outside a module".
1439
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
1440
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
1441
- })();
1442
- </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>
1443
1496
  \`\`\`
1444
1497
 
1445
- - \`/html-lib\` is hosted locally by Cockpit (same-origin, offline, no CDN).
1446
- - \`app.jsx\` uses the global \`React\` (\`const { useState } = React;\`) and ends with
1447
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`.
1448
- - Style with the **Cockpit theme** by default: \`<link rel="stylesheet" href="/html-lib/theme.css">\`,
1449
- then use the semantic vars (\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` /
1450
- \`--muted-foreground\` / \`--border\` / \`--destructive\`; translucency via \`hsl(var(--green-9) / .12)\`).
1451
- The \`cockpit-theme\` meta above gives the preview a floating light/dark toggle (top-right by
1452
- default, draggable to any corner \u2014 so don't reserve top-right space for it); it **follows the
1453
- Cockpit host theme by default** (outside Cockpit, the meta's auto/light/dark decides), and a manual
1454
- toggle is **remembered per app across reloads and overrides the host**, or call \`cockpit.toggleTheme()\`
1455
- 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\`).
1456
1500
 
1457
- 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**:
1458
1502
 
1459
1503
  \`\`\`jsx
1504
+ const { useState, useEffect, useCallback } = React;
1505
+
1460
1506
  function App() {
1461
- const [stars, setStars] = React.useState(null);
1462
- const load = async () => {
1463
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1464
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : 'error');
1465
- };
1466
- 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
+ );
1467
1537
  }
1468
- \`\`\`
1469
1538
 
1470
- ## Backend: write complex logic as a script (CGI-style, both modes)
1539
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
1540
+ \`\`\`
1471
1541
 
1472
- Inline short commands in \`cockpit.bash("...")\`. For complex logic (multi-step, parsing,
1473
- loops, a library, DB writes), write the backend as a **script file** next to the page and
1474
- 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:
1475
1543
 
1476
- - **Explicit interpreter**: \`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` /
1477
- \`bash ./api.sh\` (avoids \`chmod +x\`); probe \`node\`/\`python3\` with \`command -v\` if unsure,
1478
- and fall back to shell.
1479
- - **Pass args safely**: send structured input as **base64** (\`btoa(...)\` on the page,
1480
- \`Buffer.from(arg,'base64')\` in the script) rather than string-building; use **parameterized**
1481
- statements for DB writes.
1482
- - 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';
1483
1548
 
1484
- ## 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
1485
1552
 
1486
- 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() || '{}');
1487
1554
 
1488
- 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
+ }
1489
1566
 
1490
- \`\`\`html
1491
- <button id="load">Load</button>
1492
- <pre id="out"></pre>
1493
- <script>
1494
- document.getElementById('load').onclick = async () => {
1495
- try {
1496
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
1497
- if (exitCode !== 0) { out.textContent = 'command failed: ' + stderr; return; }
1498
- out.textContent = '\u2B50 ' + JSON.parse(stdout).stargazers_count;
1499
- } catch (e) {
1500
- out.textContent = 'exec error: ' + e.message; // spawn / infra failure
1501
- }
1502
- };
1503
- </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
+ }
1504
1582
  \`\`\`
1505
1583
 
1506
- 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.
1507
1592
 
1508
- \`\`\`html
1509
- <pre id="log"></pre>
1510
- <script>
1511
- const box = document.getElementById('log');
1512
- const h = cockpit.bash("tail -f ./build.log", {
1513
- background: true,
1514
- onOutput: c => { box.textContent += c; },
1515
- onExit: code => { box.textContent += '\\n[exit ' + code + ']'; },
1516
- onError: msg => { box.textContent += '\\n[error ' + msg + ']'; },
1517
- });
1518
- // h.kill() when done
1519
- </script>
1520
- \`\`\`
1593
+ ## 3. Contract: window.cockpit
1521
1594
 
1522
- ## Where to store it
1595
+ The SDK is ready on load \u2014 no library to import:
1523
1596
 
1524
- Put all of an app's files (\`index.html\` + \`app.jsx\` + \`api.mjs\` \u2026) in a single
1525
- \`<name>/\` subdirectory. **You always pick \`<name>\`** from the request (lowercase
1526
- kebab-case, matching \`cockpit-name\`) \u2014 the user only describes the app, they don't
1527
- 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
+ \`\`\`
1613
+
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:
1528
1660
 
1529
- - The user gave a directory in the request \u2192 store under \`<the given directory>/<name>/\`.
1530
- - The user gave none \u2192 store under the **current chat working directory** (this session's cwd), i.e. \`<cwd>/<name>/\`.
1661
+ \`\`\`bash
1662
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
1663
+ \`\`\`
1531
1664
 
1532
- 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**,
1533
- \`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.`;
1534
1675
 
1535
1676
  // packages/feature/agent/src/server/lib/newBranchPrompt.ts
1536
1677
  var NEW_BRANCH_PROMPT_ZH = `---
@@ -2392,7 +2533,7 @@ function follow(ctx) {
2392
2533
  else ctx.signal.addEventListener("abort", () => ac.abort(), { once: true });
2393
2534
  return ac;
2394
2535
  }
2395
- async function runSdkLoop(ctx, buildOptions2) {
2536
+ async function runSdkLoop(ctx, buildOptions) {
2396
2537
  const hasImages = !!(ctx.images && ctx.images.length > 0);
2397
2538
  const content = [];
2398
2539
  if (ctx.images) {
@@ -2434,7 +2575,7 @@ async function runSdkLoop(ctx, buildOptions2) {
2434
2575
  })();
2435
2576
  const attemptAbort = follow(ctx);
2436
2577
  const resume = isRetry ? ctx.currentKey() : ctx.sessionId;
2437
- 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);
2438
2579
  const response = query({ prompt: input, options });
2439
2580
  let receivedResult = false;
2440
2581
  for await (const message of response) {
@@ -2464,6 +2605,39 @@ async function runSdkLoop(ctx, buildOptions2) {
2464
2605
  // packages/feature/agent/src/server/engines/claude.ts
2465
2606
  var PLAN_EDIT_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
2466
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
+ }
2467
2641
  function planPermission(toolName, input, opts) {
2468
2642
  if (toolName === "ExitPlanMode") {
2469
2643
  return {
@@ -2490,6 +2664,9 @@ function planPermission(toolName, input, opts) {
2490
2664
  function buildClaudeOptions(ctx, independent) {
2491
2665
  const { permissionMode } = ctx.params;
2492
2666
  const isPlan = permissionMode === "plan";
2667
+ const model = resolveClaudeModel(ctx);
2668
+ const effort = resolveClaudeEffort(ctx);
2669
+ const settings = resolveClaudeSettings(ctx);
2493
2670
  return (abort, resume, isRetry) => ({
2494
2671
  // Independent task: resume is what makes the CLI load the transcript, so the first
2495
2672
  // attempt drops it and names the session explicitly instead — same file on disk, empty
@@ -2497,6 +2674,9 @@ function buildClaudeOptions(ctx, independent) {
2497
2674
  // turn (the history is stashed away), which is exactly what the retry needs.
2498
2675
  ...independent && !isRetry ? { sessionId: resume } : resume && { resume },
2499
2676
  ...ctx.cwd && { cwd: ctx.cwd },
2677
+ ...model ? { model } : {},
2678
+ ...effort ? { effort } : {},
2679
+ ...settings ? { settings } : {},
2500
2680
  settingSources: ["user", "project", "local"],
2501
2681
  // Permission mode: 'plan' (read-only) when requested, else skip all permission checks.
2502
2682
  permissionMode: isPlan ? "plan" : "bypassPermissions",
@@ -2518,15 +2698,16 @@ var claudeSpec = {
2518
2698
  name: "claude",
2519
2699
  runner: {
2520
2700
  async run(ctx) {
2521
- const independent = ctx.params.noHistory === true && !!ctx.sessionId;
2701
+ const runCtx = withClaudePromptEffort(ctx);
2702
+ const independent = runCtx.params.noHistory === true && !!runCtx.sessionId;
2522
2703
  if (!independent) {
2523
- await runSdkLoop(ctx, buildClaudeOptions(ctx, false));
2704
+ await runSdkLoop(runCtx, buildClaudeOptions(runCtx, false));
2524
2705
  return;
2525
2706
  }
2526
- const sessionPath = getClaudeSessionPath(ctx.cwd, ctx.sessionId);
2707
+ const sessionPath = getClaudeSessionPath(runCtx.cwd, runCtx.sessionId);
2527
2708
  const stashed = stashTranscript(sessionPath);
2528
2709
  try {
2529
- await runSdkLoop(ctx, buildClaudeOptions(ctx, true));
2710
+ await runSdkLoop(runCtx, buildClaudeOptions(runCtx, true));
2530
2711
  } finally {
2531
2712
  if (stashed) mergeStashedTranscript(sessionPath);
2532
2713
  }
@@ -16859,6 +17040,16 @@ ${summary}`;
16859
17040
  }
16860
17041
 
16861
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
+ }
16862
17053
  function formatToolError(error48) {
16863
17054
  if (error48 instanceof Error) return error48.message || String(error48);
16864
17055
  if (typeof error48 === "string") return error48;
@@ -16870,12 +17061,15 @@ function formatToolError(error48) {
16870
17061
  }
16871
17062
  async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16872
17063
  let text = "";
17064
+ let progressOutputTokens = 0;
16873
17065
  const toolCalls = [];
16874
17066
  const pendingToolCalls = /* @__PURE__ */ new Map();
16875
17067
  for await (const part of fullStream) {
16876
17068
  switch (part.type) {
16877
17069
  case "text-delta": {
16878
17070
  text += part.text;
17071
+ progressOutputTokens += estimateOutputUnits(part.text);
17072
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16879
17073
  safeEnqueue(
16880
17074
  `data: ${JSON.stringify({
16881
17075
  type: "stream_event",
@@ -16889,12 +17083,16 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16889
17083
  );
16890
17084
  break;
16891
17085
  }
17086
+ case "finish-step":
17087
+ break;
16892
17088
  case "tool-call": {
16893
17089
  if (text) {
16894
17090
  opts?.onTextFlush?.(text);
16895
17091
  text = "";
16896
17092
  }
16897
17093
  const input = part.input || {};
17094
+ progressOutputTokens += estimateOutputUnits(`${part.toolName} ${JSON.stringify(input)}`);
17095
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16898
17096
  opts?.onToolCall?.(part.toolCallId, part.toolName, input);
16899
17097
  safeEnqueue(
16900
17098
  `data: ${JSON.stringify({
@@ -16915,6 +17113,8 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16915
17113
  }
16916
17114
  case "tool-result": {
16917
17115
  const content = String(part.output);
17116
+ progressOutputTokens += estimateOutputUnits(content);
17117
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16918
17118
  opts?.onToolResult?.(part.toolCallId, content, false);
16919
17119
  safeEnqueue(
16920
17120
  `data: ${JSON.stringify({
@@ -16936,6 +17136,8 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16936
17136
  // unusable session. Every tool call must close with a result line, success or not.
16937
17137
  case "tool-error": {
16938
17138
  const content = formatToolError(part.error);
17139
+ progressOutputTokens += estimateOutputUnits(content);
17140
+ emitUsageUpdate(progressOutputTokens, safeEnqueue);
16939
17141
  opts?.onToolResult?.(part.toolCallId, content, true);
16940
17142
  safeEnqueue(
16941
17143
  `data: ${JSON.stringify({
@@ -16959,8 +17161,6 @@ async function consumeStream(fullStream, safeEnqueue, _sessionId, opts) {
16959
17161
  // the normal failure path (orchestrator → run `error` event → UI).
16960
17162
  case "error":
16961
17163
  throw new Error(formatProviderError(part.error), { cause: part.error });
16962
- case "finish":
16963
- break;
16964
17164
  default:
16965
17165
  break;
16966
17166
  }
@@ -17147,12 +17347,12 @@ function resolveEndpoints(p, saved, language) {
17147
17347
  const id = saved.region || p.regions.defaultFor(language);
17148
17348
  return p.regions.byId[id] ?? p.endpoints;
17149
17349
  }
17150
- function resolveBuiltinModel(requested, saved, fallback) {
17350
+ function resolveModel(requested, saved, fallback) {
17151
17351
  if (typeof requested === "string" && requested.trim()) return requested.trim();
17152
17352
  if (saved && saved.trim()) return saved.trim();
17153
17353
  return fallback;
17154
17354
  }
17155
- function buildBuiltinConfig(p, apiKey, endpoints) {
17355
+ function buildConfig(p, apiKey, endpoints) {
17156
17356
  return {
17157
17357
  sessionsRoot: getBuiltinSessionsRoot(p.name),
17158
17358
  defaultModel: p.defaultModel,
@@ -17160,37 +17360,6 @@ function buildBuiltinConfig(p, apiKey, endpoints) {
17160
17360
  ...p.builtinTemperature !== void 0 && { temperature: p.builtinTemperature }
17161
17361
  };
17162
17362
  }
17163
- function buildEnv(opts) {
17164
- const { provider: p, apiKey, model, settings, endpoints } = opts;
17165
- const wire = p.sdkWireModel ? p.sdkWireModel(model) : model;
17166
- return sanitizedSpawnEnv({
17167
- ANTHROPIC_AUTH_TOKEN: void 0,
17168
- ANTHROPIC_BASE_URL: endpoints.anthropicBaseUrl,
17169
- ANTHROPIC_API_KEY: apiKey,
17170
- // both providers accept this as x-api-key
17171
- ANTHROPIC_MODEL: wire,
17172
- ANTHROPIC_SMALL_FAST_MODEL: p.smallFastModel ?? wire,
17173
- CLAUDE_CONFIG_DIR: p.configDir,
17174
- ...p.disablePromptCaching && { DISABLE_PROMPT_CACHING: "1" },
17175
- CLAUDE_CODE_USE_BEDROCK: "0",
17176
- CLAUDE_CODE_USE_VERTEX: "0",
17177
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
17178
- ...p.sdkEnv?.(model, settings)
17179
- });
17180
- }
17181
- function buildOptions(ctx, env) {
17182
- return (abort, resume) => ({
17183
- ...resume && { resume },
17184
- ...ctx.cwd && { cwd: ctx.cwd },
17185
- settingSources: ["user", "project", "local"],
17186
- permissionMode: "bypassPermissions",
17187
- allowDangerouslySkipPermissions: true,
17188
- includePartialMessages: true,
17189
- abortController: abort,
17190
- env
17191
- });
17192
- }
17193
- var isBuiltinMode = (params) => params.mode === "builtin";
17194
17363
  function makeAnthropicCompatSpec(p) {
17195
17364
  return {
17196
17365
  name: p.name,
@@ -17206,14 +17375,13 @@ function makeAnthropicCompatSpec(p) {
17206
17375
  error: `${p.label} API key is not configured. Open the ${p.label} picker in the chat header to set one.`
17207
17376
  };
17208
17377
  }
17209
- const requested = typeof params.model === "string" ? params.model : void 0;
17210
- if (isBuiltinMode(params)) {
17211
- const textCheck = requireTextPrompt(params);
17212
- if (!textCheck.ok) return textCheck;
17213
- params.model = resolveBuiltinModel(requested, saved.builtinModel, p.defaultModel);
17214
- return { ok: true };
17215
- }
17216
- 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
+ );
17217
17385
  return { ok: true };
17218
17386
  },
17219
17387
  runner: {
@@ -17222,13 +17390,7 @@ function makeAnthropicCompatSpec(p) {
17222
17390
  const settings = await readSettings();
17223
17391
  const saved = settings.engines?.[p.name] ?? {};
17224
17392
  const endpoints = resolveEndpoints(p, saved, settings.language);
17225
- if (isBuiltinMode(ctx.params)) {
17226
- await runBuiltinAgent(ctx, buildBuiltinConfig(p, apiKey, endpoints));
17227
- return;
17228
- }
17229
- const model = typeof ctx.params.model === "string" ? ctx.params.model : p.defaultModel;
17230
- const env = buildEnv({ provider: p, apiKey, model, settings: saved, endpoints });
17231
- await runSdkLoop(ctx, buildOptions(ctx, env));
17393
+ await runBuiltinAgent(ctx, buildConfig(p, apiKey, endpoints));
17232
17394
  },
17233
17395
  resolveTitle: (cwd, sessionId) => getSessionTitle(cwd, sessionId)
17234
17396
  }
@@ -17236,28 +17398,14 @@ function makeAnthropicCompatSpec(p) {
17236
17398
  }
17237
17399
 
17238
17400
  // packages/feature/agent/src/server/engines/deepseek.ts
17239
- var DEEPSEEK_BASE_URL = "https://api.deepseek.com/anthropic";
17240
- var DEFAULT_MODEL = "deepseek-v4-pro";
17241
- var SMALL_FAST_MODEL = "deepseek-v4-flash";
17242
- var ALLOWED_MODELS = /* @__PURE__ */ new Set(["deepseek-v4-flash", "deepseek-v4-pro"]);
17243
17401
  var DEEPSEEK_OPENAI_BASE_URL = "https://api.deepseek.com/v1";
17402
+ var DEFAULT_MODEL = "deepseek-v4-pro";
17244
17403
  var deepseekProvider = {
17245
17404
  name: "deepseek",
17246
17405
  label: "DeepSeek",
17247
- endpoints: { anthropicBaseUrl: DEEPSEEK_BASE_URL, openAiBaseUrl: DEEPSEEK_OPENAI_BASE_URL },
17248
- configDir: DEEPSEEK_DIR,
17406
+ endpoints: { openAiBaseUrl: DEEPSEEK_OPENAI_BASE_URL },
17249
17407
  defaultModel: DEFAULT_MODEL,
17250
- apiKey: deepseekApiKey,
17251
- smallFastModel: SMALL_FAST_MODEL,
17252
- disablePromptCaching: true,
17253
- // DeepSeek runs its own server-side prefix KV cache
17254
- // SDK mode is whitelist-gated: with no listing API to validate against, an unknown id
17255
- // would only fail at the first chat turn.
17256
- resolveSdkModel(requested, saved) {
17257
- if (typeof requested === "string" && ALLOWED_MODELS.has(requested)) return requested;
17258
- if (saved && ALLOWED_MODELS.has(saved)) return saved;
17259
- return DEFAULT_MODEL;
17260
- }
17408
+ apiKey: deepseekApiKey
17261
17409
  };
17262
17410
  var deepseekSpec = makeAnthropicCompatSpec(deepseekProvider);
17263
17411
 
@@ -17280,72 +17428,25 @@ var ollamaSpec = {
17280
17428
  };
17281
17429
 
17282
17430
  // packages/feature/agent/src/server/engines/kimi.ts
17283
- var KIMI_BASE_URL = "https://api.kimi.com/coding/";
17284
17431
  var KIMI_OPENAI_BASE_URL = "https://api.kimi.com/coding/v1";
17285
17432
  var DEFAULT_MODEL3 = "kimi-for-coding";
17286
- var CONTEXT_TOKENS_FALLBACK = {
17287
- k3: 1048576,
17288
- "k3-256k": 262144,
17289
- "kimi-for-coding": 262144,
17290
- "kimi-for-coding-highspeed": 262144
17291
- };
17292
- var DEFAULT_CONTEXT_TOKENS = 262144;
17293
- function kimiWireModel(model) {
17294
- return model === "k3" ? "k3[1m]" : model;
17295
- }
17296
17433
  var kimiProvider = {
17297
17434
  name: "kimi",
17298
17435
  label: "Kimi",
17299
- endpoints: { anthropicBaseUrl: KIMI_BASE_URL, openAiBaseUrl: KIMI_OPENAI_BASE_URL },
17300
- configDir: KIMI_DIR,
17436
+ endpoints: { openAiBaseUrl: KIMI_OPENAI_BASE_URL },
17301
17437
  defaultModel: DEFAULT_MODEL3,
17302
17438
  apiKey: kimiApiKey,
17303
- sdkWireModel: kimiWireModel,
17304
17439
  // Kimi's coding models are thinking-only and reject any temperature but 1 with
17305
17440
  // `invalid temperature: only 1 is allowed for this model`, which fails the turn before a
17306
17441
  // token streams. Omit the field and take the provider default.
17307
- builtinTemperature: null,
17308
- // Kimi Code publishes no small/fast tier — the official Claude Code config points every
17309
- // model slot at the selected model, so ANTHROPIC_SMALL_FAST_MODEL falls through to it.
17310
- // Both protocols list live models (GET /coding/v1/models), so unlike DeepSeek there is
17311
- // nothing to whitelist against: pass the id through and let the API reject unknowns.
17312
- resolveSdkModel(requested, saved) {
17313
- if (typeof requested === "string" && requested.trim()) return requested.trim();
17314
- if (saved && saved.trim()) return saved.trim();
17315
- return DEFAULT_MODEL3;
17316
- },
17317
- sdkEnv(model, settings) {
17318
- const wire = kimiWireModel(model);
17319
- const contextTokens = settings.modelContextTokens ?? CONTEXT_TOKENS_FALLBACK[model] ?? DEFAULT_CONTEXT_TOKENS;
17320
- return {
17321
- // Kimi serves one model per key tier, so every alias the SDK may route to
17322
- // (subagents, haiku-class helpers) has to resolve to the same id — otherwise the
17323
- // SDK asks for `claude-3-5-haiku` and gets a 404.
17324
- ANTHROPIC_DEFAULT_FABLE_MODEL: wire,
17325
- ANTHROPIC_DEFAULT_OPUS_MODEL: wire,
17326
- ANTHROPIC_DEFAULT_SONNET_MODEL: wire,
17327
- ANTHROPIC_DEFAULT_HAIKU_MODEL: wire,
17328
- CLAUDE_CODE_SUBAGENT_MODEL: wire,
17329
- // Without these the SDK compacts against Claude's default window and throws away
17330
- // most of the 256K/1M context this plan pays for.
17331
- CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(contextTokens),
17332
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(contextTokens),
17333
- ...settings.modelEffort ? { CLAUDE_CODE_EFFORT_LEVEL: settings.modelEffort } : {}
17334
- };
17335
- }
17442
+ builtinTemperature: null
17336
17443
  };
17337
17444
  var kimiSpec = makeAnthropicCompatSpec(kimiProvider);
17338
17445
 
17339
17446
  // packages/feature/agent/src/server/engines/glm.ts
17340
17447
  var GLM_REGIONS = {
17341
- cn: {
17342
- anthropicBaseUrl: "https://open.bigmodel.cn/api/anthropic",
17343
- openAiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4"
17344
- },
17345
- global: {
17346
- anthropicBaseUrl: "https://api.z.ai/api/anthropic",
17347
- openAiBaseUrl: "https://api.z.ai/api/coding/paas/v4"
17348
- }
17448
+ cn: { openAiBaseUrl: "https://open.bigmodel.cn/api/coding/paas/v4" },
17449
+ global: { openAiBaseUrl: "https://api.z.ai/api/coding/paas/v4" }
17349
17450
  };
17350
17451
  var GLM_REGION_IDS = Object.keys(GLM_REGIONS);
17351
17452
  var DEFAULT_MODEL4 = "glm-5.2";
@@ -17358,34 +17459,10 @@ var glmProvider = {
17358
17459
  // Shared with the picker so the UI cannot claim one host while runs go to another.
17359
17460
  defaultFor: defaultRegionForLanguage
17360
17461
  },
17361
- configDir: GLM_DIR,
17362
17462
  defaultModel: DEFAULT_MODEL4,
17363
- apiKey: glmApiKey,
17364
- // GLM's /models reports bare ids no context window, no display name — so unlike Kimi
17365
- // there is nothing to whitelist against and nothing to derive a context limit from.
17366
- // Pass the id through and let the API reject unknowns.
17367
- resolveSdkModel(requested, saved) {
17368
- if (typeof requested === "string" && requested.trim()) return requested.trim();
17369
- if (saved && saved.trim()) return saved.trim();
17370
- return DEFAULT_MODEL4;
17371
- },
17372
- sdkEnv(model) {
17373
- return {
17374
- // GLM serves one model per request, so every alias the SDK may route to has to
17375
- // resolve to the same id — otherwise it asks for `claude-3-5-haiku` and gets a 404.
17376
- ANTHROPIC_DEFAULT_FABLE_MODEL: model,
17377
- ANTHROPIC_DEFAULT_OPUS_MODEL: model,
17378
- ANTHROPIC_DEFAULT_SONNET_MODEL: model,
17379
- ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
17380
- CLAUDE_CODE_SUBAGENT_MODEL: model
17381
- };
17382
- }
17383
- // No CLAUDE_CODE_MAX_CONTEXT_TOKENS: GLM publishes no per-model context metadata, and a
17384
- // hardcoded table would silently rot as models ship. The SDK default is close enough to
17385
- // GLM's 200K that guessing is not worth the staleness.
17386
- //
17387
- // No builtinTemperature override either: temperature 0 with tools is accepted here
17388
- // (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.
17389
17466
  };
17390
17467
  var glmSpec = makeAnthropicCompatSpec(glmProvider);
17391
17468
 
@@ -17401,6 +17478,8 @@ var CODEX_IMAGE_ONLY_TEXT = "[Image]";
17401
17478
  var CODEX_TOOL_NAMES = {
17402
17479
  bash: "Bash",
17403
17480
  applyPatch: "ApplyPatch",
17481
+ /** `view_image` → claude's Read: it opens a file, and Read is in READ_ONLY_TOOLS. */
17482
+ read: "Read",
17404
17483
  /** `spawn_agent` → claude's Task, so one bubble == one sub-agent. */
17405
17484
  task: "Task",
17406
17485
  /** `update_plan` / live `todo_list` → the checklist MessageBubble already renders. */
@@ -17411,6 +17490,9 @@ var CODEX_TOOL_NAMES = {
17411
17490
  var CODEX_MCP_NAMESPACE_PREFIX = "mcp__";
17412
17491
  var codexMcpToolName = (server, tool2) => `${CODEX_MCP_NAMESPACE_PREFIX}${server}__${tool2}`;
17413
17492
  var CODEX_SPAWN_FN_NAME = "spawn_agent";
17493
+ var CODEX_SUB_AGENT_ACTIVITY_TYPE = "sub_agent_activity";
17494
+ var CODEX_PATCH_FN_NAME = "apply_patch";
17495
+ var CODEX_EXEC_SCRIPT_FN_NAME = "exec";
17414
17496
  function codexTodoInput(items) {
17415
17497
  return {
17416
17498
  todos: (items ?? []).flatMap(
@@ -17441,17 +17523,113 @@ function codexMcpResultText(item) {
17441
17523
  if (item.result === null || item.result === void 0) return "";
17442
17524
  return typeof item.result === "string" ? item.result : JSON.stringify(item.result, null, 2);
17443
17525
  }
17526
+ var JS_STRING_LITERAL_RE = /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/g;
17527
+ var JS_SIMPLE_ESCAPES = {
17528
+ n: "\n",
17529
+ r: "\r",
17530
+ t: " ",
17531
+ b: "\b",
17532
+ f: "\f",
17533
+ v: "\v",
17534
+ "0": "\0"
17535
+ };
17536
+ function decodeJsStringLiteral(literal2) {
17537
+ if (literal2.startsWith('"')) {
17538
+ try {
17539
+ return JSON.parse(literal2);
17540
+ } catch {
17541
+ }
17542
+ }
17543
+ return literal2.slice(1, -1).replace(
17544
+ /\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[\s\S])/g,
17545
+ (_full, esc2) => {
17546
+ if (esc2.startsWith("u{")) return String.fromCodePoint(parseInt(esc2.slice(2, -1), 16));
17547
+ if (esc2.startsWith("u") || esc2.startsWith("x")) return String.fromCharCode(parseInt(esc2.slice(1), 16));
17548
+ return JS_SIMPLE_ESCAPES[esc2] ?? esc2;
17549
+ }
17550
+ );
17551
+ }
17552
+ function extractPatchLiteral(script) {
17553
+ JS_STRING_LITERAL_RE.lastIndex = 0;
17554
+ let m;
17555
+ while (m = JS_STRING_LITERAL_RE.exec(script)) {
17556
+ const value = decodeJsStringLiteral(m[0]);
17557
+ if (value.includes("*** Begin Patch")) return value;
17558
+ }
17559
+ return null;
17560
+ }
17561
+ function extractExecArgs(script) {
17562
+ const call = script.indexOf("tools.exec_command(");
17563
+ if (call === -1) return null;
17564
+ const open = script.indexOf("{", call);
17565
+ if (open === -1) return null;
17566
+ let depth = 0;
17567
+ let quote = "";
17568
+ for (let i = open; i < script.length; i++) {
17569
+ const ch = script[i];
17570
+ if (quote) {
17571
+ if (ch === "\\") i++;
17572
+ else if (ch === quote) quote = "";
17573
+ continue;
17574
+ }
17575
+ if (ch === '"' || ch === "'" || ch === "`") {
17576
+ quote = ch;
17577
+ continue;
17578
+ }
17579
+ if (ch === "{") depth++;
17580
+ else if (ch === "}" && --depth === 0) {
17581
+ try {
17582
+ const parsed = JSON.parse(script.slice(open, i + 1));
17583
+ return parsed && typeof parsed === "object" ? parsed : null;
17584
+ } catch {
17585
+ return null;
17586
+ }
17587
+ }
17588
+ }
17589
+ return null;
17590
+ }
17591
+ function parseCodexExecScript(script) {
17592
+ const patch = extractPatchLiteral(script);
17593
+ if (patch) return { kind: "patch", patch };
17594
+ const args = extractExecArgs(script);
17595
+ if (args) {
17596
+ return { kind: "exec", command: typeof args.cmd === "string" ? args.cmd : "", args };
17597
+ }
17598
+ return { kind: "unknown" };
17599
+ }
17444
17600
  var CODEX_AGENT_DONE = /* @__PURE__ */ new Set(["completed", "errored", "interrupted", "not_found", "failed"]);
17445
17601
  function parseCodexSpawnInput(args, extra) {
17446
- const prompt = typeof args.message === "string" ? args.message : "";
17447
- const agentType = typeof args.agent_type === "string" ? args.agent_type : void 0;
17602
+ const message = typeof args.message === "string" ? args.message : "";
17603
+ const encrypted = isCodexEncryptedPayload(message);
17604
+ const prompt = encrypted ? "" : message;
17605
+ const agentType = typeof args.agent_type === "string" ? args.agent_type : typeof args.task_name === "string" ? codexAgentPathName(args.task_name) : void 0;
17448
17606
  return {
17449
17607
  ...agentType ? { subagent_type: agentType } : {},
17450
17608
  description: codexSpawnDescription(extra?.nickname, agentType, prompt),
17451
- prompt,
17609
+ ...encrypted ? { message_encrypted: true } : { prompt },
17452
17610
  ...extra?.agentId ? { agent_id: extra.agentId } : {}
17453
17611
  };
17454
17612
  }
17613
+ var CODEX_FERNET_TOKEN_RE = /^gAAAAA[A-Za-z0-9_-]{32,}={0,2}$/;
17614
+ function isCodexEncryptedPayload(text) {
17615
+ return CODEX_FERNET_TOKEN_RE.test(text.trim());
17616
+ }
17617
+ function codexAgentPathName(agentPath) {
17618
+ const segments = agentPath.split("/").filter(Boolean);
17619
+ return segments[segments.length - 1] || agentPath;
17620
+ }
17621
+ function parseCodexSubAgentActivity(payload) {
17622
+ if (!payload || payload.type !== CODEX_SUB_AGENT_ACTIVITY_TYPE) return null;
17623
+ const { event_id: callId, agent_thread_id: agentThreadId } = payload;
17624
+ if (typeof callId !== "string" || !callId) return null;
17625
+ if (typeof agentThreadId !== "string" || !agentThreadId) return null;
17626
+ return {
17627
+ callId,
17628
+ agentThreadId,
17629
+ ...typeof payload.agent_path === "string" ? { agentPath: payload.agent_path } : {},
17630
+ ...typeof payload.kind === "string" ? { kind: payload.kind } : {}
17631
+ };
17632
+ }
17455
17633
  function codexSpawnDescription(nickname, agentType, prompt) {
17456
17634
  if (nickname) return agentType ? `${nickname} (${agentType})` : nickname;
17457
17635
  const firstLine = prompt.split("\n", 1)[0]?.trim() || "";
@@ -17533,8 +17711,12 @@ function parseCallLines(text, exec2, patch, spawns, mcps) {
17533
17711
  } catch {
17534
17712
  }
17535
17713
  exec2.push({ callId: p.call_id, cmd });
17536
- } else if (p.type === "custom_tool_call" && p.name === "apply_patch") {
17714
+ } else if (p.type === "custom_tool_call" && p.name === CODEX_PATCH_FN_NAME) {
17537
17715
  patch.push({ callId: p.call_id, files: parsePatchFiles(p.input || "") });
17716
+ } else if (p.type === "custom_tool_call" && p.name === CODEX_EXEC_SCRIPT_FN_NAME) {
17717
+ const script = parseCodexExecScript(p.input || "");
17718
+ if (script.kind === "exec") exec2.push({ callId: p.call_id, cmd: script.command });
17719
+ else if (script.kind === "patch") patch.push({ callId: p.call_id, files: parsePatchFiles(script.patch) });
17538
17720
  } else if (p.type === "function_call" && p.name && p.namespace?.startsWith(CODEX_MCP_NAMESPACE_PREFIX)) {
17539
17721
  mcps.push({
17540
17722
  callId: p.call_id,
@@ -17557,6 +17739,15 @@ function parseCallLines(text, exec2, patch, spawns, mcps) {
17557
17739
  target.nickname = parsed.nickname;
17558
17740
  }
17559
17741
  }
17742
+ } else {
17743
+ const activity = parseCodexSubAgentActivity(p);
17744
+ if (activity) {
17745
+ const target = spawns.find((s) => s.callId === activity.callId && !s.agentId);
17746
+ if (target) {
17747
+ target.agentId = activity.agentThreadId;
17748
+ if (activity.agentPath) target.agentPath = activity.agentPath;
17749
+ }
17750
+ }
17560
17751
  }
17561
17752
  }
17562
17753
  }
@@ -17661,7 +17852,9 @@ function createCodexEventAdapter(ctx) {
17661
17852
  const { sessionId } = ctx;
17662
17853
  let terminated = false;
17663
17854
  let failure = null;
17855
+ let progressOutputTokens = 0;
17664
17856
  const pendingToolCalls = /* @__PURE__ */ new Map();
17857
+ const progressItems = /* @__PURE__ */ new Set();
17665
17858
  let rolloutPath = null;
17666
17859
  let execBase = 0;
17667
17860
  let patchBase = 0;
@@ -17832,6 +18025,14 @@ function createCodexEventAdapter(ctx) {
17832
18025
  }
17833
18026
  emitAgentReports(item);
17834
18027
  };
18028
+ const emitOutputProgress = (text, key) => {
18029
+ if (key) {
18030
+ if (progressItems.has(key)) return;
18031
+ progressItems.add(key);
18032
+ }
18033
+ progressOutputTokens += estimateOutputUnits(text);
18034
+ ctx.emit({ type: "usage_update", output_tokens: progressOutputTokens });
18035
+ };
17835
18036
  const handle = (event) => {
17836
18037
  if (terminated) return;
17837
18038
  switch (event.type) {
@@ -17847,12 +18048,15 @@ function createCodexEventAdapter(ctx) {
17847
18048
  const item = event.item;
17848
18049
  if (!item) break;
17849
18050
  if (item.type === "agent_message" && item.text) {
18051
+ emitOutputProgress(item.text);
17850
18052
  ctx.emit({ type: "assistant", message: { content: [{ type: "text", text: item.text }] } });
17851
18053
  }
17852
18054
  if (item.type === "error" && (item.message || item.text)) {
18055
+ emitOutputProgress(item.message || item.text || "");
17853
18056
  ctx.emit({ type: "error", error: item.message || item.text });
17854
18057
  }
17855
18058
  if (item.type === "reasoning" && item.text) {
18059
+ emitOutputProgress(item.text);
17856
18060
  ctx.emit({
17857
18061
  type: "assistant",
17858
18062
  message: { content: [{ type: "text", text: `<details><summary>Reasoning</summary>
@@ -17865,11 +18069,13 @@ ${item.text}
17865
18069
  if (item.type === "command_execution") {
17866
18070
  const toolUseId = resolveExecToolUseId(item);
17867
18071
  if (!pendingToolCalls.has(toolUseId)) {
18072
+ emitOutputProgress(item.command || CODEX_TOOL_NAMES.bash, `command-start:${toolUseId}`);
17868
18073
  ctx.emit({
17869
18074
  type: "assistant",
17870
18075
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command || "" } }] }
17871
18076
  });
17872
18077
  }
18078
+ emitOutputProgress(item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`, `command-result:${toolUseId}`);
17873
18079
  ctx.emit({
17874
18080
  type: "user",
17875
18081
  message: { content: [{ tool_use_id: toolUseId, content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})` }] }
@@ -17877,17 +18083,28 @@ ${item.text}
17877
18083
  pendingToolCalls.delete(toolUseId);
17878
18084
  }
17879
18085
  if (item.type === "collab_tool_call") handleCollabItem(item);
17880
- if (item.type === "mcp_tool_call") emitMcpCall(item, true);
17881
- if (item.type === "web_search") emitWebSearch(item);
17882
- if (item.type === "todo_list") emitTodoList(item);
18086
+ if (item.type === "mcp_tool_call") {
18087
+ emitOutputProgress(`${item.server || "mcp"} ${item.tool || ""} ${JSON.stringify(item.result ?? item.error ?? "")}`);
18088
+ emitMcpCall(item, true);
18089
+ }
18090
+ if (item.type === "web_search") {
18091
+ emitOutputProgress(JSON.stringify(item.action || item.query || "web_search"));
18092
+ emitWebSearch(item);
18093
+ }
18094
+ if (item.type === "todo_list") {
18095
+ emitOutputProgress(JSON.stringify(item.items || []));
18096
+ emitTodoList(item);
18097
+ }
17883
18098
  if (item.type === "file_change") {
17884
18099
  const toolUseId = resolvePatchToolUseId(item);
17885
18100
  if (!pendingToolCalls.has(toolUseId)) {
18101
+ emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
17886
18102
  ctx.emit({
17887
18103
  type: "assistant",
17888
18104
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
17889
18105
  });
17890
18106
  }
18107
+ emitOutputProgress(patchResultText(item), `patch-result:${toolUseId}`);
17891
18108
  ctx.emit({
17892
18109
  type: "user",
17893
18110
  message: { content: [{ tool_use_id: toolUseId, content: patchResultText(item) }] }
@@ -17902,6 +18119,7 @@ ${item.text}
17902
18119
  if (item?.type === "command_execution" && item.command) {
17903
18120
  const toolUseId = resolveExecToolUseId(item);
17904
18121
  pendingToolCalls.set(toolUseId, toolUseId);
18122
+ emitOutputProgress(item.command, `command-start:${toolUseId}`);
17905
18123
  ctx.emit({
17906
18124
  type: "assistant",
17907
18125
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command } }] }
@@ -17910,6 +18128,7 @@ ${item.text}
17910
18128
  if (item?.type === "file_change" && (item.changes?.length ?? 0) > 0) {
17911
18129
  const toolUseId = resolvePatchToolUseId(item);
17912
18130
  pendingToolCalls.set(toolUseId, toolUseId);
18131
+ emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
17913
18132
  ctx.emit({
17914
18133
  type: "assistant",
17915
18134
  message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
@@ -17967,14 +18186,30 @@ function codexSdkInput(prompt, imageFiles) {
17967
18186
  ];
17968
18187
  }
17969
18188
  function codexThreadOptions(ctx) {
18189
+ const effort = resolveCodexReasoningEffort(ctx.params.codexReasoningEffort);
17970
18190
  return {
17971
18191
  ...ctx.params.model ? { model: ctx.params.model } : {},
18192
+ ...effort ? { modelReasoningEffort: effort } : {},
17972
18193
  ...ctx.cwd ? { workingDirectory: ctx.cwd } : {},
17973
18194
  sandboxMode: "danger-full-access",
17974
18195
  approvalPolicy: "never",
17975
18196
  skipGitRepoCheck: true
17976
18197
  };
17977
18198
  }
18199
+ function resolveCodexReasoningEffort(value) {
18200
+ switch (value) {
18201
+ case "minimal":
18202
+ case "low":
18203
+ case "medium":
18204
+ case "high":
18205
+ case "xhigh":
18206
+ case "max":
18207
+ case "ultra":
18208
+ return value;
18209
+ default:
18210
+ return void 0;
18211
+ }
18212
+ }
17978
18213
  async function runCodexSdk(ctx) {
17979
18214
  const imageFiles = ctx.images && ctx.images.length > 0 ? writeImagesToTemp(ctx.images) : [];
17980
18215
  const adapter = createCodexEventAdapter(ctx);
@@ -18110,8 +18345,9 @@ var en_default2 = {
18110
18345
  searchPlaceholder: "Search HTML apps...",
18111
18346
  pathLabel: "Absolute path to the .html file",
18112
18347
  invalid: "Invalid",
18348
+ builtin: "Built-in",
18113
18349
  noDescription: "No description",
18114
- emptyNoApps: "No HTML apps yet.",
18350
+ emptyNoApps: "No custom HTML apps yet.",
18115
18351
  emptyCreatePrefix: "Use",
18116
18352
  emptyCreateSuffix: "in chat to create a new app.",
18117
18353
  addExisting: "add an existing HTML file",
@@ -18158,7 +18394,6 @@ var en_default2 = {
18158
18394
  unstagedNFiles: "Unstaged {{count}} files",
18159
18395
  discardFailed: "Discard failed",
18160
18396
  discardedNFiles: "Discarded changes in {{count}} files",
18161
- stagedAllFiles: "All files staged",
18162
18397
  deletedFile: "File deleted",
18163
18398
  discardedChanges: "Changes discarded",
18164
18399
  pastedFile: "Pasted: {{name}}",
@@ -18500,10 +18735,11 @@ var en_default2 = {
18500
18735
  startConversation: "Start a conversation",
18501
18736
  loadMoreHistory: "Load more...",
18502
18737
  scrollUpForMore: "\u2191 Scroll up to load more history",
18503
- claudeThinking: "Claude is thinking...",
18504
18738
  jumpToStart: "Jump to start",
18505
18739
  jumpToLatest: "Jump to latest",
18506
18740
  copyMessage: "Copy message",
18741
+ copyImage: "Copy image",
18742
+ copiedImage: "Image copied",
18507
18743
  forkSession: "Fork session from here",
18508
18744
  excerptTurn: "Excerpt this turn to a new session",
18509
18745
  imageN: "Image {{index}}",
@@ -18528,7 +18764,6 @@ var en_default2 = {
18528
18764
  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)",
18529
18765
  placeholderDisabled: "Generating... you can keep typing (ESC to cancel; if stuck, cancel and resend 'continue')",
18530
18766
  imageSizeLimit: "Image size exceeds limit (max 5MB), current: {{size}}MB",
18531
- stageAll: "Stage all files (git add -A)",
18532
18767
  viewGitChanges: "View Git changes",
18533
18768
  viewAllComments: "View all comments",
18534
18769
  userMessages: "User messages",
@@ -19313,8 +19548,9 @@ var zh_default = {
19313
19548
  searchPlaceholder: "\u641C\u7D22 HTML \u5E94\u7528...",
19314
19549
  pathLabel: ".html \u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84",
19315
19550
  invalid: "\u65E0\u6548",
19551
+ builtin: "\u5185\u7F6E",
19316
19552
  noDescription: "\u65E0\u63CF\u8FF0",
19317
- emptyNoApps: "\u8FD8\u6CA1\u6709 HTML \u5E94\u7528\u3002",
19553
+ emptyNoApps: "\u8FD8\u6CA1\u6709\u81EA\u5B9A\u4E49 HTML \u5E94\u7528\u3002",
19318
19554
  emptyCreatePrefix: "\u5728\u804A\u5929\u91CC\u4F7F\u7528",
19319
19555
  emptyCreateSuffix: "\u521B\u5EFA\u65B0\u5E94\u7528\u3002",
19320
19556
  addExisting: "\u6DFB\u52A0\u5DF2\u6709 HTML \u6587\u4EF6",
@@ -19361,7 +19597,6 @@ var zh_default = {
19361
19597
  unstagedNFiles: "\u5DF2\u53D6\u6D88\u6682\u5B58 {{count}} \u4E2A\u6587\u4EF6",
19362
19598
  discardFailed: "\u653E\u5F03\u53D8\u66F4\u5931\u8D25",
19363
19599
  discardedNFiles: "\u5DF2\u653E\u5F03 {{count}} \u4E2A\u6587\u4EF6\u7684\u53D8\u66F4",
19364
- stagedAllFiles: "\u5DF2\u6682\u5B58\u6240\u6709\u6587\u4EF6",
19365
19600
  deletedFile: "\u5DF2\u5220\u9664\u6587\u4EF6",
19366
19601
  discardedChanges: "\u5DF2\u653E\u5F03\u53D8\u66F4",
19367
19602
  pastedFile: "\u5DF2\u7C98\u8D34: {{name}}",
@@ -19703,10 +19938,11 @@ var zh_default = {
19703
19938
  startConversation: "\u5F00\u59CB\u5BF9\u8BDD\u5427",
19704
19939
  loadMoreHistory: "\u52A0\u8F7D\u66F4\u591A...",
19705
19940
  scrollUpForMore: "\u2191 \u5411\u4E0A\u6EDA\u52A8\u52A0\u8F7D\u66F4\u591A\u5386\u53F2",
19706
- claudeThinking: "Claude \u6B63\u5728\u601D\u8003...",
19707
19941
  jumpToStart: "\u8DF3\u8F6C\u5230\u5F00\u59CB",
19708
19942
  jumpToLatest: "\u8DF3\u8F6C\u5230\u6700\u65B0",
19709
19943
  copyMessage: "\u590D\u5236\u6D88\u606F",
19944
+ copyImage: "\u590D\u5236\u56FE\u7247",
19945
+ copiedImage: "\u5DF2\u590D\u5236\u56FE\u7247",
19710
19946
  forkSession: "\u4ECE\u6B64\u5904\u5206\u53C9\u4F1A\u8BDD",
19711
19947
  excerptTurn: "\u6458\u5F55\u672C\u8F6E\u5230\u65B0\u4F1A\u8BDD",
19712
19948
  imageN: "\u56FE\u7247 {{index}}",
@@ -19731,7 +19967,6 @@ var zh_default = {
19731
19967
  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",
19732
19968
  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)",
19733
19969
  imageSizeLimit: "\u56FE\u7247\u5927\u5C0F\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u5927 5MB\uFF09\uFF0C\u5F53\u524D\u5927\u5C0F: {{size}}MB",
19734
- stageAll: "\u6682\u5B58\u6240\u6709\u6587\u4EF6 (git add -A)",
19735
19970
  viewGitChanges: "\u67E5\u770B Git \u53D8\u66F4",
19736
19971
  viewAllComments: "\u67E5\u770B\u6240\u6709\u8BC4\u8BBA",
19737
19972
  userMessages: "\u7528\u6237\u6D88\u606F\u5217\u8868",
@@ -20517,8 +20752,7 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20517
20752
  if (!spec) {
20518
20753
  throw new Error(`no engine spec for ${engine}`);
20519
20754
  }
20520
- const builtinLoop = isBuiltinLoopSession(engine, task);
20521
- const noHistory = await readSessionNoHistory(task, engine, builtinLoop);
20755
+ const noHistory = await readSessionNoHistory(task, engine);
20522
20756
  const outcome = await dispatchChat(spec, {
20523
20757
  prompt: buildTaskPrompt(task),
20524
20758
  // Omit sessionId to start a brand-new session when the resume target is gone;
@@ -20527,7 +20761,6 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20527
20761
  cwd: task.cwd,
20528
20762
  engine,
20529
20763
  ...task.model && { model: task.model },
20530
- ...builtinLoop && { mode: "builtin" },
20531
20764
  ...noHistory && { noHistory: true }
20532
20765
  });
20533
20766
  if (!outcome.ok) {
@@ -20562,29 +20795,19 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
20562
20795
  })
20563
20796
  });
20564
20797
  var BUILTIN_LOOP_PATHS = {
20798
+ ollama: getOllamaSessionPath,
20565
20799
  deepseek: getDeepseekBuiltinSessionPath,
20566
20800
  kimi: getKimiBuiltinSessionPath,
20567
20801
  glm: getGlmBuiltinSessionPath
20568
20802
  };
20569
- var SDK_STORE_PATHS = {
20570
- deepseek: getDeepseekSessionPath,
20571
- kimi: getKimiSessionPath,
20572
- glm: getGlmSessionPath
20573
- };
20574
- function isBuiltinLoopSession(engine, task) {
20575
- const resolve = BUILTIN_LOOP_PATHS[engine];
20576
- return !!resolve && existsSync3(resolve(task.cwd, task.sessionId));
20577
- }
20578
- async function readSessionNoHistory(task, engine, builtinLoop) {
20579
- if (engine !== "ollama" && !builtinLoop && engine !== "claude" && engine !== "codex") return false;
20803
+ async function readSessionNoHistory(task, engine) {
20804
+ if (!BUILTIN_LOOP_PATHS[engine] && engine !== "claude" && engine !== "codex") return false;
20580
20805
  const state = await readJsonFile(getSessionFilePath(task.cwd), {});
20581
20806
  return state.noHistories?.[task.sessionId] === true;
20582
20807
  }
20583
20808
  function sessionPathFor(engine, task) {
20584
- if (engine === "ollama") return getOllamaSessionPath(task.cwd, task.sessionId);
20585
- if (isBuiltinLoopSession(engine, task)) return BUILTIN_LOOP_PATHS[engine](task.cwd, task.sessionId);
20586
- const sdkStorePath = SDK_STORE_PATHS[engine];
20587
- if (sdkStorePath) return sdkStorePath(task.cwd, task.sessionId);
20809
+ const builtinPath = BUILTIN_LOOP_PATHS[engine];
20810
+ if (builtinPath) return builtinPath(task.cwd, task.sessionId);
20588
20811
  if (engine === "codex") return findCodexSessionPath(task.sessionId);
20589
20812
  return getClaudeSessionPath(task.cwd, task.sessionId);
20590
20813
  }