From aa757bc1a3ef0ff1c969266c651e396d3ab1000a Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 4 Jul 2026 12:32:09 -0700 Subject: [PATCH] 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) --- ...509-per-task-planner-oversight-override.md | 7 ++ docs/settings-reference.md | 2 +- packages/core/src/__tests__/db.test.ts | 45 +++++++++++ .../core/src/__tests__/store-update.test.ts | 75 +++++++++++++++++++ .../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 ++++++ .../core/src/workflow-settings-resolver.ts | 28 +++++++ 11 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 .changeset/fn-7509-per-task-planner-oversight-override.md diff --git a/.changeset/fn-7509-per-task-planner-oversight-override.md b/.changeset/fn-7509-per-task-planner-oversight-override.md new file mode 100644 index 0000000000..4ccbe4ee99 --- /dev/null +++ b/.changeset/fn-7509-per-task-planner-oversight-override.md @@ -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. diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 2da00161f8..ebf2f4382a 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -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. diff --git a/packages/core/src/__tests__/db.test.ts b/packages/core/src/__tests__/db.test.ts index fe82717047..2bc5842e34 100644 --- a/packages/core/src/__tests__/db.test.ts +++ b/packages/core/src/__tests__/db.test.ts @@ -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"); diff --git a/packages/core/src/__tests__/store-update.test.ts b/packages/core/src/__tests__/store-update.test.ts index d2529e0be6..5d418b02a1 100644 --- a/packages/core/src/__tests__/store-update.test.ts +++ b/packages/core/src/__tests__/store-update.test.ts @@ -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 () => { diff --git a/packages/core/src/__tests__/workflow-settings-resolver.test.ts b/packages/core/src/__tests__/workflow-settings-resolver.test.ts index 6956718c0f..2ab1b6d0db 100644 --- a/packages/core/src/__tests__/workflow-settings-resolver.test.ts +++ b/packages/core/src/__tests__/workflow-settings-resolver.test.ts @@ -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"); + }); +}); diff --git a/packages/core/src/db.ts b/packages/core/src/db.ts index 8c5e427752..a22c9b518c 100644 --- a/packages/core/src/db.ts +++ b/packages/core/src/db.ts @@ -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"); + }); + } + } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8eb684f44b..b0b2d24b84 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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, diff --git a/packages/core/src/mesh-task-replication.ts b/packages/core/src/mesh-task-replication.ts index 44e004f5bf..24aa64c696 100644 --- a/packages/core/src/mesh-task-replication.ts +++ b/packages/core/src/mesh-task-replication.ts @@ -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({ diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 212b81d49d..de6178b9be 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -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 { 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 { "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 { "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; 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 | null; githubTracking?: import("./types.js").TaskGithubTracking | null; gitlabTracking?: (Omit & { 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; 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 | null; githubTracking?: import("./types.js").TaskGithubTracking | null; gitlabTracking?: (Omit & { 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 { 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) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0cf9f76c8b..6634cb35a6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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; diff --git a/packages/core/src/workflow-settings-resolver.ts b/packages/core/src/workflow-settings-resolver.ts index eda62dcd63..37ce418f43 100644 --- a/packages/core/src/workflow-settings-resolver.ts +++ b/packages/core/src/workflow-settings-resolver.ts @@ -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; +}