feat(config): default thinking for sessions_spawn subagents (#7372)
* feat(config): add subagent default thinking * fix: accept config subagents.thinking + stabilize test mocks (#7372) (thanks @tyler6204) * fix: use findLast instead of clearAllMocks in test (#7372) * fix: correct test assertions for tool result structure (#7372) * fix: remove unnecessary type assertion after rebasemain
parent
d3bb32273e
commit
64849e81f5
|
|
@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai
|
||||||
- Docs: seed zh-CN translations. (#6619) Thanks @joshp123.
|
- Docs: seed zh-CN translations. (#6619) Thanks @joshp123.
|
||||||
- Docs: expand zh-Hans navigation and fix zh-CN index asset paths. (#7242) 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.
|
- 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
|
### Fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ Use `sessions_spawn`:
|
||||||
- Starts a sub-agent run (`deliver: false`, global lane: `subagent`)
|
- 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
|
- 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 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:
|
Tool params:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ x-i18n:
|
||||||
- 启动子智能体运行(`deliver: false`,全局队列:`subagent`)
|
- 启动子智能体运行(`deliver: false`,全局队列:`subagent`)
|
||||||
- 然后运行回报步骤,将回报回复发布到请求者的聊天渠道
|
- 然后运行回报步骤,将回报回复发布到请求者的聊天渠道
|
||||||
- 默认模型:继承调用者,除非你设置了 `agents.defaults.subagents.model`(或按智能体 `agents.list[].subagents.model`);显式的 `sessions_spawn.model` 仍然优先。
|
- 默认模型:继承调用者,除非你设置了 `agents.defaults.subagents.model`(或按智能体 `agents.list[].subagents.model`);显式的 `sessions_spawn.model` 仍然优先。
|
||||||
|
- 默认思考级别:继承调用者,除非你设置了 `agents.defaults.subagents.thinking`(或按智能体 `agents.list[].subagents.thinking`);显式的 `sessions_spawn.thinking` 仍然优先。
|
||||||
|
|
||||||
工具参数:
|
工具参数:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<typeof vi.fn>).mock.calls;
|
||||||
|
|
||||||
|
const agentCall = calls
|
||||||
|
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
|
||||||
|
.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<typeof vi.fn>).mock.calls;
|
||||||
|
|
||||||
|
const agentCall = calls
|
||||||
|
.map((call) => call[0] as { method: string; params?: Record<string, unknown> })
|
||||||
|
.findLast((call) => call.method === "agent");
|
||||||
|
|
||||||
|
expect(agentCall?.params?.thinking).toBe("low");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -172,15 +172,21 @@ export function createSessionsSpawnTool(opts?: {
|
||||||
normalizeModelSelection(modelOverride) ??
|
normalizeModelSelection(modelOverride) ??
|
||||||
normalizeModelSelection(targetAgentConfig?.subagents?.model) ??
|
normalizeModelSelection(targetAgentConfig?.subagents?.model) ??
|
||||||
normalizeModelSelection(cfg.agents?.defaults?.subagents?.model);
|
normalizeModelSelection(cfg.agents?.defaults?.subagents?.model);
|
||||||
|
|
||||||
|
const resolvedThinkingDefaultRaw =
|
||||||
|
readStringParam(targetAgentConfig?.subagents ?? {}, "thinking") ??
|
||||||
|
readStringParam(cfg.agents?.defaults?.subagents ?? {}, "thinking");
|
||||||
|
|
||||||
let thinkingOverride: string | undefined;
|
let thinkingOverride: string | undefined;
|
||||||
if (thinkingOverrideRaw) {
|
const thinkingCandidateRaw = thinkingOverrideRaw || resolvedThinkingDefaultRaw;
|
||||||
const normalized = normalizeThinkLevel(thinkingOverrideRaw);
|
if (thinkingCandidateRaw) {
|
||||||
|
const normalized = normalizeThinkLevel(thinkingCandidateRaw);
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
const { provider, model } = splitModelRef(resolvedModel);
|
const { provider, model } = splitModelRef(resolvedModel);
|
||||||
const hint = formatThinkingLevels(provider, model);
|
const hint = formatThinkingLevels(provider, model);
|
||||||
return jsonResult({
|
return jsonResult({
|
||||||
status: "error",
|
status: "error",
|
||||||
error: `Invalid thinking level "${thinkingOverrideRaw}". Use one of: ${hint}.`,
|
error: `Invalid thinking level "${thinkingCandidateRaw}". Use one of: ${hint}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
thinkingOverride = normalized;
|
thinkingOverride = normalized;
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,8 @@ export type AgentDefaultsConfig = {
|
||||||
archiveAfterMinutes?: number;
|
archiveAfterMinutes?: number;
|
||||||
/** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */
|
/** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */
|
||||||
model?: string | { primary?: string; fallbacks?: string[] };
|
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. */
|
/** Optional sandbox settings for non-main sessions. */
|
||||||
sandbox?: {
|
sandbox?: {
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ export const AgentDefaultsSchema = z
|
||||||
.strict(),
|
.strict(),
|
||||||
])
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
|
thinking: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,7 @@ export const AgentEntrySchema = z
|
||||||
.strict(),
|
.strict(),
|
||||||
])
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
|
thinking: z.string().optional(),
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue