Menu
popagent
publicLatest change 22e859e348543a86a311c69c2d20a2c27168a212 - Fix Mastra lifecycle gaps, human task cards, and rate-limited test models by AkurAI Build
import { describe, expect, test } from "bun:test";
import { renderToStaticMarkup } from "react-dom/server";
import type { AgentWorkflowDetail, AgentWorkflowSummary, EvolutionRevision, EvolutionSignal } from "../api-types";
import {
AutonomyRevisionList,
AutonomySignalList,
AutonomyWorkflowPhaseDetail,
AutonomyWorkflowRail,
countAutonomySignalHealth,
startAutonomyHistoryPolling,
} from "./AutonomySettingsPanel";
const timestamp = "2026-08-14T12:00:00.000Z";
describe("autonomy history", () => {
test("shows processing, retry, and dead-letter evidence without hiding errors", () => {
const signal: EvolutionSignal = {
id: "signal-1",
sessionId: "session-1",
turnId: "turn-1",
traceId: "trace-1",
agentId: "orchistrator",
workspaceId: "default",
kind: "turn-failure",
summary: "Provider timed out while reflecting.",
status: "dead-letter",
attempts: 3,
nextAttemptAt: null,
processedAt: timestamp,
error: "Provider timeout",
createdAt: timestamp,
updatedAt: timestamp,
};
const processing: EvolutionSignal = {
...signal,
id: "signal-processing",
status: "processing",
attempts: 1,
error: null,
};
const retry: EvolutionSignal = {
...signal,
id: "signal-retry",
status: "pending",
attempts: 2,
nextAttemptAt: timestamp,
error: null,
};
const signals = [processing, retry, signal];
const html = renderToStaticMarkup(<AutonomySignalList signals={signals} workspaces={[]} />);
expect(html).toContain("processing");
expect(html).toContain("pending");
expect(html).toContain("retry");
expect(html).toContain("dead-letter");
expect(html).toContain("3 attempts");
expect(html).toContain("Provider timeout");
expect(html).toContain("trace trace-1");
expect(countAutonomySignalHealth(signals)).toEqual({
pending: 1,
processing: 1,
applied: 0,
ignored: 0,
retried: 2,
deadLetters: 1,
});
});
test("refreshes while mounted, then aborts the active request on unmount", async () => {
const secondStarted = Promise.withResolvers<void>();
const releaseSecond = Promise.withResolvers<void>();
let calls = 0;
let requestSignal: AbortSignal | undefined;
const poll = startAutonomyHistoryPolling(async (signal) => {
calls++;
requestSignal = signal;
if (calls === 2) {
secondStarted.resolve();
await releaseSecond.promise;
}
}, 0);
await secondStarted.promise;
expect(calls).toBe(2);
poll.stop();
expect(requestSignal?.aborted).toBe(true);
releaseSecond.resolve();
await releaseSecond.promise;
await Promise.resolve();
expect(calls).toBe(2);
});
test("offers revert only for an applied learned revision", () => {
const revision = (status: EvolutionRevision["status"]): EvolutionRevision => ({
id: `revision-${status}`,
agentId: "orchistrator",
targetType: "overlay",
targetKey: "reflection",
beforeContent: "Before",
afterContent: "After",
rationale: "Repeated operator corrections",
evidenceIds: ["signal-1"],
revertsRevisionId: null,
status,
createdAt: timestamp,
appliedAt: timestamp,
});
const original = revision("applied");
const compensation = {
...revision("reverted"),
revertsRevisionId: original.id,
};
const applied = renderToStaticMarkup(<AutonomyRevisionList revisions={[original]} onRevert={() => undefined} />);
const reverted = renderToStaticMarkup(<AutonomyRevisionList revisions={[compensation]} onRevert={() => undefined} />);
const superseded = renderToStaticMarkup(<AutonomyRevisionList revisions={[compensation, original]} onRevert={() => undefined} />);
const fact = renderToStaticMarkup(<AutonomyRevisionList
revisions={[{ ...original, id: "revision-fact", targetType: "fact", targetKey: "verified-command" }]}
onRevert={() => undefined}
/>);
expect(applied).toContain("Inspect learned change");
expect(applied).toContain("Revert change");
expect(reverted).toContain("reverted");
expect(reverted).toContain(`reverts ${original.id}`);
expect(reverted).not.toContain("Revert change");
expect(superseded).not.toContain("Revert change");
expect(fact).toContain("Learned durable fact");
});
test("renders the ordered workflow rail, every phase state, and outcome guidance accessibly", () => {
const phaseIds = ["prepare", "research", "decide", "implement", "inspect-change", "verify", "review", "commit"] as const;
const states = ["waiting", "active", "skipped", "cancelled", "complete", "failed", "complete", "complete"] as const;
const workflow: AgentWorkflowSummary = {
runId: "run-1",
correlationId: "corr-1",
state: "failed",
currentPhase: "verify",
phases: phaseIds.map((id, index) => ({
id,
label: id,
state: states[index]!,
evidence: [`Evidence for ${id}`],
})),
};
const detail: AgentWorkflowDetail = {
...workflow,
phases: workflow.phases.map((phase) => ({
...phase,
startedAt: timestamp,
completedAt: timestamp,
error: phase.id === "verify" ? "Focused check failed" : null,
})),
};
const rail = renderToStaticMarkup(<AutonomyWorkflowRail workflow={workflow} selectedPhase="verify" onSelect={() => undefined} />);
expect(rail).toContain('aria-label="Autonomous workflow phases"');
expect(rail).toContain('aria-current="step"');
for (const label of ["Prepare", "Research", "Decide", "Implement", "Inspect change", "Verify", "Review", "Commit"]) expect(rail).toContain(label);
for (const state of ["Waiting", "Active", "Complete", "Failed", "Cancelled", "Skipped"]) expect(rail).toContain(state);
const failed = renderToStaticMarkup(<AutonomyWorkflowPhaseDetail workflow={detail} selectedPhase="verify" />);
const noOp = renderToStaticMarkup(<AutonomyWorkflowPhaseDetail workflow={detail} selectedPhase="decide" />);
const commit = renderToStaticMarkup(<AutonomyWorkflowPhaseDetail workflow={detail} selectedPhase="commit" />);
expect(failed).toContain("Focused check failed");
expect(failed).toContain("This phase failed");
expect(noOp).toContain("No-op");
expect(commit).toContain("local to the prepared branch");
// A cancelled phase inherits the abort-time stream error; it must not be shown as a failure alert.
const cancelled = renderToStaticMarkup(<AutonomyWorkflowPhaseDetail workflow={{
...detail,
phases: detail.phases.map((phase) => phase.id === "research"
? { ...phase, state: "cancelled", error: "SyntaxError: JSON Parse error: Unexpected EOF" }
: phase),
}} selectedPhase="research" />);
expect(cancelled).not.toContain("JSON Parse error");
expect(cancelled).toContain("This phase was cancelled by the operator");
});
});