@surething/cockpit 1.0.259 → 1.0.260
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next-prod/BUILD_ID +1 -1
- package/.next-prod/app-path-routes-manifest.json +4 -4
- 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 +4 -4
- package/.next-prod/server/chunks/151.js +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/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 +728 -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 +648 -459
- 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/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 → 4Jgc-wu2w_CC8iS5G1QZf}/_buildManifest.js +0 -0
- /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → 4Jgc-wu2w_CC8iS5G1QZf}/_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
|
-
|
|
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
|
|
1215
1222
|
|
|
1216
|
-
\u5373\u4FBF\u7528\u6237\u5728 \`/html\` \u540E\u5DF2\u7ECF\u628A\u9700\u6C42\u548C\u76EE\u5F55\u90FD\u5199\u6E05\u695A\u4E86\uFF0C\u4E5F\u8981\u5148\u505C\u4E00\u8F6E\uFF0C\u7ED9\u51FA\u300C\u7406\u89E3 + \u76EE\u5F55 + \u4E00\u53E5
|
|
1217
|
-
\
|
|
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
|
|
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
|
|
1222
1225
|
|
|
1223
|
-
|
|
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
|
|
1230
|
-
|
|
1231
|
-
1. **\u6570\u636E\u53EA\u8D70 \`cockpit.bash\`**\uFF08\`curl\` \u53D6\u3001\`curl -X POST\` \u6539\u3001\u6216\u8BFB\u5199\u6587\u4EF6\uFF09\uFF0C**\u7EDD\u4E0D\u7528
|
|
1232
|
-
\`fetch(\u5916\u90E8URL)\`** \u2014\u2014 \u540C\u6E90\u6C99\u7BB1\u4F1A CORS \u62E6\u622A\u3002
|
|
1233
|
-
2. **\u4E24\u7C7B\u5931\u8D25\u90FD\u8981\u5904\u7406**\uFF08\u524D\u53F0\uFF09\uFF1A\`try/catch\` \u6293\u5230\u7684**\u629B\u5F02\u5E38 = spawn/\u57FA\u5EFA\u5931\u8D25**\uFF08\u574F\u8DEF\u5F84\u3001
|
|
1234
|
-
\u8FDE\u63A5\u65AD\uFF09\uFF1B\u6CA1\u629B\u4F46 **\`exitCode !== 0\` = \u547D\u4EE4\u8DD1\u4E86\u4F46\u5931\u8D25**\uFF0C\u5C55\u793A \`stderr\` \u6216 stdout \u91CC\u7684\u9519\u8BEF
|
|
1235
|
-
\u4F53\uFF0C\u522B\u53EA\u5199"\u5931\u8D25"\u3002\u540E\u53F0\u5BF9\u5E94 \`onError\`\uFF08\u57FA\u5EFA\uFF09+ \`onExit\` \u975E\u96F6 \`code\`\uFF08\u547D\u4EE4\uFF09\u3002
|
|
1236
|
-
3. **\`command\` \u662F shell \u4E32**\uFF1A\u52A8\u6001\u503C/\u7528\u6237\u8F93\u5165\u62FC\u8FDB\u53BB\u524D\u5FC5\u987B\u6821\u9A8C\u6216\u8F6C\u4E49\uFF08shell \u5143\u5B57\u7B26\u4F1A\u6CE8\u5165\uFF09\uFF1B
|
|
1237
|
-
\u80FD\u7528\u56FA\u5B9A\u547D\u4EE4\u5C31\u522B\u88F8\u62FC\u3002
|
|
1238
|
-
4. **\u8D44\u6E90\u5F15\u7528**\uFF1A\u540C\u7EA7\u76F8\u5BF9\uFF08\`./app.jsx\`\u3001\`./style.css\`\u3001\u56FE\u7247\uFF09\u548C CDN \u7EDD\u5BF9 URL \u90FD\u80FD\u52A0\u8F7D\uFF1B\u6839
|
|
1239
|
-
\u76F8\u5BF9\uFF08\`/assets/x.css\`\uFF09\u4F1A 404\u3002
|
|
1226
|
+
## \u4E8C\u3001\u9AA8\u67B6\uFF1A\u4E09\u4E2A\u6587\u4EF6\uFF0C\u7167\u6284
|
|
1240
1227
|
|
|
1241
|
-
|
|
1228
|
+
\`index.html\` \u2014\u2014 \u5916\u58F3\u56FA\u5B9A\u8FD9\u6837\u5199\uFF0C\u522B\u81EA\u7531\u53D1\u6325\uFF1A
|
|
1242
1229
|
|
|
1243
1230
|
\`\`\`html
|
|
1244
|
-
|
|
1245
|
-
<
|
|
1246
|
-
<
|
|
1247
|
-
<meta
|
|
1248
|
-
<
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
<div id="root"></div>
|
|
1257
|
-
<script src="/html-lib/react.production.min.js"></script>
|
|
1258
|
-
<script src="/html-lib/react-dom.production.min.js"></script>
|
|
1259
|
-
<script src="/html-lib/babel.min.js"></script>
|
|
1260
|
-
<script>
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
</script>
|
|
1231
|
+
<!doctype html>
|
|
1232
|
+
<html>
|
|
1233
|
+
<head>
|
|
1234
|
+
<meta charset="utf-8">
|
|
1235
|
+
<title>\u9875\u9762\u6807\u9898</title>
|
|
1236
|
+
<meta name="cockpit-name" content="short-name"> <!-- /name \u77ED\u540D\uFF0C\u552F\u4E00\u3001\u53EA\u7528\u5B57\u6BCD\u6570\u5B57-_ -->
|
|
1237
|
+
<meta name="description" content="\u4E00\u53E5\u8BDD\u8BF4\u660E">
|
|
1238
|
+
<meta name="cockpit-icon" content="\u{1F50D}"> <!-- emoji \u6216\u56FE\u6807 url\uFF0C\u53EF\u9009 -->
|
|
1239
|
+
<meta name="cockpit-theme" content="auto"> <!-- \u4EAE/\u6697\u5207\u6362\uFF0C\u89C1\u7B2C\u4E94\u8282 -->
|
|
1240
|
+
<link rel="stylesheet" href="/html-lib/theme.css">
|
|
1241
|
+
</head>
|
|
1242
|
+
<body>
|
|
1243
|
+
<div id="root"></div>
|
|
1244
|
+
<script src="/html-lib/react.production.min.js"></script>
|
|
1245
|
+
<script src="/html-lib/react-dom.production.min.js"></script>
|
|
1246
|
+
<script src="/html-lib/babel.min.js"></script>
|
|
1247
|
+
<script>
|
|
1248
|
+
(async () => {
|
|
1249
|
+
const src = await (await fetch('./app.jsx')).text();
|
|
1250
|
+
// \u5FC5\u987B classic runtime\uFF1A\u9ED8\u8BA4 automatic \u4F1A\u6CE8\u5165 import react/jsx-runtime\uFF0C
|
|
1251
|
+
// \u4F5C\u4E3A\u666E\u901A\u811A\u672C\u6CE8\u5165\u4F1A\u62A5 "Cannot use import statement outside a module"
|
|
1252
|
+
const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
|
|
1253
|
+
const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
|
|
1254
|
+
})();
|
|
1255
|
+
</script>
|
|
1256
|
+
</body>
|
|
1257
|
+
</html>
|
|
1269
1258
|
\`\`\`
|
|
1270
1259
|
|
|
1271
|
-
|
|
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
|
+
\`\`\`
|
|
1528
1613
|
|
|
1529
|
-
|
|
1530
|
-
|
|
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:
|
|
1531
1660
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1661
|
+
\`\`\`bash
|
|
1662
|
+
curl -fsS "{{BASE_URL}}/html-lib/theme.css"
|
|
1663
|
+
\`\`\`
|
|
1664
|
+
|
|
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
|
|
|
@@ -17411,6 +17488,8 @@ var CODEX_TOOL_NAMES = {
|
|
|
17411
17488
|
var CODEX_MCP_NAMESPACE_PREFIX = "mcp__";
|
|
17412
17489
|
var codexMcpToolName = (server, tool2) => `${CODEX_MCP_NAMESPACE_PREFIX}${server}__${tool2}`;
|
|
17413
17490
|
var CODEX_SPAWN_FN_NAME = "spawn_agent";
|
|
17491
|
+
var CODEX_PATCH_FN_NAME = "apply_patch";
|
|
17492
|
+
var CODEX_EXEC_SCRIPT_FN_NAME = "exec";
|
|
17414
17493
|
function codexTodoInput(items) {
|
|
17415
17494
|
return {
|
|
17416
17495
|
todos: (items ?? []).flatMap(
|
|
@@ -17441,6 +17520,80 @@ function codexMcpResultText(item) {
|
|
|
17441
17520
|
if (item.result === null || item.result === void 0) return "";
|
|
17442
17521
|
return typeof item.result === "string" ? item.result : JSON.stringify(item.result, null, 2);
|
|
17443
17522
|
}
|
|
17523
|
+
var JS_STRING_LITERAL_RE = /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/g;
|
|
17524
|
+
var JS_SIMPLE_ESCAPES = {
|
|
17525
|
+
n: "\n",
|
|
17526
|
+
r: "\r",
|
|
17527
|
+
t: " ",
|
|
17528
|
+
b: "\b",
|
|
17529
|
+
f: "\f",
|
|
17530
|
+
v: "\v",
|
|
17531
|
+
"0": "\0"
|
|
17532
|
+
};
|
|
17533
|
+
function decodeJsStringLiteral(literal2) {
|
|
17534
|
+
if (literal2.startsWith('"')) {
|
|
17535
|
+
try {
|
|
17536
|
+
return JSON.parse(literal2);
|
|
17537
|
+
} catch {
|
|
17538
|
+
}
|
|
17539
|
+
}
|
|
17540
|
+
return literal2.slice(1, -1).replace(
|
|
17541
|
+
/\\(u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[\s\S])/g,
|
|
17542
|
+
(_full, esc2) => {
|
|
17543
|
+
if (esc2.startsWith("u{")) return String.fromCodePoint(parseInt(esc2.slice(2, -1), 16));
|
|
17544
|
+
if (esc2.startsWith("u") || esc2.startsWith("x")) return String.fromCharCode(parseInt(esc2.slice(1), 16));
|
|
17545
|
+
return JS_SIMPLE_ESCAPES[esc2] ?? esc2;
|
|
17546
|
+
}
|
|
17547
|
+
);
|
|
17548
|
+
}
|
|
17549
|
+
function extractPatchLiteral(script) {
|
|
17550
|
+
JS_STRING_LITERAL_RE.lastIndex = 0;
|
|
17551
|
+
let m;
|
|
17552
|
+
while (m = JS_STRING_LITERAL_RE.exec(script)) {
|
|
17553
|
+
const value = decodeJsStringLiteral(m[0]);
|
|
17554
|
+
if (value.includes("*** Begin Patch")) return value;
|
|
17555
|
+
}
|
|
17556
|
+
return null;
|
|
17557
|
+
}
|
|
17558
|
+
function extractExecArgs(script) {
|
|
17559
|
+
const call = script.indexOf("tools.exec_command(");
|
|
17560
|
+
if (call === -1) return null;
|
|
17561
|
+
const open = script.indexOf("{", call);
|
|
17562
|
+
if (open === -1) return null;
|
|
17563
|
+
let depth = 0;
|
|
17564
|
+
let quote = "";
|
|
17565
|
+
for (let i = open; i < script.length; i++) {
|
|
17566
|
+
const ch = script[i];
|
|
17567
|
+
if (quote) {
|
|
17568
|
+
if (ch === "\\") i++;
|
|
17569
|
+
else if (ch === quote) quote = "";
|
|
17570
|
+
continue;
|
|
17571
|
+
}
|
|
17572
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
17573
|
+
quote = ch;
|
|
17574
|
+
continue;
|
|
17575
|
+
}
|
|
17576
|
+
if (ch === "{") depth++;
|
|
17577
|
+
else if (ch === "}" && --depth === 0) {
|
|
17578
|
+
try {
|
|
17579
|
+
const parsed = JSON.parse(script.slice(open, i + 1));
|
|
17580
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
17581
|
+
} catch {
|
|
17582
|
+
return null;
|
|
17583
|
+
}
|
|
17584
|
+
}
|
|
17585
|
+
}
|
|
17586
|
+
return null;
|
|
17587
|
+
}
|
|
17588
|
+
function parseCodexExecScript(script) {
|
|
17589
|
+
const patch = extractPatchLiteral(script);
|
|
17590
|
+
if (patch) return { kind: "patch", patch };
|
|
17591
|
+
const args = extractExecArgs(script);
|
|
17592
|
+
if (args) {
|
|
17593
|
+
return { kind: "exec", command: typeof args.cmd === "string" ? args.cmd : "", args };
|
|
17594
|
+
}
|
|
17595
|
+
return { kind: "unknown" };
|
|
17596
|
+
}
|
|
17444
17597
|
var CODEX_AGENT_DONE = /* @__PURE__ */ new Set(["completed", "errored", "interrupted", "not_found", "failed"]);
|
|
17445
17598
|
function parseCodexSpawnInput(args, extra) {
|
|
17446
17599
|
const prompt = typeof args.message === "string" ? args.message : "";
|
|
@@ -17533,8 +17686,12 @@ function parseCallLines(text, exec2, patch, spawns, mcps) {
|
|
|
17533
17686
|
} catch {
|
|
17534
17687
|
}
|
|
17535
17688
|
exec2.push({ callId: p.call_id, cmd });
|
|
17536
|
-
} else if (p.type === "custom_tool_call" && p.name ===
|
|
17689
|
+
} else if (p.type === "custom_tool_call" && p.name === CODEX_PATCH_FN_NAME) {
|
|
17537
17690
|
patch.push({ callId: p.call_id, files: parsePatchFiles(p.input || "") });
|
|
17691
|
+
} else if (p.type === "custom_tool_call" && p.name === CODEX_EXEC_SCRIPT_FN_NAME) {
|
|
17692
|
+
const script = parseCodexExecScript(p.input || "");
|
|
17693
|
+
if (script.kind === "exec") exec2.push({ callId: p.call_id, cmd: script.command });
|
|
17694
|
+
else if (script.kind === "patch") patch.push({ callId: p.call_id, files: parsePatchFiles(script.patch) });
|
|
17538
17695
|
} else if (p.type === "function_call" && p.name && p.namespace?.startsWith(CODEX_MCP_NAMESPACE_PREFIX)) {
|
|
17539
17696
|
mcps.push({
|
|
17540
17697
|
callId: p.call_id,
|
|
@@ -17661,7 +17818,9 @@ function createCodexEventAdapter(ctx) {
|
|
|
17661
17818
|
const { sessionId } = ctx;
|
|
17662
17819
|
let terminated = false;
|
|
17663
17820
|
let failure = null;
|
|
17821
|
+
let progressOutputTokens = 0;
|
|
17664
17822
|
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
17823
|
+
const progressItems = /* @__PURE__ */ new Set();
|
|
17665
17824
|
let rolloutPath = null;
|
|
17666
17825
|
let execBase = 0;
|
|
17667
17826
|
let patchBase = 0;
|
|
@@ -17832,6 +17991,14 @@ function createCodexEventAdapter(ctx) {
|
|
|
17832
17991
|
}
|
|
17833
17992
|
emitAgentReports(item);
|
|
17834
17993
|
};
|
|
17994
|
+
const emitOutputProgress = (text, key) => {
|
|
17995
|
+
if (key) {
|
|
17996
|
+
if (progressItems.has(key)) return;
|
|
17997
|
+
progressItems.add(key);
|
|
17998
|
+
}
|
|
17999
|
+
progressOutputTokens += estimateOutputUnits(text);
|
|
18000
|
+
ctx.emit({ type: "usage_update", output_tokens: progressOutputTokens });
|
|
18001
|
+
};
|
|
17835
18002
|
const handle = (event) => {
|
|
17836
18003
|
if (terminated) return;
|
|
17837
18004
|
switch (event.type) {
|
|
@@ -17847,12 +18014,15 @@ function createCodexEventAdapter(ctx) {
|
|
|
17847
18014
|
const item = event.item;
|
|
17848
18015
|
if (!item) break;
|
|
17849
18016
|
if (item.type === "agent_message" && item.text) {
|
|
18017
|
+
emitOutputProgress(item.text);
|
|
17850
18018
|
ctx.emit({ type: "assistant", message: { content: [{ type: "text", text: item.text }] } });
|
|
17851
18019
|
}
|
|
17852
18020
|
if (item.type === "error" && (item.message || item.text)) {
|
|
18021
|
+
emitOutputProgress(item.message || item.text || "");
|
|
17853
18022
|
ctx.emit({ type: "error", error: item.message || item.text });
|
|
17854
18023
|
}
|
|
17855
18024
|
if (item.type === "reasoning" && item.text) {
|
|
18025
|
+
emitOutputProgress(item.text);
|
|
17856
18026
|
ctx.emit({
|
|
17857
18027
|
type: "assistant",
|
|
17858
18028
|
message: { content: [{ type: "text", text: `<details><summary>Reasoning</summary>
|
|
@@ -17865,11 +18035,13 @@ ${item.text}
|
|
|
17865
18035
|
if (item.type === "command_execution") {
|
|
17866
18036
|
const toolUseId = resolveExecToolUseId(item);
|
|
17867
18037
|
if (!pendingToolCalls.has(toolUseId)) {
|
|
18038
|
+
emitOutputProgress(item.command || CODEX_TOOL_NAMES.bash, `command-start:${toolUseId}`);
|
|
17868
18039
|
ctx.emit({
|
|
17869
18040
|
type: "assistant",
|
|
17870
18041
|
message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command || "" } }] }
|
|
17871
18042
|
});
|
|
17872
18043
|
}
|
|
18044
|
+
emitOutputProgress(item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`, `command-result:${toolUseId}`);
|
|
17873
18045
|
ctx.emit({
|
|
17874
18046
|
type: "user",
|
|
17875
18047
|
message: { content: [{ tool_use_id: toolUseId, content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})` }] }
|
|
@@ -17877,17 +18049,28 @@ ${item.text}
|
|
|
17877
18049
|
pendingToolCalls.delete(toolUseId);
|
|
17878
18050
|
}
|
|
17879
18051
|
if (item.type === "collab_tool_call") handleCollabItem(item);
|
|
17880
|
-
if (item.type === "mcp_tool_call")
|
|
17881
|
-
|
|
17882
|
-
|
|
18052
|
+
if (item.type === "mcp_tool_call") {
|
|
18053
|
+
emitOutputProgress(`${item.server || "mcp"} ${item.tool || ""} ${JSON.stringify(item.result ?? item.error ?? "")}`);
|
|
18054
|
+
emitMcpCall(item, true);
|
|
18055
|
+
}
|
|
18056
|
+
if (item.type === "web_search") {
|
|
18057
|
+
emitOutputProgress(JSON.stringify(item.action || item.query || "web_search"));
|
|
18058
|
+
emitWebSearch(item);
|
|
18059
|
+
}
|
|
18060
|
+
if (item.type === "todo_list") {
|
|
18061
|
+
emitOutputProgress(JSON.stringify(item.items || []));
|
|
18062
|
+
emitTodoList(item);
|
|
18063
|
+
}
|
|
17883
18064
|
if (item.type === "file_change") {
|
|
17884
18065
|
const toolUseId = resolvePatchToolUseId(item);
|
|
17885
18066
|
if (!pendingToolCalls.has(toolUseId)) {
|
|
18067
|
+
emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
|
|
17886
18068
|
ctx.emit({
|
|
17887
18069
|
type: "assistant",
|
|
17888
18070
|
message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
|
|
17889
18071
|
});
|
|
17890
18072
|
}
|
|
18073
|
+
emitOutputProgress(patchResultText(item), `patch-result:${toolUseId}`);
|
|
17891
18074
|
ctx.emit({
|
|
17892
18075
|
type: "user",
|
|
17893
18076
|
message: { content: [{ tool_use_id: toolUseId, content: patchResultText(item) }] }
|
|
@@ -17902,6 +18085,7 @@ ${item.text}
|
|
|
17902
18085
|
if (item?.type === "command_execution" && item.command) {
|
|
17903
18086
|
const toolUseId = resolveExecToolUseId(item);
|
|
17904
18087
|
pendingToolCalls.set(toolUseId, toolUseId);
|
|
18088
|
+
emitOutputProgress(item.command, `command-start:${toolUseId}`);
|
|
17905
18089
|
ctx.emit({
|
|
17906
18090
|
type: "assistant",
|
|
17907
18091
|
message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.bash, input: { command: item.command } }] }
|
|
@@ -17910,6 +18094,7 @@ ${item.text}
|
|
|
17910
18094
|
if (item?.type === "file_change" && (item.changes?.length ?? 0) > 0) {
|
|
17911
18095
|
const toolUseId = resolvePatchToolUseId(item);
|
|
17912
18096
|
pendingToolCalls.set(toolUseId, toolUseId);
|
|
18097
|
+
emitOutputProgress(patchResultText(item), `patch-start:${toolUseId}`);
|
|
17913
18098
|
ctx.emit({
|
|
17914
18099
|
type: "assistant",
|
|
17915
18100
|
message: { content: [{ type: "tool_use", id: toolUseId, name: CODEX_TOOL_NAMES.applyPatch, input: patchInput(item) }] }
|
|
@@ -17967,14 +18152,30 @@ function codexSdkInput(prompt, imageFiles) {
|
|
|
17967
18152
|
];
|
|
17968
18153
|
}
|
|
17969
18154
|
function codexThreadOptions(ctx) {
|
|
18155
|
+
const effort = resolveCodexReasoningEffort(ctx.params.codexReasoningEffort);
|
|
17970
18156
|
return {
|
|
17971
18157
|
...ctx.params.model ? { model: ctx.params.model } : {},
|
|
18158
|
+
...effort ? { modelReasoningEffort: effort } : {},
|
|
17972
18159
|
...ctx.cwd ? { workingDirectory: ctx.cwd } : {},
|
|
17973
18160
|
sandboxMode: "danger-full-access",
|
|
17974
18161
|
approvalPolicy: "never",
|
|
17975
18162
|
skipGitRepoCheck: true
|
|
17976
18163
|
};
|
|
17977
18164
|
}
|
|
18165
|
+
function resolveCodexReasoningEffort(value) {
|
|
18166
|
+
switch (value) {
|
|
18167
|
+
case "minimal":
|
|
18168
|
+
case "low":
|
|
18169
|
+
case "medium":
|
|
18170
|
+
case "high":
|
|
18171
|
+
case "xhigh":
|
|
18172
|
+
case "max":
|
|
18173
|
+
case "ultra":
|
|
18174
|
+
return value;
|
|
18175
|
+
default:
|
|
18176
|
+
return void 0;
|
|
18177
|
+
}
|
|
18178
|
+
}
|
|
17978
18179
|
async function runCodexSdk(ctx) {
|
|
17979
18180
|
const imageFiles = ctx.images && ctx.images.length > 0 ? writeImagesToTemp(ctx.images) : [];
|
|
17980
18181
|
const adapter = createCodexEventAdapter(ctx);
|
|
@@ -18110,8 +18311,9 @@ var en_default2 = {
|
|
|
18110
18311
|
searchPlaceholder: "Search HTML apps...",
|
|
18111
18312
|
pathLabel: "Absolute path to the .html file",
|
|
18112
18313
|
invalid: "Invalid",
|
|
18314
|
+
builtin: "Built-in",
|
|
18113
18315
|
noDescription: "No description",
|
|
18114
|
-
emptyNoApps: "No HTML apps yet.",
|
|
18316
|
+
emptyNoApps: "No custom HTML apps yet.",
|
|
18115
18317
|
emptyCreatePrefix: "Use",
|
|
18116
18318
|
emptyCreateSuffix: "in chat to create a new app.",
|
|
18117
18319
|
addExisting: "add an existing HTML file",
|
|
@@ -18158,7 +18360,6 @@ var en_default2 = {
|
|
|
18158
18360
|
unstagedNFiles: "Unstaged {{count}} files",
|
|
18159
18361
|
discardFailed: "Discard failed",
|
|
18160
18362
|
discardedNFiles: "Discarded changes in {{count}} files",
|
|
18161
|
-
stagedAllFiles: "All files staged",
|
|
18162
18363
|
deletedFile: "File deleted",
|
|
18163
18364
|
discardedChanges: "Changes discarded",
|
|
18164
18365
|
pastedFile: "Pasted: {{name}}",
|
|
@@ -18500,10 +18701,11 @@ var en_default2 = {
|
|
|
18500
18701
|
startConversation: "Start a conversation",
|
|
18501
18702
|
loadMoreHistory: "Load more...",
|
|
18502
18703
|
scrollUpForMore: "\u2191 Scroll up to load more history",
|
|
18503
|
-
claudeThinking: "Claude is thinking...",
|
|
18504
18704
|
jumpToStart: "Jump to start",
|
|
18505
18705
|
jumpToLatest: "Jump to latest",
|
|
18506
18706
|
copyMessage: "Copy message",
|
|
18707
|
+
copyImage: "Copy image",
|
|
18708
|
+
copiedImage: "Image copied",
|
|
18507
18709
|
forkSession: "Fork session from here",
|
|
18508
18710
|
excerptTurn: "Excerpt this turn to a new session",
|
|
18509
18711
|
imageN: "Image {{index}}",
|
|
@@ -18528,7 +18730,6 @@ var en_default2 = {
|
|
|
18528
18730
|
placeholder: "Type a message, Enter to send (Shift+Enter for new line, paste images; start a line with / (main session) or @ (subagent) for commands, multiple allowed)",
|
|
18529
18731
|
placeholderDisabled: "Generating... you can keep typing (ESC to cancel; if stuck, cancel and resend 'continue')",
|
|
18530
18732
|
imageSizeLimit: "Image size exceeds limit (max 5MB), current: {{size}}MB",
|
|
18531
|
-
stageAll: "Stage all files (git add -A)",
|
|
18532
18733
|
viewGitChanges: "View Git changes",
|
|
18533
18734
|
viewAllComments: "View all comments",
|
|
18534
18735
|
userMessages: "User messages",
|
|
@@ -19313,8 +19514,9 @@ var zh_default = {
|
|
|
19313
19514
|
searchPlaceholder: "\u641C\u7D22 HTML \u5E94\u7528...",
|
|
19314
19515
|
pathLabel: ".html \u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84",
|
|
19315
19516
|
invalid: "\u65E0\u6548",
|
|
19517
|
+
builtin: "\u5185\u7F6E",
|
|
19316
19518
|
noDescription: "\u65E0\u63CF\u8FF0",
|
|
19317
|
-
emptyNoApps: "\u8FD8\u6CA1\u6709 HTML \u5E94\u7528\u3002",
|
|
19519
|
+
emptyNoApps: "\u8FD8\u6CA1\u6709\u81EA\u5B9A\u4E49 HTML \u5E94\u7528\u3002",
|
|
19318
19520
|
emptyCreatePrefix: "\u5728\u804A\u5929\u91CC\u4F7F\u7528",
|
|
19319
19521
|
emptyCreateSuffix: "\u521B\u5EFA\u65B0\u5E94\u7528\u3002",
|
|
19320
19522
|
addExisting: "\u6DFB\u52A0\u5DF2\u6709 HTML \u6587\u4EF6",
|
|
@@ -19361,7 +19563,6 @@ var zh_default = {
|
|
|
19361
19563
|
unstagedNFiles: "\u5DF2\u53D6\u6D88\u6682\u5B58 {{count}} \u4E2A\u6587\u4EF6",
|
|
19362
19564
|
discardFailed: "\u653E\u5F03\u53D8\u66F4\u5931\u8D25",
|
|
19363
19565
|
discardedNFiles: "\u5DF2\u653E\u5F03 {{count}} \u4E2A\u6587\u4EF6\u7684\u53D8\u66F4",
|
|
19364
|
-
stagedAllFiles: "\u5DF2\u6682\u5B58\u6240\u6709\u6587\u4EF6",
|
|
19365
19566
|
deletedFile: "\u5DF2\u5220\u9664\u6587\u4EF6",
|
|
19366
19567
|
discardedChanges: "\u5DF2\u653E\u5F03\u53D8\u66F4",
|
|
19367
19568
|
pastedFile: "\u5DF2\u7C98\u8D34: {{name}}",
|
|
@@ -19703,10 +19904,11 @@ var zh_default = {
|
|
|
19703
19904
|
startConversation: "\u5F00\u59CB\u5BF9\u8BDD\u5427",
|
|
19704
19905
|
loadMoreHistory: "\u52A0\u8F7D\u66F4\u591A...",
|
|
19705
19906
|
scrollUpForMore: "\u2191 \u5411\u4E0A\u6EDA\u52A8\u52A0\u8F7D\u66F4\u591A\u5386\u53F2",
|
|
19706
|
-
claudeThinking: "Claude \u6B63\u5728\u601D\u8003...",
|
|
19707
19907
|
jumpToStart: "\u8DF3\u8F6C\u5230\u5F00\u59CB",
|
|
19708
19908
|
jumpToLatest: "\u8DF3\u8F6C\u5230\u6700\u65B0",
|
|
19709
19909
|
copyMessage: "\u590D\u5236\u6D88\u606F",
|
|
19910
|
+
copyImage: "\u590D\u5236\u56FE\u7247",
|
|
19911
|
+
copiedImage: "\u5DF2\u590D\u5236\u56FE\u7247",
|
|
19710
19912
|
forkSession: "\u4ECE\u6B64\u5904\u5206\u53C9\u4F1A\u8BDD",
|
|
19711
19913
|
excerptTurn: "\u6458\u5F55\u672C\u8F6E\u5230\u65B0\u4F1A\u8BDD",
|
|
19712
19914
|
imageN: "\u56FE\u7247 {{index}}",
|
|
@@ -19731,7 +19933,6 @@ var zh_default = {
|
|
|
19731
19933
|
placeholder: "\u8F93\u5165\u6D88\u606F\uFF0CEnter \u53D1\u9001\uFF08Shift+Enter \u6362\u884C\uFF0C\u53EF\u7C98\u8D34\u56FE\u7247\uFF1B\u884C\u9996 /\uFF08\u4E3B\u4F1A\u8BDD\uFF09\u6216 @\uFF08\u5B50\u4EE3\u7406\uFF09\u5524\u8D77\u547D\u4EE4\uFF0C\u652F\u6301\u591A\u884C\u591A\u547D\u4EE4\uFF09",
|
|
19732
19934
|
placeholderDisabled: "\u751F\u6210\u4E2D... \u53EF\u7EE7\u7EED\u8F93\u5165 (ESC \u53D6\u6D88;\u5361\u4F4F\u592A\u4E45\u53EF\u53D6\u6D88\u540E\u91CD\u53D1\u300C\u7EE7\u7EED\u300D)",
|
|
19733
19935
|
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
19936
|
viewGitChanges: "\u67E5\u770B Git \u53D8\u66F4",
|
|
19736
19937
|
viewAllComments: "\u67E5\u770B\u6240\u6709\u8BC4\u8BBA",
|
|
19737
19938
|
userMessages: "\u7528\u6237\u6D88\u606F\u5217\u8868",
|
|
@@ -20517,8 +20718,7 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
|
|
|
20517
20718
|
if (!spec) {
|
|
20518
20719
|
throw new Error(`no engine spec for ${engine}`);
|
|
20519
20720
|
}
|
|
20520
|
-
const
|
|
20521
|
-
const noHistory = await readSessionNoHistory(task, engine, builtinLoop);
|
|
20721
|
+
const noHistory = await readSessionNoHistory(task, engine);
|
|
20522
20722
|
const outcome = await dispatchChat(spec, {
|
|
20523
20723
|
prompt: buildTaskPrompt(task),
|
|
20524
20724
|
// Omit sessionId to start a brand-new session when the resume target is gone;
|
|
@@ -20527,7 +20727,6 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
|
|
|
20527
20727
|
cwd: task.cwd,
|
|
20528
20728
|
engine,
|
|
20529
20729
|
...task.model && { model: task.model },
|
|
20530
|
-
...builtinLoop && { mode: "builtin" },
|
|
20531
20730
|
...noHistory && { noHistory: true }
|
|
20532
20731
|
});
|
|
20533
20732
|
if (!outcome.ok) {
|
|
@@ -20562,29 +20761,19 @@ var dispatchEngineMessageEff = (task, engine, startFresh) => Effect_exports.tryP
|
|
|
20562
20761
|
})
|
|
20563
20762
|
});
|
|
20564
20763
|
var BUILTIN_LOOP_PATHS = {
|
|
20764
|
+
ollama: getOllamaSessionPath,
|
|
20565
20765
|
deepseek: getDeepseekBuiltinSessionPath,
|
|
20566
20766
|
kimi: getKimiBuiltinSessionPath,
|
|
20567
20767
|
glm: getGlmBuiltinSessionPath
|
|
20568
20768
|
};
|
|
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;
|
|
20769
|
+
async function readSessionNoHistory(task, engine) {
|
|
20770
|
+
if (!BUILTIN_LOOP_PATHS[engine] && engine !== "claude" && engine !== "codex") return false;
|
|
20580
20771
|
const state = await readJsonFile(getSessionFilePath(task.cwd), {});
|
|
20581
20772
|
return state.noHistories?.[task.sessionId] === true;
|
|
20582
20773
|
}
|
|
20583
20774
|
function sessionPathFor(engine, task) {
|
|
20584
|
-
|
|
20585
|
-
if (
|
|
20586
|
-
const sdkStorePath = SDK_STORE_PATHS[engine];
|
|
20587
|
-
if (sdkStorePath) return sdkStorePath(task.cwd, task.sessionId);
|
|
20775
|
+
const builtinPath = BUILTIN_LOOP_PATHS[engine];
|
|
20776
|
+
if (builtinPath) return builtinPath(task.cwd, task.sessionId);
|
|
20588
20777
|
if (engine === "codex") return findCodexSessionPath(task.sessionId);
|
|
20589
20778
|
return getClaudeSessionPath(task.cwd, task.sessionId);
|
|
20590
20779
|
}
|