fix: expose hwpod in code queue runners
This commit is contained in:
@@ -102,7 +102,7 @@ Code Queue 派单模型按成本、可信度和 blast radius 分层:GPT-5.5/Co
|
||||
|
||||
`codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 是同一套派单前 guardrail 的本地 dry-run 入口,用于检查 runner prompt 是否声明了 `DEV test class`、是否列出允许的 live mutation、禁止动作和 closeout 字段。它只返回分类、缺失或矛盾项和有界 evidence,不提交任务、不连接 live service、不打印完整 prompt。`codex submit --dry-run` 和 `codex steer --dry-run` 会嵌入同一 `promptLint` 结果;`dispatchDisposition=needs-authorization` 时,指挥官必须补齐授权或把 prompt 降到 `read-only` 范围后再派发/steer。
|
||||
|
||||
Device Pod 类 DS 派单必须把工具可用性设计进 prompt,而不是靠事后强制纠偏。prompt 应明确唯一 pod、workspace selector、目标工程/target、允许的 live mutation、禁止的 pod/BOOT/生产/密钥/数据库范围和 closeout 字段;文本源码修改默认要求 `hwpod ... workspace apply-patch`,新文件使用 `apply-patch --add-file`,整文件替换使用 `apply-patch --replace-file`,不要优先 `workspace put`。命令入口默认写短别名 `hwpod`,不要写长路径 `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs`;添加 Keil 源文件、build clean、download、UART/JSON-RPC smoke 也应走 `hwpod`。prompt 中只允许把 `/app/tools/tran.mjs`、`/app/tools/hwlab-gateway-tran.mjs`、临时 Python/PowerShell/JS 上传脚本列为禁止绕行;如果 DS 仍然需要这些绕行,指挥官应先把缺失能力补进 `device-pod-cli`/`hwpod`,再重置 workspace 让 DS 复测。
|
||||
Device Pod 类 DS 派单必须把工具可用性设计进 prompt,而不是靠事后强制纠偏。prompt 应明确唯一 pod、workspace selector、目标工程/target、允许的 live mutation、禁止的 pod/BOOT/生产/密钥/数据库范围和 closeout 字段;文本源码修改默认要求 `hwpod ... workspace apply-patch`,新文件使用 `apply-patch --add-file`,整文件替换使用 `apply-patch --replace-file`,不要优先 `workspace put`。命令入口默认写短别名 `hwpod`,不要写长路径 `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs`;`hwpod` 必须作为 Code Queue 镜像和 provider dev container 的 PATH 命令存在,并从 `DEVICE_POD_CLI`、`UNIDESK_SKILLS_PATH/device-pod-cli`、当前 workspace 的 `skills/device-pod-cli` 或 `tools/device-pod-cli.mjs` 解析真实 CLI。添加 Keil 源文件、build clean、download、UART/JSON-RPC smoke 也应走 `hwpod`。prompt 中只允许把 `/app/tools/tran.mjs`、`/app/tools/hwlab-gateway-tran.mjs`、临时 Python/PowerShell/JS 上传脚本列为禁止绕行;如果 DS 仍然需要这些绕行,指挥官应先把缺失能力补进 `device-pod-cli`/`hwpod`,再重置 workspace 让 DS 复测。
|
||||
|
||||
Device Pod 类 DS 验收不能只看最终回复。指挥官必须用 `codex task <taskId> --trace` / `codex output <taskId>` 审计实际命令面:确认是否使用 `hwpod`,是否出现长 CLI 路径、`tran.mjs`、`hwlab-gateway-tran.mjs`、临时脚本上传、`workspace put` 或构建产物 patch/put/delete;同时核对 build job、download job、UART/JSON-RPC 或屏幕/串口等硬件证据。若任务因为模型上游 429/503、transport 断连或 Code Queue continuation 被错误降级而没有进入工具调用,不应把它记作 device-pod-cli 失败样本,应先处理调度/运行面摩擦,再重新派发干净任务。
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ const deployJson = JSON.parse(readFileSync("deploy.json", "utf8")) as {
|
||||
const runtimePreflight = readFileSync("src/components/microservices/code-queue/src/runtime-preflight.ts", "utf8");
|
||||
const indexSource = readFileSync("src/components/microservices/code-queue/src/index.ts", "utf8");
|
||||
const skillModule = readFileSync("src/components/microservices/code-queue/src/skill-availability.ts", "utf8");
|
||||
const providerRuntimeSource = readFileSync("src/components/microservices/code-queue/src/provider-runtime.ts", "utf8");
|
||||
const codeQueueDockerfile = readFileSync("src/components/microservices/code-queue/Dockerfile", "utf8");
|
||||
const hwpodWrapper = readFileSync("scripts/hwpod", "utf8");
|
||||
const codeQueueCli = readFileSync("scripts/src/code-queue.ts", "utf8");
|
||||
const microserviceCli = readFileSync("scripts/src/microservices.ts", "utf8");
|
||||
const helpSource = readFileSync("scripts/src/help.ts", "utf8");
|
||||
@@ -88,6 +91,10 @@ assertCondition(devManifest.includes("path: /home/ubuntu/.agents/skills"), "dev
|
||||
assertCondition(countOccurrences(devManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH") === 3, "dev read/write/scheduler must expose the approved runner skills source path", {
|
||||
count: countOccurrences(devManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH"),
|
||||
});
|
||||
assertCondition(codeQueueDockerfile.includes("COPY scripts/hwpod /usr/local/bin/hwpod"), "Code Queue image must install the hwpod short alias", codeQueueDockerfile);
|
||||
assertCondition(codeQueueDockerfile.includes("chmod 755 /usr/local/bin/tran /usr/local/bin/hwpod"), "Code Queue image must make hwpod executable", codeQueueDockerfile);
|
||||
assertCondition(hwpodWrapper.includes("DEVICE_POD_CLI") && hwpodWrapper.includes("UNIDESK_SKILLS_PATH") && hwpodWrapper.includes("skills/device-pod-cli/scripts/device-pod-cli.mjs"), "hwpod wrapper must resolve generic device-pod-cli locations", hwpodWrapper);
|
||||
assertCondition(hwpodWrapper.includes("tools/device-pod-cli.mjs") && hwpodWrapper.includes("exec node"), "hwpod wrapper must support repo-local tools/device-pod-cli.mjs and exec through node", hwpodWrapper);
|
||||
|
||||
const devCodeQueueDeploy = (deployJson.environments?.dev?.services ?? []).find((service) => service.id === "code-queue");
|
||||
assertCondition(devCodeQueueDeploy !== undefined, "deploy.json dev environment must include code-queue");
|
||||
@@ -146,6 +153,7 @@ configureProviderRuntime({
|
||||
});
|
||||
const devContainerPlan = buildDevContainerPlan("D601", { workdir: "/home/ubuntu" });
|
||||
const devContainerStartScript = providerRuntimeForTest.remoteContainerStartScript(devContainerPlan, false);
|
||||
assertCondition(providerRuntimeSource.includes("hwpodWrapperSource") && providerRuntimeSource.includes("/usr/local/bin/hwpod") && providerRuntimeSource.includes("hwpod=$(command -v hwpod)"), "provider dev container runtime prepare must install and report hwpod", providerRuntimeSource);
|
||||
assertCondition(devContainerStartScript.includes("SKILLS_SOURCE='/home/ubuntu/.agents/skills'"), "provider dev container start must use the D601 host skills source", devContainerStartScript);
|
||||
assertCondition(devContainerStartScript.includes("SKILLS_TARGET='/root/.agents/skills'"), "provider dev container start must use the runner target skills path", devContainerStartScript);
|
||||
assertCondition(devContainerStartScript.includes('-v "$SKILLS_SOURCE":"$SKILLS_TARGET":ro'), "provider dev container must bind source skills read-only to target", devContainerStartScript);
|
||||
@@ -209,8 +217,12 @@ const fixtureSource = join(tmpRoot, "source");
|
||||
const fixtureMissingTarget = join(tmpRoot, "target-missing");
|
||||
const fixtureSymlinkTarget = join(tmpRoot, "target-symlink");
|
||||
const fixtureMissingSource = join(tmpRoot, "source-missing");
|
||||
const fixtureApprovedSource = join(tmpRoot, "approved-source");
|
||||
const fixtureArbitraryTarget = join(tmpRoot, "arbitrary-target");
|
||||
mkdirSync(fixtureSource, { recursive: true });
|
||||
mkdirSync(fixtureApprovedSource, { recursive: true });
|
||||
createSkillSet(fixtureSource, ["docs-spec", "cli-spec"]);
|
||||
createSkillSet(fixtureApprovedSource, ["docs-spec", "cli-spec"]);
|
||||
symlinkSync(fixtureSource, fixtureSymlinkTarget, "dir");
|
||||
|
||||
const missingTargetWithSource = collectSkillAvailability({
|
||||
@@ -250,18 +262,9 @@ assertCondition(missingBoth.ok === false && missingBoth.runnerUsable === false,
|
||||
assertCondition(missingBoth.blocker === "skills-source-and-target-missing", "missing both should expose dedicated blocker", missingBoth);
|
||||
assertCondition(missingBoth.resolvedPathSource === "missing", "missing both should expose missing resolution", missingBoth);
|
||||
|
||||
const redactionProbe = collectSkillAvailability({
|
||||
source: fixtureSource,
|
||||
target: fixtureMissingTarget,
|
||||
requiredSkills: ["docs-spec", "cli-spec"],
|
||||
});
|
||||
assertCondition(!JSON.stringify(redactionProbe).includes("ghp_"), "skill report must not include token-like values", redactionProbe);
|
||||
assertCondition(!JSON.stringify(redactionProbe).includes("github_pat_"), "skill report must not include GitHub PAT-like values", redactionProbe);
|
||||
rmSync(tmpRoot, { recursive: true, force: true });
|
||||
|
||||
const missing = collectSkillAvailability({
|
||||
source: "/home/ubuntu/.agents/skills",
|
||||
target: "/path/that/does/not/exist/for-code-queue-skills-test",
|
||||
source: fixtureApprovedSource,
|
||||
target: fixtureArbitraryTarget,
|
||||
requiredSkills: ["docs-spec", "cli-spec"],
|
||||
});
|
||||
assertCondition(missing.ok === false, "approved source must not keep missing-target runner usable");
|
||||
@@ -270,7 +273,7 @@ assertCondition(missing.contractOk === false, "missing target with approved sour
|
||||
assertCondition(missing.degraded === true, "missing target should be degraded");
|
||||
assertCondition(missing.blocker === "skills-target-missing", "missing target should expose blocker", missing);
|
||||
assertCondition(missing.targetMissingSkills.includes("docs-spec") && missing.targetMissingSkills.includes("cli-spec"), "missing target should list target missing skills", missing);
|
||||
assertCondition(missing.resolvedPath === "/path/that/does/not/exist/for-code-queue-skills-test", "missing target should keep the configured target path", missing);
|
||||
assertCondition(missing.resolvedPath === fixtureArbitraryTarget, "missing target should keep the configured target path", missing);
|
||||
assertCondition(missing.resolvedPathSource === "missing", "missing target should not expose source fallback", missing);
|
||||
assertCondition(missing.valuesPrinted === false, "missing report must also declare valuesPrinted=false");
|
||||
|
||||
@@ -287,14 +290,14 @@ assertCondition(JSON.stringify(asRecord(typoTarget.pathSpelling, "typoTarget.pat
|
||||
assertCondition(typoTarget.valuesPrinted === false, "misspelled target report must declare valuesPrinted=false");
|
||||
|
||||
const syncDryRun = collectSkillSyncPreflight({
|
||||
source: "/home/ubuntu/.agents/skills",
|
||||
target: "/path/that/does/not/exist/for-code-queue-skills-test",
|
||||
source: fixtureApprovedSource,
|
||||
target: fixtureArbitraryTarget,
|
||||
requiredSkills: ["docs-spec", "cli-spec"],
|
||||
});
|
||||
assertCondition(syncDryRun.dryRun === true && syncDryRun.mutation === false, "skills sync contract must be dry-run and non-mutating", syncDryRun);
|
||||
assertCondition(syncDryRun.syncMode === "hostPath-read-only-projection", "skills sync must describe the hostPath projection lifecycle", syncDryRun);
|
||||
assertCondition(syncDryRun.source.path === "/home/ubuntu/.agents/skills", "skills sync must expose source", syncDryRun.source);
|
||||
assertCondition(syncDryRun.target.path === "/path/that/does/not/exist/for-code-queue-skills-test", "skills sync must expose target", syncDryRun.target);
|
||||
assertCondition(syncDryRun.source.path === fixtureApprovedSource, "skills sync must expose source", syncDryRun.source);
|
||||
assertCondition(syncDryRun.target.path === fixtureArbitraryTarget, "skills sync must expose target", syncDryRun.target);
|
||||
assertCondition(syncDryRun.expected.source === "/home/ubuntu/.agents/skills", "skills sync must expose stable expected source", syncDryRun.expected);
|
||||
assertCondition(syncDryRun.expected.target === "/root/.agents/skills", "skills sync must expose stable expected target", syncDryRun.expected);
|
||||
assertCondition(syncDryRun.expected.env === "UNIDESK_SKILLS_PATH" && syncDryRun.expected.envValue === "/root/.agents/skills", "skills sync must expose env contract", syncDryRun.expected);
|
||||
@@ -303,13 +306,21 @@ assertCondition(syncDryRun.counts.targetSkills === 0 && syncDryRun.counts.missin
|
||||
assertCondition(asRecord(syncDryRun.version, "syncDryRun.version").sourceFingerprint !== undefined, "skills sync must expose source fingerprint", syncDryRun.version);
|
||||
assertCondition(asRecord(syncDryRun.version, "syncDryRun.version").targetLatestMtime !== undefined, "skills sync must expose target mtime", syncDryRun.version);
|
||||
assertCondition(syncDryRun.missing.targetSkills.includes("docs-spec") && syncDryRun.missing.targetSkills.includes("cli-spec"), "skills sync must expose missing target skills", syncDryRun.missing);
|
||||
assertCondition(syncDryRun.blocker === "unapproved-target", "arbitrary target paths must be blocked before silent copying", syncDryRun);
|
||||
assertCondition(["unapproved-source", "unapproved-target"].includes(String(syncDryRun.blocker)), "arbitrary source or target paths must be blocked before silent copying", syncDryRun);
|
||||
assertCondition(syncDryRun.plannedActions.copy === false && syncDryRun.plannedActions.copyFromArbitraryPath === false, "skills sync dry-run must not plan arbitrary copy", syncDryRun.plannedActions);
|
||||
assertCondition(syncDryRun.plannedActions.restartRequired === false && syncDryRun.plannedActions.readsSecrets === false, "skills sync dry-run must not require restart or read secrets", syncDryRun.plannedActions);
|
||||
assertCondition(Array.isArray(syncDryRun.instructions) && syncDryRun.instructions.some((item) => item.includes("read-only hostPath projection")), "skills sync must include lifecycle instructions", syncDryRun.instructions);
|
||||
assertCondition(syncDryRun.valuesPrinted === false, "skills sync must declare valuesPrinted=false", syncDryRun);
|
||||
assertCondition(!JSON.stringify(syncDryRun).includes(forbiddenPathLiteral), "skills sync report must not propagate misspelled path literal");
|
||||
|
||||
const redactionProbe = collectSkillAvailability({
|
||||
source: fixtureSource,
|
||||
target: fixtureMissingTarget,
|
||||
requiredSkills: ["docs-spec", "cli-spec"],
|
||||
});
|
||||
assertCondition(!JSON.stringify(redactionProbe).includes("ghp_"), "skill report must not include token-like values", redactionProbe);
|
||||
assertCondition(!JSON.stringify(redactionProbe).includes("github_pat_"), "skill report must not include GitHub PAT-like values", redactionProbe);
|
||||
|
||||
const missingTargetSync = collectSkillSyncPreflight({ target: "/path/that/does/not/exist/for-code-queue-skills-test" });
|
||||
assertCondition(missingTargetSync.blocker === "unapproved-target", "non-default target must be rejected as unapproved", missingTargetSync);
|
||||
|
||||
@@ -421,8 +432,8 @@ const preflightSkills = asRecord(preflight.skills, "preflight.skills");
|
||||
const preflightSkillsSync = asRecord(preflight.skillsSync, "preflight.skillsSync");
|
||||
assertCondition(preflightSummary.failureKind === "runner-skills-blocker", "full preflight should classify missing target as runner blocker", preflightSummary);
|
||||
assertCondition(asRecord(preflightSummary.skillsContract, "preflightSummary.skillsContract").degradedReason === "skills-target-missing", "full preflight should expose target missing as contract degraded reason", preflightSummary);
|
||||
assertCondition(preflightSkills.target === "/path/that/does/not/exist/for-code-queue-skills-test", "full preflight must show skills target", preflightSkills);
|
||||
assertCondition(preflightSkills.resolvedPath === "/path/that/does/not/exist/for-code-queue-skills-test", "full preflight must keep resolved path at the target", preflightSkills);
|
||||
assertCondition(preflightSkills.target === fixtureArbitraryTarget, "full preflight must show skills target", preflightSkills);
|
||||
assertCondition(preflightSkills.resolvedPath === fixtureArbitraryTarget, "full preflight must keep resolved path at the target", preflightSkills);
|
||||
assertCondition(preflightSkills.resolvedPathSource === "missing", "full preflight must not show source fallback resolution", preflightSkills);
|
||||
assertCondition(preflightSkillsSync.dryRun === true && preflightSkillsSync.mutation === false, "full preflight must show non-mutating skills sync dry-run", preflightSkillsSync);
|
||||
assertCondition(asRecord(preflightSkillsSync.counts, "preflight.skillsSync.counts").missingTargetSkills === 2, "full preflight must show missing target count", preflightSkillsSync);
|
||||
@@ -690,7 +701,7 @@ const microservice = asRecord(healthSummary.microservice, "microservice");
|
||||
const healthCompact = asRecord(microservice.summary, "microservice.summary");
|
||||
const healthSkills = asRecord(healthCompact.skills, "microservice.summary.skills");
|
||||
const healthSkillsSync = asRecord(healthCompact.skillsSync, "microservice.summary.skillsSync");
|
||||
assertCondition(healthSkills.target === "/path/that/does/not/exist/for-code-queue-skills-test", "compact health must show skills target", healthSkills);
|
||||
assertCondition(healthSkills.target === fixtureArbitraryTarget, "compact health must show skills target", healthSkills);
|
||||
assertCondition(healthSkillsSync.dryRun === true && healthSkillsSync.mutation === false, "compact health must show dry-run skills sync", healthSkillsSync);
|
||||
assertCondition(asRecord(healthSkillsSync.counts, "microservice.summary.skillsSync.counts").missingTargetSkills === 2, "compact health must show missing target count", healthSkillsSync);
|
||||
assertCondition(asRecord(healthSkillsSync.plannedActions, "microservice.summary.skillsSync.plannedActions").copyFromArbitraryPath === false, "compact health must show arbitrary copy is blocked", healthSkillsSync);
|
||||
@@ -716,6 +727,7 @@ process.stdout.write(`${JSON.stringify({
|
||||
checks: [
|
||||
"production Code Queue mounts /home/ubuntu/.agents/skills read-only at /root/.agents/skills",
|
||||
"provider dev containers bind /home/ubuntu/.agents/skills read-only at /root/.agents/skills and pass UNIDESK_SKILLS_PATH to Codex/OpenCode",
|
||||
"Code Queue image and provider dev containers expose hwpod as the short device-pod-cli alias without binding it to a specific pod",
|
||||
"deploy.json dev Code Queue pins a commit whose manifest and runtime require the target skills projection",
|
||||
"skill availability report exposes source, target, requiredSkills, missingSkills, version fingerprint/mtime, degraded/blocker and valuesPrinted=false",
|
||||
"skills sync dry-run reports source, target, counts, version fingerprint/mtime, missing skills, permission failures, instructions and no-copy actions",
|
||||
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
candidate_paths=()
|
||||
|
||||
add_candidate() {
|
||||
local value="${1:-}"
|
||||
if [ -n "$value" ]; then
|
||||
candidate_paths+=("$value")
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -n "${DEVICE_POD_CLI:-}" ]; then
|
||||
add_candidate "$DEVICE_POD_CLI"
|
||||
fi
|
||||
|
||||
for base in \
|
||||
"${UNIDESK_SKILLS_PATH:-}" \
|
||||
/app/skills \
|
||||
/root/.agents/skills \
|
||||
/home/ubuntu/.agents/skills; do
|
||||
if [ -n "$base" ]; then
|
||||
add_candidate "$base/device-pod-cli/scripts/device-pod-cli.mjs"
|
||||
fi
|
||||
done
|
||||
|
||||
for base in \
|
||||
"$PWD" \
|
||||
/workspace/hwlab \
|
||||
/root/hwlab \
|
||||
/app \
|
||||
/root/unidesk; do
|
||||
if [ -n "$base" ]; then
|
||||
add_candidate "$base/skills/device-pod-cli/scripts/device-pod-cli.mjs"
|
||||
add_candidate "$base/tools/device-pod-cli.mjs"
|
||||
fi
|
||||
done
|
||||
|
||||
dir="$PWD"
|
||||
while [ "$dir" != "/" ]; do
|
||||
add_candidate "$dir/skills/device-pod-cli/scripts/device-pod-cli.mjs"
|
||||
add_candidate "$dir/tools/device-pod-cli.mjs"
|
||||
dir="$(dirname "$dir")"
|
||||
done
|
||||
|
||||
for cli in "${candidate_paths[@]}"; do
|
||||
if [ -f "$cli" ]; then
|
||||
exec node "$cli" "$@"
|
||||
fi
|
||||
done
|
||||
|
||||
{
|
||||
echo "hwpod: device-pod-cli.mjs not found."
|
||||
echo "hwpod searches DEVICE_POD_CLI, UNIDESK_SKILLS_PATH/device-pod-cli, ./skills/device-pod-cli, and ./tools/device-pod-cli.mjs."
|
||||
echo "Set DEVICE_POD_CLI=/path/to/device-pod-cli.mjs or run from a HWLAB workspace that contains skills/device-pod-cli."
|
||||
} >&2
|
||||
exit 127
|
||||
@@ -8,7 +8,8 @@ ENV CARGO_HOME=/usr/local/cargo
|
||||
ENV PATH=/usr/local/cargo/bin:${PATH}
|
||||
|
||||
COPY scripts/tran /usr/local/bin/tran
|
||||
RUN chmod 755 /usr/local/bin/tran
|
||||
COPY scripts/hwpod /usr/local/bin/hwpod
|
||||
RUN chmod 755 /usr/local/bin/tran /usr/local/bin/hwpod
|
||||
|
||||
RUN (command -v docker >/dev/null 2>&1 && docker buildx version >/dev/null 2>&1 && command -v gh >/dev/null 2>&1 && command -v rg >/dev/null 2>&1 && command -v cargo >/dev/null 2>&1 && command -v rustc >/dev/null 2>&1 && command -v rustfmt >/dev/null 2>&1 && command -v xvfb-run >/dev/null 2>&1) \
|
||||
|| (apt-get update \
|
||||
|
||||
@@ -975,7 +975,21 @@ printf %s ${shellQuote(bridgeBase64)} | base64 -d > ${shellQuote(resolve(ctx().c
|
||||
chmod 700 ${shellQuote(resolve(ctx().config.windowsNativeCodexBridgeDir, "bridge.py"))}`;
|
||||
}
|
||||
|
||||
function hwpodWrapperSource(): string {
|
||||
const candidates = [
|
||||
"/usr/local/bin/hwpod",
|
||||
resolve(ctx().config.defaultWorkdir, "scripts/hwpod"),
|
||||
"/app/scripts/hwpod",
|
||||
"/root/unidesk/scripts/hwpod",
|
||||
resolve(process.cwd(), "scripts/hwpod"),
|
||||
];
|
||||
const path = candidates.find((candidate) => existsSync(candidate));
|
||||
if (path === undefined) throw new Error(`hwpod wrapper source not found in ${candidates.join(", ")}`);
|
||||
return readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
function remoteCodexRuntimePrepareScript(plan: DevContainerPlan): string {
|
||||
const hwpodWrapper = base64Text(hwpodWrapperSource());
|
||||
return `set -euo pipefail
|
||||
CONTAINER=${shellQuote(plan.containerName)}
|
||||
docker exec -i "$CONTAINER" bash -s <<'INNER'
|
||||
@@ -1008,8 +1022,10 @@ fi
|
||||
if ! command -v opencode >/dev/null 2>&1; then
|
||||
npm install -g --no-audit --no-fund --prefer-offline ${opencodeNpmPackage}
|
||||
fi
|
||||
printf %s ${shellQuote(hwpodWrapper)} | base64 -d > /usr/local/bin/hwpod
|
||||
chmod 755 /usr/local/bin/hwpod
|
||||
mkdir -p "$WORKDIR" "$CODEX_HOME_DIR" "$OPENCODE_XDG_DIR/data" "$OPENCODE_XDG_DIR/config" "$OPENCODE_XDG_DIR/cache" "$OPENCODE_XDG_DIR/state"
|
||||
echo "code_agent_runtime_ready codex=$(command -v codex) opencode=$(command -v opencode) cwd=$WORKDIR home=$CODEX_HOME_DIR opencodeXdg=$OPENCODE_XDG_DIR"
|
||||
echo "code_agent_runtime_ready codex=$(command -v codex) opencode=$(command -v opencode) hwpod=$(command -v hwpod) cwd=$WORKDIR home=$CODEX_HOME_DIR opencodeXdg=$OPENCODE_XDG_DIR"
|
||||
INNER`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user