FN-7509: add per-task planner oversight overrides

Add core support for tasks to carry a planner oversight override that can supersede workflow settings.

- Add nullable plannerOversightLevel task storage, schema migration, store update/create/archive plumbing, and mesh replication support.
- Export planner oversight level types/defaults and an effective-level resolver with task-over-workflow precedence.
- Document override precedence and add regression coverage for migration, persistence, updates, and resolution.
- Add a minor changeset for the published Fusion package.

Files changed:
 .../fn-7509-per-task-planner-oversight-override.md |  7 ++
 docs/settings-reference.md                         |  2 +-
 packages/core/src/__tests__/db.test.ts             | 45 +++++++++++++
 packages/core/src/__tests__/store-update.test.ts   | 75 ++++++++++++++++++++++
 .../__tests__/workflow-settings-resolver.test.ts   | 28 ++++++++
 packages/core/src/db.ts                            | 17 ++++-
 packages/core/src/index.ts                         |  5 +-
 packages/core/src/mesh-task-replication.ts         |  2 +
 packages/core/src/store.ts                         | 15 ++++-
 packages/core/src/types.ts                         | 23 +++++++
 packages/core/src/workflow-settings-resolver.ts    | 28 ++++++++
 11 files changed, 240 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-7509

Fusion-Task-Lineage: 41695cc5-34d2-4079-9e34-a8fb40f961fb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-04 12:32:09 -07:00
parent 4707eb5be0
commit aa757bc1a3
11 changed files with 240 additions and 7 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": minor
---
summary: Tasks can override the workflow planner oversight level (Off, Observe, Steer, Autonomous recovery).
category: feature
dev: New nullable Task.plannerOversightLevel field (migration 137, SCHEMA_VERSION 137) mirroring executionMode; NULL inherits the workflow setting. Adds resolveEffectivePlannerOversightLevel precedence helper. Dashboard UI/API threading and engine behavior land in follow-up tasks.

View File

@@ -356,7 +356,7 @@ The built-in workflows also declare triage/spec policy settings that were **not*
| `autoApproveSpec` | `false` | Legacy compatibility setting. Workflow Plan Review now owns optional pre-execution AI plan approval. |
| `planReviewMaxRevisions` | unset | Workflow-native Plan Review/spec revision cap. Unset/empty means unbounded automatic replans; a non-negative integer caps attempts; `0` disables automatic Plan Review revision. |
| `codeReviewMaxRevisions` | unset | Workflow-native Code Review remediation cap. Unset/empty means unbounded automatic code-fix passes; a non-negative integer caps attempts; `0` disables automatic Code Review remediation. |
| `plannerOversightLevel` | `autonomous` | Workflow-native planner oversight mode. `off` disables oversight; `observe` watches only; `steer` injects guidance or suggests revisions; `autonomous` enables bounded retry and targeted-fix recovery. Per-task overrides and engine behavior are follow-up work. |
| `plannerOversightLevel` | `autonomous` | Workflow-native planner oversight mode. `off` disables oversight; `observe` watches only; `steer` injects guidance or suggests revisions; `autonomous` enables bounded retry and targeted-fix recovery. Tasks may set a nullable `Task.plannerOversightLevel` override (same four values) that wins over this workflow value when present; `null`/unset means "inherit the workflow value". `resolveEffectivePlannerOversightLevel` in `@fusion/core` computes the effective level (task override → workflow effective → `autonomous`). Dashboard UI/API threading for the per-task override and engine read-site behavior are follow-up work (FN-7515, FN-7510+). |
When `triageProactiveSubtaskSplittingEnabled` is `true` (the default), triage may proactively replace a large task with 2-5 child tasks when the size, step-count, package breadth, file-scope, or remediation-batch signals justify the coordination overhead. When it is `false`, those automatic oversized-task signals are advisory only for writing a realistic single-task spec; triage must not split solely because the task is large. The per-task `breakIntoSubtasks: true` flag is separate and remains mandatory: if a user explicitly asks for subtask breakdown, triage still evaluates and creates child tasks when the work is meaningfully decomposable.

View File

