diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index 3b730ca78ea..4218731e42e 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -1831,12 +1831,16 @@ describe("persistSessionUsageUpdate", () => { models: { providers: { openai: { + baseUrl: "https://api.openai.com/v1", models: [ { id: "gpt-5.4", - label: "GPT 5.4", - baseUrl: "https://api.openai.com/v1", + name: "GPT 5.4", + reasoning: true, + input: ["text"], cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0.5 }, + contextWindow: 200_000, + maxTokens: 8_192, }, ], }, @@ -1873,12 +1877,16 @@ describe("persistSessionUsageUpdate", () => { models: { providers: { "openai-codex": { + baseUrl: "https://api.openai.com/v1", models: [ { id: "gpt-5.3-codex-spark", - label: "GPT 5.3 Codex Spark", - baseUrl: "https://api.openai.com/v1", + name: "GPT 5.3 Codex Spark", + reasoning: true, + input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 200_000, + maxTokens: 8_192, }, ], }, diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 98554b98a65..3933c9ff7c6 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -775,7 +775,7 @@ export async function runCronIsolatedAgentTurn(params: { cost: resolveModelCostConfig({ provider: providerUsed, model: modelUsed, - config: cfg, + config: cfgWithAgentDefaults, }), }), ); diff --git a/src/gateway/server-methods/sessions.ts b/src/gateway/server-methods/sessions.ts index 59bc2594612..d1c2efe155e 100644 --- a/src/gateway/server-methods/sessions.ts +++ b/src/gateway/server-methods/sessions.ts @@ -403,6 +403,11 @@ async function handleSessionSend(params: { let sendPayload: unknown; let sendCached = false; let startedRunId: string | undefined; + const rawIdempotencyKey = (p as { idempotencyKey?: string }).idempotencyKey; + const idempotencyKey = + typeof rawIdempotencyKey === "string" && rawIdempotencyKey.trim() + ? rawIdempotencyKey.trim() + : randomUUID(); await chatHandlers["chat.send"]({ req: params.req, params: { @@ -411,11 +416,7 @@ async function handleSessionSend(params: { thinking: (p as { thinking?: string }).thinking, attachments: (p as { attachments?: unknown[] }).attachments, timeoutMs: (p as { timeoutMs?: number }).timeoutMs, - idempotencyKey: - typeof (p as { idempotencyKey?: string }).idempotencyKey === "string" && - (p as { idempotencyKey?: string }).idempotencyKey?.trim() - ? (p as { idempotencyKey?: string }).idempotencyKey.trim() - : randomUUID(), + idempotencyKey, }, respond: (ok, payload, error, meta) => { sendAcked = ok; diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts index 271a6cbe375..cefb1883db0 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts @@ -525,6 +525,7 @@ describe("gateway server sessions", () => { const broadcastToConnIds = vi.fn(); const respond = vi.fn(); await sessionsHandlers["sessions.patch"]({ + req: {} as never, params: { key: "main", label: "Renamed", diff --git a/src/gateway/session-kill-http.test.ts b/src/gateway/session-kill-http.test.ts index f24891eae73..b313b289383 100644 --- a/src/gateway/session-kill-http.test.ts +++ b/src/gateway/session-kill-http.test.ts @@ -5,7 +5,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vites const TEST_GATEWAY_TOKEN = "test-gateway-token-1234567890"; let cfg: Record = {}; -const authMock = vi.fn(async () => ({ ok: true })); +const authMock = vi.fn(async () => ({ ok: true }) as { ok: boolean; rateLimited?: boolean }); const isLocalDirectRequestMock = vi.fn(() => true); const loadSessionEntryMock = vi.fn(); const getSubagentRunByChildSessionKeyMock = vi.fn(); @@ -18,23 +18,22 @@ vi.mock("../config/config.js", () => ({ })); vi.mock("./auth.js", () => ({ - authorizeHttpGatewayConnect: (...args: unknown[]) => authMock(...args), - isLocalDirectRequest: (...args: unknown[]) => isLocalDirectRequestMock(...args), + authorizeHttpGatewayConnect: authMock, + isLocalDirectRequest: isLocalDirectRequestMock, })); vi.mock("./session-utils.js", () => ({ - loadSessionEntry: (...args: unknown[]) => loadSessionEntryMock(...args), + loadSessionEntry: loadSessionEntryMock, })); vi.mock("../agents/subagent-registry.js", () => ({ - getSubagentRunByChildSessionKey: (...args: unknown[]) => - getSubagentRunByChildSessionKeyMock(...args), + getSubagentRunByChildSessionKey: getSubagentRunByChildSessionKeyMock, })); vi.mock("../agents/subagent-control.js", () => ({ - killControlledSubagentRun: (...args: unknown[]) => killControlledSubagentRunMock(...args), - killSubagentRunAdmin: (...args: unknown[]) => killSubagentRunAdminMock(...args), - resolveSubagentController: (...args: unknown[]) => resolveSubagentControllerMock(...args), + killControlledSubagentRun: killControlledSubagentRunMock, + killSubagentRunAdmin: killSubagentRunAdminMock, + resolveSubagentController: resolveSubagentControllerMock, })); const { handleSessionKillHttpRequest } = await import("./session-kill-http.js"); diff --git a/src/gateway/session-message-events.test.ts b/src/gateway/session-message-events.test.ts index 2e1ddfdf7ec..293ebed9be3 100644 --- a/src/gateway/session-message-events.test.ts +++ b/src/gateway/session-message-events.test.ts @@ -152,6 +152,9 @@ describe("session.message websocket events", () => { const [appended, event] = await Promise.all([appendPromise, eventPromise]); expect(appended.ok).toBe(true); + if (!appended.ok) { + throw new Error(`append failed: ${appended.reason}`); + } expect( (event.payload as { message?: { content?: Array<{ text?: string }> } }).message ?.content?.[0]?.text, diff --git a/src/gateway/session-transcript-key.test.ts b/src/gateway/session-transcript-key.test.ts index 40ad2ccc650..f9105f321e1 100644 --- a/src/gateway/session-transcript-key.test.ts +++ b/src/gateway/session-transcript-key.test.ts @@ -29,6 +29,8 @@ import { } from "./session-transcript-key.js"; describe("resolveSessionKeyForTranscriptFile", () => { + const now = 1_700_000_000_000; + beforeEach(() => { clearSessionTranscriptKeyCacheForTests(); loadConfigMock.mockClear(); @@ -45,8 +47,8 @@ describe("resolveSessionKeyForTranscriptFile", () => { it("reuses the cached session key for repeat transcript lookups", () => { const store = { - "agent:main:one": { sessionId: "sess-1" }, - "agent:main:two": { sessionId: "sess-2" }, + "agent:main:one": { sessionId: "sess-1", updatedAt: now }, + "agent:main:two": { sessionId: "sess-2", updatedAt: now }, } satisfies Record; loadCombinedSessionStoreForGatewayMock.mockReturnValue({ storePath: "(multiple)", @@ -71,8 +73,8 @@ describe("resolveSessionKeyForTranscriptFile", () => { it("drops stale cached mappings and falls back to the current store contents", () => { let store: Record = { - "agent:main:alpha": { sessionId: "sess-alpha" }, - "agent:main:beta": { sessionId: "sess-beta" }, + "agent:main:alpha": { sessionId: "sess-alpha", updatedAt: now }, + "agent:main:beta": { sessionId: "sess-beta", updatedAt: now }, }; loadCombinedSessionStoreForGatewayMock.mockImplementation(() => ({ storePath: "(multiple)", @@ -96,8 +98,12 @@ describe("resolveSessionKeyForTranscriptFile", () => { expect(resolveSessionKeyForTranscriptFile("/tmp/shared.jsonl")).toBe("agent:main:beta"); store = { - "agent:main:alpha": { sessionId: "sess-alpha-2" }, - "agent:main:beta": { sessionId: "sess-beta", sessionFile: "/tmp/beta.jsonl" }, + "agent:main:alpha": { sessionId: "sess-alpha-2", updatedAt: now + 1 }, + "agent:main:beta": { + sessionId: "sess-beta", + updatedAt: now + 1, + sessionFile: "/tmp/beta.jsonl", + }, }; expect(resolveSessionKeyForTranscriptFile("/tmp/shared.jsonl")).toBe("agent:main:alpha"); diff --git a/src/gateway/sessions-history-http.test.ts b/src/gateway/sessions-history-http.test.ts index be001efb95e..a43f3953367 100644 --- a/src/gateway/sessions-history-http.test.ts +++ b/src/gateway/sessions-history-http.test.ts @@ -309,6 +309,9 @@ describe("session history HTTP endpoints", () => { ?.content?.[0]?.text, ).toBe("second message"); expect((messageEvent.data as { messageSeq?: number }).messageSeq).toBe(2); + if (!appended.ok) { + throw new Error(`append failed: ${appended.reason}`); + } expect( ( messageEvent.data as { diff --git a/ui/src/ui/app-gateway.sessions.node.test.ts b/ui/src/ui/app-gateway.sessions.node.test.ts index 707091e58b6..241caa203d5 100644 --- a/ui/src/ui/app-gateway.sessions.node.test.ts +++ b/ui/src/ui/app-gateway.sessions.node.test.ts @@ -58,11 +58,15 @@ function createHost() { sessionKey: "main", lastActiveSessionKey: "main", theme: "system", + themeMode: "system", chatFocusMode: false, chatShowThinking: true, + chatShowToolCalls: true, splitRatio: 0.6, navCollapsed: false, + navWidth: 280, navGroupsCollapsed: {}, + borderRadius: 50, }, password: "", clientInstanceId: "instance-test", @@ -83,6 +87,9 @@ function createHost() { toolsCatalogLoading: false, toolsCatalogError: null, toolsCatalogResult: null, + healthLoading: false, + healthResult: null, + healthError: null, debugHealth: null, assistantName: "OpenClaw", assistantAvatar: null, @@ -94,7 +101,7 @@ function createHost() { execApprovalQueue: [], execApprovalError: null, updateAvailable: null, - } as Parameters[0]; + } as unknown as Parameters[0]; } describe("handleGatewayEvent sessions.changed", () => { @@ -103,6 +110,7 @@ describe("handleGatewayEvent sessions.changed", () => { const host = createHost(); handleGatewayEvent(host, { + type: "event", event: "sessions.changed", payload: { sessionKey: "agent:main:main", reason: "patch" }, seq: 1,