mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:10:51 +00:00
fix: validate resolved context engine contracts
This commit is contained in:
committed by
Josh Lehman
parent
785b9b1bc0
commit
f1737993b7
@@ -709,6 +709,71 @@ describe("Invalid engine fallback", () => {
|
||||
expect(message).toContain("legacy");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects resolved engines that omit info metadata", async () => {
|
||||
const engineId = `invalid-info-${Date.now().toString(36)}`;
|
||||
registerContextEngine(
|
||||
engineId,
|
||||
() =>
|
||||
({
|
||||
async ingest() {
|
||||
return { ingested: false };
|
||||
},
|
||||
async assemble({ messages }: { messages: AgentMessage[] }) {
|
||||
return { messages, estimatedTokens: 0 };
|
||||
},
|
||||
async compact() {
|
||||
return { ok: true, compacted: false };
|
||||
},
|
||||
}) as unknown as ContextEngine,
|
||||
);
|
||||
|
||||
await expect(resolveContextEngine(configWithSlot(engineId))).rejects.toThrow(
|
||||
`Context engine "${engineId}" factory returned an invalid ContextEngine: missing info.`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects resolved engines that omit required info fields", async () => {
|
||||
const engineId = `invalid-info-fields-${Date.now().toString(36)}`;
|
||||
registerContextEngine(
|
||||
engineId,
|
||||
() =>
|
||||
({
|
||||
info: { id: engineId },
|
||||
async ingest() {
|
||||
return { ingested: false };
|
||||
},
|
||||
async assemble({ messages }: { messages: AgentMessage[] }) {
|
||||
return { messages, estimatedTokens: 0 };
|
||||
},
|
||||
async compact() {
|
||||
return { ok: true, compacted: false };
|
||||
},
|
||||
}) as unknown as ContextEngine,
|
||||
);
|
||||
|
||||
await expect(resolveContextEngine(configWithSlot(engineId))).rejects.toThrow(
|
||||
`Context engine "${engineId}" factory returned an invalid ContextEngine: missing info.name.`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects resolved engines that omit required lifecycle methods", async () => {
|
||||
const engineId = `invalid-methods-${Date.now().toString(36)}`;
|
||||
registerContextEngine(
|
||||
engineId,
|
||||
() =>
|
||||
({
|
||||
info: { id: engineId, name: "Broken Engine" },
|
||||
async ingest() {
|
||||
return { ingested: false };
|
||||
},
|
||||
}) as unknown as ContextEngine,
|
||||
);
|
||||
|
||||
await expect(resolveContextEngine(configWithSlot(engineId))).rejects.toThrow(
|
||||
`Context engine "${engineId}" factory returned an invalid ContextEngine: missing assemble(), missing compact().`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -395,6 +395,46 @@ export function listContextEngineIds(): string[] {
|
||||
return [...getContextEngineRegistryState().engines.keys()];
|
||||
}
|
||||
|
||||
function describeResolvedContextEngineContractError(
|
||||
engineId: string,
|
||||
engine: unknown,
|
||||
): string | null {
|
||||
if (!engine || typeof engine !== "object") {
|
||||
return `Context engine "${engineId}" factory returned ${JSON.stringify(engine)} instead of a ContextEngine object.`;
|
||||
}
|
||||
|
||||
const candidate = engine as Record<string, unknown>;
|
||||
const issues: string[] = [];
|
||||
const info = candidate.info;
|
||||
if (!info || typeof info !== "object") {
|
||||
issues.push("missing info");
|
||||
} else {
|
||||
const infoRecord = info as Record<string, unknown>;
|
||||
if (typeof infoRecord.id !== "string" || !infoRecord.id.trim()) {
|
||||
issues.push("missing info.id");
|
||||
}
|
||||
if (typeof infoRecord.name !== "string" || !infoRecord.name.trim()) {
|
||||
issues.push("missing info.name");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof candidate.ingest !== "function") {
|
||||
issues.push("missing ingest()");
|
||||
}
|
||||
if (typeof candidate.assemble !== "function") {
|
||||
issues.push("missing assemble()");
|
||||
}
|
||||
if (typeof candidate.compact !== "function") {
|
||||
issues.push("missing compact()");
|
||||
}
|
||||
|
||||
if (issues.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `Context engine "${engineId}" factory returned an invalid ContextEngine: ${issues.join(", ")}.`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resolution
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -423,5 +463,11 @@ export async function resolveContextEngine(config?: OpenClawConfig): Promise<Con
|
||||
);
|
||||
}
|
||||
|
||||
return wrapContextEngineWithSessionKeyCompat(await entry.factory());
|
||||
const engine = await entry.factory();
|
||||
const contractError = describeResolvedContextEngineContractError(engineId, engine);
|
||||
if (contractError) {
|
||||
throw new Error(contractError);
|
||||
}
|
||||
|
||||
return wrapContextEngineWithSessionKeyCompat(engine);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user