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`);