test(agents): slim embedded runner hotspot coverage

This commit is contained in:
Peter Steinberger
2026-04-25 03:15:16 +01:00
parent f9ac92d1cc
commit 4a7ddd7ff5
5 changed files with 52 additions and 149 deletions

View File

@@ -1,64 +0,0 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
cleanupTempPaths,
createContextEngineAttemptRunner,
createContextEngineBootstrapAndAssemble,
resetEmbeddedAttemptHarness,
} from "./attempt.spawn-workspace.test-support.js";
describe("runEmbeddedAttempt empty prompt guard", () => {
const tempPaths: string[] = [];
beforeEach(() => {
resetEmbeddedAttemptHarness();
});
afterEach(async () => {
await cleanupTempPaths(tempPaths);
vi.restoreAllMocks();
});
it("skips provider submission when prompt, history, and images are empty", async () => {
const sessionPrompt = vi.fn(async () => {});
const { assemble } = createContextEngineBootstrapAndAssemble();
const result = await createContextEngineAttemptRunner({
contextEngine: { assemble },
sessionKey: "agent:main:guildchat:dm:empty-prompt",
tempPaths,
sessionMessages: [],
sessionPrompt,
attemptOverrides: {
prompt: " ",
},
});
expect(sessionPrompt).not.toHaveBeenCalled();
expect(result.promptError).toBeNull();
expect(result.finalPromptText).toBeUndefined();
expect(result.messagesSnapshot).toEqual([]);
expect(result.assistantTexts).toEqual([]);
});
it("still submits a blank prompt when replay history has content", async () => {
const sessionPrompt = vi.fn(async () => {});
const { assemble } = createContextEngineBootstrapAndAssemble();
const sessionMessages = [
{ role: "user", content: "previous turn", timestamp: 1 },
] as AgentMessage[];
await createContextEngineAttemptRunner({
contextEngine: { assemble },
sessionKey: "agent:main:guildchat:dm:empty-prompt-with-history",
tempPaths,
sessionMessages,
sessionPrompt,
attemptOverrides: {
prompt: " ",
},
});
expect(sessionPrompt).toHaveBeenCalledTimes(1);
});
});

View File

