Merge pull request #64 from pikasTech/fix/v01-resource-alias-default-path

fix: expose resource aliases on runner path
This commit is contained in:
Lyon
2026-06-02 09:13:42 +08:00
committed by GitHub
3 changed files with 43 additions and 10 deletions
+2
View File
@@ -3,6 +3,7 @@ import type { BackendProfile, ExecutionPolicy, JsonRecord, JsonValue, RunRecord,
import { backendProfileSpec } from "../common/backend-profiles.js";
const defaultBootRepoUrl = "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/agentrun.git";
const defaultResourceBinPath = "/usr/local/bin";
export interface RunnerJobRenderOptions {
run: RunRecord;
@@ -165,6 +166,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string
{ name: "AGENTRUN_SESSION_REF_JSON", value: JSON.stringify(options.run.sessionRef ?? null) },
{ name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) },
{ name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" },
{ name: "AGENTRUN_RESOURCE_BIN_PATH", value: defaultResourceBinPath },
{ name: "AGENTRUN_SOURCE_COMMIT", value: context.sourceCommit },
{ name: "AGENTRUN_BOOT_COMMIT", value: context.sourceCommit },
{ name: "AGENTRUN_BOOT_REPO_URL", value: defaultBootRepoUrl },
+20 -6
View File
@@ -1,5 +1,5 @@
import { spawn } from "node:child_process";
import { chmod, mkdir, writeFile } from "node:fs/promises";
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { AgentRunError } from "../common/errors.js";
import { redactText } from "../common/redaction.js";
@@ -59,7 +59,9 @@ async function materializeToolAliases(checkoutPath: string, aliases: NonNullable
for (const alias of aliases) {
const target = resolveBundlePath(checkoutPath, alias.path, `toolAliases.${alias.name}.path`);
const wrapper = path.join(binPath, alias.name);
await writeFile(wrapper, aliasWrapper(alias.kind, target), "utf8");
const content = aliasWrapper(alias.kind, target);
await assertAliasWrapperWritable(wrapper, alias.name);
await writeFile(wrapper, content, "utf8");
await chmod(wrapper, 0o755);
names.push(alias.name);
}
@@ -67,10 +69,22 @@ async function materializeToolAliases(checkoutPath: string, aliases: NonNullable
}
function aliasWrapper(kind: string, target: string): string {
if (kind === "node-script") return `#!/usr/bin/env sh\nexec node ${shellArg(target)} "$@"\n`;
if (kind === "bun-script") return `#!/usr/bin/env sh\nexec bun ${shellArg(target)} "$@"\n`;
if (kind === "sh-script") return `#!/usr/bin/env sh\nexec sh ${shellArg(target)} "$@"\n`;
return `#!/usr/bin/env sh\nexec ${shellArg(target)} "$@"\n`;
if (kind === "node-script") return `#!/usr/bin/env sh\n# agentrun-resource-alias-wrapper\nexec node ${shellArg(target)} "$@"\n`;
if (kind === "bun-script") return `#!/usr/bin/env sh\n# agentrun-resource-alias-wrapper\nexec bun ${shellArg(target)} "$@"\n`;
if (kind === "sh-script") return `#!/usr/bin/env sh\n# agentrun-resource-alias-wrapper\nexec sh ${shellArg(target)} "$@"\n`;
return `#!/usr/bin/env sh\n# agentrun-resource-alias-wrapper\nexec ${shellArg(target)} "$@"\n`;
}
async function assertAliasWrapperWritable(wrapper: string, name: string): Promise<void> {
try {
const existing = await readFile(wrapper, "utf8");
if (existing.includes("agentrun-resource-alias-wrapper")) return;
throw new AgentRunError("schema-invalid", `resource bundle tool alias ${name} would overwrite an existing command`, { httpStatus: 409, details: { wrapper: pathSummary(wrapper) } });
} catch (error) {
if (error instanceof AgentRunError) throw error;
if (error && typeof error === "object" && "code" in error && (error as { code?: unknown }).code === "ENOENT") return;
throw error;
}
}
async function git(args: string[], cwd: string, options: { allowFailure?: boolean } = {}): Promise<{ stdout: string; stderr: string }> {
+21 -4
View File
@@ -7,10 +7,11 @@ import { startManagerServer } from "../../mgr/server.js";
import { ManagerClient } from "../../mgr/client.js";
import { MemoryAgentRunStore } from "../../mgr/store.js";
import { runOnce } from "../../runner/run-once.js";
import type { JsonRecord } from "../../common/types.js";
import type { JsonRecord, ResourceBundleRef } from "../../common/types.js";
import { assertNoSecretLeak, type SelfTestCase, type SelfTestContext } from "../harness.js";
const execFile = promisify(execFileCallback);
type LocalBundle = { repoUrl: string; commitId: string; toolAliases?: ResourceBundleRef["toolAliases"] };
const selfTest: SelfTestCase = async (context) => {
const containerfile = await readFile(path.join(context.root, "deploy/container/Containerfile"), "utf8");
@@ -56,6 +57,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
);
const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord;
assert.ok(JSON.stringify(manifest).includes("AGENTRUN_RESOURCE_BUNDLE_JSON"));
assert.equal(runnerEnvValue(manifest, "AGENTRUN_RESOURCE_BIN_PATH"), "/usr/local/bin");
assert.ok(JSON.stringify(manifest).includes("/opt/agentrun/deploy/runtime/boot/agentrun-runner.sh"));
assert.ok(JSON.stringify(manifest).includes("AGENTRUN_BOOT_COMMIT"));
assertNoSecretLeak(created);
@@ -78,6 +80,11 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
assert.equal(runResult.terminalStatus, "completed");
const hwpod = await execFile(path.join(resourceBin, "hwpod"), ["profile", "list"]);
assert.match(hwpod.stdout, /"argv":\["profile","list"\]/u);
await writeFile(path.join(resourceBin, "blocked"), "#!/usr/bin/env sh\necho existing\n", "utf8");
const blockedRun = await createHwlabRun(client, context, { ...bundle, toolAliases: [{ name: "blocked", path: "tools/device-pod-cli.mjs", kind: "node-script" }] }, "hwlab-session-blocked-alias", "blocked alias", "hwlab-command-blocked-alias");
const blockedResult = await runOnce({ managerUrl: server.baseUrl, runId: blockedRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces-blocked"), AGENTRUN_RESOURCE_BIN_PATH: resourceBin }, oneShot: true });
assert.equal(blockedResult.terminalStatus, "blocked");
assert.equal(blockedResult.failureKind, "schema-invalid");
const session = await store.getSession("hwlab-session-resume");
assert.equal(session?.threadId, "thread_selftest_1");
const resultEnvelope = await client.get(`/api/v1/runs/${sessionRun.runId}/commands/${sessionRun.commandId}/result`) as JsonRecord;
@@ -137,7 +144,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
}
};
async function createLocalGitBundle(context: SelfTestContext): Promise<{ repoUrl: string; commitId: string }> {
async function createLocalGitBundle(context: SelfTestContext): Promise<LocalBundle> {
const repo = path.join(context.tmp, "bundle-repo");
await mkdir(repo, { recursive: true });
await execFile("git", ["init"], { cwd: repo });
@@ -150,13 +157,14 @@ async function createLocalGitBundle(context: SelfTestContext): Promise<{ repoUrl
return { repoUrl: repo, commitId: stdout.trim() };
}
async function createHwlabRun(client: ManagerClient, context: SelfTestContext, bundle: { repoUrl: string; commitId: string }, sessionId: string, prompt: string, idempotencyKey: string, timeoutMs = 15_000): Promise<{ runId: string; commandId: string }> {
async function createHwlabRun(client: ManagerClient, context: SelfTestContext, bundle: LocalBundle, sessionId: string, prompt: string, idempotencyKey: string, timeoutMs = 15_000): Promise<{ runId: string; commandId: string }> {
const toolAliases = bundle.toolAliases ?? [{ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }];
const run = await client.post("/api/v1/runs", {
tenantId: "hwlab",
projectId: "pikasTech/HWLAB",
workspaceRef: { kind: "opaque", repo: "pikasTech/HWLAB" },
sessionRef: { sessionId, conversationId: sessionId },
resourceBundleRef: { kind: "git", repoUrl: bundle.repoUrl, commitId: bundle.commitId, toolAliases: [{ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }], submodules: false, lfs: false },
resourceBundleRef: { kind: "git", repoUrl: bundle.repoUrl, commitId: bundle.commitId, toolAliases, submodules: false, lfs: false },
providerId: "G14",
backendProfile: "codex",
executionPolicy: {
@@ -195,4 +203,13 @@ async function readTextIfExists(filePath: string): Promise<string> {
}
}
function runnerEnvValue(manifest: JsonRecord, name: string): unknown {
const spec = manifest.spec as JsonRecord;
const template = spec.template as JsonRecord;
const podSpec = template.spec as JsonRecord;
const containers = podSpec.containers as JsonRecord[];
const env = containers[0]?.env as JsonRecord[];
return env.find((entry) => entry.name === name)?.value;
}
export default selfTest;