@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.
- package/.next-prod/BUILD_ID +1 -1
- package/.next-prod/app-path-routes-manifest.json +1 -1
- package/.next-prod/build-manifest.json +2 -2
- package/.next-prod/cockpit-build.json +1 -1
- package/.next-prod/prerender-manifest.json +4 -4
- package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
- package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_global-error.html +1 -1
- package/.next-prod/server/app/_global-error.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found/page.js +1 -1
- package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
- package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_not-found.html +2 -2
- package/.next-prod/server/app/_not-found.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/codex/route.js +1 -1
- package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/deepseek/route.js +1 -1
- package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/glm/route.js +1 -1
- package/.next-prod/server/app/api/chat/glm/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/kimi/route.js +1 -1
- package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/route.js +1 -1
- package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/stop/route.js +1 -1
- package/.next-prod/server/app/api/chat/stop/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/deepseek/balance/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/deepseek/credentials/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/deepseek/models/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/current-branch/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/glm/credentials/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/glm/models/route.js +1 -1
- package/.next-prod/server/app/api/glm/models/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/glm/usage/route.js +1 -1
- package/.next-prod/server/app/api/glm/usage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/health/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/html-apps/[id]/route.js +1 -1
- package/.next-prod/server/app/api/html-apps/[id]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/html-apps/route.js +1 -1
- package/.next-prod/server/app/api/html-apps/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/kimi/credentials/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/kimi/models/route.js +1 -1
- package/.next-prod/server/app/api/kimi/models/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/kimi/usage/route.js +1 -1
- package/.next-prod/server/app/api/kimi/usage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/ollama/config/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/project-state/route.js +1 -1
- package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/prompts/config/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/push/public-key/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/push/subscribe/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/push/unsubscribe/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/scheduled-tasks/route.js +2 -2
- package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session/[sessionId]/fork/route.js +1 -1
- package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session-by-path/route.js +1 -1
- package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js +1 -1
- package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/projects/route.js +1 -1
- package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/snapshots/diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/snapshots/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
- package/.next-prod/server/app/apps/[...path]/route.js +1 -1
- package/.next-prod/server/app/apps/[...path]/route.js.nft.json +1 -1
- package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next-prod/server/app/m/page.js +1 -1
- package/.next-prod/server/app/m/page.js.nft.json +1 -1
- package/.next-prod/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next-prod/server/app/page.js +1 -1
- package/.next-prod/server/app/page.js.nft.json +1 -1
- package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/project/page.js +1 -1
- package/.next-prod/server/app/project/page.js.nft.json +1 -1
- package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/review/[id]/page.js +1 -1
- package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
- package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app-paths-manifest.json +1 -1
- package/.next-prod/server/chunks/3286.js +406 -270
- package/.next-prod/server/chunks/{9004.js → 3641.js} +2 -2
- package/.next-prod/server/chunks/516.js +1 -0
- package/.next-prod/server/chunks/5750.js +16 -10
- package/.next-prod/server/chunks/6495.js +1 -1
- package/.next-prod/server/chunks/6548.js +1 -1
- package/.next-prod/server/chunks/816.js +2 -0
- package/.next-prod/server/chunks/841.js +12 -10
- package/.next-prod/server/chunks/9352.js +4 -4
- package/.next-prod/server/chunks/9830.js +1 -1
- package/.next-prod/server/middleware-build-manifest.js +1 -1
- package/.next-prod/server/pages/404.html +2 -2
- package/.next-prod/server/pages/500.html +1 -1
- package/.next-prod/server/server-reference-manifest.json +1 -1
- package/.next-prod/static/chunks/1157-164bcf046a0a9b15.js +32 -0
- package/.next-prod/static/chunks/1372-d16d68b1b03a345a.js +15 -0
- package/.next-prod/static/chunks/{2653-3f8bfa49727caa72.js → 4333-9e6709e96d3ec973.js} +7 -7
- package/.next-prod/static/chunks/{225-75b2e22f496b56a1.js → 6160-958532439d324185.js} +2 -2
- package/.next-prod/static/chunks/6784-09dffdb0e8ca7fb3.js +381 -0
- package/.next-prod/static/chunks/app/{layout-fef0d44c94fcb415.js → layout-ab52fc1a83af968b.js} +1 -1
- package/.next-prod/static/chunks/app/m/page-7dcf56a65528216f.js +1 -0
- package/.next-prod/static/chunks/app/{page-824be00a023f34d7.js → page-5de8b6584e328a6f.js} +1 -1
- package/.next-prod/static/chunks/app/project/{page-824be00a023f34d7.js → page-5de8b6584e328a6f.js} +1 -1
- package/.next-prod/static/css/83c134c954630942.css +1 -0
- package/.next-prod/trace +14 -14
- package/.next-prod/trace-build +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/apps/devkit/app.jsx +730 -0
- package/apps/devkit/index.html +178 -0
- package/apps/devkit/md5.js +98 -0
- package/dist/{chunk-L5TWVHYB.mjs → chunk-B5LXMESP.mjs} +19 -9
- package/dist/{chunk-YEZ3U3F2.mjs → chunk-CLPJYIFC.mjs} +1 -1
- package/dist/{chunk-RSRPFWNL.mjs → chunk-TU3GL2HK.mjs} +1 -27
- package/dist/httpApi.mjs +2 -2
- package/dist/scheduledTasks.mjs +685 -462
- package/dist/{server-UZPDELUK.mjs → server-2UJDVZ32.mjs} +2 -2
- package/dist/wsServer.mjs +5 -4
- package/package.json +1 -1
- package/public/agent-icons/claude.svg +1 -0
- package/public/agent-icons/codex.svg +1 -0
- package/public/agent-icons/deepseek.svg +1 -0
- package/public/agent-icons/glm.svg +1 -0
- package/public/agent-icons/kimi.svg +1 -0
- package/public/agent-icons/ollama.svg +1 -0
- package/.next-prod/server/chunks/151.js +0 -1
- package/.next-prod/static/chunks/1372-d94b8f9ed5cd2713.js +0 -15
- package/.next-prod/static/chunks/6784-ec12c61b77c64dc0.js +0 -381
- package/.next-prod/static/chunks/7482-3ffe7da02d3e7f45.js +0 -26
- package/.next-prod/static/chunks/app/m/page-aff83d3f5f4047c5.js +0 -1
- package/.next-prod/static/css/a82580b2376d7cae.css +0 -1
- /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_buildManifest.js +0 -0
- /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_ssgManifest.js +0 -0
package/dist/scheduledTasks.mjs
CHANGED
|
@@ -14,18 +14,15 @@ import {
|
|
|
14
14
|
stashCodexRollout,
|
|
15
15
|
stashTranscript,
|
|
16
16
|
updateGlobalState
|
|
17
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
1192
|
-
argument-hint: "\u63CF\u8FF0\u4F60\u60F3\u8981\u7684
|
|
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
|
|
1200
|
+
# \u751F\u6210 Cockpit \u5E94\u7528\uFF08\u672C\u5730 React \u9875\u9762\uFF09
|
|
1196
1201
|
|
|
1197
|
-
\u4E00\u4E2A\
|
|
1198
|
-
\
|
|
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
|
-
|
|
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
|
|
1209
|
-
\
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
\
|
|
1217
|
-
|
|
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
|
-
|
|
1232
|
-
|
|
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
|
-
## \
|
|
1226
|
+
## \u4E8C\u3001\u9AA8\u67B6\uFF1A\u4E09\u4E2A\u6587\u4EF6\uFF0C\u7167\u6284
|
|
1252
1227
|
|
|
1253
|
-
\u5916\u58F3\u56FA\u5B9A\u8FD9\u6837\u5199\uFF0C\
|
|
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
|
-
|
|
1257
|
-
<
|
|
1258
|
-
<
|
|
1259
|
-
<
|
|
1260
|
-
<
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
-
|
|
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 \
|
|
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 [
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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
|
-
|
|
1300
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
1301
|
+
\`\`\`
|
|
1296
1302
|
|
|
1297
|
-
\
|
|
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
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1314
|
+
const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
|
|
1309
1315
|
|
|
1310
|
-
|
|
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
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
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
|
-
\
|
|
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
|
-
|
|
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
|
-
|
|
1354
|
+
\u9875\u9762\u52A0\u8F7D\u65F6 SDK \u5DF2\u5C31\u7EEA\uFF0C\u65E0\u9700\u5F15\u5165\u4EFB\u4F55\u5E93\uFF1A
|
|
1345
1355
|
|
|
1346
|
-
\
|
|
1347
|
-
\
|
|
1348
|
-
\
|
|
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
|
-
|
|
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
|
-
\
|
|
1354
|
-
\
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
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
|
-
|
|
1385
|
+
## \u56DB\u3001\u672C\u5730\u6570\u636E\uFF1A\u4E09\u7C7B\u76EE\u5F55
|
|
1362
1386
|
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1413
|
+
\`\`\`bash
|
|
1414
|
+
curl -fsS "{{BASE_URL}}/html-lib/theme.css"
|
|
1415
|
+
\`\`\`
|
|
1394
1416
|
|
|
1395
|
-
- \`
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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
|
-
|
|
1432
|
+
# Build a Cockpit app (a local React page)
|
|
1402
1433
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
-
|
|
1438
|
+
Zero build: React / Babel load from the same-origin \`/html-lib\` \u2014 no bundler, no deps, works offline.
|
|
1415
1439
|
|
|
1416
|
-
|
|
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
|
-
|
|
1442
|
+
**Don't write files up front.** Three steps:
|
|
1425
1443
|
|
|
1426
|
-
|
|
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
|
-
|
|
1430
|
-
<
|
|
1431
|
-
<
|
|
1432
|
-
<
|
|
1433
|
-
<
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
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
|
-
|
|
1446
|
-
|
|
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
|
-
|
|
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 [
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1539
|
+
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
1540
|
+
\`\`\`
|
|
1471
1541
|
|
|
1472
|
-
|
|
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
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1553
|
+
const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
|
|
1487
1554
|
|
|
1488
|
-
|
|
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
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1595
|
+
The SDK is ready on load \u2014 no library to import:
|
|
1523
1596
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
1530
|
-
-
|
|
1661
|
+
\`\`\`bash
|
|
1662
|
+
curl -fsS "{{BASE_URL}}/html-lib/theme.css"
|
|
1663
|
+
\`\`\`
|
|
1531
1664
|
|
|
1532
|
-
|
|
1533
|
-
|
|
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,
|
|
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 ? { ...
|
|
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
|
|
2701
|
+
const runCtx = withClaudePromptEffort(ctx);
|
|
2702
|
+
const independent = runCtx.params.noHistory === true && !!runCtx.sessionId;
|
|
2522
2703
|
if (!independent) {
|
|
2523
|
-
await runSdkLoop(
|
|
2704
|
+
await runSdkLoop(runCtx, buildClaudeOptions(runCtx, false));
|
|
2524
2705
|
return;
|
|
2525
2706
|
}
|
|
2526
|
-
const sessionPath = getClaudeSessionPath(
|
|
2707
|
+
const sessionPath = getClaudeSessionPath(runCtx.cwd, runCtx.sessionId);
|
|
2527
2708
|
const stashed = stashTranscript(sessionPath);
|
|
2528
2709
|
try {
|
|
2529
|
-
await runSdkLoop(
|
|
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
|
|
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
|
|
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
|
|
17210
|
-
if (
|
|
17211
|
-
|
|
17212
|
-
|
|
17213
|
-
|
|
17214
|
-
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
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
|
-
//
|
|
17365
|
-
//
|
|
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
|
|
17447
|
-
const
|
|
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 ===
|
|
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")
|
|
17881
|
-
|
|
17882
|
-
|
|
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
|
|
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
|
-
|
|
20570
|
-
|
|
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
|
-
|
|
20585
|
-
if (
|
|
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
|
}
|