mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 14:52:52 +00:00
fix(agents): scope cli binding clears
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
export { runCliAgent } from "./cli-runner.js";
|
||||
export { getCliSessionId, setCliSessionId } from "./cli-session.js";
|
||||
export { clearCliSession, getCliSessionId, setCliSessionId } from "./cli-session.js";
|
||||
|
||||
@@ -95,10 +95,11 @@ export function keepCliSessionBindingOnlyWhenReused(params: {
|
||||
const existingSessionId = normalizeOptionalString(params.existingSessionId);
|
||||
const agentMeta = params.result.meta.agentMeta;
|
||||
const returnedSessionId = normalizeOptionalString(agentMeta?.cliSessionBinding?.sessionId);
|
||||
const shouldClearStoredSession = agentMeta?.clearCliSessionBinding === true;
|
||||
if (agentMeta === undefined || (existingSessionId && returnedSessionId === existingSessionId)) {
|
||||
return params.result;
|
||||
}
|
||||
if (returnedSessionId) {
|
||||
if (returnedSessionId || shouldClearStoredSession) {
|
||||
params.onDroppedReplacement?.();
|
||||
}
|
||||
return {
|
||||
@@ -109,6 +110,7 @@ export function keepCliSessionBindingOnlyWhenReused(params: {
|
||||
...agentMeta,
|
||||
sessionId: "",
|
||||
cliSessionBinding: undefined,
|
||||
clearCliSessionBinding: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1950,6 +1950,7 @@ describe("runAgentTurnWithFallback", () => {
|
||||
sessionId: "transient-cli-session",
|
||||
authProfileId: "profile",
|
||||
},
|
||||
clearCliSessionBinding: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1985,6 +1986,7 @@ describe("runAgentTurnWithFallback", () => {
|
||||
}
|
||||
expect(result.runResult.meta?.agentMeta?.sessionId).toBe("");
|
||||
expect(result.runResult.meta?.agentMeta?.cliSessionBinding).toBeUndefined();
|
||||
expect(result.runResult.meta?.agentMeta?.clearCliSessionBinding).toBeUndefined();
|
||||
expect(activeSessionStore.main.cliSessionBindings?.["codex-cli"]).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -2036,6 +2038,54 @@ describe("runAgentTurnWithFallback", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("clears room-event CLI bindings when an unflushed replacement is dropped", async () => {
|
||||
state.isCliProviderMock.mockReturnValue(true);
|
||||
state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({
|
||||
result: await params.run("codex-cli", "gpt-5.4"),
|
||||
provider: "codex-cli",
|
||||
model: "gpt-5.4",
|
||||
attempts: [],
|
||||
}));
|
||||
state.runCliAgentMock.mockResolvedValueOnce({
|
||||
payloads: [{ text: "handled" }],
|
||||
meta: {
|
||||
agentMeta: {
|
||||
sessionId: "",
|
||||
provider: "codex-cli",
|
||||
model: "gpt-5.4",
|
||||
clearCliSessionBinding: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
|
||||
const followupRun = createFollowupRun();
|
||||
followupRun.currentInboundEventKind = "room_event";
|
||||
followupRun.run.provider = "codex-cli";
|
||||
followupRun.run.model = "gpt-5.4";
|
||||
const sessionEntry = {
|
||||
cliSessionBindings: {
|
||||
"codex-cli": { sessionId: "existing-cli-session" },
|
||||
},
|
||||
} as unknown as SessionEntry;
|
||||
const activeSessionStore = { main: sessionEntry };
|
||||
|
||||
const result = await runAgentTurnWithFallback({
|
||||
...createMinimalRunAgentTurnParams({ followupRun }),
|
||||
activeSessionStore,
|
||||
getActiveSessionEntry: () => sessionEntry,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("success");
|
||||
if (result.kind !== "success") {
|
||||
throw new Error("expected success");
|
||||
}
|
||||
expect(result.runResult.meta?.agentMeta?.sessionId).toBe("");
|
||||
expect(result.runResult.meta?.agentMeta?.cliSessionBinding).toBeUndefined();
|
||||
expect(result.runResult.meta?.agentMeta?.clearCliSessionBinding).toBeUndefined();
|
||||
expect(activeSessionStore.main.cliSessionBindings?.["codex-cli"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("bridges CLI assistant agent events into onPartialReply for live preview (#76869)", async () => {
|
||||
state.isCliProviderMock.mockReturnValue(true);
|
||||
state.runWithModelFallbackMock.mockImplementationOnce(async (params: FallbackRunnerParams) => ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearCliSessionMock,
|
||||
clearFastTestEnv,
|
||||
getCliSessionIdMock,
|
||||
isCliProviderMock,
|
||||
@@ -284,6 +285,45 @@ describe("runCronIsolatedAgentTurn — cron model override forwarding (#58065)",
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("clears stale CLI bindings when cron CLI replacement is unflushed", async () => {
|
||||
isCliProviderMock.mockReturnValue(true);
|
||||
runWithModelFallbackMock.mockImplementation(async ({ provider, model, run }) => {
|
||||
const result = await run(provider, model);
|
||||
return { result, provider, model, attempts: [] };
|
||||
});
|
||||
const cronSession = makeCronSession({
|
||||
sessionEntry: makeCronSessionEntry({
|
||||
cliSessionBindings: {
|
||||
"claude-cli": { sessionId: "stale-cli-session" },
|
||||
"codex-cli": { sessionId: "codex-session" },
|
||||
},
|
||||
}),
|
||||
isNewSession: false,
|
||||
});
|
||||
resolveCronSessionMock.mockReturnValue(cronSession);
|
||||
runCliAgentMock.mockResolvedValueOnce({
|
||||
payloads: [{ text: "summary done" }],
|
||||
meta: {
|
||||
agentMeta: {
|
||||
provider: "claude-cli",
|
||||
model: "claude-opus-4-6",
|
||||
sessionId: "",
|
||||
clearCliSessionBinding: true,
|
||||
usage: { input: 10, output: 20 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runCronIsolatedAgentTurn(
|
||||
makeParams({
|
||||
job: makeJob({ sessionTarget: "session:existing-cron-session" }),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(clearCliSessionMock).toHaveBeenCalledWith(cronSession.sessionEntry, "claude-cli");
|
||||
});
|
||||
|
||||
it("validates cron thinking with catalog reasoning metadata", async () => {
|
||||
resolveAllowedModelRefMock.mockImplementation(() => ({
|
||||
ref: { provider: "ollama", model: "qwen3:0.6b" },
|
||||
|
||||
@@ -56,6 +56,7 @@ export const runEmbeddedAgentMock = createMock();
|
||||
export const runCliAgentMock = createMock();
|
||||
export const lookupContextTokensMock = createMock();
|
||||
export const getCliSessionIdMock = createMock();
|
||||
export const clearCliSessionMock = createMock();
|
||||
export const updateSessionStoreMock = createMock();
|
||||
export const resolveCronSessionMock = createMock();
|
||||
export const logWarnMock = createMock();
|
||||
@@ -283,6 +284,7 @@ vi.mock("./run-subagent-registry.runtime.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/cli-runner.runtime.js", () => ({
|
||||
clearCliSession: clearCliSessionMock,
|
||||
setCliSessionId: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -487,6 +489,7 @@ function resetRunExecutionMocks(): void {
|
||||
runEmbeddedAgentMock.mockReset();
|
||||
runEmbeddedAgentMock.mockResolvedValue(makeDefaultEmbeddedResult());
|
||||
runCliAgentMock.mockReset();
|
||||
clearCliSessionMock.mockReset();
|
||||
getCliSessionIdMock.mockReturnValue(undefined);
|
||||
countActiveDescendantRunsMock.mockReset();
|
||||
countActiveDescendantRunsMock.mockReturnValue(0);
|
||||
|
||||
@@ -944,7 +944,10 @@ async function finalizeCronRun(params: {
|
||||
prepared.cronSession.sessionEntry.contextTokens = contextTokens;
|
||||
if (isCliProvider(providerUsed, prepared.cfgWithAgentDefaults)) {
|
||||
const cliSessionId = finalRunResult.meta?.agentMeta?.sessionId?.trim();
|
||||
if (cliSessionId) {
|
||||
if (finalRunResult.meta?.agentMeta?.clearCliSessionBinding === true) {
|
||||
const { clearCliSession } = await loadCliRunnerRuntime();
|
||||
clearCliSession(prepared.cronSession.sessionEntry, providerUsed);
|
||||
} else if (cliSessionId) {
|
||||
const { setCliSessionId } = await loadCliRunnerRuntime();
|
||||
setCliSessionId(prepared.cronSession.sessionEntry, providerUsed, cliSessionId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user