import { beforeEach, describe, expect, it, vi } from "vitest" ;
import type { OpenClawConfig } from "../../config/config.js" ;
import {
resolveAgentDirMock,
resolveSessionAgentIdMock,
} from "./commands-agent-scope.test-support.js" ;
import type { HandleCommandsParams } from "./commands-types.js" ;
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" );
const { handleCompactCommand } = await import ("./commands-compact.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();
resolveAgentDirMock.mockImplementation(
(_cfg: unknown, agentId: string) => `/tmp/workspace/.openclaw/agents/${agentId}/agent`,
);
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" );
resolveAgentDirMock.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(resolveAgentDirMock).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 ,
}),
);
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland