Expose planner oversight as workflow-native configuration across task and workflow surfaces. - Add a shared TaskForm selector for per-task planner oversight overrides with inherit semantics. - Thread plannerOversightLevel through new task creation, task detail edits, and legacy dashboard API payloads. - Add a Workflow Editor Values display group and documentation for configuring workflow defaults. - Cover create, edit, form, and workflow settings behavior with dashboard tests. Files changed: .changeset/fn-7515-planner-oversight-config-exposure.md | 7 ++ docs/dashboard-guide.md | 1 + docs/settings-reference.md | 2 +- packages/dashboard/app/api/legacy.ts | 3 + packages/dashboard/app/components/NewTaskModal.tsx | 12 +++- packages/dashboard/app/components/TaskDetailModal.tsx | 11 +++- packages/dashboard/app/components/TaskForm.tsx | 34 ++++++++++ packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx | 31 +++++++++ packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx | 76 ++++++++++++++++++++++ packages/dashboard/app/components/__tests__/TaskForm.test.tsx | 28 ++++++++ packages/dashboard/app/components/__tests__/WorkflowSettingsPanel.test.tsx | 34 ++++++++++ packages/dashboard/app/components/workflow-setting-display.ts | 17 ++++- 12 files changed, 251 insertions(+), 5 deletions(-) Fusion-Task-Id: FN-7515 Fusion-Task-Lineage: aded67c0-835c-4046-b691-04dc2bb2d314 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
203 lines
6.7 KiB
TypeScript
203 lines
6.7 KiB
TypeScript
import type { WorkflowSettingDefinition } from "../api";
|
|
|
|
export type WorkflowSettingGroup = "models" | "review" | "oversight" | "steps" | "advanced";
|
|
|
|
export interface WorkflowSettingDisplay {
|
|
group: WorkflowSettingGroup;
|
|
label: string;
|
|
description?: string;
|
|
}
|
|
|
|
/*
|
|
FNXC:WorkflowSettings 2026-06-17-09:13:
|
|
Title summarization is project/global-scoped and configured in Project Models, so this workflow display map intentionally omits titleSummarizer* keys. If a custom workflow declares a same-named key, render it as Advanced instead of implying Fusion will execute it as a workflow model lane.
|
|
*/
|
|
const DISPLAY: Record<string, WorkflowSettingDisplay> = {
|
|
planningProvider: {
|
|
group: "models",
|
|
label: "Plan/Triage provider",
|
|
description: "Provider used when planning or triaging tasks.",
|
|
},
|
|
planningModelId: {
|
|
group: "models",
|
|
label: "Plan/Triage model",
|
|
description: "Model used when planning or triaging tasks.",
|
|
},
|
|
planningFallbackProvider: {
|
|
group: "models",
|
|
label: "Plan/Triage fallback provider",
|
|
},
|
|
planningFallbackModelId: {
|
|
group: "models",
|
|
label: "Plan/Triage fallback model",
|
|
},
|
|
executionProvider: {
|
|
group: "models",
|
|
label: "Executor provider",
|
|
description: "Provider used by task implementation agents.",
|
|
},
|
|
executionModelId: {
|
|
group: "models",
|
|
label: "Executor model",
|
|
description: "Model used by task implementation agents.",
|
|
},
|
|
validatorProvider: {
|
|
group: "models",
|
|
label: "Reviewer provider",
|
|
description: "Provider used by review and validation agents.",
|
|
},
|
|
validatorModelId: {
|
|
group: "models",
|
|
label: "Reviewer model",
|
|
description: "Model used by review and validation agents.",
|
|
},
|
|
validatorFallbackProvider: {
|
|
group: "models",
|
|
label: "Reviewer fallback provider",
|
|
},
|
|
validatorFallbackModelId: {
|
|
group: "models",
|
|
label: "Reviewer fallback model",
|
|
},
|
|
requirePrApproval: {
|
|
group: "review",
|
|
label: "Require PR approval",
|
|
},
|
|
requirePlanApproval: {
|
|
group: "review",
|
|
label: "Require plan approval",
|
|
},
|
|
reviewHandoffPolicy: {
|
|
group: "review",
|
|
label: "Review handoff policy",
|
|
},
|
|
maxReviewerContextRetries: {
|
|
group: "review",
|
|
label: "Reviewer context retries",
|
|
},
|
|
maxReviewerFallbackRetries: {
|
|
group: "review",
|
|
label: "Reviewer fallback retries",
|
|
},
|
|
reflectionEnabled: {
|
|
group: "review",
|
|
label: "Reflection enabled",
|
|
},
|
|
planReviewMaxRevisions: {
|
|
group: "review",
|
|
label: "Plan Review revision cap",
|
|
/*
|
|
* FNXC:WorkflowRevisionBudget 2026-06-30-19:47:
|
|
* The Values tab must tell operators that an empty built-in workflow value is intentionally unbounded, not the old three-pass Plan Review default. Numeric values cap retries and `0` turns off automatic revision.
|
|
*/
|
|
description: "Leave empty for unbounded automatic Plan Review/spec revision; set 0 to disable automatic revision.",
|
|
},
|
|
codeReviewMaxRevisions: {
|
|
group: "review",
|
|
label: "Code Review revision cap",
|
|
/*
|
|
* FNXC:WorkflowRevisionBudget 2026-06-30-19:47:
|
|
* Built-in workflow values are editable even when definitions are read-only, so this label is the operator-facing cap for Code Review remediation without requiring a workflow duplicate.
|
|
*/
|
|
description: "Leave empty for unbounded automatic Code Review remediation; set 0 to disable automatic revision.",
|
|
},
|
|
triageProactiveSubtaskSplittingEnabled: {
|
|
group: "steps",
|
|
label: "Automatic large-task splitting",
|
|
/*
|
|
* FNXC:TriagePolicy 2026-07-04-00:00:
|
|
* Workflow Settings is the canonical operator surface for this workflow/project policy. The copy must make the default enabled state clear and preserve trust that explicit `breakIntoSubtasks: true` requests still split even when automatic large-task splitting is off.
|
|
*/
|
|
description:
|
|
"Default enabled. When off, triage keeps oversized tasks whole unless breakIntoSubtasks: true is explicitly requested.",
|
|
},
|
|
workflowStepTimeoutMs: {
|
|
group: "steps",
|
|
label: "Step timeout",
|
|
},
|
|
workflowStepScopeEnforcement: {
|
|
group: "steps",
|
|
label: "Step scope enforcement",
|
|
},
|
|
planOnlyScopeLeakEnforcement: {
|
|
group: "steps",
|
|
label: "Plan-only scope leak enforcement",
|
|
},
|
|
workflowRevisionForkOnScopeMismatch: {
|
|
group: "steps",
|
|
label: "Fork revision on scope mismatch",
|
|
},
|
|
strictScopeEnforcement: {
|
|
group: "steps",
|
|
label: "Strict scope enforcement",
|
|
},
|
|
runStepsInNewSessions: {
|
|
group: "steps",
|
|
label: "Run steps in new sessions",
|
|
},
|
|
maxParallelSteps: {
|
|
group: "steps",
|
|
label: "Max parallel steps",
|
|
},
|
|
buildRetryCount: {
|
|
group: "steps",
|
|
label: "Build retry count",
|
|
},
|
|
verificationFixRetries: {
|
|
group: "steps",
|
|
label: "Verification fix retries",
|
|
},
|
|
maxPostReviewFixes: {
|
|
group: "steps",
|
|
label: "Post-review fix passes",
|
|
},
|
|
/*
|
|
* FNXC:PlannerOversight 2026-07-04-00:00:
|
|
* First-class Values-tab display entry for the workflow-native plannerOversightLevel
|
|
* setting (FN-7508). This is the project/global default surface: the default
|
|
* workflow's stored value here is the effective oversight level for every task
|
|
* under it that does not set a per-task override (FN-7515, TaskForm selector).
|
|
*/
|
|
plannerOversightLevel: {
|
|
group: "oversight",
|
|
label: "Planner oversight level",
|
|
description:
|
|
"Off disables oversight; Observe watches only; Steer injects guidance or suggests revisions; Autonomous recovery enables bounded retry and targeted-fix recovery. Per-task overrides are set on individual tasks and win over this project/global default.",
|
|
},
|
|
};
|
|
|
|
export const WORKFLOW_SETTING_GROUP_ORDER: WorkflowSettingGroup[] = [
|
|
"models",
|
|
"review",
|
|
"oversight",
|
|
"steps",
|
|
"advanced",
|
|
];
|
|
|
|
export const WORKFLOW_SETTING_GROUP_LABELS: Record<WorkflowSettingGroup, string> = {
|
|
models: "Models",
|
|
review: "Review & Approval",
|
|
oversight: "Planner Oversight",
|
|
steps: "Step Execution",
|
|
advanced: "Advanced",
|
|
};
|
|
|
|
export function getWorkflowSettingDisplay(setting: WorkflowSettingDefinition): WorkflowSettingDisplay {
|
|
return DISPLAY[setting.id] ?? { group: "advanced", label: setting.name, description: setting.description };
|
|
}
|
|
|
|
export function groupWorkflowSettings(
|
|
settings: WorkflowSettingDefinition[],
|
|
): Array<{ group: WorkflowSettingGroup; settings: WorkflowSettingDefinition[] }> {
|
|
const byGroup = new Map<WorkflowSettingGroup, WorkflowSettingDefinition[]>();
|
|
for (const setting of settings) {
|
|
const group = getWorkflowSettingDisplay(setting).group;
|
|
const list = byGroup.get(group) ?? [];
|
|
list.push(setting);
|
|
byGroup.set(group, list);
|
|
}
|
|
return WORKFLOW_SETTING_GROUP_ORDER
|
|
.map((group) => ({ group, settings: byGroup.get(group) ?? [] }))
|
|
.filter((entry) => entry.settings.length > 0);
|
|
}
|