fix: 通过主工作区契约委派 tran

This commit is contained in:
root
2026-07-13 04:04:50 +02:00
parent da4b3c0d62
commit da4b9ff8ae
7 changed files with 57 additions and 8 deletions
+3 -1
View File
@@ -97,7 +97,9 @@ imageRef:
- UniDesk repo 提供 primary source workspace 与 `unidesk-*` skills - UniDesk repo 提供 primary source workspace 与 `unidesk-*` skills
- AgentRun repo 提供 `tools/trans``tools/tran``tools/apply_patch` 等 runner tool shim - AgentRun repo 提供 `tools/trans``tools/tran``tools/apply_patch` 等 runner tool shim
- `agent_skills` repo 提供 `dad-dev``cli-spec``docs-spec``git-spec` - `agent_skills` repo 提供 `dad-dev``cli-spec``docs-spec``git-spec`
- `tools/tran` 只委派 primary UniDesk workspace`scripts/tran`Windows、host 与 k3s route 解析和 frontend transport 均由 UniDesk 共享实现负责 - runner 通过 `AGENTRUN_WORKSPACE_PATH` 显式传递已物化的 primary workspace
- `tools/tran` 只从该路径委派 primary UniDesk workspace 的 `scripts/tran`,不得根据安装 shim 的 `$0` 反推源码目录;
- Windows、host 与 k3s route 解析和 frontend transport 均由 UniDesk 共享实现负责;
- runner 物化阶段自动把 GitHub URL 改写到 G14 git mirrorArtificer 规格本身不声明 mirror 开关或 mirror base URL。 - runner 物化阶段自动把 GitHub URL 改写到 G14 git mirrorArtificer 规格本身不声明 mirror 开关或 mirror base URL。
- primary source workspace 与受控远端 tool target 是不同边界: - primary source workspace 与受控远端 tool target 是不同边界:
- Agent 修改的源码仍以 `ResourceBundleRef` 物化出的 primary workspace 为唯一来源; - Agent 修改的源码仍以 `ResourceBundleRef` 物化出的 primary workspace 为唯一来源;
+17 -1
View File
@@ -206,10 +206,26 @@ runner 对 managed overlay 的逻辑 `tools/` 做统一装配:
- 顶层带 shebang 的脚本会被 `chmod +x`,并通过 `PATH` 暴露; - 顶层带 shebang 的脚本会被 `chmod +x`,并通过 `PATH` 暴露;
- runtime 配置 `AGENTRUN_RESOURCE_BIN_PATH` 时,只能在该目录写入执行原始 overlay tool 的 shim - runtime 配置 `AGENTRUN_RESOURCE_BIN_PATH` 时,只能在该目录写入执行原始 overlay tool 的 shim
- 禁止复制 tool 文件到 bin 目录后改变 `dirname "$0"`、相对 import 或辅助源码解析语义; - 禁止复制 tool 文件到 bin 目录后改变 `dirname "$0"`、相对 import 或辅助源码解析语义;
- runner 必须通过 `AGENTRUN_WORKSPACE_PATH` 向 backend tool 显式传递已物化的 primary workspace
- runtime tool 不得根据安装 shim 的 `$0` 反推 primary workspace
- 非 shebang 文件只作为 bundle 源码、测试或辅助文件,不作为可执行工具发现; - 非 shebang 文件只作为 bundle 源码、测试或辅助文件,不作为可执行工具发现;
- 短命令名称来自 repo 内真实文件,例如 `tools/hwpod`repo tool 本身承担业务 wrapper 语义。 - 短命令名称来自 repo 内真实文件,例如 `tools/hwpod`repo tool 本身承担业务 wrapper 语义。
AgentRun 自身仓库必须提供 `tools/tran``tools/trans``tools/apply_patch`,用于承接 UniDesk frontend `/ws/ssh` 的 scoped client-token 透传。runner 只通过 `executionPolicy.secretScope.toolCredentials[]` 投影 `UNIDESK_SSH_CLIENT_TOKEN`,并通过调用方 `transientEnv` 或 manager 受控默认值注入非敏感 `UNIDESK_MAIN_SERVER_IP``UNIDESK_MAIN_SERVER_HOST``UNIDESK_FRONTEND_URL`;工具不得读取 provider token、主 server SSH key 或完整 frontend 登录态。`tran --help` 必须输出 JSON,并列出当前支持的最小开发面:host/host workspace `script``argv`、普通 ssh-like 命令、host workspace `apply-patch``k3s kubectl``k3s script`、k3s workload `argv/script` 和 k3s workload `apply-patch`。runner 侧 `apply-patch` 只把 stdin patch 和相邻 `tools/apply_patch` helper 传到目标 route 执行,不接触或回显 Secret 值;`upload``download` 和 Windows route 未实现时必须显式 `unsupported-operation`,不能静默改走不受控 shell 拼接或 token fallback。 AgentRun 自身仓库必须提供 `tools/tran``tools/trans``tools/apply_patch`
- 这些工具用于承接 UniDesk frontend `/ws/ssh` 的 scoped client-token 透传;
- runner 只通过 `executionPolicy.secretScope.toolCredentials[]` 投影 `UNIDESK_SSH_CLIENT_TOKEN`
- 非敏感 frontend 地址只由受控入口注入:
- 可用字段是 `UNIDESK_MAIN_SERVER_IP``UNIDESK_MAIN_SERVER_HOST``UNIDESK_FRONTEND_URL`
- 注入来源是调用方 `transientEnv` 或 manager 受控默认值;
- 工具不得读取 provider token、主 server SSH key 或完整 frontend 登录态;
- `tools/tran` 只委派 `AGENTRUN_WORKSPACE_PATH` 指向的 primary UniDesk workspace 中的 `scripts/tran`
- route 与 transport 职责保持单一:
- Windows、host 与 k3s route 解析和 frontend transport 均由 UniDesk 共享实现负责;
- AgentRun 不维护第二套 parser 或 fallback
- runner 侧 `apply-patch` 保持最小职责:
- 只把 stdin patch 和相邻 `tools/apply_patch` helper 传到目标 route 执行;
- 不接触或回显 Secret 值。
#### promptRefs #### promptRefs
+1 -1
View File
@@ -226,7 +226,7 @@ function resourceMaterializationEnv(env: NodeJS.ProcessEnv, runId: string, attem
function resourceEnvForMaterialized(env: NodeJS.ProcessEnv, materialized: Awaited<ReturnType<typeof materializeResourceBundle>>): NodeJS.ProcessEnv | undefined { function resourceEnvForMaterialized(env: NodeJS.ProcessEnv, materialized: Awaited<ReturnType<typeof materializeResourceBundle>>): NodeJS.ProcessEnv | undefined {
if (!materialized) return undefined; if (!materialized) return undefined;
const sanitized = { ...env }; const sanitized = { ...env, AGENTRUN_WORKSPACE_PATH: materialized.workspacePath };
delete sanitized.AGENTRUN_RESOURCE_CREDENTIAL_PATH; delete sanitized.AGENTRUN_RESOURCE_CREDENTIAL_PATH;
let next: NodeJS.ProcessEnv | undefined = sanitized; let next: NodeJS.ProcessEnv | undefined = sanitized;
if (materialized.binPath) { if (materialized.binPath) {
@@ -196,6 +196,8 @@ process.exit(1);
assert.equal(assemblyRunnerResult.commandsProcessed, 2); assert.equal(assemblyRunnerResult.commandsProcessed, 2);
const assemblyInputs = (await readTextIfExists(assemblyInputFile)).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line) as JsonRecord); const assemblyInputs = (await readTextIfExists(assemblyInputFile)).trim().split("\n").filter(Boolean).map((line) => JSON.parse(line) as JsonRecord);
assert.equal(assemblyInputs.length, 2); assert.equal(assemblyInputs.length, 2);
assert.equal(assemblyInputs[0]?.workspacePath, assemblyInputs[0]?.cwd, "backend tool env must identify the materialized primary workspace explicitly");
assert.equal(assemblyInputs[1]?.workspacePath, assemblyInputs[1]?.cwd, "continued turns must preserve the primary workspace env contract");
const firstAssemblyInput = turnInputText(assemblyInputs[0]); const firstAssemblyInput = turnInputText(assemblyInputs[0]);
const secondAssemblyInput = turnInputText(assemblyInputs[1]); const secondAssemblyInput = turnInputText(assemblyInputs[1]);
assert.match(firstAssemblyInput, /HWLAB v0\.2 runtime prompt self-test/u); assert.match(firstAssemblyInput, /HWLAB v0\.2 runtime prompt self-test/u);
@@ -31,12 +31,32 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal(applyPatch.startsWith("#!/bin/sh\n"), true, "tools/apply_patch must be a shebang helper copied with runner tools"); assert.equal(applyPatch.startsWith("#!/bin/sh\n"), true, "tools/apply_patch must be a shebang helper copied with runner tools");
assert.equal(agentrunGit.startsWith("#!/usr/bin/env bun\n"), true, "tools/agentrun-git must be a shebang executable discovered by gitbundle tools"); assert.equal(agentrunGit.startsWith("#!/usr/bin/env bun\n"), true, "tools/agentrun-git must be a shebang executable discovered by gitbundle tools");
assert.equal(tran.includes("scripts/tran"), true, "tools/tran must delegate to the primary UniDesk workspace implementation"); assert.equal(tran.includes("scripts/tran"), true, "tools/tran must delegate to the primary UniDesk workspace implementation");
assert.equal(tran.includes("AGENTRUN_WORKSPACE_PATH"), true, "tools/tran must use the explicit primary workspace contract");
assert.equal(tran.includes("UNIDESK_TRAN_REPO_ROOT"), true, "tools/tran must bind the shared implementation to the primary workspace"); assert.equal(tran.includes("UNIDESK_TRAN_REPO_ROOT"), true, "tools/tran must bind the shared implementation to the primary workspace");
assert.equal(tran.includes("dirname -- \"$0\""), false, "tools/tran must not infer the primary workspace from an installed shim path");
assert.equal(tran.includes("parseRoute"), false, "tools/tran must not carry a second route parser"); assert.equal(tran.includes("parseRoute"), false, "tools/tran must not carry a second route parser");
assert.equal(tran.includes("does not support Windows routes"), false, "tools/tran must not reject shared Windows routes"); assert.equal(tran.includes("does not support Windows routes"), false, "tools/tran must not reject shared Windows routes");
assert.equal(agentrunGit.includes("GIT_TERMINAL_PROMPT"), true, "tools/agentrun-git must disable interactive git prompts"); assert.equal(agentrunGit.includes("GIT_TERMINAL_PROMPT"), true, "tools/agentrun-git must disable interactive git prompts");
assert.equal(agentrunGit.includes("AGENTRUN_GIT_DIRECT_HOSTS"), true, "tools/agentrun-git must expose direct host policy"); assert.equal(agentrunGit.includes("AGENTRUN_GIT_DIRECT_HOSTS"), true, "tools/agentrun-git must expose direct host policy");
const primaryWorkspace = path.join(context.tmp, "tran-primary-workspace");
const primaryScripts = path.join(primaryWorkspace, "scripts");
await mkdir(primaryScripts, { recursive: true });
await writeFile(path.join(primaryScripts, "ssh-cli.ts"), "// UniDesk shared tran entrypoint\n", "utf8");
await writeFile(path.join(primaryScripts, "tran"), "#!/bin/sh\nprintf '%s\\n' \"$UNIDESK_TRAN_REPO_ROOT\" \"$*\"\n", "utf8");
await chmod(path.join(primaryScripts, "tran"), 0o755);
const installedBin = path.join(context.tmp, "installed-resource-bin");
await mkdir(installedBin, { recursive: true });
const installedTran = path.join(installedBin, "tran");
await writeFile(installedTran, `#!/bin/sh\nexec '${path.join(context.root, "tools/tran")}' \"$@\"\n`, "utf8");
await chmod(installedTran, 0o755);
const delegatedTran = await execFileAsync(installedTran, ["TEST:win", "cmd", "cd"], { cwd: context.tmp, env: { ...process.env, AGENTRUN_WORKSPACE_PATH: primaryWorkspace } });
assert.deepEqual(delegatedTran.stdout.trim().split("\n"), [primaryWorkspace, "TEST:win cmd cd"]);
await assert.rejects(
() => execFileAsync(installedTran, ["--help"], { cwd: context.tmp, env: { ...process.env, AGENTRUN_WORKSPACE_PATH: undefined } }),
(error) => error instanceof Error && "stderr" in error && String(error.stderr).includes("runner did not provide an absolute primary workspace path"),
);
const patchHelp = await execFileAsync(path.join(context.root, "tools/apply_patch"), ["--help"], { cwd: context.root, timeout: 10_000 }); const patchHelp = await execFileAsync(path.join(context.root, "tools/apply_patch"), ["--help"], { cwd: context.root, timeout: 10_000 });
assert.equal(patchHelp.stdout.includes("reads *** Begin Patch format"), true); assert.equal(patchHelp.stdout.includes("reads *** Begin Patch format"), true);
const agentrunGitHelp = await execFileAsync(path.join(context.root, "tools/agentrun-git"), ["--help"], { cwd: context.root, timeout: 10_000 }); const agentrunGitHelp = await execFileAsync(path.join(context.root, "tools/agentrun-git"), ["--help"], { cwd: context.root, timeout: 10_000 });
+1 -1
View File
@@ -75,7 +75,7 @@ for await (const line of rl) {
continue; continue;
} }
if (message.method === "turn/start") { if (message.method === "turn/start") {
if (process.env.AGENTRUN_FAKE_CODEX_TURN_INPUT_FILE) appendFileSync(process.env.AGENTRUN_FAKE_CODEX_TURN_INPUT_FILE, `${JSON.stringify({ threadId: message.params?.threadId ?? null, input: message.params?.input ?? null, model: message.params?.model ?? null, effort: message.params?.effort ?? null })}\n`); if (process.env.AGENTRUN_FAKE_CODEX_TURN_INPUT_FILE) appendFileSync(process.env.AGENTRUN_FAKE_CODEX_TURN_INPUT_FILE, `${JSON.stringify({ threadId: message.params?.threadId ?? null, input: message.params?.input ?? null, model: message.params?.model ?? null, effort: message.params?.effort ?? null, workspacePath: process.env.AGENTRUN_WORKSPACE_PATH ?? null, cwd: process.cwd() })}\n`);
if (mode === "reject-unexpected-model" && (observedThreadModel || Object.hasOwn(message.params ?? {}, "model"))) { if (mode === "reject-unexpected-model" && (observedThreadModel || Object.hasOwn(message.params ?? {}, "model"))) {
respond(message.id, null, { code: -32000, message: "turn/start unexpectedly included model" }); respond(message.id, null, { code: -32000, message: "turn/start unexpectedly included model" });
continue; continue;
+13 -4
View File
@@ -1,11 +1,20 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
self_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) primary_workspace=${AGENTRUN_WORKSPACE_PATH:-}
primary_workspace=$(CDPATH= cd -- "$self_dir/../../.." && pwd)
shared_tran="$primary_workspace/scripts/tran"
if [ ! -x "$shared_tran" ]; then case "$primary_workspace" in
/*) ;;
*)
printf '%s\n' '{"ok":false,"failureKind":"tool-unavailable","message":"runner did not provide an absolute primary workspace path","source":"primary-workspace","valuesPrinted":false}' >&2
exit 2
;;
esac
shared_tran="$primary_workspace/scripts/tran"
shared_cli="$primary_workspace/scripts/ssh-cli.ts"
if [ ! -x "$shared_tran" ] || [ ! -f "$shared_cli" ]; then
printf '%s\n' '{"ok":false,"failureKind":"tool-unavailable","message":"primary UniDesk workspace does not provide executable scripts/tran","source":"primary-workspace","valuesPrinted":false}' >&2 printf '%s\n' '{"ok":false,"failureKind":"tool-unavailable","message":"primary UniDesk workspace does not provide executable scripts/tran","source":"primary-workspace","valuesPrinted":false}' >&2
exit 2 exit 2
fi fi