diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b9458fc1a..fd44ded9bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai - Docs: seed zh-CN translations. (#6619) Thanks @joshp123. - Docs: expand zh-Hans navigation and fix zh-CN index asset paths. (#7242) Thanks @joshp123. - Docs: add zh-CN landing notice + AI-translated image. (#7303) Thanks @joshp123. +- Config: allow setting a default subagent thinking level via `agents.defaults.subagents.thinking` (and per-agent `agents.list[].subagents.thinking`). (#7372) Thanks @tyler6204. ### Fixes diff --git a/docs/tools/subagents.md b/docs/tools/subagents.md index 425c276e0d9..d1696f8d43a 100644 --- a/docs/tools/subagents.md +++ b/docs/tools/subagents.md @@ -40,6 +40,7 @@ Use `sessions_spawn`: - Starts a sub-agent run (`deliver: false`, global lane: `subagent`) - Then runs an announce step and posts the announce reply to the requester chat channel - Default model: inherits the caller unless you set `agents.defaults.subagents.model` (or per-agent `agents.list[].subagents.model`); an explicit `sessions_spawn.model` still wins. +- Default thinking: inherits the caller unless you set `agents.defaults.subagents.thinking` (or per-agent `agents.list[].subagents.thinking`); an explicit `sessions_spawn.thinking` still wins. Tool params: diff --git a/docs/zh-CN/tools/subagents.md b/docs/zh-CN/tools/subagents.md index 01d11a23e0f..7ecfb06178d 100644 --- a/docs/zh-CN/tools/subagents.md +++ b/docs/zh-CN/tools/subagents.md @@ -45,6 +45,7 @@ x-i18n: - 启动子智能体运行(`deliver: false`,全局队列:`subagent`) - 然后运行回报步骤,将回报回复发布到请求者的聊天渠道 - 默认模型:继承调用者,除非你设置了 `agents.defaults.subagents.model`(或按智能体 `agents.list[].subagents.model`);显式的 `sessions_spawn.model` 仍然优先。 +- 默认思考级别:继承调用者,除非你设置了 `agents.defaults.subagents.thinking`(或按智能体 `agents.list[].subagents.thinking`);显式的 `sessions_spawn.thinking` 仍然优先。 工具参数: diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn-applies-thinking-default.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn-applies-thinking-default.test.ts new file mode 100644 index 00000000000..36eb50b553c --- /dev/null +++ b/src/agents/openclaw-tools.subagents.sessions-spawn-applies-thinking-default.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it, vi } from "vitest"; +import { createSessionsSpawnTool } from "./tools/sessions-spawn-tool.js"; + +vi.mock("../config/config.js", async () => { + const actual = await vi.importActual("../config/config.js"); + return { + ...actual, + loadConfig: () => ({ + agents: { + defaults: { + subagents: { + thinking: "high", + }, + }, + }, + routing: { + sessions: { + mainKey: "agent:test:main", + }, + }, + }), + }; +}); + +vi.mock("../gateway/call.js", () => { + return { + callGateway: vi.fn(async ({ method }: { method: string }) => { + if (method === "agent") { + return { runId: "run-123" }; + } + return {}; + }), + }; +}); + +describe("sessions_spawn thinking defaults", () => { + it("applies agents.defaults.subagents.thinking when thinking is omitted", async () => { + const tool = createSessionsSpawnTool({ agentSessionKey: "agent:test:main" }); + const result = await tool.execute("call-1", { task: "hello" }); + expect(result.details).toMatchObject({ status: "accepted" }); + + const { callGateway } = await import("../gateway/call.js"); + const calls = (callGateway as unknown as ReturnType).mock.calls; + + const agentCall = calls + .map((call) => call[0] as { method: string; params?: Record }) + .findLast((call) => call.method === "agent"); + + expect(agentCall?.params?.thinking).toBe("high"); + }); + + it("prefers explicit sessions_spawn.thinking over config default", async () => { + const tool = createSessionsSpawnTool({ agentSessionKey: "agent:test:main" }); + const result = await tool.execute("call-2", { task: "hello", thinking: "low" }); + expect(result.details).toMatchObject({ status: "accepted" }); + + const { callGateway } = await import("../gateway/call.js"); + const calls = (callGateway as unknown as ReturnType).mock.calls; + + const agentCall = calls + .map((call) => call[0] as { method: string; params?: Record }) + .findLast((call) => call.method === "agent"); + + expect(agentCall?.params?.thinking).toBe("low"); + }); +}); diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index c1abe004a96..6fe582c528a 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -172,15 +172,21 @@ export function createSessionsSpawnTool(opts?: { normalizeModelSelection(modelOverride) ?? normalizeModelSelection(targetAgentConfig?.subagents?.model) ?? normalizeModelSelection(cfg.agents?.defaults?.subagents?.model); + + const resolvedThinkingDefaultRaw = + readStringParam(targetAgentConfig?.subagents ?? {}, "thinking") ?? + readStringParam(cfg.agents?.defaults?.subagents ?? {}, "thinking"); + let thinkingOverride: string | undefined; - if (thinkingOverrideRaw) { - const normalized = normalizeThinkLevel(thinkingOverrideRaw); + const thinkingCandidateRaw = thinkingOverrideRaw || resolvedThinkingDefaultRaw; + if (thinkingCandidateRaw) { + const normalized = normalizeThinkLevel(thinkingCandidateRaw); if (!normalized) { const { provider, model } = splitModelRef(resolvedModel); const hint = formatThinkingLevels(provider, model); return jsonResult({ status: "error", - error: `Invalid thinking level "${thinkingOverrideRaw}". Use one of: ${hint}.`, + error: `Invalid thinking level "${thinkingCandidateRaw}". Use one of: ${hint}.`, }); } thinkingOverride = normalized; diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 8019abac430..0008c717340 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -204,6 +204,8 @@ export type AgentDefaultsConfig = { archiveAfterMinutes?: number; /** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */ model?: string | { primary?: string; fallbacks?: string[] }; + /** Default thinking level for spawned sub-agents (e.g. "off", "low", "medium", "high"). */ + thinking?: string; }; /** Optional sandbox settings for non-main sessions. */ sandbox?: { diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index a849078ed4a..ff2f9dff83d 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -150,6 +150,7 @@ export const AgentDefaultsSchema = z .strict(), ]) .optional(), + thinking: z.string().optional(), }) .strict() .optional(), diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index aec597c54b6..bab9748f639 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -446,6 +446,7 @@ export const AgentEntrySchema = z .strict(), ]) .optional(), + thinking: z.string().optional(), }) .strict() .optional(),