diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts new file mode 100644 index 00000000000..19ad241f7e2 --- /dev/null +++ b/src/agents/command/session-store.test.ts @@ -0,0 +1,73 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; +import { loadSessionStore, type SessionEntry } from "../../config/sessions.js"; +import type { EmbeddedPiRunResult } from "../pi-embedded.js"; +import { updateSessionStoreAfterAgentRun } from "./session-store.js"; + +describe("updateSessionStoreAfterAgentRun", () => { + let tmpDir: string; + let storePath: string; + + beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-session-store-")); + storePath = path.join(tmpDir, "sessions.json"); + }); + + afterEach(async () => { + await fs.rm(tmpDir, { recursive: true, force: true }); + }); + + it("persists claude-cli session bindings without explicit cliBackends config", async () => { + const cfg = {} as OpenClawConfig; + const sessionKey = "agent:main:explicit:test-claude-cli"; + const sessionId = "test-openclaw-session"; + const sessionStore: Record = { + [sessionKey]: { + sessionId, + updatedAt: 1, + }, + }; + await fs.writeFile(storePath, JSON.stringify(sessionStore, null, 2)); + + const result: EmbeddedPiRunResult = { + meta: { + durationMs: 1, + agentMeta: { + sessionId: "cli-session-123", + provider: "claude-cli", + model: "claude-sonnet-4-6", + cliSessionBinding: { + sessionId: "cli-session-123", + }, + }, + }, + }; + + await updateSessionStoreAfterAgentRun({ + cfg, + sessionId, + sessionKey, + storePath, + sessionStore, + defaultProvider: "claude-cli", + defaultModel: "claude-sonnet-4-6", + result, + }); + + expect(sessionStore[sessionKey]?.cliSessionBindings?.["claude-cli"]).toEqual({ + sessionId: "cli-session-123", + }); + expect(sessionStore[sessionKey]?.cliSessionIds?.["claude-cli"]).toBe("cli-session-123"); + expect(sessionStore[sessionKey]?.claudeCliSessionId).toBe("cli-session-123"); + + const persisted = loadSessionStore(storePath); + expect(persisted[sessionKey]?.cliSessionBindings?.["claude-cli"]).toEqual({ + sessionId: "cli-session-123", + }); + expect(persisted[sessionKey]?.cliSessionIds?.["claude-cli"]).toBe("cli-session-123"); + expect(persisted[sessionKey]?.claudeCliSessionId).toBe("cli-session-123"); + }); +}); diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index df3813d3390..fd59b78710b 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -4,6 +4,7 @@ import { resetLogger, setLoggerOverride } from "../logging/logger.js"; import { buildAllowedModelSet, inferUniqueProviderFromConfiguredModels, + isCliProvider, parseModelRef, buildModelAliasIndex, normalizeModelSelection, @@ -130,6 +131,12 @@ describe("model-selection", () => { }); }); + describe("isCliProvider", () => { + it("treats claude-cli as a CLI provider even without explicit cliBackends config", () => { + expect(isCliProvider("claude-cli", {} as OpenClawConfig)).toBe(true); + }); + }); + describe("modelKey", () => { it("keeps canonical OpenRouter native ids without duplicating the provider", () => { expect(modelKey("openrouter", "openrouter/hunter-alpha")).toBe("openrouter/hunter-alpha"); diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index dd42d24847a..313c2251aa5 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -1,3 +1,4 @@ +import { CLAUDE_CLI_BACKEND_ID } from "../../extensions/anthropic/cli-backend-api.js"; import { resolveThinkingDefaultForModel } from "../auto-reply/thinking.shared.js"; import type { OpenClawConfig } from "../config/config.js"; import { @@ -6,6 +7,7 @@ import { toAgentModelListLike, } from "../config/model-input.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js"; import { sanitizeForLog, stripAnsi } from "../terminal/ansi.js"; import { resolveAgentConfig, @@ -27,29 +29,7 @@ import { normalizeProviderModelIdWithRuntime } from "./provider-model-normalizat let log: ReturnType | null = null; -type CliBackendRuntimeModule = typeof import("../plugins/cli-backends.runtime.js"); - -const CLI_BACKEND_RUNTIME_CANDIDATES = [ - "../plugins/cli-backends.runtime.js", - "../plugins/cli-backends.runtime.ts", -] as const; - -let cliBackendRuntimeModule: CliBackendRuntimeModule | undefined; - -function loadCliBackendRuntime(): CliBackendRuntimeModule | null { - if (cliBackendRuntimeModule) { - return cliBackendRuntimeModule; - } - for (const candidate of CLI_BACKEND_RUNTIME_CANDIDATES) { - try { - cliBackendRuntimeModule = require(candidate) as CliBackendRuntimeModule; - return cliBackendRuntimeModule; - } catch { - // Try source/runtime candidates in order. - } - } - return null; -} +const BUILTIN_CLI_PROVIDER_IDS = new Set([normalizeProviderId(CLAUDE_CLI_BACKEND_ID)]); function getLog(): ReturnType { log ??= createSubsystemLogger("model-selection"); @@ -112,7 +92,10 @@ export { export function isCliProvider(provider: string, cfg?: OpenClawConfig): boolean { const normalized = normalizeProviderId(provider); - const cliBackends = loadCliBackendRuntime()?.resolveRuntimeCliBackends() ?? []; + if (BUILTIN_CLI_PROVIDER_IDS.has(normalized)) { + return true; + } + const cliBackends = resolveRuntimeCliBackends(); if (cliBackends.some((backend) => normalizeProviderId(backend.id) === normalized)) { return true; }