## Summary Project workflow model lanes now apply to tasks on every workflow instead of only tasks using the active default workflow. Model selection consistently resolves task-specific choice -> project workflow baseline -> global lane -> selected-workflow value -> project/global default for primary models, fallback models, and thinking levels. The active default workflow remains the storage owner for backward compatibility, while runtime resolution keeps its project baseline distinct from lower-priority selected-workflow values. Non-model workflow policies remain isolated to their selected workflow. ## Validation - Core workflow/model resolution: 60 tests passed - Engine effective settings and session resolution: 59 tests passed - Reviewer: 85 tests passed - Scheduler: 154 tests passed - Heartbeat: 90 tests passed - Settings UI: 67 tests passed - Workspace lint and core/engine/dashboard typechecks passed - `pnpm verify:fast` passed workspace builds, the published CLI build, and real `/api/health` boot smoke --- [](https://github.com/EveryInc/compound-engineering-plugin) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added **Project workflow model lanes** to establish a project baseline for model selection and thinking levels across workflows. * Updated model/fallback resolution to account for task overrides, project baselines, global lanes, and selected-workflow values. * **Bug Fixes** * Improved effective settings merging so project baselines are applied correctly (including scheduled/idle and heartbeat flows) while preserving selected-workflow provenance. * **Documentation** * Refreshed settings and dashboard guidance for workflow lane inheritance and resolution precedence. * **Tests** * Expanded unit test coverage for lane precedence, fallback detection, and thinking-level behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
/**
|
|
* Engine-side helper: merge per-task EFFECTIVE workflow settings (U3, KTD-3) over a
|
|
* base settings object fetched from the store, so the engine's flat
|
|
* `settings.<key>` read sites pick up workflow-setting values with zero changes at
|
|
* the read sites.
|
|
*
|
|
* TWO-TIER MERGE (the parity-preserving rule):
|
|
* - a STORED flat value ALWAYS overrides the base (workflow policy, or the
|
|
* project workflow-model baseline);
|
|
* - a declaration-DEFAULT-only key (no stored value) only FILLS the base when the
|
|
* base lacks the key.
|
|
*
|
|
* This is what keeps U3 behavior-identical BEFORE the U4 hard-move: a customized
|
|
* project setting still present in the base is NOT clobbered by a declaration
|
|
* default; only a real stored workflow value overrides it. After the hard-move the
|
|
* base lacks the moved key, so the declaration default fills it. Absent-default
|
|
* model lanes contribute nothing. Lower-precedence selected-workflow model lanes
|
|
* are retained separately for the centralized model resolver.
|
|
*
|
|
* `resolveEffectiveSettingsDetailed` never throws (degrades to declaration
|
|
* defaults), so this helper is a thin store-coupled wrapper that also never throws.
|
|
*/
|
|
|
|
import {
|
|
applyWorkflowSettingsOverlay,
|
|
resolveEffectiveSettingsDetailed,
|
|
resolveProjectWorkflowModelLaneBaseline,
|
|
type Settings,
|
|
type TaskStore,
|
|
} from "@fusion/core";
|
|
|
|
/** The minimal task shape the resolver needs. Task carries no projectId field —
|
|
* the project key is derived from the store. */
|
|
export interface EffectiveSettingsTask {
|
|
id: string;
|
|
}
|
|
|
|
/**
|
|
* Merge `base` with the task's effective workflow settings via the two-tier rule
|
|
* (stored overrides; default-only fills only-absent). Returns a NEW object; `base`
|
|
* is not mutated. Degrades to returning `base` unchanged on any resolver error.
|
|
*/
|
|
export async function mergeEffectiveSettings<T extends Partial<Settings>>(
|
|
store: Pick<
|
|
TaskStore,
|
|
| "getDefaultWorkflowId"
|
|
| "getTaskWorkflowSelection"
|
|
| "getTaskWorkflowSelectionAsync"
|
|
| "getWorkflowDefinition"
|
|
| "getWorkflowSettingValues"
|
|
| "getWorkflowSettingsProjectId"
|
|
>,
|
|
task: EffectiveSettingsTask,
|
|
base: T,
|
|
): Promise<T> {
|
|
try {
|
|
const detailed = await resolveEffectiveSettingsDetailed(
|
|
store as Parameters<typeof resolveEffectiveSettingsDetailed>[0],
|
|
task,
|
|
);
|
|
return applyWorkflowSettingsOverlay(base, detailed);
|
|
} catch {
|
|
return base;
|
|
}
|
|
}
|
|
|
|
/** Merge the Project Models workflow-lane baseline when no task-selected
|
|
* workflow exists, such as scheduled AI prompts and idle heartbeats. */
|
|
export async function mergeProjectWorkflowModelLaneBaseline<T extends Partial<Settings>>(
|
|
store: Pick<
|
|
TaskStore,
|
|
| "getDefaultWorkflowId"
|
|
| "getWorkflowDefinition"
|
|
| "getWorkflowSettingValues"
|
|
| "getWorkflowSettingsProjectId"
|
|
>,
|
|
base: T,
|
|
): Promise<T> {
|
|
try {
|
|
const detailed = await resolveProjectWorkflowModelLaneBaseline(
|
|
store as Parameters<typeof resolveProjectWorkflowModelLaneBaseline>[0],
|
|
store.getWorkflowSettingsProjectId(),
|
|
);
|
|
return applyWorkflowSettingsOverlay(base, detailed);
|
|
} catch {
|
|
return base;
|
|
}
|
|
}
|