AkurAI Build
Menu

popagent

public

Latest change da93a36aa02bf1a49b5498d2b9007fe7c24551e2 - Release 1.0.51: warm code-intelligence index in the background, researcher queries the index first by AkurAI Build

import { describe, expect, test } from "bun:test";
import type { Agent } from "@mastra/core/agent";
import type { AgentTask } from "./api-types";
import type { AgentExecutionRuntime } from "./agent-execution";
import {
  isContainmentDeniedEvidence,
  runSelfUpdateWorkflow,
  type SelfUpdateWorkflowAgents,
  type SelfUpdateWorkflowDependencies,
} from "./self-update-workflow";
import type { SelfUpdateRun } from "./self-update-policy";
const DIFF_DIGEST = "1".repeat(64);

const run: SelfUpdateRun = {
  taskId: "workflow-task",
  workspaceId: "workflow-workspace",
  repositoryPath: "/registered",
  path: "/contained/self-update",
  branch: "autonomous/self-update/test",
  snapshot: { head: "a".repeat(40), branch: "main", remotes: "remote-hash" },
  cloneRemotes: "clone-remote-hash",
  delegations: new Set<string>(),
  deploymentEnabled: false,
};

function task(): AgentTask {
  return {
    id: run.taskId,
    sessionId: "workflow-session",
    scheduleId: "workflow-schedule",
    source: "self-update",
    workspaceId: run.workspaceId,
    prompt: "Improve the contained repository",
    model: "test/model",
    status: "running",
    output: null,
    error: null,
    stepsCompleted: 0,
    progress: null,
    recoveryCount: 0,
    attemptCount: 1,
    maxAttempts: 1,
    nextAttemptAt: null,
    lastErrorClass: null,
    deadLetteredAt: null,
    createdAt: "",
    startedAt: "",
    completedAt: null,
  };
}

function fakeAgent(
  onGenerate: (phase: string) => unknown | Promise<unknown>,
): Agent {
  const fake = {
    __fork: () => fake,
    setBrowser: () => undefined,
    getModel: () => ({ provider: "test", modelId: "structurer" }),
    stream: async (_prompt: string, options: { runId?: string }) => ({
      object: Promise.resolve(await onGenerate(options.runId?.split("-").at(-1) ?? "")),
    }),
  };
  return fake as unknown as Agent;
}

function agents(
  onGenerate: (phase: string) => unknown | Promise<unknown>,
): SelfUpdateWorkflowAgents {
  return {
    researcher: fakeAgent(onGenerate),
    orchistrator: fakeAgent(onGenerate),
    implementer: fakeAgent(onGenerate),
    reviewer: fakeAgent(onGenerate),
  };
}

function dependencies(
  signal: AbortSignal,
  workflowAgents: SelfUpdateWorkflowAgents,
  calls: string[],
  reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }>,
): SelfUpdateWorkflowDependencies {
  let inspections = 0;
  return {
    execution: {
      prepare: async (input: { prompt: string }) => ({
        prompt: input.prompt,
        options: {},
      }),
    } as unknown as AgentExecutionRuntime,
    policy: {
      begin: async () => run,
      observeDelegation: () => undefined,
      inspect: async () => {
        inspections += 1;
        return inspections === 1
          ? {
            changedPaths: [],
            diffCheck: "clean",
            diffEvidence: "",
            diffDigest: DIFF_DIGEST,
            clean: true,
          }
          : {
            changedPaths: ["src/change.ts"],
            diffCheck: "checked",
            diffEvidence: "diff --git a/src/change.ts b/src/change.ts",
            diffDigest: DIFF_DIGEST,
            clean: false,
          };
      },
      verify: async () => {
        calls.push("verify");
        return "verified";
      },
      commit: async () => {
        calls.push("commit");
        return "commit-id";
      },
      accept: async () => {
        calls.push("accept");
      },
      reject: async () => {
        calls.push("reject");
      },
    },
    agents: workflowAgents,
    report: async (detail) => {
      reports.push({
        state: detail.state,
        phases: detail.phases.map((phase) => ({ id: phase.id, state: phase.state })),
      });
    },
    signal,
  };
}

