mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
7a8eee4a36
* perf(agents): keep turn-path model catalog reads off the full live build First agent turns (embedded and cron) resolved thinking capability through loadPreparedModelCatalogSnapshot without readOnly, which materialized the full live model-runtime catalog: ambient synthetic-auth discovery fanned out to every registered provider and loaded plugin discovery modules through jiti source transform (3,172 TS modules, 36s event-loop block, +600MB heap, 58.7s model-selection on a cold gateway). - add loadProviderScopedThinkingCatalog: manifest metadata first, then a provider-scoped read-only static catalog, then scoped live discovery only for runtime-discovery providers (preserves #116584 Ollama semantics) - route scopedLiveProviderDiscovery through the scoped read-only loader - scope live-mode ambient synthetic-auth refs to the requested providers - bound the last-resort synthetic-auth sweep to discovery entry modules - memoize per-turn plugin skill dir resolution/republish (single-slot, lifecycle-cleared; was a full walk + symlink republish every turn) Cold first turn 72.7s -> ~22s wall (remaining cost is provider prefill of the ~19.5k-token default prompt); model-selection 58,726ms -> 124ms. * test(agents): align model-catalog.runtime mocks with scoped thinking catalog seam Explicit vi.mock factories must export every binding prod touches; the new loadProviderScopedThinkingCatalog export is now mocked everywhere the module is stubbed, and the live-model-switch Ollama hydration test asserts the new provider-scoped seam instead of the retired unscoped snapshot call shape. * test(agents): export scoped thinking catalog from every prepared-catalog mock; split synthetic-auth helpers - add loadProviderScopedThinkingCatalog to all explicit prepared-model-catalog and model-catalog.runtime mock factories (vi.mock factories must export every binding prod touches) - move synthetic-auth ref scoping/resolution into prepared-model-runtime.synthetic-auth.ts; keeps facts under the max-lines cap * test(agents): prove scoped thinking hydration for runtime-only models Boundary proof for the ClawSweeper review gap: the three-tier helper stops at manifest or scoped-static when they resolve, and runs provider-scoped live discovery (no broad fanout) only for runtime-only models; cron selection hydrates through the same scoped helper and skips it entirely for thinking=off. * test(agents): accept rest args in scoped thinking catalog mocks
151 lines
6.1 KiB
TypeScript
151 lines
6.1 KiB
TypeScript
/** Shared E2E mocks for directive behavior tests that exercise reply-agent dispatch. */
|
|
import { vi, type Mock } from "vitest";
|
|
|
|
export const runEmbeddedAgentMock: Mock = vi.fn();
|
|
export const compactEmbeddedAgentSessionMock: Mock = vi.fn();
|
|
export const loadModelCatalogMock: Mock = vi.fn();
|
|
export const resolveCommandSecretRefsViaGatewayMock: Mock = vi.fn();
|
|
export const clearSessionAuthProfileOverrideMock: Mock = vi.fn();
|
|
export const resolveSessionAuthProfileOverrideMock: Mock = vi.fn();
|
|
|
|
function objectRecord(value: unknown): Record<string, unknown> | undefined {
|
|
return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;
|
|
}
|
|
|
|
function normalizeReplyAgentPayload(payload: Record<string, unknown>, params: unknown) {
|
|
const text = typeof payload.text === "string" ? payload.text : undefined;
|
|
if (!text) {
|
|
return payload;
|
|
}
|
|
const explicitReplyMatch = text.match(/\[\[\s*reply_to\s*:\s*([^\]]+?)\s*\]\]/i);
|
|
const explicitReplyToId = explicitReplyMatch?.[1]?.trim();
|
|
const replyToCurrentPattern = /\[\[\s*reply_to_current\s*\]\]/gi;
|
|
const hasReplyToCurrent = replyToCurrentPattern.test(text);
|
|
const currentMessageId = objectRecord(objectRecord(params)?.sessionCtx)?.MessageSid;
|
|
// Directive tests encode reply targets in text markers so mocked agents can stay lightweight.
|
|
const cleanedText = text
|
|
.replace(replyToCurrentPattern, "")
|
|
.replace(/\[\[\s*reply_to\s*:\s*([^\]]+?)\s*\]\]/gi, "")
|
|
.trim();
|
|
|
|
return {
|
|
...payload,
|
|
text: cleanedText,
|
|
...(explicitReplyToId
|
|
? { replyToId: explicitReplyToId }
|
|
: hasReplyToCurrent && typeof currentMessageId === "string"
|
|
? { replyToId: currentMessageId, replyToCurrent: true }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
async function runMockedReplyAgent(runParams: unknown, params: unknown) {
|
|
const result = await runEmbeddedAgentMock(runParams);
|
|
const payloadsRaw = objectRecord(result)?.payloads;
|
|
const payloads = Array.isArray(payloadsRaw)
|
|
? payloadsRaw.flatMap((payload) => {
|
|
const record = objectRecord(payload);
|
|
return record ? [record] : [];
|
|
})
|
|
: [];
|
|
const normalized = payloads.map((payload) => normalizeReplyAgentPayload(payload, params));
|
|
if (normalized.length === 0) {
|
|
return undefined;
|
|
}
|
|
return normalized.length === 1 ? normalized[0] : normalized;
|
|
}
|
|
|
|
/** Runs the mocked reply agent using the follow-up run payload from directive tests. */
|
|
export async function runDirectiveBehaviorReplyAgent(params: unknown) {
|
|
const runParams = objectRecord(objectRecord(params)?.followupRun)?.run ?? {};
|
|
return await runMockedReplyAgent(runParams, params);
|
|
}
|
|
|
|
export const runReplyAgentMock: Mock = vi.fn(runDirectiveBehaviorReplyAgent);
|
|
|
|
/** Runs the mocked prepared-reply path with the resolved model and elevation settings. */
|
|
export async function runDirectiveBehaviorPreparedReply(params: unknown) {
|
|
const input = objectRecord(params) ?? {};
|
|
const runParams = {
|
|
provider: input.provider,
|
|
model: input.model,
|
|
thinkLevel: input.resolvedThinkLevel,
|
|
reasoningLevel: input.resolvedReasoningLevel,
|
|
bashElevated: {
|
|
enabled: input.elevatedEnabled === true,
|
|
allowed: input.elevatedAllowed === true,
|
|
defaultLevel:
|
|
typeof input.resolvedElevatedLevel === "string" ? input.resolvedElevatedLevel : "off",
|
|
fullAccessAvailable: true,
|
|
},
|
|
};
|
|
return await runMockedReplyAgent(runParams, params);
|
|
}
|
|
|
|
export const runPreparedReplyMock: Mock = vi.fn(runDirectiveBehaviorPreparedReply);
|
|
|
|
vi.mock("../agents/embedded-agent.js", () => ({
|
|
abortEmbeddedAgentRun: vi.fn().mockReturnValue(false),
|
|
compactEmbeddedAgentSession: (...args: unknown[]) => compactEmbeddedAgentSessionMock(...args),
|
|
runEmbeddedAgent: (...args: unknown[]) => runEmbeddedAgentMock(...args),
|
|
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
|
isEmbeddedAgentRunActive: vi.fn().mockReturnValue(false),
|
|
isEmbeddedAgentRunStreaming: vi.fn().mockReturnValue(false),
|
|
}));
|
|
|
|
vi.mock("../agents/embedded-agent.runtime.js", () => ({
|
|
abortEmbeddedAgentRun: vi.fn().mockReturnValue(false),
|
|
compactEmbeddedAgentSession: (...args: unknown[]) => compactEmbeddedAgentSessionMock(...args),
|
|
runEmbeddedAgent: (...args: unknown[]) => runEmbeddedAgentMock(...args),
|
|
resolveActiveEmbeddedRunSessionId: vi.fn().mockReturnValue(undefined),
|
|
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
|
isEmbeddedAgentRunActive: vi.fn().mockReturnValue(false),
|
|
isEmbeddedAgentRunStreaming: vi.fn().mockReturnValue(false),
|
|
waitForEmbeddedAgentRunEnd: vi.fn().mockResolvedValue(true),
|
|
}));
|
|
|
|
vi.mock("../agents/prepared-model-catalog.js", () => ({
|
|
loadProviderScopedThinkingCatalog: vi.fn(async () => []),
|
|
loadPreparedModelCatalog: loadModelCatalogMock,
|
|
}));
|
|
|
|
vi.mock("../agents/thinking-runtime.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../agents/thinking-runtime.js")>();
|
|
return {
|
|
...actual,
|
|
// These tests cover directive acknowledgements and persistence, not harness selection.
|
|
// Keep each directive from loading unrelated provider-route metadata through auto selection.
|
|
resolveEffectiveAgentRuntime: () => "openclaw",
|
|
};
|
|
});
|
|
|
|
vi.mock("../cli/command-secret-gateway.js", () => ({
|
|
resolveCommandSecretRefsViaGateway: (...args: unknown[]) =>
|
|
resolveCommandSecretRefsViaGatewayMock(...args),
|
|
}));
|
|
|
|
vi.mock("../agents/auth-profiles/session-override.js", () => ({
|
|
clearSessionAuthProfileOverride: (...args: unknown[]) =>
|
|
clearSessionAuthProfileOverrideMock(...args),
|
|
resolveSessionAuthProfileOverride: (...args: unknown[]) =>
|
|
resolveSessionAuthProfileOverrideMock(...args),
|
|
}));
|
|
|
|
vi.mock("../plugins/hook-runner-global.js", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("../plugins/hook-runner-global.js")>();
|
|
return {
|
|
...actual,
|
|
getGlobalHookRunner: () => undefined,
|
|
initializeGlobalHookRunner: vi.fn(),
|
|
resetGlobalHookRunner: vi.fn(),
|
|
};
|
|
});
|
|
|
|
vi.mock("./reply/agent-runner.runtime.js", () => ({
|
|
runReplyAgent: (...args: unknown[]) => runReplyAgentMock(...args),
|
|
}));
|
|
|
|
vi.mock("./reply/get-reply-run.js", () => ({
|
|
runPreparedReply: (...args: unknown[]) => runPreparedReplyMock(...args),
|
|
}));
|