mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-18 20:24:47 +00:00
fix: filter heartbeat pairs before context shaping
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { HEARTBEAT_PROMPT } from "../../../auto-reply/heartbeat.js";
|
||||
import { limitHistoryTurns } from "../history.js";
|
||||
import {
|
||||
cleanupTempPaths,
|
||||
createContextEngineAttemptRunner,
|
||||
@@ -130,4 +133,42 @@ describe("runEmbeddedAttempt context injection", () => {
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it("filters no-op heartbeat pairs before history limiting and context-engine assembly", async () => {
|
||||
hoisted.getDmHistoryLimitFromSessionKeyMock.mockReturnValue(1);
|
||||
hoisted.limitHistoryTurnsMock.mockImplementation(limitHistoryTurns);
|
||||
const assemble = vi.fn(async ({ messages }: { messages: AgentMessage[] }) => ({
|
||||
messages,
|
||||
estimatedTokens: 1,
|
||||
}));
|
||||
const sessionMessages: AgentMessage[] = [
|
||||
{ role: "user", content: "real question", timestamp: 1 } as AgentMessage,
|
||||
{ role: "assistant", content: "real answer", timestamp: 2 } as AgentMessage,
|
||||
{ role: "user", content: HEARTBEAT_PROMPT, timestamp: 3 } as AgentMessage,
|
||||
{ role: "assistant", content: "HEARTBEAT_OK", timestamp: 4 } as AgentMessage,
|
||||
];
|
||||
|
||||
await createContextEngineAttemptRunner({
|
||||
contextEngine: { assemble },
|
||||
attemptOverrides: {
|
||||
config: {
|
||||
agents: {
|
||||
list: [{ id: "main", heartbeat: {} }],
|
||||
},
|
||||
},
|
||||
},
|
||||
sessionKey: "agent:main:discord:dm:test-user",
|
||||
sessionMessages,
|
||||
tempPaths,
|
||||
});
|
||||
|
||||
expect(assemble).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
messages: [
|
||||
expect.objectContaining({ role: "user", content: "real question" }),
|
||||
expect.objectContaining({ role: "assistant", content: "real answer" }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,6 +53,10 @@ type AttemptSpawnWorkspaceHoisted = {
|
||||
getGlobalHookRunnerMock: Mock<() => unknown>;
|
||||
initializeGlobalHookRunnerMock: UnknownMock;
|
||||
runContextEngineMaintenanceMock: AsyncUnknownMock;
|
||||
getDmHistoryLimitFromSessionKeyMock: Mock<
|
||||
(sessionKey: string | undefined, config: unknown) => number | undefined
|
||||
>;
|
||||
limitHistoryTurnsMock: Mock<<T>(messages: T, limit: number | undefined) => T>;
|
||||
sessionManager: SessionManagerMocks;
|
||||
};
|
||||
|
||||
@@ -99,6 +103,12 @@ const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => {
|
||||
const getGlobalHookRunnerMock = vi.fn<() => unknown>(() => undefined);
|
||||
const initializeGlobalHookRunnerMock = vi.fn();
|
||||
const runContextEngineMaintenanceMock = vi.fn(async (_params?: unknown) => undefined);
|
||||
const getDmHistoryLimitFromSessionKeyMock = vi.fn<
|
||||
(sessionKey: string | undefined, config: unknown) => number | undefined
|
||||
>(() => undefined);
|
||||
const limitHistoryTurnsMock = vi.fn<<T>(messages: T, limit: number | undefined) => T>(
|
||||
(messages) => messages,
|
||||
);
|
||||
const sessionManager = {
|
||||
getLeafEntry: vi.fn(() => null),
|
||||
branch: vi.fn(),
|
||||
@@ -124,6 +134,8 @@ const hoisted = vi.hoisted((): AttemptSpawnWorkspaceHoisted => {
|
||||
getGlobalHookRunnerMock,
|
||||
initializeGlobalHookRunnerMock,
|
||||
runContextEngineMaintenanceMock,
|
||||
getDmHistoryLimitFromSessionKeyMock,
|
||||
limitHistoryTurnsMock,
|
||||
sessionManager,
|
||||
};
|
||||
});
|
||||
@@ -430,8 +442,10 @@ vi.mock("../compaction-safety-timeout.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../history.js", () => ({
|
||||
getDmHistoryLimitFromSessionKey: () => undefined,
|
||||
limitHistoryTurns: <T>(messages: T) => messages,
|
||||
getDmHistoryLimitFromSessionKey: (sessionKey: string | undefined, config: unknown) =>
|
||||
hoisted.getDmHistoryLimitFromSessionKeyMock(sessionKey, config),
|
||||
limitHistoryTurns: <T>(messages: T, limit: number | undefined) =>
|
||||
hoisted.limitHistoryTurnsMock(messages, limit),
|
||||
}));
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
@@ -599,6 +613,8 @@ export function resetEmbeddedAttemptHarness(
|
||||
hoisted.hasCompletedBootstrapTurnMock.mockReset().mockResolvedValue(false);
|
||||
hoisted.getGlobalHookRunnerMock.mockReset().mockReturnValue(undefined);
|
||||
hoisted.runContextEngineMaintenanceMock.mockReset().mockResolvedValue(undefined);
|
||||
hoisted.getDmHistoryLimitFromSessionKeyMock.mockReset().mockReturnValue(undefined);
|
||||
hoisted.limitHistoryTurnsMock.mockReset().mockImplementation((messages) => messages);
|
||||
hoisted.sessionManager.getLeafEntry.mockReset().mockReturnValue(null);
|
||||
hoisted.sessionManager.branch.mockReset();
|
||||
hoisted.sessionManager.resetLeaf.mockReset();
|
||||
@@ -755,6 +771,7 @@ export async function createContextEngineAttemptRunner(params: {
|
||||
info?: Partial<ContextEngineInfo>;
|
||||
};
|
||||
attemptOverrides?: Partial<Parameters<Awaited<ReturnType<typeof loadRunEmbeddedAttempt>>>[0]>;
|
||||
sessionMessages?: AgentMessage[];
|
||||
sessionKey: string;
|
||||
tempPaths: string[];
|
||||
}) {
|
||||
@@ -764,9 +781,8 @@ export async function createContextEngineAttemptRunner(params: {
|
||||
const sessionFile = path.join(workspaceDir, "session.jsonl");
|
||||
params.tempPaths.push(workspaceDir, agentDir);
|
||||
await fs.writeFile(sessionFile, "", "utf8");
|
||||
const seedMessages: AgentMessage[] = [
|
||||
{ role: "user", content: "seed", timestamp: 1 } as AgentMessage,
|
||||
];
|
||||
const seedMessages: AgentMessage[] =
|
||||
params.sessionMessages ?? ([{ role: "user", content: "seed", timestamp: 1 }] as AgentMessage[]);
|
||||
const infoId = params.contextEngine.info?.id ?? "test-context-engine";
|
||||
const infoName = params.contextEngine.info?.name ?? "Test Context Engine";
|
||||
const infoVersion = params.contextEngine.info?.version ?? "0.0.1";
|
||||
|
||||
@@ -1217,8 +1217,17 @@ export async function runEmbeddedAttempt(
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
});
|
||||
const truncated = limitHistoryTurns(
|
||||
const heartbeatSummary =
|
||||
params.config && sessionAgentId
|
||||
? resolveHeartbeatSummaryForAgent(params.config, sessionAgentId)
|
||||
: undefined;
|
||||
const heartbeatFiltered = filterHeartbeatPairs(
|
||||
validated,
|
||||
heartbeatSummary?.ackMaxChars,
|
||||
heartbeatSummary?.prompt,
|
||||
);
|
||||
const truncated = limitHistoryTurns(
|
||||
heartbeatFiltered,
|
||||
getDmHistoryLimitFromSessionKey(params.sessionKey, params.config),
|
||||
);
|
||||
// Re-run tool_use/tool_result pairing repair after truncation, since
|
||||
@@ -1667,10 +1676,6 @@ export async function runEmbeddedAttempt(
|
||||
activeSession.agent.state.messages = activeSession.messages;
|
||||
}
|
||||
|
||||
const heartbeatSummary =
|
||||
params.config && sessionAgentId
|
||||
? resolveHeartbeatSummaryForAgent(params.config, sessionAgentId)
|
||||
: undefined;
|
||||
const filteredMessages = filterHeartbeatPairs(
|
||||
activeSession.messages,
|
||||
heartbeatSummary?.ackMaxChars,
|
||||
|
||||
Reference in New Issue
Block a user