mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
[AI-assisted] fix(agents): initialize context engines before subagent spawn prep (#73904)
Merged via squash.
Prepared head SHA: a9f32b858a
Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/setup: print the generated app manifest as plain JSON instead of embedding it inside the framed setup note, so it can be copied into Slack without deleting border characters. Fixes #65751. Thanks @theDanielJLewis.
|
||||
- Channels/WhatsApp: route CLI logout through the live Gateway and stop runtime-backed listeners before channel removal, so removing a WhatsApp account does not leave the old socket replying until restart. Fixes #67746. Thanks @123Mismail.
|
||||
- Voice Call/Twilio: honor TTS directive text and provider voice/model overrides during telephony synthesis, so `[[tts:...]]` tags are not spoken literally and voiceId overrides reach OpenAI/ElevenLabs calls. Fixes #58114. Thanks @legonhilltech-jpg.
|
||||
- Agents/subagents: initialize built-in context engines before native `sessions_spawn` resolves spawn preparation, so cliBackend-only cold starts no longer fail with an unregistered `legacy` context engine. Fixes #73095. (#73904) Thanks @brokemac79.
|
||||
- Agents/Codex: stop prompting message-tool-only source turns to finish with `NO_REPLY`, so quiet turns are represented by not calling the visible message tool instead of conflicting final-text instructions. Thanks @pashpashpash.
|
||||
- Gateway/config: report failed backup restores as failed in logs and config observe audit records instead of marking them valid. (#70515) Thanks @davidangularme.
|
||||
- Compaction: use the active session model fallback chain for implicit summarization failures without persisting fallback model selection, so Azure content-filter 400s can recover. Fixes #64960. (#74470) Thanks @jalehman and @OpenCodeEngineer.
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("sessions_spawn context modes", () => {
|
||||
const callGatewayMock = vi.fn();
|
||||
const updateSessionStoreMock = vi.fn();
|
||||
const forkSessionFromParentMock = vi.fn();
|
||||
const ensureContextEnginesInitializedMock = vi.fn();
|
||||
const resolveContextEngineMock = vi.fn();
|
||||
let spawnSubagentDirect: Awaited<
|
||||
ReturnType<typeof loadSubagentSpawnModuleForTest>
|
||||
@@ -23,6 +24,7 @@ describe("sessions_spawn context modes", () => {
|
||||
callGatewayMock,
|
||||
updateSessionStoreMock,
|
||||
forkSessionFromParentMock,
|
||||
ensureContextEnginesInitializedMock,
|
||||
resolveContextEngineMock,
|
||||
sessionStorePath: storePath,
|
||||
}));
|
||||
@@ -32,6 +34,7 @@ describe("sessions_spawn context modes", () => {
|
||||
callGatewayMock.mockReset();
|
||||
updateSessionStoreMock.mockReset();
|
||||
forkSessionFromParentMock.mockReset();
|
||||
ensureContextEnginesInitializedMock.mockReset();
|
||||
resolveContextEngineMock.mockReset();
|
||||
setupAcceptedSubagentGatewayMock(callGatewayMock);
|
||||
resolveContextEngineMock.mockResolvedValue({});
|
||||
@@ -112,6 +115,29 @@ describe("sessions_spawn context modes", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes built-in context engines before resolving spawn preparation", async () => {
|
||||
let initialized = false;
|
||||
ensureContextEnginesInitializedMock.mockImplementation(() => {
|
||||
initialized = true;
|
||||
});
|
||||
const prepareSubagentSpawn = vi.fn(async () => undefined);
|
||||
resolveContextEngineMock.mockImplementation(async () => {
|
||||
if (!initialized) {
|
||||
throw new Error('Context engine "legacy" is not registered. Available engines: (none)');
|
||||
}
|
||||
return { prepareSubagentSpawn };
|
||||
});
|
||||
|
||||
const result = await spawnSubagentDirect({ task: "clean worker" }, { agentSessionKey: "main" });
|
||||
|
||||
expect(result.status).toBe("accepted");
|
||||
expect(ensureContextEnginesInitializedMock).toHaveBeenCalledTimes(1);
|
||||
expect(resolveContextEngineMock).toHaveBeenCalledTimes(1);
|
||||
expect(ensureContextEnginesInitializedMock.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
resolveContextEngineMock.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
|
||||
it("rolls back context-engine preparation when agent start fails", async () => {
|
||||
const store: SessionStore = {
|
||||
main: { sessionId: "parent-session-id", updatedAt: 1 },
|
||||
|
||||
@@ -8,6 +8,7 @@ export {
|
||||
forkSessionFromParent,
|
||||
resolveParentForkMaxTokens,
|
||||
} from "../auto-reply/reply/session-fork.js";
|
||||
export { ensureContextEnginesInitialized } from "../context-engine/init.js";
|
||||
export { resolveContextEngine } from "../context-engine/registry.js";
|
||||
export { callGateway } from "../gateway/call.js";
|
||||
export { ADMIN_SCOPE, isAdminOnlyMethod } from "../gateway/method-scopes.js";
|
||||
|
||||
@@ -117,6 +117,7 @@ export function expectPersistedRuntimeModel(params: {
|
||||
export async function loadSubagentSpawnModuleForTest(params: {
|
||||
callGatewayMock: MockFn;
|
||||
getRuntimeConfig?: () => Record<string, unknown>;
|
||||
ensureContextEnginesInitializedMock?: MockFn;
|
||||
updateSessionStoreMock?: MockFn;
|
||||
forkSessionFromParentMock?: MockFn;
|
||||
resolveContextEngineMock?: MockFn;
|
||||
@@ -178,6 +179,8 @@ export async function loadSubagentSpawnModuleForTest(params: {
|
||||
getRuntimeConfig: () =>
|
||||
params.getRuntimeConfig?.() ??
|
||||
createSubagentSpawnTestConfig(params.workspaceDir ?? os.tmpdir()),
|
||||
ensureContextEnginesInitialized:
|
||||
params.ensureContextEnginesInitializedMock ?? (() => undefined),
|
||||
resolveContextEngine: params.resolveContextEngineMock ?? (async () => ({})),
|
||||
resolveParentForkMaxTokens: params.resolveParentForkMaxTokensMock ?? (() => 100_000),
|
||||
mergeSessionEntry: (
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
mergeDeliveryContext,
|
||||
normalizeDeliveryContext,
|
||||
pruneLegacyStoreKeys,
|
||||
ensureContextEnginesInitialized,
|
||||
resolveAgentConfig,
|
||||
resolveContextEngine,
|
||||
resolveDisplaySessionKey,
|
||||
@@ -92,6 +93,7 @@ type SubagentSpawnDeps = {
|
||||
forkSessionFromParent: typeof forkSessionFromParent;
|
||||
getGlobalHookRunner: () => SubagentLifecycleHookRunner | null;
|
||||
getRuntimeConfig: typeof getRuntimeConfig;
|
||||
ensureContextEnginesInitialized: typeof ensureContextEnginesInitialized;
|
||||
resolveContextEngine: typeof resolveContextEngine;
|
||||
resolveParentForkMaxTokens: typeof resolveParentForkMaxTokens;
|
||||
updateSessionStore: typeof updateSessionStore;
|
||||
@@ -102,6 +104,7 @@ const defaultSubagentSpawnDeps: SubagentSpawnDeps = {
|
||||
forkSessionFromParent,
|
||||
getGlobalHookRunner,
|
||||
getRuntimeConfig,
|
||||
ensureContextEnginesInitialized,
|
||||
resolveContextEngine,
|
||||
resolveParentForkMaxTokens,
|
||||
updateSessionStore,
|
||||
@@ -417,6 +420,7 @@ async function prepareContextEngineSubagentSpawn(params: {
|
||||
{ status: "ok"; preparation?: SubagentSpawnPreparation } | { status: "error"; error: string }
|
||||
> {
|
||||
try {
|
||||
subagentSpawnDeps.ensureContextEnginesInitialized();
|
||||
const engine = await subagentSpawnDeps.resolveContextEngine(params.cfg);
|
||||
const preparation = await engine.prepareSubagentSpawn?.({
|
||||
parentSessionKey: params.requesterInternalKey,
|
||||
|
||||
Reference in New Issue
Block a user