mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 13:11:40 +00:00
test: share msteams monitor and pi runner fixtures
This commit is contained in:
@@ -3,13 +3,15 @@ import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
||||
import type { MSTeamsAdapter } from "./messenger.js";
|
||||
import {
|
||||
type MSTeamsActivityHandler,
|
||||
type MSTeamsMessageHandlerDeps,
|
||||
registerMSTeamsHandlers,
|
||||
} from "./monitor-handler.js";
|
||||
import {
|
||||
createActivityHandler,
|
||||
createMSTeamsMessageHandlerDeps,
|
||||
} from "./monitor-handler.test-helpers.js";
|
||||
import type { MSTeamsPollStore } from "./polls.js";
|
||||
import { setMSTeamsRuntime } from "./runtime.js";
|
||||
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
||||
@@ -57,66 +59,16 @@ function createRuntimeStub(readAllowFromStore: ReturnType<typeof vi.fn>): Plugin
|
||||
} as unknown as PluginRuntime;
|
||||
}
|
||||
|
||||
function createActivityHandler(run = vi.fn(async () => undefined)): MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
} {
|
||||
let handler: MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
handler = {
|
||||
onMessage: () => handler,
|
||||
onMembersAdded: () => handler,
|
||||
onReactionsAdded: () => handler,
|
||||
onReactionsRemoved: () => handler,
|
||||
run,
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
function createDeps(params: {
|
||||
cfg: OpenClawConfig;
|
||||
readAllowFromStore?: ReturnType<typeof vi.fn>;
|
||||
}): MSTeamsMessageHandlerDeps {
|
||||
const readAllowFromStore = params.readAllowFromStore ?? vi.fn(async () => []);
|
||||
setMSTeamsRuntime(createRuntimeStub(readAllowFromStore));
|
||||
|
||||
const adapter: MSTeamsAdapter = {
|
||||
continueConversation: async () => {},
|
||||
process: async () => {},
|
||||
updateActivity: async () => {},
|
||||
deleteActivity: async () => {},
|
||||
};
|
||||
const conversationStore: MSTeamsConversationStore = {
|
||||
upsert: async () => {},
|
||||
get: async () => null,
|
||||
list: async () => [],
|
||||
remove: async () => false,
|
||||
findByUserId: async () => null,
|
||||
};
|
||||
const pollStore: MSTeamsPollStore = {
|
||||
createPoll: async () => {},
|
||||
getPoll: async () => null,
|
||||
recordVote: async () => null,
|
||||
};
|
||||
|
||||
return {
|
||||
return createMSTeamsMessageHandlerDeps({
|
||||
cfg: params.cfg,
|
||||
runtime: { error: vi.fn() } as unknown as RuntimeEnv,
|
||||
appId: "test-app-id",
|
||||
adapter,
|
||||
tokenProvider: {
|
||||
getAccessToken: async () => "token",
|
||||
},
|
||||
textLimit: 4000,
|
||||
mediaMaxBytes: 8 * 1024 * 1024,
|
||||
conversationStore,
|
||||
pollStore,
|
||||
log: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createFeedbackInvokeContext(params: {
|
||||
@@ -174,6 +126,33 @@ async function expectFileMissing(filePath: string) {
|
||||
await expect(access(filePath)).rejects.toThrow();
|
||||
}
|
||||
|
||||
async function withFeedbackHandler(params: {
|
||||
cfg: OpenClawConfig;
|
||||
context: Parameters<typeof createFeedbackInvokeContext>[0];
|
||||
assertResult: (args: { tmpDir: string; originalRun: ReturnType<typeof vi.fn> }) => Promise<void>;
|
||||
}) {
|
||||
const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
|
||||
try {
|
||||
const originalRun = vi.fn(async () => undefined);
|
||||
const handler = registerMSTeamsHandlers(
|
||||
createActivityHandler(originalRun),
|
||||
createDeps({
|
||||
cfg: {
|
||||
...params.cfg,
|
||||
session: { store: tmpDir },
|
||||
},
|
||||
}),
|
||||
) as MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
|
||||
await handler.run(createFeedbackInvokeContext(params.context));
|
||||
await params.assertResult({ tmpDir, originalRun });
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("msteams feedback invoke authz", () => {
|
||||
beforeEach(() => {
|
||||
feedbackReflectionMockState.runFeedbackReflection.mockReset();
|
||||
@@ -181,148 +160,106 @@ describe("msteams feedback invoke authz", () => {
|
||||
});
|
||||
|
||||
it("records feedback for an allowlisted DM sender", async () => {
|
||||
const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
|
||||
try {
|
||||
const originalRun = vi.fn(async () => undefined);
|
||||
const handler = registerMSTeamsHandlers(
|
||||
createActivityHandler(originalRun),
|
||||
createDeps({
|
||||
cfg: {
|
||||
session: { store: tmpDir },
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
}),
|
||||
) as MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
|
||||
await handler.run(
|
||||
createFeedbackInvokeContext({
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "owner-aad",
|
||||
senderName: "Owner",
|
||||
comment: "allowed feedback",
|
||||
}),
|
||||
);
|
||||
|
||||
const transcript = await readFile(
|
||||
path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
expect(JSON.parse(transcript.trim())).toMatchObject({
|
||||
event: "feedback",
|
||||
messageId: "bot-msg-1",
|
||||
value: "positive",
|
||||
await withFeedbackHandler({
|
||||
cfg: {
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
context: {
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "owner-aad",
|
||||
senderName: "Owner",
|
||||
comment: "allowed feedback",
|
||||
sessionKey: "msteams:direct:owner-aad",
|
||||
conversationId: "a:personal-chat",
|
||||
});
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
assertResult: async ({ tmpDir, originalRun }) => {
|
||||
const transcript = await readFile(
|
||||
path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
expect(JSON.parse(transcript.trim())).toMatchObject({
|
||||
event: "feedback",
|
||||
messageId: "bot-msg-1",
|
||||
value: "positive",
|
||||
comment: "allowed feedback",
|
||||
sessionKey: "msteams:direct:owner-aad",
|
||||
conversationId: "a:personal-chat",
|
||||
});
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps DM feedback allowed when team route allowlists exist", async () => {
|
||||
const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
|
||||
try {
|
||||
const originalRun = vi.fn(async () => undefined);
|
||||
const handler = registerMSTeamsHandlers(
|
||||
createActivityHandler(originalRun),
|
||||
createDeps({
|
||||
cfg: {
|
||||
session: { store: tmpDir },
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
teams: {
|
||||
team123: {
|
||||
channels: {
|
||||
"19:group@thread.tacv2": { requireMention: false },
|
||||
},
|
||||
},
|
||||
await withFeedbackHandler({
|
||||
cfg: {
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
teams: {
|
||||
team123: {
|
||||
channels: {
|
||||
"19:group@thread.tacv2": { requireMention: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
}),
|
||||
) as MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
|
||||
await handler.run(
|
||||
createFeedbackInvokeContext({
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "owner-aad",
|
||||
senderName: "Owner",
|
||||
comment: "allowed dm feedback",
|
||||
}),
|
||||
);
|
||||
|
||||
const transcript = await readFile(
|
||||
path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
expect(JSON.parse(transcript.trim())).toMatchObject({
|
||||
event: "feedback",
|
||||
value: "positive",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
context: {
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "owner-aad",
|
||||
senderName: "Owner",
|
||||
comment: "allowed dm feedback",
|
||||
sessionKey: "msteams:direct:owner-aad",
|
||||
});
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
assertResult: async ({ tmpDir, originalRun }) => {
|
||||
const transcript = await readFile(
|
||||
path.join(tmpDir, "msteams_direct_owner-aad.jsonl"),
|
||||
"utf-8",
|
||||
);
|
||||
expect(JSON.parse(transcript.trim())).toMatchObject({
|
||||
event: "feedback",
|
||||
value: "positive",
|
||||
comment: "allowed dm feedback",
|
||||
sessionKey: "msteams:direct:owner-aad",
|
||||
});
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not record feedback for a DM sender outside allowFrom", async () => {
|
||||
const tmpDir = await mkdtemp(path.join(tmpdir(), "openclaw-msteams-feedback-"));
|
||||
try {
|
||||
const originalRun = vi.fn(async () => undefined);
|
||||
const handler = registerMSTeamsHandlers(
|
||||
createActivityHandler(originalRun),
|
||||
createDeps({
|
||||
cfg: {
|
||||
session: { store: tmpDir },
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
}),
|
||||
) as MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
|
||||
await handler.run(
|
||||
createFeedbackInvokeContext({
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "attacker-aad",
|
||||
senderName: "Attacker",
|
||||
comment: "blocked feedback",
|
||||
}),
|
||||
);
|
||||
|
||||
await expectFileMissing(path.join(tmpDir, "msteams_direct_attacker-aad.jsonl"));
|
||||
expect(feedbackReflectionMockState.runFeedbackReflection).not.toHaveBeenCalled();
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
await withFeedbackHandler({
|
||||
cfg: {
|
||||
channels: {
|
||||
msteams: {
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["owner-aad"],
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
context: {
|
||||
reaction: "like",
|
||||
conversationId: "a:personal-chat;messageid=bot-msg-1",
|
||||
conversationType: "personal",
|
||||
senderId: "attacker-aad",
|
||||
senderName: "Attacker",
|
||||
comment: "blocked feedback",
|
||||
},
|
||||
assertResult: async ({ tmpDir, originalRun }) => {
|
||||
await expectFileMissing(path.join(tmpDir, "msteams_direct_attacker-aad.jsonl"));
|
||||
expect(feedbackReflectionMockState.runFeedbackReflection).not.toHaveBeenCalled();
|
||||
expect(originalRun).not.toHaveBeenCalled();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not trigger reflection for a group sender outside groupAllowFrom", async () => {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
||||
import type { MSTeamsAdapter } from "./messenger.js";
|
||||
import {
|
||||
type MSTeamsActivityHandler,
|
||||
type MSTeamsMessageHandlerDeps,
|
||||
registerMSTeamsHandlers,
|
||||
} from "./monitor-handler.js";
|
||||
import {
|
||||
createActivityHandler,
|
||||
createMSTeamsMessageHandlerDeps,
|
||||
} from "./monitor-handler.test-helpers.js";
|
||||
import { clearPendingUploads, getPendingUpload, storePendingUpload } from "./pending-uploads.js";
|
||||
import type { MSTeamsPollStore } from "./polls.js";
|
||||
import { setMSTeamsRuntime } from "./runtime.js";
|
||||
import type { MSTeamsTurnContext } from "./sdk-types.js";
|
||||
|
||||
@@ -39,56 +40,12 @@ const runtimeStub: PluginRuntime = {
|
||||
} as unknown as PluginRuntime;
|
||||
|
||||
function createDeps(): MSTeamsMessageHandlerDeps {
|
||||
const adapter: MSTeamsAdapter = {
|
||||
continueConversation: async () => {},
|
||||
process: async () => {},
|
||||
updateActivity: async () => {},
|
||||
deleteActivity: async () => {},
|
||||
};
|
||||
const conversationStore: MSTeamsConversationStore = {
|
||||
upsert: async () => {},
|
||||
get: async () => null,
|
||||
list: async () => [],
|
||||
remove: async () => false,
|
||||
findByUserId: async () => null,
|
||||
};
|
||||
const pollStore: MSTeamsPollStore = {
|
||||
createPoll: async () => {},
|
||||
getPoll: async () => null,
|
||||
recordVote: async () => null,
|
||||
};
|
||||
return {
|
||||
return createMSTeamsMessageHandlerDeps({
|
||||
cfg: {} as OpenClawConfig,
|
||||
runtime: {
|
||||
error: vi.fn(),
|
||||
} as unknown as RuntimeEnv,
|
||||
appId: "test-app-id",
|
||||
adapter,
|
||||
tokenProvider: {
|
||||
getAccessToken: async () => "token",
|
||||
},
|
||||
textLimit: 4000,
|
||||
mediaMaxBytes: 8 * 1024 * 1024,
|
||||
conversationStore,
|
||||
pollStore,
|
||||
log: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createActivityHandler(): MSTeamsActivityHandler {
|
||||
let handler: MSTeamsActivityHandler;
|
||||
handler = {
|
||||
onMessage: () => handler,
|
||||
onMembersAdded: () => handler,
|
||||
onReactionsAdded: () => handler,
|
||||
onReactionsRemoved: () => handler,
|
||||
run: async () => {},
|
||||
};
|
||||
return handler;
|
||||
});
|
||||
}
|
||||
|
||||
function createInvokeContext(params: {
|
||||
|
||||
67
extensions/msteams/src/monitor-handler.test-helpers.ts
Normal file
67
extensions/msteams/src/monitor-handler.test-helpers.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { vi } from "vitest";
|
||||
import type { OpenClawConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { MSTeamsConversationStore } from "./conversation-store.js";
|
||||
import type { MSTeamsAdapter } from "./messenger.js";
|
||||
import type { MSTeamsActivityHandler, MSTeamsMessageHandlerDeps } from "./monitor-handler.js";
|
||||
import type { MSTeamsPollStore } from "./polls.js";
|
||||
|
||||
export function createActivityHandler(
|
||||
run = vi.fn(async () => undefined),
|
||||
): MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
} {
|
||||
let handler: MSTeamsActivityHandler & {
|
||||
run: NonNullable<MSTeamsActivityHandler["run"]>;
|
||||
};
|
||||
handler = {
|
||||
onMessage: () => handler,
|
||||
onMembersAdded: () => handler,
|
||||
onReactionsAdded: () => handler,
|
||||
onReactionsRemoved: () => handler,
|
||||
run,
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
||||
export function createMSTeamsMessageHandlerDeps(params?: {
|
||||
cfg?: OpenClawConfig;
|
||||
runtime?: RuntimeEnv;
|
||||
}): MSTeamsMessageHandlerDeps {
|
||||
const adapter: MSTeamsAdapter = {
|
||||
continueConversation: async () => {},
|
||||
process: async () => {},
|
||||
updateActivity: async () => {},
|
||||
deleteActivity: async () => {},
|
||||
};
|
||||
const conversationStore: MSTeamsConversationStore = {
|
||||
upsert: async () => {},
|
||||
get: async () => null,
|
||||
list: async () => [],
|
||||
remove: async () => false,
|
||||
findByUserId: async () => null,
|
||||
};
|
||||
const pollStore: MSTeamsPollStore = {
|
||||
createPoll: async () => {},
|
||||
getPoll: async () => null,
|
||||
recordVote: async () => null,
|
||||
};
|
||||
|
||||
return {
|
||||
cfg: (params?.cfg ?? {}) as OpenClawConfig,
|
||||
runtime: (params?.runtime ?? { error: vi.fn() }) as RuntimeEnv,
|
||||
appId: "test-app-id",
|
||||
adapter,
|
||||
tokenProvider: {
|
||||
getAccessToken: async () => "token",
|
||||
},
|
||||
textLimit: 4000,
|
||||
mediaMaxBytes: 8 * 1024 * 1024,
|
||||
conversationStore,
|
||||
pollStore,
|
||||
log: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -95,6 +95,43 @@ const sessionHook = (action: string): SessionHookEvent | undefined =>
|
||||
return event?.type === "session" && event.action === action;
|
||||
})?.[0] as SessionHookEvent | undefined;
|
||||
|
||||
async function runCompactionHooks(params: { sessionKey?: string; messageProvider?: string }) {
|
||||
const originalMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const currentMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
|
||||
originalMessages,
|
||||
currentMessages,
|
||||
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
|
||||
});
|
||||
|
||||
const hookState = await compactTesting.runBeforeCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: TEST_SESSION_ID,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionAgentId: "main",
|
||||
workspaceDir: TEST_WORKSPACE_DIR,
|
||||
messageProvider: params.messageProvider,
|
||||
metrics: beforeMetrics,
|
||||
});
|
||||
|
||||
await compactTesting.runAfterCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: TEST_SESSION_ID,
|
||||
sessionAgentId: "main",
|
||||
hookSessionKey: hookState.hookSessionKey,
|
||||
missingSessionKey: hookState.missingSessionKey,
|
||||
workspaceDir: TEST_WORKSPACE_DIR,
|
||||
messageProvider: params.messageProvider,
|
||||
messageCountAfter: 1,
|
||||
tokensAfter: 10,
|
||||
compactedCount: 1,
|
||||
sessionFile: TEST_SESSION_FILE,
|
||||
summaryLength: "summary".length,
|
||||
tokensBefore: 120,
|
||||
firstKeptEntryId: "entry-1",
|
||||
});
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const loaded = await loadCompactHooksHarness();
|
||||
compactEmbeddedPiSessionDirect = loaded.compactEmbeddedPiSessionDirect;
|
||||
@@ -174,37 +211,9 @@ describe("compactEmbeddedPiSessionDirect hooks", () => {
|
||||
|
||||
it("emits internal + plugin compaction hooks with counts", async () => {
|
||||
hookRunner.hasHooks.mockReturnValue(true);
|
||||
const originalMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const currentMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
|
||||
originalMessages,
|
||||
currentMessages,
|
||||
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
|
||||
});
|
||||
const { hookSessionKey, missingSessionKey } = await compactTesting.runBeforeCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:session-1",
|
||||
sessionAgentId: "main",
|
||||
workspaceDir: "/tmp",
|
||||
await runCompactionHooks({
|
||||
sessionKey: TEST_SESSION_KEY,
|
||||
messageProvider: "telegram",
|
||||
metrics: beforeMetrics,
|
||||
});
|
||||
await compactTesting.runAfterCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: "session-1",
|
||||
sessionAgentId: "main",
|
||||
hookSessionKey,
|
||||
missingSessionKey,
|
||||
workspaceDir: "/tmp",
|
||||
messageProvider: "telegram",
|
||||
messageCountAfter: 1,
|
||||
tokensAfter: 10,
|
||||
compactedCount: 1,
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
summaryLength: "summary".length,
|
||||
tokensBefore: 120,
|
||||
firstKeptEntryId: "entry-1",
|
||||
});
|
||||
|
||||
expect(sessionHook("compact:before")).toMatchObject({
|
||||
@@ -248,32 +257,7 @@ describe("compactEmbeddedPiSessionDirect hooks", () => {
|
||||
|
||||
it("uses sessionId as hook session key fallback when sessionKey is missing", async () => {
|
||||
hookRunner.hasHooks.mockReturnValue(true);
|
||||
const originalMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const currentMessages = sessionMessages.slice(1) as AgentMessage[];
|
||||
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
|
||||
originalMessages,
|
||||
currentMessages,
|
||||
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
|
||||
});
|
||||
const { hookSessionKey, missingSessionKey } = await compactTesting.runBeforeCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: "session-1",
|
||||
sessionAgentId: "main",
|
||||
workspaceDir: "/tmp",
|
||||
metrics: beforeMetrics,
|
||||
});
|
||||
await compactTesting.runAfterCompactionHooks({
|
||||
hookRunner,
|
||||
sessionId: "session-1",
|
||||
sessionAgentId: "main",
|
||||
hookSessionKey,
|
||||
missingSessionKey,
|
||||
workspaceDir: "/tmp",
|
||||
messageCountAfter: 1,
|
||||
tokensAfter: 10,
|
||||
compactedCount: 1,
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
});
|
||||
await runCompactionHooks({});
|
||||
|
||||
expect(sessionHook("compact:before")?.sessionKey).toBe("session-1");
|
||||
expect(sessionHook("compact:after")?.sessionKey).toBe("session-1");
|
||||
|
||||
@@ -34,43 +34,6 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
|
||||
resetRunOverflowCompactionHarnessMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockedRunEmbeddedAttempt.mockReset();
|
||||
mockedRunContextEngineMaintenance.mockReset();
|
||||
mockedCompactDirect.mockReset();
|
||||
mockedCoerceToFailoverError.mockReset();
|
||||
mockedDescribeFailoverError.mockReset();
|
||||
mockedResolveFailoverStatus.mockReset();
|
||||
mockedSessionLikelyHasOversizedToolResults.mockReset();
|
||||
mockedTruncateOversizedToolResultsInSession.mockReset();
|
||||
mockedGlobalHookRunner.runBeforeAgentStart.mockReset();
|
||||
mockedGlobalHookRunner.runBeforeCompaction.mockReset();
|
||||
mockedGlobalHookRunner.runAfterCompaction.mockReset();
|
||||
mockedPickFallbackThinkingLevel.mockReset();
|
||||
mockedContextEngine.info.ownsCompaction = false;
|
||||
mockedCompactDirect.mockResolvedValue({
|
||||
ok: false,
|
||||
compacted: false,
|
||||
reason: "nothing to compact",
|
||||
});
|
||||
mockedRunContextEngineMaintenance.mockResolvedValue(undefined);
|
||||
mockedCoerceToFailoverError.mockReturnValue(null);
|
||||
mockedDescribeFailoverError.mockImplementation((err: unknown) => ({
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
reason: undefined,
|
||||
status: undefined,
|
||||
code: undefined,
|
||||
}));
|
||||
mockedSessionLikelyHasOversizedToolResults.mockReturnValue(false);
|
||||
mockedTruncateOversizedToolResultsInSession.mockResolvedValue({
|
||||
truncated: false,
|
||||
truncatedCount: 0,
|
||||
reason: "no oversized tool results",
|
||||
});
|
||||
mockedPickFallbackThinkingLevel.mockReturnValue(null);
|
||||
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
|
||||
});
|
||||
|
||||
it("passes precomputed legacy before_agent_start result into the attempt", async () => {
|
||||
const legacyResult = {
|
||||
modelOverride: "legacy-model",
|
||||
|
||||
@@ -2,19 +2,14 @@ import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { makeAttemptResult, makeCompactionSuccess } from "./run.overflow-compaction.fixture.js";
|
||||
import {
|
||||
loadRunOverflowCompactionHarness,
|
||||
mockedCoerceToFailoverError,
|
||||
mockedCompactDirect,
|
||||
mockedContextEngine,
|
||||
mockedDescribeFailoverError,
|
||||
mockedGetApiKeyForModel,
|
||||
mockedGlobalHookRunner,
|
||||
mockedPickFallbackThinkingLevel,
|
||||
mockedResolveAuthProfileOrder,
|
||||
mockedResolveFailoverStatus,
|
||||
mockedRunEmbeddedAttempt,
|
||||
mockedRunPostCompactionSideEffects,
|
||||
mockedSessionLikelyHasOversizedToolResults,
|
||||
mockedTruncateOversizedToolResultsInSession,
|
||||
overflowBaseRunParams,
|
||||
resetRunOverflowCompactionHarnessMocks,
|
||||
} from "./run.overflow-compaction.harness.js";
|
||||
@@ -38,47 +33,6 @@ describe("timeout-triggered compaction", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
resetRunOverflowCompactionHarnessMocks();
|
||||
mockedRunEmbeddedAttempt.mockReset();
|
||||
mockedCompactDirect.mockReset();
|
||||
mockedCoerceToFailoverError.mockReset();
|
||||
mockedDescribeFailoverError.mockReset();
|
||||
mockedResolveFailoverStatus.mockReset();
|
||||
mockedSessionLikelyHasOversizedToolResults.mockReset();
|
||||
mockedTruncateOversizedToolResultsInSession.mockReset();
|
||||
mockedGlobalHookRunner.runBeforeAgentStart.mockReset();
|
||||
mockedGlobalHookRunner.runBeforeCompaction.mockReset();
|
||||
mockedGlobalHookRunner.runAfterCompaction.mockReset();
|
||||
mockedPickFallbackThinkingLevel.mockReset();
|
||||
mockedRunPostCompactionSideEffects.mockReset();
|
||||
mockedRunPostCompactionSideEffects.mockResolvedValue(undefined);
|
||||
mockedContextEngine.info.ownsCompaction = false;
|
||||
mockedCompactDirect.mockResolvedValue({
|
||||
ok: false,
|
||||
compacted: false,
|
||||
reason: "nothing to compact",
|
||||
});
|
||||
mockedCoerceToFailoverError.mockReturnValue(null);
|
||||
mockedDescribeFailoverError.mockImplementation((err: unknown) => ({
|
||||
message: err instanceof Error ? err.message : String(err),
|
||||
reason: undefined,
|
||||
status: undefined,
|
||||
code: undefined,
|
||||
}));
|
||||
mockedSessionLikelyHasOversizedToolResults.mockReturnValue(false);
|
||||
mockedTruncateOversizedToolResultsInSession.mockResolvedValue({
|
||||
truncated: false,
|
||||
truncatedCount: 0,
|
||||
reason: "no oversized tool results",
|
||||
});
|
||||
mockedPickFallbackThinkingLevel.mockReturnValue(null);
|
||||
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
|
||||
mockedGetApiKeyForModel.mockImplementation(async ({ profileId } = {}) => ({
|
||||
apiKey: "test-key",
|
||||
profileId: profileId ?? "test-profile",
|
||||
source: "test",
|
||||
mode: "api-key",
|
||||
}));
|
||||
mockedResolveAuthProfileOrder.mockReturnValue([]);
|
||||
});
|
||||
|
||||
it("attempts compaction when LLM times out with high prompt token usage (>65%)", async () => {
|
||||
|
||||
Reference in New Issue
Block a user