@@ -11,7 +11,10 @@ const videoGenerationTaskStatusMocks = vi.hoisted(() => ({
vi.mock("../../music-generation-task-status.js", () => musicGenerationTaskStatusMocks);
vi.mock("../../video-generation-task-status.js", () => videoGenerationTaskStatusMocks);
import { resolveAttemptPrependSystemContext } from "./attempt.prompt-helpers.js";
import {
hasPromptSubmissionContent,
resolveAttemptPrependSystemContext,
} from "./attempt.prompt-helpers.js";
describe("resolveAttemptPrependSystemContext", () => {
it("prepends active video task guidance ahead of hook system context", () => {
@@ -62,3 +65,42 @@ describe("resolveAttemptPrependSystemContext", () => {
expect(result).toBe("Hook system context");
});
});
describe("hasPromptSubmissionContent", () => {
it("rejects empty prompt submissions without history or images", () => {
expect(
hasPromptSubmissionContent({
prompt: " ",
messages: [],
imageCount: 0,
}),
).toBe(false);
});
it("allows blank prompt submissions when replay history has content", () => {
expect(
hasPromptSubmissionContent({
prompt: " ",
messages: [{ role: "user", content: "previous turn", timestamp: 1 }],
imageCount: 0,
}),
).toBe(true);
});
it("allows text or image prompt submissions", () => {
expect(
hasPromptSubmissionContent({
prompt: "hello",
messages: [],
imageCount: 0,
}),
).toBe(true);
expect(
hasPromptSubmissionContent({
prompt: " ",
messages: [],
imageCount: 1,
}),
).toBe(true);
});
});

View File

@@ -122,6 +122,14 @@ export function shouldWarnOnOrphanedUserRepair(
return trigger === "user" || trigger === "manual";
}
export function hasPromptSubmissionContent(params: {
prompt: string;
messages: readonly unknown[];
imageCount: number;
}): boolean {
return params.prompt.trim().length > 0 || params.messages.length > 0 || params.imageCount > 0;
}
const QUEUED_USER_MESSAGE_MARKER =
"[Queued user message that arrived while the previous turn was still active]";
const MAX_STRUCTURED_MEDIA_REF_CHARS = 300;

View File

@@ -18,7 +18,6 @@ import {
import {
cleanupTempPaths,
createContextEngineBootstrapAndAssemble,
createContextEngineAttemptRunner,
expectCalledWithSessionKey,
getHoisted,
resetEmbeddedAttemptHarness,
@@ -277,81 +276,6 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
expect(params.sessionKey).toBe(sessionKey);
});
it("prechecks unwindowed context before submitting a windowed context-engine prompt", async () => {
const sessionPrompt = vi.fn(async () => {});
const fullHistory = [
{
role: "assistant",
content: [{ type: "text", text: "large historical context ".repeat(600) }],
timestamp: 1,
},
] as AgentMessage[];
const windowedMessages = [
{ role: "assistant", content: [{ type: "text", text: "small window" }], timestamp: 2 },
] as AgentMessage[];
const assemble = vi.fn(async () => ({
messages: windowedMessages,
estimatedTokens: 3,
}));
const result = await createContextEngineAttemptRunner({
contextEngine: { assemble },
sessionKey,
tempPaths,
sessionMessages: fullHistory,
sessionPrompt,
attemptOverrides: {
contextTokenBudget: 512,
},
});
expect(assemble).toHaveBeenCalledWith(
expect.objectContaining({
messages: fullHistory,
}),
);
expect(sessionPrompt).not.toHaveBeenCalled();
expect(result.promptErrorSource).toBe("precheck");
expect(result.preflightRecovery).toEqual({ route: "compact_only" });
});
it("keeps preflight overflow checks active for engines that own compaction", async () => {
const sessionPrompt = vi.fn(async () => {});
const fullHistory = [
{
role: "assistant",
content: [{ type: "text", text: "engine-owned large historical context ".repeat(600) }],
timestamp: 1,
},
] as AgentMessage[];
const assemble = vi.fn(async () => ({
messages: [
{ role: "assistant", content: [{ type: "text", text: "small window" }], timestamp: 2 },
] as AgentMessage[],
estimatedTokens: 3,
}));
const result = await createContextEngineAttemptRunner({
contextEngine: {
assemble,
info: {
ownsCompaction: true,
},
},
sessionKey,
tempPaths,
sessionMessages: fullHistory,
sessionPrompt,
attemptOverrides: {
contextTokenBudget: 512,
},
});
expect(sessionPrompt).not.toHaveBeenCalled();
expect(result.promptErrorSource).toBe("precheck");
expect(result.preflightRecovery).toEqual({ route: "compact_only" });
});
it("skips maintenance when afterTurn fails", async () => {
const { bootstrap, assemble } = createContextEngineBootstrapAndAssemble();
const afterTurn = vi.fn(async () => {

View File

@@ -252,6 +252,7 @@ import {
resolveAttemptPrependSystemContext,
resolvePromptBuildHookResult,
resolvePromptModeForSession,
hasPromptSubmissionContent,
shouldWarnOnOrphanedUserRepair,
shouldInjectHeartbeatPrompt,
} from "./attempt.prompt-helpers.js";
@@ -453,14 +454,6 @@ function summarizeSessionContext(messages: AgentMessage[]): {
};
}
function hasPromptSubmissionContent(params: {
prompt: string;
messages: readonly AgentMessage[];
imageCount: number;
}): boolean {
return params.prompt.trim().length > 0 || params.messages.length > 0 || params.imageCount > 0;
}
export function applyEmbeddedAttemptToolsAllow<T extends { name: string }>(
tools: T[],
toolsAllow?: string[],