From ee2fab7ceb533c78146433d3e4e60854272a929c Mon Sep 17 00:00:00 2001 From: kris Date: Tue, 31 Mar 2026 23:36:12 +0800 Subject: [PATCH] feat: apply per-chat master-agent execution config --- src/lib/boss-master-agent.ts | 34 ++++++++-- tests/master-agent-config-resolution.test.ts | 65 ++++++++++++++++++++ 2 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 tests/master-agent-config-resolution.test.ts diff --git a/src/lib/boss-master-agent.ts b/src/lib/boss-master-agent.ts index 4477d06..5e16b8d 100644 --- a/src/lib/boss-master-agent.ts +++ b/src/lib/boss-master-agent.ts @@ -38,6 +38,28 @@ type QueuedMasterAgentReplyEnvelope = { }; }; +export async function resolveMasterAgentExecutionConfig(projectId: string) { + const runtime = await getMasterAgentRuntimeAccount(); + if (!runtime?.account) { + throw new Error("NO_MASTER_AGENT_RUNTIME_ACCOUNT"); + } + + const agentControls = await getProjectAgentControls(projectId); + const reasoningEffort = + agentControls?.reasoningEffortOverride || + (runtime.account as typeof runtime.account & { reasoningEffort?: ReasoningEffort }).reasoningEffort || + "medium"; + + return { + runtime, + account: runtime.account, + agentControls, + provider: runtime.account.provider, + model: agentControls?.modelOverride || runtime.account.model || "gpt-5.4", + reasoningEffort, + }; +} + function buildAgentControlsDigest(agentControls?: ProjectAgentControls | null) { if (!agentControls) { return "当前对话覆盖:无"; @@ -1129,7 +1151,6 @@ export async function replyToMasterAgentUserMessage(params: { mode?: "wait" | "enqueue"; }) { const runtime = await getMasterAgentRuntimeAccount(); - const agentControls = await getProjectAgentControls("master-agent"); if (!runtime?.account) { await appendMasterAgentSystemReply( @@ -1138,6 +1159,9 @@ export async function replyToMasterAgentUserMessage(params: { return { ok: false as const, reason: "NO_AI_ACCOUNT" }; } + const executionConfig = await resolveMasterAgentExecutionConfig("master-agent"); + const agentControls = executionConfig.agentControls; + if (params.mode === "enqueue") { if (runtime.account.provider === "master_codex_node") { const state = await readState(); @@ -1221,8 +1245,8 @@ export async function replyToMasterAgentUserMessage(params: { requestedByAccount: params.requestedByAccount, currentSessionExpiresAt: params.currentSessionExpiresAt, apiKey: runtime.account.apiKey, - model: agentControls?.modelOverride || runtime.account.model || "gpt-5.4", - reasoningEffort: agentControls?.reasoningEffortOverride || "medium", + model: executionConfig.model, + reasoningEffort: executionConfig.reasoningEffort, agentControls, }); } @@ -1338,8 +1362,8 @@ export async function replyToMasterAgentUserMessage(params: { try { const generated = await generateOpenAiReply({ apiKey: runtime.account.apiKey, - model: agentControls?.modelOverride || runtime.account.model || "gpt-5.4", - reasoningEffort: agentControls?.reasoningEffortOverride || "medium", + model: executionConfig.model, + reasoningEffort: executionConfig.reasoningEffort, requestText: params.requestText, currentSessionExpiresAt: params.currentSessionExpiresAt, agentControls, diff --git a/tests/master-agent-config-resolution.test.ts b/tests/master-agent-config-resolution.test.ts new file mode 100644 index 0000000..5b2eb4b --- /dev/null +++ b/tests/master-agent-config-resolution.test.ts @@ -0,0 +1,65 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import { mkdtemp, rm } from "node:fs/promises"; + +let runtimeRoot = ""; +let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"]; +let updateProjectAgentControls: (typeof import("../src/lib/boss-data"))["updateProjectAgentControls"]; +let resolveMasterAgentExecutionConfig: (typeof import("../src/lib/boss-master-agent"))["resolveMasterAgentExecutionConfig"]; + +async function setup() { + if (runtimeRoot) return; + + runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-config-")); + process.env.BOSS_RUNTIME_ROOT = runtimeRoot; + process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); + + const [data, masterAgent] = await Promise.all([ + import("../src/lib/boss-data.ts"), + import("../src/lib/boss-master-agent.ts"), + ]); + + saveAiAccount = data.saveAiAccount; + updateProjectAgentControls = data.updateProjectAgentControls; + resolveMasterAgentExecutionConfig = masterAgent.resolveMasterAgentExecutionConfig; +} + +test.after(async () => { + if (runtimeRoot) { + await rm(runtimeRoot, { recursive: true, force: true }); + } +}); + +test("当前对话 override 优先于主控账号默认值", async () => { + await setup(); + + await saveAiAccount({ + accountId: "master-codex-primary", + label: "主 GPT", + role: "primary", + provider: "master_codex_node", + displayName: "Mac 上的 Master Codex Node", + nodeId: "local-codex-node", + nodeLabel: "本机 Codex", + model: "gpt-4.1-mini", + enabled: true, + setActive: true, + loginStatusNote: "通过绑定的 Master Codex Node 对话。", + }); + + await updateProjectAgentControls("master-agent", { + modelOverride: "gpt-5.4", + reasoningEffortOverride: "high", + }); + + assert.equal(typeof resolveMasterAgentExecutionConfig, "function"); + + const resolved = await resolveMasterAgentExecutionConfig("master-agent"); + + assert.equal(resolved.model, "gpt-5.4"); + assert.equal(resolved.reasoningEffort, "high"); + assert.equal(resolved.account.accountId, "master-codex-primary"); + assert.equal(resolved.account.model, "gpt-4.1-mini"); +});