mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
327 lines
10 KiB
TypeScript
327 lines
10 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { resolveAgentDir } from "../../agents/agent-scope.js";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import { handleCompactCommand } from "./commands-compact.js";
|
|
import type { HandleCommandsParams } from "./commands-types.js";
|
|
|
|
const resolveSessionAgentIdMock = vi.hoisted(() => vi.fn(() => "main"));
|
|
|
|
vi.mock("../../agents/agent-scope.js", async () => {
|
|
const actual = await vi.importActual<typeof import("../../agents/agent-scope.js")>(
|
|
"../../agents/agent-scope.js",
|
|
);
|
|
return {
|
|
...actual,
|
|
resolveSessionAgentId: resolveSessionAgentIdMock,
|
|
resolveAgentDir: vi.fn(actual.resolveAgentDir),
|
|
};
|
|
});
|
|
|
|
vi.mock("./commands-compact.runtime.js", () => ({
|
|
abortEmbeddedPiRun: vi.fn(),
|
|
compactEmbeddedPiSession: vi.fn(),
|
|
enqueueSystemEvent: vi.fn(),
|
|
formatContextUsageShort: vi.fn(() => "Context 12.1k"),
|
|
formatTokenCount: vi.fn((value: number) => `${value}`),
|
|
incrementCompactionCount: vi.fn(),
|
|
isEmbeddedPiRunActive: vi.fn().mockReturnValue(false),
|
|
resolveFreshSessionTotalTokens: vi.fn(() => 12_345),
|
|
resolveSessionFilePath: vi.fn(() => "/tmp/session.json"),
|
|
resolveSessionFilePathOptions: vi.fn(() => ({})),
|
|
waitForEmbeddedPiRunEnd: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
const { compactEmbeddedPiSession, incrementCompactionCount, resolveSessionFilePathOptions } =
|
|
await import("./commands-compact.runtime.js");
|
|
|
|
function buildCompactParams(
|
|
commandBodyNormalized: string,
|
|
cfg: OpenClawConfig,
|
|
): HandleCommandsParams {
|
|
return {
|
|
cfg,
|
|
ctx: {
|
|
Provider: "whatsapp",
|
|
Surface: "whatsapp",
|
|
CommandSource: "text",
|
|
CommandBody: commandBodyNormalized,
|
|
},
|
|
command: {
|
|
commandBodyNormalized,
|
|
isAuthorizedSender: true,
|
|
senderIsOwner: false,
|
|
senderId: "owner",
|
|
channel: "whatsapp",
|
|
ownerList: [],
|
|
},
|
|
sessionKey: "agent:main:main",
|
|
sessionStore: {},
|
|
resolveDefaultThinkingLevel: async () => "medium",
|
|
} as unknown as HandleCommandsParams;
|
|
}
|
|
|
|
describe("handleCompactCommand", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
resolveSessionAgentIdMock.mockReturnValue("main");
|
|
});
|
|
|
|
it("returns null when command is not /compact", async () => {
|
|
const result = await handleCompactCommand(
|
|
buildCompactParams("/status", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
} as OpenClawConfig),
|
|
true,
|
|
);
|
|
|
|
expect(result).toBeNull();
|
|
expect(vi.mocked(compactEmbeddedPiSession)).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects unauthorized /compact commands", async () => {
|
|
const params = buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
} as OpenClawConfig);
|
|
|
|
const result = await handleCompactCommand(
|
|
{
|
|
...params,
|
|
command: {
|
|
...params.command,
|
|
isAuthorizedSender: false,
|
|
senderId: "unauthorized",
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(result).toEqual({ shouldContinue: false });
|
|
expect(vi.mocked(compactEmbeddedPiSession)).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("routes manual compaction with explicit trigger and context metadata", async () => {
|
|
vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({
|
|
ok: true,
|
|
compacted: false,
|
|
});
|
|
|
|
const result = await handleCompactCommand(
|
|
{
|
|
...buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
session: { store: "/tmp/openclaw-session-store.json" },
|
|
} as OpenClawConfig),
|
|
ctx: {
|
|
Provider: "whatsapp",
|
|
Surface: "whatsapp",
|
|
CommandSource: "text",
|
|
CommandBody: "/compact: focus on decisions",
|
|
From: "+15550001",
|
|
To: "+15550002",
|
|
SenderName: "Alice",
|
|
SenderUsername: "alice_u",
|
|
SenderE164: "+15551234567",
|
|
},
|
|
agentDir: "/tmp/openclaw-agent-compact",
|
|
sessionEntry: {
|
|
sessionId: "session-1",
|
|
updatedAt: Date.now(),
|
|
groupId: "group-1",
|
|
groupChannel: "#general",
|
|
space: "workspace-1",
|
|
spawnedBy: "agent:main:parent",
|
|
totalTokens: 12345,
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(result?.shouldContinue).toBe(false);
|
|
expect(vi.mocked(compactEmbeddedPiSession)).toHaveBeenCalledOnce();
|
|
expect(vi.mocked(compactEmbeddedPiSession)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sessionId: "session-1",
|
|
sessionKey: "agent:main:main",
|
|
allowGatewaySubagentBinding: true,
|
|
trigger: "manual",
|
|
customInstructions: "focus on decisions",
|
|
messageChannel: "whatsapp",
|
|
groupId: "group-1",
|
|
groupChannel: "#general",
|
|
groupSpace: "workspace-1",
|
|
spawnedBy: "agent:main:parent",
|
|
senderId: "owner",
|
|
senderName: "Alice",
|
|
senderUsername: "alice_u",
|
|
senderE164: "+15551234567",
|
|
agentDir: "/tmp/openclaw-agent-compact",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("uses the canonical session agent when resolving the compaction session file", async () => {
|
|
vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({
|
|
ok: true,
|
|
compacted: false,
|
|
});
|
|
resolveSessionAgentIdMock.mockReturnValue("target");
|
|
|
|
await handleCompactCommand(
|
|
{
|
|
...buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
session: { store: "/tmp/openclaw-session-store.json" },
|
|
} as OpenClawConfig),
|
|
agentId: "main",
|
|
sessionKey: "agent:target:whatsapp:direct:12345",
|
|
sessionEntry: {
|
|
sessionId: "session-1",
|
|
updatedAt: Date.now(),
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(resolveSessionAgentIdMock).toHaveBeenCalledWith({
|
|
sessionKey: "agent:target:whatsapp:direct:12345",
|
|
config: expect.any(Object),
|
|
});
|
|
expect(vi.mocked(resolveSessionFilePathOptions)).toHaveBeenCalledWith({
|
|
agentId: "target",
|
|
storePath: undefined,
|
|
});
|
|
});
|
|
|
|
it("uses the canonical session agent directory for compaction runtime inputs", async () => {
|
|
vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({
|
|
ok: true,
|
|
compacted: false,
|
|
});
|
|
resolveSessionAgentIdMock.mockReturnValue("target");
|
|
vi.mocked(resolveAgentDir).mockReturnValue("/tmp/target-agent");
|
|
|
|
await handleCompactCommand(
|
|
{
|
|
...buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
} as OpenClawConfig),
|
|
agentId: "main",
|
|
agentDir: "/tmp/main-agent",
|
|
sessionKey: "agent:target:whatsapp:direct:12345",
|
|
sessionEntry: {
|
|
sessionId: "session-1",
|
|
updatedAt: Date.now(),
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(vi.mocked(compactEmbeddedPiSession)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
agentDir: "/tmp/target-agent",
|
|
}),
|
|
);
|
|
expect(vi.mocked(resolveAgentDir)).toHaveBeenCalledWith(expect.any(Object), "target");
|
|
});
|
|
|
|
it("prefers the target session entry for compaction runtime metadata", async () => {
|
|
vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({
|
|
ok: true,
|
|
compacted: false,
|
|
});
|
|
|
|
await handleCompactCommand(
|
|
{
|
|
...buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
} as OpenClawConfig),
|
|
sessionKey: "agent:target:whatsapp:direct:12345",
|
|
sessionEntry: {
|
|
sessionId: "wrapper-session",
|
|
updatedAt: Date.now(),
|
|
groupId: "wrapper-group",
|
|
groupChannel: "#wrapper",
|
|
space: "wrapper-space",
|
|
spawnedBy: "agent:wrapper",
|
|
skillsSnapshot: { prompt: "wrapper", skills: [] },
|
|
contextTokens: 111,
|
|
},
|
|
sessionStore: {
|
|
"agent:target:whatsapp:direct:12345": {
|
|
sessionId: "target-session",
|
|
updatedAt: Date.now(),
|
|
groupId: "target-group",
|
|
groupChannel: "#target",
|
|
space: "target-space",
|
|
spawnedBy: "agent:target-parent",
|
|
skillsSnapshot: { prompt: "target", skills: [] },
|
|
contextTokens: 222,
|
|
},
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(vi.mocked(compactEmbeddedPiSession)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sessionId: "target-session",
|
|
groupId: "target-group",
|
|
groupChannel: "#target",
|
|
groupSpace: "target-space",
|
|
spawnedBy: "agent:target-parent",
|
|
skillsSnapshot: { prompt: "target", skills: [] },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("prefers the target session entry when incrementing compaction count", async () => {
|
|
vi.mocked(compactEmbeddedPiSession).mockResolvedValueOnce({
|
|
ok: true,
|
|
compacted: true,
|
|
result: {
|
|
summary: "compacted",
|
|
firstKeptEntryId: "first-kept",
|
|
tokensBefore: 999,
|
|
tokensAfter: 321,
|
|
},
|
|
});
|
|
|
|
await handleCompactCommand(
|
|
{
|
|
...buildCompactParams("/compact", {
|
|
commands: { text: true },
|
|
channels: { whatsapp: { allowFrom: ["*"] } },
|
|
} as OpenClawConfig),
|
|
sessionKey: "agent:target:whatsapp:direct:12345",
|
|
sessionEntry: {
|
|
sessionId: "wrapper-session",
|
|
updatedAt: Date.now(),
|
|
},
|
|
sessionStore: {
|
|
"agent:target:whatsapp:direct:12345": {
|
|
sessionId: "target-session",
|
|
updatedAt: Date.now(),
|
|
},
|
|
},
|
|
} as HandleCommandsParams,
|
|
true,
|
|
);
|
|
|
|
expect(vi.mocked(incrementCompactionCount)).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sessionEntry: expect.objectContaining({
|
|
sessionId: "target-session",
|
|
}),
|
|
tokensAfter: 321,
|
|
skillsSnapshot: { prompt: "target", skills: [] },
|
|
}),
|
|
);
|
|
});
|
|
});
|