[codex] Add remote provider compatibility map#2642
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
3dd0567 to
3fdd679
Compare
ApprovabilityVerdict: Needs human review This PR introduces a new provider compatibility checking feature with remote document fetching, UI banners, and targeted update capabilities. The scope includes new services, driver integrations, and significant UI changes that warrant human review. You can customize Macroscope's approvability policy. Learn more. |
3fdd679 to
1408240
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Compatibility message overrides more-severe probe error message
- Moved status computation before message computation and gated compatibilityMessage on
status !== snapshot.status, so the advisory message only overrides the snapshot message when the advisory actually changed the status, preserving more-severe probe error messages.
- Moved status computation before message computation and gated compatibilityMessage on
Or push these changes by commenting:
@cursor push 14123261fe
Preview (14123261fe)
diff --git a/apps/server/src/provider/providerCompatibility.ts b/apps/server/src/provider/providerCompatibility.ts
--- a/apps/server/src/provider/providerCompatibility.ts
+++ b/apps/server/src/provider/providerCompatibility.ts
@@ -293,10 +293,6 @@
return snapshotWithoutAdvisory as Snapshot;
}
- const compatibilityMessage =
- compatibilityAdvisory.severity !== "info"
- ? (compatibilityAdvisory.message ?? undefined)
- : undefined;
const status =
snapshot.enabled && compatibilityAdvisory.severity === "error"
? "error"
@@ -305,6 +301,10 @@
snapshot.status === "ready"
? "warning"
: snapshot.status;
+ const compatibilityMessage =
+ status !== snapshot.status && compatibilityAdvisory.severity !== "info"
+ ? (compatibilityAdvisory.message ?? undefined)
+ : undefined;
return {
...snapshot,You can send follow-ups to the cloud agent here.
1408240 to
99151ed
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Remote advisory cannot demote status set by bundled advisory
- Modified applyCompatibilityAdvisory to compute base status/message by reversing the previous advisory's promotion before applying the new one, so a less-severe remote advisory can correctly demote status elevated by a bundled advisory.
Or push these changes by commenting:
@cursor push a788468c7f
Preview (a788468c7f)
diff --git a/apps/server/src/provider/Drivers/ClaudeDriver.ts b/apps/server/src/provider/Drivers/ClaudeDriver.ts
--- a/apps/server/src/provider/Drivers/ClaudeDriver.ts
+++ b/apps/server/src/provider/Drivers/ClaudeDriver.ts
@@ -47,6 +47,7 @@
normalizeCommandPath,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";
+import { enrichProviderSnapshotWithCompatibilityAdvisory } from "../providerCompatibility.ts";
import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts";
const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings);
@@ -171,6 +172,7 @@
checkProvider,
enrichSnapshot: ({ snapshot, publishSnapshot }) =>
enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe(
+ Effect.flatMap(enrichProviderSnapshotWithCompatibilityAdvisory),
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
),
diff --git a/apps/server/src/provider/Drivers/CodexDriver.ts b/apps/server/src/provider/Drivers/CodexDriver.ts
--- a/apps/server/src/provider/Drivers/CodexDriver.ts
+++ b/apps/server/src/provider/Drivers/CodexDriver.ts
@@ -46,6 +46,7 @@
makePackageManagedProviderMaintenanceResolver,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";
+import { enrichProviderSnapshotWithCompatibilityAdvisory } from "../providerCompatibility.ts";
import {
codexContinuationIdentity,
materializeCodexShadowHome,
@@ -171,6 +172,7 @@
checkProvider,
enrichSnapshot: ({ snapshot, publishSnapshot }) =>
enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe(
+ Effect.flatMap(enrichProviderSnapshotWithCompatibilityAdvisory),
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
),
diff --git a/apps/server/src/provider/Drivers/OpenCodeDriver.ts b/apps/server/src/provider/Drivers/OpenCodeDriver.ts
--- a/apps/server/src/provider/Drivers/OpenCodeDriver.ts
+++ b/apps/server/src/provider/Drivers/OpenCodeDriver.ts
@@ -46,6 +46,7 @@
normalizeCommandPath,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";
+import { enrichProviderSnapshotWithCompatibilityAdvisory } from "../providerCompatibility.ts";
const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings);
const DRIVER_KIND = ProviderDriverKind.make("opencode");
@@ -150,6 +151,7 @@
checkProvider,
enrichSnapshot: ({ snapshot, publishSnapshot }) =>
enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe(
+ Effect.flatMap(enrichProviderSnapshotWithCompatibilityAdvisory),
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
),
diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts
--- a/apps/server/src/provider/Layers/CursorProvider.ts
+++ b/apps/server/src/provider/Layers/CursorProvider.ts
@@ -41,6 +41,7 @@
enrichProviderSnapshotWithVersionAdvisory,
type ProviderMaintenanceCapabilities,
} from "../providerMaintenance.ts";
+import { enrichProviderSnapshotWithCompatibilityAdvisory } from "../providerCompatibility.ts";
import { AcpSessionRuntime } from "../acp/AcpSessionRuntime.ts";
const PROVIDER = ProviderDriverKind.make("cursor");
@@ -1242,6 +1243,7 @@
snapshot,
input.maintenanceCapabilities,
).pipe(
+ Effect.flatMap(enrichProviderSnapshotWithCompatibilityAdvisory),
Effect.provideService(HttpClient.HttpClient, input.httpClient),
Effect.flatMap((enrichedSnapshot) =>
publishSnapshot(stampIdentity(enrichedSnapshot)).pipe(Effect.as(enrichedSnapshot)),
diff --git a/apps/server/src/provider/providerCompatibility.test.ts b/apps/server/src/provider/providerCompatibility.test.ts
new file mode 100644
--- /dev/null
+++ b/apps/server/src/provider/providerCompatibility.test.ts
@@ -1,0 +1,163 @@
+import { afterEach, describe, expect, it } from "vitest";
+import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
+import * as Effect from "effect/Effect";
+import { HttpClient, HttpClientResponse } from "effect/unstable/http";
+
+import {
+ applyBundledProviderCompatibilityAdvisory,
+ clearProviderCompatibilityCacheForTests,
+ createProviderCompatibilityAdvisory,
+ enrichProviderSnapshotWithCompatibilityAdvisory,
+ type ProviderCompatibilityDocument,
+} from "./providerCompatibility.ts";
+
+const codexDriver = ProviderDriverKind.make("codex");
+
+const baseProvider: ServerProvider = {
+ instanceId: ProviderInstanceId.make("codex"),
+ driver: codexDriver,
+ displayName: "Codex",
+ enabled: true,
+ installed: true,
+ version: "0.130.0",
+ status: "ready",
+ auth: { status: "authenticated" },
+ checkedAt: "2026-04-10T00:00:00.000Z",
+ models: [],
+ slashCommands: [],
+ skills: [],
+};
+
+function jsonHttpClient(payload: unknown, status = 200): HttpClient.HttpClient {
+ return HttpClient.make((request) =>
+ Effect.succeed(
+ HttpClientResponse.fromWeb(
+ request,
+ new Response(JSON.stringify(payload), {
+ status,
+ headers: { "content-type": "application/json" },
+ }),
+ ),
+ ),
+ );
+}
+
+afterEach(() => {
+ clearProviderCompatibilityCacheForTests();
+});
+
+describe("provider compatibility", () => {
+ it("selects policies by T3 Code version range", () => {
+ const document: ProviderCompatibilityDocument = {
+ version: 1,
+ policies: [
+ {
+ t3CodeRange: "<0.1.0",
+ driver: codexDriver,
+ recommendedRange: "<0.130.0",
+ recommendedVersion: "0.129.0",
+ ranges: [{ status: "broken", range: ">=0.130.0" }],
+ },
+ {
+ t3CodeRange: ">=0.1.0",
+ driver: codexDriver,
+ recommendedRange: ">=0.130.0",
+ recommendedVersion: "0.130.0",
+ ranges: [{ status: "supported", range: ">=0.130.0" }],
+ },
+ ],
+ };
+
+ expect(
+ createProviderCompatibilityAdvisory({
+ driver: codexDriver,
+ currentVersion: "0.130.0",
+ document,
+ t3CodeVersion: "0.0.22",
+ }),
+ ).toMatchObject({
+ status: "broken",
+ recommendedVersion: "0.129.0",
+ });
+ });
+
+ it("enriches snapshots from the remote compatibility map when available", async () => {
+ const remoteDocument = {
+ version: 1,
+ policies: [
+ {
+ t3CodeRange: ">=0.0.0",
+ driver: "codex",
+ recommendedRange: "<0.130.0",
+ recommendedVersion: "0.129.0",
+ ranges: [{ status: "broken", range: ">=0.130.0" }],
+ },
+ ],
+ };
+
+ const enriched = await Effect.runPromise(
+ enrichProviderSnapshotWithCompatibilityAdvisory(baseProvider).pipe(
+ Effect.provideService(HttpClient.HttpClient, jsonHttpClient(remoteDocument)),
+ ),
+ );
+
+ expect(enriched.status).toBe("error");
+ expect(enriched.compatibilityAdvisory).toMatchObject({
+ status: "broken",
+ recommendedVersion: "0.129.0",
+ });
+ });
+
+ it("falls back to the bundled map when the remote compatibility fetch fails", async () => {
+ const enriched = await Effect.runPromise(
+ enrichProviderSnapshotWithCompatibilityAdvisory({
+ ...baseProvider,
+ version: "0.128.0",
+ }).pipe(Effect.provideService(HttpClient.HttpClient, jsonHttpClient({}, 404))),
+ );
+
+ expect(enriched.status).toBe("error");
+ expect(enriched.compatibilityAdvisory).toMatchObject({
+ status: "broken",
+ recommendedVersion: "0.129.0",
+ });
+ });
+
+ it("remote advisory demotes status set by bundled advisory", async () => {
+ // The bundled document marks <0.129.0 as "broken", so 0.128.0 triggers
+ // an error advisory on the initial snapshot.
+ const snapshotAfterBundled = applyBundledProviderCompatibilityAdvisory({
+ snapshot: { ...baseProvider, version: "0.128.0" },
+ driver: codexDriver,
+ currentVersion: "0.128.0",
+ });
+ expect(snapshotAfterBundled.status).toBe("error");
+ expect(snapshotAfterBundled.compatibilityAdvisory?.severity).toBe("error");
+
+ // The remote document relaxes the policy: 0.128.0 is now "supported".
+ const remoteDocument = {
+ version: 1,
+ policies: [
+ {
+ t3CodeRange: ">=0.0.0",
+ driver: "codex",
+ recommendedRange: ">=0.129.0",
+ recommendedVersion: "0.129.0",
+ ranges: [{ status: "supported", range: ">=0.128.0" }],
+ },
+ ],
+ };
+
+ const enriched = await Effect.runPromise(
+ enrichProviderSnapshotWithCompatibilityAdvisory({
+ ...snapshotAfterBundled,
+ instanceId: baseProvider.instanceId,
+ driver: baseProvider.driver,
+ }).pipe(Effect.provideService(HttpClient.HttpClient, jsonHttpClient(remoteDocument))),
+ );
+
+ expect(enriched.status).toBe("ready");
+ expect(enriched.compatibilityAdvisory?.severity).toBe("info");
+ expect(enriched.message).toBeUndefined();
+ });
+});
diff --git a/apps/server/src/provider/providerCompatibility.ts b/apps/server/src/provider/providerCompatibility.ts
new file mode 100644
--- /dev/null
+++ b/apps/server/src/provider/providerCompatibility.ts
@@ -1,0 +1,371 @@
+import {
+ ProviderDriverKind,
+ TrimmedNonEmptyString,
+ type ServerProvider,
+ type ServerProviderCompatibilityAdvisory,
+ type ServerProviderCompatibilityRange,
+} from "@t3tools/contracts";
+import { satisfiesSemverRange } from "@t3tools/shared/semver";
+import * as DateTime from "effect/DateTime";
+import * as Effect from "effect/Effect";
+import * as Option from "effect/Option";
+import * as Schema from "effect/Schema";
+import { HttpClient, HttpClientRequest } from "effect/unstable/http";
+
+import bundledCompatibilityDocumentJson from "../../../../provider-compatibility.v1.json" with { type: "json" };
+import packageJson from "../../package.json" with { type: "json" };
+
+export interface ProviderCompatibilityPolicy {
+ readonly t3CodeRange: string;
+ readonly driver: ProviderDriverKind;
+ readonly recommendedRange: string | null;
+ readonly recommendedVersion?: string | null;
+ readonly ranges: ReadonlyArray<ServerProviderCompatibilityRange>;
+}
+
+export interface ProviderCompatibilityDocument {
+ readonly version: 1;
+ readonly policies: ReadonlyArray<ProviderCompatibilityPolicy>;
+}
+
+interface RemoteCompatibilityCacheEntry {
+ readonly expiresAt: number;
+ readonly document: ProviderCompatibilityDocument | null;
+}
+
+type ProviderCompatibilitySnapshot = Pick<ServerProvider, "enabled" | "status" | "message"> & {
+ readonly compatibilityAdvisory?: ServerProviderCompatibilityAdvisory | undefined;
+};
+
+const T3_CODE_VERSION = packageJson.version;
+const REMOTE_COMPATIBILITY_CACHE_TTL_MS = 15 * 60 * 1_000;
+const REMOTE_COMPATIBILITY_TIMEOUT_MS = 2_500;
+
+export const DEFAULT_PROVIDER_COMPATIBILITY_MAP_URL =
+ "https://raw.githubusercontent.com/pingdotgg/t3code/main/provider-compatibility.v1.json";
+
+const remoteCompatibilityCache = new Map<string, RemoteCompatibilityCacheEntry>();
+
+const RemoteCompatibilityRange = Schema.Struct({
+ status: Schema.Literals(["unknown", "supported", "graceful", "unsupported", "broken"]),
+ range: TrimmedNonEmptyString,
+ label: Schema.optional(TrimmedNonEmptyString),
+});
+
+const RemoteCompatibilityPolicy = Schema.Struct({
+ t3CodeRange: TrimmedNonEmptyString,
+ driver: TrimmedNonEmptyString,
+ recommendedRange: Schema.NullOr(TrimmedNonEmptyString),
+ recommendedVersion: Schema.optionalKey(Schema.NullOr(TrimmedNonEmptyString)),
+ ranges: Schema.Array(RemoteCompatibilityRange),
+});
+
+const RemoteCompatibilityDocument = Schema.Struct({
+ version: Schema.Literal(1),
+ policies: Schema.Array(RemoteCompatibilityPolicy),
+});
+
+function normalizeCompatibilityDocument(
+ document: typeof RemoteCompatibilityDocument.Type,
+): ProviderCompatibilityDocument {
+ return {
+ version: document.version,
+ policies: document.policies.map((policy) => ({
+ t3CodeRange: policy.t3CodeRange,
+ driver: ProviderDriverKind.make(policy.driver),
+ recommendedRange: policy.recommendedRange,
+ ...(policy.recommendedVersion !== undefined
+ ? { recommendedVersion: policy.recommendedVersion }
+ : {}),
+ ranges: policy.ranges.map((range) => ({
+ status: range.status,
+ range: range.range,
+ ...(range.label !== undefined ? { label: range.label } : {}),
+ })),
+ })),
+ };
+}
+
+const decodeRawCompatibilityDocument = Schema.decodeUnknownEffect(RemoteCompatibilityDocument);
+const decodeCompatibilityDocument = (input: unknown) =>
+ decodeRawCompatibilityDocument(input).pipe(Effect.map(normalizeCompatibilityDocument));
+
+/**
+ * Repo-root compatibility JSON bundled into the app. Used when the remote map
+ * cannot be fetched or has no matching policy for the current provider/build.
+ */
+const bundledProviderCompatibilityDocument: ProviderCompatibilityDocument =
+ normalizeCompatibilityDocument(
+ bundledCompatibilityDocumentJson as typeof RemoteCompatibilityDocument.Type,
+ );
+
+export function clearProviderCompatibilityCacheForTests(): void {
+ remoteCompatibilityCache.clear();
+}
+
+function remoteCompatibilityMapUrl(): string {
+ return (
+ process.env.T3_PROVIDER_COMPATIBILITY_MAP_URL?.trim() || DEFAULT_PROVIDER_COMPATIBILITY_MAP_URL
+ );
+}
+
+function policyMatches(input: {
+ readonly policy: ProviderCompatibilityPolicy;
+ readonly driver: ProviderDriverKind;
+ readonly t3CodeVersion: string;
+}): boolean {
+ return (
+ input.policy.driver === input.driver &&
+ satisfiesSemverRange(input.t3CodeVersion, input.policy.t3CodeRange)
+ );
+}
+
+function compatibilityPolicyForDriver(input: {
+ readonly document: ProviderCompatibilityDocument;
+ readonly driver: ProviderDriverKind;
+ readonly t3CodeVersion?: string;
+}): ProviderCompatibilityPolicy | null {
+ const t3CodeVersion = input.t3CodeVersion ?? T3_CODE_VERSION;
+ return (
+ input.document.policies.find((policy) =>
+ policyMatches({ policy, driver: input.driver, t3CodeVersion }),
+ ) ?? null
+ );
+}
+
+function severityForStatus(
+ status: ServerProviderCompatibilityAdvisory["status"],
+): ServerProviderCompatibilityAdvisory["severity"] {
+ switch (status) {
+ case "broken":
+ return "error";
+ case "unsupported":
+ case "graceful":
+ return "warning";
+ case "supported":
+ case "unknown":
+ return "info";
+ }
+}
+
+function messageForStatus(input: {
+ readonly status: ServerProviderCompatibilityAdvisory["status"];
+ readonly currentVersion: string | null;
+ readonly recommendedRange: string | null;
+ readonly recommendedVersion: string | null;
+}) {
+ const current = input.currentVersion ? ` ${input.currentVersion}` : "";
+ const recommendedTarget = input.recommendedVersion ?? input.recommendedRange;
+ const recommended = recommendedTarget ? ` Use ${recommendedTarget}.` : "";
+ switch (input.status) {
+ case "broken":
+ return `This provider harness version${current} is known to be incompatible with this T3 Code release.${recommended}`;
+ case "unsupported":
+ return `This provider harness version${current} is outside the compatibility range for this T3 Code release.${recommended}`;
+ case "graceful":
+ return `This provider harness version${current} should still work, but updating is recommended.${recommended}`;
+ case "unknown":
+ return `T3 Code could not determine whether this provider harness version is compatible.${recommended}`;
+ case "supported":
+ return null;
+ }
+}
+
+function createProviderCompatibilityAdvisoryFromDocument(input: {
+ readonly document: ProviderCompatibilityDocument;
+ readonly driver: ProviderDriverKind;
+ readonly currentVersion: string | null;
+ readonly t3CodeVersion?: string;
+}): ServerProviderCompatibilityAdvisory | undefined {
+ const policy = compatibilityPolicyForDriver({
+ document: input.document,
+ driver: input.driver,
+ ...(input.t3CodeVersion ? { t3CodeVersion: input.t3CodeVersion } : {}),
+ });
+ if (!policy) {
+ return undefined;
+ }
+
+ const currentVersion = input.currentVersion;
+ const matchedRange =
+ currentVersion === null
+ ? undefined
+ : policy.ranges.find((range) => satisfiesSemverRange(currentVersion, range.range));
+ const status = matchedRange?.status ?? (currentVersion === null ? "unknown" : "unsupported");
+ const recommendedVersion = policy.recommendedVersion ?? null;
+
+ return {
+ status,
+ severity: severityForStatus(status),
+ currentVersion: input.currentVersion,
+ message: messageForStatus({
+ status,
+ currentVersion: input.currentVersion,
+ recommendedRange: policy.recommendedRange,
+ recommendedVersion,
+ }),
+ recommendedRange: policy.recommendedRange,
+ recommendedVersion,
+ ranges: [...policy.ranges],
+ };
+}
+
+export function createProviderCompatibilityAdvisory(input: {
+ readonly driver: ProviderDriverKind;
+ readonly currentVersion: string | null;
+ readonly document?: ProviderCompatibilityDocument;
+ readonly t3CodeVersion?: string;
+}): ServerProviderCompatibilityAdvisory | undefined {
+ return createProviderCompatibilityAdvisoryFromDocument({
+ document: input.document ?? bundledProviderCompatibilityDocument,
+ driver: input.driver,
+ currentVersion: input.currentVersion,
+ ...(input.t3CodeVersion ? { t3CodeVersion: input.t3CodeVersion } : {}),
+ });
+}
+
+function fetchRemoteCompatibilityDocument(
+ url: string,
+): Effect.Effect<ProviderCompatibilityDocument | null, never, HttpClient.HttpClient> {
+ return Effect.gen(function* () {
+ const client = yield* HttpClient.HttpClient;
+ const response = yield* client
+ .execute(
+ HttpClientRequest.get(url).pipe(
+ HttpClientRequest.setHeader("accept", "application/json"),
+ HttpClientRequest.setHeader("user-agent", `t3code/${T3_CODE_VERSION}`),
+ ),
+ )
+ .pipe(Effect.timeoutOption(REMOTE_COMPATIBILITY_TIMEOUT_MS));
+
+ if (Option.isNone(response)) {
+ return null;
+ }
+
+ const httpResponse = response.value;
+ if (httpResponse.status < 200 || httpResponse.status >= 300) {
+ return null;
+ }
+
+ const payload = yield* httpResponse.json.pipe(
+ Effect.flatMap(decodeCompatibilityDocument),
+ Effect.catch(() => Effect.succeed(null)),
+ );
+ return payload;
+ }).pipe(
+ Effect.tapError((cause) =>
+ Effect.logWarning("provider compatibility map fetch failed", {
+ cause,
+ url,
+ }),
+ ),
+ Effect.catch(() => Effect.succeed(null)),
+ );
+}
+
+export const resolveRemoteProviderCompatibilityDocument = Effect.fn(
+ "resolveRemoteProviderCompatibilityDocument",
+)(function* () {
+ const url = remoteCompatibilityMapUrl();
+ const now = DateTime.toEpochMillis(yield* DateTime.now);
+ const cached = remoteCompatibilityCache.get(url);
+ if (cached && cached.expiresAt > now) {
+ return cached.document;
+ }
+
+ const document = yield* fetchRemoteCompatibilityDocument(url);
+ remoteCompatibilityCache.set(url, {
+ expiresAt: now + REMOTE_COMPATIBILITY_CACHE_TTL_MS,
+ document,
+ });
+ return document;
+});
+
+function baseStatusWithoutAdvisory<Snapshot extends ProviderCompatibilitySnapshot>(
+ snapshot: Snapshot,
+): Snapshot["status"] {
+ const prev = snapshot.compatibilityAdvisory;
+ if (!prev || !snapshot.enabled) return snapshot.status;
+ if (prev.severity === "error" && snapshot.status === "error") return "ready";
+ if (prev.severity === "warning" && snapshot.status === "warning") return "ready";
+ return snapshot.status;
+}
+
+function baseMessageWithoutAdvisory<Snapshot extends ProviderCompatibilitySnapshot>(
+ snapshot: Snapshot,
+): Snapshot["message"] {
+ const prev = snapshot.compatibilityAdvisory;
+ if (prev?.message != null && snapshot.message === prev.message) return undefined;
+ return snapshot.message;
+}
+
+function applyCompatibilityAdvisory<Snapshot extends ProviderCompatibilitySnapshot>(
+ snapshot: Snapshot,
+ compatibilityAdvisory: ServerProviderCompatibilityAdvisory | undefined,
+): Snapshot {
+ if (!compatibilityAdvisory) {
+ const { compatibilityAdvisory: _omit, ...snapshotWithoutAdvisory } = snapshot;
+ return snapshotWithoutAdvisory as Snapshot;
+ }
+
+ const baseStatus = baseStatusWithoutAdvisory(snapshot);
+
+ const compatibilityMessage =
+ compatibilityAdvisory.severity !== "info"
+ ? (compatibilityAdvisory.message ?? undefined)
+ : undefined;
+ const status =
+ snapshot.enabled && compatibilityAdvisory.severity === "error"
+ ? "error"
+ : snapshot.enabled && compatibilityAdvisory.severity === "warning" && baseStatus === "ready"
+ ? "warning"
+ : baseStatus;
+
+ const baseMessage = baseMessageWithoutAdvisory(snapshot);
+ const resolvedMessage = compatibilityMessage ?? baseMessage;
+ const { message: _existingMessage, ...snapshotWithoutMessage } = snapshot;
+
+ return {
+ ...snapshotWithoutMessage,
+ status,
+ ...(resolvedMessage != null ? { message: resolvedMessage } : {}),
+ compatibilityAdvisory,
+ } as Snapshot;
+}
+
+export function applyBundledProviderCompatibilityAdvisory<
+ Snapshot extends ProviderCompatibilitySnapshot,
+>(input: {
+ readonly snapshot: Snapshot;
+ readonly driver: ProviderDriverKind;
+ readonly currentVersion: string | null;
+}): Snapshot {
+ return applyCompatibilityAdvisory(
+ input.snapshot,
+ createProviderCompatibilityAdvisory({
+ driver: input.driver,
+ currentVersion: input.currentVersion,
+ }),
+ );
+}
+
+export const enrichProviderSnapshotWithCompatibilityAdvisory = Effect.fn(
+ "enrichProviderSnapshotWithCompatibilityAdvisory",
+)(function* (snapshot: ServerProvider) {
+ const remoteDocument = yield* resolveRemoteProviderCompatibilityDocument();
+ const remoteAdvisory = remoteDocument
+ ? createProviderCompatibilityAdvisory({
+ driver: snapshot.driver,
+ currentVersion: snapshot.version,
+ document: remoteDocument,
+ })
+ : undefined;
+ const advisory =
+ remoteAdvisory ??
+ createProviderCompatibilityAdvisory({
+ driver: snapshot.driver,
+ currentVersion: snapshot.version,
+ });
+
+ return applyCompatibilityAdvisory(snapshot, advisory);
+});
diff --git a/apps/server/src/provider/providerSnapshot.test.ts b/apps/server/src/provider/providerSnapshot.test.ts
--- a/apps/server/src/provider/providerSnapshot.test.ts
+++ b/apps/server/src/provider/providerSnapshot.test.ts
@@ -2,7 +2,7 @@
import { ProviderDriverKind, type ModelCapabilities } from "@t3tools/contracts";
import { createModelCapabilities } from "@t3tools/shared/model";
-import { providerModelsFromSettings } from "./providerSnapshot.ts";
+import { buildServerProvider, providerModelsFromSettings } from "./providerSnapshot.ts";
const OPENCODE_CUSTOM_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({
optionDescriptors: [
@@ -42,3 +42,56 @@
]);
});
});
+
+describe("buildServerProvider", () => {
+ it("marks known incompatible provider harness versions as errors", () => {
+ const provider = buildServerProvider({
+ driver: ProviderDriverKind.make("codex"),
+ presentation: { displayName: "Codex" },
+ enabled: true,
+ checkedAt: "2026-04-10T00:00:00.000Z",
+ models: [],
+ probe: {
+ installed: true,
+ version: "0.128.0",
+ status: "ready",
+ auth: { status: "authenticated" },
+ },
+ });
+
+ expect(provider.status).toBe("error");
+ expect(provider.compatibilityAdvisory).toMatchObject({
+ status: "broken",
+ severity: "error",
+ currentVersion: "0.128.0",
+ recommendedRange: ">=0.129.0",
+ recommendedVersion: "0.129.0",
+ });
+ expect(provider.message).toContain("known to be incompatible");
+ });
+
+ it("keeps known supported provider harness versions ready", () => {
+ const provider = buildServerProvider({
+ driver: ProviderDriverKind.make("codex"),
+ presentation: { displayName: "Codex" },
+ enabled: true,
+ checkedAt: "2026-04-10T00:00:00.000Z",
+ models: [],
+ probe: {
+ installed: true,
+ version: "0.129.0",
+ status: "ready",
+ auth: { status: "authenticated" },
+ },
+ });
+
+ expect(provider.status).toBe("ready");
+ expect(provider.compatibilityAdvisory).toMatchObject({
+ status: "supported",
+ severity: "info",
+ currentVersion: "0.129.0",
+ recommendedVersion: "0.129.0",
+ });
+ expect(provider.message).toBeUndefined();
+ });
+});
diff --git a/apps/server/src/provider/providerSnapshot.ts b/apps/server/src/provider/providerSnapshot.ts
--- a/apps/server/src/provider/providerSnapshot.ts
+++ b/apps/server/src/provider/providerSnapshot.ts
@@ -14,6 +14,7 @@
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
import { normalizeModelSlug } from "@t3tools/shared/model";
import { isWindowsCommandNotFound } from "../processRunner.ts";
+import { applyBundledProviderCompatibilityAdvisory } from "./providerCompatibility.ts";
import { createProviderVersionAdvisory } from "./providerMaintenance.ts";
import { collectUint8StreamText } from "../stream/collectUint8StreamText.ts";
@@ -208,7 +209,7 @@
checkedAt: input.checkedAt,
})
: undefined;
- return {
+ const snapshot: ServerProviderDraft = {
displayName: input.presentation.displayName,
...(input.presentation.badgeLabel ? { badgeLabel: input.presentation.badgeLabel } : {}),
...(typeof input.presentation.showInteractionModeToggle === "boolean"
@@ -226,6 +227,13 @@
skills: [...(input.skills ?? [])],
...(versionAdvisory ? { versionAdvisory } : {}),
};
+ return input.driver
+ ? applyBundledProviderCompatibilityAdvisory({
+ snapshot,
+ driver: input.driver,
+ currentVersion: input.probe.version,
+ })
+ : snapshot;
}
export const collectStreamAsString = <E>(
diff --git a/apps/web/src/components/settings/ProviderInstanceCard.tsx b/apps/web/src/components/settings/ProviderInstanceCard.tsx
--- a/apps/web/src/components/settings/ProviderInstanceCard.tsx
+++ b/apps/web/src/components/settings/ProviderInstanceCard.tsx
@@ -1,6 +1,7 @@
"use client";
import {
+ AlertTriangleIcon,
ArrowUpCircleIcon,
ChevronDownIcon,
CopyIcon,
@@ -40,6 +41,7 @@
import {
getProviderVersionAdvisoryPresentation,
PROVIDER_STATUS_STYLES,
+ getProviderCompatibilityAdvisoryPresentation,
getProviderSummary,
getProviderVersionLabel,
type ProviderStatusKey,
@@ -481,6 +483,9 @@
: null;
const summary = rawSummary;
const versionLabel = getProviderVersionLabel(liveProvider?.version);
+ const compatibilityAdvisory = getProviderCompatibilityAdvisoryPresentation(
+ liveProvider?.compatibilityAdvisory,
+ );
const versionAdvisory = getProviderVersionAdvisoryPresentation(liveProvider?.versionAdvisory);
const updateCommand = versionAdvisory?.updateCommand ?? null;
const FallbackIconComponent = driverOption?.icon;
@@ -677,6 +682,45 @@
<div className="flex min-w-0 flex-wrap items-center gap-2">
{titleHeadNode}
{versionCodeNode}
+ {compatibilityAdvisory ? (
+ <Popover>
+ <PopoverTrigger
+ render={
+ <Button
+ type="button"
+ size="icon-xs"
+ variant="ghost"
+ className={cn(
+ "size-5 rounded-sm p-0",
+ compatibilityAdvisory.emphasis === "strong"
+ ? "text-destructive hover:text-destructive"
+ : "text-warning hover:text-warning",
+ )}
+ aria-label={`${compatibilityAdvisory.title} — view details`}
+ >
+ <AlertTriangleIcon className="size-3.5" />
+ </Button>
+ }
+ />
+ <PopoverPopup side="bottom" align="start" className="w-84">
+ <div className="grid gap-1">
+ <p className="text-[13px] font-semibold leading-tight text-foreground">
+ {compatibilityAdvisory.title}
+ </p>
+ <p
+ className={cn(
+ "text-xs leading-snug",
+ compatibilityAdvisory.emphasis === "strong"
+ ? "text-destructive"
+ : "text-warning",
+ )}
+ >
+ {compatibilityAdvisory.detail}
+ </p>
+ </div>
... diff truncated: showing 800 of 1015 linesYou can send follow-ups to the cloud agent here.
c14e731 to
e3818c6
Compare
b6fa144 to
9502396
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Negative cache prevents retry after transient failures
- Added a separate 60-second negative-cache TTL constant used when caching null (failed) fetch results, while keeping the 15-minute TTL for successful entries.
Or push these changes by commenting:
@cursor push 3edc33ea14
Preview (3edc33ea14)
diff --git a/apps/server/src/provider/providerCompatibility.ts b/apps/server/src/provider/providerCompatibility.ts
--- a/apps/server/src/provider/providerCompatibility.ts
+++ b/apps/server/src/provider/providerCompatibility.ts
@@ -39,6 +39,7 @@
const T3_CODE_VERSION = packageJson.version;
const REMOTE_COMPATIBILITY_CACHE_TTL_MS = 15 * 60 * 1_000;
+const REMOTE_COMPATIBILITY_NEGATIVE_CACHE_TTL_MS = 60 * 1_000;
const REMOTE_COMPATIBILITY_TIMEOUT_MS = 2_500;
export const DEFAULT_PROVIDER_COMPATIBILITY_MAP_URL =
@@ -290,8 +291,11 @@
}
const document = yield* fetchRemoteCompatibilityDocument(url);
+ const ttl = document
+ ? REMOTE_COMPATIBILITY_CACHE_TTL_MS
+ : REMOTE_COMPATIBILITY_NEGATIVE_CACHE_TTL_MS;
remoteCompatibilityCache.set(url, {
- expiresAt: now + REMOTE_COMPATIBILITY_CACHE_TTL_MS,
+ expiresAt: now + ttl,
document,
});
if (document) {You can send follow-ups to the cloud agent here.
9502396 to
5f9dc67
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Status reset loses real provider errors during advisory removal
- Added preAdvisoryStatus and preAdvisoryMessage fields to the advisory object so removeExistingCompatibilityAdvisory can restore the original provider status and message instead of unconditionally resetting to 'ready'.
Or push these changes by commenting:
@cursor push fad51d16d8
Preview (fad51d16d8)
diff --git a/apps/server/src/provider/providerCompatibility.test.ts b/apps/server/src/provider/providerCompatibility.test.ts
--- a/apps/server/src/provider/providerCompatibility.test.ts
+++ b/apps/server/src/provider/providerCompatibility.test.ts
@@ -327,6 +327,52 @@
});
});
+ it.effect("preserves genuine probe errors when the remote map relaxes a bundled advisory", () => {
+ const bundledMessage =
+ "This provider harness version 0.130.0 is known to be incompatible with this T3 Code release. Use 0.129.0.";
+ const probeErrorMessage = "CLI health-check failed: process exited with code 1";
+ const remoteDocument = {
+ version: 1,
+ policies: [
+ {
+ t3CodeRange: ">=0.0.0",
+ driver: "codex",
+ recommendedRange: ">=0.130.0",
+ recommendedVersion: "0.130.0",
+ ranges: [{ status: "supported", range: ">=0.130.0" }],
+ },
+ ],
+ };
+
+ return Effect.gen(function* () {
+ const enriched = yield* enrichProviderSnapshotWithCompatibilityAdvisory({
+ ...baseProvider,
+ status: "error",
+ message: bundledMessage,
+ compatibilityAdvisory: {
+ status: "broken",
+ severity: "error",
+ currentVersion: "0.130.0",
+ message: bundledMessage,
+ recommendedRange: "<0.130.0",
+ recommendedVersion: "0.129.0",
+ ranges: [{ status: "broken", range: ">=0.130.0" }],
+ preAdvisoryStatus: "error",
+ preAdvisoryMessage: probeErrorMessage,
+ },
+ }).pipe(
+ Effect.provideService(
+ HttpClient.HttpClient,
+ jsonHttpClient(() => ({ payload: remoteDocument })),
+ ),
+ );
+
+ expect(enriched.status).toBe("error");
+ expect(enriched.message).toBe(probeErrorMessage);
+ expect(enriched.compatibilityAdvisory).toMatchObject({ status: "supported" });
+ });
+ });
+
it.effect("falls back to the bundled map when the remote compatibility fetch fails", () =>
Effect.gen(function* () {
const enriched = yield* enrichProviderSnapshotWithCompatibilityAdvisory({
diff --git a/apps/server/src/provider/providerCompatibility.ts b/apps/server/src/provider/providerCompatibility.ts
--- a/apps/server/src/provider/providerCompatibility.ts
+++ b/apps/server/src/provider/providerCompatibility.ts
@@ -325,13 +325,19 @@
? "warning"
: baseSnapshot.status;
+ const advisoryWithPreState: ServerProviderCompatibilityAdvisory = {
+ ...compatibilityAdvisory,
+ preAdvisoryStatus: baseSnapshot.status,
+ ...(baseSnapshot.message ? { preAdvisoryMessage: baseSnapshot.message } : {}),
+ };
+
return {
...baseSnapshot,
status,
...(compatibilityMessage || baseSnapshot.message
? { message: compatibilityMessage ?? baseSnapshot.message }
: {}),
- compatibilityAdvisory,
+ compatibilityAdvisory: advisoryWithPreState,
} as Snapshot;
}
@@ -349,11 +355,19 @@
: undefined;
if (compatibilityMessage && baseSnapshot.message === compatibilityMessage) {
const { message: _message, ...snapshotWithoutCompatibilityMessage } = baseSnapshot;
+ const restoredStatus =
+ existingCompatibilityAdvisory.preAdvisoryStatus ?? (snapshot.enabled ? "ready" : "disabled");
return {
...snapshotWithoutCompatibilityMessage,
- status: snapshot.enabled ? "ready" : "disabled",
+ status: restoredStatus,
+ ...(existingCompatibilityAdvisory.preAdvisoryMessage
+ ? { message: existingCompatibilityAdvisory.preAdvisoryMessage }
+ : {}),
} as Snapshot;
}
+ if (existingCompatibilityAdvisory.preAdvisoryStatus !== undefined) {
+ return { ...baseSnapshot, status: existingCompatibilityAdvisory.preAdvisoryStatus } as Snapshot;
+ }
return baseSnapshot as Snapshot;
}
diff --git a/packages/contracts/src/server.ts b/packages/contracts/src/server.ts
--- a/packages/contracts/src/server.ts
+++ b/packages/contracts/src/server.ts
@@ -163,6 +163,8 @@
ranges: Schema.Array(ServerProviderCompatibilityRange).pipe(
Schema.withDecodingDefault(Effect.succeed([])),
),
+ preAdvisoryStatus: Schema.optionalKey(ServerProviderState),
+ preAdvisoryMessage: Schema.optionalKey(TrimmedNonEmptyString),
});
export type ServerProviderCompatibilityAdvisory = typeof ServerProviderCompatibilityAdvisory.Type;You can send follow-ups to the cloud agent here.
|
Bugbot Autofix prepared a fix for the issue found in the latest run.
Or push these changes by commenting: Preview (b0048f7a73)diff --git a/apps/server/src/provider/ProviderCompatibility.ts b/apps/server/src/provider/ProviderCompatibility.ts
--- a/apps/server/src/provider/ProviderCompatibility.ts
+++ b/apps/server/src/provider/ProviderCompatibility.ts
@@ -233,13 +233,6 @@
),
);
-export const resolveRemoteProviderCompatibilityDocument = Effect.fn(
- "resolveRemoteProviderCompatibilityDocument",
-)(function* () {
- const compatibility = yield* ProviderCompatibilityService;
- return yield* compatibility.resolveRemoteDocument;
-});
-
function applyCompatibilityAdvisory<Snapshot extends ProviderCompatibilitySnapshot>(
snapshot: Snapshot,
compatibilityAdvisory: ServerProviderCompatibilityAdvisory | undefined,You can send follow-ups to the cloud agent here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Test contradicts bundled compatibility map for codex version
- Changed the test driver from 'codex' (which has a matching bundled policy marking <0.129.0 as broken) to 'unmapped-test-driver' (no policy in the bundled map), so the test correctly exercises the no-matching-policy code path.
Or push these changes by commenting:
@cursor push e24a57b546
Preview (e24a57b546)
diff --git a/apps/server/src/provider/providerSnapshot.test.ts b/apps/server/src/provider/providerSnapshot.test.ts
--- a/apps/server/src/provider/providerSnapshot.test.ts
+++ b/apps/server/src/provider/providerSnapshot.test.ts
@@ -46,8 +46,8 @@
describe("buildServerProvider", () => {
it("leaves compatibility unset when no bundled policy matches this T3 Code version", () => {
const provider = buildServerProvider({
- driver: ProviderDriverKind.make("codex"),
- presentation: { displayName: "Codex" },
+ driver: ProviderDriverKind.make("unmapped-test-driver"),
+ presentation: { displayName: "Test" },
enabled: true,
checkedAt: "2026-04-10T00:00:00.000Z",
models: [],You can send follow-ups to the cloud agent here.
- move provider compatibility logic behind a shared service - cache remote map fetches across snapshot refreshes - update drivers and tests to provide the new layer
- Generate provider-specific install commands from compatibility advisories - Surface provider update actions in the chat UI and block sends when unavailable - Skip stale advisories for disabled providers before version probing Co-authored-by: codex <codex@users.noreply.github.com>
b924b40 to
5af5606
Compare
- Move the Vercel-specific install and build commands into `apps/marketing/vercel.ts` - Simplify the marketing package build script to `astro build`
Co-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Remote map drops bundled policy
- Modified createProviderCompatibilityAdvisory to fall back to the bundled document when the provided remote document yields no matching policy, preventing the enrichment path from stripping a valid bundled advisory.
Or push these changes by commenting:
@cursor push 0883def1a8
Preview (0883def1a8)
diff --git a/apps/server/src/provider/ProviderCompatibility.ts b/apps/server/src/provider/ProviderCompatibility.ts
--- a/apps/server/src/provider/ProviderCompatibility.ts
+++ b/apps/server/src/provider/ProviderCompatibility.ts
@@ -204,12 +204,26 @@
readonly maintenanceCapabilities?: ProviderMaintenanceCapabilities | undefined;
readonly t3CodeVersion?: string;
}): ServerProviderCompatibilityAdvisory | undefined {
- return createProviderCompatibilityAdvisoryFromDocument({
- document: input.document ?? bundledProviderCompatibilityDocument,
+ const shared = {
driver: input.driver,
currentVersion: input.currentVersion,
maintenanceCapabilities: input.maintenanceCapabilities,
...(input.t3CodeVersion ? { t3CodeVersion: input.t3CodeVersion } : {}),
+ };
+
+ if (input.document) {
+ const advisory = createProviderCompatibilityAdvisoryFromDocument({
+ document: input.document,
+ ...shared,
+ });
+ if (advisory) {
+ return advisory;
+ }
+ }
+
+ return createProviderCompatibilityAdvisoryFromDocument({
+ document: bundledProviderCompatibilityDocument,
+ ...shared,
});
}You can send follow-ups to the cloud agent here.
Co-authored-by: codex <codex@users.noreply.github.com>
- Decode the source JSON inline before copying - Switch logging to `Effect.logInfo` and remove unused console import
- Require t3Code 0.0.24+ for bundled drivers - Pin OpenCode recommendation to 1.14.40 and flag newer harness versions
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: OpenCode policy misclassifies versions
- Changed the >1.14.40 range status from 'supported' to 'broken' and narrowed the supported range from '>=1.14.19' to '>=1.14.19 <=1.14.40' so versions above 1.14.40 correctly receive broken advisories.
Or push these changes by commenting:
@cursor push 973bdec065
Preview (973bdec065)
diff --git a/provider-compatibility.v1.json b/provider-compatibility.v1.json
--- a/provider-compatibility.v1.json
+++ b/provider-compatibility.v1.json
@@ -39,13 +39,13 @@
"recommendedVersion": "1.14.40",
"ranges": [
{
- "status": "supported",
+ "status": "broken",
"range": ">1.14.40",
"label": "Known incompatible OpenCode harness"
},
{
"status": "supported",
- "range": ">=1.14.19",
+ "range": ">=1.14.19 <=1.14.40",
"label": "Known working OpenCode harness"
},
{You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 97201a8. Configure here.
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>


What changed
provider-compatibility.v1.jsonintended for GitHub raw hosting.Why
Released T3 Code builds need compatibility policy updates without requiring users to update the app. This lets maintainers update a static GitHub-hosted map to mark provider harness versions as supported, graceful, unsupported, or broken for specific T3 Code release ranges.
Validation
bun fmtbun lint(passes with existing unrelated warnings)bun typecheckbunx vitest run packages/contracts/src/server.test.ts apps/server/src/provider/providerSnapshot.test.ts apps/server/src/provider/providerCompatibility.test.ts apps/web/src/components/settings/providerStatus.test.tsNote
Add remote provider compatibility map with advisory enrichment and UI update actions for Codex
ProviderCompatibilityservice (ProviderCompatibility.ts) that loads bundled policies or fetches a remote document, enriches provider snapshots withcompatibilityAdvisoryfields, and may override provider status and message.ProviderCompatibilityServicein their Effect environments.compatibilityAdvisoryonServerProviderand optionaltargetVersionon update input; the maintenance runner now executes a version-pinned install command whentargetVersionis supplied.ProviderStatusBannercomponent.providerAdvisoryhelpers to@t3tools/client-runtimefor deriving update commands, tracking in-progress updates, and building targeted update request payloads.Macroscope summarized f446519.
Note
Medium Risk
Adds new compatibility advisory plumbing and makes multiple provider drivers depend on
ProviderCompatibilityService, affecting provider snapshot enrichment and update execution paths. Risk is mitigated by bundled fallback and new/updated tests, but failures could surface as provider status errors or missing advisories.Overview
Introduces a provider compatibility map (
provider-compatibility.v1.json) that is bundled into the server and optionally fetched at runtime (with timeout + TTL cache) to classify provider harness versions as supported / graceful / unsupported / broken for a given T3 Code version.Wires compatibility advisory enrichment into provider snapshot generation and refresh for
codex,claudeAgent,opencode, andcursor, and extends theServerProvidercontract with acompatibilityAdvisoryplustargetVersionsupport onServerProviderUpdateInputto run targeted installs.Updates the web UI to surface compatibility warnings and install actions via a reusable
ProviderUpdateActionPopover(including chat composer banner integration, disabling send when the selected provider is errored), and adds a marketing build step (scripts/mirror-provider-compatibility-map.ts+apps/marketing/vercel.ts) to publish the map as a static asset.Reviewed by Cursor Bugbot for commit f446519. Bugbot is set up for automated code reviews on this repo. Configure here.