describe("self-update workflow contracts", () => {
  test("recognizes varied containment-denial evidence", () => {
    expect(isContainmentDeniedEvidence("Self-update containment denied a protected path")).toBe(true);
    expect(isContainmentDeniedEvidence("The operation was denied at the containment level")).toBe(true);
    expect(isContainmentDeniedEvidence("Repository inspection completed inside containment")).toBe(false);
  });

  test("no-op completes without implementation, review, or commit", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const result = await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(
        new AbortController().signal,
        agents((phase) => {
          if (phase === "research") {
            calls.push("research");
            return { summary: "Nothing requires a source change", evidence: [], relevantPaths: [], noOp: true };
          }
          calls.push("decide");
          return { rationale: "The requested state is already satisfied", noOp: true, verificationCommand: null };
        }),
        calls,
        reports,
      ),
    );

    expect(result).toBe("Self-update verified no-op with no source changes");
    expect(calls).toEqual(["research", "decide", "accept"]);
    expect(reports.at(-1)?.state).toBe("completed");
    expect(reports.at(-1)?.phases.filter((phase) => phase.state === "skipped").map((phase) => phase.id)).toEqual([
      "implement",
      "inspect-change",
      "verify",
      "review",
      "commit",
    ]);
  });

  test("reports safe structured-handoff trace stages", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const trace: Array<{ phase: string; stage: string; attempt: number }> = [];
    const configured = dependencies(
      new AbortController().signal,
      agents((phase) => phase === "research"
        ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
        : { rationale: "Already satisfied", noOp: true, verificationCommand: null }),
      calls,
      reports,
    );
    configured.traceHandoff = (event) => trace.push(event);

    await runSelfUpdateWorkflow(task(), "workflow-turn", "workflow-trace", configured);

    expect(trace).toEqual([
      { phase: "research", stage: "stream-start", attempt: 1 },
      { phase: "research", stage: "stream-created", attempt: 1 },
      { phase: "research", stage: "object-resolved", attempt: 1 },
      { phase: "decide", stage: "stream-start", attempt: 1 },
      { phase: "decide", stage: "stream-created", attempt: 1 },
      { phase: "decide", stage: "object-resolved", attempt: 1 },
    ]);
  });

  test("uses the orchestrator model to structure local specialist output", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const modes: unknown[] = [];
    const structurer = { provider: "test", modelId: "structurer" };
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => structurer,
      stream: async (_prompt: string, options: {
        runId?: string;
        structuredOutput?: { jsonPromptInjection?: unknown; model?: unknown };
      }) => {
        modes.push([options.structuredOutput?.jsonPromptInjection, options.structuredOutput?.model]);
        const phase = options.runId?.split("-").at(-1);
        return {
          object: Promise.resolve(phase === "research"
            ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
            : { rationale: "Already satisfied", noOp: true, verificationCommand: null }),
        };
      },
    } as unknown as Agent;

    await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    );

    expect(modes).toEqual([["auto", structurer], ["auto", structurer]]);
  });

  test("gives repository research the configured specialist budget", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const maxSteps: Array<[string | undefined, number | undefined]> = [];
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => ({ provider: "test", modelId: "structurer" }),
      stream: async (_prompt: string, options: { runId?: string; maxSteps?: number }) => {
        const phase = options.runId?.split("-").at(-1);
        maxSteps.push([phase, options.maxSteps]);
        return {
          object: Promise.resolve(phase === "research"
            ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
            : { rationale: "Already satisfied", noOp: true, verificationCommand: null }),
        };
      },
    } as unknown as Agent;

    await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    );
    expect(maxSteps[0]).toEqual(["research", 12]);
  });

  test("recovers structured handoffs from the streamed JSON text", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => ({ provider: "test", modelId: "structurer" }),
      stream: async (_prompt: string, options: { runId?: string }) => {
        const phase = options.runId?.split("-").at(-1);
        return {
          object: Promise.resolve(phase === "research"
            ? { result: { summary: "No change", evidence: { path: "src/agent.ts", finding: "No implementation needed" }, relevantPaths: [], noOp: true } }
            : undefined),
          text: Promise.resolve(`handoff follows\n${JSON.stringify({ rationale: "Already satisfied", noOp: true, verificationCommand: null })}\nfinished`),
        };
      },
    } as unknown as Agent;

    const result = await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    );

    expect(result).toBe("Self-update verified no-op with no source changes");
    expect(reports.at(-1)?.state).toBe("completed");
  });

  test("synthesizes a tool-free handoff when the specialist exhausts its steps with no text", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const streams: Array<{ runId?: string; toolChoice?: string; maxSteps?: number; prompt: unknown }> = [];
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => ({ provider: "test", modelId: "structurer" }),
      stream: async (prompt: unknown, options: { runId?: string; toolChoice?: string; maxSteps?: number }) => {
        streams.push({ ...options, prompt });
        const phase = options.runId?.split("-").at(-1);
        if (phase === "research") {
          // Budget exhausted mid-tool-call: no object, no text, dangling assistant tail.
          return {
            object: Promise.resolve(undefined),
            text: Promise.resolve(""),
            // response.messages carries only generated turns, never the request.
            response: Promise.resolve({ messages: [
              { role: "assistant", content: [{ type: "tool-call", toolName: "grep" }] },
              { role: "tool", content: [{ type: "tool-result", toolName: "grep" }] },
              { role: "assistant", content: [{ type: "tool-call", toolName: "read" }] },
            ] }),
          };
        }
        if (phase === "synthesis") {
          return { object: Promise.resolve({ summary: "No change", evidence: [], relevantPaths: [], noOp: true }) };
        }
        return { object: Promise.resolve({ rationale: "Already satisfied", noOp: true, verificationCommand: null }) };
      },
    } as unknown as Agent;

    const result = await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    );

    expect(result).toBe("Self-update verified no-op with no source changes");
    const synthesis = streams.find((entry) => entry.runId?.endsWith("-research-synthesis"));
    expect(synthesis).toMatchObject({ toolChoice: "none", maxSteps: 1 });
    const history = synthesis?.prompt as Array<{ role: string; content: unknown }>;
    expect(history.map((message) => message.role)).toEqual(["user", "assistant", "tool", "user"]);
    // The original research request leads the synthesis conversation.
    expect(String(history[0]?.content)).toContain("Research the requested contained self-update");
  });

  test("bounds oversized research evidence instead of failing the workflow", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string; evidence: string[] }> }> = [];
    const oversized = "x".repeat(501);
    const result = await runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, agents((phase) => phase === "research"
        ? { summary: "No change", evidence: ["bounded", oversized], relevantPaths: [], noOp: true }
        : { rationale: "Already satisfied", noOp: true, verificationCommand: null }), calls, reports),
    );

    expect(result).toBe("Self-update verified no-op with no source changes");
  });

  test("retries one malformed structured handoff", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    let attempts = 0;
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => ({ provider: "test", modelId: "structurer" }),
      stream: async (_prompt: string, options: { runId?: string }) => {
        const phase = options.runId?.split("-").at(-1);
        return {
          object: phase === "research" && attempts++ === 0
            ? Promise.reject(new SyntaxError("JSON Parse error: Unrecognized token '#'"))
            : Promise.resolve(phase === "research"
              ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
              : { rationale: "Already satisfied", noOp: true, verificationCommand: null }),
        };
      },
    } as unknown as Agent;

    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    )).resolves.toBe("Self-update verified no-op with no source changes");
    expect(attempts).toBe(2);
  });

  test("retries two malformed structured handoffs", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    let attempts = 0;
    const agent = {
      __fork: () => agent,
      setBrowser: () => undefined,
      getModel: () => ({ provider: "test", modelId: "structurer" }),
      stream: async (_prompt: string, options: { runId?: string }) => {
        const phase = options.runId?.split("-").at(-1);
        return {
          object: phase === "research" && attempts++ < 2
            ? Promise.reject(new SyntaxError("JSON Parse error: Unexpected EOF"))
            : Promise.resolve(phase === "research"
              ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
              : { rationale: "Already satisfied", noOp: true, verificationCommand: null }),
        };
      },
    } as unknown as Agent;

    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    )).resolves.toBe("Self-update verified no-op with no source changes");
    expect(attempts).toBe(3);
  });

  test("retries one transient phase failure", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    let attempts = 0;
    const agent = fakeAgent((phase) => {
      if (phase === "research" && attempts++ === 0) throw new Error("socket connection was closed unexpectedly");
      return phase === "research"
        ? { summary: "No change", evidence: [], relevantPaths: [], noOp: true }
        : { rationale: "Already satisfied", noOp: true, verificationCommand: null };
    });
    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(new AbortController().signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    )).resolves.toBe("Self-update verified no-op with no source changes");
    expect(attempts).toBe(2);
  });

  test("does not retry a transient failure after the run has been cancelled", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const controller = new AbortController();
    let attempts = 0;
    const agent = fakeAgent((phase) => {
      if (phase === "research") {
        attempts += 1;
        controller.abort();
        throw new Error("SyntaxError: JSON Parse error: Unexpected EOF");
      }
      throw new Error(`unexpected phase ${phase}`);
    });
    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(controller.signal, {
        researcher: agent,
        orchistrator: agent,
        implementer: agent,
        reviewer: agent,
      }, calls, reports),
    )).rejects.toThrow();

    expect(attempts).toBe(1);
    expect(reports.at(-1)?.state).toBe("cancelled");
  });

  test("research failure prevents commit", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(
        new AbortController().signal,
        agents((phase) => {
          calls.push(phase);
          if (phase === "research") throw new Error("research failed");
          throw new Error(`unexpected phase ${phase}`);
        }),
        calls,
        reports,
      ),
    )).rejects.toThrow("Research phase failed: research failed");

    expect(calls).toEqual(["research", "reject"]);
    expect(calls).not.toContain("commit");
    expect(reports.at(-1)?.state).toBe("failed");
    expect(reports.at(-1)?.phases.find((phase) => phase.id === "research")?.state).toBe("failed");
  });

  test("cancellation prevents later phases", async () => {
    const calls: string[] = [];
    const reports: Array<{ state: string; phases: Array<{ id: string; state: string }> }> = [];
    const controller = new AbortController();
    await expect(runSelfUpdateWorkflow(
      task(),
      "workflow-turn",
      "workflow-trace",
      dependencies(
        controller.signal,
        agents((phase) => {
          calls.push(phase);
          if (phase === "research") {
            controller.abort();
            return { summary: "Research complete", evidence: [], relevantPaths: [], noOp: false };
          }
          throw new Error(`unexpected phase ${phase}`);
        }),
        calls,
        reports,
      ),
    )).rejects.toThrow();

    expect(calls).toEqual(["research", "reject"]);
    expect(calls).not.toContain("commit");
    expect(reports.at(-1)?.state).toBe("cancelled");
    expect(reports.at(-1)?.phases.filter((phase) => phase.state === "skipped").map((phase) => phase.id)).toEqual([
      "implement",
      "inspect-change",
      "verify",
      "review",
      "commit",
    ]);
  });
});