feat(FN-1710): add dual-scope model settings lane fields
- Add new model settings lane fields to support dual-scope configuration (global + per-project) - Add dual-scope provider/model override fields for executor, validator, and planning models - Add schema entries in settings-schema.ts with validation constraints - Add backward-compat regression tests for legacy settings shapes - Add mixed-shape tests covering global/project scopes in same config - Update settings-reference.md documentation with new fields
This commit is contained in:
@@ -377,4 +377,128 @@ describe("GlobalSettingsStore", () => {
|
||||
expect(afterInvalidate.themeMode).toBe("system");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Global Lane Model Settings (FN-1710) ──────────────────────────
|
||||
|
||||
describe("global lane model settings", () => {
|
||||
it("all *Global* lane fields default to undefined", async () => {
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBeUndefined();
|
||||
expect(settings.executionGlobalModelId).toBeUndefined();
|
||||
expect(settings.planningGlobalProvider).toBeUndefined();
|
||||
expect(settings.planningGlobalModelId).toBeUndefined();
|
||||
expect(settings.validatorGlobalProvider).toBeUndefined();
|
||||
expect(settings.validatorGlobalModelId).toBeUndefined();
|
||||
expect(settings.titleSummarizerGlobalProvider).toBeUndefined();
|
||||
expect(settings.titleSummarizerGlobalModelId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("persists executionGlobalProvider/executionGlobalModelId", async () => {
|
||||
await store.updateSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBe("anthropic");
|
||||
expect(settings.executionGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
|
||||
// Verify persistence
|
||||
const raw = JSON.parse(await readFile(join(dir, "settings.json"), "utf-8"));
|
||||
expect(raw.executionGlobalProvider).toBe("anthropic");
|
||||
expect(raw.executionGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("persists planningGlobalProvider/planningGlobalModelId", async () => {
|
||||
await store.updateSettings({
|
||||
planningGlobalProvider: "google",
|
||||
planningGlobalModelId: "gemini-2.5-pro",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.planningGlobalProvider).toBe("google");
|
||||
expect(settings.planningGlobalModelId).toBe("gemini-2.5-pro");
|
||||
});
|
||||
|
||||
it("persists validatorGlobalProvider/validatorGlobalModelId", async () => {
|
||||
await store.updateSettings({
|
||||
validatorGlobalProvider: "openai",
|
||||
validatorGlobalModelId: "gpt-4o",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.validatorGlobalProvider).toBe("openai");
|
||||
expect(settings.validatorGlobalModelId).toBe("gpt-4o");
|
||||
});
|
||||
|
||||
it("persists titleSummarizerGlobalProvider/titleSummarizerGlobalModelId", async () => {
|
||||
await store.updateSettings({
|
||||
titleSummarizerGlobalProvider: "anthropic",
|
||||
titleSummarizerGlobalModelId: "claude-haiku",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.titleSummarizerGlobalProvider).toBe("anthropic");
|
||||
expect(settings.titleSummarizerGlobalModelId).toBe("claude-haiku");
|
||||
});
|
||||
|
||||
it("persists all global lane fields together", async () => {
|
||||
await store.updateSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-opus-4",
|
||||
planningGlobalProvider: "google",
|
||||
planningGlobalModelId: "gemini-2.5-pro",
|
||||
validatorGlobalProvider: "openai",
|
||||
validatorGlobalModelId: "gpt-4-turbo",
|
||||
titleSummarizerGlobalProvider: "anthropic",
|
||||
titleSummarizerGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBe("anthropic");
|
||||
expect(settings.executionGlobalModelId).toBe("claude-opus-4");
|
||||
expect(settings.planningGlobalProvider).toBe("google");
|
||||
expect(settings.planningGlobalModelId).toBe("gemini-2.5-pro");
|
||||
expect(settings.validatorGlobalProvider).toBe("openai");
|
||||
expect(settings.validatorGlobalModelId).toBe("gpt-4-turbo");
|
||||
expect(settings.titleSummarizerGlobalProvider).toBe("anthropic");
|
||||
expect(settings.titleSummarizerGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("can clear global lane fields with null", async () => {
|
||||
await store.updateSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
// Clear them
|
||||
// @ts-expect-error - null is intentionally used to clear field (null-as-delete)
|
||||
await store.updateSettings({ executionGlobalProvider: null });
|
||||
// @ts-expect-error - null is intentionally used to clear field (null-as-delete)
|
||||
await store.updateSettings({ executionGlobalModelId: null });
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBeUndefined();
|
||||
expect(settings.executionGlobalModelId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("merges global lane fields without losing other fields", async () => {
|
||||
await store.updateSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
await store.updateSettings({
|
||||
planningGlobalProvider: "google",
|
||||
planningGlobalModelId: "gemini-2.5-pro",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
// Both should be present
|
||||
expect(settings.executionGlobalProvider).toBe("anthropic");
|
||||
expect(settings.executionGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
expect(settings.planningGlobalProvider).toBe("google");
|
||||
expect(settings.planningGlobalModelId).toBe("gemini-2.5-pro");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,15 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
||||
favoriteModels: undefined,
|
||||
openrouterModelSync: true,
|
||||
modelOnboardingComplete: undefined,
|
||||
// Global baseline lanes for per-role model selection
|
||||
executionGlobalProvider: undefined,
|
||||
executionGlobalModelId: undefined,
|
||||
planningGlobalProvider: undefined,
|
||||
planningGlobalModelId: undefined,
|
||||
validatorGlobalProvider: undefined,
|
||||
validatorGlobalModelId: undefined,
|
||||
titleSummarizerGlobalProvider: undefined,
|
||||
titleSummarizerGlobalModelId: undefined,
|
||||
} satisfies CompleteSettings<GlobalSettings>;
|
||||
|
||||
/** Default values for project-level settings. */
|
||||
@@ -56,6 +65,11 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
planningModelId: undefined,
|
||||
planningFallbackProvider: undefined,
|
||||
planningFallbackModelId: undefined,
|
||||
// Project-level default override and execution lane
|
||||
defaultProviderOverride: undefined,
|
||||
defaultModelIdOverride: undefined,
|
||||
executionProvider: undefined,
|
||||
executionModelId: undefined,
|
||||
validatorProvider: undefined,
|
||||
validatorModelId: undefined,
|
||||
validatorFallbackProvider: undefined,
|
||||
|
||||
@@ -705,6 +705,292 @@ describe("TaskStore", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── Dual-Scope Lane Model Settings (FN-1710) ─────────────────────
|
||||
|
||||
describe("dual-scope lane model settings", () => {
|
||||
// Legacy backward compatibility tests
|
||||
it("legacy: project config with only planningProvider/planningModelId round-trips unchanged", async () => {
|
||||
await store.updateSettings({
|
||||
planningProvider: "anthropic",
|
||||
planningModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.planningProvider).toBe("anthropic");
|
||||
expect(settings.planningModelId).toBe("claude-sonnet-4-5");
|
||||
|
||||
// Verify it's persisted correctly
|
||||
const configRaw = await readFile(join(rootDir, ".fusion", "config.json"), "utf-8");
|
||||
const config = JSON.parse(configRaw);
|
||||
expect(config.settings.planningProvider).toBe("anthropic");
|
||||
expect(config.settings.planningModelId).toBe("claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("legacy: project config with only validatorProvider/validatorModelId round-trips unchanged", async () => {
|
||||
await store.updateSettings({
|
||||
validatorProvider: "openai",
|
||||
validatorModelId: "gpt-4o",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.validatorProvider).toBe("openai");
|
||||
expect(settings.validatorModelId).toBe("gpt-4o");
|
||||
|
||||
const configRaw = await readFile(join(rootDir, ".fusion", "config.json"), "utf-8");
|
||||
const config = JSON.parse(configRaw);
|
||||
expect(config.settings.validatorProvider).toBe("openai");
|
||||
expect(config.settings.validatorModelId).toBe("gpt-4o");
|
||||
});
|
||||
|
||||
it("legacy: project config with only titleSummarizerProvider/titleSummarizerModelId round-trips unchanged", async () => {
|
||||
await store.updateSettings({
|
||||
titleSummarizerProvider: "google",
|
||||
titleSummarizerModelId: "gemini-2.5-pro",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.titleSummarizerProvider).toBe("google");
|
||||
expect(settings.titleSummarizerModelId).toBe("gemini-2.5-pro");
|
||||
|
||||
const configRaw = await readFile(join(rootDir, ".fusion", "config.json"), "utf-8");
|
||||
const config = JSON.parse(configRaw);
|
||||
expect(config.settings.titleSummarizerProvider).toBe("google");
|
||||
expect(config.settings.titleSummarizerModelId).toBe("gemini-2.5-pro");
|
||||
});
|
||||
|
||||
it("legacy: partial provider without modelId behaves correctly", async () => {
|
||||
// Set provider only without modelId (partial legacy pair)
|
||||
await store.updateSettings({
|
||||
planningProvider: "anthropic",
|
||||
// No planningModelId
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.planningProvider).toBe("anthropic");
|
||||
expect(settings.planningModelId).toBeUndefined();
|
||||
});
|
||||
|
||||
// New default override fields
|
||||
it("persists defaultProviderOverride/defaultModelIdOverride via updateSettings", async () => {
|
||||
await store.updateSettings({
|
||||
defaultProviderOverride: "openai",
|
||||
defaultModelIdOverride: "gpt-4o-mini",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.defaultProviderOverride).toBe("openai");
|
||||
expect(settings.defaultModelIdOverride).toBe("gpt-4o-mini");
|
||||
});
|
||||
|
||||
it("defaultProviderOverride/defaultModelIdOverride appear in project scope", async () => {
|
||||
await store.updateSettings({
|
||||
defaultProviderOverride: "anthropic",
|
||||
defaultModelIdOverride: "claude-3-5-sonnet",
|
||||
});
|
||||
|
||||
const { project } = await store.getSettingsByScope();
|
||||
expect(project.defaultProviderOverride).toBe("anthropic");
|
||||
expect(project.defaultModelIdOverride).toBe("claude-3-5-sonnet");
|
||||
});
|
||||
|
||||
it("defaultProviderOverride/defaultModelIdOverride default to undefined", async () => {
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.defaultProviderOverride).toBeUndefined();
|
||||
expect(settings.defaultModelIdOverride).toBeUndefined();
|
||||
});
|
||||
|
||||
// New execution lane fields
|
||||
it("persists executionProvider/executionModelId via updateSettings", async () => {
|
||||
await store.updateSettings({
|
||||
executionProvider: "anthropic",
|
||||
executionModelId: "claude-opus-4",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionProvider).toBe("anthropic");
|
||||
expect(settings.executionModelId).toBe("claude-opus-4");
|
||||
});
|
||||
|
||||
it("executionProvider/executionModelId appear in project scope", async () => {
|
||||
await store.updateSettings({
|
||||
executionProvider: "openai",
|
||||
executionModelId: "gpt-4-turbo",
|
||||
});
|
||||
|
||||
const { project } = await store.getSettingsByScope();
|
||||
expect(project.executionProvider).toBe("openai");
|
||||
expect(project.executionModelId).toBe("gpt-4-turbo");
|
||||
});
|
||||
|
||||
it("executionProvider/executionModelId default to undefined", async () => {
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionProvider).toBeUndefined();
|
||||
expect(settings.executionModelId).toBeUndefined();
|
||||
});
|
||||
|
||||
// Global lane fields via updateGlobalSettings
|
||||
it("persists executionGlobalProvider/executionGlobalModelId via updateGlobalSettings", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBe("anthropic");
|
||||
expect(settings.executionGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
});
|
||||
|
||||
it("persists planningGlobalProvider/planningGlobalModelId via updateGlobalSettings", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
planningGlobalProvider: "google",
|
||||
planningGlobalModelId: "gemini-2.5-pro",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.planningGlobalProvider).toBe("google");
|
||||
expect(settings.planningGlobalModelId).toBe("gemini-2.5-pro");
|
||||
});
|
||||
|
||||
it("persists validatorGlobalProvider/validatorGlobalModelId via updateGlobalSettings", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
validatorGlobalProvider: "openai",
|
||||
validatorGlobalModelId: "gpt-4o",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.validatorGlobalProvider).toBe("openai");
|
||||
expect(settings.validatorGlobalModelId).toBe("gpt-4o");
|
||||
});
|
||||
|
||||
it("persists titleSummarizerGlobalProvider/titleSummarizerGlobalModelId via updateGlobalSettings", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
titleSummarizerGlobalProvider: "anthropic",
|
||||
titleSummarizerGlobalModelId: "claude-haiku",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.titleSummarizerGlobalProvider).toBe("anthropic");
|
||||
expect(settings.titleSummarizerGlobalModelId).toBe("claude-haiku");
|
||||
});
|
||||
|
||||
it("all *Global* lane fields default to undefined", async () => {
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBeUndefined();
|
||||
expect(settings.executionGlobalModelId).toBeUndefined();
|
||||
expect(settings.planningGlobalProvider).toBeUndefined();
|
||||
expect(settings.planningGlobalModelId).toBeUndefined();
|
||||
expect(settings.validatorGlobalProvider).toBeUndefined();
|
||||
expect(settings.validatorGlobalModelId).toBeUndefined();
|
||||
expect(settings.titleSummarizerGlobalProvider).toBeUndefined();
|
||||
expect(settings.titleSummarizerGlobalModelId).toBeUndefined();
|
||||
});
|
||||
|
||||
// Mixed shape compatibility tests
|
||||
it("mixed shape: project planningProvider + global planningGlobalProvider is stable", async () => {
|
||||
// Set global baseline
|
||||
await store.updateGlobalSettings({
|
||||
planningGlobalProvider: "anthropic",
|
||||
planningGlobalModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
// Set project override
|
||||
await store.updateSettings({
|
||||
planningProvider: "openai",
|
||||
planningModelId: "gpt-4o",
|
||||
});
|
||||
|
||||
// Both should be readable with no crashes
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.planningGlobalProvider).toBe("anthropic");
|
||||
expect(settings.planningGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
expect(settings.planningProvider).toBe("openai");
|
||||
expect(settings.planningModelId).toBe("gpt-4o");
|
||||
});
|
||||
|
||||
it("mixed shape: project validatorProvider + global validatorGlobalProvider is stable", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
validatorGlobalProvider: "google",
|
||||
validatorGlobalModelId: "gemini-2.5-pro",
|
||||
});
|
||||
|
||||
await store.updateSettings({
|
||||
validatorProvider: "anthropic",
|
||||
validatorModelId: "claude-opus-4",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.validatorGlobalProvider).toBe("google");
|
||||
expect(settings.validatorGlobalModelId).toBe("gemini-2.5-pro");
|
||||
expect(settings.validatorProvider).toBe("anthropic");
|
||||
expect(settings.validatorModelId).toBe("claude-opus-4");
|
||||
});
|
||||
|
||||
it("mixed shape: project titleSummarizerProvider + global titleSummarizerGlobalProvider is stable", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
titleSummarizerGlobalProvider: "openai",
|
||||
titleSummarizerGlobalModelId: "gpt-4o-mini",
|
||||
});
|
||||
|
||||
await store.updateSettings({
|
||||
titleSummarizerProvider: "anthropic",
|
||||
titleSummarizerModelId: "claude-haiku",
|
||||
});
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.titleSummarizerGlobalProvider).toBe("openai");
|
||||
expect(settings.titleSummarizerGlobalModelId).toBe("gpt-4o-mini");
|
||||
expect(settings.titleSummarizerProvider).toBe("anthropic");
|
||||
expect(settings.titleSummarizerModelId).toBe("claude-haiku");
|
||||
});
|
||||
|
||||
// Global-only key filtering tests
|
||||
it("updateSettings does not persist *Global* keys to project config", async () => {
|
||||
// Attempt to set global keys through updateSettings (should be filtered)
|
||||
await store.updateSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
planningGlobalProvider: "openai",
|
||||
planningGlobalModelId: "gpt-4o",
|
||||
} as any);
|
||||
|
||||
// The global keys should be filtered out and not appear in merged settings
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.executionGlobalProvider).toBeUndefined();
|
||||
expect(settings.executionGlobalModelId).toBeUndefined();
|
||||
expect(settings.planningGlobalProvider).toBeUndefined();
|
||||
expect(settings.planningGlobalModelId).toBeUndefined();
|
||||
|
||||
// Verify they are NOT in the project config
|
||||
const configRaw = await readFile(join(rootDir, ".fusion", "config.json"), "utf-8");
|
||||
const config = JSON.parse(configRaw);
|
||||
expect((config.settings as any).executionGlobalProvider).toBeUndefined();
|
||||
expect((config.settings as any).planningGlobalProvider).toBeUndefined();
|
||||
});
|
||||
|
||||
it("all global lane fields appear in global scope", async () => {
|
||||
await store.updateGlobalSettings({
|
||||
executionGlobalProvider: "anthropic",
|
||||
executionGlobalModelId: "claude-sonnet-4-5",
|
||||
planningGlobalProvider: "google",
|
||||
planningGlobalModelId: "gemini-2.5-pro",
|
||||
validatorGlobalProvider: "openai",
|
||||
validatorGlobalModelId: "gpt-4o",
|
||||
titleSummarizerGlobalProvider: "anthropic",
|
||||
titleSummarizerGlobalModelId: "claude-haiku",
|
||||
});
|
||||
|
||||
const { global } = await store.getSettingsByScope();
|
||||
expect(global.executionGlobalProvider).toBe("anthropic");
|
||||
expect(global.executionGlobalModelId).toBe("claude-sonnet-4-5");
|
||||
expect(global.planningGlobalProvider).toBe("google");
|
||||
expect(global.planningGlobalModelId).toBe("gemini-2.5-pro");
|
||||
expect(global.validatorGlobalProvider).toBe("openai");
|
||||
expect(global.validatorGlobalModelId).toBe("gpt-4o");
|
||||
expect(global.titleSummarizerGlobalProvider).toBe("anthropic");
|
||||
expect(global.titleSummarizerGlobalModelId).toBe("claude-haiku");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Global/Project Settings Merging ─────────────────────────────
|
||||
|
||||
describe("global/project settings merging", () => {
|
||||
|
||||
@@ -899,6 +899,38 @@ export interface GlobalSettings {
|
||||
* false/undefined, the dashboard will auto-open the onboarding modal.
|
||||
* Also set to true when the user explicitly dismisses onboarding. */
|
||||
modelOnboardingComplete?: boolean;
|
||||
/** Global baseline AI model provider for task execution (executor agent).
|
||||
* This is the global lane that project-level `executionProvider` can override.
|
||||
* Must be set together with `executionGlobalModelId`. Falls back to
|
||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||
executionGlobalProvider?: string;
|
||||
/** Global baseline AI model ID for task execution.
|
||||
* Must be set together with `executionGlobalProvider`. */
|
||||
executionGlobalModelId?: string;
|
||||
/** Global baseline AI model provider for planning/triage (specification) agent.
|
||||
* This is the global lane that project-level `planningProvider` can override.
|
||||
* Must be set together with `planningGlobalModelId`. Falls back to
|
||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||
planningGlobalProvider?: string;
|
||||
/** Global baseline AI model ID for planning/triage.
|
||||
* Must be set together with `planningGlobalProvider`. */
|
||||
planningGlobalModelId?: string;
|
||||
/** Global baseline AI model provider for validator/reviewer agent.
|
||||
* This is the global lane that project-level `validatorProvider` can override.
|
||||
* Must be set together with `validatorGlobalModelId`. Falls back to
|
||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||
validatorGlobalProvider?: string;
|
||||
/** Global baseline AI model ID for validator/reviewer.
|
||||
* Must be set together with `validatorGlobalProvider`. */
|
||||
validatorGlobalModelId?: string;
|
||||
/** Global baseline AI model provider for title summarization.
|
||||
* This is the global lane that project-level `titleSummarizerProvider` can override.
|
||||
* Must be set together with `titleSummarizerGlobalModelId`. Falls back to
|
||||
* `defaultProvider`/`defaultModelId` when undefined. */
|
||||
titleSummarizerGlobalProvider?: string;
|
||||
/** Global baseline AI model ID for title summarization.
|
||||
* Must be set together with `titleSummarizerGlobalProvider`. */
|
||||
titleSummarizerGlobalModelId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -993,6 +1025,28 @@ export interface ProjectSettings {
|
||||
/** Fallback model ID for planning/triage. When unset, falls back to the
|
||||
* global fallback model. Must be set together with `planningFallbackProvider`. */
|
||||
planningFallbackModelId?: string;
|
||||
/** Project-level override for the base default AI model provider.
|
||||
* When set, this overrides the global `defaultProvider`/`defaultModelId` baseline
|
||||
* for all lanes that don't have their own explicit project override.
|
||||
* 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;
|
||||
/** Project-level override for the base default AI model ID.
|
||||
* Must be set together with `defaultProviderOverride`. */
|
||||
defaultModelIdOverride?: string;
|
||||
/** Project-level AI model provider for task execution (executor agent).
|
||||
* This is the execution lane that overrides the global `executionGlobalProvider`.
|
||||
* Must be set together with `executionModelId`. Falls back to
|
||||
* `executionGlobalProvider`/`executionGlobalModelId` or
|
||||
* `defaultProviderOverride`/`defaultModelIdOverride` or
|
||||
* `defaultProvider`/`defaultModelId` when undefined.
|
||||
* NOTE: This is a schema/persistence contract only. Runtime fallback precedence
|
||||
* logic is deferred to FN-1711. */
|
||||
executionProvider?: string;
|
||||
/** Project-level AI model ID for task execution.
|
||||
* Must be set together with `executionProvider`. */
|
||||
executionModelId?: string;
|
||||
/** AI model provider for validator/reviewer agent.
|
||||
* Must be set together with `validatorModelId`. When both are undefined,
|
||||
* falls back to `defaultProvider`/`defaultModelId`. */
|
||||
|
||||
Reference in New Issue
Block a user