From ec0256f7e55e8891b0d292609d6682ff90bbe331 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 2 Jul 2026 13:38:02 -0700 Subject: [PATCH] FN-7444: add planning summary deepening checkpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Planning Mode now holds completed summaries behind a mandatory deepening prompt before finalization. - Add shared constants and persistence for pending planning summaries. - Insert the “Would you like to go deeper?” checkpoint with proceed, themed, and custom-topic responses. - Update modal handling, API formatting, docs, changeset, and regression coverage for persisted and streamed planning flows. Files changed: .changeset/fn-7444-planning-deepening-checkpoint.md | 7 + docs/dashboard-guide.md | 4 + packages/core/src/index.ts | 2 +- packages/core/src/types.ts | 12 + packages/dashboard/app/components/PlanningModeModal.tsx | 25 ++- packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx | 193 +++++++++++++++- packages/dashboard/src/__tests__/planning-interview-formatters.test.ts | 79 ++++++- packages/dashboard/src/__tests__/routes-planning.test.ts | 207 ++++++++++++++++- packages/dashboard/src/__tests__/session-persistence-roundtrip.test.ts | 74 +++++- packages/dashboard/src/ai-session-store.ts | 2 + packages/dashboard/src/planning.ts | 249 ++++++++++++++++++--- packages/dashboard/src/test/mockCoreEngine.ts | 2 + 12 files changed, 815 insertions(+), 41 deletions(-) Fusion-Task-Id: FN-7444 Fusion-Task-Lineage: 43ee1853-558b-4168-b52c-ebcc4ece4521 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7444-planning-deepening-checkpoint.md | 7 + docs/dashboard-guide.md | 4 + packages/core/src/index.ts | 2 +- packages/core/src/types.ts | 12 + .../app/components/PlanningModeModal.tsx | 25 +- .../PlanningModeModal.planning-flow.test.tsx | 193 +++++++++++++- .../planning-interview-formatters.test.ts | 79 +++++- .../src/__tests__/routes-planning.test.ts | 207 ++++++++++++++- .../session-persistence-roundtrip.test.ts | 74 +++++- packages/dashboard/src/ai-session-store.ts | 2 + packages/dashboard/src/planning.ts | 249 ++++++++++++++++-- packages/dashboard/src/test/mockCoreEngine.ts | 2 + 12 files changed, 815 insertions(+), 41 deletions(-) create mode 100644 .changeset/fn-7444-planning-deepening-checkpoint.md diff --git a/.changeset/fn-7444-planning-deepening-checkpoint.md b/.changeset/fn-7444-planning-deepening-checkpoint.md new file mode 100644 index 0000000000..94dff7c1ba --- /dev/null +++ b/.changeset/fn-7444-planning-deepening-checkpoint.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add a mandatory Planning Mode deepening checkpoint before final summaries. +category: feature +dev: Planning sessions now persist pending summaries behind a "Would you like to go deeper?" checkpoint. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index af186da21e..79e5da0ac1 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -348,6 +348,10 @@ Use **Show worktree grouping on the board** in **Settings → Worktrees** when y Planning is a desktop/tablet left-sidebar main-content destination after **Command Center**. It opens the planning-session list and composer in the main content region; mobile continues to use the compact planning entry points. Planning Mode now includes branch controls on the summary screen before you create a task. + + +Before Planning Mode shows **Planning Complete!** or the final plan summary, it first asks **Would you like to go deeper?**. Select one or more suggested themes to continue the interview, use **Other** to add a custom topic, or choose **No, continue to final summary** to reveal the pending summary and task-creation actions. + - **Branch strategy** options mirror Subtask Breakdown semantics: - `Use project/default branch` - `Create auto-named branch per task` diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 640055ece1..6e33b96afd 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, 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, 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 { 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, 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 { 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 f73c084db4..e42c2659bc 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -4941,6 +4941,18 @@ 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"; diff --git a/packages/dashboard/app/components/PlanningModeModal.tsx b/packages/dashboard/app/components/PlanningModeModal.tsx index 4015fcc3f1..be31976de9 100644 --- a/packages/dashboard/app/components/PlanningModeModal.tsx +++ b/packages/dashboard/app/components/PlanningModeModal.tsx @@ -5,7 +5,13 @@ import { useState, useCallback, useEffect, useRef, useMemo, type MouseEvent } fr import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { Task, PlanningQuestion, PlanningSummary, TaskPriority } from "@fusion/core"; -import { DEFAULT_TASK_PRIORITY, TASK_PRIORITIES, getErrorMessage } from "@fusion/core"; +import { + DEFAULT_TASK_PRIORITY, + PLANNING_DEEPEN_CHECKPOINT_ID, + PLANNING_DEEPEN_PROCEED_OPTION_ID, + TASK_PRIORITIES, + getErrorMessage, +} from "@fusion/core"; import { startPlanningStreaming, createPlanningDraft, @@ -2478,6 +2484,7 @@ 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 [response, setResponse] = useState({}); const [textValue, setTextValue] = useState(""); const [commentValue, setCommentValue] = useState(""); @@ -2530,6 +2537,9 @@ function QuestionForm({ question: rawQuestion, progress, historyEntries, onSubmi /* FNXC:PlanningInterview 2026-06-26-00:00: Multi-select planning questions can combine provided choices with a user-authored Other answer. Preserve selected option ids and append `_other` only while the Other checkbox is active and non-empty. + + FNXC:PlanningMode 2026-07-02-00:00: + The mandatory deepening checkpoint reuses multi-select payloads, with the server-reserved proceed option acting as an explicit final-summary gate. Other/custom topics stay additive only for deepening choices, never hidden final-summary actions. */ nextResponse = isOtherSelected && trimmedOther.length > 0 ? { ...response, [PLANNING_OTHER_RESPONSE_KEY]: trimmedOther } @@ -2682,6 +2692,7 @@ 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 (