diff --git a/docs/reference/agentrun.md b/docs/reference/agentrun.md index 4ba69571..6a0b668d 100644 --- a/docs/reference/agentrun.md +++ b/docs/reference/agentrun.md @@ -111,7 +111,7 @@ UniDesk 不能作为以下内容的事实来源: AgentRun `v0.1` 的指挥官任务面已经按 AgentRun issue #105 完成真实运行面验收,可作为新任务派发、Queue commander、trace/output、steer、turn/reuse、read 和 cancel 的 AgentRun 侧标准路径。长期使用时仍以 AgentRun 仓库自身 SPEC 为能力事实来源;UniDesk 只记录该路径已经通过 G14 `agentrun-v01` 运行面和 `hy` profile + `gpt-5.5` 验证。 -UniDesk 指挥官新任务入口固定使用 `bun scripts/cli.ts agentrun v01 queue|sessions`。该入口是 G14 `/root/agentrun-v01` 中官方 `./scripts/agentrun --manager-url auto` CLI 的直接 bridge;本地 `--json-file`、`--json-stdin`、`--prompt-file`、`--prompt-stdin`、`--runner-json-file` 和 `--runner-json-stdin` 只会被 materialize 到 G14 临时文件后传给官方 CLI,不在 UniDesk 内实现 AgentRun queue 协议,也不把任务 double-write 回旧 Code Queue。日常派单、dispatch、turn 和 steer 优先用 heredoc/stdin,避免为了 JSON 或 prompt 输入先落 dump 文件。 +UniDesk 指挥官新任务入口固定使用 `bun scripts/cli.ts agentrun v01 queue|sessions`。该入口是 G14 `/root/agentrun-v01` 中官方 `./scripts/agentrun --manager-url auto` CLI 的直接 bridge;本地 `--json-file`、`--prompt-file` 和 `--runner-json-file` 会被 materialize 到 G14 临时文件后传给官方 CLI,`--json-stdin`、`--prompt-stdin`、`--runner-json-stdin` 和 `--*-file -` 则通过管道直通官方 CLI 的 stdin,不在 UniDesk 内实现 AgentRun queue 协议,也不把任务 double-write 回旧 Code Queue。日常派单、dispatch、turn 和 steer 优先用 heredoc/stdin,避免为了 JSON 或 prompt 输入先落 dump 文件。 AgentRun Queue 任务如果需要调用 UniDesk 维护桥,例如 `trans` / `unidesk-ssh`,长期契约以 AgentRun 仓库 `docs/reference/spec-v01-runtime-assembly.md` 和 `docs/reference/spec-v01-secret-distribution.md` 为准:调用方通过 `executionPolicy.secretScope.toolCredentials[].tool=unidesk-ssh` 请求 `UNIDESK_SSH_CLIENT_TOKEN` SecretRef;非敏感 endpoint 由 runner-job `transientEnv` 显式提供,或由 manager 受控默认值自动补齐。UniDesk bridge 提交 Queue payload 时不得在 prompt、payload 或 `transientEnv` 中携带 token,也不得使用 HWLAB runtime Web 入口冒充 UniDesk frontend。若 dispatcher 已正确请求 `unidesk-ssh` 但 trace 的 `runner-job-created.transientEnv.names` 没有 `UNIDESK_MAIN_SERVER_IP`、`UNIDESK_MAIN_SERVER_HOST` 或 `UNIDESK_FRONTEND_URL`,归为 AgentRun assembly 问题;若 endpoint env 已存在但 route denied/timeout,再按 UniDesk frontend/token scope 或 provider session 排查。 diff --git a/scripts/src/agentrun.ts b/scripts/src/agentrun.ts index 68a3743b..524d0a49 100644 --- a/scripts/src/agentrun.ts +++ b/scripts/src/agentrun.ts @@ -1445,15 +1445,23 @@ interface AgentRunCliMaterializedFile { base64: string; } +interface AgentRunCliForwardedStdin { + flag: string; + source: string; + bytes: number; + base64: string; +} + interface PreparedAgentRunCliArgs { args: string[]; materializedFiles: AgentRunCliMaterializedFile[]; + stdinPayload: AgentRunCliForwardedStdin | null; } async function runOfficialAgentRunCli(config: UniDeskConfig, group: "queue" | "sessions", args: string[]): Promise> { const prepared = prepareOfficialAgentRunCliArgs([group, ...args]); const command = `agentrun v01 ${prepared.args.join(" ")}`.trim(); - const bridge = agentRunQueueBridgeMetadata(prepared.materializedFiles); + const bridge = agentRunQueueBridgeMetadata(prepared.materializedFiles, prepared.stdinPayload); const script = officialAgentRunCliScript(prepared); const result = await capture(config, g14SourceRoute, ["script", "--", script]); const payload = captureJsonPayload(result); @@ -1489,6 +1497,7 @@ function prepareOfficialAgentRunCliArgs(args: string[]): PreparedAgentRunCliArgs const materializedFiles: AgentRunCliMaterializedFile[] = []; const prepared: string[] = []; let stdinBuffer: Buffer | null = null; + let stdinPayload: AgentRunCliForwardedStdin | null = null; const stdin = (): Buffer => { if (stdinBuffer === null) stdinBuffer = readFileSync(0); @@ -1507,6 +1516,23 @@ function prepareOfficialAgentRunCliArgs(args: string[]): PreparedAgentRunCliArgs return remotePath; }; + const forwardStdin = (flag: string, source: string, buffer: Buffer): void => { + if (stdinPayload !== null) throw new Error(`${flag} cannot be combined with ${stdinPayload.flag}; pass one stdin payload per AgentRun CLI call`); + stdinPayload = { + flag, + source, + bytes: buffer.length, + base64: buffer.toString("base64"), + }; + }; + + const stdinFlagForFileFlag = (flag: string): string | null => { + if (flag === "--json-file") return "--json-stdin"; + if (flag === "--prompt-file") return "--prompt-stdin"; + if (flag === "--runner-json-file") return "--runner-json-stdin"; + return null; + }; + for (let i = 0; i < args.length; i += 1) { const arg = args[i] ?? ""; const equalsFlag = Array.from(fileFlags).find((flag) => arg.startsWith(`${flag}=`)); @@ -1514,6 +1540,13 @@ function prepareOfficialAgentRunCliArgs(args: string[]): PreparedAgentRunCliArgs const source = arg.slice(equalsFlag.length + 1); if (source.length === 0) throw new Error(`${equalsFlag} requires a path`); const buffer = source === "-" ? stdin() : readFileSync(source); + if (source === "-") { + const stdinFlag = stdinFlagForFileFlag(equalsFlag); + if (!stdinFlag) throw new Error(`${equalsFlag} does not support stdin`); + forwardStdin(stdinFlag, "stdin", buffer); + prepared.push(stdinFlag); + continue; + } prepared.push(equalsFlag, materialize(equalsFlag, source === "-" ? "stdin" : source, buffer)); continue; } @@ -1521,26 +1554,37 @@ function prepareOfficialAgentRunCliArgs(args: string[]): PreparedAgentRunCliArgs const source = args[i + 1]; if (source === undefined || source.length === 0) throw new Error(`${arg} requires a path`); const buffer = source === "-" ? stdin() : readFileSync(source); + if (source === "-") { + const stdinFlag = stdinFlagForFileFlag(arg); + if (!stdinFlag) throw new Error(`${arg} does not support stdin`); + forwardStdin(stdinFlag, "stdin", buffer); + prepared.push(stdinFlag); + i += 1; + continue; + } prepared.push(arg, materialize(arg, source === "-" ? "stdin" : source, buffer)); i += 1; continue; } if (arg === "--prompt-stdin" || arg === "--stdin") { - prepared.push("--prompt-file", materialize("--prompt-stdin", "stdin", stdin())); + forwardStdin("--prompt-stdin", "stdin", stdin()); + prepared.push("--prompt-stdin"); continue; } if (arg === "--json-stdin") { - prepared.push("--json-file", materialize("--json-stdin", "stdin", stdin())); + forwardStdin("--json-stdin", "stdin", stdin()); + prepared.push("--json-stdin"); continue; } if (arg === "--runner-json-stdin") { - prepared.push("--runner-json-file", materialize("--runner-json-stdin", "stdin", stdin())); + forwardStdin("--runner-json-stdin", "stdin", stdin()); + prepared.push("--runner-json-stdin"); continue; } prepared.push(arg); } - return { args: prepared, materializedFiles }; + return { args: prepared, materializedFiles, stdinPayload }; } function officialAgentRunCliScript(prepared: PreparedAgentRunCliArgs): string { @@ -1552,11 +1596,15 @@ function officialAgentRunCliScript(prepared: PreparedAgentRunCliArgs): string { "trap 'rm -rf \"$tmp_dir\"' EXIT", ...prepared.materializedFiles.map((file) => `printf %s ${shQuote(file.base64)} | base64 -d > ${shQuote(file.remotePath)}`), ]; + const cliCommand = `./scripts/agentrun --manager-url auto ${prepared.args.map(shQuote).join(" ")}`; + const invocation = prepared.stdinPayload + ? `printf %s ${shQuote(prepared.stdinPayload.base64)} | base64 -d | ${cliCommand}` + : cliCommand; return [ "set -euo pipefail", ...setup, "cd /root/agentrun-v01", - `./scripts/agentrun --manager-url auto ${prepared.args.map(shQuote).join(" ")}`, + invocation, ].join("\n"); } @@ -1565,7 +1613,7 @@ function parentDir(pathValue: string): string { return index > 0 ? pathValue.slice(0, index) : "."; } -function agentRunQueueBridgeMetadata(materializedFiles: AgentRunCliMaterializedFile[]): Record { +function agentRunQueueBridgeMetadata(materializedFiles: AgentRunCliMaterializedFile[], stdinPayload: AgentRunCliForwardedStdin | null): Record { return { route: g14SourceRoute, sourceWorktree: "/root/agentrun-v01", @@ -1574,6 +1622,11 @@ function agentRunQueueBridgeMetadata(materializedFiles: AgentRunCliMaterializedF officialCli: "./scripts/agentrun", mode: "direct-official-cli", compatibility: "no-code-queue-adapter-no-double-write", + stdinForwarded: stdinPayload ? { + flag: stdinPayload.flag, + source: stdinPayload.source, + bytes: stdinPayload.bytes, + } : null, fileFlagsMaterialized: materializedFiles.map((file) => ({ flag: file.flag, source: file.source,