本地 Agent 只能修改系统临时目录中的独立副本。只有 Workspace 已确认 status=active,且本次 workspace connect 返回 ssh_binding_status=bound、ssh_verified=true,再加上开发者明确授权本次同步和目标路径已经确认,才可上传。
保存原路径与 SHA-256,把源文件视为只读。不得对它执行原地补丁、格式化、重定向覆盖或编辑器保存。
只复制本轮需要修改的文件,所有编辑器、补丁、生成器和格式化器都指向该临时目录。
展示源文件与副本差异,运行与目标语言匹配的语法/格式检查,并确认没有 Secret、会话和缓存。
复用已有 workspace_id 查询状态,再对它运行 workspace connect。只有 active + bound + ssh_verified=true 才能使用该次 connect 返回的 SSH/SCP 信息;没有明确同步授权就停在本地差异阶段。
使用 CLI 返回的完整 SCP 命令,把副本传至 /oasn/.oasn-staging/<run-id>/。不要直接覆盖正式目标。
进入 CLI 返回的 SSH 会话,核对远端暂存文件摘要;一致后才从暂存区复制或安装到已确认的目标。
完成业务验证后,只清理本轮创建的临时目录和暂存目录。最后重算开发者原文件摘要,必须与步骤 1 相同。
source_file="<developer-file>"
file_name="<file-name>"
sha256_file() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum -- "$1" | awk '{print $1}'
else
shasum -a 256 "$1" | awk '{print $1}'
fi
}
source_hash="$(sha256_file "$source_file")"
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/oasn-sa-dev.XXXXXX")"
copy_file="$work_dir/$file_name"
cp -p -- "$source_file" "$copy_file"
copy_hash="$(sha256_file "$copy_file")"
printf 'source=%s\ncopy=%s\n' "$source_hash" "$copy_hash"
git diff --no-index -- "$source_file" "$copy_file"
git diff --no-index 发现预期差异时退出码为 1,这不是校验失败。
$sourceFile = "<developer-file>"
$workDir = Join-Path ([System.IO.Path]::GetTempPath()) ("oasn-sa-dev-" + [guid]::NewGuid())
New-Item -ItemType Directory -Path $workDir | Out-Null
$copyFile = Join-Path $workDir "<file-name>"
Copy-Item -LiteralPath $sourceFile -Destination $copyFile
$sourceHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $sourceFile).Hash
$copyHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $copyFile).Hash
"source=$sourceHash"
"copy=$copyHash"
把 <developer-file>、<file-name> 替换为本轮已经确认的单个目标。不要使用宽泛 glob,不要复制整个用户目录或整个仓库。
.env、Token、Cookie、私钥、浏览器数据、会话、日志、.git 或缓存。workspace_id 已通过 workspace status 返回 active,且本次 connect 返回 bound + ssh_verified=true。/oasn 下明确路径。CLI 的 connect 结果会给出本次已验证的完整 SSH/SCP 连接信息。请复制该结果,不要根据 status、URL、其他 Workspace 或历史记录拼接命令。进入该 SSH 会话后,可以为本轮创建明确的暂存目录:
run_id="sync-$(date -u +%Y%m%dT%H%M%SZ)-$$"
stage_dir="/oasn/.oasn-staging/$run_id"
install -d -m 0700 -- "$stage_dir"
printf '%s\n' "$stage_dir"
run_id 必须是本轮唯一值;上面的 UTC 时间与当前 shell PID 会生成一个新目录,不要把字面量占位符反复复用。随后退出 SSH,从 CLI 返回的完整 scp_command 复制一份:只把其中的 <file> 替换为本地临时副本,并把命令末尾的远端目标替换为上一步打印的 stage_dir;-P、-i、host、user 等连接参数全部保持原样。上传完成后重新进入同一个 SSH 会话:
stage_dir="/oasn/.oasn-staging/<run-id>"
stage_file="$stage_dir/<file-name>"
test -f "$stage_file"
sha256sum -- "$stage_file"
把远端第一列与上传前记录的 copy_hash/$copyHash 逐字比较;不一致就停止。下面给出把单个普通 Program 文件应用为 /oasn/AGENTS.md 的完整例子。其他目标必须先把 target_file 改为已确认的 /oasn 内路径,并按文件用途选择权限;不要照搬到 Secret、目录或可执行文件。
仍在同一个 active Workspace 的 SSH 会话内,先设置本轮已经打印的暂存目录。命令会保留现有目标的临时副本,然后以普通只读数据文件权限安装:
stage_dir="/oasn/.oasn-staging/<run-id>"
stage_file="$stage_dir/AGENTS.md"
target_file="/oasn/AGENTS.md"
backup_file="$stage_dir/AGENTS.md.before"
test -f "$stage_file"
test "$target_file" = "/oasn/AGENTS.md"
if test -e "$target_file"; then
cp -p -- "$target_file" "$backup_file"
fi
install -m 0644 -- "$stage_file" "$target_file"
sha256sum -- "$target_file"
sed -n '1,40p' -- "$target_file"
目标文件 SHA-256 必须仍等于本地副本摘要,前 40 行应是预期内容。接着打开 CLI 返回的 WebChat URL,执行正常输入和受控失败用例。若摘要、内容或业务行为不对,在清理暂存目录前回退:
if test -f "$backup_file"; then
install -m 0644 -- "$backup_file" "$target_file"
else
rm -- "$target_file"
fi
只有本轮确认目标原先不存在时,回退分支才会删除刚创建的 /oasn/AGENTS.md。若目标是 /root/.openclaw/openclaw.json 等受保护位置,只在远端按已授权范围使用 sudo,并先设计该文件专属的验证和回退步骤;不要把本地原文件直接作为远端写入目标。
WebChat 与文件摘要都通过后,在远端只移除本轮两个已知暂存文件;若目录里还有意外内容,rmdir 会安全失败并保留现场:
stage_dir="/oasn/.oasn-staging/<run-id>"
test "$stage_dir" = "/oasn/.oasn-staging/<run-id>"
rm -f -- "$stage_dir/AGENTS.md" "$stage_dir/AGENTS.md.before"
rmdir -- "$stage_dir"
最后回到本地终端,确认开发者原文件摘要从未变化,再删除本轮唯一临时副本:
test "$(sha256_file "$source_file")" = "$source_hash"
case "$work_dir" in
"${TMPDIR:-/tmp}"/oasn-sa-dev.*) rm -rf -- "$work_dir" ;;
*) printf '拒绝清理意外路径:%s\n' "$work_dir" >&2; exit 1 ;;
esac
if ((Get-FileHash -Algorithm SHA256 -LiteralPath $sourceFile).Hash -ne $sourceHash) {
throw "开发者原文件已变化,停止清理"
}
$expectedPrefix = Join-Path ([System.IO.Path]::GetTempPath()) "oasn-sa-dev-"
$resolvedWorkDir = (Resolve-Path -LiteralPath $workDir).Path
if (-not $resolvedWorkDir.StartsWith($expectedPrefix, [StringComparison]::OrdinalIgnoreCase)) {
throw "拒绝清理意外路径:$resolvedWorkDir"
}
Remove-Item -LiteralPath $resolvedWorkDir -Recurse -Force