mirror of https://github.com/openclaw/openclaw.git
227 lines
6.7 KiB
TypeScript
227 lines
6.7 KiB
TypeScript
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
|
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
import {
|
|
loadRunOverflowCompactionHarness,
|
|
mockedEnsureRuntimePluginsLoaded,
|
|
mockedRunEmbeddedAttempt,
|
|
} from "./run.overflow-compaction.harness.js";
|
|
import { buildAttemptReplayMetadata } from "./run/incomplete-turn.js";
|
|
import type { EmbeddedRunAttemptResult } from "./run/types.js";
|
|
|
|
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
|
|
|
function makeAttemptResult(
|
|
overrides: Partial<EmbeddedRunAttemptResult> = {},
|
|
): EmbeddedRunAttemptResult {
|
|
const toolMetas = overrides.toolMetas ?? [];
|
|
const didSendViaMessagingTool = overrides.didSendViaMessagingTool ?? false;
|
|
const successfulCronAdds = overrides.successfulCronAdds;
|
|
return {
|
|
aborted: false,
|
|
timedOut: false,
|
|
timedOutDuringCompaction: false,
|
|
promptError: null,
|
|
sessionIdUsed: "test-session",
|
|
messagesSnapshot: [],
|
|
assistantTexts: [],
|
|
toolMetas,
|
|
lastAssistant: undefined,
|
|
replayMetadata:
|
|
overrides.replayMetadata ??
|
|
buildAttemptReplayMetadata({
|
|
toolMetas,
|
|
didSendViaMessagingTool,
|
|
successfulCronAdds,
|
|
}),
|
|
didSendViaMessagingTool,
|
|
messagingToolSentTexts: [],
|
|
messagingToolSentMediaUrls: [],
|
|
messagingToolSentTargets: [],
|
|
cloudCodeAssistFormatError: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeAssistantMessage(
|
|
overrides: Partial<AssistantMessage> = {},
|
|
): NonNullable<EmbeddedRunAttemptResult["lastAssistant"]> {
|
|
return {
|
|
role: "assistant",
|
|
api: "openai-responses",
|
|
provider: "openai",
|
|
model: "gpt-5.2",
|
|
usage: { input: 0, output: 0 } as AssistantMessage["usage"],
|
|
stopReason: "end_turn" as AssistantMessage["stopReason"],
|
|
timestamp: Date.now(),
|
|
content: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("runEmbeddedPiAgent usage reporting", () => {
|
|
beforeAll(async () => {
|
|
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
|
|
});
|
|
|
|
beforeEach(() => {
|
|
mockedEnsureRuntimePluginsLoaded.mockReset();
|
|
mockedRunEmbeddedAttempt.mockReset();
|
|
});
|
|
|
|
it("bootstraps runtime plugins with the resolved workspace before running", async () => {
|
|
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
|
makeAttemptResult({
|
|
assistantTexts: ["Response 1"],
|
|
}),
|
|
);
|
|
|
|
await runEmbeddedPiAgent({
|
|
sessionId: "test-session",
|
|
sessionKey: "test-key",
|
|
sessionFile: "/tmp/session.json",
|
|
workspaceDir: "/tmp/workspace",
|
|
prompt: "hello",
|
|
timeoutMs: 30000,
|
|
runId: "run-plugin-bootstrap",
|
|
});
|
|
|
|
expect(mockedEnsureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
|
config: undefined,
|
|
workspaceDir: "/tmp/workspace",
|
|
});
|
|
});
|
|
|
|
it("forwards gateway subagent binding opt-in to runtime plugin bootstrap", async () => {
|
|
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
|
makeAttemptResult({
|
|
assistantTexts: ["Response 1"],
|
|
}),
|
|
);
|
|
|
|
await runEmbeddedPiAgent({
|
|
sessionId: "test-session",
|
|
sessionKey: "test-key",
|
|
sessionFile: "/tmp/session.json",
|
|
workspaceDir: "/tmp/workspace",
|
|
prompt: "hello",
|
|
timeoutMs: 30000,
|
|
runId: "run-gateway-bind",
|
|
allowGatewaySubagentBinding: true,
|
|
});
|
|
|
|
expect(mockedEnsureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
|
config: undefined,
|
|
workspaceDir: "/tmp/workspace",
|
|
allowGatewaySubagentBinding: true,
|
|
});
|
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
allowGatewaySubagentBinding: true,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("forwards sender identity fields into embedded attempts", async () => {
|
|
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
|
makeAttemptResult({
|
|
assistantTexts: ["Response 1"],
|
|
}),
|
|
);
|
|
|
|
await runEmbeddedPiAgent({
|
|
sessionId: "test-session",
|
|
sessionKey: "test-key",
|
|
sessionFile: "/tmp/session.json",
|
|
workspaceDir: "/tmp/workspace",
|
|
prompt: "hello",
|
|
timeoutMs: 30000,
|
|
runId: "run-sender-forwarding",
|
|
senderId: "user-123",
|
|
senderName: "Josh Lehman",
|
|
senderUsername: "josh",
|
|
senderE164: "+15551234567",
|
|
});
|
|
|
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
senderId: "user-123",
|
|
senderName: "Josh Lehman",
|
|
senderUsername: "josh",
|
|
senderE164: "+15551234567",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("forwards memory flush write paths into memory-triggered attempts", async () => {
|
|
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
|
makeAttemptResult({
|
|
assistantTexts: [],
|
|
}),
|
|
);
|
|
|
|
await runEmbeddedPiAgent({
|
|
sessionId: "test-session",
|
|
sessionKey: "test-key",
|
|
sessionFile: "/tmp/session.json",
|
|
workspaceDir: "/tmp/workspace",
|
|
prompt: "flush",
|
|
timeoutMs: 30000,
|
|
runId: "run-memory-forwarding",
|
|
trigger: "memory",
|
|
memoryFlushWritePath: "memory/2026-03-10.md",
|
|
});
|
|
|
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
trigger: "memory",
|
|
memoryFlushWritePath: "memory/2026-03-10.md",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("reports total usage from the last turn instead of accumulated total", async () => {
|
|
// Simulate a multi-turn run result.
|
|
// Turn 1: Input 100, Output 50. Total 150.
|
|
// Turn 2: Input 150, Output 50. Total 200.
|
|
|
|
// The accumulated usage (attemptUsage) will be the sum:
|
|
// Input: 100 + 150 = 250 (Note: runEmbeddedAttempt actually returns accumulated usage)
|
|
// Output: 50 + 50 = 100
|
|
// Total: 150 + 200 = 350
|
|
|
|
// The last assistant usage (lastAssistant.usage) will be Turn 2:
|
|
// Input: 150, Output 50, Total 200.
|
|
|
|
// We expect result.meta.agentMeta.usage.total to be 200 (last turn total).
|
|
// The bug causes it to be 350 (accumulated total).
|
|
|
|
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
|
makeAttemptResult({
|
|
assistantTexts: ["Response 1", "Response 2"],
|
|
lastAssistant: makeAssistantMessage({
|
|
usage: { input: 150, output: 50, total: 200 } as unknown as AssistantMessage["usage"],
|
|
}),
|
|
attemptUsage: { input: 250, output: 100, total: 350 },
|
|
}),
|
|
);
|
|
|
|
const result = await runEmbeddedPiAgent({
|
|
sessionId: "test-session",
|
|
sessionKey: "test-key",
|
|
sessionFile: "/tmp/session.json",
|
|
workspaceDir: "/tmp/workspace",
|
|
prompt: "hello",
|
|
timeoutMs: 30000,
|
|
runId: "run-1",
|
|
});
|
|
|
|
// Check usage in meta
|
|
const usage = result.meta.agentMeta?.usage;
|
|
expect(usage).toBeDefined();
|
|
|
|
// Check if total matches the last turn's total (200)
|
|
// If the bug exists, it will likely be 350
|
|
expect(usage?.total).toBe(200);
|
|
});
|
|
});
|