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

269 lines
10 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { renderSessionSendError, renderSessionSendResult } from "./agentrun/session-send-render";
import type { AgentRunOutputMode, AgentRunResourceOptions } from "./agentrun/utils";
const command = "agentrun send session/sess_admission_fixture --prompt-stdin";
const sessionId = "sess_admission_fixture";
const runId = "run_admission_fixture";
const commandId = "cmd_admission_fixture";
const dispatchIntentId = "rdi_admission_fixture";
const plannedRunnerJobId = "rjob_planned_fixture";
const durableAdmissionFixture: Record<string, unknown> = {
ok: true,
command,
data: {
action: "session-send",
dryRun: false,
mutation: true,
partialWrite: false,
sessionId,
decision: "turn",
internalCommandType: "turn",
run: { id: runId, status: "queued", sessionRef: { sessionId } },
command: {
id: commandId,
state: "pending",
type: "turn",
dispatchIntent: {
id: dispatchIntentId,
state: "pending",
runnerJobId: plannedRunnerJobId,
actualRunnerJobId: null,
attemptCount: 0,
durable: true,
},
},
runnerJob: {
action: "runner-dispatch-admitted",
state: "pending",
runId,
commandId,
runnerJobId: plannedRunnerJobId,
dispatchIntentId,
durable: true,
},
runnerAdmission: {
mode: "durable-dispatch-intent",
state: "pending",
reason: "session-runner-admission-recovered",
disposition: "recovered",
failureKind: null,
sessionId,
runId,
commandId,
dispatchIntentId,
plannedRunnerJobId,
actualRunnerJobId: null,
attemptCount: 0,
durable: true,
mutation: true,
partialWrite: false,
recoveredPriorPartialWrite: true,
retryable: true,
retryAuthority: "dispatcher",
recoveryActions: [
{ operation: "describe", resourceKind: "session", resourceName: sessionId },
{ operation: "events", resourceKind: "run", resourceName: runId, runId, afterSeq: 0, limit: 100 },
],
valuesPrinted: false,
},
pollActions: [
{ operation: "logs", resourceKind: "session", resourceName: sessionId, sessionId, limit: 100 },
{ operation: "cancel", resourceKind: "session", resourceName: sessionId },
],
valuesPrinted: false,
},
};
const legacySuccessFixture: Record<string, unknown> = {
ok: true,
command,
data: {
action: "session-send",
dryRun: false,
mutation: true,
partialWrite: false,
sessionId,
decision: "turn",
internalCommandType: "turn",
run: { id: runId, status: "queued", sessionRef: { sessionId } },
command: { id: commandId, state: "pending", type: "turn" },
runnerJob: { id: "rjob_legacy_fixture", state: "queued", runId, commandId },
pollActions: [
{ operation: "describe", resourceKind: "session", resourceName: sessionId },
{ operation: "events", resourceKind: "run", resourceName: runId, runId, afterSeq: 0, limit: 100 },
],
valuesPrinted: false,
},
};
const typedFailureFixture: Record<string, unknown> = {
ok: false,
command,
failureKind: "validation-failed",
message: "durable runner dispatch is not enabled for session turn admission",
httpStatus: 503,
agentrun: {
failureKind: "infra-failed",
error: {
name: "AgentRunError",
failureKind: "infra-failed",
details: {
code: "runner-admission-failed",
reason: "session-runner-dispatcher-disabled",
phase: "runner-admission",
sessionId,
mutation: false,
partialWrite: false,
retryable: false,
retryAuthority: "none",
recoveryAction: "restore-runner-dispatcher",
recoveryActions: [
{ operation: "describe", resourceKind: "session", resourceName: sessionId },
],
valuesPrinted: false,
},
},
},
};
describe("AgentRun session send runner admission rendering", () => {
test("default output exposes durable admission without claiming a Kubernetes Job exists", () => {
const result = renderSessionSendResult(command, durableAdmissionFixture, options("human"));
expect(result.ok).toBe(true);
expect(result.renderedText).toContain("RunnerAdmission:");
expect(result.renderedText).toContain("mode=durable-dispatch-intent state=pending disposition=recovered");
expect(result.renderedText).toContain(`runId=${runId} commandId=${commandId} dispatchIntentId=${dispatchIntentId}`);
expect(result.renderedText).toContain(`plannedRunnerJobId=${plannedRunnerJobId} actualRunnerJobId=-`);
expect(result.renderedText).toContain("mutation=true partialWrite=false recoveredPriorPartialWrite=true");
expect(result.renderedText).toContain("retryable=true retryAuthority=dispatcher");
expect(result.renderedText).toContain(`planned-runnerjob/${plannedRunnerJobId}`);
expect(result.renderedText).toContain("它本身不是 Kubernetes Job 已创建的证据");
expect(result.renderedText).toContain("durable dispatcher 负责自动重试;这不是重复执行 session send 的授权");
expect(result.renderedText).not.toContain(`describe runnerjob/${plannedRunnerJobId}`);
expect(result.renderedText).not.toContain("agentrun send session/");
expect(Buffer.byteLength(result.renderedText, "utf8")).toBeLessThan(7000);
});
test("JSON and YAML expose the same bounded runner admission contract and read-only Next", () => {
const jsonText = renderSessionSendResult(command, durableAdmissionFixture, options("json")).renderedText;
const yamlText = renderSessionSendResult(command, durableAdmissionFixture, options("yaml")).renderedText;
const json = JSON.parse(jsonText) as Record<string, any>;
const yaml = Bun.YAML.parse(yamlText) as Record<string, any>;
expect(yaml).toEqual(json);
expect(json.runnerAdmission).toMatchObject({
mode: "durable-dispatch-intent",
state: "pending",
reason: "session-runner-admission-recovered",
disposition: "recovered",
runId,
commandId,
dispatchIntentId,
plannedRunnerJobId,
actualRunnerJobId: null,
mutation: true,
partialWrite: false,
recoveredPriorPartialWrite: true,
retryable: true,
retryAuthority: "dispatcher",
});
expect(json.resources.runnerJob).toBeNull();
expect(json.resources.plannedRunnerJob).toEqual({
kind: "planned-runnerjob",
id: plannedRunnerJobId,
identityRole: "durable-planned",
kubernetesJobCreated: null,
kubernetesJobCreationEvidence: "not-asserted",
state: "pending",
durable: true,
});
expect(json.next.actions.length).toBeGreaterThan(0);
for (const action of json.next.actions) {
expect(action.mutation).toBe(false);
expect(action.command).toMatch(/^bun scripts\/cli\.ts agentrun (?:describe|events|logs|result)\b/u);
expect(action.command).not.toContain("agentrun send");
expect(action.command).not.toContain(`runnerjob/${plannedRunnerJobId}`);
}
for (const output of [jsonText, yamlText]) expect(Buffer.byteLength(output, "utf8")).toBeLessThan(7000);
});
test("legacy manager success output keeps runnerJob behavior when runnerAdmission is absent", () => {
const human = renderSessionSendResult(command, legacySuccessFixture, options("human")).renderedText;
const json = JSON.parse(renderSessionSendResult(command, legacySuccessFixture, options("json")).renderedText) as Record<string, any>;
expect(human).not.toContain("RunnerAdmission:");
expect(human).toContain("runnerjob/rjob_legacy_fixture state=queued");
expect(human).toContain("只使用 manager 返回的资源 identity 做只读下钻");
expect(json.runnerAdmission).toBeNull();
expect(json.resources.runnerJob).toEqual({ kind: "runnerjob", id: "rjob_legacy_fixture", state: "queued" });
expect(json.resources).not.toHaveProperty("plannedRunnerJob");
expect(json.next.actions.some((action: Record<string, unknown>) => String(action.command).includes("describe runnerjob/rjob_legacy_fixture"))).toBe(true);
});
test("typed failure remains fail-closed, bounded, and does not suggest a repeated send", () => {
const human = renderSessionSendError(command, typedFailureFixture, options("human"));
const jsonText = renderSessionSendError(command, typedFailureFixture, options("json")).renderedText;
const yamlText = renderSessionSendError(command, typedFailureFixture, options("yaml")).renderedText;
const json = JSON.parse(jsonText) as Record<string, any>;
const yaml = Bun.YAML.parse(yamlText) as Record<string, any>;
expect(human.ok).toBe(false);
expect(human.renderedText).toContain("TypedFailure: infra-failed");
expect(human.renderedText).toContain("Reason: session-runner-dispatcher-disabled");
expect(human.renderedText).toContain("Mutation: false");
expect(human.renderedText).toContain("PartialWrite: false");
expect(human.renderedText).toContain("Retryable: false");
expect(human.renderedText).toContain("RetryAuthority: none");
expect(human.renderedText).toContain("RecoveryAction: restore-runner-dispatcher");
expect(human.renderedText).not.toContain("agentrun send session/");
expect(yaml).toEqual(json);
expect(json).toMatchObject({
kind: "SessionSendError",
mutation: false,
partialWrite: false,
error: {
failureKind: "validation-failed",
typedFailureKind: "infra-failed",
code: "runner-admission-failed",
reason: "session-runner-dispatcher-disabled",
retryable: false,
retryAuthority: "none",
recoveryAction: "restore-runner-dispatcher",
},
});
for (const output of [human.renderedText, jsonText, yamlText]) expect(Buffer.byteLength(output, "utf8")).toBeLessThan(7000);
});
});
function options(output: AgentRunOutputMode): AgentRunResourceOptions {
return {
output,
full: false,
raw: false,
debug: false,
taskInput: false,
limit: 20,
cursor: null,
queue: null,
state: null,
unread: false,
readerId: "cli",
taskId: null,
runId: null,
commandId: null,
sessionId: null,
afterSeq: null,
eventDetailSeq: null,
tail: null,
fullText: false,
reason: null,
dryRun: false,
file: null,
aipod: null,
idempotencyKey: null,
promptStdin: false,
node: null,
lane: null,
passthroughArgs: [],
};
}