Files
pikasTech-unidesk/scripts/src/agentrun.test.ts
T

826 lines
38 KiB
TypeScript

import { readFileSync, rmSync } from "node:fs";
import { describe, expect, test } from "bun:test";
import { parseAgentRunClientConfigYaml, resolveAgentRunAuth, runAgentRunCommand, stripAgentRunResourceWrapperArgs } from "./agentrun";
import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
import { placeholderAgentRunImage, renderAgentRunGitopsFiles } from "./agentrun-manifests";
function renderedTextOf(value: unknown): string {
if (typeof value !== "object" || value === null) throw new Error("expected rendered CLI result");
const text = (value as { renderedText?: unknown }).renderedText;
if (typeof text !== "string") throw new Error("expected renderedText string");
return text;
}
function renderedContentTypeOf(value: unknown): string {
if (typeof value !== "object" || value === null) throw new Error("expected rendered CLI result");
const contentType = (value as { contentType?: unknown }).contentType;
if (typeof contentType !== "string") throw new Error("expected contentType string");
return contentType;
}
const agentRunClientYaml = [
"version: 1",
"kind: AgentRunConfig",
"metadata:",
" name: agentrun",
"manager:",
" baseUrl: https://agentrun.127-0-0-1.nip.io/",
" timeoutMs: 15000",
"publicExposure:",
" enabled: true",
" proxyName: agentrun-v01-frpc",
" remotePort: 22880",
" publicBaseUrl: https://agentrun.127-0-0-1.nip.io/",
" masterBaseUrl: http://127.0.0.1:22880",
" masterFrps:",
" configPath: /opt/hwlab-frp/frps.dev.toml",
" containerName: hwlab-frps-dev",
" masterCaddy:",
" enabled: true",
" domain: agentrun.127-0-0-1.nip.io",
" configPath: /etc/caddy/Caddyfile",
" serviceName: caddy",
" upstreamBaseUrl: http://127.0.0.1:22880",
" responseHeaderTimeoutSeconds: 60",
"auth:",
" env: HWLAB_API_KEY",
" file: /tmp/hwlab-api-key.env",
" header: Authorization",
" scheme: Bearer",
"client:",
" role: render-only",
" transport: direct-http",
" sessionPolicy:",
" tenantId: unidesk",
" projectId: test",
" providerId: NC01",
" backendProfile: codex",
" workspaceRef:",
" kind: opaque",
" executionPolicy:",
" sandbox: workspace-write",
" approval: never",
" timeoutMs: 900000",
" network: enabled",
" secretScope:",
" allowCredentialEcho: false",
].join("\n");
const agentRunMinimalClientYaml = [
"version: 1",
"kind: AgentRunConfig",
"metadata:",
" name: agentrun",
"manager:",
" baseUrl: http://agentrun.example.local:8080",
" timeoutMs: 15000",
"auth:",
" env: HWLAB_API_KEY",
" file: /tmp/hwlab-api-key.env",
" header: Authorization",
" scheme: Bearer",
"client:",
" role: render-only",
" transport: direct-http",
" sessionPolicy:",
" tenantId: unidesk",
" projectId: test",
" providerId: NC01",
" backendProfile: codex",
" workspaceRef:",
" kind: opaque",
" executionPolicy:",
" sandbox: workspace-write",
" approval: never",
" timeoutMs: 900000",
" network: enabled",
" secretScope:",
" allowCredentialEcho: false",
].join("\n");
describe("AgentRun resource bridge argv", () => {
test("does not forward UniDesk output flags to create task prompt argv", () => {
expect(stripAgentRunResourceWrapperArgs([
"--aipod",
"Artificer",
"--prompt-stdin",
"-o",
"json",
"--idempotency-key",
"case-262",
"--dry-run",
])).toEqual([
"--aipod",
"Artificer",
"--prompt-stdin",
"--idempotency-key",
"case-262",
"--dry-run",
]);
});
test("strips wrapper display flags while preserving official apply arguments", () => {
expect(stripAgentRunResourceWrapperArgs([
"-f",
"-",
"--output=yaml",
"--raw",
"--full",
"--input",
"--full-text",
"--dry-run",
])).toEqual([
"-f",
"-",
"--dry-run",
]);
});
});
describe("AgentRun render-only REST client config", () => {
test("parses explicit YAML config for direct REST render-only client", () => {
const config = parseAgentRunClientConfigYaml(agentRunClientYaml, "config/agentrun.yaml");
expect(config.manager.baseUrl).toBe("https://agentrun.127-0-0-1.nip.io/");
expect(config.publicExposure?.publicBaseUrl).toBe("https://agentrun.127-0-0-1.nip.io/");
expect(config.publicExposure?.masterCaddy.domain).toBe("agentrun.127-0-0-1.nip.io");
expect(config.auth.env).toBe("HWLAB_API_KEY");
expect(config.auth.header).toBe("Authorization");
expect(config.auth.scheme).toBe("Bearer");
expect(config.client.role).toBe("render-only");
expect(config.client.transport).toBe("direct-http");
});
test("uses env auth before configured file auth without exposing the key", () => {
const config = parseAgentRunClientConfigYaml(agentRunClientYaml, "config/agentrun.yaml");
const auth = resolveAgentRunAuth(config, { HWLAB_API_KEY: "secret-value" });
expect(auth.source).toBe("env");
expect(auth.value).toBe("secret-value");
});
test("requires explicit render-only direct-http client contract", () => {
expect(() => parseAgentRunClientConfigYaml(agentRunClientYaml.replace(" transport: direct-http", " transport: ssh-bridge"), "config/agentrun.yaml")).toThrow("client.transport must be direct-http");
expect(() => parseAgentRunClientConfigYaml(agentRunClientYaml.replace(" role: render-only", " role: proxy"), "config/agentrun.yaml")).toThrow("client.role must be render-only");
});
});
describe("AgentRun default transport contract", () => {
test("server-facing compatibility groups use REST dispatcher, not official SSH CLI wrapper", () => {
const source = readFileSync(new URL("./agentrun/entry.ts", import.meta.url), "utf8");
const commandRouter = source.slice(source.indexOf("export async function runAgentRunCommand"), source.indexOf("function isAgentRunRestCompatGroup"));
expect(commandRouter).toContain("runAgentRunRestCompatCommand");
expect(commandRouter).not.toContain("runOfficialAgentRunCli");
expect(commandRouter).not.toContain("runPreparedOfficialAgentRunCli");
});
test("resource verbs can use direct REST without UniDesk SSH config", async () => {
const server = Bun.serve({
port: 0,
fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
expect(url.pathname).toBe("/api/v1/queue/tasks");
expect(url.searchParams.get("queue")).toBe("commander");
expect(url.searchParams.get("state")).toBe("running");
return Response.json({
ok: true,
data: {
items: [
{ id: "qt_1", state: "queued", queue: "commander" },
],
},
});
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(process.env.AGENTRUN_CLIENT_CONFIG, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const result = await runAgentRunCommand(null, ["get", "tasks", "--queue", "commander", "-o", "json"]);
expect(result.ok).toBe(true);
expect("renderedText" in result).toBe(true);
if ("renderedText" in result) {
expect(result.renderedText).toContain("\"kind\": \"TaskList\"");
expect(result.renderedText).toContain("\"name\": \"qt_1\"");
}
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
test("task input drill-down is bounded, reconstructable, and SecretRef-only", async () => {
const taskId = "qt_artificer_input";
const hiddenCredential = "HIDDEN_CREDENTIAL_VALUE_MUST_NOT_BE_PRINTED";
const task = {
id: taskId,
tenantId: "unidesk",
projectId: "pikasTech/unidesk",
queue: "commander",
lane: "v0.1",
title: "Artificer input fixture",
priority: 50,
state: "completed",
version: 7,
backendProfile: "sub2api",
providerId: "G14",
workspaceRef: { kind: "opaque", path: ".", repo: "pikasTech/unidesk", branch: "master" },
sessionRef: { sessionId: "ses_fixture", conversationId: "conv_fixture", metadata: { credential: hiddenCredential } },
executionPolicy: {
sandbox: "workspace-write",
approval: "never",
timeoutMs: 1_800_000,
network: "enabled",
secretScope: {
allowCredentialEcho: false,
providerCredentials: [{
profile: "sub2api",
secretRef: { name: "agentrun-provider", keys: ["auth.json"], value: hiddenCredential },
value: hiddenCredential,
}],
toolCredentials: [{
tool: "github",
purpose: "github-pr",
secretRef: { name: "agentrun-github", keys: ["GH_TOKEN"], token: hiddenCredential },
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN", value: hiddenCredential },
}],
},
},
resourceBundleRef: {
kind: "gitbundle",
repoUrl: `https://fixture:${hiddenCredential}@github.com/pikasTech/unidesk.git`,
ref: "master",
bundles: [{ name: "unidesk-skills", subpath: ".agents/skills", targetPath: ".agents/skills" }],
requiredSkills: [{ name: "unidesk-gh" }],
credentialRef: { name: "agentrun-resource-git", keys: ["username", "password"], value: hiddenCredential },
submodules: false,
lfs: false,
},
payload: { prompt: "Inspect the existing sentinel configuration.", model: "gpt-5.5" },
payloadHash: "sha256:fixture",
references: [{ kind: "github-issue", url: "https://github.com/pikasTech/unidesk/issues/1819" }],
metadata: {
aipod: "Artificer",
source: "config/aipods/artificer.yaml",
aipodSpecHash: "sha256:aipod-fixture",
aipodImageRef: {
kind: "env-image-dockerfile",
repoUrl: `https://fixture:${hiddenCredential}@github.com/pikasTech/agentrun.git`,
commitId: "a".repeat(40),
dockerfilePath: "deploy/container/Containerfile",
},
credential: hiddenCredential,
},
idempotencyKey: "artificer-input-fixture",
latestAttempt: { attemptId: "qat_fixture", state: "completed", runId: "run_fixture", commandId: "cmd_fixture", runnerJobId: "rjob_fixture", sessionId: "ses_fixture" },
supervisor: { verboseDiagnostic: "x".repeat(20_000), credential: hiddenCredential },
valuesPrinted: false,
};
let requests = 0;
const server = Bun.serve({
port: 0,
fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
expect(url.pathname).toBe(`/api/v1/queue/tasks/${taskId}`);
requests += 1;
return Response.json({ ok: true, data: task });
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-input-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const invalidCombinations = [
{ args: ["--input", "--full"], contentType: "text/plain" },
{ args: ["--full", "--input", "-o", "json"], contentType: "application/json" },
{ args: ["--input", "--full", "-o", "yaml"], contentType: "application/yaml" },
{ args: ["--input", "--raw"], contentType: "application/json" },
];
for (const invalid of invalidCombinations) {
const rejected = await runAgentRunCommand(null, ["describe", `task/${taskId}`, ...invalid.args]);
const rejectedText = renderedTextOf(rejected);
expect(rejected.ok).toBe(false);
expect(renderedContentTypeOf(rejected)).toBe(invalid.contentType);
expect(rejectedText).toContain("validation-failed");
expect(rejectedText).toContain("cannot be combined with complete resource disclosure");
expect(rejectedText).not.toContain(hiddenCredential);
expect(rejectedText).not.toContain("supervisor");
expect(Buffer.byteLength(rejectedText, "utf8")).toBeLessThan(2400);
}
expect(requests).toBe(0);
const summaryText = renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`]));
expect(summaryText).toContain(`agentrun describe task/${taskId} --input -o json`);
expect(summaryText).not.toContain(hiddenCredential);
const compact = JSON.parse(renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`, "-o", "json"]))) as { next?: { input?: string } };
expect(compact.next?.input).toContain("--input -o json");
const inputText = renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`, "--input"]));
const input = JSON.parse(inputText) as {
kind?: string;
spec?: Record<string, any>;
aipodSpec?: Record<string, any>;
provenance?: Record<string, any>;
redaction?: Record<string, any>;
valuesPrinted?: boolean;
};
expect(input.kind).toBe("TaskInput");
expect(input.spec?.payload?.prompt).toBe(task.payload.prompt);
expect(input.spec?.idempotencyKey).toBe(task.idempotencyKey);
expect(input.spec?.metadata?.aipod).toBe("Artificer");
expect(input.aipodSpec?.specHash).toBe("sha256:aipod-fixture");
expect(input.provenance?.sourceTaskId).toBe(taskId);
expect(input.spec?.executionPolicy?.secretScope?.providerCredentials?.[0]?.secretRef).toEqual({
name: "agentrun-provider",
keys: ["auth.json"],
valuesPrinted: false,
});
expect(input.spec?.resourceBundleRef?.credentialRef).toEqual({
name: "agentrun-resource-git",
keys: ["username", "password"],
valuesPrinted: false,
});
expect(input.spec?.resourceBundleRef?.repoUrl).toBe("https://github.com/pikasTech/unidesk.git");
expect(input.aipodSpec?.imageRef?.repoUrl).toBe("https://github.com/pikasTech/agentrun.git");
expect(input.redaction?.credentialValues).toBe("omitted");
expect(input.valuesPrinted).toBe(false);
expect(inputText).not.toContain(hiddenCredential);
expect(Buffer.byteLength(inputText, "utf8")).toBeLessThan(10_240);
const full = JSON.parse(renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`, "--full", "-o", "json"]))) as { resource?: typeof task };
expect(full.resource?.executionPolicy.secretScope.providerCredentials[0]?.secretRef.value).toBe(hiddenCredential);
const raw = JSON.parse(renderedTextOf(await runAgentRunCommand(null, ["describe", `task/${taskId}`, "--raw"]))) as { data?: typeof task };
expect(raw.data?.resourceBundleRef.credentialRef.value).toBe(hiddenCredential);
expect(requests).toBe(5);
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
test("aipodspec describe is bounded and homologous while full and raw stay complete", async () => {
const aipodName = "Artificer";
const hiddenCredential = "HIDDEN_AIPOD_CREDENTIAL_VALUE_MUST_NOT_BE_PRINTED";
const providerCredential = {
profile: "sub2api",
name: "agentrun-provider",
namespace: null,
keys: ["auth.json", "config.toml"],
value: hiddenCredential,
valuesPrinted: false,
};
const toolCredentials = [
{
tool: "github",
purpose: "github-pr",
name: "agentrun-github",
namespace: null,
keys: ["GH_TOKEN"],
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN", value: hiddenCredential },
token: hiddenCredential,
valuesPrinted: false,
},
{
tool: "ssh",
purpose: "remote-workspace",
name: "agentrun-ssh",
namespace: null,
keys: ["config", "key"],
projection: { kind: "mount", mountPath: "/run/secrets/ssh", value: hiddenCredential },
valuesPrinted: false,
},
{
tool: "provider",
purpose: "model-auth",
name: "agentrun-provider",
namespace: null,
keys: ["auth.json"],
projection: { kind: "mount", mountPath: "/run/secrets/provider", value: hiddenCredential },
valuesPrinted: false,
},
];
const data = {
action: "show",
item: {
name: aipodName,
displayName: "Artificer",
description: "x".repeat(20_000),
specHash: "sha256:aipod-fixture",
source: "config/aipods/artificer.yaml",
backendProfile: "sub2api",
model: { model: "gpt-5.5", reasoningEffort: "high" },
imageRef: {
kind: "env-image-dockerfile",
repoUrl: "https://github.com/pikasTech/agentrun.git",
commitId: "a".repeat(40),
dockerfilePath: "deploy/container/Containerfile",
sourceIdentity: "agentrun@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
valuesPrinted: false,
},
queue: "commander",
lane: "v0.1",
providerId: "NC01",
providerCredentials: { count: 1, profiles: ["sub2api"], items: [providerCredential], valuesPrinted: false },
toolCredentials: { count: 3, tools: ["github", "ssh", "provider"], items: toolCredentials, valuesPrinted: false },
resourceBundleRef: {
kind: "gitbundle",
repoUrl: `https://fixture:${hiddenCredential}@github.com/pikasTech/unidesk.git`,
ref: "master",
commitId: null,
bundles: { count: 6, items: [{ name: "unidesk-skills", targetPath: ".agents/skills" }], valuesPrinted: false },
requiredSkills: { count: 11, names: ["unidesk-gh"], valuesPrinted: false },
promptRefs: { count: 0, names: [], valuesPrinted: false },
valuesPrinted: false,
},
createdAt: "2026-07-12T00:00:00.000Z",
updatedAt: "2026-07-12T01:00:00.000Z",
valuesPrinted: false,
},
spec: {
apiVersion: "agentrun.pikastech.local/v1alpha1",
kind: "AipodSpec",
metadata: { name: aipodName, displayName: "Artificer", description: "x".repeat(20_000) },
spec: {
queue: "commander",
lane: "v0.1",
providerId: "NC01",
backendProfile: "sub2api",
model: { model: "gpt-5.5", reasoningEffort: "high" },
workspaceRef: { kind: "opaque", path: "." },
executionPolicy: {
secretScope: {
allowCredentialEcho: false,
providerCredentials: [{ profile: "sub2api", secretRef: { name: "agentrun-provider", keys: ["auth.json"], value: hiddenCredential } }],
toolCredentials: [{ tool: "github", secretRef: { name: "agentrun-github", keys: ["GH_TOKEN"], token: hiddenCredential } }],
},
},
resourceBundleRef: {
kind: "gitbundle",
repoUrl: `https://fixture:${hiddenCredential}@github.com/pikasTech/unidesk.git`,
ref: "master",
bundles: [{ name: "unidesk-skills", subpath: ".agents/skills", targetPath: ".agents/skills" }],
requiredSkills: [{ name: "unidesk-gh" }],
},
metadata: { aipod: aipodName, source: "config/aipods/artificer.yaml" },
},
},
valuesPrinted: false,
};
let requests = 0;
const server = Bun.serve({
port: 0,
fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
expect(url.pathname).toBe(`/api/v1/aipod-specs/${aipodName}`);
requests += 1;
return Response.json({ ok: true, data });
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-aipodspec-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const text = renderedTextOf(await runAgentRunCommand(null, ["describe", `aipodspec/${aipodName}`]));
expect(text).toContain(`Name: aipodspec/${aipodName}`);
expect(text).toContain("Hash: sha256:aipod-fixture");
expect(text).toContain("Kind: opaque path=. branch=-");
expect(text).toContain("References: bundles=6 requiredSkills=11 promptRefs=0");
expect(text).toContain("Credentials: provider=1 tool=3 total=4");
expect(text).toContain("secret=agentrun-provider keys=auth.json,config.toml");
expect(text).toContain(`agentrun describe aipodspec/${aipodName} --full -o json`);
expect(Buffer.byteLength(text, "utf8")).toBeLessThan(10_240);
expect(text).not.toContain(hiddenCredential);
const jsonText = renderedTextOf(await runAgentRunCommand(null, ["describe", `aipodspec/${aipodName}`, "-o", "json"]));
const json = JSON.parse(jsonText) as Record<string, any>;
const yamlText = renderedTextOf(await runAgentRunCommand(null, ["describe", `aipodspec/${aipodName}`, "-o", "yaml"]));
const yaml = Bun.YAML.parse(yamlText) as Record<string, any>;
expect(yaml).toEqual(json);
expect(json.kind).toBe("AipodSpec");
expect(json.identity.specHash).toBe("sha256:aipod-fixture");
expect(json.runtime).toMatchObject({ lane: "v0.1", backendProfile: "sub2api", providerId: "NC01" });
expect(json.workspaceRef).toMatchObject({ kind: "opaque", path: ".", valuesPrinted: false });
expect(json.resourceBundleRef.sourceIdentity.repoUrl).toBe("https://github.com/pikasTech/unidesk.git");
expect(json.resourceBundleRef).toMatchObject({ bundleCount: 6, requiredSkillCount: 11, promptRefCount: 0 });
expect(json.credentialRefs).toMatchObject({ totalCount: 4, valuesPrinted: false });
expect(json.credentialRefs.provider.items[0].secretRef).toEqual({
namespace: null,
name: "agentrun-provider",
keys: ["auth.json", "config.toml"],
valuesPrinted: false,
});
expect(json.redaction).toEqual({ credentialValues: "omitted", secretRefsOnly: true, valuesPrinted: false });
expect(json.valuesPrinted).toBe(false);
for (const bounded of [jsonText, yamlText]) {
expect(Buffer.byteLength(bounded, "utf8")).toBeLessThan(10_240);
expect(bounded).not.toContain(hiddenCredential);
expect(bounded).not.toContain("x".repeat(200));
}
const full = JSON.parse(renderedTextOf(await runAgentRunCommand(null, ["describe", `aipodspec/${aipodName}`, "--full", "-o", "json"]))) as { resource?: typeof data };
expect(full.resource?.item.description).toBe("x".repeat(20_000));
expect(full.resource?.spec.kind).toBe("AipodSpec");
const raw = JSON.parse(renderedTextOf(await runAgentRunCommand(null, ["describe", `aipodspec/${aipodName}`, "--raw"]))) as { data?: typeof data };
expect(raw.data?.item.description).toBe("x".repeat(20_000));
expect(raw.data?.spec.kind).toBe("AipodSpec");
expect(requests).toBe(5);
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
test("retry dry-run is server-validated and attempts use the formal resource API", async () => {
const taskId = "qt_failed_1";
const attempt = {
attemptId: "qat_retry_1",
taskId,
retryIndex: 1,
state: "reserved",
idempotencyKey: "retry-key-1",
reason: "dependency repaired",
payloadHash: "sha256:payload",
previousAttemptId: "qat_initial_0",
runId: "run_retry_1",
commandId: "cmd_retry_1",
runnerJobId: "rjob_retry_1",
sessionId: "ses_retry_1",
sessionPath: "/api/v1/sessions/ses_retry_1",
failureKind: "infra-failed",
failureMessage: "kubectl create runner job failed before materialization",
createdAt: "2026-07-12T00:00:00.000Z",
updatedAt: "2026-07-12T00:00:01.000Z",
activatedAt: "2026-07-12T00:00:01.000Z",
terminalAt: null,
};
let retryRequests = 0;
let attemptRequests = 0;
const server = Bun.serve({
port: 0,
async fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
if (url.pathname === `/api/v1/queue/tasks/${taskId}/retry`) {
retryRequests += 1;
expect(request.method).toBe("POST");
expect(await request.json()).toEqual({
idempotencyKey: "retry-key-1",
reason: "dependency repaired",
dryRun: true,
});
return Response.json({
ok: true,
data: {
action: "queue-retry",
mutation: false,
idempotentReplay: false,
task: { id: taskId, state: "failed", queue: "commander", payloadHash: "sha256:payload" },
attempt: null,
previousAttempt: { ...attempt, attemptId: "qat_initial_0", retryIndex: 0, state: "failed", previousAttemptId: null },
run: null,
command: null,
runnerJob: null,
allowedTransition: { from: "failed", to: "running", retryIndex: 1, previousAttemptId: "qat_initial_0", payloadHash: "sha256:payload", valuesPrinted: false },
pollActions: [],
},
});
}
if (url.pathname === `/api/v1/queue/tasks/${taskId}/attempts`) {
attemptRequests += 1;
expect(request.method).toBe("GET");
expect(url.searchParams.get("cursor")).toBe("1");
expect(url.searchParams.get("limit")).toBe("10");
return Response.json({ ok: true, data: { taskId, items: [attempt], count: 1, cursor: null } });
}
return new Response("not found", { status: 404 });
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-retry-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const retryResult = await runAgentRunCommand(null, ["retry", `task/${taskId}`, "--idempotency-key", "retry-key-1", "--reason", "dependency repaired", "--dry-run"]);
expect(retryResult.ok).toBe(true);
expect("renderedText" in retryResult).toBe(true);
if ("renderedText" in retryResult) {
expect(retryResult.renderedText).toContain(`Task retry validated: task/${taskId}`);
expect(retryResult.renderedText).toContain("Transition: failed -> running retryIndex=1");
expect(retryResult.renderedText).toContain("Attempt: planned");
}
const attemptsResult = await runAgentRunCommand(null, ["get", "attempts", "--task", taskId, "--cursor", "1", "--limit", "10", "-o", "json"]);
expect(attemptsResult.ok).toBe(true);
expect("renderedText" in attemptsResult).toBe(true);
if ("renderedText" in attemptsResult && typeof attemptsResult.renderedText === "string") {
const payload = JSON.parse(attemptsResult.renderedText) as { kind?: string; taskId?: string; count?: number; items?: Array<Record<string, unknown>> };
expect(payload.kind).toBe("AttemptList");
expect(payload.taskId).toBe(taskId);
expect(payload.count).toBe(1);
expect(payload.items?.[0]?.name).toBe(attempt.attemptId);
expect(payload.items?.[0]?.retryIndex).toBe(1);
expect(payload.items?.[0]?.failureMessage).toBe(attempt.failureMessage);
}
const attemptsHumanResult = await runAgentRunCommand(null, ["get", "attempts", "--task", taskId, "--cursor", "1", "--limit", "10"]);
expect("renderedText" in attemptsHumanResult ? attemptsHumanResult.renderedText : "").toContain("infra-failed: kubectl create runner job failed before materialization");
expect(retryRequests).toBe(1);
expect(attemptRequests).toBe(2);
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
test("retry and attempt resource help stays scoped to the controlled commands", async () => {
const retryHelp = await runAgentRunCommand(null, ["retry", "--help"]);
const attemptHelp = await runAgentRunCommand(null, ["get", "attempts", "--help"]);
const describeHelp = await runAgentRunCommand(null, ["describe", "--help"]);
expect("renderedText" in retryHelp ? retryHelp.renderedText : "").toContain("agentrun retry task/<taskId> --idempotency-key <key> --reason <text>");
expect("renderedText" in attemptHelp ? attemptHelp.renderedText : "").toContain("agentrun get attempts --task <taskId>");
expect("renderedText" in describeHelp ? describeHelp.renderedText : "").toContain("agentrun describe task/qt_... --input -o json");
const rootHelp = Bun.spawnSync(["bun", "scripts/cli.ts", "agentrun", "--help"], {
cwd: new URL("../..", import.meta.url).pathname,
stdout: "pipe",
stderr: "pipe",
});
const rootHelpText = rootHelp.stdout.toString();
expect(rootHelp.exitCode).toBe(0);
expect(rootHelpText).toContain("Verbs: get, describe, events, logs, result, ack, cancel, retry,");
expect(rootHelpText).toContain("Resources: task/qt, attempt, run,");
expect(rootHelpText).toContain("agentrun retry task/<taskId> --idempotency-key <key> --reason <text> --dry-run");
expect(rootHelpText).toContain("agentrun get attempts --task <taskId> --limit 20");
expect(rootHelpText.split("\n").length).toBeLessThan(40);
});
test("local resource validation preserves bounded human and machine errors", async () => {
const cases = [
{
args: ["retry", "task/qt_failed_1", "--reason", "dependency repaired"],
message: "retry requires --idempotency-key <key>",
},
{
args: ["retry", "task/qt_failed_1", "--idempotency-key", "retry-key-1"],
message: "retry requires --reason <text>",
},
{
args: ["get", "attempts"],
message: "get attempts requires --task <taskId>",
},
{
args: ["retry", "run/run_invalid", "--idempotency-key", "retry-key-1", "--reason", "dependency repaired"],
message: "retry supports task/<taskId>",
},
{
args: ["retry"],
message: "retry requires task/<taskId>",
},
];
for (const item of cases) {
const human = await runAgentRunCommand(null, item.args);
const humanText = renderedTextOf(human);
expect(human.ok).toBe(false);
expect(humanText).toContain("Error: validation-failed");
expect(humanText).toContain(item.message);
expect(humanText.length).toBeLessThan(1600);
for (const outputArgs of [["-o", "json"], ["--raw"]]) {
const machine = await runAgentRunCommand(null, [...item.args, ...outputArgs]);
const machineText = renderedTextOf(machine);
const payload = JSON.parse(machineText) as { ok?: boolean; failureKind?: string; message?: string };
expect(machine.ok).toBe(false);
expect(renderedContentTypeOf(machine)).toBe("application/json");
expect(payload.ok).toBe(false);
expect(payload.failureKind).toBe("validation-failed");
expect(payload.message).toBe(item.message);
expect(machineText.length).toBeLessThan(2400);
}
}
for (const cliArgs of [
["retry", "run/run_invalid", "--idempotency-key", "retry-key-1", "--reason", "dependency repaired", "-o", "json"],
["get", "attempts", "--raw"],
["describe", "task/qt_sensitive", "--input", "--full", "-o", "json"],
["describe", "task/qt_sensitive", "--input", "--raw"],
]) {
const cli = Bun.spawnSync(["bun", "scripts/cli.ts", "agentrun", ...cliArgs], {
cwd: new URL("../..", import.meta.url).pathname,
stdout: "pipe",
stderr: "pipe",
});
const payload = JSON.parse(cli.stdout.toString()) as { ok?: boolean; failureKind?: string };
expect(cli.exitCode).toBe(1);
expect(payload.ok).toBe(false);
expect(payload.failureKind).toBe("validation-failed");
expect(cli.stdout.toString().length).toBeLessThan(2400);
expect(cli.stdout.toString()).not.toContain('"resource":');
expect(cli.stdout.toString()).not.toContain('"data":');
}
});
test("server retry validation errors stay bounded in human and JSON output", async () => {
const taskId = "qt_completed_1";
const longMessage = `queue task ${taskId} cannot be retried from state completed: ${"x".repeat(6000)}`;
const longDiagnostic = `server diagnostic: ${"y".repeat(9000)}`;
let requests = 0;
const server = Bun.serve({
port: 0,
async fetch(request) {
const url = new URL(request.url);
expect(url.pathname).toBe(`/api/v1/queue/tasks/${taskId}/retry`);
expect(request.method).toBe("POST");
expect(await request.json()).toEqual({ idempotencyKey: "retry-key-terminal", reason: "operator requested retry", dryRun: true });
requests += 1;
return Response.json({
ok: false,
failureKind: "schema-invalid",
message: longMessage,
details: { allowedStates: ["failed", "blocked"], diagnostic: longDiagnostic, valuesPrinted: false },
valuesPrinted: false,
}, { status: 409 });
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-retry-error-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const baseArgs = ["retry", `task/${taskId}`, "--idempotency-key", "retry-key-terminal", "--reason", "operator requested retry", "--dry-run"];
const human = await runAgentRunCommand(null, baseArgs);
const humanText = renderedTextOf(human);
expect(human.ok).toBe(false);
expect(humanText).toContain(`queue task ${taskId} cannot be retried from state completed`);
expect(humanText).not.toContain("x".repeat(500));
expect(humanText.length).toBeLessThan(1800);
const json = await runAgentRunCommand(null, [...baseArgs, "-o", "json"]);
const jsonText = renderedTextOf(json);
const payload = JSON.parse(jsonText) as { ok?: boolean; failureKind?: string; message?: string; agentrun?: { details?: { diagnostic?: string } } };
expect(json.ok).toBe(false);
expect(renderedContentTypeOf(json)).toBe("application/json");
expect(payload.failureKind).toBe("validation-failed");
expect(payload.message?.length).toBeLessThanOrEqual(400);
expect(payload.agentrun?.details?.diagnostic?.length).toBeLessThanOrEqual(400);
expect(jsonText.length).toBeLessThan(5000);
expect(requests).toBe(2);
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
});
describe("AgentRun runner API key SecretRef", () => {
test("renders manager runtime authority from the YAML runner SecretRef", () => {
const { spec } = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" });
const files = renderAgentRunGitopsFiles(spec, { sourceCommit: "a".repeat(40), image: placeholderAgentRunImage(spec, "a".repeat(40)) });
const managerFile = files.find((file) => file.path.endsWith("/mgr.yaml"));
expect(managerFile).toBeDefined();
const resources = (managerFile?.content ?? "").split(/\n---\n/u).map((document) => Bun.YAML.parse(document) as Record<string, unknown>);
const deployment = resources.find((resource) => resource.kind === "Deployment") as { spec?: { template?: { spec?: { containers?: Array<{ env?: Array<{ name?: string; value?: string }> }> } } } } | undefined;
const env = deployment?.spec?.template?.spec?.containers?.[0]?.env ?? [];
const values = new Map(env.map((item) => [item.name, item.value]));
expect(values.get("AGENTRUN_RUNNER_API_KEY_SECRET_NAME")).toBe(spec.deployment.runner.apiKeySecretRef.name);
expect(values.get("AGENTRUN_RUNNER_API_KEY_SECRET_KEY")).toBe(spec.deployment.runner.apiKeySecretRef.key);
expect(spec.secrets.some((secret) => secret.targetRef.name === "agentrun-v01-api-key")).toBe(false);
});
});