@@ -1778,6 +1778,51 @@ describe("schema migrations", () => {
db.close();
});
it("migrates v136 databases by adding plannerOversightLevel column with legacy rows staying NULL (no backfill)", () => {
tmpDir = makeTmpDir();
const fusionDir = join(tmpDir, ".fusion");
const db = new Database(fusionDir);
db.exec(`
CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT);
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
description TEXT NOT NULL,
"column" TEXT NOT NULL,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL,
executionMode TEXT DEFAULT 'standard'
);
CREATE TABLE IF NOT EXISTS config (
id INTEGER PRIMARY KEY CHECK (id = 1),
nextId INTEGER DEFAULT 1,
nextWorkflowStepId INTEGER DEFAULT 1,
settings TEXT DEFAULT '{}',
workflowSteps TEXT DEFAULT '[]',
updatedAt TEXT
);
`);
db.exec("INSERT INTO __meta (key, value) VALUES ('schemaVersion', '136')");
db.exec("INSERT INTO __meta (key, value) VALUES ('lastModified', '1000')");
db.exec(`INSERT INTO tasks (id, description, "column", createdAt, updatedAt) VALUES ('FN-1', 'legacy', 'triage', '2026-01-01', '2026-01-01')`);
db.init();
expect(db.getSchemaVersion()).toBe(SCHEMA_VERSION);
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
expect(cols.map((col) => col.name)).toContain("plannerOversightLevel");
// FNXC:PlannerOversight 2026-07-04-00:00: migration 137 is additive-only and must NOT backfill
// legacy rows — a NULL value means "inherit workflow default".
const task = db.prepare("SELECT plannerOversightLevel FROM tasks WHERE id = 'FN-1'").get() as {
plannerOversightLevel: string | null;
};
expect(task.plannerOversightLevel).toBeNull();
db.close();
});
it("migrates v43 databases by adding task token-usage aggregate columns with null-compatible defaults", () => {
tmpDir = makeTmpDir();
const fusionDir = join(tmpDir, ".fusion");

View File

@@ -945,6 +945,81 @@ describe("TaskStore", () => {
});
});
// FNXC:PlannerOversight 2026-07-04-00:00: per-task override of the workflow-native
// `plannerOversightLevel` setting (FN-7508/FN-7509); mirrors executionMode persistence.
describe("plannerOversightLevel persistence", () => {
it("sets plannerOversightLevel to 'steer' via createTask and persists", async () => {
const created = await store.createTask({
description: "Task with steer oversight override",
plannerOversightLevel: "steer",
});
expect(created.plannerOversightLevel).toBe("steer");
const persisted = await store.getTask(created.id);
expect(persisted.plannerOversightLevel).toBe("steer");
});
it("persists plannerOversightLevel as undefined (inherit) by default when not specified", async () => {
const created = await store.createTask({
description: "Task without oversight override",
});
expect(created.plannerOversightLevel).toBeUndefined();
const persisted = await store.getTask(created.id);
expect(persisted.plannerOversightLevel).toBeUndefined();
});
it("updates plannerOversightLevel via updateTask", async () => {
const created = await store.createTask({
description: "Task for oversight override update",
plannerOversightLevel: "observe",
});
expect(created.plannerOversightLevel).toBe("observe");
const updated = await store.updateTask(created.id, { plannerOversightLevel: "off" });
expect(updated.plannerOversightLevel).toBe("off");
const reloaded = await store.getTask(created.id);
expect(reloaded.plannerOversightLevel).toBe("off");
});
it("clears plannerOversightLevel via null in updateTask (reverts to inherit)", async () => {
const task = await store.createTask({
description: "Task with oversight override to clear",
plannerOversightLevel: "observe",
});
expect(task.plannerOversightLevel).toBe("observe");
const updated = await store.updateTask(task.id, { plannerOversightLevel: null });
expect(updated.plannerOversightLevel).toBeUndefined();
const reloaded = await store.getTask(task.id);
expect(reloaded.plannerOversightLevel).toBeUndefined();
});
it("leaves plannerOversightLevel unchanged when updateTask omits it", async () => {
const task = await store.createTask({
description: "Task with oversight override to preserve",
plannerOversightLevel: "steer",
});
const updated = await store.updateTask(task.id, { title: "Updated title" });
expect(updated.plannerOversightLevel).toBe("steer");
expect(updated.title).toBe("Updated title");
});
it("returns plannerOversightLevel in listTasks", async () => {
await store.createTask({ description: "Steer task", plannerOversightLevel: "steer" });
await store.createTask({ description: "Unspecified oversight task" });
const tasks = await store.listTasks();
const steerTask = tasks.find((t) => t.description === "Steer task");
const unspecifiedTask = tasks.find((t) => t.description === "Unspecified oversight task");
expect(steerTask?.plannerOversightLevel).toBe("steer");
expect(unspecifiedTask?.plannerOversightLevel).toBeUndefined();
});
});
describe("updateTask — PROMPT.md regeneration", () => {
it("regenerates PROMPT.md when title is updated", async () => {

View File

@@ -6,6 +6,7 @@ import {
resolveEffectiveSettings,
resolveEffectiveSettingsById,
resolveOptionalReviewRevisionBudget,
resolveEffectivePlannerOversightLevel,
type WorkflowSettingsResolverStore,
} from "../workflow-settings-resolver.js";
@@ -284,3 +285,30 @@ describe("resolveEffectiveSettingsById", () => {
expect(eff.requirePrApproval).toBe(false);
});
});
// FNXC:PlannerOversight 2026-07-04-00:00: task override > workflow effective > "autonomous" default (FN-7509).
describe("resolveEffectivePlannerOversightLevel", () => {
it("task override wins over workflow effective value", () => {
expect(resolveEffectivePlannerOversightLevel("steer", "observe")).toBe("steer");
});
it("uses workflow effective value when no task override", () => {
expect(resolveEffectivePlannerOversightLevel(undefined, "observe")).toBe("observe");
});
it("falls back to 'autonomous' when task override is an unknown/invalid string", () => {
expect(resolveEffectivePlannerOversightLevel("bogus", "observe")).toBe("observe");
});
it("falls back to 'autonomous' when workflow effective value is an unknown/invalid string", () => {
expect(resolveEffectivePlannerOversightLevel(undefined, "bogus")).toBe("autonomous");
});
it("falls back to 'autonomous' when both are unset", () => {
expect(resolveEffectivePlannerOversightLevel(undefined, undefined)).toBe("autonomous");
});
it("falls back to 'autonomous' when both are null", () => {
expect(resolveEffectivePlannerOversightLevel(null, null)).toBe("autonomous");
});
});

View File

@@ -183,7 +183,7 @@ export function isFts5CorruptionError(error: unknown): boolean {
// ── Schema Definition ────────────────────────────────────────────────
const SCHEMA_VERSION = 136;
const SCHEMA_VERSION = 137;
const TASKS_FTS_AUTOMERGE = 8;
const TASKS_FTS_CRISISMERGE = 16;
@@ -299,6 +299,7 @@ CREATE TABLE IF NOT EXISTS tasks (
summary TEXT,
thinkingLevel TEXT,
executionMode TEXT DEFAULT 'standard',
plannerOversightLevel TEXT,
tokenUsageInputTokens INTEGER,
tokenUsageOutputTokens INTEGER,
tokenUsageCachedTokens INTEGER,
@@ -5556,6 +5557,20 @@ export class Database {
});
}
if (version < 137) {
/*
* FNXC:PlannerOversight 2026-07-04-00:00:
* Per-task override of the workflow-native `plannerOversightLevel` setting
* (FN-7508/FN-7509). Additive-only: no SQL default and no backfill — a NULL
* value means "inherit the workflow's effective oversight level", which is
* distinct from executionMode's non-null 'standard' default. Legacy rows
* must stay NULL.
*/
this.applyMigration(137, () => {
this.addColumnIfMissing("tasks", "plannerOversightLevel", "TEXT");
});
}
}
/**

View File

@@ -1,5 +1,5 @@
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, isMergeRequestContractShadowEnabled, resolvePersistAgentThinkingLog, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, SUPPORTED_LOCALES, DEFAULT_LOCALE, isLocale, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PROVISIONING_APPROVAL_MODES, SANDBOX_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, WORKFLOW_WORK_ITEM_KINDS, WORKFLOW_WORK_ITEM_STATES, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeIntegrationWorktreeMode, normalizeMergeAdvanceAutoSyncMode, DEFAULT_GITLAB_API_BASE_URL, DEFAULT_GITLAB_INSTANCE_URL, resolveGitlabConfig, resolveGitlabEnabled, PLANNING_DEEPEN_CHECKPOINT_ID, PLANNING_DEEPEN_CHECKPOINT_QUESTION, PLANNING_DEEPEN_PROCEED_OPTION_ID, PLANNING_DEEPEN_PROCEED_RESPONSE_KEY, MERGE_ADVANCE_AUTO_SYNC_MODES, normalizeMergeConflictStrategy, normalizeMergeStrategyOverlapBehavior, normalizePostMergeAuditMode, POST_MERGE_AUDIT_MODES, normalizeMergeAuditAutoRecovery, MERGE_AUDIT_AUTO_RECOVERY_MODES, normalizeMergerMode, MERGER_MODES, normalizeAutoRecovery, AUTO_RECOVERY_MODES, buildResearchDocumentKey, REPO_OVERRIDE_RE, SHARED_STATE_SNAPSHOT_VERSION, sanitizeCliAgentSettings, sanitizeCliAgentsSettings, sanitizeMcpServers, CLI_AGENT_ADAPTER_IDS, CLI_AGENT_AUTONOMY_MODES, isMcpSecretRef } from "./types.js";
export type { Column, ColumnId, IssueInfo, IssueState, TaskSourceIssue, TaskGitLabTracking, TaskGitLabTrackedItem, GitLabTrackedItemKind, PrInfo, PrConflictState, PrConflictDiagnostics, PrCheckState, PrCheckStatus, PrStatus, BranchGroup, BranchGroupCreateInput, BranchGroupUpdate, BranchGroupPrState, Task, TaskTokenUsage, TaskTokenUsagePerModel, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, ArtifactType, Artifact, ArtifactCreateInput, ArtifactWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, RetrySummary, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOrphanRecord, AutostashOutcome, MergeDetails, MergeResult, MergeIntegrationWorktreeMode, MergeAdvanceAutoSyncMode, MergeConflictStrategy, CanonicalMergeConflictStrategy, MergeStrategyOverlapBehavior, PostMergeAuditMode, MergeAuditAutoRecoveryMode, MergerMode, MergerSettings, AutoRecoveryMode, AutoRecoveryFailureClass, AutoRecoverySettings, DirectMergeCommitStrategy, Settings, GlobalSettings, ProjectSettings, SecretsEnvConfig, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SandboxBackendName, SandboxFailureMode, SandboxPolicy, SandboxProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, Locale, ExecutionMode, TaskPriority, MergeQueueEntry, MergeQueueEnqueueOptions, MergeQueueAcquireOptions, MergeQueueReleaseOutcome, MergeRequestState, MergeRequestRecord, MergeRequestWorkflowProjectionOptions, CompletionHandoffMarker, WorkflowWorkItem, WorkflowWorkItemDueFilter, WorkflowWorkItemKind, WorkflowWorkItemState, WorkflowWorkItemTransitionPatch, WorkflowWorkItemUpsertInput, HandoffEvidence, HandoffToReviewOptions, UnavailableNodePolicy, OwningNodeHandoffPolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, GithubIssueAction, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepGateMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyToolRules, AgentPermissionPolicyActionCategory, AgentProvisioningApprovalMode, SandboxProvisioningApprovalMode, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, ApprovalRequestActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, CheckoutClaimPrecondition, TaskClaimRow, CentralClaimStore, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter, AgentMemoryInclusionMode, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode, WorktrunkSettings, WorktrunkOnFailure, TaskBranchContext, CliAgentSettings, McpSecretRef, McpSensitiveValue, McpStdioTransport, McpSseTransport, McpStreamableHttpTransport, McpTransport, McpServerDefinition, McpServersSettings, GitlabConfigSettingsSource, ResolvedGitlabConfig, ResolveGitlabConfigInput, GitlabAuthTokenType } from "./types.js";
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, isMergeRequestContractShadowEnabled, resolvePersistAgentThinkingLog, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, SUPPORTED_LOCALES, DEFAULT_LOCALE, isLocale, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PROVISIONING_APPROVAL_MODES, SANDBOX_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, PLANNER_OVERSIGHT_LEVELS, DEFAULT_PLANNER_OVERSIGHT_LEVEL, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, WORKFLOW_WORK_ITEM_KINDS, WORKFLOW_WORK_ITEM_STATES, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeIntegrationWorktreeMode, normalizeMergeAdvanceAutoSyncMode, DEFAULT_GITLAB_API_BASE_URL, DEFAULT_GITLAB_INSTANCE_URL, resolveGitlabConfig, resolveGitlabEnabled, PLANNING_DEEPEN_CHECKPOINT_ID, PLANNING_DEEPEN_CHECKPOINT_QUESTION, PLANNING_DEEPEN_PROCEED_OPTION_ID, PLANNING_DEEPEN_PROCEED_RESPONSE_KEY, MERGE_ADVANCE_AUTO_SYNC_MODES, normalizeMergeConflictStrategy, normalizeMergeStrategyOverlapBehavior, normalizePostMergeAuditMode, POST_MERGE_AUDIT_MODES, normalizeMergeAuditAutoRecovery, MERGE_AUDIT_AUTO_RECOVERY_MODES, normalizeMergerMode, MERGER_MODES, normalizeAutoRecovery, AUTO_RECOVERY_MODES, buildResearchDocumentKey, REPO_OVERRIDE_RE, SHARED_STATE_SNAPSHOT_VERSION, sanitizeCliAgentSettings, sanitizeCliAgentsSettings, sanitizeMcpServers, CLI_AGENT_ADAPTER_IDS, CLI_AGENT_AUTONOMY_MODES, isMcpSecretRef } from "./types.js";
export type { Column, ColumnId, IssueInfo, IssueState, TaskSourceIssue, TaskGitLabTracking, TaskGitLabTrackedItem, GitLabTrackedItemKind, PrInfo, PrConflictState, PrConflictDiagnostics, PrCheckState, PrCheckStatus, PrStatus, BranchGroup, BranchGroupCreateInput, BranchGroupUpdate, BranchGroupPrState, Task, TaskTokenUsage, TaskTokenUsagePerModel, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, ArtifactType, Artifact, ArtifactCreateInput, ArtifactWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, RetrySummary, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOrphanRecord, AutostashOutcome, MergeDetails, MergeResult, MergeIntegrationWorktreeMode, MergeAdvanceAutoSyncMode, MergeConflictStrategy, CanonicalMergeConflictStrategy, MergeStrategyOverlapBehavior, PostMergeAuditMode, MergeAuditAutoRecoveryMode, MergerMode, MergerSettings, AutoRecoveryMode, AutoRecoveryFailureClass, AutoRecoverySettings, DirectMergeCommitStrategy, Settings, GlobalSettings, ProjectSettings, SecretsEnvConfig, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SandboxBackendName, SandboxFailureMode, SandboxPolicy, SandboxProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, Locale, ExecutionMode, PlannerOversightLevel, TaskPriority, MergeQueueEntry, MergeQueueEnqueueOptions, MergeQueueAcquireOptions, MergeQueueReleaseOutcome, MergeRequestState, MergeRequestRecord, MergeRequestWorkflowProjectionOptions, CompletionHandoffMarker, WorkflowWorkItem, WorkflowWorkItemDueFilter, WorkflowWorkItemKind, WorkflowWorkItemState, WorkflowWorkItemTransitionPatch, WorkflowWorkItemUpsertInput, HandoffEvidence, HandoffToReviewOptions, UnavailableNodePolicy, OwningNodeHandoffPolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, GithubIssueAction, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepGateMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyToolRules, AgentPermissionPolicyActionCategory, AgentProvisioningApprovalMode, SandboxProvisioningApprovalMode, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, ApprovalRequestActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, CheckoutClaimPrecondition, TaskClaimRow, CentralClaimStore, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter, AgentMemoryInclusionMode, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode, WorktrunkSettings, WorktrunkOnFailure, TaskBranchContext, CliAgentSettings, McpSecretRef, McpSensitiveValue, McpStdioTransport, McpSseTransport, McpStreamableHttpTransport, McpTransport, McpServerDefinition, McpServersSettings, GitlabConfigSettingsSource, ResolvedGitlabConfig, ResolveGitlabConfigInput, GitlabAuthTokenType } from "./types.js";
export { AGENT_VALID_TRANSITIONS, DUPLICATE_OF_METADATA_KEY, assertNotWorkspaceTaskMerge, isWorkspaceTask, WorkspaceTaskMergeError } from "./types.js";
export {
resolveEntryPointBranchAssignment,
@@ -417,6 +417,7 @@ export {
resolveEffectiveSettingsDetailed,
resolveEffectiveSettingsById,
resolveOptionalReviewRevisionBudget,
resolveEffectivePlannerOversightLevel,
PLAN_REVIEW_MAX_REVISIONS_SETTING_ID,
CODE_REVIEW_MAX_REVISIONS_SETTING_ID,
type WorkflowSettingsResolverStore,

View File

@@ -101,6 +101,7 @@ export function taskMatchesReplicatedCreate(existing: TaskDetail, payload: MeshR
assigneeUserId: existing.assigneeUserId,
reviewLevel: existing.reviewLevel,
executionMode: existing.executionMode,
plannerOversightLevel: existing.plannerOversightLevel,
priority: existing.priority,
sourceIssue: existing.sourceIssue,
source: toTaskSource({
@@ -152,6 +153,7 @@ export function toReplicatedCreateInput(task: Task): TaskCreateInput {
assigneeUserId: task.assigneeUserId,
reviewLevel: task.reviewLevel,
executionMode: task.executionMode,
plannerOversightLevel: task.plannerOversightLevel,
priority: task.priority,
sourceIssue: task.sourceIssue,
source: toTaskSource({

View File

@@ -270,6 +270,7 @@ interface TaskRow {
summary: string | null;
thinkingLevel: string | null;
executionMode: string | null;
plannerOversightLevel: string | null;
tokenUsageInputTokens: number | null;
tokenUsageOutputTokens: number | null;
tokenUsageCachedTokens: number | null;
@@ -435,6 +436,7 @@ const TASK_COLUMN_DESCRIPTORS: TaskColumnDescriptor[] = [
defineTaskColumn("summary", (task) => task.summary ?? null),
defineTaskColumn("thinkingLevel", (task) => task.thinkingLevel ?? null),
defineTaskColumn("executionMode", (task) => task.executionMode ?? null),
defineTaskColumn("plannerOversightLevel", (task) => task.plannerOversightLevel ?? null),
defineTaskColumn("tokenUsageInputTokens", (task) => task.tokenUsage?.inputTokens ?? null),
defineTaskColumn("tokenUsageOutputTokens", (task) => task.tokenUsage?.outputTokens ?? null),
defineTaskColumn("tokenUsageCachedTokens", (task) => task.tokenUsage?.cachedTokens ?? null),
@@ -2119,6 +2121,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
summary: row.summary || undefined,
thinkingLevel: (row.thinkingLevel || undefined) as Task["thinkingLevel"],
executionMode: (row.executionMode || undefined) as Task["executionMode"],
plannerOversightLevel: (row.plannerOversightLevel || undefined) as Task["plannerOversightLevel"],
createdAt: row.createdAt,
updatedAt: row.updatedAt,
columnMovedAt: row.columnMovedAt || undefined,
@@ -2665,7 +2668,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
"validatorModelProvider", "validatorModelId",
"planningModelProvider", "planningModelId",
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "graphResumeRetryCount", "resumeLimboTipSha", "resumeLimboStepSignature", "postReviewFixCount", "recoveryRetryCount", "taskDoneRetryCount", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
"error", "summary", "thinkingLevel", "executionMode",
"error", "summary", "thinkingLevel", "executionMode", "plannerOversightLevel",
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt", "tokenUsageModelProvider", "tokenUsageModelId", "tokenUsagePerModel", "tokenBudgetSoftAlertedAt", "tokenBudgetHardAlertedAt", "tokenBudgetOverride",
"createdAt", "updatedAt", "columnMovedAt", "firstExecutionAt", "cumulativeActiveMs", "columnDwellMs", "executionStartedAt", "executionCompletedAt",
"dependencies", "steps", "customFields", "comments", "review", "reviewState", "workflowStepResults", "steeringComments",
@@ -2761,7 +2764,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
"validatorModelProvider", "validatorModelId",
"planningModelProvider", "planningModelId",
"mergeRetries", "workflowStepRetries", "stuckKillCount", "resumeLimboCount", "graphResumeRetryCount", "resumeLimboTipSha", "resumeLimboStepSignature", "postReviewFixCount", "recoveryRetryCount", "taskDoneRetryCount", "worktreeSessionRetryCount", "completionHandoffLimboRecoveryCount", "verificationFailureCount", "mergeConflictBounceCount", "mergeAuditBounceCount", "mergeTransientRetryCount", "branchConflictRecoveryCount", "reviewerContextRetryCount", "reviewerFallbackRetryCount", "nextRecoveryAt",
"error", "summary", "thinkingLevel", "executionMode",
"error", "summary", "thinkingLevel", "executionMode", "plannerOversightLevel",
"tokenUsageInputTokens", "tokenUsageOutputTokens", "tokenUsageCachedTokens", "tokenUsageCacheWriteTokens", "tokenUsageTotalTokens", "tokenUsageFirstUsedAt", "tokenUsageLastUsedAt", "tokenUsageModelProvider", "tokenUsageModelId", "tokenUsagePerModel", "tokenBudgetSoftAlertedAt", "tokenBudgetHardAlertedAt", "tokenBudgetOverride",
"createdAt", "updatedAt", "columnMovedAt", "firstExecutionAt", "cumulativeActiveMs", "columnDwellMs", "executionStartedAt", "executionCompletedAt",
"dependencies", "steps", "customFields", "attachments", "steeringComments",
@@ -5021,6 +5024,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
thinkingLevel: input.thinkingLevel,
reviewLevel: input.reviewLevel,
executionMode: input.executionMode,
plannerOversightLevel: input.plannerOversightLevel,
baseBranch: input.baseBranch,
branch: input.branch,
missionId: input.missionId,
@@ -8313,7 +8317,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
async updateTask(
id: string,
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("./types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("./types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("./types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; graphResumeRetryCount?: number | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; postReviewFixCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; reviewState?: import("./types.js").TaskReviewState | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("./types.js").TaskGithubTracking | null; gitlabTracking?: (Omit<import("./types.js").TaskGitLabTracking, "item"> & { item?: import("./types.js").TaskGitLabTrackedItem | null }) | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; workflowTransitionNotification?: import("./types.js").Task["workflowTransitionNotification"] | null; missionId?: string | null; sliceId?: string | null },
updates: { title?: string; description?: string; priority?: TaskPriority | null; prompt?: string; worktree?: string | null; workspaceWorktrees?: import("./types.js").Task["workspaceWorktrees"]; status?: string | null; dependencies?: string[]; steps?: import("./types.js").TaskStep[]; customFields?: Record<string, unknown>; currentStep?: number; blockedBy?: string | null; overlapBlockedBy?: string | null; assignedAgentId?: string | null; pausedByAgentId?: string | null; pausedReason?: string | null; tokenBudgetSoftAlertedAt?: string | null; worktrunkFallbackAlertedAt?: string | null; worktrunkFailure?: import("./types.js").Task["worktrunkFailure"] | null; tokenBudgetHardAlertedAt?: string | null; tokenBudgetOverride?: import("./types.js").TaskTokenBudgetOverride | null; dispatchStormCount?: number | null; lastDispatchAt?: string | null; assigneeUserId?: string | null; scopeOverride?: boolean | null; scopeOverrideReason?: string | null; scopeAutoWiden?: string[] | null; nodeId?: string | null; effectiveNodeId?: string | null; effectiveNodeSource?: string | null; checkedOutBy?: string | null; checkedOutAt?: string | null; checkoutNodeId?: string | null; checkoutRunId?: string | null; checkoutLeaseRenewedAt?: string | null; checkoutLeaseEpoch?: number | null; paused?: boolean; baseBranch?: string | null; autoMerge?: boolean | null; branch?: string | null; executionStartBranch?: string | null; baseCommitSha?: string | null; size?: "S" | "M" | "L"; reviewLevel?: number; executionMode?: import("./types.js").ExecutionMode | null; plannerOversightLevel?: import("./types.js").PlannerOversightLevel | null; mergeRetries?: number; workflowStepRetries?: number; stuckKillCount?: number | null; resumeLimboCount?: number | null; graphResumeRetryCount?: number | null; resumeLimboTipSha?: string | null; resumeLimboStepSignature?: string | null; postReviewFixCount?: number | null; recoveryRetryCount?: number | null; taskDoneRetryCount?: number | null; worktreeSessionRetryCount?: number | null; completionHandoffLimboRecoveryCount?: number | null; verificationFailureCount?: number | null; mergeConflictBounceCount?: number | null; mergeAuditBounceCount?: number | null; mergeTransientRetryCount?: number | null; branchConflictRecoveryCount?: number | null; reviewerContextRetryCount?: number | null; reviewerFallbackRetryCount?: number | null; nextRecoveryAt?: string | null; enabledWorkflowSteps?: string[]; noCommitsExpected?: boolean | null; modelProvider?: string | null; modelId?: string | null; validatorModelProvider?: string | null; validatorModelId?: string | null; planningModelProvider?: string | null; planningModelId?: string | null; thinkingLevel?: string | null; error?: string | null; summary?: string | null; sessionFile?: string | null; firstExecutionAt?: string | null; cumulativeActiveMs?: number | null; executionStartedAt?: string | null; executionCompletedAt?: string | null; review?: import("./types.js").TaskReview | null; reviewState?: import("./types.js").TaskReviewState | null; workflowStepResults?: import("./types.js").WorkflowStepResult[] | null; mergeDetails?: import("./types.js").MergeDetails | null; sourceIssue?: import("./types.js").TaskSourceIssue | null; sourceMetadataPatch?: Record<string, unknown> | null; githubTracking?: import("./types.js").TaskGithubTracking | null; gitlabTracking?: (Omit<import("./types.js").TaskGitLabTracking, "item"> & { item?: import("./types.js").TaskGitLabTrackedItem | null }) | null; tokenUsage?: import("./types.js").TaskTokenUsage | null; modifiedFiles?: string[] | null; workflowTransitionNotification?: import("./types.js").Task["workflowTransitionNotification"] | null; missionId?: string | null; sliceId?: string | null },
runContext?: RunMutationContext,
): Promise<Task> {
return this.withTaskLock(id, () => this.updateTaskUnlocked(id, updates, runContext));
@@ -9129,6 +9133,11 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
} else if (updates.executionMode !== undefined) {
task.executionMode = updates.executionMode as import("./types.js").ExecutionMode;
}
if (updates.plannerOversightLevel === null) {
task.plannerOversightLevel = undefined;
} else if (updates.plannerOversightLevel !== undefined) {
task.plannerOversightLevel = updates.plannerOversightLevel as import("./types.js").PlannerOversightLevel;
}
if (updates.error === null) {
task.error = undefined;
} else if (updates.error !== undefined) {

View File

@@ -272,6 +272,18 @@ export type ExecutionMode = (typeof EXECUTION_MODES)[number];
/** Default execution mode for new tasks */
export const DEFAULT_EXECUTION_MODE: ExecutionMode = "standard";
/*
* FNXC:PlannerOversight 2026-07-04-00:00:
* Per-task override of the workflow-native `plannerOversightLevel` setting
* (declared in `BUILTIN_OVERSIGHT_SETTINGS`, packages/core/src/builtin-workflow-settings.ts).
* When a task sets this field, it wins over the workflow's effective oversight
* value; unset (NULL in storage) means "inherit the workflow default". Values,
* order, and default here must stay in sync with `BUILTIN_OVERSIGHT_SETTINGS`.
*/
export const PLANNER_OVERSIGHT_LEVELS = ["off", "observe", "steer", "autonomous"] as const;
export type PlannerOversightLevel = (typeof PLANNER_OVERSIGHT_LEVELS)[number];
export const DEFAULT_PLANNER_OVERSIGHT_LEVEL: PlannerOversightLevel = "autonomous";
/** Controls whether triage should require completion documentation artifacts in task specs. */
export const COMPLETION_DOCUMENTATION_MODES = ["off", "changeset", "changelog"] as const;
export type CompletionDocumentationMode = (typeof COMPLETION_DOCUMENTATION_MODES)[number];
@@ -2456,6 +2468,11 @@ export interface Task {
* - "fast": Expedited execution with minimal overhead for simple tasks
* Defaults to "standard" when not specified. */
executionMode?: ExecutionMode;
/** Per-task override of the workflow-native planner oversight level (FNXC:PlannerOversight).
* When set, wins over the workflow's effective `plannerOversightLevel`. Unset means
* "inherit workflow default" — see `resolveEffectivePlannerOversightLevel` in
* workflow-settings-resolver.ts for precedence. */
plannerOversightLevel?: PlannerOversightLevel;
/** Explicitly assigned agent ID for task-agent linking. Distinct from Agent.taskId active execution state. */
assignedAgentId?: string;
/** Per-task node override. When set, this task routes to the specified node instead of the project's default node. Undefined means use the project default. Use empty string to explicitly clear. */
@@ -2733,6 +2750,10 @@ export interface TaskCreateInput {
* - "fast": Expedited execution with minimal overhead for simple tasks
* Defaults to "standard" when not specified. */
executionMode?: ExecutionMode;
/** Per-task override of the workflow-native planner oversight level (FNXC:PlannerOversight).
* When set, wins over the workflow's effective `plannerOversightLevel`. Unset means
* "inherit workflow default". */
plannerOversightLevel?: PlannerOversightLevel;
}
// ── Todo List Types ──────────────────────────────────────────────────────
@@ -4878,6 +4899,8 @@ export interface ArchivedTaskEntry {
* - "standard": Full execution with complete review workflow (default)
* - "fast": Expedited execution with minimal overhead for simple tasks */
executionMode?: ExecutionMode;
/** Per-task override of the workflow-native planner oversight level at time of archival. */
plannerOversightLevel?: PlannerOversightLevel;
prInfo?: PrInfo;
prInfos?: PrInfo[];
issueInfo?: IssueInfo;

View File

@@ -35,6 +35,7 @@ import {
import { resolveEffectiveSettingValues, findOrphanedSettingValues } from "./workflow-settings.js";
import { BUILTIN_WORKFLOW_SETTINGS } from "./builtin-workflow-settings.js";
import type { WorkflowSettingDefinition, WorkflowIr, WorkflowOptionalGroupConfig } from "./workflow-ir-types.js";
import { PLANNER_OVERSIGHT_LEVELS, DEFAULT_PLANNER_OVERSIGHT_LEVEL, type PlannerOversightLevel } from "./types.js";
export const PLAN_REVIEW_MAX_REVISIONS_SETTING_ID = "planReviewMaxRevisions";
export const CODE_REVIEW_MAX_REVISIONS_SETTING_ID = "codeReviewMaxRevisions";
@@ -226,3 +227,30 @@ export async function resolveEffectiveSettingsDetailed(
}
return effectiveFrom(store, ir, effectiveWorkflowId, projectId);
}
function isPlannerOversightLevel(value: unknown): value is PlannerOversightLevel {
return typeof value === "string" && (PLANNER_OVERSIGHT_LEVELS as readonly string[]).includes(value);
}
/**
* FNXC:PlannerOversight 2026-07-04-00:00:
* Resolves the effective planner oversight level for a task: a per-task
* `Task.plannerOversightLevel` override (FN-7509) always wins over the
* workflow's effective `plannerOversightLevel` setting value (declared in
* `BUILTIN_OVERSIGHT_SETTINGS`, resolved via {@link resolveEffectiveSettings}).
* If neither is set, or either value is an unrecognized string (defensive
* normalization — never trust arbitrary/legacy input), falls back to
* `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous"). Pure and never throws.
*/
export function resolveEffectivePlannerOversightLevel(
taskOverride: PlannerOversightLevel | string | null | undefined,
workflowEffective: PlannerOversightLevel | string | null | undefined,
): PlannerOversightLevel {
if (isPlannerOversightLevel(taskOverride)) {
return taskOverride;
}
if (isPlannerOversightLevel(workflowEffective)) {
return workflowEffective;
}
return DEFAULT_PLANNER_OVERSIGHT_LEVEL;
}