diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5829546cc63..7a769cd7e65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
- Agents/sessions: preserve terminal lifecycle state when final run metadata persists from a stale in-memory snapshot, preventing `main` sessions from staying stuck as running after completed or timed-out turns.
- Gateway/CLI: make `openclaw gateway start` repair stale managed service definitions that point at old OpenClaw versions, missing binaries, or temporary installer paths before starting.
- Heartbeat/scheduler: make heartbeat phase scheduling active-hours-aware so the scheduler seeks forward to the first in-window phase slot instead of arming timers for quiet-hours slots and relying solely on the runtime guard. Non-UTC `activeHours.timezone` values (e.g. `Asia/Shanghai`) now correctly influence when the next heartbeat timer fires, avoiding wasted quiet-hours ticks and long dormant gaps after gateway restarts. Fixes #75487. Thanks @amknight.
+- Providers/Arcee AI: mark Trinity Large Thinking as tool-incompatible so main-session runs use the same text-only request shape that made subagent runs recover, avoiding the remaining main-session response-shape mismatch after the #62848 transport failover fix. Fixes #62851 and #62847; carries forward #62848. Thanks @Adam-Researchh.
- Status: show the `openai-codex` OAuth profile for `openai/gpt-*` sessions running through the native Codex runtime instead of reporting auth as unknown. (#76197) Thanks @mbelinky.
- Gateway: avoid repeated plugin tool descriptor config hashing so large runtime configs do not block reply startup and trigger reconnect/timeouts. (#75944) Thanks @joshavant.
- Plugins/externalization: keep diagnostics ClawHub packages and persisted bundled-plugin relocation on npm-first install metadata for launch, and omit Discord from the core package now that its external package is published. Thanks @vincentkoc.
diff --git a/docs/providers/arcee.md b/docs/providers/arcee.md
index 90e0523cca2..b08f7a86c22 100644
--- a/docs/providers/arcee.md
+++ b/docs/providers/arcee.md
@@ -98,24 +98,24 @@ Arcee AI models can be accessed directly via the Arcee platform or through [Open
OpenClaw currently ships this bundled Arcee catalog:
-| Model ref | Name | Input | Context | Cost (in/out per 1M) | Notes |
-| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ----------------------------------------- |
-| `arcee/trinity-large-thinking` | Trinity Large Thinking | text | 256K | $0.25 / $0.90 | Default model; reasoning enabled |
-| `arcee/trinity-large-preview` | Trinity Large Preview | text | 128K | $0.25 / $1.00 | General-purpose; 400B params, 13B active |
-| `arcee/trinity-mini` | Trinity Mini 26B | text | 128K | $0.045 / $0.15 | Fast and cost-efficient; function calling |
+| Model ref | Name | Input | Context | Cost (in/out per 1M) | Notes |
+| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ------------------------------------------ |
+| `arcee/trinity-large-thinking` | Trinity Large Thinking | text | 256K | $0.25 / $0.90 | Default model; reasoning enabled; no tools |
+| `arcee/trinity-large-preview` | Trinity Large Preview | text | 128K | $0.25 / $1.00 | General-purpose; 400B params, 13B active |
+| `arcee/trinity-mini` | Trinity Mini 26B | text | 128K | $0.045 / $0.15 | Fast and cost-efficient; function calling |
-The onboarding preset sets `arcee/trinity-large-thinking` as the default model.
+The onboarding preset sets `arcee/trinity-large-thinking` as the default model. It is reasoning/text-only and does not support tool use or function calling.
## Supported features
-| Feature | Supported |
-| --------------------------------------------- | ---------------------------- |
-| Streaming | Yes |
-| Tool use / function calling | Yes |
-| Structured output (JSON mode and JSON schema) | Yes |
-| Extended thinking | Yes (Trinity Large Thinking) |
+| Feature | Supported |
+| --------------------------------------------- | ------------------------------------------- |
+| Streaming | Yes |
+| Tool use / function calling | Model-dependent; not Trinity Large Thinking |
+| Structured output (JSON mode and JSON schema) | Yes |
+| Extended thinking | Yes (Trinity Large Thinking) |
diff --git a/extensions/arcee/index.test.ts b/extensions/arcee/index.test.ts
index 8379057b443..a8ed79207f0 100644
--- a/extensions/arcee/index.test.ts
+++ b/extensions/arcee/index.test.ts
@@ -69,6 +69,14 @@ describe("arcee provider plugin", () => {
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
]);
+ expect(
+ config?.models?.providers?.arcee?.models?.find(
+ (model) => model.id === "arcee/trinity-large-thinking",
+ )?.compat,
+ ).toMatchObject({
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ });
});
it("keeps direct Arcee auth env candidates separate from OpenRouter", () => {
@@ -92,6 +100,12 @@ describe("arcee provider plugin", () => {
"trinity-large-preview",
"trinity-large-thinking",
]);
+ expect(
+ catalogProvider.models?.find((model) => model.id === "trinity-large-thinking")?.compat,
+ ).toMatchObject({
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ });
});
it("builds the OpenRouter-backed Arcee AI model catalog", async () => {
@@ -112,6 +126,12 @@ describe("arcee provider plugin", () => {
"arcee/trinity-large-preview",
"arcee/trinity-large-thinking",
]);
+ expect(
+ catalogProvider.models?.find((model) => model.id === "arcee/trinity-large-thinking")?.compat,
+ ).toMatchObject({
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ });
});
it("normalizes Arcee OpenRouter models to vendor-prefixed runtime ids", async () => {
@@ -130,6 +150,10 @@ describe("arcee provider plugin", () => {
} as never),
).toMatchObject({
id: "arcee/trinity-large-thinking",
+ compat: {
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ },
});
expect(
@@ -176,6 +200,10 @@ describe("arcee provider plugin", () => {
).toMatchObject({
id: "arcee/trinity-large-thinking",
baseUrl: "https://openrouter.ai/api/v1",
+ compat: {
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ },
});
expect(
@@ -189,4 +217,152 @@ describe("arcee provider plugin", () => {
baseUrl: "https://openrouter.ai/api/v1",
});
});
+
+ it("repairs stale Trinity tool compat on existing Arcee configs and runtime models", async () => {
+ const provider = await registerSingleProviderPlugin(arceePlugin);
+
+ expect(
+ provider.normalizeConfig?.({
+ provider: "arcee",
+ providerConfig: {
+ api: "openai-completions",
+ baseUrl: "https://openrouter.ai/v1/",
+ models: [
+ {
+ id: "arcee/trinity-large-thinking",
+ name: "Trinity Large Thinking",
+ reasoning: true,
+ input: ["text"],
+ contextWindow: 262144,
+ maxTokens: 80000,
+ cost: {
+ input: 0.25,
+ output: 0.9,
+ cacheRead: 0.25,
+ cacheWrite: 0.25,
+ },
+ compat: {
+ supportsReasoningEffort: false,
+ supportsStrictMode: true,
+ },
+ },
+ ],
+ },
+ } as never),
+ ).toMatchObject({
+ baseUrl: "https://openrouter.ai/api/v1",
+ models: [
+ {
+ id: "arcee/trinity-large-thinking",
+ compat: {
+ supportsReasoningEffort: false,
+ supportsStrictMode: true,
+ supportsTools: false,
+ },
+ },
+ ],
+ });
+
+ expect(
+ provider.normalizeConfig?.({
+ provider: "arcee",
+ providerConfig: {
+ api: "openai-completions",
+ baseUrl: "https://api.arcee.ai/api/v1",
+ models: [
+ {
+ id: "trinity-large-thinking",
+ name: "Trinity Large Thinking",
+ reasoning: true,
+ input: ["text"],
+ contextWindow: 262144,
+ maxTokens: 80000,
+ cost: {
+ input: 0.25,
+ output: 0.9,
+ cacheRead: 0.25,
+ cacheWrite: 0.25,
+ },
+ compat: {
+ supportsReasoningEffort: false,
+ },
+ },
+ ],
+ },
+ } as never),
+ ).toMatchObject({
+ baseUrl: "https://api.arcee.ai/api/v1",
+ models: [
+ {
+ id: "trinity-large-thinking",
+ compat: {
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ },
+ },
+ ],
+ });
+
+ const trinityRuntimeModel = {
+ name: "Trinity Large Thinking",
+ api: "openai-completions",
+ reasoning: true,
+ input: ["text"],
+ contextWindow: 262144,
+ maxTokens: 80000,
+ cost: {
+ input: 0.25,
+ output: 0.9,
+ cacheRead: 0.25,
+ cacheWrite: 0.25,
+ },
+ compat: {
+ supportsReasoningEffort: false,
+ },
+ };
+
+ const trinityCompat = {
+ supportsReasoningEffort: false,
+ supportsTools: false,
+ };
+
+ expect(
+ provider.contributeResolvedModelCompat?.({
+ provider: "arcee",
+ modelId: "arcee/trinity-large-thinking",
+ model: {
+ ...trinityRuntimeModel,
+ provider: "arcee",
+ id: "arcee/trinity-large-thinking",
+ baseUrl: "https://openrouter.ai/api/v1",
+ },
+ } as never),
+ ).toEqual(trinityCompat);
+
+ expect(
+ provider.contributeResolvedModelCompat?.({
+ provider: "arcee",
+ modelId: "trinity-large-thinking",
+ model: {
+ ...trinityRuntimeModel,
+ provider: "arcee",
+ id: "trinity-large-thinking",
+ baseUrl: "https://api.arcee.ai/api/v1",
+ },
+ } as never),
+ ).toEqual(trinityCompat);
+
+ expect(
+ provider.contributeResolvedModelCompat?.({
+ provider: "openrouter",
+ modelId: "trinity-large-thinking",
+ model: {
+ ...trinityRuntimeModel,
+ provider: "openrouter",
+ id: "trinity-large-thinking",
+ baseUrl: "https://openrouter.ai/api/v1",
+ },
+ } as never),
+ ).toBeUndefined();
+ });
});
diff --git a/extensions/arcee/index.ts b/extensions/arcee/index.ts
index e7fd0126950..edd4fda7aa3 100644
--- a/extensions/arcee/index.ts
+++ b/extensions/arcee/index.ts
@@ -17,6 +17,12 @@ import {
normalizeArceeOpenRouterBaseUrl,
toArceeOpenRouterModelId,
} from "./provider-catalog.js";
+import {
+ ARCEE_TRINITY_LARGE_THINKING_COMPAT,
+ applyArceeTrinityLargeThinkingCompat,
+ normalizeArceeProviderConfig,
+ shouldContributeArceeTrinityLargeThinkingCompat,
+} from "./provider-policy.js";
const PROVIDER_ID = "arcee";
const ARCEE_WIZARD_GROUP = {
@@ -95,7 +101,7 @@ function normalizeArceeResolvedModel
return undefined;
}
return {
- ...model,
+ ...applyArceeTrinityLargeThinkingCompat(model),
id: normalizedId,
baseUrl: normalizedBaseUrl,
};
@@ -120,13 +126,12 @@ export default definePluginEntry({
config,
providerId: PROVIDER_ID,
}),
- normalizeConfig: ({ providerConfig }) => {
- const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(providerConfig.baseUrl);
- return normalizedBaseUrl && normalizedBaseUrl !== providerConfig.baseUrl
- ? { ...providerConfig, baseUrl: normalizedBaseUrl }
- : undefined;
- },
+ normalizeConfig: ({ providerConfig }) => normalizeArceeProviderConfig(providerConfig),
normalizeResolvedModel: ({ model }) => normalizeArceeResolvedModel(model),
+ contributeResolvedModelCompat: (ctx) =>
+ shouldContributeArceeTrinityLargeThinkingCompat(ctx)
+ ? ARCEE_TRINITY_LARGE_THINKING_COMPAT
+ : undefined,
normalizeTransport: ({ api, baseUrl }) => {
const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(baseUrl);
return normalizedBaseUrl && normalizedBaseUrl !== baseUrl
diff --git a/extensions/arcee/models.ts b/extensions/arcee/models.ts
index 399faed49a8..cb8873301f2 100644
--- a/extensions/arcee/models.ts
+++ b/extensions/arcee/models.ts
@@ -1,6 +1,7 @@
-import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
+import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
+import { ARCEE_BASE_URL, ARCEE_TRINITY_LARGE_THINKING_COMPAT } from "./provider-policy.js";
-export const ARCEE_BASE_URL = "https://api.arcee.ai/api/v1";
+export { ARCEE_BASE_URL, ARCEE_TRINITY_LARGE_THINKING_COMPAT };
export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
{
@@ -44,9 +45,7 @@ export const ARCEE_MODEL_CATALOG: ModelDefinitionConfig[] = [
cacheRead: 0.25,
cacheWrite: 0.25,
},
- compat: {
- supportsReasoningEffort: false,
- },
+ compat: ARCEE_TRINITY_LARGE_THINKING_COMPAT,
},
];
diff --git a/extensions/arcee/provider-catalog.ts b/extensions/arcee/provider-catalog.ts
index 5631ad2998d..844d9a68dd6 100644
--- a/extensions/arcee/provider-catalog.ts
+++ b/extensions/arcee/provider-catalog.ts
@@ -1,31 +1,13 @@
-import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
-import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./models.js";
+import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
+import { buildArceeModelDefinition, ARCEE_MODEL_CATALOG } from "./models.js";
+import {
+ ARCEE_BASE_URL,
+ normalizeArceeOpenRouterBaseUrl,
+ OPENROUTER_BASE_URL,
+ toArceeOpenRouterModelId,
+} from "./provider-policy.js";
-export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
-const OPENROUTER_LEGACY_BASE_URL = "https://openrouter.ai/v1";
-
-function normalizeBaseUrl(baseUrl: string | undefined): string {
- return (baseUrl ?? "").trim().replace(/\/+$/, "");
-}
-
-export function normalizeArceeOpenRouterBaseUrl(baseUrl: string | undefined): string | undefined {
- const normalized = normalizeBaseUrl(baseUrl);
- if (!normalized) {
- return undefined;
- }
- if (normalized === OPENROUTER_BASE_URL || normalized === OPENROUTER_LEGACY_BASE_URL) {
- return OPENROUTER_BASE_URL;
- }
- return undefined;
-}
-
-export function toArceeOpenRouterModelId(modelId: string): string {
- const normalized = modelId.trim();
- if (!normalized || normalized.startsWith("arcee/")) {
- return normalized;
- }
- return `arcee/${normalized}`;
-}
+export { normalizeArceeOpenRouterBaseUrl, OPENROUTER_BASE_URL, toArceeOpenRouterModelId };
export function buildArceeCatalogModels(): NonNullable {
return ARCEE_MODEL_CATALOG.map(buildArceeModelDefinition);
diff --git a/extensions/arcee/provider-policy-api.test.ts b/extensions/arcee/provider-policy-api.test.ts
new file mode 100644
index 00000000000..0d4afe464d2
--- /dev/null
+++ b/extensions/arcee/provider-policy-api.test.ts
@@ -0,0 +1,73 @@
+import { describe, expect, it } from "vitest";
+import { normalizeConfig } from "./provider-policy-api.js";
+
+describe("arcee provider policy public artifact", () => {
+ it("normalizes stale OpenRouter base URLs and Trinity compat without loading the full plugin", () => {
+ expect(
+ normalizeConfig({
+ provider: "arcee",
+ providerConfig: {
+ api: "openai-completions",
+ baseUrl: "https://openrouter.ai/v1/",
+ models: [
+ {
+ id: "arcee/trinity-large-thinking",
+ name: "Trinity Large Thinking",
+ reasoning: true,
+ input: ["text"],
+ contextWindow: 262144,
+ maxTokens: 80000,
+ cost: {
+ input: 0.25,
+ output: 0.9,
+ cacheRead: 0.25,
+ cacheWrite: 0.25,
+ },
+ compat: {
+ supportsReasoningEffort: false,
+ supportsStrictMode: true,
+ },
+ },
+ ],
+ },
+ }),
+ ).toMatchObject({
+ baseUrl: "https://openrouter.ai/api/v1",
+ models: [
+ {
+ id: "arcee/trinity-large-thinking",
+ compat: {
+ supportsReasoningEffort: false,
+ supportsStrictMode: true,
+ supportsTools: false,
+ },
+ },
+ ],
+ });
+ });
+
+ it("returns unchanged non-Trinity configs by identity", () => {
+ const providerConfig = {
+ api: "openai-completions",
+ baseUrl: "https://api.arcee.ai/api/v1",
+ models: [
+ {
+ id: "trinity-mini",
+ name: "Trinity Mini 26B",
+ reasoning: false,
+ input: ["text"],
+ contextWindow: 131072,
+ maxTokens: 80000,
+ cost: {
+ input: 0.045,
+ output: 0.15,
+ cacheRead: 0.045,
+ cacheWrite: 0.045,
+ },
+ },
+ ],
+ } satisfies Parameters[0]["providerConfig"];
+
+ expect(normalizeConfig({ provider: "arcee", providerConfig })).toBe(providerConfig);
+ });
+});
diff --git a/extensions/arcee/provider-policy-api.ts b/extensions/arcee/provider-policy-api.ts
new file mode 100644
index 00000000000..61fffb9057f
--- /dev/null
+++ b/extensions/arcee/provider-policy-api.ts
@@ -0,0 +1,11 @@
+import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
+import { normalizeArceeProviderConfig } from "./provider-policy.js";
+
+export { normalizeArceeProviderConfig };
+
+export function normalizeConfig(params: {
+ provider?: string;
+ providerConfig: ModelProviderConfig;
+}): ModelProviderConfig {
+ return normalizeArceeProviderConfig(params.providerConfig);
+}
diff --git a/extensions/arcee/provider-policy.ts b/extensions/arcee/provider-policy.ts
new file mode 100644
index 00000000000..a02cf9336cd
--- /dev/null
+++ b/extensions/arcee/provider-policy.ts
@@ -0,0 +1,132 @@
+import type {
+ ModelCompatConfig,
+ ModelProviderConfig,
+} from "openclaw/plugin-sdk/provider-model-types";
+
+export const ARCEE_BASE_URL = "https://api.arcee.ai/api/v1";
+export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
+export const ARCEE_TRINITY_LARGE_THINKING_COMPAT = {
+ supportsReasoningEffort: false,
+ supportsTools: false,
+} as const satisfies ModelCompatConfig;
+
+const ARCEE_PROVIDER_ID = "arcee";
+const OPENROUTER_LEGACY_BASE_URL = "https://openrouter.ai/v1";
+const ARCEE_TRINITY_LARGE_THINKING_ID = "trinity-large-thinking";
+const ARCEE_TRINITY_LARGE_THINKING_REF = `${ARCEE_PROVIDER_ID}/${ARCEE_TRINITY_LARGE_THINKING_ID}`;
+
+function normalizeModelId(modelId: string): string {
+ return modelId.trim().toLowerCase();
+}
+
+function normalizeBaseUrl(baseUrl: unknown): string {
+ return typeof baseUrl === "string" ? baseUrl.trim().replace(/\/+$/, "") : "";
+}
+
+export function normalizeArceeOpenRouterBaseUrl(baseUrl: string | undefined): string | undefined {
+ const normalized = normalizeBaseUrl(baseUrl);
+ if (!normalized) {
+ return undefined;
+ }
+ if (normalized === OPENROUTER_BASE_URL || normalized === OPENROUTER_LEGACY_BASE_URL) {
+ return OPENROUTER_BASE_URL;
+ }
+ return undefined;
+}
+
+export function toArceeOpenRouterModelId(modelId: string): string {
+ const normalized = modelId.trim();
+ if (!normalized || normalized.startsWith("arcee/")) {
+ return normalized;
+ }
+ return `arcee/${normalized}`;
+}
+
+export function isArceeTrinityLargeThinkingModelId(modelId: string): boolean {
+ const normalized = normalizeModelId(modelId);
+ return (
+ normalized === ARCEE_TRINITY_LARGE_THINKING_ID ||
+ normalized === ARCEE_TRINITY_LARGE_THINKING_REF
+ );
+}
+
+export function shouldContributeArceeTrinityLargeThinkingCompat(params: {
+ provider?: unknown;
+ modelId: string;
+ model: { id: string; provider?: unknown; baseUrl?: unknown };
+}): boolean {
+ const modelId = normalizeModelId(params.modelId);
+ const resolvedId = normalizeModelId(params.model.id);
+ if (
+ modelId === ARCEE_TRINITY_LARGE_THINKING_REF ||
+ resolvedId === ARCEE_TRINITY_LARGE_THINKING_REF
+ ) {
+ return true;
+ }
+ if (
+ modelId !== ARCEE_TRINITY_LARGE_THINKING_ID &&
+ resolvedId !== ARCEE_TRINITY_LARGE_THINKING_ID
+ ) {
+ return false;
+ }
+ if (params.provider === ARCEE_PROVIDER_ID || params.model.provider === ARCEE_PROVIDER_ID) {
+ return true;
+ }
+ return normalizeBaseUrl(params.model.baseUrl) === normalizeBaseUrl(ARCEE_BASE_URL);
+}
+
+export function applyArceeTrinityLargeThinkingCompat(
+ model: T,
+): T {
+ if (!isArceeTrinityLargeThinkingModelId(model.id)) {
+ return model;
+ }
+ const compat =
+ model.compat && typeof model.compat === "object"
+ ? (model.compat as Record)
+ : undefined;
+ if (
+ compat?.supportsReasoningEffort ===
+ ARCEE_TRINITY_LARGE_THINKING_COMPAT.supportsReasoningEffort &&
+ compat?.supportsTools === ARCEE_TRINITY_LARGE_THINKING_COMPAT.supportsTools
+ ) {
+ return model;
+ }
+ return {
+ ...model,
+ compat: {
+ ...compat,
+ ...ARCEE_TRINITY_LARGE_THINKING_COMPAT,
+ } as T extends { compat?: infer TCompat } ? TCompat : never,
+ } as T;
+}
+
+export function normalizeArceeProviderConfig(
+ providerConfig: ModelProviderConfig,
+): ModelProviderConfig {
+ let changed = false;
+ const normalizedBaseUrl = normalizeArceeOpenRouterBaseUrl(providerConfig.baseUrl);
+ const baseUrl =
+ normalizedBaseUrl && normalizedBaseUrl !== providerConfig.baseUrl
+ ? normalizedBaseUrl
+ : providerConfig.baseUrl;
+ if (baseUrl !== providerConfig.baseUrl) {
+ changed = true;
+ }
+
+ const hasModels = Array.isArray(providerConfig.models);
+ const models = hasModels
+ ? providerConfig.models.map((model) => {
+ const normalizedModel = applyArceeTrinityLargeThinkingCompat(model);
+ if (normalizedModel === model) {
+ return model;
+ }
+ changed = true;
+ return normalizedModel;
+ })
+ : providerConfig.models;
+
+ return changed
+ ? { ...providerConfig, baseUrl, ...(hasModels ? { models } : {}) }
+ : providerConfig;
+}