feat(FN-2662): enforce model override fallback hierarchy
- Fix triage planning model resolution to fall back through project/global planning settings and default overrides - Fix reviewer model selection to honor validator-specific settings before default provider/model overrides - Update merger model resolution to apply default override fallback and align shared task setting types/executor flow - Add regression coverage for triage, reviewer, and merger fallback behavior and update settings hierarchy documentation
This commit is contained in:
@@ -304,6 +304,12 @@ Fusion uses a dual-scope model settings system with five lanes. Global settings
|
|||||||
5. Global `defaultProvider` + `defaultModelId`
|
5. Global `defaultProvider` + `defaultModelId`
|
||||||
6. Automatic provider/model resolution
|
6. Automatic provider/model resolution
|
||||||
|
|
||||||
|
### Merger model
|
||||||
|
|
||||||
|
1. Project `defaultProviderOverride` + `defaultModelIdOverride`
|
||||||
|
2. Global `defaultProvider` + `defaultModelId`
|
||||||
|
3. Automatic provider/model resolution
|
||||||
|
|
||||||
### Title summarization model
|
### Title summarization model
|
||||||
|
|
||||||
1. Project `titleSummarizerProvider` + `titleSummarizerModelId`
|
1. Project `titleSummarizerProvider` + `titleSummarizerModelId`
|
||||||
@@ -313,7 +319,7 @@ Fusion uses a dual-scope model settings system with five lanes. Global settings
|
|||||||
5. Global `defaultProvider` + `defaultModelId`
|
5. Global `defaultProvider` + `defaultModelId`
|
||||||
6. Automatic provider/model resolution
|
6. Automatic provider/model resolution
|
||||||
|
|
||||||
> **Note:** Runtime fallback precedence logic is implemented in engine and dashboard routes (FN-1711). The hierarchy above reflects the full schema contracts added in FN-1710.
|
> **Note:** Runtime fallback precedence logic is implemented in engine and dashboard routes. The hierarchies above reflect current runtime behavior.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1352,9 +1352,7 @@ export interface ProjectSettings {
|
|||||||
/** Project-level override for the base default AI model provider.
|
/** Project-level override for the base default AI model provider.
|
||||||
* When set, this overrides the global `defaultProvider`/`defaultModelId` baseline
|
* When set, this overrides the global `defaultProvider`/`defaultModelId` baseline
|
||||||
* for all lanes that don't have their own explicit project override.
|
* for all lanes that don't have their own explicit project override.
|
||||||
* Must be set together with `defaultModelIdOverride`.
|
* Must be set together with `defaultModelIdOverride`. */
|
||||||
* NOTE: This is a schema/persistence contract only. Runtime fallback precedence
|
|
||||||
* logic is deferred to FN-1711. */
|
|
||||||
defaultProviderOverride?: string;
|
defaultProviderOverride?: string;
|
||||||
/** Project-level override for the base default AI model ID.
|
/** Project-level override for the base default AI model ID.
|
||||||
* Must be set together with `defaultProviderOverride`. */
|
* Must be set together with `defaultProviderOverride`. */
|
||||||
@@ -1364,9 +1362,7 @@ export interface ProjectSettings {
|
|||||||
* Must be set together with `executionModelId`. Falls back to
|
* Must be set together with `executionModelId`. Falls back to
|
||||||
* `executionGlobalProvider`/`executionGlobalModelId` or
|
* `executionGlobalProvider`/`executionGlobalModelId` or
|
||||||
* `defaultProviderOverride`/`defaultModelIdOverride` or
|
* `defaultProviderOverride`/`defaultModelIdOverride` or
|
||||||
* `defaultProvider`/`defaultModelId` when undefined.
|
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||||
* NOTE: This is a schema/persistence contract only. Runtime fallback precedence
|
|
||||||
* logic is deferred to FN-1711. */
|
|
||||||
executionProvider?: string;
|
executionProvider?: string;
|
||||||
/** Project-level AI model ID for task execution.
|
/** Project-level AI model ID for task execution.
|
||||||
* Must be set together with `executionProvider`. */
|
* Must be set together with `executionProvider`. */
|
||||||
|
|||||||
@@ -6331,7 +6331,7 @@ describe("aiMergeTask — in-merge verification fix", () => {
|
|||||||
return Buffer.from("");
|
return Buffer.from("");
|
||||||
});
|
});
|
||||||
|
|
||||||
mockedCreateFnAgent.mockImplementation(async (opts: any) => {
|
mockedCreateFnAgent.mockImplementation(async (_opts: any) => {
|
||||||
return {
|
return {
|
||||||
session: {
|
session: {
|
||||||
prompt: vi.fn().mockResolvedValue(undefined),
|
prompt: vi.fn().mockResolvedValue(undefined),
|
||||||
@@ -6362,6 +6362,61 @@ describe("aiMergeTask — in-merge verification fix", () => {
|
|||||||
expect(fixAgentCall[0].defaultModelId).toBe("claude-sonnet-4-5");
|
expect(fixAgentCall[0].defaultModelId).toBe("claude-sonnet-4-5");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses project default override for merge agent model", async () => {
|
||||||
|
mockedExecSync.mockImplementation((cmd: any) => {
|
||||||
|
const cmdStr = String(cmd);
|
||||||
|
if (cmdStr.includes("rev-parse --verify")) return Buffer.from("abc123");
|
||||||
|
if (cmdStr.includes("git log")) return "- feat: something" as any;
|
||||||
|
if (cmdStr.includes("merge-base")) return Buffer.from("abc123");
|
||||||
|
if (cmdStr.includes("git diff") && cmdStr.includes("--stat")) return "1 file changed" as any;
|
||||||
|
if (cmdStr.includes("merge --squash")) return Buffer.from("");
|
||||||
|
if (cmdStr.includes("vitest run")) {
|
||||||
|
const err = new Error("Test failed") as any;
|
||||||
|
err.status = 1;
|
||||||
|
err.stdout = "";
|
||||||
|
err.stderr = "";
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (cmdStr.includes("diff --cached --quiet")) return "1" as any;
|
||||||
|
if (cmdStr.includes("diff --cached")) return "" as any;
|
||||||
|
if (cmdStr.includes("branch -d") || cmdStr.includes("branch -D")) return Buffer.from("");
|
||||||
|
if (cmdStr.includes("worktree remove")) return Buffer.from("");
|
||||||
|
if (cmdStr === "git rev-parse HEAD" || cmdStr.startsWith("git rev-parse HEAD ")) return "mergedcommit123";
|
||||||
|
return Buffer.from("");
|
||||||
|
});
|
||||||
|
|
||||||
|
mockedCreateFnAgent.mockImplementation(async (_opts: any) => {
|
||||||
|
return {
|
||||||
|
session: {
|
||||||
|
prompt: vi.fn().mockResolvedValue(undefined),
|
||||||
|
dispose: vi.fn(),
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createMockStore(
|
||||||
|
{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050" },
|
||||||
|
[{ id: "FN-050", worktree: "/tmp/root/.worktrees/KB-050", column: "in-review" } as Task],
|
||||||
|
);
|
||||||
|
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||||
|
...DEFAULT_SETTINGS,
|
||||||
|
testCommand: "vitest run",
|
||||||
|
verificationFixRetries: 0,
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
defaultProviderOverride: "openai",
|
||||||
|
defaultModelIdOverride: "gpt-4o",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(aiMergeTask(store, "/tmp/root", "FN-050")).rejects.toMatchObject({
|
||||||
|
name: "VerificationError",
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergeAgentCall = mockedCreateFnAgent.mock.calls[0];
|
||||||
|
expect(mergeAgentCall[0].defaultProvider).toBe("openai");
|
||||||
|
expect(mergeAgentCall[0].defaultModelId).toBe("gpt-4o");
|
||||||
|
});
|
||||||
|
|
||||||
it("fix agent session is disposed", async () => {
|
it("fix agent session is disposed", async () => {
|
||||||
const disposeMock = vi.fn();
|
const disposeMock = vi.fn();
|
||||||
|
|
||||||
|
|||||||
@@ -425,6 +425,51 @@ describe("reviewStep — validator model overrides", () => {
|
|||||||
expect(opts.defaultModelId).toBe("gemini-2.5");
|
expect(opts.defaultModelId).toBe("gemini-2.5");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses project default override when validator lanes are absent", async () => {
|
||||||
|
mockedCreateFnAgent.mockResolvedValue(
|
||||||
|
createMockSession("### Verdict: APPROVE\n### Summary\nLooks good."),
|
||||||
|
);
|
||||||
|
|
||||||
|
await reviewStep(
|
||||||
|
"/tmp/worktree", "FN-100", 1, "Test Step", "plan", "# prompt",
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
projectDefaultOverrideProvider: "openai",
|
||||||
|
projectDefaultOverrideModelId: "gpt-4o",
|
||||||
|
// No validator lanes set
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedCreateFnAgent).toHaveBeenCalledTimes(1);
|
||||||
|
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||||
|
expect(opts.defaultProvider).toBe("openai");
|
||||||
|
expect(opts.defaultModelId).toBe("gpt-4o");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls through to execution default when project default override is incomplete", async () => {
|
||||||
|
mockedCreateFnAgent.mockResolvedValue(
|
||||||
|
createMockSession("### Verdict: APPROVE\n### Summary\nLooks good."),
|
||||||
|
);
|
||||||
|
|
||||||
|
await reviewStep(
|
||||||
|
"/tmp/worktree", "FN-100", 1, "Test Step", "plan", "# prompt",
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
projectDefaultOverrideProvider: "openai",
|
||||||
|
// projectDefaultOverrideModelId intentionally omitted
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedCreateFnAgent).toHaveBeenCalledTimes(1);
|
||||||
|
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||||
|
expect(opts.defaultProvider).toBe("anthropic");
|
||||||
|
expect(opts.defaultModelId).toBe("claude-sonnet-4-5");
|
||||||
|
});
|
||||||
|
|
||||||
it("falls back to execution default when no validator lanes are set", async () => {
|
it("falls back to execution default when no validator lanes are set", async () => {
|
||||||
mockedCreateFnAgent.mockResolvedValue(
|
mockedCreateFnAgent.mockResolvedValue(
|
||||||
createMockSession("### Verdict: APPROVE\n### Summary\nLooks good."),
|
createMockSession("### Verdict: APPROVE\n### Summary\nLooks good."),
|
||||||
|
|||||||
@@ -2195,9 +2195,139 @@ describe("taskCreate tool model inheritance", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to global defaults when neither task nor settings have planning model", async () => {
|
it("uses project default override when planning lanes are absent", async () => {
|
||||||
const task = {
|
const task = {
|
||||||
id: "FN-402",
|
id: "FN-402",
|
||||||
|
description: "Test fallback to project default override",
|
||||||
|
column: "triage",
|
||||||
|
dependencies: [],
|
||||||
|
steps: [],
|
||||||
|
currentStep: 0,
|
||||||
|
log: [],
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
} as unknown as Task;
|
||||||
|
|
||||||
|
const mockDispose = vi.fn();
|
||||||
|
const mockPrompt = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const mockGetLeafId = vi.fn().mockReturnValue(null);
|
||||||
|
const mockNavigateTree = vi.fn();
|
||||||
|
|
||||||
|
const store = createMockStore({
|
||||||
|
getTask: vi.fn().mockResolvedValue({ ...task, attachments: [] }),
|
||||||
|
getSettings: vi.fn().mockResolvedValue({
|
||||||
|
maxConcurrent: 2,
|
||||||
|
maxWorktrees: 4,
|
||||||
|
pollIntervalMs: 10000,
|
||||||
|
groupOverlappingFiles: false,
|
||||||
|
autoMerge: true,
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
defaultProviderOverride: "openai",
|
||||||
|
defaultModelIdOverride: "gpt-4o",
|
||||||
|
// No planningProvider/planningModelId set
|
||||||
|
} as Settings),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockCreateFnAgent.mockResolvedValue({
|
||||||
|
session: {
|
||||||
|
prompt: mockPrompt,
|
||||||
|
dispose: mockDispose,
|
||||||
|
sessionManager: {
|
||||||
|
getLeafId: mockGetLeafId,
|
||||||
|
navigateTree: mockNavigateTree,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { promptWithFallback } = await import("../pi.js");
|
||||||
|
(promptWithFallback as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
||||||
|
new Error("test stop after model check"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const processor = new TriageProcessor(store, "/test/root", {
|
||||||
|
pollIntervalMs: 100_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await processor.specifyTask(task);
|
||||||
|
|
||||||
|
// Should use project default override when planning lanes are absent
|
||||||
|
expect(mockCreateFnAgent).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
defaultProvider: "openai",
|
||||||
|
defaultModelId: "gpt-4o",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls through to global default when project default override is incomplete", async () => {
|
||||||
|
const task = {
|
||||||
|
id: "FN-403",
|
||||||
|
description: "Test fallback when project default override is incomplete",
|
||||||
|
column: "triage",
|
||||||
|
dependencies: [],
|
||||||
|
steps: [],
|
||||||
|
currentStep: 0,
|
||||||
|
log: [],
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
} as unknown as Task;
|
||||||
|
|
||||||
|
const mockDispose = vi.fn();
|
||||||
|
const mockPrompt = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const mockGetLeafId = vi.fn().mockReturnValue(null);
|
||||||
|
const mockNavigateTree = vi.fn();
|
||||||
|
|
||||||
|
const store = createMockStore({
|
||||||
|
getTask: vi.fn().mockResolvedValue({ ...task, attachments: [] }),
|
||||||
|
getSettings: vi.fn().mockResolvedValue({
|
||||||
|
maxConcurrent: 2,
|
||||||
|
maxWorktrees: 4,
|
||||||
|
pollIntervalMs: 10000,
|
||||||
|
groupOverlappingFiles: false,
|
||||||
|
autoMerge: true,
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
defaultProviderOverride: "openai",
|
||||||
|
// defaultModelIdOverride intentionally omitted
|
||||||
|
// No planningProvider/planningModelId set
|
||||||
|
} as Settings),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockCreateFnAgent.mockResolvedValue({
|
||||||
|
session: {
|
||||||
|
prompt: mockPrompt,
|
||||||
|
dispose: mockDispose,
|
||||||
|
sessionManager: {
|
||||||
|
getLeafId: mockGetLeafId,
|
||||||
|
navigateTree: mockNavigateTree,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { promptWithFallback } = await import("../pi.js");
|
||||||
|
(promptWithFallback as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
||||||
|
new Error("test stop after model check"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const processor = new TriageProcessor(store, "/test/root", {
|
||||||
|
pollIntervalMs: 100_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await processor.specifyTask(task);
|
||||||
|
|
||||||
|
// Incomplete override should fall through to global defaults
|
||||||
|
expect(mockCreateFnAgent).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to global defaults when neither task nor settings have planning model", async () => {
|
||||||
|
const task = {
|
||||||
|
id: "FN-404",
|
||||||
description: "Test fallback to global defaults",
|
description: "Test fallback to global defaults",
|
||||||
column: "triage",
|
column: "triage",
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
|||||||
@@ -2908,6 +2908,9 @@ export class TaskExecutor {
|
|||||||
// Global validator lane
|
// Global validator lane
|
||||||
globalValidatorProvider: settings.validatorGlobalProvider,
|
globalValidatorProvider: settings.validatorGlobalProvider,
|
||||||
globalValidatorModelId: settings.validatorGlobalModelId,
|
globalValidatorModelId: settings.validatorGlobalModelId,
|
||||||
|
// Project-level default override (fallback before execution defaults)
|
||||||
|
projectDefaultOverrideProvider: settings.defaultProviderOverride,
|
||||||
|
projectDefaultOverrideModelId: settings.defaultModelIdOverride,
|
||||||
store,
|
store,
|
||||||
taskId,
|
taskId,
|
||||||
task: detail,
|
task: detail,
|
||||||
|
|||||||
@@ -922,8 +922,12 @@ A merge has been applied and the verification command failed. Your job is to fix
|
|||||||
onThinking: logger.onThinking,
|
onThinking: logger.onThinking,
|
||||||
onToolStart: logger.onToolStart,
|
onToolStart: logger.onToolStart,
|
||||||
onToolEnd: logger.onToolEnd,
|
onToolEnd: logger.onToolEnd,
|
||||||
defaultProvider: settings.defaultProvider,
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
defaultModelId: settings.defaultModelId,
|
? settings.defaultProviderOverride
|
||||||
|
: settings.defaultProvider,
|
||||||
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
|
? settings.defaultModelIdOverride
|
||||||
|
: settings.defaultModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||||
@@ -1808,8 +1812,12 @@ You are assisting with a paused \`git pull --rebase\`.
|
|||||||
onThinking: agentLogger.onThinking,
|
onThinking: agentLogger.onThinking,
|
||||||
onToolStart: agentLogger.onToolStart,
|
onToolStart: agentLogger.onToolStart,
|
||||||
onToolEnd: agentLogger.onToolEnd,
|
onToolEnd: agentLogger.onToolEnd,
|
||||||
defaultProvider: settings.defaultProvider,
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
defaultModelId: settings.defaultModelId,
|
? settings.defaultProviderOverride
|
||||||
|
: settings.defaultProvider,
|
||||||
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
|
? settings.defaultModelIdOverride
|
||||||
|
: settings.defaultModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3490,8 +3498,12 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
|||||||
onThinking: agentLogger.onThinking,
|
onThinking: agentLogger.onThinking,
|
||||||
onToolStart: agentLogger.onToolStart,
|
onToolStart: agentLogger.onToolStart,
|
||||||
onToolEnd: agentLogger.onToolEnd,
|
onToolEnd: agentLogger.onToolEnd,
|
||||||
defaultProvider: settings.defaultProvider,
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
defaultModelId: settings.defaultModelId,
|
? settings.defaultProviderOverride
|
||||||
|
: settings.defaultProvider,
|
||||||
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
|
? settings.defaultModelIdOverride
|
||||||
|
: settings.defaultModelId,
|
||||||
defaultThinkingLevel: settings.defaultThinkingLevel,
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
||||||
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
||||||
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
...(skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}),
|
||||||
|
|||||||
@@ -201,10 +201,14 @@ export interface ReviewOptions {
|
|||||||
projectValidatorProvider?: string;
|
projectValidatorProvider?: string;
|
||||||
/** Project-level validator model ID override. Takes precedence over global validator lane. */
|
/** Project-level validator model ID override. Takes precedence over global validator lane. */
|
||||||
projectValidatorModelId?: string;
|
projectValidatorModelId?: string;
|
||||||
/** Global validator lane provider. Takes precedence over execution defaults. */
|
/** Global validator lane provider. Takes precedence over project default override + execution defaults. */
|
||||||
globalValidatorProvider?: string;
|
globalValidatorProvider?: string;
|
||||||
/** Global validator lane model ID. Takes precedence over execution defaults. */
|
/** Global validator lane model ID. Takes precedence over project default override + execution defaults. */
|
||||||
globalValidatorModelId?: string;
|
globalValidatorModelId?: string;
|
||||||
|
/** Project-level default provider override, used when validator lanes are absent. */
|
||||||
|
projectDefaultOverrideProvider?: string;
|
||||||
|
/** Project-level default model override, used when validator lanes are absent. */
|
||||||
|
projectDefaultOverrideModelId?: string;
|
||||||
/** Fallback model provider used when the primary reviewer model hits a retryable provider-side error. */
|
/** Fallback model provider used when the primary reviewer model hits a retryable provider-side error. */
|
||||||
fallbackProvider?: string;
|
fallbackProvider?: string;
|
||||||
/** Fallback model ID used with `fallbackProvider`. */
|
/** Fallback model ID used with `fallbackProvider`. */
|
||||||
@@ -269,21 +273,26 @@ export async function reviewStep(
|
|||||||
// 1. Task-level validator override pair (taskValidatorProvider + taskValidatorModelId)
|
// 1. Task-level validator override pair (taskValidatorProvider + taskValidatorModelId)
|
||||||
// 2. Project-level validator override pair (projectValidatorProvider + projectValidatorModelId)
|
// 2. Project-level validator override pair (projectValidatorProvider + projectValidatorModelId)
|
||||||
// 3. Global validator lane pair (globalValidatorProvider + globalValidatorModelId)
|
// 3. Global validator lane pair (globalValidatorProvider + globalValidatorModelId)
|
||||||
// 4. Execution default pair (defaultProvider + defaultModelId)
|
// 4. Project default override pair (projectDefaultOverrideProvider + projectDefaultOverrideModelId)
|
||||||
|
// 5. Execution default pair (defaultProvider + defaultModelId)
|
||||||
const validatorProvider = options.taskValidatorProvider && options.taskValidatorModelId
|
const validatorProvider = options.taskValidatorProvider && options.taskValidatorModelId
|
||||||
? options.taskValidatorProvider
|
? options.taskValidatorProvider
|
||||||
: (options.projectValidatorProvider && options.projectValidatorModelId
|
: (options.projectValidatorProvider && options.projectValidatorModelId
|
||||||
? options.projectValidatorProvider
|
? options.projectValidatorProvider
|
||||||
: (options.globalValidatorProvider && options.globalValidatorModelId
|
: (options.globalValidatorProvider && options.globalValidatorModelId
|
||||||
? options.globalValidatorProvider
|
? options.globalValidatorProvider
|
||||||
: options.defaultProvider));
|
: (options.projectDefaultOverrideProvider && options.projectDefaultOverrideModelId
|
||||||
|
? options.projectDefaultOverrideProvider
|
||||||
|
: options.defaultProvider)));
|
||||||
const validatorModelId = options.taskValidatorProvider && options.taskValidatorModelId
|
const validatorModelId = options.taskValidatorProvider && options.taskValidatorModelId
|
||||||
? options.taskValidatorModelId
|
? options.taskValidatorModelId
|
||||||
: (options.projectValidatorProvider && options.projectValidatorModelId
|
: (options.projectValidatorProvider && options.projectValidatorModelId
|
||||||
? options.projectValidatorModelId
|
? options.projectValidatorModelId
|
||||||
: (options.globalValidatorProvider && options.globalValidatorModelId
|
: (options.globalValidatorProvider && options.globalValidatorModelId
|
||||||
? options.globalValidatorModelId
|
? options.globalValidatorModelId
|
||||||
: options.defaultModelId));
|
: (options.projectDefaultOverrideProvider && options.projectDefaultOverrideModelId
|
||||||
|
? options.projectDefaultOverrideModelId
|
||||||
|
: options.defaultModelId)));
|
||||||
|
|
||||||
// Resolve validator fallback using lane hierarchy:
|
// Resolve validator fallback using lane hierarchy:
|
||||||
// 1. Project-level validator fallback (projectValidatorFallbackProvider + projectValidatorFallbackModelId)
|
// 1. Project-level validator fallback (projectValidatorFallbackProvider + projectValidatorFallbackModelId)
|
||||||
|
|||||||
@@ -909,23 +909,28 @@ export class TriageProcessor {
|
|||||||
onToolEnd: agentLogger.onToolEnd,
|
onToolEnd: agentLogger.onToolEnd,
|
||||||
// Resolve planning model using canonical lane hierarchy:
|
// Resolve planning model using canonical lane hierarchy:
|
||||||
// 1. Task planning override pair (planningModelProvider + planningModelId)
|
// 1. Task planning override pair (planningModelProvider + planningModelId)
|
||||||
// 2. Project planning override pair (planningProvider + planningModelId)
|
// 2. Project planning lane pair (planningProvider + planningModelId)
|
||||||
// 3. Global planning lane pair (planningGlobalProvider + planningGlobalModelId)
|
// 3. Global planning lane pair (planningGlobalProvider + planningGlobalModelId)
|
||||||
// 4. Default pair (defaultProvider + defaultModelId)
|
// 4. Project default override pair (defaultProviderOverride + defaultModelIdOverride)
|
||||||
|
// 5. Global default pair (defaultProvider + defaultModelId)
|
||||||
defaultProvider: task.planningModelProvider && task.planningModelId
|
defaultProvider: task.planningModelProvider && task.planningModelId
|
||||||
? task.planningModelProvider
|
? task.planningModelProvider
|
||||||
: (settings.planningProvider && settings.planningModelId
|
: (settings.planningProvider && settings.planningModelId
|
||||||
? settings.planningProvider
|
? settings.planningProvider
|
||||||
: (settings.planningGlobalProvider && settings.planningGlobalModelId
|
: (settings.planningGlobalProvider && settings.planningGlobalModelId
|
||||||
? settings.planningGlobalProvider
|
? settings.planningGlobalProvider
|
||||||
: settings.defaultProvider)),
|
: (settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
|
? settings.defaultProviderOverride
|
||||||
|
: settings.defaultProvider))),
|
||||||
defaultModelId: task.planningModelProvider && task.planningModelId
|
defaultModelId: task.planningModelProvider && task.planningModelId
|
||||||
? task.planningModelId
|
? task.planningModelId
|
||||||
: (settings.planningProvider && settings.planningModelId
|
: (settings.planningProvider && settings.planningModelId
|
||||||
? settings.planningModelId
|
? settings.planningModelId
|
||||||
: (settings.planningGlobalProvider && settings.planningGlobalModelId
|
: (settings.planningGlobalProvider && settings.planningGlobalModelId
|
||||||
? settings.planningGlobalModelId
|
? settings.planningGlobalModelId
|
||||||
: settings.defaultModelId)),
|
: (settings.defaultProviderOverride && settings.defaultModelIdOverride
|
||||||
|
? settings.defaultModelIdOverride
|
||||||
|
: settings.defaultModelId))),
|
||||||
fallbackProvider: settings.planningFallbackProvider && settings.planningFallbackModelId
|
fallbackProvider: settings.planningFallbackProvider && settings.planningFallbackModelId
|
||||||
? settings.planningFallbackProvider
|
? settings.planningFallbackProvider
|
||||||
: settings.fallbackProvider,
|
: settings.fallbackProvider,
|
||||||
@@ -1711,6 +1716,9 @@ export class TriageProcessor {
|
|||||||
// Global validator lane
|
// Global validator lane
|
||||||
globalValidatorProvider: currentSettings.validatorGlobalProvider,
|
globalValidatorProvider: currentSettings.validatorGlobalProvider,
|
||||||
globalValidatorModelId: currentSettings.validatorGlobalModelId,
|
globalValidatorModelId: currentSettings.validatorGlobalModelId,
|
||||||
|
// Project-level default override (fallback before execution defaults)
|
||||||
|
projectDefaultOverrideProvider: currentSettings.defaultProviderOverride,
|
||||||
|
projectDefaultOverrideModelId: currentSettings.defaultModelIdOverride,
|
||||||
defaultThinkingLevel: currentSettings.defaultThinkingLevel,
|
defaultThinkingLevel: currentSettings.defaultThinkingLevel,
|
||||||
store,
|
store,
|
||||||
taskId,
|
taskId,
|
||||||
|
|||||||
Reference in New Issue
Block a user