Files
pikasTech-unidesk/scripts/src/agentrun-events.test.ts
T
2026-07-13 17:57:29 +02:00

364 lines
16 KiB
TypeScript

import { createHash } from "node:crypto";
import { rmSync } from "node:fs";
import { describe, expect, test } from "bun:test";
import { runAgentRunCommand } from "./agentrun";
const FIXTURE_RUN_ID = "run_event_summary_fixture";
const HIDDEN_OUTPUT_MARKER = "HIDDEN_TOOL_OUTPUT_MUST_NOT_REACH_DEFAULT_RENDER";
const HIDDEN_STDERR_MARKER = "HIDDEN_STDERR_MUST_NOT_REACH_DEFAULT_RENDER";
const HIDDEN_SECOND_COMMAND_LINE = "HIDDEN_SECOND_COMMAND_LINE";
const HIDDEN_CREDENTIAL_MARKER = "HIDDEN_EVENT_CREDENTIAL_MUST_NOT_REACH_DETAIL";
const HIDDEN_URL_CREDENTIAL = "HIDDEN_URL_CREDENTIAL_MUST_NOT_REACH_DETAIL";
const SCP_LIKE_REMOTE = `${HIDDEN_URL_CREDENTIAL}@mirror.example.invalid:pikasTech/unidesk.git?token=${HIDDEN_CREDENTIAL_MARKER}`;
const SCP_LIKE_REMOTE_SHA256 = createHash("sha256").update(SCP_LIKE_REMOTE).digest("hex");
const LARGE_TOOL_OUTPUT = `${HIDDEN_OUTPUT_MARKER}\n${"skill body and command output\n".repeat(2_400)}`;
const LARGE_OUTPUT_BYTES = Buffer.byteLength(LARGE_TOOL_OUTPUT, "utf8");
const LARGE_OUTPUT_SHA256 = `sha256:${createHash("sha256").update(LARGE_TOOL_OUTPUT).digest("hex")}`;
const agentRunClientYaml = [
"version: 1",
"kind: AgentRunConfig",
"metadata:",
" name: agentrun",
"manager:",
" baseUrl: __BASE_URL__",
" 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: 7200000",
" network: enabled",
" secretScope:",
" allowCredentialEcho: false",
].join("\n");
function fixtureEvents(): Array<Record<string, unknown>> {
const items: Array<Record<string, unknown>> = [];
for (let seq = 22; seq <= 47; seq += 1) {
if (seq <= 41) {
items.push({
id: `evt_${seq}`,
seq,
type: "tool_call",
payload: {
commandId: "cmd_fixture",
itemId: `tool_${seq}`,
toolName: "commandExecution",
type: "commandExecution",
method: "item/completed",
status: "completed",
command: `find . -maxdepth 3 -name package.json\n${HIDDEN_SECOND_COMMAND_LINE}`,
exitCode: 0,
durationMs: 17,
outputSummary: LARGE_TOOL_OUTPUT,
stderr: HIDDEN_STDERR_MARKER,
outputBytes: LARGE_OUTPUT_BYTES,
outputTruncated: true,
summary: {
text: `commandExecution completed output=${LARGE_TOOL_OUTPUT}`,
textBytes: LARGE_OUTPUT_BYTES,
textTruncated: true,
},
},
});
continue;
}
if (seq === 42) {
items.push({
id: `evt_${seq}`,
seq,
type: "backend_status",
payload: {
commandId: "cmd_fixture",
phase: "resource-bundle-materialized",
kind: "gitbundle",
repoUrl: `https://fixture:${HIDDEN_URL_CREDENTIAL}@example.invalid/pikasTech/unidesk.git?access_token=${HIDDEN_CREDENTIAL_MARKER}`,
fetchRepoUrl: SCP_LIKE_REMOTE,
mirrorUsed: true,
commitId: "1".repeat(40),
requestedCommitId: null,
requestedRef: "refs/heads/master",
treeId: "2".repeat(40),
workspaceLifecycle: "first-materialization",
resolutionAuthority: "REDACTED",
primaryWorkspace: {
status: "materialized",
sourceContract: "resourceBundleRef",
repoUrl: `https://fixture:${HIDDEN_URL_CREDENTIAL}@example.invalid/pikasTech/unidesk.git?access_token=${HIDDEN_CREDENTIAL_MARKER}`,
fetchRepoUrl: SCP_LIKE_REMOTE,
mirrorUsed: true,
requestedRef: "refs/heads/master",
requestedCommitId: null,
sourceCommit: "1".repeat(40),
headCommit: "3".repeat(40),
headIsFrozenSource: false,
treeId: "2".repeat(40),
headTreeId: "4".repeat(40),
sourceBranch: "master",
branch: "fix/cadence",
targetPath: ".",
workspaceLifecycle: "continuation-reuse",
resolutionAuthority: "REDACTED",
assemblyContractHash: "sha256:assembly-fixture",
bundleContractHash: "sha256:bundle-contract-fixture",
bundleProvenanceHash: "sha256:bundle-provenance-fixture",
provenanceHash: "sha256:workspace-provenance-fixture",
writable: true,
credential: HIDDEN_CREDENTIAL_MARKER,
valuesPrinted: false,
},
bundles: {
count: 6,
items: Array.from({ length: 6 }, (_, index) => ({ name: `bundle-${index}`, credential: HIDDEN_CREDENTIAL_MARKER })),
valuesPrinted: false,
},
tools: { count: 4, items: [{ name: "git", output: LARGE_TOOL_OUTPUT }], valuesPrinted: false },
skillDirs: { count: 2, items: [{ path: ".agents/skills", body: LARGE_TOOL_OUTPUT }], valuesPrinted: false },
requiredSkills: { count: 11, items: [{ name: "unidesk-gh", body: LARGE_TOOL_OUTPUT }], valuesPrinted: false },
promptRefs: { count: 0, items: [], valuesPrinted: false },
initialPrompt: {
available: true,
bytes: LARGE_OUTPUT_BYTES,
sha256: LARGE_OUTPUT_SHA256,
promptRefCount: 0,
skillCount: 11,
text: LARGE_TOOL_OUTPUT,
valuesPrinted: false,
},
credentialRef: { name: "fixture-secret", value: HIDDEN_CREDENTIAL_MARKER },
valuesPrinted: false,
},
});
continue;
}
items.push({
id: `evt_${seq}`,
seq,
type: "backend_status",
payload: { commandId: "cmd_fixture", phase: `phase-${seq}`, message: "bounded status" },
});
}
return items;
}
function renderedText(value: Awaited<ReturnType<typeof runAgentRunCommand>>): string {
if (!("renderedText" in value) || typeof value.renderedText !== "string") throw new Error("expected rendered AgentRun CLI output");
return value.renderedText;
}
describe("AgentRun events render-only progressive disclosure", () => {
test("default human/json/yaml stay bounded while full/raw retain the exact event page", async () => {
const events = fixtureEvents();
const observedLimits: number[] = [];
const server = Bun.serve({
port: 0,
fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer fixture-secret");
expect(url.pathname).toBe(`/api/v1/runs/${FIXTURE_RUN_ID}/events`);
const afterSeq = Number(url.searchParams.get("afterSeq") ?? "0");
const limit = Number(url.searchParams.get("limit") ?? "100");
observedLimits.push(limit);
return Response.json({
ok: true,
data: {
items: events.filter((item) => Number(item.seq) > afterSeq).slice(0, limit),
},
});
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
const configPath = `/tmp/unidesk-agentrun-events-${process.pid}-${Date.now()}.yaml`;
process.env.AGENTRUN_CLIENT_CONFIG = configPath;
process.env.HWLAB_API_KEY = "fixture-secret";
await Bun.write(configPath, agentRunClientYaml.replace("__BASE_URL__", server.url.href.replace(/\/$/u, "")));
try {
const args = ["events", `run/${FIXTURE_RUN_ID}`, "--after-seq", "21", "--limit", "100"];
const jsonResult = await runAgentRunCommand(null, [...args, "-o", "json"]);
const jsonText = renderedText(jsonResult);
const json = JSON.parse(jsonText) as {
kind: string;
count: number;
requestedLimit: number;
effectiveLimit: number;
fetchedCount: number;
hasMore: boolean;
nextAfterSeq: number;
items: Array<Record<string, unknown>>;
};
expect(json.kind).toBe("EventList");
expect(json.count).toBe(20);
expect(json.requestedLimit).toBe(100);
expect(json.effectiveLimit).toBe(20);
expect(json.fetchedCount).toBe(21);
expect(json.hasMore).toBe(true);
expect(json.nextAfterSeq).toBe(41);
expect(Buffer.byteLength(jsonText, "utf8")).toBeLessThan(10_240);
expect(jsonText).not.toContain(HIDDEN_OUTPUT_MARKER);
expect(jsonText).not.toContain(HIDDEN_STDERR_MARKER);
expect(jsonText).not.toContain(HIDDEN_SECOND_COMMAND_LINE);
const completed = json.items.find((item) => item.seq === 24);
expect(completed).toMatchObject({
identity: "event/evt_24",
type: "tool_call",
status: "completed",
commandId: "cmd_fixture",
tool: "commandExecution",
command: "find . -maxdepth 3 -name package.json",
exitCode: 0,
durationMs: 17,
outputBytes: LARGE_OUTPUT_BYTES,
outputSha256: LARGE_OUTPUT_SHA256,
outputTruncated: true,
outputOmitted: true,
});
const humanText = renderedText(await runAgentRunCommand(null, args));
expect(Buffer.byteLength(humanText, "utf8")).toBeLessThan(10_240);
expect(humanText).toContain("commandExecution find . -maxdepth 3 -name package.json");
expect(humanText).toContain(`${LARGE_OUTPUT_BYTES}B`);
expect(humanText).toContain("truncated");
expect(humanText).toContain("Detail: bun scripts/cli.ts agentrun events");
expect(humanText).not.toContain(HIDDEN_OUTPUT_MARKER);
expect(humanText).not.toContain(HIDDEN_STDERR_MARKER);
expect(humanText).not.toContain(HIDDEN_SECOND_COMMAND_LINE);
const yamlText = renderedText(await runAgentRunCommand(null, [...args, "-o", "yaml"]));
const yaml = Bun.YAML.parse(yamlText) as { kind?: string; items?: Array<Record<string, unknown>> };
expect(yaml.kind).toBe("EventList");
expect(yaml.items?.find((item) => item.seq === 24)?.outputSha256).toBe(LARGE_OUTPUT_SHA256);
expect(Buffer.byteLength(yamlText, "utf8")).toBeLessThan(10_240);
expect(yamlText).not.toContain(HIDDEN_OUTPUT_MARKER);
for (const disclosure of ["--full", "--raw"]) {
const fullText = renderedText(await runAgentRunCommand(null, [...args, disclosure]));
const full = JSON.parse(fullText) as { data?: { items?: Array<{ payload?: { outputSummary?: string; stderr?: string } }> } };
expect(full.data?.items?.length).toBe(26);
expect(full.data?.items?.[2]?.payload?.outputSummary).toBe(LARGE_TOOL_OUTPUT);
expect(full.data?.items?.[2]?.payload?.stderr).toBe(HIDDEN_STDERR_MARKER);
expect(fullText).toContain(HIDDEN_OUTPUT_MARKER);
expect(fullText).toContain(HIDDEN_SECOND_COMMAND_LINE);
}
const detailArgs = ["events", `run/${FIXTURE_RUN_ID}`, "--detail-seq", "42"];
const detailJsonText = renderedText(await runAgentRunCommand(null, [...detailArgs, "-o", "json"]));
const detailJson = JSON.parse(detailJsonText) as {
kind?: string;
source?: string;
seq?: number;
phase?: string;
correlation?: { runId?: string; commandId?: string };
evidence?: {
primaryWorkspace?: Record<string, unknown>;
counts?: Record<string, unknown>;
initialPrompt?: Record<string, unknown>;
};
redaction?: Record<string, unknown>;
valuesPrinted?: boolean;
};
expect(detailJson.kind).toBe("EventDetail");
expect(detailJson.source).toBe(`run/${FIXTURE_RUN_ID}`);
expect(detailJson.seq).toBe(42);
expect(detailJson.phase).toBe("resource-bundle-materialized");
expect(detailJson.correlation).toMatchObject({ runId: FIXTURE_RUN_ID, commandId: "cmd_fixture" });
expect(detailJson.evidence?.primaryWorkspace).toMatchObject({
sourceContract: "resourceBundleRef",
repoUrl: "https://example.invalid/pikasTech/unidesk.git",
fetchRepoUrl: `<scp-like-remote;path-omitted;sha256:${SCP_LIKE_REMOTE_SHA256}>`,
sourceCommit: "1".repeat(40),
headCommit: "3".repeat(40),
workspaceLifecycle: "continuation-reuse",
resolutionAuthority: null,
resolutionAuthoritySource: "durable-event",
resolutionAuthorityRedacted: true,
resolutionModeInference: {
mode: "runner-first-materialization",
source: "durable-event requestedCommitId",
authoritative: false,
valuesPrinted: false,
},
writable: true,
assemblyContractHash: "sha256:assembly-fixture",
bundleContractHash: "sha256:bundle-contract-fixture",
bundleProvenanceHash: "sha256:bundle-provenance-fixture",
provenanceHash: "sha256:workspace-provenance-fixture",
valuesPrinted: false,
});
expect(detailJson.evidence?.counts).toEqual({ bundles: 6, tools: 4, skillDirs: 2, requiredSkills: 11, promptRefs: 0 });
expect(detailJson.evidence?.initialPrompt).toMatchObject({
available: true,
bytes: LARGE_OUTPUT_BYTES,
sha256: LARGE_OUTPUT_SHA256,
promptRefCount: 0,
skillCount: 11,
contentOmitted: true,
valuesPrinted: false,
});
expect(detailJson.redaction).toEqual({ credentialValues: "omitted", urlCredentials: "removed", secretRefsOnly: true, valuesPrinted: false });
expect(detailJson.valuesPrinted).toBe(false);
expect(Buffer.byteLength(detailJsonText, "utf8")).toBeLessThan(10_240);
const detailYamlText = renderedText(await runAgentRunCommand(null, [...detailArgs, "-o", "yaml"]));
const detailYaml = Bun.YAML.parse(detailYamlText) as typeof detailJson;
expect(detailYaml.evidence).toEqual(detailJson.evidence);
const detailHumanText = renderedText(await runAgentRunCommand(null, detailArgs));
expect(detailHumanText).toContain("workspaceLifecycle");
expect(detailHumanText).toContain("sha256:workspace-provenance-fixture");
expect(detailHumanText).toContain("--after-seq 41 --limit 1 --full -o json");
for (const boundedText of [detailJsonText, detailYamlText, detailHumanText]) {
expect(Buffer.byteLength(boundedText, "utf8")).toBeLessThan(10_240);
expect(boundedText).not.toContain(HIDDEN_OUTPUT_MARKER);
expect(boundedText).not.toContain(HIDDEN_CREDENTIAL_MARKER);
expect(boundedText).not.toContain(HIDDEN_URL_CREDENTIAL);
expect(boundedText).not.toContain("access_token");
}
const requestsBeforeInvalid = observedLimits.length;
const invalid = await runAgentRunCommand(null, [...detailArgs, "--full", "-o", "json"]);
expect(invalid.ok).toBe(false);
expect(renderedText(invalid)).toContain("validation-failed");
expect(renderedText(invalid)).toContain("bounded EventDetail and complete event disclosure are separate read modes");
expect(observedLimits.length).toBe(requestsBeforeInvalid);
const wrongVerb = await runAgentRunCommand(null, ["describe", `run/${FIXTURE_RUN_ID}`, "--detail-seq", "42", "-o", "json"]);
expect(wrongVerb.ok).toBe(false);
expect(renderedText(wrongVerb)).toContain("--detail-seq is supported only by agentrun events");
expect(observedLimits.length).toBe(requestsBeforeInvalid);
const missing = await runAgentRunCommand(null, ["events", `run/${FIXTURE_RUN_ID}`, "--detail-seq", "48", "-o", "json"]);
expect(missing.ok).toBe(false);
expect(renderedText(missing)).toContain("not-found");
expect(renderedText(missing)).toContain("no durable event at seq 48");
expect(observedLimits.slice(0, 5)).toEqual([21, 21, 21, 100, 100]);
expect(observedLimits.slice(5)).toEqual([1, 1, 1, 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(configPath, { force: true });
}
});
});