feat(system-agent): local-model viability — context cap, thinking off, route-aware timeout (#109445)

* feat: improve system agent local model viability

* fix: forward Ollama effective context cap

* fix(system-agent): keep flat 120s agent-turn budget

* fix(system-agent): keep manifests timeout helper module-local
This commit is contained in:
Peter Steinberger
2026-07-16 18:11:22 -07:00
committed by GitHub
parent 92146f9f80
commit 784ede0af1
16 changed files with 287 additions and 28 deletions
+8 -7
View File
@@ -1725,13 +1725,12 @@ describe("ollama plugin", () => {
it("resolves GLM-5.2 from the cloud fallback catalog", () => {
const provider = registerOllamaCloudProvider();
const model = provider.resolveDynamicModel?.({
provider: "ollama-cloud",
modelId: "glm-5.2:cloud",
} as never);
expect(
provider.resolveDynamicModel?.({
provider: "ollama-cloud",
modelId: "glm-5.2:cloud",
} as never),
).toEqual(
expect(model).toEqual(
expect.objectContaining({
provider: "ollama-cloud",
id: "glm-5.2:cloud",
@@ -1740,6 +1739,7 @@ describe("ollama plugin", () => {
reasoning: true,
}),
);
expect(model?.contextTokens).toBeUndefined();
});
it("does not mint synthetic auth for public IPv4 baseUrl", () => {
@@ -1786,6 +1786,7 @@ describe("ollama plugin", () => {
id: "qwen3:32b",
baseUrl: "http://127.0.0.1:11434/v1",
contextWindow: 202_752,
contextTokens: 32_768,
},
streamFn: baseStreamFn,
});
@@ -1795,7 +1796,7 @@ describe("ollama plugin", () => {
}
void wrapped({} as never, {} as never, {});
expect(baseStreamFn).toHaveBeenCalledTimes(1);
expect((payloadSeen?.options as Record<string, unknown> | undefined)?.num_ctx).toBe(202752);
expect((payloadSeen?.options as Record<string, unknown> | undefined)?.num_ctx).toBe(32_768);
});
it("owns replay policy for OpenAI-compatible and native Ollama routes", () => {
+37 -8
View File
@@ -67,6 +67,10 @@ import {
createOllamaNodeInvokePolicy,
} from "./src/node-inference.js";
import { readProviderBaseUrl } from "./src/provider-base-url.js";
import {
capLocalOllamaModelContext,
capLocalOllamaProviderContext,
} from "./src/provider-models.js";
import {
OLLAMA_INCOMPLETE_STREAM_ERROR,
createConfiguredOllamaCompatStreamWrapper,
@@ -101,6 +105,13 @@ const OLLAMA_CLOUD_DEFAULT_MODEL_REF = `${OLLAMA_CLOUD_PROVIDER_ID}/${OLLAMA_CLO
const OLLAMA_CONFIGURED_SHOW_CONCURRENCY = 4;
const OLLAMA_CONFIGURED_SHOW_MAX_MODELS = 8;
async function buildLocalOllamaProvider(
configuredBaseUrl?: string,
opts?: Parameters<typeof buildOllamaProvider>[1],
): Promise<ModelProviderConfig> {
return capLocalOllamaProviderContext(await buildOllamaProvider(configuredBaseUrl, opts));
}
async function discoverAppGuidedOllamaModel(ctx: ProviderAppGuidedSetupContext) {
const pluginConfig = resolvePluginConfigObject(ctx.config, OLLAMA_PROVIDER_ID) as
| OllamaPluginConfig
@@ -131,7 +142,14 @@ async function discoverAppGuidedOllamaModel(ctx: ProviderAppGuidedSetupContext)
ownerValue = OLLAMA_DEFAULT_API_KEY;
}
}
return model ? { existing, provider, model, ownerValue } : null;
return model
? {
existing,
provider: capLocalOllamaProviderContext(provider),
model: capLocalOllamaModelContext(model),
ownerValue,
}
: null;
}
function buildDynamicCacheKey(provider: string, baseUrl: string | undefined): string {
@@ -164,6 +182,9 @@ function toDynamicOllamaModel(params: {
input: input.length > 0 ? input : ["text"],
cost: params.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: params.model.contextWindow ?? 8192,
...(params.model.contextTokens !== undefined
? { contextTokens: params.model.contextTokens }
: {}),
maxTokens: params.model.maxTokens ?? 8192,
...(params.model.compat ? { compat: params.model.compat as never } : {}),
...(params.model.params ? { params: params.model.params } : {}),
@@ -421,6 +442,7 @@ async function resolveRequestedDynamicOllamaModel(params: {
providerConfig: ModelProviderConfig;
modelId: string;
showApiKey?: string;
capContextTokens?: boolean;
}): Promise<ProviderRuntimeModel | undefined> {
const showBaseUrl = readProviderBaseUrl(params.providerConfig) ?? OLLAMA_DEFAULT_BASE_URL;
const showInfo = params.showApiKey
@@ -429,14 +451,16 @@ async function resolveRequestedDynamicOllamaModel(params: {
if (typeof showInfo.contextWindow !== "number" && (showInfo.capabilities?.length ?? 0) === 0) {
return undefined;
}
const definition = buildOllamaModelDefinition(
params.modelId,
showInfo.contextWindow,
showInfo.capabilities,
);
const model = params.capContextTokens ? capLocalOllamaModelContext(definition) : definition;
return toDynamicOllamaModel({
provider: params.provider,
providerConfig: params.providerConfig,
model: buildOllamaModelDefinition(
params.modelId,
showInfo.contextWindow,
showInfo.capabilities,
),
model,
});
}
@@ -447,6 +471,7 @@ async function augmentConfiguredOllamaCatalogModels(params: {
provider: string;
entries: ProviderAugmentModelCatalogContext["entries"];
resolveProviderApiKey: ProviderAugmentModelCatalogContext["resolveProviderApiKey"];
capContextTokens?: boolean;
}): Promise<ProviderAugmentModelCatalogContext["entries"]> {
const models = collectConfiguredOllamaModelIds({
config: params.config,
@@ -488,6 +513,7 @@ async function augmentConfiguredOllamaCatalogModels(params: {
providerConfig,
modelId: model.id,
showApiKey,
capContextTokens: params.capContextTokens,
});
return requested
? {
@@ -498,6 +524,7 @@ async function augmentConfiguredOllamaCatalogModels(params: {
reasoning: requested.reasoning,
input: requested.input,
contextWindow: requested.contextWindow,
contextTokens: requested.contextTokens,
compat: requested.compat,
}
: undefined;
@@ -745,7 +772,7 @@ export default definePluginEntry({
await resolveOllamaDiscoveryResult({
ctx,
pluginConfig: resolveCurrentPluginConfig(ctx.config),
buildProvider: buildOllamaProvider,
buildProvider: buildLocalOllamaProvider,
}),
},
wizard: {
@@ -801,6 +828,7 @@ export default definePluginEntry({
provider: OLLAMA_PROVIDER_ID,
entries: ctx.entries,
resolveProviderApiKey: ctx.resolveProviderApiKey,
capContextTokens: true,
}),
createEmbeddingProvider: async ({ config, model, provider: embeddingProvider, remote }) => {
const { provider, client } = await createOllamaEmbeddingProvider({
@@ -838,7 +866,7 @@ export default definePluginEntry({
return;
}
const baseUrl = readProviderBaseUrl(providerConfig);
const provider = await buildOllamaProvider(baseUrl, { quiet: true });
const provider = await buildLocalOllamaProvider(baseUrl, { quiet: true });
const dynamicApi = providerConfig?.api ?? provider.api;
const dynamicProvider = {
...provider,
@@ -861,6 +889,7 @@ export default definePluginEntry({
provider: ctx.provider,
providerConfig: dynamicProvider,
modelId: ctx.modelId,
capContextTokens: true,
});
if (requestedModel) {
dynamicModels.push(requestedModel);
+3 -2
View File
@@ -8,7 +8,7 @@ import {
shouldUseSyntheticOllamaAuth,
type OllamaPluginConfig,
} from "./src/discovery-shared.js";
import { buildOllamaProvider } from "./src/provider-models.js";
import { buildOllamaProvider, capLocalOllamaProviderContext } from "./src/provider-models.js";
type OllamaProviderPlugin = {
id: string;
@@ -41,7 +41,8 @@ async function runOllamaDiscovery(ctx: ProviderCatalogContext) {
return await resolveOllamaDiscoveryResult({
ctx,
pluginConfig: resolveOllamaPluginConfig(ctx),
buildProvider: buildOllamaProvider,
buildProvider: async (...args) =>
capLocalOllamaProviderContext(await buildOllamaProvider(...args)),
});
}
+1
View File
@@ -13,6 +13,7 @@ export const OLLAMA_CLOUD_DEFAULT_MODELS = [
] as const;
export const OLLAMA_DEFAULT_CONTEXT_WINDOW = 128000;
export const OLLAMA_LOCAL_CONTEXT_TOKENS = 32_768;
export const OLLAMA_DEFAULT_MAX_TOKENS = 8192;
export const OLLAMA_DEFAULT_COST = {
input: 0,
+33 -3
View File
@@ -63,9 +63,10 @@ describe("resolveOllamaDiscoveryResult — hosted Ollama Cloud guard", () => {
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
contextTokens: 24_000,
maxTokens: 8192,
compat: { supportsTools: true, supportsUsageInStreaming: true },
params: { num_ctx: 128000 },
params: { num_ctx: 48_000 },
} satisfies ModelProviderConfig["models"][number];
const buildMockProvider = async (
@@ -130,11 +131,40 @@ describe("resolveOllamaDiscoveryResult — hosted Ollama Cloud guard", () => {
expect(result).not.toBeNull();
const discoveryResult = expectDefined(result, "Ollama Cloud discovery result");
expect(discoveryResult.provider.models).toHaveLength(1);
expect(expectDefined(discoveryResult.provider.models[0], "Ollama Cloud model").id).toBe(
"minimax-m3:cloud",
expect(expectDefined(discoveryResult.provider.models[0], "Ollama Cloud model")).toEqual(
cloudModel,
);
});
it("preserves explicit local model context overrides without discovery", async () => {
let providerCalled = false;
const result = await resolveOllamaDiscoveryResult({
ctx: {
config: {
models: {
providers: {
ollama: {
baseUrl: "http://127.0.0.1:11434",
api: "ollama",
models: [cloudModel],
},
},
},
},
env: {},
resolveProviderApiKey: () => ({}),
},
pluginConfig: {},
buildProvider: async () => {
providerCalled = true;
return await buildMockProvider();
},
});
expect(providerCalled).toBe(false);
expect(result?.provider.models).toEqual([cloudModel]);
});
it("does not call buildProvider for remote base URL without explicit models", async () => {
let providerCalled = false;
const trackingBuildProvider = async (
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import {
buildOllamaProvider,
buildOllamaModelDefinition,
capLocalOllamaProviderContext,
enrichOllamaModelsWithContext,
fetchOllamaModels,
queryOllamaModelShowInfo,
@@ -22,6 +23,22 @@ describe("ollama provider models", () => {
expect(resolveOllamaApiBase("http://127.0.0.1:11434///")).toBe("http://127.0.0.1:11434");
});
it("caps local discovered runtime context while preserving native metadata", () => {
const provider = capLocalOllamaProviderContext({
api: "ollama",
baseUrl: "http://127.0.0.1:11434",
models: [
buildOllamaModelDefinition("qwen3.5:4b", 262_144),
buildOllamaModelDefinition("small", 16_384),
],
});
expect(provider.models).toEqual([
expect.objectContaining({ contextWindow: 262_144, contextTokens: 32_768 }),
expect.objectContaining({ contextWindow: 16_384, contextTokens: 16_384 }),
]);
});
it("sets discovered models with context windows from /api/show", async () => {
const models: OllamaTagModel[] = [{ name: "llama3:8b" }, { name: "deepseek-r1:14b" }];
const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
+20
View File
@@ -11,6 +11,7 @@ import {
OLLAMA_DEFAULT_MAX_TOKENS,
OLLAMA_GLM52_CLOUD_MODEL_ID,
OLLAMA_GLM52_CONTEXT_WINDOW,
OLLAMA_LOCAL_CONTEXT_TOKENS,
} from "./defaults.js";
export type OllamaTagModel = {
@@ -301,6 +302,25 @@ export function buildOllamaModelDefinition(
};
}
export function capLocalOllamaModelContext(model: ModelDefinitionConfig): ModelDefinitionConfig {
if (typeof model.contextWindow !== "number") {
return model;
}
return {
...model,
// Local Ollama allocates KV cache from num_ctx. Keep native metadata, but cap
// setup-assistant and typical agent turns at 32k; config overlays remain authoritative.
contextTokens: Math.min(OLLAMA_LOCAL_CONTEXT_TOKENS, model.contextWindow),
};
}
export function capLocalOllamaProviderContext(provider: ModelProviderConfig): ModelProviderConfig {
return {
...provider,
models: provider.models?.map(capLocalOllamaModelContext),
};
}
export async function fetchOllamaModels(
baseUrl: string,
opts?: { apiKey?: string },
@@ -2509,6 +2509,7 @@ describe("createOllamaStreamFn", () => {
streaming: false,
},
contextWindow: 131072,
contextTokens: 16384,
},
options: { temperature: 0.7, maxTokens: 55 },
});
@@ -2540,6 +2541,32 @@ describe("createOllamaStreamFn", () => {
);
});
it("uses effective contextTokens for native Ollama chat options", async () => {
await withMockNdjsonFetch(
[
'{"model":"m","created_at":"t","message":{"role":"assistant","content":"ok"},"done":false}',
'{"model":"m","created_at":"t","message":{"role":"assistant","content":""},"done":true,"prompt_eval_count":1,"eval_count":1}',
],
async (fetchMock) => {
const stream = await createOllamaTestStream({
baseUrl: "http://ollama-host:11434",
model: { contextWindow: 262_144, contextTokens: 32_768 },
});
await collectStreamEvents(stream);
const requestInit = getGuardedFetchCall(fetchMock).init ?? {};
if (typeof requestInit.body !== "string") {
throw new Error("Expected string request body");
}
const requestBody = JSON.parse(requestInit.body) as {
options: { num_ctx?: number };
};
expect(requestBody.options.num_ctx).toBe(32_768);
},
);
});
it("sets top_p=1 for native Ollama greedy sampling requests", async () => {
await withMockNdjsonFetch(
[
+18 -5
View File
@@ -347,23 +347,36 @@ function resolveOllamaConfiguredNumCtx(model: ProviderRuntimeModel): number | un
function resolveOllamaNumCtx(model: ProviderRuntimeModel): number {
return (
resolveOllamaConfiguredNumCtx(model) ??
Math.max(1, Math.floor(model.contextWindow ?? model.maxTokens ?? DEFAULT_CONTEXT_TOKENS))
Math.max(
1,
Math.floor(
model.contextTokens ?? model.contextWindow ?? model.maxTokens ?? DEFAULT_CONTEXT_TOKENS,
),
)
);
}
/**
* Resolves num_ctx for native /api/chat requests:
* 1. explicit `params.num_ctx` set on the model wins,
* 2. otherwise return undefined so Ollama's model, OLLAMA_CONTEXT_LENGTH,
* VRAM, or Modelfile policy decides.
* 2. the effective `contextTokens` runtime cap is forwarded when present,
* 3. otherwise Ollama's model, OLLAMA_CONTEXT_LENGTH, VRAM, or Modelfile policy decides.
*
* This intentionally differs from `resolveOllamaNumCtx` by not falling back
* to `DEFAULT_CONTEXT_TOKENS`: that constant is a sane wrapper-side guess for
* the OpenAI-compat path, but native `/api/chat` should not force the full
* advertised catalog context for local models unless the operator opted in.
* advertised `contextWindow`; only an explicit runtime cap or operator override is forwarded.
*/
function resolveOllamaNativeNumCtx(model: ProviderRuntimeModel): number | undefined {
return resolveOllamaConfiguredNumCtx(model);
const configured = resolveOllamaConfiguredNumCtx(model);
if (configured !== undefined) {
return configured;
}
const effective = model.contextTokens;
if (typeof effective !== "number" || !Number.isFinite(effective) || effective <= 0) {
return undefined;
}
return Math.floor(effective);
}
function resolveOllamaModelOptions(model: ProviderRuntimeModel): Record<string, unknown> {
+2
View File
@@ -232,6 +232,8 @@ describe("runSystemAgentTurn", () => {
authProfileId: "openai:p2",
authProfileIdSource: "user",
config: binding.execution.runConfig,
thinkLevel: "off",
timeoutMs: 120_000,
}),
);
+4
View File
@@ -29,6 +29,9 @@ import {
* Turns share one persistent session so the conversation has genuine
* multi-turn memory. Inference setup must succeed before this runner is entered.
*/
// Flat budget for both route classes: agent-loop turns run multi-step tool
// calls, so even metered external routes need the full window, and 120s
// already covers local startup + generation (planner evidence).
const AGENT_TURN_TIMEOUT_MS = 120_000;
const SYSTEM_AGENT_MCP_TOOL_NAME = "mcp__openclaw__openclaw";
@@ -323,6 +326,7 @@ async function runSystemAgentTurnWithDeps(
config: plan.runConfig,
prompt: params.input,
timeoutMs: AGENT_TURN_TIMEOUT_MS,
thinkLevel: "off" as const,
runId,
messageChannel: "openclaw",
messageProvider: "openclaw",
+3 -1
View File
@@ -9,8 +9,10 @@ import type { SystemAgentOverview } from "./overview.js";
* touch the system through OpenClaw's typed command vocabulary; parsing
* stays deliberately narrow so free-form model text never executes directly.
*/
/** Timeout for one assistant turn (local CLI backends cold-start slowly). */
/** Timeout for one assistant turn on an external, potentially metered route. */
export const SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS = 30_000;
/** Local startup stages can consume nearly 30s before dispatch; leave inference a real budget. */
export const SYSTEM_AGENT_ASSISTANT_LOCAL_TIMEOUT_MS = 120_000;
/** System prompt: persona plus the closed command vocabulary. */
export const SYSTEM_AGENT_ASSISTANT_SYSTEM_PROMPT = [
@@ -0,0 +1,51 @@
// System-agent timeout tests cover manifest-owned local-route classification.
import { describe, expect, it } from "vitest";
import {
SYSTEM_AGENT_ASSISTANT_LOCAL_TIMEOUT_MS,
SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
} from "./assistant-prompts.js";
import "./assistant-timeout.js";
const { resolveSystemAgentAssistantTimeoutFromManifests } = (
globalThis as Record<PropertyKey, unknown>
)[Symbol.for("openclaw.systemAgentTimeoutTestApi")] as {
resolveSystemAgentAssistantTimeoutFromManifests: (params: {
route: { modelLabel: string; provider: string };
plugins: ReadonlyArray<{
modelPricing?: { providers?: Record<string, { external?: boolean }> };
}>;
}) => number;
};
describe("system-agent assistant timeout", () => {
it.each([
{
name: "external provider",
provider: "openai",
modelLabel: "openai/gpt-5.5",
external: true,
expected: SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
},
{
name: "local provider",
provider: "ollama",
modelLabel: "ollama/qwen3.5:4b",
external: false,
expected: SYSTEM_AGENT_ASSISTANT_LOCAL_TIMEOUT_MS,
},
{
name: "hosted sibling provider",
provider: "ollama-cloud",
modelLabel: "ollama-cloud/glm-5.2:cloud",
external: true,
expected: SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
},
])("uses the $name budget", ({ provider, modelLabel, external, expected }) => {
expect(
resolveSystemAgentAssistantTimeoutFromManifests({
route: { provider, modelLabel },
plugins: [{ modelPricing: { providers: { [provider]: { external } } } }],
}),
).toBe(expected);
});
});
+53
View File
@@ -0,0 +1,53 @@
// Resolves the system-agent turn budget from manifest-owned provider metadata.
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
import {
SYSTEM_AGENT_ASSISTANT_LOCAL_TIMEOUT_MS,
SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
} from "./assistant-prompts.js";
import type { SystemAgentConfiguredRoute } from "./inference-route.js";
type SystemAgentPricingManifest = Pick<PluginManifestRecord, "modelPricing">;
function resolveSystemAgentAssistantTimeoutFromManifests(params: {
route: Pick<SystemAgentConfiguredRoute, "modelLabel" | "provider">;
plugins: readonly SystemAgentPricingManifest[];
}): number {
const providers = new Set([
normalizeProviderId(params.route.provider),
normalizeProviderId(params.route.modelLabel.split("/", 1)[0] ?? ""),
]);
const isLocal = params.plugins.some((plugin) =>
Object.entries(plugin.modelPricing?.providers ?? {}).some(
([provider, pricing]) =>
providers.has(normalizeProviderId(provider)) && pricing.external === false,
),
);
return isLocal ? SYSTEM_AGENT_ASSISTANT_LOCAL_TIMEOUT_MS : SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS;
}
export function resolveSystemAgentAssistantTimeoutMs(route: SystemAgentConfiguredRoute): number {
try {
const workspaceDir = resolveAgentWorkspaceDir(route.runConfig, route.agentId);
const snapshot = resolvePluginMetadataSnapshot({
config: route.runConfig,
workspaceDir,
env: process.env,
allowWorkspaceScopedCurrent: true,
});
return resolveSystemAgentAssistantTimeoutFromManifests({
route,
plugins: snapshot.plugins,
});
} catch {
return SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS;
}
}
if (process.env.VITEST || process.env.NODE_ENV === "test") {
(globalThis as Record<PropertyKey, unknown>)[Symbol.for("openclaw.systemAgentTimeoutTestApi")] = {
resolveSystemAgentAssistantTimeoutFromManifests,
};
}
@@ -306,6 +306,7 @@ describe("OpenClaw configured-model planner", () => {
runEmbeddedAgent: runEmbeddedAgent as never,
createTempDir: async () => "/tmp/openclaw-planner",
removeTempDir: async () => {},
resolveAssistantTimeoutMs: () => 120_000,
},
});
@@ -325,6 +326,8 @@ describe("OpenClaw configured-model planner", () => {
disableTools: true,
disableTrajectory: true,
toolsAllow: [],
thinkLevel: "off",
timeoutMs: 120_000,
}),
);
expect(runEmbeddedAgent).toHaveBeenCalledWith(
+7 -2
View File
@@ -5,12 +5,12 @@ import os from "node:os";
import path from "node:path";
import {
SYSTEM_AGENT_ASSISTANT_SYSTEM_PROMPT,
SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
buildSystemAgentAssistantUserPrompt,
parseSystemAgentAssistantPlanText,
type SystemAgentAssistantPlan,
type SystemAgentAssistantTurn,
} from "./assistant-prompts.js";
import { resolveSystemAgentAssistantTimeoutMs } from "./assistant-timeout.js";
import { SystemAgentInferenceUnavailableError } from "./inference-error.js";
import type { SystemAgentOverview } from "./overview.js";
import {
@@ -43,6 +43,7 @@ export type SystemAgentConfiguredModelPlannerDeps = SystemAgentVerifiedInference
runEmbeddedAgent?: RunEmbeddedAgentFn;
createTempDir?: () => Promise<string>;
removeTempDir?: (dir: string) => Promise<void>;
resolveAssistantTimeoutMs?: typeof resolveSystemAgentAssistantTimeoutMs;
};
export async function planSystemAgentCommand(params: {
@@ -90,6 +91,9 @@ export async function planSystemAgentCommandWithConfiguredModel(params: {
let plan: SystemAgentAssistantPlan | null;
try {
const runId = `openclaw-planner-${randomUUID()}`;
const timeoutMs = (
params.deps?.resolveAssistantTimeoutMs ?? resolveSystemAgentAssistantTimeoutMs
)(route);
const shared = {
sessionId: `${runId}-session`,
agentId: "openclaw",
@@ -102,7 +106,8 @@ export async function planSystemAgentCommandWithConfiguredModel(params: {
prompt,
provider: route.provider,
model: route.model,
timeoutMs: SYSTEM_AGENT_ASSISTANT_TIMEOUT_MS,
timeoutMs,
thinkLevel: "off" as const,
runId,
extraSystemPrompt: SYSTEM_AGENT_ASSISTANT_SYSTEM_PROMPT,
extraSystemPromptStatic: SYSTEM_AGENT_ASSISTANT_SYSTEM_PROMPT,