diff --git a/.changeset/fn-8341-deepening-checkpoint-removal.md b/.changeset/fn-8341-deepening-checkpoint-removal.md new file mode 100644 index 0000000000..c7fe24ba16 --- /dev/null +++ b/.changeset/fn-8341-deepening-checkpoint-removal.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Remove the Planning Mode deepening checkpoint and fixed interview depth caps. +category: breaking +dev: User validation replaces AI completion and checkpoint-driven finalization. diff --git a/.changeset/fn-8341-planning-reactive-backend.md b/.changeset/fn-8341-planning-reactive-backend.md new file mode 100644 index 0000000000..52c21758a1 --- /dev/null +++ b/.changeset/fn-8341-planning-reactive-backend.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Make Planning Mode an infinite interview validated explicitly by the user. +category: feature +dev: Running plans update on every question with normalized alternatives, pros/cons, and Other steering. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 7a7c7f7c66..376279e5f1 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -518,12 +518,10 @@ When an active Planning AI generation appears stuck, Planning Mode automatically Use **Copy prompt** in the error panel or an active interview question to copy the original “What do you want to build?” text, then paste it into **New session** to restart cleanly. - - - + +Planning Mode keeps asking high-impact, context-aware questions until you choose **Validate plan**. The running title, description, and deliverables are available throughout the interview; the AI never ends it on its own. Selection questions provide alternatives with pros and cons plus an **Other** free-text choice, whose wording follows your input language and whose answer steers the next question. You may edit an earlier answer by question ID without losing later answers; Planning re-derives the running plan and appends a fresh next question. - -Before Planning Mode shows **Planning Complete!** or the final plan summary, it first asks **Would you like to go deeper?**. If the final AI response is incomplete, Planning Mode requests a clean complete response once; if that fails, it shows a retryable session error rather than presenting a partial plan. A read-only preview of the generated plan appears above the refinement options so you can review its title, formatted description, and key deliverables before deciding. The suggested themes are plan-specific: the planning AI proposes topics tailored to your plan's title, description, and deliverables as part of its completion response, surfacing angles you may not have anticipated. When the AI does not supply any themes, Planning Mode falls back to a generic, regex-derived set (scope, edge cases, UX, dependencies, testing, rollout) inferred from the interview text. Either way, select one or more suggested themes to continue the interview, use **Other** to add a custom topic, or choose **Proceed to final plan** to reveal the pending summary and task-creation actions. +Choose **Validate plan** when the running plan is ready for task creation. Validation is durable and is required before **Create task**, **Create tasks**, or **Start breakdown**; those actions reject unvalidated sessions. - **Branch strategy** options mirror Subtask Breakdown semantics: - `Use project/default branch` diff --git a/packages/cli/src/commands/task.ts b/packages/cli/src/commands/task.ts index 95fde60359..e7b6b16ae9 100644 --- a/packages/cli/src/commands/task.ts +++ b/packages/cli/src/commands/task.ts @@ -2,7 +2,7 @@ import { TaskStore, COLUMNS, COLUMN_LABELS, CentralCore, buildAutoPauseClearPatc import { isInReviewMissingWorktreeSessionStartFailure, runAiMerge, landWorkspaceTask, installBaselineArchiveWorktreeDisposer } from "@fusion/engine"; import { createInterface } from "node:readline/promises"; import type { PlanningQuestion, PlanningSummary } from "@fusion/core"; -import { createSession, submitResponse, RateLimitError, SessionNotFoundError, InvalidSessionStateError } from "@fusion/dashboard/planning"; +import { createSession, submitResponse, validateSession, RateLimitError, SessionNotFoundError, InvalidSessionStateError } from "@fusion/dashboard/planning"; import { watchFile, unwatchFile, statSync, existsSync, readFileSync } from "node:fs"; import { basename, join } from "node:path"; import * as dashboard from "@fusion/dashboard"; @@ -2287,12 +2287,18 @@ export async function runTaskPlan( throw promptErr; } - // Submit response and get next question or summary + // `/validate` is an explicit user command available in every text answer. + // The session never auto-finalizes: only this command calls validateSession. let result: { type: "question"; data: PlanningQuestion } | { type: "complete"; data: PlanningSummary }; try { + const requestedValidation = Object.values(response).some((value) => + typeof value === "string" && value.trim().toLowerCase() === "/validate", + ); showThinking(); - result = await submitResponse(sessionId, response) as typeof result; + result = requestedValidation + ? { type: "complete", data: await validateSession(sessionId) } + : await submitResponse(sessionId, response) as typeof result; clearThinking(); } catch (err) { clearThinking(); diff --git a/packages/core/src/index.gate.ts b/packages/core/src/index.gate.ts index d12a8cffde..3d1860cf04 100644 --- a/packages/core/src/index.gate.ts +++ b/packages/core/src/index.gate.ts @@ -42,7 +42,7 @@ version of this file was falsified: 141/335 gate tests failed on missing etc. pulled in by production modules, not test files). */ -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, resolveEphemeralTaskCreationPolicy, 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, OVERSEER_INTERVENTION_MUTATION } 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, resolveEphemeralTaskCreationPolicy, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeIntegrationWorktreeMode, normalizeMergeAdvanceAutoSyncMode, DEFAULT_GITLAB_API_BASE_URL, DEFAULT_GITLAB_INSTANCE_URL, resolveGitlabConfig, resolveGitlabEnabled, 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, OVERSEER_INTERVENTION_MUTATION } 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, 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, ReportMode, ReportActionType, 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, ProposedTaskMetadata, EphemeralTaskCreationPolicy, 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, PlannerOversightStage, PlannerInterventionAction, PlannerInterventionOutcome, PlannerInterventionSourceLink, PlannerInterventionEntry, BackupSettingsMigrationCandidate, BackupSettingsMigrationConflict } from "./types.js"; export { AGENT_VALID_TRANSITIONS, DUPLICATE_OF_METADATA_KEY, assertNotWorkspaceTaskMerge, isWorkspaceTask, WorkspaceTaskMergeError } from "./types.js"; export { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 31cb8124e4..a2b344a008 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -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, REVIEW_ARTIFACTS_MODES, LIVE_DEMO_ARTIFACT_MIME_TYPE, isReviewArtifact, parseReviewArtifactsModeOverride, resolveReviewArtifactsMode, classifyReviewArtifactTask, isReviewArtifactGenerationEligible, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, resolveEphemeralTaskCreationPolicy, 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, OVERSEER_INTERVENTION_MUTATION } 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, REVIEW_ARTIFACTS_MODES, LIVE_DEMO_ARTIFACT_MIME_TYPE, isReviewArtifact, parseReviewArtifactsModeOverride, resolveReviewArtifactsMode, classifyReviewArtifactTask, isReviewArtifactGenerationEligible, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, resolveEphemeralTaskCreationPolicy, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeIntegrationWorktreeMode, normalizeMergeAdvanceAutoSyncMode, DEFAULT_GITLAB_API_BASE_URL, DEFAULT_GITLAB_INSTANCE_URL, resolveGitlabConfig, resolveGitlabEnabled, 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, OVERSEER_INTERVENTION_MUTATION } 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, 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, ReportMode, ReportActionType, 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, ReviewArtifactsMode, ReviewArtifactTaskClassification, 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, ProposedTaskMetadata, EphemeralTaskCreationPolicy, 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, PlannerOversightStage, PlannerInterventionAction, PlannerInterventionOutcome, PlannerInterventionSourceLink, PlannerInterventionEntry, ExecutorOverseerSignalMemory, BackupSettingsMigrationCandidate, BackupSettingsMigrationConflict } from "./types.js"; export { AGENT_VALID_TRANSITIONS, DUPLICATE_OF_METADATA_KEY, assertNotWorkspaceTaskMerge, isWorkspaceTask, WorkspaceTaskMergeError } from "./types.js"; export { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 72fed288e6..8d0dc23f4e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -4811,18 +4811,6 @@ export interface ArchivedTaskEntry { /** Type of planning question presented to the user */ export type PlanningQuestionType = "text" | "single_select" | "multi_select" | "confirm"; -/** Exact Planning Mode checkpoint prompt shown before a final summary can be displayed. */ -export const PLANNING_DEEPEN_CHECKPOINT_QUESTION = "Would you like to go deeper?"; - -/** Reserved question id for the server-owned Planning Mode deepening checkpoint. */ -export const PLANNING_DEEPEN_CHECKPOINT_ID = "__planning_deepen_checkpoint__"; - -/** Reserved checkbox option id that lets the user accept the pending final summary. */ -export const PLANNING_DEEPEN_PROCEED_OPTION_ID = "__planning_deepen_proceed_to_final__"; - -/** Reserved response key accepted as an explicit proceed signal for the deepening checkpoint. */ -export const PLANNING_DEEPEN_PROCEED_RESPONSE_KEY = "__planning_deepen_proceed__"; - /** Isolation mode for project execution */ export type IsolationMode = "in-process" | "child-process"; @@ -5486,18 +5474,7 @@ export interface PlanningQuestion { type: PlanningQuestionType; question: string; description?: string; - options?: Array<{ id: string; label: string; description?: string }>; - /** - * FNXC:PlanningMode 2026-07-16-00:00: - * FN-8065 / GitHub #2150 requires the deepening checkpoint to carry a read-only preview - * of its withheld pendingSummary. Keeping this optional preserves legacy persisted - * currentQuestion rows and leaves ordinary interview questions unchanged. - */ - planPreview?: { - title: string; - description: string; - keyDeliverables: string[]; - }; + options?: Array<{ id: string; label: string; description?: string; pros?: string[]; cons?: string[]; isOther?: boolean; customText?: string }>; } /** The final summary generated after planning conversation completes */ @@ -5508,17 +5485,6 @@ export interface PlanningSummary { priority?: TaskPriority; suggestedDependencies: string[]; keyDeliverables: string[]; - /** - * FNXC:PlanningMode 2026-07-05-00:00: - * The planning AI proposes plan-specific deepening topics (instead of the - * fixed, regex-derived generic buckets) so the "Would you like to go - * deeper?" checkpoint surfaces suggestions aligned with the user's actual - * plan — including angles they had not anticipated. Optional so existing - * persisted rows/payloads without it remain valid; the dashboard falls - * back to the generic theme candidates when absent or empty - * (FN-7616 / issue #1912). - */ - deepeningThemes?: Array<{ id?: string; label: string; description?: string }>; } /** Response from planning endpoints - either a question or the final summary */ @@ -5534,6 +5500,8 @@ export interface PlanningSession { history: Array<{ question: PlanningQuestion; response: unknown }>; currentQuestion?: PlanningQuestion; summary?: PlanningSummary; + /** User explicitly validated the continuously maintained running plan. */ + validated?: boolean; /** * Optional per-session auto-merge override for tasks planned in this session. * Not separately persisted; durable form is a branch_groups row keyed by session id. diff --git a/packages/dashboard/app/api/legacy.ts b/packages/dashboard/app/api/legacy.ts index bb8c49a6b0..3bf52a4122 100644 --- a/packages/dashboard/app/api/legacy.ts +++ b/packages/dashboard/app/api/legacy.ts @@ -2638,14 +2638,12 @@ export type AgentOnboardingStreamEvent = export function startPlanning( initialPlan: string, projectId?: string, - planningOptions?: { planningDepth?: "small" | "medium" | "large"; customQuestionCount?: number }, + ): Promise { return api(withProjectId("/planning/start", projectId), { method: "POST", body: JSON.stringify({ initialPlan, - planningDepth: planningOptions?.planningDepth, - customQuestionCount: planningOptions?.customQuestionCount, }), }); } @@ -2671,7 +2669,7 @@ export function startPlanningStreaming( initialPlan: string, projectId?: string, modelOverride?: { planningModelProvider?: string; planningModelId?: string; thinkingLevel?: ThinkingLevel }, - planningOptions?: { planningDepth?: "small" | "medium" | "large"; customQuestionCount?: number; clarificationEnabled?: boolean }, + planningOptions?: { clarificationEnabled?: boolean }, existingSessionId?: string, ): Promise<{ sessionId: string }> { return api<{ sessionId: string }>(withProjectId("/planning/start-streaming", projectId), { @@ -2681,14 +2679,17 @@ export function startPlanningStreaming( planningModelProvider: modelOverride?.planningModelProvider, planningModelId: modelOverride?.planningModelId, thinkingLevel: modelOverride?.thinkingLevel, - planningDepth: planningOptions?.planningDepth, - customQuestionCount: planningOptions?.customQuestionCount, clarificationEnabled: planningOptions?.clarificationEnabled, ...(existingSessionId ? { existingSessionId } : {}), }), }); } +/** Explicitly validate the current running planning summary before creating work. */ +export function validatePlanningSession(sessionId: string, projectId?: string): Promise<{ summary: PlanningSummary; validated: boolean }> { + return api<{ summary: PlanningSummary; validated: boolean }>(withProjectId(`/planning/${encodeURIComponent(sessionId)}/validate`, projectId), { method: "POST" }); +} + /** Submit a response to the current planning question */ export function respondToPlanning( sessionId: string, @@ -2705,11 +2706,13 @@ export function respondToPlanning( export function rewindPlanningSession( sessionId: string, projectId?: string, + questionId?: string, ): Promise<{ currentQuestion: PlanningQuestion; history: Array<{ question: PlanningQuestion; response: unknown; thinkingOutput?: string }> }> { return api<{ currentQuestion: PlanningQuestion; history: Array<{ question: PlanningQuestion; response: unknown; thinkingOutput?: string }> }>( withProjectId(`/planning/${encodeURIComponent(sessionId)}/back`, projectId), { method: "POST", + ...(questionId ? { body: JSON.stringify({ questionId }) } : {}), }, ); } diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index 9493734613..6d85faa0b1 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -7,8 +7,6 @@ import remarkGfm from "remark-gfm"; import type { Task, PlanningQuestion, PlanningSummary, TaskPriority, ThinkingLevel } from "@fusion/core"; import { DEFAULT_TASK_PRIORITY, - PLANNING_DEEPEN_CHECKPOINT_ID, - PLANNING_DEEPEN_PROCEED_OPTION_ID, TASK_PRIORITIES, THINKING_LEVELS, getErrorMessage, @@ -20,6 +18,7 @@ import { rewindPlanningSession, retryPlanningSession, createTaskFromPlanning, + validatePlanningSession, connectPlanningStream, fetchAiSession, fetchAiSessions, @@ -444,8 +443,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat const [planningModelProvider, setPlanningModelProvider] = useState(undefined); const [planningModelId, setPlanningModelId] = useState(undefined); const [planningThinkingLevel, setPlanningThinkingLevel] = useState(""); - const [planningDepth, setPlanningDepth] = useState<"small" | "medium" | "large">("medium"); - const [customQuestionCount, setCustomQuestionCount] = useState(""); const [clarificationEnabled, setClarificationEnabled] = useState(true); const [clarificationSettingsLoading, setClarificationSettingsLoading] = useState(true); const [loadedModels, setLoadedModels] = useState([]); @@ -737,8 +734,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat setPlanningModelProvider(undefined); setPlanningModelId(undefined); setPlanningThinkingLevel(""); - setPlanningDepth("medium"); - setCustomQuestionCount(""); currentSessionIdRef.current = null; }, [resetPlanningAutoRetryBudget]); @@ -1138,22 +1133,12 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat ? { planningModelProvider, planningModelId, thinkingLevel: planningThinkingLevel || undefined } : (planningThinkingLevel ? { thinkingLevel: planningThinkingLevel } : undefined); - const parsedCustomQuestionCount = customQuestionCount.trim() - ? Number.parseInt(customQuestionCount, 10) - : undefined; - const draftSessionId = draftSessionIdRef.current; const { sessionId } = await startPlanningStreaming( startedPlan, projectId, modelOverride, - { - planningDepth, - customQuestionCount: Number.isInteger(parsedCustomQuestionCount) - ? parsedCustomQuestionCount - : undefined, - clarificationEnabled, - }, + { clarificationEnabled }, draftSessionId ?? undefined, ); draftSessionIdRef.current = null; @@ -1173,9 +1158,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat clarificationEnabled, clarificationSettingsLoading, connectToPlanningStream, - customQuestionCount, initialPlan, - planningDepth, planningModelId, planningModelProvider, planningThinkingLevel, @@ -1906,42 +1889,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat [projectId, resetPlanningAutoRetryBudget, view] ); - const handleRefineFurther = useCallback(async () => { - if (view.type !== "summary" || refineSummaryInFlightRef.current) { - return; - } - - const { session, summary } = view; - const sessionId = session.sessionId; - currentSessionIdRef.current = sessionId; - - refineSummaryInFlightRef.current = true; - setIsRefiningSummary(true); - setError(null); - setIsRetrying(false); - resetPlanningAutoRetryBudget(); - setStreamingOutput(""); - setView({ type: "loading" }); - liveGenerationSessionIdRef.current = sessionId; - - connectToPlanningStream(sessionId); - - try { - await respondToPlanning(sessionId, { refine: true }, projectId); - } catch (err) { - const message = getErrorMessage(err) || t("planning.failedRefinePlan", "Failed to refine plan"); - if (/generation already in progress/i.test(message)) { - return; - } - refineSummaryInFlightRef.current = false; - setIsRefiningSummary(false); - streamConnectionRef.current?.close(); - streamConnectionRef.current = null; - setError(message); - setView({ type: "summary", session, summary: editedSummary ?? summary }); - } - }, [connectToPlanningStream, editedSummary, projectId, resetPlanningAutoRetryBudget, view]); - const handleStopGeneration = useCallback(async () => { const sessionId = currentSessionIdRef.current; if (!sessionId) { @@ -1978,6 +1925,19 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat await startPlanningRetry(view.session, { auto: false }); }, [resetPlanningAutoRetryBudget, startPlanningRetry, view]); + const handleValidatePlan = useCallback(async () => { + if (view.type !== "question") return; + setError(null); + try { + const result = await validatePlanningSession(view.session.sessionId, projectId); + const summary = normalizePlanningSummary(result.summary); + setEditedSummary(summary); + setView({ type: "summary", session: { ...view.session, summary }, summary }); + } catch (err) { + setError(getErrorMessage(err) || t("planning.failedValidatePlan", "Failed to validate plan")); + } + }, [projectId, t, view]); + const handleCreateTask = useCallback(async () => { if (view.type !== "summary") return; if ((branchMode === "existing" || branchMode === "custom-new") && !branchName.trim()) return; @@ -1987,6 +1947,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat try { const completedSessionId = view.session.sessionId; + await validatePlanningSession(completedSessionId, projectId); const normalizedSummary = editedSummary ? normalizePlanningSummary(editedSummary) : undefined; const task = await createTaskFromPlanning(completedSessionId, normalizedSummary, projectId, { branchSelection: { @@ -2021,6 +1982,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat try { const normalizedSummary = editedSummary ? normalizePlanningSummary(editedSummary) : undefined; + await validatePlanningSession(view.session.sessionId, projectId); const result = await startPlanningBreakdown(view.session.sessionId, normalizedSummary, projectId); const normalizedSubtasks = (Array.isArray(result.subtasks) ? result.subtasks : []).map(normalizeSubtaskItem); setView({ @@ -2045,6 +2007,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat try { const completedSessionId = view.sessionId; + await validatePlanningSession(completedSessionId, projectId); const result = await createTasksFromPlanning( completedSessionId, buildCompactPlanningSubtaskDrafts( @@ -2080,8 +2043,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat setPlanningModelProvider(undefined); setPlanningModelId(undefined); setPlanningThinkingLevel(""); - setPlanningDepth("medium"); - setCustomQuestionCount(""); currentSessionIdRef.current = null; setSelectedSessionId(null); handleClose(); @@ -2419,50 +2380,11 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat -
+
-

- {t("planning.depthBlurb", "Plan size sets default interview depth. Questions lets you override with an exact count.")} -

-
-
- {(["small", "medium", "large"] as const).map((depthValue) => { - const depthLabels: Record = { - small: t("planning.depthSmall", "Small"), - medium: t("planning.depthMedium", "Medium"), - large: t("planning.depthLarge", "Large"), - }; - const depthOption = { value: depthValue, label: depthLabels[depthValue] }; - return ( - - );})} -
- - -
@@ -2571,6 +2493,9 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat isBackPending={isBackPending} onCopyPlanPrompt={activePlanPrompt.trim() ? handleCopyPlanPrompt : undefined} /> + )} @@ -2588,9 +2513,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat onBaseBranchChange={setBaseBranch} onCreateTask={handleCreateTask} onBreakIntoTasks={handleStartBreakdown} - onRefine={() => { - void handleRefineFurther(); - }} isCreatingTask={isCreatingTask} isStartingBreakdown={isStartingBreakdown} isRefiningSummary={isRefiningSummary} @@ -2645,16 +2567,6 @@ function QuestionForm({ question: rawQuestion, progress, historyEntries, onSubmi const { t } = useTranslation("app"); const question = normalizeQuestionOptions(rawQuestion); const questionOptions = question.options ?? []; - const isDeepeningCheckpoint = question.id === PLANNING_DEEPEN_CHECKPOINT_ID; - const planPreview = isDeepeningCheckpoint && question.planPreview - ? { - title: typeof question.planPreview.title === "string" ? question.planPreview.title : "", - description: typeof question.planPreview.description === "string" ? question.planPreview.description : "", - keyDeliverables: Array.isArray(question.planPreview.keyDeliverables) - ? question.planPreview.keyDeliverables.filter((deliverable): deliverable is string => typeof deliverable === "string") - : [], - } - : undefined; const [response, setResponse] = useState({}); const [textValue, setTextValue] = useState(""); const [commentValue, setCommentValue] = useState(""); @@ -2795,41 +2707,6 @@ function QuestionForm({ question: rawQuestion, progress, historyEntries, onSubmi {t("planning.questionProgress", "Question {{progress}} of ~3", { progress })} - {/* - FNXC:PlanningMode 2026-07-16-00:00: - FN-8065 / GitHub #2150 requires the deepening checkpoint to show its persisted - pendingSummary preview before users choose whether to refine or proceed. The strict - checkpoint-and-payload guard preserves ordinary questions and legacy checkpoint rows. - */} - {planPreview && ( -
-
-

- {t("planning.checkpointPlanPreviewHeading", "Your plan so far")} -

-

- {t("planning.checkpointPlanPreviewDescription", "Review the plan below, then choose to refine further or proceed.")} -

-
-
{planPreview.title}
- {planPreview.description && ( -
- {planPreview.description} -
- )} - {planPreview.keyDeliverables.length > 0 && ( -
-
{t("planning.keyDeliverables", "Key Deliverables")}
-
    - {planPreview.keyDeliverables.map((deliverable, index) => ( -
  • {deliverable}
  • - ))} -
-
- )} -
- )} -
{/* FNXC:PlanningInterview 2026-07-16-00:00: @@ -2924,7 +2801,6 @@ function QuestionForm({ question: rawQuestion, progress, historyEntries, onSubmi
{questionOptions.map((option) => { const selected = Array.isArray(response[question.id]) ? (response[question.id] as string[]) : []; - const isProceedOption = isDeepeningCheckpoint && option.id === PLANNING_DEEPEN_PROCEED_OPTION_ID; return (
- + {onRefine && ( + + )}