diff --git a/CHANGELOG.md b/CHANGELOG.md index a3b5426231c..27709c698eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Docs: https://docs.openclaw.ai - Security/exec approvals: fail closed for ambiguous inline loader and shell-payload script execution, bind the real script after POSIX shell value-taking flags, and unwrap `pnpm`/`npm exec`/`npx` script runners before approval binding. (`GHSA-57jw-9722-6rf2`)(`GHSA-jvqh-rfmh-jh27`)(`GHSA-x7pp-23xv-mmr4`)(`GHSA-jc5j-vg4r-j5jx`)(#44247) Thanks @tdjackey and @vincentkoc. - Doctor/gateway service audit: canonicalize service entrypoint paths before comparing them so symlink-vs-realpath installs no longer trigger false "entrypoint does not match the current install" repair prompts. (#43882) Thanks @ngutman. - Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub. +- Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman. ## 2026.3.11 diff --git a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift index b743060f6c0..3ffe84fabb6 100644 --- a/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/macos/Sources/OpenClawProtocol/GatewayModels.swift @@ -1106,6 +1106,7 @@ public struct PushTestResult: Codable, Sendable { public let tokensuffix: String public let topic: String public let environment: String + public let transport: String public init( ok: Bool, @@ -1114,7 +1115,8 @@ public struct PushTestResult: Codable, Sendable { reason: String?, tokensuffix: String, topic: String, - environment: String) + environment: String, + transport: String) { self.ok = ok self.status = status @@ -1123,6 +1125,7 @@ public struct PushTestResult: Codable, Sendable { self.tokensuffix = tokensuffix self.topic = topic self.environment = environment + self.transport = transport } private enum CodingKeys: String, CodingKey { @@ -1133,6 +1136,7 @@ public struct PushTestResult: Codable, Sendable { case tokensuffix = "tokenSuffix" case topic case environment + case transport } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index b743060f6c0..3ffe84fabb6 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -1106,6 +1106,7 @@ public struct PushTestResult: Codable, Sendable { public let tokensuffix: String public let topic: String public let environment: String + public let transport: String public init( ok: Bool, @@ -1114,7 +1115,8 @@ public struct PushTestResult: Codable, Sendable { reason: String?, tokensuffix: String, topic: String, - environment: String) + environment: String, + transport: String) { self.ok = ok self.status = status @@ -1123,6 +1125,7 @@ public struct PushTestResult: Codable, Sendable { self.tokensuffix = tokensuffix self.topic = topic self.environment = environment + self.transport = transport } private enum CodingKeys: String, CodingKey { @@ -1133,6 +1136,7 @@ public struct PushTestResult: Codable, Sendable { case tokensuffix = "tokenSuffix" case topic case environment + case transport } } diff --git a/src/agents/models-config.merge.test.ts b/src/agents/models-config.merge.test.ts index 42aa6216aa4..9d7786af524 100644 --- a/src/agents/models-config.merge.test.ts +++ b/src/agents/models-config.merge.test.ts @@ -9,6 +9,20 @@ import type { ProviderConfig } from "./models-config.providers.js"; describe("models-config merge helpers", () => { const preservedApiKey = "AGENT_KEY"; // pragma: allowlist secret + const kimiModel: ProviderConfig["models"][number] = { + id: "k2p5", + name: "Kimi for Coding", + input: ["text", "image"], + reasoning: true, + cost: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + }, + contextWindow: 128_000, + maxTokens: 8_000, + }; it("refreshes implicit model metadata while preserving explicit reasoning overrides", () => { const merged = mergeProviderModels( @@ -70,27 +84,15 @@ describe("models-config merge helpers", () => { const merged = mergeProviderModels( { api: "anthropic-messages", + baseUrl: "https://api.anthropic.com", headers: { "User-Agent": "claude-code/0.1.0" }, - models: [ - { - id: "k2p5", - name: "Kimi for Coding", - input: ["text", "image"], - reasoning: true, - }, - ], + models: [kimiModel], } as ProviderConfig, { api: "anthropic-messages", + baseUrl: "https://api.anthropic.com", headers: { "X-Kimi-Tenant": "tenant-a" }, - models: [ - { - id: "k2p5", - name: "Kimi for Coding", - input: ["text", "image"], - reasoning: true, - }, - ], + models: [kimiModel], } as ProviderConfig, ); diff --git a/src/config/sessions/targets.test.ts b/src/config/sessions/targets.test.ts index aee55706572..8d924c8feae 100644 --- a/src/config/sessions/targets.test.ts +++ b/src/config/sessions/targets.test.ts @@ -1,3 +1,4 @@ +import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; @@ -10,7 +11,8 @@ import { } from "./targets.js"; async function resolveRealStorePath(sessionsDir: string): Promise { - return await fs.realpath(path.join(sessionsDir, "sessions.json")); + // Match the native realpath behavior used by both discovery paths. + return fsSync.realpathSync.native(path.join(sessionsDir, "sessions.json")); } describe("resolveSessionStoreTargets", () => { diff --git a/src/config/sessions/targets.ts b/src/config/sessions/targets.ts index 0a676f98ddf..c647a17e41f 100644 --- a/src/config/sessions/targets.ts +++ b/src/config/sessions/targets.ts @@ -68,8 +68,8 @@ function resolveValidatedDiscoveredStorePathSync(params: { if (stat.isSymbolicLink() || !stat.isFile()) { return undefined; } - const realStorePath = fsSync.realpathSync(storePath); - const realAgentsRoot = params.realAgentsRoot ?? fsSync.realpathSync(params.agentsRoot); + const realStorePath = fsSync.realpathSync.native(storePath); + const realAgentsRoot = params.realAgentsRoot ?? fsSync.realpathSync.native(params.agentsRoot); return isWithinRoot(realStorePath, realAgentsRoot) ? realStorePath : undefined; } catch (err) { if (shouldSkipDiscoveryError(err)) { @@ -153,7 +153,7 @@ export function resolveAllAgentSessionStoreTargetsSync( return cached; } try { - const realAgentsRoot = fsSync.realpathSync(agentsRoot); + const realAgentsRoot = fsSync.realpathSync.native(agentsRoot); realAgentsRoots.set(agentsRoot, realAgentsRoot); return realAgentsRoot; } catch (err) { diff --git a/src/gateway/session-utils.test.ts b/src/gateway/session-utils.test.ts index af90c96d1b9..3c69ce1bcd7 100644 --- a/src/gateway/session-utils.test.ts +++ b/src/gateway/session-utils.test.ts @@ -22,6 +22,10 @@ import { resolveSessionStoreKey, } from "./session-utils.js"; +function resolveSyncRealpath(filePath: string): string { + return fs.realpathSync.native(filePath); +} + function createSymlinkOrSkip(targetPath: string, linkPath: string): boolean { try { fs.symlinkSync(targetPath, linkPath); @@ -287,7 +291,7 @@ describe("gateway session utils", () => { const target = resolveGatewaySessionStoreTarget({ cfg, key: "agent:retired-agent:main" }); - expect(target.storePath).toBe(fs.realpathSync(retiredStorePath)); + expect(target.storePath).toBe(resolveSyncRealpath(retiredStorePath)); }); }); @@ -316,7 +320,7 @@ describe("gateway session utils", () => { const loaded = loadSessionEntry("agent:retired-agent:main"); - expect(loaded.storePath).toBe(fs.realpathSync(retiredStorePath)); + expect(loaded.storePath).toBe(resolveSyncRealpath(retiredStorePath)); expect(loaded.entry?.sessionId).toBe("sess-retired"); }); } finally {