From 0341fa18b24be9104eb4f4d454408d87fe76f0c0 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 06:35:13 +0200 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20=E8=B0=83=E6=95=B4=20Webterm=20?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E5=85=89=E7=8E=AF=E5=86=B7=E5=8D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 5f980961..81b7b2e0 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -88,7 +88,7 @@ targets: BROWSER_KEEPALIVE_INTERVAL_MS: "30000" BROWSER_STALE_TIMEOUT_MS: "180000" BROWSER_SESSION_OUTPUT_IDLE_MS: "3000" - BROWSER_OUTPUT_PULSE_MIN_INTERVAL_MS: "100" + BROWSER_OUTPUT_PULSE_MIN_INTERVAL_MS: "250" FILE_UPLOAD_DIR: /root/user_uploads FILE_UPLOAD_MAX_BYTES: "104857600" publicExposure: From 444ddf13fde00e65b3796128170ac75f08cb9e5f Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 06:50:01 +0200 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AE=20WORKBENCH=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=81=A2=E5=A4=8D=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 81b7b2e0..63f30543 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -74,6 +74,12 @@ targets: cols: 100 rows: 30 resumePrompt: true + - title: WORKBENCH + cwd: /root/unidesk + command: mycx resume 019f4a1a-fd46-7662-8dc2-bf628210739d + cols: 100 + rows: 30 + resumePrompt: true environment: HOST: 0.0.0.0 PORT: "7681" From 43993c8eb21db3bdea97977136e7514f7bb519f0 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 06:57:30 +0200 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E5=B0=86=207682=20=E7=BA=B3?= =?UTF-8?q?=E5=85=A5=20Webterm=20YAML=20=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 80 +++++++++++++++++++++++++++ scripts/src/platform-infra-webterm.ts | 11 ++-- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 63f30543..99ea8552 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -33,6 +33,86 @@ defaults: targetId: local-7683 targets: + - id: local-7682 + enabled: true + runtime: + mode: host-docker + workDir: /root/webterm + composePath: /root/webterm/build/docker-compose.7682.yaml + composeProject: webterm-7682 + serviceName: web-terminal + containerName: web-terminal-7682 + image: webterm-web-terminal-7682:latest + envFile: /root/webterm/.env + hostPort: 7682 + containerPort: 7681 + privileged: true + pid: host + sourceMounts: + - source: /root/webterm/src + target: /app/src + readOnly: true + - source: /root/webterm/public + target: /app/public + readOnly: true + - source: /root/webterm/scripts/host-shell.sh + target: /usr/local/bin/host-shell + readOnly: true + - source: /root/user_uploads + target: /root/user_uploads + readOnly: false + autoStartSessions: + - title: TERM + cwd: /root/unidesk + command: mycx resume 019f4eeb-bbb3-7e32-bb98-d10753561516 + cols: 100 + rows: 30 + resumePrompt: true + environment: + HOST: 0.0.0.0 + PORT: "7681" + SESSION_COOKIE_NAME: webterm_session_7682 + SESSION_MAX_AGE_SECONDS: "604800" + SERVER_PROVIDER_ID: remote-server + SERVER_PROVIDER_NAME: Remote Server 7682 + TERMINAL_SHELL: /usr/local/bin/host-shell + TERMINAL_CWD: /root + TERMINAL_REMOTE_CWD: /root/unidesk + TERMINAL_START_COMMAND: "" + TERMINAL_HISTORY_BYTES: "2097152" + TERMINAL_SNAPSHOT_BYTES: "131072" + TERMINAL_SNAPSHOT_POLICY: tail + BROWSER_SNAPSHOT_WRITE_CHUNK_BYTES: "8192" + BROWSER_MANUAL_REDRAW_MODE: double + BROWSER_MANUAL_REDRAW_DELAY_MS: "60" + OUTPUT_BATCH_MS: "8" + OUTPUT_BATCH_BYTES: "32768" + BROWSER_KEEPALIVE_ENABLED: "true" + BROWSER_RESUME_PROBE_ENABLED: "false" + BROWSER_KEEPALIVE_INTERVAL_MS: "15000" + BROWSER_STALE_TIMEOUT_MS: "180000" + BROWSER_SESSION_OUTPUT_IDLE_MS: "3000" + BROWSER_OUTPUT_PULSE_MIN_INTERVAL_MS: "250" + PROTOCOL_METRICS_ENABLED: "true" + PROTOCOL_METRICS_WINDOW: "400" + FILE_UPLOAD_DIR: /root/user_uploads + FILE_UPLOAD_MAX_BYTES: "104857600" + publicExposure: + enabled: false + publicBaseUrl: https://term.pikapython.com + dns: + hostname: term.pikapython.com + expectedA: 82.156.23.220 + resolvers: [1.1.1.1, 8.8.8.8, 223.5.5.5, 114.114.114.114] + upstream: + host: 152.53.229.148 + port: 7682 + pk01: + route: PK01 + caddyConfigPath: /etc/caddy/Caddyfile + caddyServiceName: caddy + responseHeaderTimeoutSeconds: 600 + - id: local-7683 enabled: true runtime: diff --git a/scripts/src/platform-infra-webterm.ts b/scripts/src/platform-infra-webterm.ts index 4bf92fc4..a7cbafcd 100644 --- a/scripts/src/platform-infra-webterm.ts +++ b/scripts/src/platform-infra-webterm.ts @@ -115,7 +115,7 @@ export function webtermHelp(): Record { "bun scripts/cli.ts platform-infra webterm validate", ], configPath, - description: "Deploy the host-Docker Web Terminal from YAML and expose it through PK01 Caddy HTTPS without touching the existing 7682 service.", + description: "Deploy target-scoped host-Docker Web Terminal instances from YAML and optionally expose them through PK01 Caddy HTTPS.", }; } @@ -317,8 +317,6 @@ function parseTarget(record: Record, path: string, sessionStart }, }, }; - if (target.runtime.hostPort === 7682) throw new Error(`${path}.runtime.hostPort must not reuse existing 7682`); - if (target.runtime.containerName === "web-terminal-7682") throw new Error(`${path}.runtime.containerName must not reuse web-terminal-7682`); if (!target.publicExposure.publicBaseUrl.startsWith("https://")) throw new Error(`${path}.publicExposure.publicBaseUrl must be https`); return target; } @@ -386,11 +384,10 @@ function quoteShellArg(value: string): string { function policyChecks(target: WebtermTarget, compose: string): Array & { ok: boolean }> { return [ { name: "enabled", ok: target.enabled, detail: "target must be enabled" }, - { name: "no-7682-host-port", ok: target.runtime.hostPort !== 7682, detail: "existing 7682 service must not be reused" }, - { name: "no-7682-container", ok: target.runtime.containerName !== "web-terminal-7682", detail: "existing 7682 container must not be reused" }, - { name: "compose-path-7683", ok: target.runtime.composePath.includes("7683"), detail: target.runtime.composePath }, + { name: "compose-path", ok: target.runtime.composePath.endsWith(".yaml"), detail: target.runtime.composePath }, + { name: "container-name", ok: compose.includes(`container_name: ${target.runtime.containerName}`), detail: target.runtime.containerName }, { name: "public-https", ok: target.publicExposure.publicBaseUrl.startsWith("https://"), detail: target.publicExposure.publicBaseUrl }, - { name: "compose-renders-7683", ok: compose.includes(`"${target.runtime.hostPort}:${target.runtime.containerPort}"`), detail: `${target.runtime.hostPort}:${target.runtime.containerPort}` }, + { name: "compose-port", ok: compose.includes(`"${target.runtime.hostPort}:${target.runtime.containerPort}"`), detail: `${target.runtime.hostPort}:${target.runtime.containerPort}` }, { name: "source-mounts-present", ok: target.runtime.sourceMounts.length >= 3, detail: `mounts=${target.runtime.sourceMounts.length}` }, ]; } From f52186dbe0887453f4ec9b103b1b5454bffd2ce4 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 07:58:20 +0200 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E4=BC=9A=E8=AF=9D=E9=80=80=E5=87=BA=E5=90=8E=E5=9B=9E?= =?UTF-8?q?=E5=88=B0=20Bash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 4 ++++ scripts/src/platform-infra-webterm.ts | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index 99ea8552..ee329bb4 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -68,6 +68,7 @@ targets: cols: 100 rows: 30 resumePrompt: true + fallbackShell: true environment: HOST: 0.0.0.0 PORT: "7681" @@ -148,18 +149,21 @@ targets: cols: 100 rows: 30 resumePrompt: true + fallbackShell: true - title: CONSTAR cwd: /root/unidesk command: mycx resume 019f4b5f-b5ce-76b3-8ccb-508790e9662d cols: 100 rows: 30 resumePrompt: true + fallbackShell: true - title: WORKBENCH cwd: /root/unidesk command: mycx resume 019f4a1a-fd46-7662-8dc2-bf628210739d cols: 100 rows: 30 resumePrompt: true + fallbackShell: true environment: HOST: 0.0.0.0 PORT: "7681" diff --git a/scripts/src/platform-infra-webterm.ts b/scripts/src/platform-infra-webterm.ts index a7cbafcd..76bf1a8c 100644 --- a/scripts/src/platform-infra-webterm.ts +++ b/scripts/src/platform-infra-webterm.ts @@ -56,7 +56,7 @@ interface WebtermTarget { privileged: boolean; pid: "host" | "container"; sourceMounts: Array<{ source: string; target: string; readOnly: boolean }>; - autoStartSessions: Array<{ title: string; cwd: string; command: string; cols: number; rows: number; resumePrompt: boolean }>; + autoStartSessions: Array<{ title: string; cwd: string; command: string; cols: number; rows: number; resumePrompt: boolean; fallbackShell: boolean }>; sessionStartModes: SessionStartModes; autoResumePrompt: AutoResumePrompt; environment: Record; @@ -370,11 +370,14 @@ function resumeCommandWithPrompt( session: WebtermTarget["runtime"]["autoStartSessions"][number], prompt: AutoResumePrompt, ): string { - if (!session.resumePrompt) return session.command; - if (!/^\s*(?:mycx|oncx)\s+resume\s+\S+\s*$/.test(session.command)) { - throw new Error(`resumePrompt requires a mycx/oncx resume command: ${session.title}`); + let command = session.command.trim(); + if (session.resumePrompt) { + if (!/^\s*(?:mycx|oncx)\s+resume\s+\S+\s*$/.test(session.command)) { + throw new Error(`resumePrompt requires a mycx/oncx resume command: ${session.title}`); + } + command = `${command} ${quoteShellArg(prompt.prompt)}`; } - return `${session.command.trim()} ${quoteShellArg(prompt.prompt)}`; + return session.fallbackShell ? `${command}; exec /bin/bash -l` : command; } function quoteShellArg(value: string): string { @@ -554,6 +557,7 @@ function parseAutoStartSessions(value: unknown, path: string): WebtermTarget["ru cols: integerField(record, "cols", itemPath), rows: integerField(record, "rows", itemPath), resumePrompt: booleanField(record, "resumePrompt", itemPath), + fallbackShell: booleanField(record, "fallbackShell", itemPath), }; }); } From dc7aa0af161693601966a2b1b7b3058fcb29478c Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 08:02:15 +0200 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20=E5=B0=86=207681=20=E7=BA=B3?= =?UTF-8?q?=E5=85=A5=20Webterm=20YAML=20=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/platform-infra/webterm.yaml | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/config/platform-infra/webterm.yaml b/config/platform-infra/webterm.yaml index ee329bb4..b26ad2fd 100644 --- a/config/platform-infra/webterm.yaml +++ b/config/platform-infra/webterm.yaml @@ -33,6 +33,87 @@ defaults: targetId: local-7683 targets: + - id: local-7681 + enabled: true + runtime: + mode: host-docker + workDir: /root/webterm + composePath: /root/webterm/build/docker-compose.7681.yaml + composeProject: webterm-7681 + serviceName: web-terminal + containerName: web-terminal + image: webterm-web-terminal-7681:latest + envFile: /root/webterm/.env + hostPort: 7681 + containerPort: 7681 + privileged: true + pid: host + sourceMounts: + - source: /root/webterm/src + target: /app/src + readOnly: true + - source: /root/webterm/public + target: /app/public + readOnly: true + - source: /root/webterm/scripts/host-shell.sh + target: /usr/local/bin/host-shell + readOnly: true + - source: /root/user_uploads + target: /root/user_uploads + readOnly: false + autoStartSessions: + - title: TERM + cwd: /root/unidesk + command: mycx resume 019f4eeb-bbb3-7e32-bb98-d10753561516 + cols: 100 + rows: 30 + resumePrompt: true + fallbackShell: true + environment: + HOST: 0.0.0.0 + PORT: "7681" + SESSION_COOKIE_NAME: webterm_session_7681 + SESSION_MAX_AGE_SECONDS: "604800" + SERVER_PROVIDER_ID: remote-server + SERVER_PROVIDER_NAME: Remote Server + TERMINAL_SHELL: /usr/local/bin/host-shell + TERMINAL_CWD: /root + TERMINAL_REMOTE_CWD: /root/unidesk + TERMINAL_START_COMMAND: "" + TERMINAL_HISTORY_BYTES: "2097152" + TERMINAL_SNAPSHOT_BYTES: "0" + TERMINAL_SNAPSHOT_POLICY: tail + BROWSER_SNAPSHOT_WRITE_CHUNK_BYTES: "8192" + BROWSER_MANUAL_REDRAW_MODE: double + BROWSER_MANUAL_REDRAW_DELAY_MS: "60" + OUTPUT_BATCH_MS: "8" + OUTPUT_BATCH_BYTES: "32768" + BROWSER_KEEPALIVE_ENABLED: "true" + BROWSER_RESUME_PROBE_ENABLED: "false" + BROWSER_KEEPALIVE_INTERVAL_MS: "30000" + BROWSER_STALE_TIMEOUT_MS: "180000" + BROWSER_SESSION_OUTPUT_IDLE_MS: "3000" + BROWSER_OUTPUT_PULSE_MIN_INTERVAL_MS: "250" + PROTOCOL_METRICS_ENABLED: "true" + PROTOCOL_METRICS_WINDOW: "400" + FILE_UPLOAD_DIR: /root/user_uploads + FILE_UPLOAD_MAX_BYTES: "104857600" + publicExposure: + enabled: false + publicBaseUrl: https://term.pikapython.com + dns: + hostname: term.pikapython.com + expectedA: 82.156.23.220 + resolvers: [1.1.1.1, 8.8.8.8, 223.5.5.5, 114.114.114.114] + upstream: + host: 152.53.229.148 + port: 7681 + pk01: + route: PK01 + caddyConfigPath: /etc/caddy/Caddyfile + caddyServiceName: caddy + responseHeaderTimeoutSeconds: 600 + - id: local-7682 enabled: true runtime: From 16f067ad7f35588038db772634ffc85d3998abcc Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 11 Jul 2026 08:11:04 +0200 Subject: [PATCH 6/6] fix: guide remote text operations away from file transfer --- .agents/skills/unidesk-trans/SKILL.md | 4 ++++ scripts/src/help.ts | 5 +++-- scripts/src/ssh-file-transfer.ts | 11 +++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.agents/skills/unidesk-trans/SKILL.md b/.agents/skills/unidesk-trans/SKILL.md index f1539367..7ac9b06b 100644 --- a/.agents/skills/unidesk-trans/SKILL.md +++ b/.agents/skills/unidesk-trans/SKILL.md @@ -32,6 +32,10 @@ Host workspace、k3s、Windows、GitHub issue/PR route 见 [references/routes.md ## P0 边界 - 远端文本修改优先 `trans apply-patch`;不要 download/upload/sed 拼临时 diff 代替 patch。 +- `upload` / `download` 仅用于必要的二进制文件或生成物: + - 远端文本读取优先使用 `cat` / `rg`; + - 远端文本修改优先使用 `apply-patch`; + - 成功传输结果的 `hint` 会提示避免对远端文本反复上传下载。 - Host/WSL 与 Windows route 的 `apply-patch` 优先走 fs adapter bulk update path;不要为了规避旧的 `read-b64-block` / `write-b64-argv` 慢路径改用临时脚本写文件。 - `apply-patch v2` 字节与完整性边界: - localized replacement 必须保留纯 CRLF 和 `legacy-single-byte` 文件的原始字节风格。 diff --git a/scripts/src/help.ts b/scripts/src/help.ts index af00e63e..6f5d8ead 100644 --- a/scripts/src/help.ts +++ b/scripts/src/help.ts @@ -249,7 +249,7 @@ export function sshHelp(scope: SshHelpScope | undefined = undefined): unknown { "sh and bash helper modes inject a tiny POSIX-compatible printf wrapper before user shell text, so portable printf headings such as `printf \"--- section ---\\n\"` work consistently under dash/sh and bash. Direct argv commands are unchanged.", "For arbitrary stdin streams into a workload command, use a workload route plus `exec --stdin -- ...`; this keeps the route as location-only and avoids heredoc/base64/tar shell wrapping.", "`apply-patch` is the default remote text patch entry and uses the v2 local line-based patch engine with remote read/write operations, including Windows routes such as `D601:win/c/test`, so long Unicode/Chinese lines and pure insertion hunks avoid the legacy remote shell hunk parser. Plain multi-file Update File patches on POSIX host/k3s and Windows workspace routes use bulk read/write operations to avoid per-file SSH round trips. Its stdout follows Codex apply_patch text output rather than UniDesk JSON output; stderr keeps Codex-style failure text and appends one `UNIDESK_APPLY_PATCH_TIMING` JSON summary with durationMs, patchBytes, fileCount, hunkCount, changedCount, remoteOperationCount, remoteOperationCounts and remoteElapsedMs so slow patch runs can be attributed without changing success stdout.", - "`upload` and `download` are the default whole-file transfer entries for non-text and generated files. They write through remote temp files, verify byte count and SHA-256 on both sides, and return `verification.automatic=true`, `verification.verified=true`, and `verification.match.{bytes,sha256}=true`; this JSON is the transfer integrity proof, so callers do not need a separate manual `sha256sum` check. Downloads stream over `host.ssh.tcp-pool`, emit progress JSON, and may receive a caller-supplied `--inactivity-timeout-ms` from async artifact/deploy jobs so active large transfers are not killed by the generic short-command budget. Windows route downloads such as `trans D601:win download C:\\Temp\\tool.mjs ./tool.mjs` automatically map drive-letter paths to the same provider's WSL `/mnt/` host route and still return verified bytes/SHA-256 evidence.", + "`upload` 和 `download` 只用于必要的二进制文件或生成物;远端文本应避免反复传输,读取使用 `cat`/`rg`,修改使用 `apply-patch`。成功传输的 JSON 会在 `hint` 中重复该提示,通过远端临时文件写入,自动核对两端字节数和 SHA-256,并返回 `verification.automatic=true`、`verification.verified=true` 和 `verification.match.{bytes,sha256}=true`;该 JSON 已是完整性证据,无需额外执行 `sha256sum`。下载通过 `host.ssh.tcp-pool` 流式传输并输出进度 JSON;受控异步产物任务可传入 `--inactivity-timeout-ms`,只要数据持续流动就不受普通短命令预算影响。Windows route 下载(例如 `trans D601:win download C:\\Temp\\tool.mjs ./tool.mjs`)会自动把盘符路径映射到同一 provider 的 WSL `/mnt/` host route,同时仍返回字节数和 SHA-256 校验证据。", "`apply-patch-v1` is the only legacy fallback entry: it rejects low-context update hunks by default, reports the matched file:line for each hunk on stderr, and only accepts --allow-loose when the caller has manually reviewed an intentionally ambiguous insertion.", "`sh` inherits provider proxy variables such as HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY and runs target `/bin/sh`; use `bash` only for Bash syntax such as `pipefail`, arrays, substring expansion, or `[[ ... ]]`, not as a proxy workaround.", "Route syntax is `{provider}:{plane}[:{scope...}] {operation} [operation-args...]`: the first argv token locates a distributed target only, and every following token belongs to the operation parser. Host workspace routes use `:/absolute/workspace`; WSL providers can use `:win ps` for Windows PowerShell and `:win cmd` for Windows cmd.exe, with `:win/c/test ...` mapping the Windows cwd to `C:\\test`; native k3s providers such as D601 and G14 use `:k3s` for the control plane and `:k3s::[:]` for a workload/container. In k3s routes, `:` is the distributed route separator; `/...` is only an in-container filesystem cwd and never selects a container. Prefer operation `--cwd /path` when a container is also specified.", @@ -276,7 +276,7 @@ function sshDownloadHelp(): unknown { command: `${entrypoint} download`, output: "json", scope: "download", - description: "Download one remote file through the selected route and verify remote/local bytes plus SHA-256 automatically.", + description: "下载一个必要的二进制文件或生成物,并自动核对远端/本地字节数与 SHA-256;远端文本改用 cat/rg/apply-patch。", runtime: { entrypoint, repoRoot, @@ -298,6 +298,7 @@ function sshDownloadHelp(): unknown { "--inactivity-timeout-ms ": "Allow a controlled progress-emitting download to stay open while data continues flowing; ordinary trans operations retain the short runtime budget.", }, contract: { + hint: "避免对远端文本反复 upload/download;读取优先使用 cat/rg,修改优先使用 apply-patch。", pathOrder: ["remote-file", "local-file"], transport: "host.ssh.tcp-pool", strategy: "tcp-pool-stdout-stream", diff --git a/scripts/src/ssh-file-transfer.ts b/scripts/src/ssh-file-transfer.ts index d0626bae..b8e0d8a0 100644 --- a/scripts/src/ssh-file-transfer.ts +++ b/scripts/src/ssh-file-transfer.ts @@ -99,6 +99,15 @@ const fileTransferWriteRawChunkBytes = 131_072; const fileTransferWriteB64ChunkChars = 1_398_104; const fileTransferProgressEveryChunks = 16; +const fileTransferUsageHint = { + code: "prefer-remote-text-operations", + message: "避免对远端文本反复 upload/download;读取优先使用 cat/rg,修改优先使用 apply-patch。upload/download 仅用于必要的二进制文件或生成物传输。", + preferredOperations: { + read: ["cat", "rg"], + edit: ["apply-patch"], + }, +}; + export function isSshFileTransferOperation(args: string[]): boolean { const subcommand = args[0] ?? ""; return subcommand === "upload" || subcommand === "download"; @@ -132,6 +141,7 @@ export async function runSshFileTransferOperation( bytes: expected.bytes, sha256: expected.sha256, verified: true, + hint: fileTransferUsageHint, verification, transfer: write, }, null, 2)}\n`); @@ -149,6 +159,7 @@ export async function runSshFileTransferOperation( bytes: download.bytes, sha256: download.sha256, verified: true, + hint: fileTransferUsageHint, verification: download.verification, transfer: download.transfer, }, null, 2)}\n`);