fix: 收敛 AgentRun 事件摘要输出
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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 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: 900000",
|
||||
" 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
expect(observedLimits).toEqual([21, 21, 21, 100, 100]);
|
||||
} 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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user