From 9cb0fa58c20c71e22a58f4b499a982fa169f6385 Mon Sep 17 00:00:00 2001 From: Josh Lehman Date: Thu, 12 Mar 2026 10:55:29 -0700 Subject: [PATCH] fix: restore protocol outputs and stabilize Windows path CI (#44266) * fix(ci): restore protocol outputs and stabilize Windows path test Regenerate the Swift protocol models so protocol:check stops failing on main. Align the session target test helper with the sync production realpath behavior so Windows does not compare runneradmin and RUNNER~1 spellings for the same file. Regeneration-Prompt: | Investigate the failing checks from merged PR #34485 and confirm whether they still affect current main before changing code. Keep the fix tight: do not alter runtime behavior beyond what is required to clear the reproduced CI regressions. Commit the generated Swift protocol outputs for the PushTestResult transport field because protocol:check was failing from stale generated files on main. Also fix the Windows-only session target test by making its helper use the same synchronous realpath behavior as production discovery, so path spelling differences like runneradmin versus RUNNER~1 do not cause a false assertion failure. * fix(ci): align session target realpath behavior on Windows Use native realpath for sync session target discovery so it matches the async path on Windows, and update the session target test helper to assert against the same canonical path form. Regeneration-Prompt: | After opening the follow-up PR for the CI regressions from merged PR #34485, inspect the new failing Windows shard instead of assuming the first fix covered every case. Keep scope limited to the session target path mismatch exposed by CI. Fix the inconsistency at the source by making sync session target discovery use the same native realpath canonicalization as the async discovery path on Windows, then update the test helper to match that shared behavior and verify the touched file with targeted tests and file-scoped lint/format checks. * test: make merge config fixtures satisfy provider type After rebasing the PR onto current origin/main, the merge helper test fixtures no longer satisfied ProviderConfig because the anthropic provider examples were missing required provider and model fields. Add a shared fully-typed model fixture and explicit anthropic baseUrl values so the test keeps full type coverage under tsgo. Regeneration-Prompt: | Rebase the PR branch for #44266 onto the current origin/main because the failing CI error only reproduced on the merge ref. Re-run the type-check path and inspect src/agents/models-config.merge.test.ts at the exact compiler lines instead of weakening types globally. Keep the fix test-only: make the anthropic ProviderConfig fixtures structurally valid by supplying the required baseUrl and full model definition fields, and keep the shared fixture typed so tsgo accepts it without unknown casts. * fix: align Windows session store test expectations --- CHANGELOG.md | 1 + .../OpenClawProtocol/GatewayModels.swift | 6 +++- .../OpenClawProtocol/GatewayModels.swift | 6 +++- src/agents/models-config.merge.test.ts | 34 ++++++++++--------- src/config/sessions/targets.test.ts | 4 ++- src/config/sessions/targets.ts | 6 ++-- src/gateway/session-utils.test.ts | 8 +++-- 7 files changed, 41 insertions(+), 24 deletions(-) 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 {