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 = { 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 = { 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 = { 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; const yaml = Bun.YAML.parse(yamlText) as Record; 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; 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(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; const yaml = Bun.YAML.parse(yamlText) as Record; 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: [], }; }