@mediadatafusion/pi-workflow-suite 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,10 +13,11 @@ import { renderHandoffProjectContext, renderWorkflowStatus, renderWorkflowSummar
13
13
  import { BASE_EXECUTE_TOOLS, EXECUTE_TOOLS, PLAN_TOOLS, REVIEW_TOOLS, WORKFLOW_DIAGRAM_TOOL, WORKFLOW_PLAN_RESULT_TOOL, WORKFLOW_REVIEW_RESULT_TOOL, WORKFLOW_EXECUTION_RESULT_TOOL, WORKFLOW_VALIDATION_RESULT_TOOL, WORKFLOW_REPAIR_RESULT_TOOL, WORKFLOW_PROGRESS_TOOL, MISSION_PLAN_RESULT_TOOL, MISSION_MILESTONE_RESULT_TOOL, STANDARD_HANDOFF_RESULT_TOOL, isBlockedExecuteCommand, registerToolGuard, standardSafeReadOnlyBash, VALIDATOR_TOOLS } from "./workflow-tool-guard.js";
14
14
  import { refreshRuntimeWebTools, registerWorkflowWebTools, runtimeWebResearchGuidance, webSafePlanTools, withRuntimeWebTools } from "./workflow-web-tools.js";
15
15
  import { addMissionCheckpoint, applyPlanRuntimeAccounting, applyStandardRuntimeAccounting, clearOldMissionStates, clearOldWorkflowPlans, compact, createMissionState, createStandardRuntimeId, createWorkflowPlanId, emptyState, extractVerdict, isMissionRuntimeActiveStatus, isPlanRuntimeActiveMode, listMissionStates, listWorkflowPlans, loadMissionState, loadState, loadWorkflowPlan, missionActiveRuntimeMs, missionRuntimeCounterState, missionWallClockAgeMs, planActiveRuntimeMs, planRuntimeCounterState, planWallClockAgeMs, saveMissionState, saveState, saveWorkflowPlan, standardActiveRuntimeMs, standardRuntimeCounterState, standardWallClockAgeMs, PLAN_HISTORY_DIR, MISSION_HISTORY_DIR, type ClarificationAnswer, type ClarificationQuestion, type MissionAutonomy, type MissionMilestone, type MissionState, type PlanProgressState, type PlanStepStatus, type PlanValidationStatus, type SavedWorkflowPlan, type StandardTodoItemStatus, type StandardTodoState, type WorkflowState, type WorkflowTypedHandoffType } from "./workflow-state.js";
16
+ import { WORKFLOW_SHORTCUTS, workflowEntryShortcutLabel as workflowRegistryEntryShortcutLabel, workflowPresetCycleShortcutLabel, workflowSettingsShortcutLines, workflowShortcutKey, workflowWidgetShortcutLabel, type WorkflowShortcutActionId } from "./workflow-shortcuts.js";
16
17
  import { cleanupOrphanProcesses, clearSubagentResultCache, runWorkflowSubagents, workflowSubagentResultOutput, type WorkflowSubagentResult, type WorkflowSubagentTask } from "./subagent/runner.js";
17
18
  import { parseScope, parseBool, parsePlanningDepth, parseClarificationMode, parseStandardTodoTriggerMode, parseStandardClarificationMode, parseStandardModelRole, parseWorkflowAgentScope, parseClarificationTiming, parseSubagentPolicy, parseSubagentPlanningPolicy, parseEditConcurrencyMode, parsePlanningOrchestrationPolicy, parseCompactionMode, parseWorkflowCompactionCheckMode, parseMissionAutonomy, parseValidationRetryMode, parsePositiveInt, updatedMessage, parseClarifyingQuestions, parseShorthandAnswers, formatAnswersForPlanner, planValidationStatusForVerdict } from "./workflow-parsers.js";
18
19
  import { classifyValidationFailure, normalizeValidationVerdict, validationReportHasRepairableIssue } from "./workflow-validation-classifier.js";
19
- import { type SubagentPhase, type SubagentPolicyValue, subagentPhaseSettingKeys, phasePolicy, phaseAutoUseAllowed, phaseParallelAllowed, workerCount, workerTargetForPolicy, activeWorkerTargetLabel, planningSubagentsAllowed, executionSubagentsAllowed, reviewSubagentsAllowed, validationSubagentsAllowed, repairPolicySource, forcedSubagentUnavailableReason, forcedSubagentMessage, fileWriteModeLabel, hasRequiredSubagentPreflight, requiredSubagentPreflightSection, forcedSubagentPolicySatisfiedGuidance, subagentSuitableForForcedPhase, subagentToolProfileLabel, subagentToolsAllowMutation } from "./workflow-subagent-policy.js";
20
+ import { type SubagentPhase, type SubagentPolicyValue, type SubagentPolicyDecision, subagentPhaseSettingKeys, phasePolicy, phaseAutoUseAllowed, phaseParallelAllowed, workerCount, workerTargetForPolicy, activeWorkerTargetLabel, planningSubagentsAllowed, executionSubagentsAllowed, reviewSubagentsAllowed, validationSubagentsAllowed, repairPolicySource, forcedSubagentUnavailableReason, forcedSubagentMessage, fileWriteModeLabel, hasRequiredSubagentPreflight, requiredSubagentPreflightSection, forcedSubagentPolicySatisfiedGuidance, subagentSuitableForForcedPhase, subagentToolProfileLabel, subagentToolsAllowMutation, subagentPolicyRequiresRequiredEvidence, subagentPolicyNeedsInternalDecision, formatSubagentPolicyDecision, forcedSubagentActionDecision, advisorySubagentPolicyDecision } from "./workflow-subagent-policy.js";
20
21
  import { renderWorkflowSettingsCapabilityMatrix, workflowSettingsCapabilitiesByStatus } from "./workflow-settings-capabilities.js";
21
22
  import { Box, Markdown, Spacer, Text, hyperlink, type Component } from "@earendil-works/pi-tui";
22
23
 
@@ -907,6 +908,17 @@ type WorkflowQueuedTurnOptions = {
907
908
  onFinalFailure?: (failure: WorkflowQueuedTurnFailure) => void;
908
909
  };
909
910
 
911
+ class WorkflowToolSurfaceAbort extends Error {
912
+ constructor(message: string) {
913
+ super(message);
914
+ this.name = "WorkflowToolSurfaceAbort";
915
+ }
916
+ }
917
+
918
+ function workflowToolSurfaceAbort(error: unknown): boolean {
919
+ return error instanceof WorkflowToolSurfaceAbort;
920
+ }
921
+
910
922
  const WORKFLOW_IDLE_RETRY_DELAYS_MS = [50, 100, 250, 500, 1000, 1500, 2000, 3000, 5000] as const;
911
923
 
912
924
  function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: string, attempt = 0, connectionAttempt = 0, idleAttempt = 0, options: WorkflowQueuedTurnOptions = {}): void {
@@ -928,6 +940,11 @@ function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: st
928
940
  pi.sendMessage({ customType, content, display: false }, { triggerTurn: true, deliverAs: "followUp" });
929
941
  workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
930
942
  } catch (error) {
943
+ if (workflowToolSurfaceAbort(error)) {
944
+ workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
945
+ recordWorkflowInternalEvent(undefined, `Workflow agent turn blocked by tool-surface verification: ${customType}: ${workflowTurnSendErrorMessage(error)}`);
946
+ return;
947
+ }
931
948
  if (workflowTurnSendErrorIsBusy(error) && attempt < WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS.length - 1) {
932
949
  recordWorkflowInternalEvent(undefined, `Workflow agent turn delayed because Pi is still processing: ${customType}`);
933
950
  queueGuardedAgentTurn(pi, content, customType, attempt + 1, connectionAttempt, idleAttempt, options);
@@ -961,6 +978,10 @@ function sendAgentTurnNowOrQueue(pi: ExtensionAPI, content: string, customType:
961
978
  workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
962
979
  } catch (error) {
963
980
  workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
981
+ if (workflowToolSurfaceAbort(error)) {
982
+ recordWorkflowInternalEvent(undefined, `Workflow agent turn immediate send blocked by tool-surface verification: ${customType}: ${workflowTurnSendErrorMessage(error)}`);
983
+ return;
984
+ }
964
985
  queueAgentTurn(pi, content, customType, options);
965
986
  recordWorkflowInternalEvent(undefined, `Workflow agent turn immediate send fell back to queued delivery: ${customType}: ${workflowTurnSendErrorMessage(error)}`);
966
987
  }
@@ -991,7 +1012,7 @@ function workflowDeferredPhaseTimeoutMs(ctx: ExtensionContext, phase?: WorkflowP
991
1012
  try {
992
1013
  const settings = loadWorkflowSettings(ctx.cwd);
993
1014
  const policy = phasePolicy(settings, phase);
994
- if (policy !== "forced") return 30_000;
1015
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return 30_000;
995
1016
  const limits = settings.subagents as typeof settings.subagents & { subagentTimeoutMinutes?: number };
996
1017
  const timeoutMinutes = Math.max(1, Math.min(240, Number(limits.subagentTimeoutMinutes ?? 20)));
997
1018
  return timeoutMinutes * 60_000 + 60_000;
@@ -1098,6 +1119,16 @@ function executionToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): s
1098
1119
  return executionSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
1099
1120
  }
1100
1121
 
1122
+ function missionExecutionToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
1123
+ const tools = withRuntimeWebTools([...BASE_EXECUTE_TOOLS, MISSION_MILESTONE_RESULT_TOOL]);
1124
+ return executionSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
1125
+ }
1126
+
1127
+ function missionRepairToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
1128
+ const tools = withRuntimeWebTools([...BASE_EXECUTE_TOOLS, WORKFLOW_REPAIR_RESULT_TOOL]);
1129
+ return executionSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
1130
+ }
1131
+
1101
1132
  function requiredPlanExecutionTools(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
1102
1133
  const tools = [...PLAN_TOOLS, "edit", "write", "bash", WORKFLOW_PROGRESS_TOOL, WORKFLOW_EXECUTION_RESULT_TOOL];
1103
1134
  if (executionSubagentsAllowed(settings)) tools.push("subagent");
@@ -1777,22 +1808,27 @@ function planPrompt(task: string, priorPlan?: string, feedback?: string, setting
1777
1808
  const subagentsBeforeClarification = useSubagentsBeforeClarification(settings, "plan");
1778
1809
  const planningOrchestrationPolicy = (subagents as typeof subagents & { planningOrchestrationPolicy?: string }).planningOrchestrationPolicy ?? "orchestrator_first";
1779
1810
  const preflightSatisfied = hasRequiredSubagentPreflight(options.preflightBlock);
1811
+ const forcedPlanningEvidenceRequired = subagentPolicyRequiresRequiredEvidence(subagentPolicy) && !preflightSatisfied;
1780
1812
  const orchestratorGuidance = preflightSatisfied
1781
1813
  ? "Planning orchestration required by forced policy has already been handled by Workflow Suite preflight. Do not call workflow-orchestrator again unless there is genuinely new targeted orchestration work beyond the preflight findings."
1814
+ : forcedPlanningEvidenceRequired && planningNeedsOrchestrator(settings, "plan")
1815
+ ? planningOrchestrationPolicy === "forced_orchestrated"
1816
+ ? "ORCHESTRATION REQUIRED: include workflow-orchestrator as one task inside the required forced Planning parallel subagent call. Do not call workflow-orchestrator separately before the worker batch."
1817
+ : "Planning orchestration is available for this turn. If it is useful, include workflow-orchestrator as one task inside the required forced Planning parallel subagent call; do not call workflow-orchestrator separately before the worker batch."
1782
1818
  : planningNeedsOrchestrator(settings, "plan")
1783
1819
  ? "ORCHESTRATION REQUIRED: call workflow-orchestrator before launching parallel planning/research workers or producing the final plan. The orchestrator must scope worker tasks, identify capability gaps, and decide whether live docs/API access is available before workers run."
1784
1820
  : "Orchestrator is optional for this planning turn. Use workflow-orchestrator if the task is broad, multi-pass, or needs coordinated workers.";
1785
1821
  const subagentGuidance = !planningSubagentsAllowed(settings)
1786
1822
  ? "Sub-agent use is disabled by settings. Do not call subagent."
1787
- : preflightSatisfied && subagentPolicy === "forced"
1823
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(subagentPolicy)
1788
1824
  ? forcedSubagentPolicySatisfiedGuidance("planning")
1789
1825
  : subagentPolicy === "forced"
1790
1826
  ? `FORCED SUB-AGENT POLICY: before producing the final plan, you MUST call the subagent tool with at least ${Math.max(1, subagents.minPlanningWorkersForMaximum ?? 2)} planning/research worker(s). Use read-only agents such as implementation-planning, codebase-research, quality-validation, workflow-orchestrator, or general-worker. If subagent execution fails, stop and report the exact blocker. Do not use or recommend parallel editing.`
1791
1827
  : subagentPolicy === "maximum"
1792
- ? `For non-trivial codebase work, use multiple read-only sub-agents before finalizing the plan. Strongly target at least ${subagents.minPlanningWorkersForMaximum ?? 2} workers, such as implementation-planning plus codebase-research or quality-validation. Skip only for truly trivial work or unavailable sub-agent execution, and explain the exact reason. Do not use or recommend parallel editing.`
1828
+ ? `For non-trivial codebase work, use multiple read-only sub-agents before finalizing the plan. Strongly target at least ${subagents.minPlanningWorkersForMaximum ?? 2} workers, such as implementation-planning plus codebase-research or quality-validation. Internally decide delegate or skip; skip only for truly trivial work or unavailable sub-agent execution, and give only the concise skip reason in Sub-Agent Usage Summary. Do not print internal policy deliberation. Do not use or recommend parallel editing.`
1793
1829
  : subagentPolicy === "deep"
1794
- ? `For non-trivial codebase work, use at least ${subagents.minPlanningWorkersForDeep ?? 1} read-only planning sub-agent before finalizing the plan. Prefer implementation-planning or codebase-research for safe read-only plan refinement. Explain any skip. Do not use or recommend parallel editing.`
1795
- : "Sub-agents are strongly encouraged for speed and quality. Use them for file discovery, project-rule audit, ambiguity reduction, risk checks, or validation planning; skip only for trivial one-file/doc tasks and explain why.";
1830
+ ? `For non-trivial codebase work, use at least ${subagents.minPlanningWorkersForDeep ?? 1} read-only planning sub-agent before finalizing the plan. Prefer implementation-planning or codebase-research for safe read-only plan refinement. Internally decide delegate or skip; give only the concise skip reason in Sub-Agent Usage Summary. Do not print internal policy deliberation. Do not use or recommend parallel editing.`
1831
+ : "Sub-agents are strongly encouraged for speed and quality. Internally decide delegate or skip. Use them for file discovery, project-rule audit, ambiguity reduction, risk checks, or validation planning; skip only for trivial one-file/doc tasks or no useful parallel work, and give only the concise skip reason in Sub-Agent Usage Summary. Do not print internal policy deliberation.";
1796
1832
  const feedbackKind = options.feedbackKind ?? (feedback ? "revision" : undefined);
1797
1833
  const feedbackBlock = feedbackKind === "clarification" && feedback
1798
1834
  ? `Clarification answers from user:\n${feedback}\n\nCLARIFICATION HAS BEEN RESOLVED. Produce the final approval-ready plan now. Do not ask more clarification questions. Do not execute. Do not call tools, inspect files, generate diagrams, or create artifacts after this point. If more facts are needed, list files to inspect in the plan instead of exhaustively investigating them now.\n\n`
@@ -1940,7 +1976,7 @@ Sub-agent planning policy:
1940
1976
  - When useSubagentsBeforeClarification=true and planning depth/policy is deep, maximum, or forced, use read-only codebase-research, implementation-planning, quality-validation, or general-worker before asking clarification if that analysis would make the questions more specific.
1941
1977
  - In Sub-Agent Usage Summary, list each sub-agent used and what it checked; if none ran, give the exact trivial/unavailable reason.
1942
1978
  - If sub-agents are not used when subagents.planningPolicy is auto, deep, or maximum, explain why in Sub-Agent Usage Summary.
1943
- - ${preflightSatisfied && subagentPolicy === "forced" ? "Forced planning policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If subagents.planningPolicy is forced, you must use the required sub-agents before the final plan or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}`;
1979
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(subagentPolicy) ? "Forced planning policy was satisfied by Workflow Suite preflight or an internal trivial-work exemption; do not rerun required workers solely for policy compliance." : "If subagents.planningPolicy is forced, you must use the required sub-agents before the final plan unless Workflow Suite supplied an internal trivial-work exemption, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}`;
1944
1980
  }
1945
1981
 
1946
1982
  // SubagentPhase and SubagentPolicyValue imported from workflow-subagent-policy.ts
@@ -2018,22 +2054,39 @@ function repairArtifactDispositionOutput(): string {
2018
2054
  List movedFiles, preservedFiles, deletedFiles, rootArtifacts, possiblyUserOwnedFiles, and needsUserApproval. State "none" for empty categories. Set needsUserApproval only for a concrete disposition/action that should block automatic revalidation. Do not set it for advisory-only follow-up, credential rotation recommendations, preserved ambiguous files, manual QA still needed, or pre-existing debt. Deletion is acceptable only when explicitly approved or clearly current-task-generated, unrecoverable, and non-user-owned; otherwise preserve or request approval.`;
2019
2055
  }
2020
2056
 
2057
+ function forcedSubagentCallShape(phase: SubagentPhase, required: number): string {
2058
+ const workflowPhase = phase.toLowerCase();
2059
+ const preferred = phase === "Planning"
2060
+ ? ["implementation-planning", "codebase-research", "general-worker", "quality-validation"]
2061
+ : phase === "Execution"
2062
+ ? ["general-worker", "implementation-planning", "codebase-research", "quality-validation"]
2063
+ : phase === "Repair"
2064
+ ? ["general-worker", "implementation-planning", "quality-validation", "codebase-research"]
2065
+ : phase === "Review"
2066
+ ? ["quality-validation", "implementation-planning", "general-worker", "codebase-research"]
2067
+ : ["quality-validation", "general-worker", "codebase-research", "implementation-planning"];
2068
+ const agents = preferred.slice(0, Math.max(1, required));
2069
+ const tasks = agents.map((agent) => `{ agent: "${agent}", task: "<focused ${workflowPhase} support task>" }`).join(", ");
2070
+ return `Use one visible parallel call with this shape: subagent({ workflowPhase: "${workflowPhase}", tasks: [${tasks}] }). Provide at least ${Math.max(1, required)} worker task entr${Math.max(1, required) === 1 ? "y" : "ies"} in that single parallel tasks array. Worker count means task entries, not unique agent names. The agents shown are examples only; choose phase-suitable workers by task fit. Duplicate suitable agent roles are valid when useful, and distinct role names are not required. Do not add irrelevant filler roles. Do not use subagent chain for forced multi-worker evidence; chain is sequential and will be blocked. Every task must be separate, focused, useful, and phase-suitable.`;
2071
+ }
2072
+
2021
2073
  function phasePromptPolicyBlock(settings: ReturnType<typeof loadWorkflowSettings>, phase: SubagentPhase, label: string = phase, preflightBlock?: string): string {
2022
2074
  const policy = phasePolicy(settings, phase);
2023
2075
  const workers = workerCount(settings, phase);
2076
+ const required = Math.max(1, workers.maximum);
2024
2077
  const phaseName = phase === "Repair" ? "repair-mode sub-agent workers" : `${phase.toLowerCase()} sub-agent workers`;
2025
2078
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
2026
2079
  const guidance = !phaseAutoUseAllowed(settings, phase) || policy === "off"
2027
2080
  ? `${label} sub-agent use is disabled by settings. Do not call subagent.`
2028
- : preflightSatisfied && policy === "forced"
2081
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
2029
2082
  ? forcedSubagentPolicySatisfiedGuidance(label)
2030
2083
  : policy === "forced"
2031
- ? `FORCED SUB-AGENT POLICY: you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} ${phaseName}, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>. Do not continue this phase or write files until the required workers have reported.`
2084
+ ? `FORCED SUB-AGENT POLICY: you MUST call the subagent tool with at least ${required} ${phaseName}, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>. ${forcedSubagentCallShape(phase, required)} Do not continue this phase or write files until the required workers have reported.`
2032
2085
  : policy === "maximum"
2033
- ? `Strongly target ${Math.max(1, workers.maximum)} ${phaseName}; skip only when the work is truly trivial or sub-agents are unavailable, and give the exact skip reason.`
2086
+ ? `Strongly target ${Math.max(1, workers.maximum)} ${phaseName}; internally decide delegate or skip, skip only when the work is truly trivial or sub-agents are unavailable, and give only the concise skip reason. Do not print internal policy deliberation.`
2034
2087
  : policy === "deep"
2035
- ? `Use ${Math.max(1, workers.deep)} ${phaseName} for non-trivial work; give a concrete skip reason if no worker is useful or available.`
2036
- : `${label} sub-agent use is strongly encouraged; use workers for speed, file inspection, risk discovery, validation prep, or evidence-heavy work and give a concrete skip reason if none run.`;
2088
+ ? `Use ${Math.max(1, workers.deep)} ${phaseName} for non-trivial work; internally decide delegate or skip and give a concrete skip reason if no worker is useful or available. Do not print internal policy deliberation.`
2089
+ : `${label} sub-agent use is strongly encouraged; internally decide delegate or skip, strongly consider up to ${Math.max(1, workers.deep)} worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work, use workers for speed, file inspection, risk discovery, validation prep, or evidence-heavy work, and give a concrete skip reason only when none run. Do not print internal policy deliberation.`;
2037
2090
  return `Sub-agent policy for ${label}:
2038
2091
  - policy: ${policy}
2039
2092
  - workers: ${activeWorkerTargetLabel(policy, workers)}
@@ -2120,15 +2173,15 @@ function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
2120
2173
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
2121
2174
  const subagentGuidance = !executionSubagentsAllowed(settings)
2122
2175
  ? "Execution sub-agents are disabled by settings. Do not call subagent."
2123
- : preflightSatisfied && policy === "forced"
2176
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
2124
2177
  ? forcedSubagentPolicySatisfiedGuidance("execution")
2125
2178
  : policy === "forced"
2126
- ? `FORCED SUB-AGENT POLICY: first call workflow_progress({ step: N, status: "active" }) for the current approved Plan step. Then call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s) before any other executor work, file write, bash command, or final execution summary. Use them in parallel where safe for file inspection, implementation strategy, scoped implementation help, patch planning, regression search, and validation preparation. If subagent execution fails, stop and report the exact blocker. Do not apply simultaneous conflicting edits.`
2179
+ ? `FORCED SUB-AGENT POLICY: first call workflow_progress({ step: N, status: "active" }) for the current approved Plan step. Then call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s) before any other executor work, file write, bash command, or final execution summary. ${forcedSubagentCallShape("Execution", Math.max(1, workers.maximum))} Use them in parallel where safe for file inspection, implementation strategy, scoped implementation help, patch planning, regression search, and validation preparation. If subagent execution fails, stop and report the exact blocker. Do not apply simultaneous conflicting edits.`
2127
2180
  : policy === "maximum"
2128
- ? `For non-trivial implementation, use execution sub-agents before editing for analysis, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation. Strongly target at least ${workers.maximum} execution workers; skip only for trivial work or unavailable sub-agent execution and explain why. Do not apply simultaneous conflicting edits.`
2181
+ ? `For non-trivial implementation, use execution sub-agents before editing for analysis, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation. Strongly target at least ${workers.maximum} execution workers; internally decide delegate or skip, skip only for trivial work or unavailable sub-agent execution, and give only the concise skip reason. Do not print internal policy deliberation. Do not apply simultaneous conflicting edits.`
2129
2182
  : policy === "deep"
2130
- ? `For non-trivial implementation, use at least ${workers.deep} execution sub-agent before editing for analysis, implementation preparation, scoped implementation help, patch planning, or validation preparation. Explain any skip. Do not apply simultaneous conflicting edits.`
2131
- : "Execution sub-agents are strongly encouraged for speed and quality. Use them for file inspection, implementation prep, scoped implementation help, risk discovery, patch planning, or validation prep; skip only for trivial changes and explain why.";
2183
+ ? `For non-trivial implementation, use at least ${workers.deep} execution sub-agent before editing for analysis, implementation preparation, scoped implementation help, patch planning, or validation preparation. Internally decide delegate or skip and give only the concise skip reason. Do not print internal policy deliberation. Do not apply simultaneous conflicting edits.`
2184
+ : `Execution sub-agents are strongly encouraged for speed and quality. Internally decide delegate or skip. Strongly consider up to ${Math.max(1, workers.deep)} execution worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work. Use them for file inspection, implementation prep, scoped implementation help, risk discovery, patch planning, or validation prep; skip only for trivial changes or no useful parallel work and give only the concise skip reason. Do not print internal policy deliberation.`;
2132
2185
  const automatableEvidenceGuidance = `Automatable evidence contract:
2133
2186
  - Before calling workflow_execution_result, you MUST gather all automatable validation evidence that the approved plan requires.
2134
2187
  - For web/app projects (React, Vite, Next.js, Vue, Svelte, etc.): run build and test commands; when the plan requires runtime/browser/localStorage behavior, start a bounded dev or preview server and verify the behavior with available commands (curl, node scripts, or the platform's built-in tools), then stop/cleanup the server.
@@ -2202,7 +2255,7 @@ ${requiredSubagentPreflightSection(preflightBlock)}
2202
2255
  - Execution agents may run in parallel for analysis, file inspection, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation.
2203
2256
  - Execution agents may perform work allowed by their configured tools and agent contract, but must stay inside the approved Plan scope and current per-step boundary when per-step gates are enabled.
2204
2257
  - Main executor owns final integration, validation-ready summary, and workflow_execution_result. Sub-agent file writes must follow editConcurrencyMode=${settings.subagents.editConcurrencyMode ?? "sequential"}; with sequential mode, serialize writes and avoid conflicting parallel edits.
2205
- - ${preflightSatisfied && policy === "forced" ? "Forced execution policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If executionPolicy is forced, activate the current step with workflow_progress first, then required sub-agents must run before read/grep/find/ls/edit/write/bash or final summary, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2258
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy) ? "Forced execution policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If executionPolicy is forced, activate the current step with workflow_progress first, then required sub-agents must run before edit/write/meaningful bash/final summary. Local read/grep/find/ls probes may run only to orient the parent or form the required sub-agent call; they do not satisfy the worker requirement. If sub-agent execution is unavailable, stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2206
2259
  - Final execution summary must include Sub-Agent Usage Summary with agents used and findings applied, or a forced-policy blocker.
2207
2260
 
2208
2261
  ${subagentCapabilityTable()}
@@ -2226,15 +2279,15 @@ function validatePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
2226
2279
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
2227
2280
  const subagentGuidance = !validationSubagentsAllowed(settings)
2228
2281
  ? "Validation sub-agents are disabled by settings. Do not call subagent."
2229
- : preflightSatisfied && policy === "forced"
2282
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
2230
2283
  ? forcedSubagentPolicySatisfiedGuidance("validation")
2231
2284
  : policy === "forced"
2232
- ? `FORCED SUB-AGENT POLICY: you are the parent validator turn. Before final validation verdict, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} validation worker(s), preferably quality-validation, general-worker, codebase-research, or implementation-planning. Do not call workflow-orchestrator for this forced Plan Validation requirement; the validator is already the parent controller. If subagent execution fails, stop and report the exact blocker.`
2285
+ ? `FORCED SUB-AGENT POLICY: you are the parent validator turn. Before final validation verdict or any substantive validation work, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} validation worker(s), preferably quality-validation, general-worker, codebase-research, or implementation-planning. ${forcedSubagentCallShape("Validation", Math.max(1, workers.maximum))} Do not call workflow-orchestrator for this forced Plan Validation requirement; the validator is already the parent controller. If subagent execution fails, stop and report the exact blocker.`
2233
2286
  : policy === "maximum"
2234
- ? `For non-trivial validation, use validation sub-agents for independent checks, regression review, and risk analysis. Strongly target at least ${workers.maximum} validation workers, preferably quality-validation plus focused reviewers; skip only for trivial validation or unavailable sub-agent execution and explain why.`
2287
+ ? `For non-trivial validation, use validation sub-agents for independent checks, regression review, and risk analysis. Strongly target at least ${workers.maximum} validation workers, preferably quality-validation plus focused reviewers; internally decide delegate or skip, skip only for trivial validation or unavailable sub-agent execution, and give only the concise skip reason. Do not print internal policy deliberation.`
2235
2288
  : policy === "deep"
2236
- ? `For non-trivial validation, use at least ${workers.deep} validation sub-agent for independent checks, regression review, or risk analysis. Prefer quality-validation. Explain any skip.`
2237
- : "Validation sub-agents are strongly encouraged for independent review. Use them for plan compliance, diff review, regression risk, build/test review, or manual-QA classification; skip only for trivial validation and explain why.";
2289
+ ? `For non-trivial validation, use at least ${workers.deep} validation sub-agent for independent checks, regression review, or risk analysis. Prefer quality-validation. Internally decide delegate or skip and give only the concise skip reason. Do not print internal policy deliberation.`
2290
+ : `Validation sub-agents are strongly encouraged for independent review. Internally decide delegate or skip. Strongly consider up to ${Math.max(1, workers.deep)} validation worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work. Use them for plan compliance, diff review, regression risk, build/test review, or manual-QA classification; skip only for trivial validation or no useful parallel work and give only the concise skip reason. Do not print internal policy deliberation.`;
2238
2291
  const automatableEvidenceVerifierGuidance = `Automatable evidence verification:
2239
2292
  - Before marking Manual Verification Required: yes, verify that the missing evidence is genuinely non-automatable.
2240
2293
  - If the plan required dev server, browser, localStorage, runtime, or endpoint checks that were not attempted by the executor, and those checks can be performed with safe read-only bash or parent runtime tools such as workflow_browser_check, mark Concrete Repairable Issue: yes and Evidence Gap: yes, then return FAIL rather than PARTIAL PASS.
@@ -2292,7 +2345,7 @@ ${requiredSubagentPreflightSection(preflightBlock)}
2292
2345
  ${subagentCapabilityTable()}
2293
2346
 
2294
2347
  - When validationPolicy is auto, deep, or maximum, validation sub-agents are expected for non-trivial work; prefer quality-validation for independent diff/risk/build-test review.
2295
- - ${preflightSatisfied && policy === "forced" ? "Forced validation policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If validationPolicy is forced, required sub-agents must run before verdict, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2348
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy) ? "Forced validation policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If validationPolicy is forced, required sub-agents must run before verdict, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2296
2349
  - PASS only when the approved plan is fully satisfied with no blocking unresolved risk.
2297
2350
  - FAIL when concrete missing requirements, unexpected changes, regressions, broken checks, unsafe/out-of-scope work, or concrete code/content/citation/source/file/metadata/artifact fixes remain.
2298
2351
  - FAIL when automatable runtime evidence (build, test, dev server, browser, localStorage, API response) was not gathered and the checks are performable with available tools. Missing automatable evidence is a concrete repairable issue, not a manual-only caveat.
@@ -2356,15 +2409,15 @@ function workflowRepairPrompt(state: WorkflowState, settings = loadWorkflowSetti
2356
2409
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
2357
2410
  const subagentGuidance = !phaseAutoUseAllowed(settings, "Repair") || policy === "off"
2358
2411
  ? "Repair sub-agents are disabled by settings. Do not call subagent."
2359
- : preflightSatisfied && policy === "forced"
2412
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
2360
2413
  ? forcedSubagentPolicySatisfiedGuidance("repair")
2361
2414
  : policy === "forced"
2362
- ? `FORCED SUB-AGENT POLICY: before any repair edit or final Workflow Repair Summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} repair/execution worker(s). Use them for read-only repair verification, missing-file inspection, patch planning, and validation preparation. If subagent execution fails, stop and report the exact blocker.`
2415
+ ? `FORCED SUB-AGENT POLICY: before any repair edit or final Workflow Repair Summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} repair/execution worker(s). ${forcedSubagentCallShape("Repair", Math.max(1, workers.maximum))} Use them for read-only repair verification, missing-file inspection, patch planning, and validation preparation. If subagent execution fails, stop and report the exact blocker.`
2363
2416
  : policy === "maximum"
2364
- ? `For non-trivial repair, strongly target ${Math.max(1, workers.maximum)} repair/execution worker(s) before edits; continue if skipped only with an explanation.`
2417
+ ? `For non-trivial repair, strongly target ${Math.max(1, workers.maximum)} repair/execution worker(s) before edits; internally decide delegate or skip, and continue if skipped only with a concise reason. Do not print internal policy deliberation.`
2365
2418
  : policy === "deep"
2366
- ? `For non-trivial repair, use ${Math.max(1, workers.deep)} repair/execution worker(s) before edits when useful and explain any skip.`
2367
- : "Repair sub-agents are strongly encouraged. Use them for failure triage, missing-file inspection, patch planning, and validation prep; skip only with a concrete reason.";
2419
+ ? `For non-trivial repair, use ${Math.max(1, workers.deep)} repair/execution worker(s) before edits when useful. Internally decide delegate or skip and give only the concise skip reason. Do not print internal policy deliberation.`
2420
+ : `Repair sub-agents are strongly encouraged. Internally decide delegate or skip. Strongly consider up to ${Math.max(1, workers.deep)} repair worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work. Use them for failure triage, missing-file inspection, patch planning, and validation prep; skip only with a concrete reason. Do not print internal policy deliberation.`;
2368
2421
  return `${repairPrompt}
2369
2422
 
2370
2423
  You are in PI WORKFLOW REPAIR MODE.
@@ -2394,7 +2447,7 @@ Sub-agent repair policy:
2394
2447
  ${requiredSubagentPreflightSection(preflightBlock)}
2395
2448
  - Repair-mode sub-agent workers may inspect, triage, and plan safely. File writes must remain serialized through the Executor in repair mode unless scoped conflict protection is explicitly enabled.
2396
2449
  - You, the main repair executor, own all file writes, edits, and bash commands. Even when forced sub-agent policy is active and sub-agents report they cannot write files, you must proceed with your own file writes, edits, and bash commands after sub-agent inspection completes. Sub-agents are read-only by design; their inability to write does not mean you cannot write.
2397
- - ${preflightSatisfied && policy === "forced" ? "Forced repair policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If repairPolicy is forced, required sub-agents must run before any repair edit or final repair summary, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2450
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy) ? "Forced repair policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If repairPolicy is forced, required sub-agents must run before any repair edit or final repair summary, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2398
2451
  - Do not re-grade validation. Do not claim PASS/FAIL for repaired work; recommend revalidation.
2399
2452
  - If no concrete repairable issue exists, perform no-op repair summary and recommend manual verification or /plan revalidate.
2400
2453
 
@@ -2430,15 +2483,15 @@ function reviewerPrompt(state: WorkflowState, settings = loadWorkflowSettings(),
2430
2483
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
2431
2484
  const subagentGuidance = !reviewSubagentsAllowed(settings)
2432
2485
  ? "Reviewer sub-agents are disabled by settings. Do not call subagent."
2433
- : preflightSatisfied && policy === "forced"
2486
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
2434
2487
  ? forcedSubagentPolicySatisfiedGuidance("review")
2435
2488
  : policy === "forced"
2436
- ? `FORCED SUB-AGENT POLICY: before final reviewer report, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} review worker(s) for independent read-only challenge review. If subagent execution fails, stop and report the exact blocker.`
2489
+ ? `FORCED SUB-AGENT POLICY: before final reviewer report or your own review analysis, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} review worker(s) for independent read-only challenge review. ${forcedSubagentCallShape("Review", Math.max(1, workers.maximum))} If subagent execution fails, stop and report the exact blocker.`
2437
2490
  : policy === "maximum"
2438
- ? `For non-trivial review, use multiple reviewer sub-agents for independent risk review, regression review, implementation-scope review, and validation-plan review. Strongly target at least ${workers.maximum} review workers; skip only for trivial review or unavailable sub-agent execution and explain why.`
2491
+ ? `For non-trivial review, use multiple reviewer sub-agents for independent risk review, regression review, implementation-scope review, and validation-plan review. Strongly target at least ${workers.maximum} review workers; internally decide delegate or skip, skip only for trivial review or unavailable sub-agent execution, and give only the concise skip reason. Do not print internal policy deliberation.`
2439
2492
  : policy === "deep"
2440
- ? `For non-trivial review, use at least ${workers.deep} reviewer sub-agent for independent risk review, regression review, implementation-scope review, or validation-plan review. Explain any skip.`
2441
- : "Reviewer sub-agents are strongly encouraged. Use them to challenge scope, files, risk, and validation plan; skip only for trivial review and explain why.";
2493
+ ? `For non-trivial review, use at least ${workers.deep} reviewer sub-agent for independent risk review, regression review, implementation-scope review, or validation-plan review. Internally decide delegate or skip and give only the concise skip reason. Do not print internal policy deliberation.`
2494
+ : `Reviewer sub-agents are strongly encouraged. Internally decide delegate or skip. Strongly consider up to ${Math.max(1, workers.deep)} review worker${Math.max(1, workers.deep) === 1 ? "" : "s"} for non-trivial work. Use them to challenge scope, files, risk, and validation plan; skip only for trivial review or no useful parallel work and give only the concise skip reason. Do not print internal policy deliberation.`;
2442
2495
  return `You are in PI WORKFLOW REVIEWER MODE.
2443
2496
 
2444
2497
  CRITICAL: If forced review sub-agents are required by policy, call the subagent tool FIRST — before your own review analysis. Sub-agent findings must inform your review, not validate it after the fact. Once sub-agents complete (or if no sub-agents are required), perform your read-only review and call workflow_review_result with your verdict, issues, summary, and safety flags. After workflow_review_result returns its control-verdict tool result, STOP IMMEDIATELY. Do not call any more tools, do not call subagent again, do not create diagrams, and do not continue prose analysis. Workflow Suite owns the next handoff to execution or review retry. Do not do your own review analysis before dispatching required sub-agents.
@@ -2465,7 +2518,7 @@ ${subagentCapabilityTable()}
2465
2518
 
2466
2519
  - When reviewPolicy is auto, deep, or maximum, reviewer sub-agents are expected for non-trivial plans to challenge scope, files, risk, and validation plan.
2467
2520
  - Reviewer sub-agents must not perform direct file edits.
2468
- - ${preflightSatisfied && policy === "forced" ? "Forced review policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If reviewPolicy is forced, required sub-agents must run before the report, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2521
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy) ? "Forced review policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If reviewPolicy is forced, required sub-agents must run before the report, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
2469
2522
  - The reviewer must not rubber-stamp execution; surface missing requirements before the executor starts.
2470
2523
  - Plan Review is notes-first for control flow. Use NOTES for nearly all actionable advice, including severe executor-correctable findings. Use NEEDS REPAIR only when the Plan text is structurally unusable for execution, such as having no executable implementation steps.
2471
2524
  - Validation command additions, rollback wording fixes, selector/test-hook refinements, off-limits/out-of-scope lists, instruction text updates, implementation parameter suggestions, game-rule details, impossible browser/test move sequences, missing draw/test data sequences, dev-server readiness, AI/settings/accessibility details, localStorage keys, icon choices, and executor cautions are executor notes, not repair blockers.
@@ -2561,13 +2614,13 @@ Themes:
2561
2614
  - /workflow-settings theme brand base minimal|workflow_duo|mission_control|diagnostic_center|data_stream|neural_grid
2562
2615
 
2563
2616
  Workflow entry shortcuts:
2564
- - Ctrl+Shift+S toggles Standard Mode
2565
- - Ctrl+Shift+L enters Plan Mode
2566
- - Ctrl+Shift+M enters Mission Mode
2567
- - In the opposite active mode, Ctrl+Shift+L/M switches directly to that mode
2617
+ - ${workflowEntryShortcutLabel("standard").replace(/^Standard:/, "")} toggles Standard Mode
2618
+ - ${workflowEntryShortcutLabel("plan").replace(/^Plan:/, "")} enters Plan Mode
2619
+ - ${workflowEntryShortcutLabel("mission").replace(/^Mission:/, "")} enters Mission Mode
2620
+ - In the opposite active mode, ${workflowEntryShortcutLabel("plan").replace(/^Plan:/, "")}/${workflowEntryShortcutLabel("mission").replace(/^Mission:/, "")} switches directly to that mode
2568
2621
 
2569
2622
  Presets:
2570
- - Ctrl+Shift+U cycles workflow presets from the active Plan/Mission/Standard footer/status line
2623
+ - ${workflowPresetCycleShortcutLabel()} cycles workflow presets from the active Plan/Mission/Standard footer/status line
2571
2624
  - /workflow presets opens the preset selector
2572
2625
  - /workflow presets list
2573
2626
  - /workflow presets apply <name>
@@ -3708,11 +3761,7 @@ Startup On Session Start: ${settings.ui.startupVisualOnSessionStart === true ? "
3708
3761
  Custom Brand: ${settings.ui.customBrandEnabled === true ? "enabled" : "disabled"}
3709
3762
  Custom Brand Text: ${sanitizeCustomBrandText(settings.ui.customBrandText) || "(empty)"}
3710
3763
  Custom Brand Base Template: ${customBrandBaseVisualOrDefault(settings)}
3711
- Standard Shortcut: Ctrl+Shift+S toggles Standard Mode
3712
- Plan Shortcut: Ctrl+Shift+L enters Plan Mode
3713
- Mission Shortcut: Ctrl+Shift+M enters Mission Mode
3714
- Widget Shortcuts: Ctrl+Shift+T/B while Plan/Mission/Standard Mode is active
3715
- Preset Cycle Shortcut: Ctrl+Shift+U while Plan/Mission/Standard Mode is active
3764
+ ${workflowSettingsShortcutLines().join("\n")}
3716
3765
  Footer Line: idle displays Plan/Mission entry hints only; active workflows display compact widget/preset hints and workflow switch hints by default
3717
3766
  Fallback Commands: /workflow widgets status, /workflow presets, /standard, /plan, /mission
3718
3767
 
@@ -3904,6 +3953,20 @@ function planProgressHasOpenSteps(progress: PlanProgressState | undefined): bool
3904
3953
  return Boolean(progress?.steps.some((step) => step.status === "pending" || step.status === "active" || step.status === "failed" || step.status === "blocked"));
3905
3954
  }
3906
3955
 
3956
+ function planValidationBoundaryReached(state: WorkflowState, settings: ReturnType<typeof loadWorkflowSettings>): boolean {
3957
+ if (settings.workflow.validateAfterEachStep === true && typeof state.planStepValidationIndex === "number") return true;
3958
+ if (!state.approvedPlan?.trim() || !workflowPlanProgressEnabled(settings)) return true;
3959
+ const steps = state.planProgress?.steps ?? [];
3960
+ if (!steps.length) return true;
3961
+ return planProgressAllStepsCompleted(state);
3962
+ }
3963
+
3964
+ function planExecutionIncomplete(state: WorkflowState, settings: ReturnType<typeof loadWorkflowSettings>): boolean {
3965
+ if (!state.approvedPlan?.trim() || !workflowPlanProgressEnabled(settings)) return false;
3966
+ const steps = state.planProgress?.steps ?? [];
3967
+ return steps.length > 0 && planProgressHasOpenSteps(state.planProgress) && !planValidationBoundaryReached(state, settings);
3968
+ }
3969
+
3907
3970
  function planRuntimeLabel(state: WorkflowState): string {
3908
3971
  return `Runtime: ${formatDurationMs(planActiveRuntimeMs(state))} active`;
3909
3972
  }
@@ -4452,8 +4515,13 @@ function classifyStandardWork(task: string | undefined, answerSummary?: string):
4452
4515
  return { phase: "Planning", kind: "read_only" };
4453
4516
  }
4454
4517
 
4518
+ function trivialSubagentSkipReason(phase: SubagentPhase, task: string | undefined, kind?: WorkflowState["standardWorkKind"]): string | undefined {
4519
+ const decision = forcedSubagentActionDecision({ phase, policy: "forced", task, kind });
4520
+ return decision.outcome === "exempt_trivial" ? decision.reason : undefined;
4521
+ }
4522
+
4455
4523
  function standardForcedSubagentSafeBash(command: string): boolean {
4456
- return standardSafeReadOnlyBash(command);
4524
+ return forcedSubagentActionDecision({ phase: "Execution", policy: "forced", toolName: "bash", command }).allowBeforeEvidence;
4457
4525
  }
4458
4526
 
4459
4527
  function standardTodoUsageText(): string {
@@ -4854,6 +4922,9 @@ function standardPrompt(state: WorkflowState, settings: ReturnType<typeof loadWo
4854
4922
  const standardSubagentPolicyBlock = standardSubagentsAllowed(settings)
4855
4923
  ? `Standard sub-agent policy:\n- active phase: ${state.standardActivePhase ?? "Planning"}${state.standardWorkKind ? ` (${state.standardWorkKind})` : ""}\n- agentScope: ${settings.standard.subagentScope ?? "user"}\n- planning/research: ${activeWorkerTargetLabel(standardPhasePolicy(settings, "Planning"), standardWorkerCount(settings, "Planning"))}\n- execution: ${activeWorkerTargetLabel(standardPhasePolicy(settings, "Execution"), standardWorkerCount(settings, "Execution"))}\n- repair: ${activeWorkerTargetLabel(standardPhasePolicy(settings, "Repair"), standardWorkerCount(settings, "Repair"))}\n- review: ${activeWorkerTargetLabel(standardPhasePolicy(settings, "Review"), standardWorkerCount(settings, "Review"))}\n- validation: ${activeWorkerTargetLabel(standardPhasePolicy(settings, "Validation"), standardWorkerCount(settings, "Validation"))}\nUse any configured package, user, or project agent that fits the task. When calling subagent in Standard Mode, pass agentScope=${settings.standard.subagentScope ?? "user"} and workflowPhase=planning, execution, repair, review, or validation. Do not launch execution sub-agents merely for read-only summaries, status updates, action-item reports, or docs/repo inspection; use execution sub-agents only before actual mutation or execution-class bash. If the active Standard phase is execution or repair and that phase policy is forced, satisfy the required sub-agent usage before edit/write/unsafe bash.`
4856
4924
  : "Standard Mode sub-agent delegation is disabled by settings.";
4925
+ const standardSubagentDecisionInstruction = standardSubagentsAllowed(settings)
4926
+ ? "For Standard auto/deep/maximum sub-agent policies, internally decide delegate or skip. Delegate for non-trivial parallelizable research, validation, repair, or implementation prep. Skip for trivial read-only/status work or when no useful parallel worker exists. Do not print internal sub-agent policy deliberation; if no worker runs, give only a concise user-facing skip reason when a Sub-Agent Usage Summary is naturally relevant."
4927
+ : "";
4857
4928
  const autoCheckInstruction = standardAutoChecksRequired(state, settings)
4858
4929
  ? `Your first visible lines for every user-submitted Standard Mode response must be exactly this parser-safe Standard Auto Checks contract before any other text:\nStandard Auto Checks:\nCLARIFICATION_DECISION: ask|skip|disabled\nCLARIFICATION_REASON: <brief user-facing reason>\nTODO_DECISION: create|skip|on request|required|disabled|active\nTODO_REASON: <brief user-facing reason>\nUse CLARIFICATION_DECISION=ask only when you will ask clarification. Use skip when clarification is not needed. Use TODO_DECISION=create when you will initialize optional task-specific To Do tracking with standard_todo, required when To Do tracking is required by settings, active when a To Do list is already active, skip when visible tracking is not useful, on request when To Do tracking starts only on explicit request, and disabled when settings disable it. Keep reasons concise; do not reveal chain-of-thought.`
4859
4930
  : "Standard auto To Do and clarification checks are disabled; no Standard Auto Checks block is required.";
@@ -4891,6 +4962,7 @@ Rules:
4891
4962
  - ${clarificationLine}
4892
4963
  - ${autoCheckInstruction}
4893
4964
  - ${standardSubagentPolicyBlock}
4965
+ ${standardSubagentDecisionInstruction ? `- ${standardSubagentDecisionInstruction}` : ""}
4894
4966
 
4895
4967
  ${standardSubagentsAllowed(settings) ? subagentCapabilityTable() : ""}
4896
4968
 
@@ -5032,15 +5104,10 @@ function planProgressWidget(state: WorkflowState, settings: ReturnType<typeof lo
5032
5104
  return lines;
5033
5105
  }
5034
5106
 
5035
- const STANDARD_MODE_SHORTCUT = "Ctrl+Shift+S";
5036
- const PLAN_MODE_SHORTCUT = "Ctrl+Shift+L";
5037
- const MISSION_MODE_SHORTCUT = "Ctrl+Shift+M";
5038
-
5039
5107
  type WorkflowEntryMode = "standard" | "plan" | "mission";
5040
5108
 
5041
5109
  function workflowEntryShortcutLabel(mode: WorkflowEntryMode): string {
5042
- if (mode === "standard") return `Standard:${STANDARD_MODE_SHORTCUT}`;
5043
- return mode === "plan" ? `Plan:${PLAN_MODE_SHORTCUT}` : `Mission:${MISSION_MODE_SHORTCUT}`;
5110
+ return workflowRegistryEntryShortcutLabel(mode);
5044
5111
  }
5045
5112
 
5046
5113
  function workflowFooterShortcutsEnabled(): boolean {
@@ -5053,10 +5120,10 @@ function workflowActiveShortcutSummary(mode: WorkflowEntryMode, state: WorkflowS
5053
5120
  const parts: string[] = [];
5054
5121
  if (shortcuts && ui.showWidgetShortcutHint !== false) {
5055
5122
  const bottomRelevant = mode === "plan" ? planBottomRelevant(state) : mode === "mission" ? missionBottomRelevant(state, activeMissionForUi(state)) : Boolean(state.standardTodo?.items.length);
5056
- parts.push(bottomRelevant ? "Widgets:Ctrl+Shift+T/B" : "Widget:Ctrl+Shift+T");
5123
+ parts.push(workflowWidgetShortcutLabel(bottomRelevant));
5057
5124
  }
5058
5125
  if (ui.showPresetShortcutHint !== false) {
5059
- parts.push(`Preset:${activeWorkflowPresetLabel(settings)}${shortcuts ? " Ctrl+Shift+U" : ""}`);
5126
+ parts.push(`Preset:${activeWorkflowPresetLabel(settings)}${shortcuts ? ` ${workflowPresetCycleShortcutLabel()}` : ""}`);
5060
5127
  }
5061
5128
  if (ui.showActiveWorkflowSwitchHint !== false) {
5062
5129
  const switchHints = (["standard", "plan", "mission"] as WorkflowEntryMode[])
@@ -5416,7 +5483,7 @@ function renderWorkflowThemePreview(settings: ReturnType<typeof loadWorkflowSett
5416
5483
  }
5417
5484
 
5418
5485
  function renderWorkflowFooterThemePreview(settings: ReturnType<typeof loadWorkflowSettings>): string {
5419
- const suffix = ` ${workflowShortcutStatusText(settings, "Widgets:Ctrl+Shift+T/B Preset:simple Mission:Ctrl+Shift+M")}`;
5486
+ const suffix = ` ${workflowShortcutStatusText(settings, `${workflowWidgetShortcutLabel(true)} Preset:simple ${workflowPresetCycleShortcutLabel()} ${workflowEntryShortcutLabel("mission")}`)}`;
5420
5487
  return [
5421
5488
  `${workflowWidgetRgb(settings, "title", "workflow:")}${workflowWidgetRgb(settings, "emphasis", "planning")}${suffix}`,
5422
5489
  `${workflowWidgetRgb(settings, "title", "workflow:")}${workflowWidgetRgb(settings, "progress", "executing")}${suffix}`,
@@ -7179,7 +7246,7 @@ function renderSavedPlan(plan: SavedWorkflowPlan): string {
7179
7246
  }
7180
7247
 
7181
7248
  function missionHelp(): string {
7182
- return `# Mission Mode Help\n\nMission Mode is a persistent milestone workflow for longer-running work. It runs alongside Plan Mode and does not replace /p.\n\nCommands:\n- /mission enters Mission Mode and waits for a goal\n- /mission help\n- /mission start <goal>\n- /mission <goal>\n- /mission clarify\n- /mission clarify answer 1A 2C\n- /mission clarify skip 1\n- /mission plan\n- /mission review\n- /mission approve\n- /mission run\n- /mission continue\n- /mission next\n- /mission pause\n- /mission resume\n- /mission retry\n- /mission repair\n- /mission revalidate\n- /mission set autonomy manual|approval_gated|supervised_auto|full_auto\n- /mission sync-settings\n- /mission status\n- /mission checkpoints\n- /mission stop\n- /mission list\n- /mission latest\n- /mission cleanup [limit]\n\nShortcuts:\n- Ctrl+Shift+M enters Mission Mode\n- Ctrl+Shift+L switches from Mission Mode to Plan Mode\n\nSafety:\n- Mission plans require approval before /mission run.\n- full_auto never runs unless missions.allowFullAuto=true.\n- Destructive actions, push, deploy, database mutation, and secret edits require explicit approval.\n- Mission run uses executor, validator/final-validator, safety, sub-agent, and repair gates and records stop or block reasons.\n- Validation failures attempt safe repair/revalidation within configured retry limits, then block for approval if limits or safety gates are hit.`;
7249
+ return `# Mission Mode Help\n\nMission Mode is a persistent milestone workflow for longer-running work. It runs alongside Plan Mode and does not replace /p.\n\nCommands:\n- /mission enters Mission Mode and waits for a goal\n- /mission help\n- /mission start <goal>\n- /mission <goal>\n- /mission clarify\n- /mission clarify answer 1A 2C\n- /mission clarify skip 1\n- /mission plan\n- /mission review\n- /mission approve\n- /mission run\n- /mission continue\n- /mission next\n- /mission pause\n- /mission resume\n- /mission retry\n- /mission repair\n- /mission revalidate\n- /mission set autonomy manual|approval_gated|supervised_auto|full_auto\n- /mission sync-settings\n- /mission status\n- /mission checkpoints\n- /mission stop\n- /mission list\n- /mission latest\n- /mission cleanup [limit]\n\nShortcuts:\n- ${workflowEntryShortcutLabel("mission").replace(/^Mission:/, "")} enters Mission Mode\n- ${workflowEntryShortcutLabel("plan").replace(/^Plan:/, "")} switches from Mission Mode to Plan Mode\n\nSafety:\n- Mission plans require approval before /mission run.\n- full_auto never runs unless missions.allowFullAuto=true.\n- Destructive actions, push, deploy, database mutation, and secret edits require explicit approval.\n- Mission run uses executor, validator/final-validator, safety, sub-agent, and repair gates and records stop or block reasons.\n- Validation failures attempt safe repair/revalidation within configured retry limits, then block for approval if limits or safety gates are hit.`;
7183
7250
  }
7184
7251
 
7185
7252
  function promptCandidateFiles(name: string): string[] {
@@ -7467,12 +7534,34 @@ function repairFailureApprovalReason(text: string, options: { retryMode?: string
7467
7534
  return undefined;
7468
7535
  }
7469
7536
 
7470
- function validationFailureRequiresApproval(text: string, settings: ReturnType<typeof loadWorkflowSettings>): string | undefined {
7537
+ function concreteRepairFailureHasExplicitApprovalRisk(text: string, options: { retryMode?: string; requireApprovalForDestructiveRepair: boolean; requireApprovalForOutOfScopeRepair: boolean }): boolean {
7538
+ const actionable = actionableSafetyText(text).replace(/\bworkflow_\w+\b/gi, "[tool]");
7539
+ const lines = actionable.split(/\n+/).map((line) => line.trim()).filter(Boolean);
7540
+ const highRiskAction = lines.some((line) =>
7541
+ /\b(delete|drop|destroy|reset|clean|force|overwrite|destructive|rm\s+-rf|migration|migrate|database|db\b|schema|sql|ddl|deploy|push|secret|token|credential|password|api key|auth|session|\.env|\.factory|\.cursor)\b/i.test(line)
7542
+ && /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should|perform|run|execute|apply|approval|permission)\b/i.test(line)
7543
+ );
7544
+ if (options.requireApprovalForDestructiveRepair && highRiskAction) return true;
7545
+ if (options.requireApprovalForOutOfScopeRepair && lines.some((line) =>
7546
+ /\b(out[- ]of[- ]scope|outside scope|scope expansion|new requirement|unapproved)\b/i.test(line)
7547
+ && /\b(repair|fix|change|modify|update|write|requires?|needs?|must|should|perform|run|execute|apply)\b/i.test(line)
7548
+ )) return true;
7549
+ return (options.retryMode ?? "safe_only") === "safe_only" && lines.some((line) =>
7550
+ /\b(manual approval|needs approval|ask user|unsafe|permission)\b/i.test(line)
7551
+ && /\b(repair|fix|change|modify|update|write|requires?|needs?|must|should|perform|run|execute|apply)\b/i.test(line)
7552
+ );
7553
+ }
7554
+
7555
+ function validationFailureRequiresApproval(text: string, settings: ReturnType<typeof loadWorkflowSettings>, options: { concreteRepairableIssue?: boolean; evidenceGap?: boolean } = {}): string | undefined {
7471
7556
  if (settings.missions.validationRetryMode === "off") return "validationRetryMode=off.";
7472
- return repairFailureApprovalReason(text, {
7557
+ const gateOptions = {
7473
7558
  retryMode: settings.missions.validationRetryMode,
7474
7559
  requireApprovalForDestructiveRepair: settings.missions.requireApprovalForDestructiveRepair !== false,
7475
7560
  requireApprovalForOutOfScopeRepair: settings.missions.requireApprovalForOutOfScopeRepair !== false,
7561
+ };
7562
+ if (options.concreteRepairableIssue === true && options.evidenceGap !== true && !concreteRepairFailureHasExplicitApprovalRisk(text, gateOptions)) return undefined;
7563
+ return repairFailureApprovalReason(text, {
7564
+ ...gateOptions,
7476
7565
  outOfScopeReason: "repair appears outside the approved milestone scope.",
7477
7566
  safeOnlyReason: "safe_only repair mode requires approval for this validation failure.",
7478
7567
  });
@@ -7944,7 +8033,7 @@ function fallbackMissionMilestones(goal: string): MissionMilestone[] {
7944
8033
  ];
7945
8034
  }
7946
8035
 
7947
- function missionPlanPrompt(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, options: { forceClarification?: boolean; forceReason?: string; answerSummary?: string; qualityGateFeedback?: string; reviewFeedback?: string; preflightBlock?: string } = {}): string {
8036
+ function missionPlanPrompt(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, options: { forceClarification?: boolean; forceAdditionalClarification?: boolean; forceReason?: string; answerSummary?: string; qualityGateFeedback?: string; reviewFeedback?: string; preflightBlock?: string } = {}): string {
7948
8037
  const base = readPromptFile("mission-plan.md", "You are PI MISSION MODE PLANNER. Output MISSION_DECISION: plan and milestone headings. Do not execute.");
7949
8038
  const baseWithSubagentGuidance = `${base}\n\n${subagentCapabilityTable()}`;
7950
8039
  const sub = settings.subagents;
@@ -7957,12 +8046,24 @@ function missionPlanPrompt(mission: MissionState, settings: ReturnType<typeof lo
7957
8046
  const subagentsBeforeClarification = useSubagentsBeforeClarification(settings, "mission");
7958
8047
  const planningOrchestrationPolicy = (sub as typeof sub & { planningOrchestrationPolicy?: string }).planningOrchestrationPolicy ?? "orchestrator_first";
7959
8048
  const preflightSatisfied = hasRequiredSubagentPreflight(options.preflightBlock);
8049
+ const forcedPlanningEvidenceRequired = subagentPolicyRequiresRequiredEvidence(policy) && !preflightSatisfied;
7960
8050
  const orchestratorGuidance = preflightSatisfied
7961
8051
  ? "Mission planning orchestration required by forced policy has already been handled by Workflow Suite preflight. Do not call workflow-orchestrator again unless there is genuinely new targeted orchestration work beyond the preflight findings."
8052
+ : forcedPlanningEvidenceRequired && planningNeedsOrchestrator(settings, "mission")
8053
+ ? planningOrchestrationPolicy === "forced_orchestrated"
8054
+ ? "ORCHESTRATION REQUIRED: include workflow-orchestrator as one task inside the required forced Mission Planning parallel subagent call. Do not call workflow-orchestrator separately before the worker batch."
8055
+ : "Mission planning orchestration is available for this turn. If it is useful, include workflow-orchestrator as one task inside the required forced Mission Planning parallel subagent call; do not call workflow-orchestrator separately before the worker batch."
7962
8056
  : planningNeedsOrchestrator(settings, "mission")
7963
8057
  ? "ORCHESTRATION REQUIRED: call workflow-orchestrator before launching parallel mission planning/research workers or producing the milestone plan. The orchestrator must scope worker tasks, identify capability gaps, and decide whether live docs/API access is available before workers run."
7964
8058
  : "Orchestrator is optional for this mission planning turn. Use workflow-orchestrator if the mission is broad, multi-pass, or needs coordinated workers.";
7965
- return `${baseWithSubagentGuidance}\n\n${professionalOutputGuidance("Mission planning")}\n\nMission ID: ${mission.id}\nGoal: ${mission.goal}\nAutonomy: ${mission.autonomy}\nApproval Required: ${mission.approvalRequired}\nClarification recommended: ${missionNeedsClarification(mission.goal) ? "yes" : "no"}\nInitial analysis requirement: ${analysisInstruction}\n${options.answerSummary ? `\nMission clarification answers already supplied:\n${options.answerSummary}\nUse these answers as planning constraints. Do not ask the same clarification again. Produce MISSION_DECISION: plan with parser-safe milestones unless a genuinely new blocker exists; carry remaining uncertainty as assumptions/open questions inside the mission plan.\n` : ""}${options.reviewFeedback ? `\nMISSION REVIEW REPAIR REQUIRED BEFORE APPROVAL.\nReviewer feedback:\n${options.reviewFeedback}\nRevise the mission milestone plan only. Do not execute. Return MISSION_DECISION: plan with parser-safe milestones that address the reviewer feedback.\n` : ""}${options.forceClarification ? `\nMISSION CLARIFICATION IS REQUIRED BEFORE FINAL MILESTONE PLANNING. Reason: ${options.forceReason ?? "mission clarification policy requires it"}\nYour first line must be exactly MISSION_DECISION: clarify. Your job in this turn is to perform lightweight mission analysis, then generate only high-value mission-goal-specific A/B/C/D clarification questions. Do not produce the mission plan yet. ${subagentsBeforeClarification ? "If mission planning depth/policy calls for it and sub-agent use is available, use read-only planning/research sub-agents before asking clarification so the questions are context-aware." : "Do not call sub-agents in this clarification-generation turn unless forced by sub-agent policy."}\n` : ""}${options.qualityGateFeedback ? `\nThe previous mission clarification output failed the clarification quality gate: ${options.qualityGateFeedback}\nRegenerate better mission-specific clarification questions${options.forceClarification ? ". Do not produce a mission plan in this retry." : ", or use MISSION_DECISION: plan if no high-value question exists."}\n` : ""}\nMANDATORY STRUCTURED HANDOFF: call mission_plan_result with decision=clarify, plan, or blocked before final response. The typed tool payload is the primary handoff control plane. If the tool is unavailable for any reason, fall back to the parser-safe legacy format below:\n## Clarifying Questions\n\n## Q1. <mission-specific short question>\nA. <option specific to this mission>\nB. <option specific to this mission>\nC. <option specific to this mission>\nD. Other: type your own answer\nSkip this question\n\nRules for questions:\n- Generate questions from the actual mission goal after initial analysis.\n- Clarification is not a survey. Ask only when the answer genuinely changes the milestone plan.\n- Prefer one focused question when possible. Maximum ${missionMaxClarificationQuestions(settings)} questions.\n- Each question must resolve at least one real ambiguity, risk, scope decision, validation decision, target-environment choice, permission boundary, sub-agent need, diagnostic-vs-implementation choice, acceptance criterion, or forbidden-file concern.\n- Each question must be specific and actionable.\n- Each option must be a concrete choice, not "yes/no".\n- Always include D. Other as last option.\n- Do not ask product-roadmap or strategy questions unless the user explicitly requested product strategy planning.\n\nWorkflow settings:\n- mission.planningDepth: ${missionPlanningDepth(settings)}\n- mission.clarificationMode: ${missionClarificationMode(settings)}\n- mission.maxClarificationQuestions: ${missionMaxClarificationQuestions(settings)}\n- mission.interactiveClarificationEnabled: ${settings.missions.interactiveClarificationEnabled !== false}\n- mission.clarificationTiming: ${settings.missions.clarificationTiming ?? "after_initial_analysis"}\n- mission.clarificationQualityGate: ${settings.missions.clarificationQualityGate !== false}\n- mission.allowClarificationWithoutAnalysis: ${settings.missions.allowClarificationWithoutAnalysis === true}\n- mission.useSubagentsBeforeClarification: ${settings.missions.useSubagentsBeforeClarification !== false}\n- subagents.enabled: ${sub.enabled}\n- mission.subagentPolicy: ${policy}\n- Mission Planning Workers: ${activeWorkerTargetLabel(policy, { deep: deepWorkers, maximum: maxWorkers })}\n- subagents.requireApprovalBeforeRun: ${sub.requireApprovalBeforeRun === true}\n- planningOrchestrationPolicy: ${planningOrchestrationPolicy}\n- allowParallelReadOnly: ${sub.allowParallelReadOnly !== false}\n- allowParallelPlanning: ${sub.allowParallelPlanning !== false}\n- allowParallelEdits: ${sub.allowParallelEdits === true}${requiredSubagentPreflightSection(options.preflightBlock)}\n\nMission sub-agent policy:\n- off: do not use sub-agents.\n- auto: sub-agents are strongly encouraged; skip only with a concrete trivial/unavailable reason.\n- deep: use at least ${deepWorkers} worker(s) for non-trivial mission planning.\n- maximum: strongly target at least ${maxWorkers} worker(s); skip only for trivial or unavailable cases with exact reason.\n- forced: ${preflightSatisfied ? "already satisfied by Workflow Suite preflight; do not call the visible subagent tool solely for policy compliance." : `you MUST use at least ${Math.max(1, maxWorkers)} worker(s), or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>.`}\n\n${orchestratorGuidance}\n\nUse safe read-only/planning sub-agents when enabled. Do not execute or edit. Include mission Sub-Agent Usage Summary with workers used and findings, or the exact skip/blocker reason.
8059
+ const forceClarification = options.forceClarification === true || options.forceAdditionalClarification === true;
8060
+ const answerGuidance = options.forceAdditionalClarification === true
8061
+ ? "Use these prior answers as planning constraints. Do not ask duplicate or already-answered clarification questions. This turn is an explicit request for additional Mission clarification, so produce MISSION_DECISION: clarify with only new mission-specific questions that materially change the plan."
8062
+ : "Use these answers as planning constraints. Do not ask the same clarification again. Produce MISSION_DECISION: plan with parser-safe milestones unless a genuinely new blocker exists; carry remaining uncertainty as assumptions/open questions inside the mission plan.";
8063
+ const forceClarificationGuidance = options.forceAdditionalClarification === true
8064
+ ? "Your first line must be exactly MISSION_DECISION: clarify. Your job in this turn is to perform lightweight contextual mission analysis, including relevant codebase/project-rule inspection when available, then generate only new high-value mission-goal-specific A/B/C/D clarification questions. Do not reuse stale question indexes or duplicate prior answered questions. Do not produce the mission plan yet."
8065
+ : "Your first line must be exactly MISSION_DECISION: clarify. Your job in this turn is to perform lightweight mission analysis, then generate only high-value mission-goal-specific A/B/C/D clarification questions. Do not produce the mission plan yet.";
8066
+ return `${baseWithSubagentGuidance}\n\n${professionalOutputGuidance("Mission planning")}\n\nMission ID: ${mission.id}\nGoal: ${mission.goal}\nAutonomy: ${mission.autonomy}\nApproval Required: ${mission.approvalRequired}\nClarification recommended: ${missionNeedsClarification(mission.goal) ? "yes" : "no"}\nInitial analysis requirement: ${analysisInstruction}\n${options.answerSummary ? `\nMission clarification answers already supplied:\n${options.answerSummary}\n${answerGuidance}\n` : ""}${options.reviewFeedback ? `\nMISSION REVIEW REPAIR REQUIRED BEFORE APPROVAL.\nReviewer feedback:\n${options.reviewFeedback}\nRevise the mission milestone plan only. Do not execute. Return MISSION_DECISION: plan with parser-safe milestones that address the reviewer feedback.\n` : ""}${forceClarification ? `\nMISSION CLARIFICATION IS REQUIRED BEFORE FINAL MILESTONE PLANNING. Reason: ${options.forceReason ?? "mission clarification policy requires it"}\n${forceClarificationGuidance} ${subagentsBeforeClarification ? "If mission planning depth/policy calls for it and sub-agent use is available, use read-only planning/research sub-agents before asking clarification so the questions are context-aware." : "Do not call sub-agents in this clarification-generation turn unless forced by sub-agent policy."}\n` : ""}${options.qualityGateFeedback ? `\nThe previous mission clarification output failed the clarification quality gate: ${options.qualityGateFeedback}\nRegenerate better mission-specific clarification questions${forceClarification ? ". Do not produce a mission plan in this retry." : ", or use MISSION_DECISION: plan if no high-value question exists."}\n` : ""}\nMANDATORY STRUCTURED HANDOFF: call mission_plan_result with decision=clarify, plan, or blocked before final response. The typed tool payload is the primary handoff control plane. If the tool is unavailable for any reason, fall back to the parser-safe legacy format below:\n## Clarifying Questions\n\n## Q1. <mission-specific short question>\nA. <option specific to this mission>\nB. <option specific to this mission>\nC. <option specific to this mission>\nD. Other: type your own answer\nSkip this question\n\nRules for questions:\n- Generate questions from the actual mission goal after initial analysis.\n- Additional clarification turns must use fresh dynamic mission analysis and must not display or reuse stored prior questions.\n- Additional clarification turns must ask only new, non-duplicate, mission-specific questions; prior answers are constraints, not question templates.\n- Do not reuse stale question indexes from a prior clarification turn.\n- Clarification is not a survey. Ask only when the answer genuinely changes the milestone plan.\n- Prefer one focused question when possible. Maximum ${missionMaxClarificationQuestions(settings)} questions.\n- Each question must resolve at least one real ambiguity, risk, scope decision, validation decision, target-environment choice, permission boundary, sub-agent need, diagnostic-vs-implementation choice, acceptance criterion, or forbidden-file concern.\n- Each question must be specific and actionable.\n- Each option must be a concrete choice, not "yes/no".\n- Always include D. Other as last option.\n- Do not ask product-roadmap or strategy questions unless the user explicitly requested product strategy planning.\n\nWorkflow settings:\n- mission.planningDepth: ${missionPlanningDepth(settings)}\n- mission.clarificationMode: ${missionClarificationMode(settings)}\n- mission.maxClarificationQuestions: ${missionMaxClarificationQuestions(settings)}\n- mission.interactiveClarificationEnabled: ${settings.missions.interactiveClarificationEnabled !== false}\n- mission.clarificationTiming: ${settings.missions.clarificationTiming ?? "after_initial_analysis"}\n- mission.clarificationQualityGate: ${settings.missions.clarificationQualityGate !== false}\n- mission.allowClarificationWithoutAnalysis: ${settings.missions.allowClarificationWithoutAnalysis === true}\n- mission.useSubagentsBeforeClarification: ${settings.missions.useSubagentsBeforeClarification !== false}\n- subagents.enabled: ${sub.enabled}\n- mission.subagentPolicy: ${policy}\n- Mission Planning Workers: ${activeWorkerTargetLabel(policy, { deep: deepWorkers, maximum: maxWorkers })}\n- subagents.requireApprovalBeforeRun: ${sub.requireApprovalBeforeRun === true}\n- planningOrchestrationPolicy: ${planningOrchestrationPolicy}\n- allowParallelReadOnly: ${sub.allowParallelReadOnly !== false}\n- allowParallelPlanning: ${sub.allowParallelPlanning !== false}\n- allowParallelEdits: ${sub.allowParallelEdits === true}${requiredSubagentPreflightSection(options.preflightBlock)}\n\nMission sub-agent policy:\n- off: do not use sub-agents.\n- auto: sub-agents are strongly encouraged; skip only with a concrete trivial/unavailable reason.\n- deep: use at least ${deepWorkers} worker(s) for non-trivial mission planning.\n- maximum: strongly target at least ${maxWorkers} worker(s); skip only for trivial or unavailable cases with exact reason.\n- forced: ${preflightSatisfied ? "already satisfied by Workflow Suite preflight; do not call the visible subagent tool solely for policy compliance." : `you MUST use at least ${Math.max(1, maxWorkers)} worker(s), or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>.`}\n\n${orchestratorGuidance}\n\nUse safe read-only/planning sub-agents when enabled. Do not execute or edit. Include mission Sub-Agent Usage Summary with workers used and findings, or the exact skip/blocker reason.
7966
8067
 
7967
8068
  Diagram guidance:
7968
8069
  ${workflowMermaidGuidance()}
@@ -7981,15 +8082,15 @@ function missionReviewPrompt(mission: MissionState, settings = loadWorkflowSetti
7981
8082
  const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
7982
8083
  const subagentGuidance = !reviewSubagentsAllowed(settings)
7983
8084
  ? "Reviewer sub-agents are disabled by settings. Do not call subagent."
7984
- : preflightSatisfied && policy === "forced"
8085
+ : preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
7985
8086
  ? forcedSubagentPolicySatisfiedGuidance("review")
7986
8087
  : policy === "forced"
7987
8088
  ? `FORCED SUB-AGENT POLICY: before final reviewer report, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} review worker(s) for independent read-only challenge review. If subagent execution fails, stop and report the exact blocker.`
7988
8089
  : policy === "maximum"
7989
- ? `For non-trivial review, use multiple reviewer sub-agents for independent risk review, regression review, implementation-scope review, and validation-plan review. Strongly target at least ${workers.maximum} review workers; skip only for trivial or unavailable cases with exact reason.`
8090
+ ? `For non-trivial review, use multiple reviewer sub-agents for independent risk review, regression review, implementation-scope review, and validation-plan review. Strongly target at least ${workers.maximum} review workers; internally decide delegate or skip, skip only for trivial/no-useful-parallel/unavailable cases, and give only the concise skip reason. Do not print internal policy deliberation.`
7990
8091
  : policy === "deep"
7991
- ? `For non-trivial review, use at least ${workers.deep} reviewer sub-agent for independent risk review, implementation-scope review, or validation-plan review. Explain any skip.`
7992
- : "Reviewer sub-agents are strongly encouraged. Use them to challenge scope, milestones, risks, and validation plan; skip only for trivial review and explain why.";
8092
+ ? `For non-trivial review, use at least ${workers.deep} reviewer sub-agent for independent risk review, implementation-scope review, or validation-plan review. Internally decide delegate or skip and give only the concise skip reason. Do not print internal policy deliberation.`
8093
+ : "Reviewer sub-agents are strongly encouraged. Internally decide delegate or skip. Use them to challenge scope, milestones, risks, and validation plan; skip only for trivial review or no useful parallel work and give only the concise skip reason. Do not print internal policy deliberation.";
7993
8094
  const milestoneLines = mission.milestones.map((milestone, index) => `- ${milestone.id || `M${index + 1}`}: ${milestone.title || "Untitled"}\n Objective: ${milestone.objective || "none recorded"}\n Steps: ${(milestone.steps ?? []).join("; ") || "none recorded"}\n Acceptance Criteria: ${(milestone.validation ?? []).join("; ") || "none recorded"}\n Risks: ${(milestone.risks ?? []).join("; ") || "none recorded"}`).join("\n") || "- none recorded";
7994
8095
  return `You are in PI MISSION MODE REVIEWER MODE.
7995
8096
 
@@ -8030,7 +8131,7 @@ ${subagentCapabilityTable()}
8030
8131
 
8031
8132
  - When reviewPolicy is auto, deep, or maximum, reviewer sub-agents are expected for non-trivial mission plans to challenge scope, milestones, risks, and validation plan.
8032
8133
  - Reviewer sub-agents must not perform direct file edits.
8033
- - ${preflightSatisfied && policy === "forced" ? "Forced review policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If reviewPolicy is forced, required sub-agents must run before the report, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
8134
+ - ${preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy) ? "Forced review policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If reviewPolicy is forced, required sub-agents must run before the report, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
8034
8135
  - The reviewer must not rubber-stamp execution; surface missing requirements before the mission is approved.
8035
8136
  - Mission Review is notes-first for control flow. Use NOTES for nearly all actionable advice, including severe executor-correctable milestone findings. Use NEEDS REPAIR only when the Mission plan is structurally unusable, such as having no parser-safe milestones.
8036
8137
  - Rule clarifications, game-rule pinning, AI contracts, settings-step details, accessibility criteria, implementation details, validation improvements, rollback wording, files-to-avoid lists, UI instruction notes, README scope decisions, icon choices, localStorage key naming, impossible-but-correctable milestone details, and executor cautions are NOTES.
@@ -8410,6 +8511,8 @@ export default function workflowModes(pi: ExtensionAPI): void {
8410
8511
  if (phase === "Planning") return planToolsFor(settings);
8411
8512
  if (phase === "Review") return reviewToolsFor(settings);
8412
8513
  if (phase === "Validation") return validationToolsFor(settings);
8514
+ if (phase === "Repair" && state.mode === "mission_repairing") return missionRepairToolsFor(settings);
8515
+ if (phase === "Execution" && state.mode === "mission_running") return missionExecutionToolsFor(settings);
8413
8516
  return executionToolsFor(settings);
8414
8517
  };
8415
8518
  const armWorkflowToolsForPhase = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, reason: string): void => {
@@ -8453,6 +8556,124 @@ export default function workflowModes(pi: ExtensionAPI): void {
8453
8556
  syncWorkflowRuntimeForActivity(ctx, `pending phase clear: ${phase}`);
8454
8557
  traceWorkflowTracking(ctx, "pending-phase-clear", { phase, reason });
8455
8558
  };
8559
+ const requiredToolsForWorkflowPhase = (settings: ReturnType<typeof loadWorkflowSettings>, phase: WorkflowPendingToolPhase, mode: WorkflowState["mode"] = state.mode): string[] => {
8560
+ let tools: string[];
8561
+ if (mode === "standard") {
8562
+ tools = [...BASE_EXECUTE_TOOLS, "standard_todo", STANDARD_HANDOFF_RESULT_TOOL];
8563
+ if (standardSubagentsAllowed(settings)) tools.push("subagent");
8564
+ return Array.from(new Set(tools));
8565
+ }
8566
+ if (phase === "Planning") {
8567
+ tools = [...PLAN_TOOLS, WORKFLOW_PLAN_RESULT_TOOL];
8568
+ if (planningSubagentsAllowed(settings)) tools.push("subagent");
8569
+ return Array.from(new Set(tools));
8570
+ }
8571
+ if (phase === "Review") {
8572
+ tools = ["read", "grep", "find", "ls", WORKFLOW_DIAGRAM_TOOL, WORKFLOW_REVIEW_RESULT_TOOL];
8573
+ if (reviewSubagentsAllowed(settings)) tools.push("subagent");
8574
+ return Array.from(new Set(tools));
8575
+ }
8576
+ if (phase === "Validation") {
8577
+ tools = ["read", "grep", "find", "ls", "bash", WORKFLOW_DIAGRAM_TOOL, WORKFLOW_VALIDATION_RESULT_TOOL];
8578
+ if (validationSubagentsAllowed(settings)) tools.push("subagent");
8579
+ return Array.from(new Set(tools));
8580
+ }
8581
+ if (phase === "Repair" && mode === "mission_repairing") {
8582
+ tools = [...PLAN_TOOLS, "edit", "write", "bash", WORKFLOW_DIAGRAM_TOOL, WORKFLOW_REPAIR_RESULT_TOOL];
8583
+ if (executionSubagentsAllowed(settings)) tools.push("subagent");
8584
+ return Array.from(new Set(tools));
8585
+ }
8586
+ if (phase === "Execution" && mode === "mission_running") {
8587
+ tools = [...PLAN_TOOLS, "edit", "write", "bash", WORKFLOW_DIAGRAM_TOOL, MISSION_MILESTONE_RESULT_TOOL];
8588
+ if (executionSubagentsAllowed(settings)) tools.push("subagent");
8589
+ return Array.from(new Set(tools));
8590
+ }
8591
+ if (phase === "Repair") {
8592
+ tools = [...PLAN_TOOLS, "edit", "write", "bash", WORKFLOW_PROGRESS_TOOL, WORKFLOW_REPAIR_RESULT_TOOL];
8593
+ if (executionSubagentsAllowed(settings)) tools.push("subagent");
8594
+ return Array.from(new Set(tools));
8595
+ }
8596
+ return requiredPlanExecutionTools(settings);
8597
+ };
8598
+ const missingToolsForWorkflowPhase = (activeTools: string[], settings: ReturnType<typeof loadWorkflowSettings>, phase: WorkflowPendingToolPhase, mode: WorkflowState["mode"] = state.mode): string[] => {
8599
+ const active = new Set(activeTools);
8600
+ return requiredToolsForWorkflowPhase(settings, phase, mode).filter((tool) => !active.has(tool));
8601
+ };
8602
+ const toolSurfaceBlockReason = (phase: WorkflowPendingToolPhase, missing: string[], source: string): string =>
8603
+ `Required ${phase} tools are unavailable after tool rearm at ${source}: ${missing.join(", ")}. Phase prompt was not delivered.`;
8604
+ const blockWorkflowToolSurface = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, missing: string[], source: string): string => {
8605
+ const settings = loadWorkflowSettings(ctx.cwd);
8606
+ const reason = toolSurfaceBlockReason(phase, missing, source);
8607
+ clearPendingWorkflowToolPhase(ctx, phase, `tool-surface block: ${source}`);
8608
+ recordWorkflowInternalEvent(ctx, `Workflow ${phase} tool surface blocked at ${source}: ${missing.join(", ")}.`);
8609
+ traceWorkflowTracking(ctx, "tool-surface-blocked", { phase, source, missingTools: missing });
8610
+ if (state.mode === "standard") {
8611
+ setStandardRuntimeActive(ctx, false);
8612
+ pi.setActiveTools(standardToolsFor(settings));
8613
+ show(pi, `# Standard Workflow Blocked\n\n${reason}\n\nRetry the request after the Standard tool surface is repaired, or run /standard status.`);
8614
+ return reason;
8615
+ }
8616
+ if (isMissionWorkflowMode(state)) {
8617
+ const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
8618
+ if (mission) {
8619
+ const nextAction = phase === "Validation"
8620
+ ? "Repair the validation tool surface, then run /mission revalidate or /mission resume."
8621
+ : phase === "Repair"
8622
+ ? "Repair the Mission repair tool surface, then run /mission repair or /mission resume."
8623
+ : phase === "Review"
8624
+ ? "Repair the Mission review tool surface, then run /mission review or /mission approve."
8625
+ : "Repair the Mission execution tool surface, then run /mission continue or /mission resume.";
8626
+ const paused = saveActiveMission({
8627
+ ...mission,
8628
+ status: "paused",
8629
+ lastStopReason: reason,
8630
+ lastBlockReason: reason,
8631
+ nextAction,
8632
+ lastSummary: `Mission ${phase.toLowerCase()} did not start because required tools were unavailable.`,
8633
+ });
8634
+ activeMission = paused;
8635
+ updateState({ mode: "mission_paused", activeMissionId: paused.id, task: paused.goal, originalTask: paused.goal, approvedPlan: paused.planText }, ctx);
8636
+ } else {
8637
+ updateState({ mode: "awaiting_mission_input", activeMissionId: undefined }, ctx);
8638
+ }
8639
+ pi.setActiveTools(planToolsFor(settings));
8640
+ show(pi, `# Mission Workflow Blocked\n\n${reason}\n\nRun /mission status before continuing.`);
8641
+ return reason;
8642
+ }
8643
+ const planText = state.approvedPlan ?? state.draftPlan;
8644
+ const targetMode: WorkflowState["mode"] = phase === "Validation"
8645
+ ? "executed"
8646
+ : phase === "Repair"
8647
+ ? "validated"
8648
+ : phase === "Review"
8649
+ ? "plan_approved"
8650
+ : state.reviewerReport ? "reviewed" : "plan_approved";
8651
+ updateState({
8652
+ mode: targetMode,
8653
+ lastReviewFailure: phase === "Review" ? reason : state.lastReviewFailure,
8654
+ lastValidationFailure: phase === "Validation" ? reason : state.lastValidationFailure,
8655
+ lastRepairStatus: state.lastRepairStatus === "running" ? "failed" : state.lastRepairStatus,
8656
+ lastRepairAttempt: phase === "Repair" ? reason : state.lastRepairAttempt,
8657
+ planProgress: workflowPlanProgressEnabled(settings) && planText?.trim()
8658
+ ? mergePlanProgress({ ...state, mode: targetMode }, settings, { lifecycleStatus: "blocked", nextAction: `repair ${phase.toLowerCase()} tool surface, then /plan continue` }, planText)
8659
+ : state.planProgress,
8660
+ lastPlanStopSummary: undefined,
8661
+ }, ctx);
8662
+ pi.setActiveTools(webSafePlanTools(PLAN_TOOLS));
8663
+ show(pi, `# Plan Workflow Blocked\n\n${reason}\n\nRun /plan continue after the tool surface is repaired.`);
8664
+ return reason;
8665
+ };
8666
+ const verifyWorkflowToolSurface = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, source: string, mode: WorkflowState["mode"] = state.mode): string | undefined => {
8667
+ const settings = loadWorkflowSettings(ctx.cwd);
8668
+ const missing = missingToolsForWorkflowPhase(pi.getActiveTools(), settings, phase, mode);
8669
+ traceWorkflowTracking(ctx, "verify-tool-surface", { phase, source, mode, activeTools: pi.getActiveTools(), missingTools: missing });
8670
+ return missing.length ? blockWorkflowToolSurface(ctx, phase, missing, source) : undefined;
8671
+ };
8672
+ const armAndVerifyWorkflowToolsForPhase = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, reason: string): void => {
8673
+ armWorkflowToolsForPhase(ctx, phase, reason);
8674
+ const blockReason = verifyWorkflowToolSurface(ctx, phase, reason);
8675
+ if (blockReason) throw new WorkflowToolSurfaceAbort(blockReason);
8676
+ };
8456
8677
  let workflowSubagentActivityTimer: ReturnType<typeof setInterval> | undefined;
8457
8678
  let workflowSubagentActivityRenderCtx: ExtensionContext | undefined;
8458
8679
  let workflowUiClockTimer: ReturnType<typeof setInterval> | undefined;
@@ -8642,6 +8863,17 @@ export default function workflowModes(pi: ExtensionAPI): void {
8642
8863
 
8643
8864
  const initialPlanParentSuppressed = (): boolean => state.mode === "plan_draft" && state.reviewHandoffSuppression?.kind === "plan_typed_initial_to_approval" && state.lastWorkflowHandoff?.type === WORKFLOW_PLAN_RESULT_TOOL;
8644
8865
  const planReviewParentSuppressed = (): boolean => state.mode === "executing" && state.reviewHandoffSuppression?.kind === "plan_typed_review_to_execution";
8866
+ const missionReviewParentSuppressed = (): boolean => {
8867
+ if (state.reviewHandoffSuppression?.kind !== "mission_typed_review_to_approval") return false;
8868
+ if (state.mode !== "mission_plan_ready" && state.mode !== "mission_approved" && state.mode !== "mission_running") return false;
8869
+ const suppressedMissionId = state.reviewHandoffSuppression.activeMissionId;
8870
+ if (suppressedMissionId && state.activeMissionId && suppressedMissionId !== state.activeMissionId) return false;
8871
+ const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined)
8872
+ ?? (suppressedMissionId ? loadMissionState(suppressedMissionId) : undefined)
8873
+ ?? activeMission
8874
+ ?? loadMissionState("latest");
8875
+ return mission?.currentStep !== "reviewer" && (mission?.reviewerVerdict === "PASS" || mission?.reviewerVerdict === "NOTES");
8876
+ };
8645
8877
 
8646
8878
  const markPlanValidationHandoffDidNotStart = (ctx: ExtensionContext, settings: ReturnType<typeof loadWorkflowSettings>, reason: string): void => {
8647
8879
  updateState({
@@ -8829,7 +9061,10 @@ export default function workflowModes(pi: ExtensionAPI): void {
8829
9061
  const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex;
8830
9062
  const progressEnabled = workflowPlanProgressEnabled(settings);
8831
9063
  const progressedPlanProgress = progressEnabled ? planProgressWithCompletedSteps(state, settings, completedSteps) : state.planProgress;
8832
- const progressedState = progressedPlanProgress ? { ...state, planProgress: progressedPlanProgress } : state;
9064
+ const nextPlanStepValidationIndex = settings.workflow.validateAfterEachStep === true ? executedStepIndex : state.planStepValidationIndex;
9065
+ const progressedState = progressedPlanProgress
9066
+ ? { ...state, planProgress: progressedPlanProgress, planStepValidationIndex: nextPlanStepValidationIndex }
9067
+ : { ...state, planStepValidationIndex: nextPlanStepValidationIndex };
8833
9068
  // Fix C4: lifecycle matching mode for blocked/completion paths
8834
9069
  const blockedLifecycle = state.reviewerReport ? "reviewed" : "blocked";
8835
9070
  if (status !== "completed") {
@@ -8838,16 +9073,32 @@ export default function workflowModes(pi: ExtensionAPI): void {
8838
9073
  showBlockedPlanRecoveryMenu(ctx);
8839
9074
  return { ...typedToolAck(), details: { accepted: true, status, validationStarted: false } };
8840
9075
  }
8841
- const hasOpenSteps = progressEnabled && progressedPlanProgress?.steps.length && settings.workflow.validateAfterEachStep !== true && planProgressHasOpenSteps(progressedPlanProgress);
8842
- if (hasOpenSteps && !completedSteps.length) {
8843
- updateState({ mode: state.reviewerReport ? "reviewed" : "plan_approved", executionSummary: summary, planExecutionStepIndex: undefined, planProgress: progressEnabled ? mergePlanProgress(progressedState, settings, { lifecycleStatus: blockedLifecycle, validationStatus: "pending", nextAction: "continue execution" }) : state.planProgress }, ctx);
8844
- queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason: "Executor reported completion, but step tracking shows incomplete steps with no completedSteps metadata. Resume execution to complete remaining steps." });
9076
+ const hasOpenSteps = progressEnabled && progressedPlanProgress?.steps.length && planProgressHasOpenSteps(progressedPlanProgress);
9077
+ const executionIncomplete = planExecutionIncomplete(progressedState, settings);
9078
+ const missingCompletedSteps = executionIncomplete && !completedSteps.length;
9079
+ if (executionIncomplete) {
9080
+ const reason = missingCompletedSteps
9081
+ ? "Executor reported completion, but step tracking shows incomplete steps with no completedSteps metadata. Resume execution to complete remaining steps before validation."
9082
+ : "Executor reported completion, but approved Plan progress still has incomplete steps. Resume execution to complete remaining steps before validation.";
9083
+ updateState({
9084
+ mode: "reviewed",
9085
+ executionSummary: summary,
9086
+ planExecutionStepIndex: undefined,
9087
+ planStepValidationIndex: undefined,
9088
+ validationReport: undefined,
9089
+ validationVerdict: undefined,
9090
+ lastValidationFailure: undefined,
9091
+ lastRepairStatus: "none",
9092
+ lastRepairAttempt: undefined,
9093
+ planProgress: progressEnabled ? mergePlanProgress({ ...progressedState, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
9094
+ }, ctx);
9095
+ queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason });
8845
9096
  showBlockedPlanRecoveryMenu(ctx);
8846
9097
  return { ...typedToolAck(), details: { accepted: true, status: "blocked", validationStarted: false } };
8847
9098
  }
8848
- const progressWarning = hasOpenSteps ? "Execution completed with incomplete Plan step metadata; validator must verify approved Plan coverage." : undefined;
9099
+ const progressWarning = hasOpenSteps && !planValidationBoundaryReached(progressedState, settings) ? "Execution completed with incomplete Plan step metadata; validator must verify approved Plan coverage." : undefined;
8849
9100
  const executionSummary = progressWarning ? `${summary}\n\nPlan Progress Warning: ${progressWarning}` : summary;
8850
- updateState({ mode: "executed", executionSummary, planStepValidationIndex: settings.workflow.validateAfterEachStep === true ? executedStepIndex : state.planStepValidationIndex, planExecutionStepIndex: undefined, planProgress: progressEnabled ? mergePlanProgress({ ...progressedState, mode: "executed" }, settings, { lifecycleStatus: "executing", validationStatus: progressWarning ? "unknown" : progressedState.planProgress?.validationStatus, nextAction: validationAvailable && validateAfterExecution ? "validator" : "finish workflow" }) : progressedPlanProgress }, ctx);
9101
+ updateState({ mode: "executed", executionSummary, planStepValidationIndex: nextPlanStepValidationIndex, planExecutionStepIndex: undefined, planProgress: progressEnabled ? mergePlanProgress({ ...progressedState, mode: "executed" }, settings, { lifecycleStatus: "executing", validationStatus: progressWarning ? "unknown" : progressedState.planProgress?.validationStatus, nextAction: validationAvailable && validateAfterExecution ? "validator" : "finish workflow" }) : progressedPlanProgress }, ctx);
8851
9102
  if (validationAvailable && validateAfterExecution) {
8852
9103
  setPendingWorkflowToolPhase(ctx, "Validation", "typed execution accepted");
8853
9104
  }
@@ -8913,6 +9164,21 @@ export default function workflowModes(pi: ExtensionAPI): void {
8913
9164
  return { ...typedToolAck(false), details: { accepted: false, verdict, reason: `workflow_validation_result is only accepted during validation phases; current mode is ${state.mode}.` }, isError: true };
8914
9165
  }
8915
9166
  if (planValidationMode) {
9167
+ const settings = loadWorkflowSettings(ctx.cwd);
9168
+ if (!planValidationBoundaryReached(state, settings)) {
9169
+ const reason = "Approved Plan execution is incomplete. Validation results are not accepted until the configured validation boundary is reached.";
9170
+ updateState({
9171
+ mode: "reviewed",
9172
+ validationReport: undefined,
9173
+ validationVerdict: undefined,
9174
+ lastValidationFailure: undefined,
9175
+ lastRepairStatus: "none",
9176
+ lastRepairAttempt: undefined,
9177
+ planStepValidationIndex: undefined,
9178
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
9179
+ }, ctx);
9180
+ return { ...typedToolAck(false), details: { accepted: false, verdict, reason }, isError: true };
9181
+ }
8916
9182
  planForcedSubagentPreflightReconcile(ctx, "Validation");
8917
9183
  const forcedValidationBlock = forcedSubagentUsageSatisfied(ctx, "Validation");
8918
9184
  if (forcedValidationBlock) {
@@ -8942,6 +9208,22 @@ export default function workflowModes(pi: ExtensionAPI): void {
8942
9208
  showBlockedPlanRecoveryMenu(ctx);
8943
9209
  return { ...typedToolAck(), details: { accepted: true, status } };
8944
9210
  }
9211
+ if (!planValidationBoundaryReached(state, settings)) {
9212
+ updateState({
9213
+ mode: "reviewed",
9214
+ executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary received before validation boundary (not revalidated):\n${summary}`.trim(),
9215
+ lastWorkflowHandoff: undefined,
9216
+ lastRepairStatus: "none",
9217
+ lastRepairAttempt: undefined,
9218
+ validationReport: undefined,
9219
+ validationVerdict: undefined,
9220
+ lastValidationFailure: undefined,
9221
+ planStepValidationIndex: undefined,
9222
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
9223
+ }, ctx);
9224
+ show(pi, "# Plan Repair Ignored\n\nApproved Plan execution is incomplete. Repair/revalidation is not available until the configured validation boundary is reached.\n\nUse /plan continue.");
9225
+ return { ...typedToolAck(), details: { accepted: true, status, ignored: true } };
9226
+ }
8945
9227
  updateState({ mode: "revalidating", executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary:\n${summary}`.trim(), lastRepairStatus: "completed", lastRepairAttempt: compact(summary, 1200), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "revalidating", lastRepairStatus: "completed" }, settings, { lifecycleStatus: "revalidating", validationStatus: "running", repairStatus: "completed", nextAction: "validation result" }) : state.planProgress }, ctx);
8946
9228
  setPendingWorkflowToolPhase(ctx, "Validation", "typed repair completed");
8947
9229
  deferWorkflowAction(pi, "begin typed revalidation after repair", async () => { await beginValidation(ctx, true, true); }, { timeoutMs: workflowDeferredPhaseTimeoutMs(ctx, "Validation"), onFailure: (error) => recoverPlanTransientHandoffFailure(ctx, "repair_revalidation", transientFailureReason("Typed repair completed but revalidation handoff failed", error instanceof Error ? error.message : String(error)), { phase: "Validation", completedRepair: true }) });
@@ -9020,6 +9302,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
9020
9302
  if (!passed) {
9021
9303
  await handleMissionReviewFailure(ctx, reviewed, missionReview.verdict, missionReview.report);
9022
9304
  } else {
9305
+ setReviewHandoffSuppression(ctx, "mission_typed_review_to_approval");
9023
9306
  deferWorkflowAction(pi, "resume mission approval after reviewer pass", async () => {
9024
9307
  const latest = loadMissionState(reviewed.id) ?? activeMission ?? reviewed;
9025
9308
  const block = missionReviewContinuationBlock(latest);
@@ -9803,7 +10086,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
9803
10086
  if (isStandardWorkflowMode(state)) {
9804
10087
  return `# Workflow Widgets\n\nStandard Top Widget: ${widgetVisibilityLabel(settings, "standardTop")}\nStandard To Do Widget: ${widgetVisibilityLabel(settings, "standardBottom")}\nStatus Line: ${widgetVisibilityStatus(state, settings) ?? "none"}\n${common}`;
9805
10088
  }
9806
- return `# Workflow Widgets\n\nNo active Plan/Mission/Standard widget is currently visible.\nStatus Line: ${widgetVisibilityStatus(state, settings) ?? "none"}\n\nEntry Shortcuts:\n- ${workflowEntryShortcutLabel("standard")}\n- ${workflowEntryShortcutLabel("plan")}\n- ${workflowEntryShortcutLabel("mission")}\n\nActive-mode editor hints use compact text such as: Widgets:Ctrl+Shift+T/B Preset:${activeWorkflowPresetLabel(settings)} Ctrl+Shift+U Standard:Ctrl+Shift+S Mission:Ctrl+Shift+M\nWidget toggles and preset cycling are visible only while Plan/Mission/Standard Mode is active.\n\n${common}`;
10089
+ return `# Workflow Widgets\n\nNo active Plan/Mission/Standard widget is currently visible.\nStatus Line: ${widgetVisibilityStatus(state, settings) ?? "none"}\n\nEntry Shortcuts:\n- ${workflowEntryShortcutLabel("standard")}\n- ${workflowEntryShortcutLabel("plan")}\n- ${workflowEntryShortcutLabel("mission")}\n\nActive-mode editor hints use compact text such as: ${workflowWidgetShortcutLabel(true)} Preset:${activeWorkflowPresetLabel(settings)} ${workflowPresetCycleShortcutLabel()} ${workflowEntryShortcutLabel("standard")} ${workflowEntryShortcutLabel("mission")}\nWidget toggles and preset cycling are visible only while Plan/Mission/Standard Mode is active.\n\n${common}`;
9807
10090
  };
9808
10091
 
9809
10092
  const renderEditorHintsSettings = (ctx: ExtensionContext): string => {
@@ -10355,6 +10638,29 @@ ${reportExcerpt(validation, 2400)}
10355
10638
  : kind === "run" ? "Run /mission resume or /mission continue."
10356
10639
  : kind === "planning" ? "Run /mission resume or rerun /mission plan."
10357
10640
  : "Run /mission review, /mission approve, or /mission resume.";
10641
+ if (kind === "run") {
10642
+ const activeIndex = mission.currentMilestoneIndex ?? 0;
10643
+ const runningMilestones = mission.milestones.map((milestone, index) => index === activeIndex ? { ...milestone, status: "active" as const } : milestone);
10644
+ const runNextAction = "Wait for mission_milestone_result from the active executor, or run /mission continue to re-arm execution if no result arrives.";
10645
+ const running = saveActiveMission({
10646
+ ...mission,
10647
+ status: "running",
10648
+ milestones: runningMilestones,
10649
+ currentValidationRetry: retry,
10650
+ missionValidationRetryCount: missionRetry,
10651
+ lastRepairStatus: repairStatus,
10652
+ lastStopReason: retryableInterruptionText(reason),
10653
+ lastBlockReason: "",
10654
+ nextAction: runNextAction,
10655
+ lastSummary: "Mission run handoff interrupted by a transient failure; execution boundary preserved for typed milestone handoff.",
10656
+ });
10657
+ checkpointMission(running, `Mission ${kind} interrupted by transient handoff failure; execution boundary preserved. ${compact(reason, 500)}`, runNextAction, running.milestones[activeIndex]?.id, { validationResult: running.lastValidationResult });
10658
+ updateState({ mode: "mission_running", activeMissionId: running.id, task: running.goal, originalTask: running.goal, approvedPlan: running.planText }, ctx);
10659
+ activeMission = running;
10660
+ armWorkflowToolsForPhase(ctx, "Execution", "recover mission run transient handoff failure");
10661
+ show(pi, `# Mission Workflow Interrupted\n\nA transient connection or model handoff failure interrupted Mission execution handoff. This is not a semantic mission block; the active milestone execution boundary was preserved so a valid mission_milestone_result can still be accepted.\n\nPhase: ${workflowDisplayText(kind)}\nReason: ${workflowDisplayText(reason)}\n\nNext: ${workflowDisplayText(runNextAction)}.`);
10662
+ return;
10663
+ }
10358
10664
  const paused = saveActiveMission({
10359
10665
  ...mission,
10360
10666
  status: "paused",
@@ -10385,7 +10691,7 @@ ${reportExcerpt(validation, 2400)}
10385
10691
  initialDelayMs: 2000,
10386
10692
  requireIdle: true,
10387
10693
  isIdle: () => ctx.isIdle(),
10388
- onBeforeSend: (customType) => armWorkflowToolsForPhase(ctx, phase, `queued turn before send: ${customType}`),
10694
+ onBeforeSend: (customType) => armAndVerifyWorkflowToolsForPhase(ctx, phase, `queued turn before send: ${customType}`),
10389
10695
  });
10390
10696
 
10391
10697
  const buildQueuedPlanningRecovery = (ctx: ExtensionContext, recover: (reason: string) => void): WorkflowQueuedTurnOptions =>
@@ -10570,7 +10876,15 @@ ${renderMissionProgress(mission, settings)}
10570
10876
  return true;
10571
10877
  }
10572
10878
 
10573
- function resolveActiveMissionForResume(): { mission?: MissionState; candidates: MissionState[]; reason?: string } {
10879
+ function missionMatchesCurrentProject(ctx: ExtensionContext, mission?: MissionState): boolean {
10880
+ return Boolean(mission?.cwd && mission.cwd === ctx.cwd);
10881
+ }
10882
+
10883
+ function sortMissionResumeCandidates(a: MissionState, b: MissionState): number {
10884
+ return missionResumePriority(a.status) - missionResumePriority(b.status) || b.updatedAt.localeCompare(a.updatedAt);
10885
+ }
10886
+
10887
+ function resolveActiveMissionForResume(ctx: ExtensionContext): { mission?: MissionState; candidates: MissionState[]; reason?: string; fallbackProject?: boolean } {
10574
10888
  const byState = state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined;
10575
10889
  const loadedActive = activeMission && (!state.activeMissionId || activeMission.id === state.activeMissionId) ? activeMission : undefined;
10576
10890
  const latest = loadMissionState();
@@ -10584,18 +10898,24 @@ ${renderMissionProgress(mission, settings)}
10584
10898
  add(byState);
10585
10899
  [loadedActive, latest, ...all]
10586
10900
  .filter((mission): mission is MissionState => missionIsResumeCandidate(mission) && mission.id !== byState.id)
10587
- .sort((a, b) => missionResumePriority(a.status) - missionResumePriority(b.status) || b.updatedAt.localeCompare(a.updatedAt))
10901
+ .sort((a, b) => Number(missionMatchesCurrentProject(ctx, b)) - Number(missionMatchesCurrentProject(ctx, a)) || sortMissionResumeCandidates(a, b))
10588
10902
  .forEach(add);
10589
10903
  const inactiveReason = missionIsResumeCandidate(byState) ? undefined : `Current mission is ${(byState as MissionState).status}.`;
10590
10904
  return { mission: byState, candidates: ordered, reason: inactiveReason };
10591
10905
  }
10592
10906
 
10593
- [loadedActive, latest, ...all]
10594
- .filter(missionIsResumeCandidate)
10595
- .sort((a, b) => missionResumePriority(a.status) - missionResumePriority(b.status) || b.updatedAt.localeCompare(a.updatedAt))
10596
- .forEach(add);
10907
+ const currentProject = [loadedActive, latest, ...all]
10908
+ .filter((mission): mission is MissionState => missionIsResumeCandidate(mission) && missionMatchesCurrentProject(ctx, mission))
10909
+ .sort(sortMissionResumeCandidates);
10910
+ const otherProject = [loadedActive, latest, ...all]
10911
+ .filter((mission): mission is MissionState => missionIsResumeCandidate(mission) && !missionMatchesCurrentProject(ctx, mission))
10912
+ .sort(sortMissionResumeCandidates);
10597
10913
 
10598
- if (ordered.length > 0) return { mission: ordered[0], candidates: ordered };
10914
+ currentProject.forEach(add);
10915
+ otherProject.forEach(add);
10916
+
10917
+ if (currentProject.length > 0) return { mission: currentProject[0], candidates: ordered };
10918
+ if (otherProject.length > 0) return { mission: otherProject[0], candidates: ordered, fallbackProject: true, reason: `No resumable mission found for current project ${basename(ctx.cwd)}; showing saved missions from other projects.` };
10599
10919
 
10600
10920
  add(loadedActive);
10601
10921
  add(latest);
@@ -10608,7 +10928,7 @@ ${renderMissionProgress(mission, settings)}
10608
10928
  const done = mission.milestones.filter((m) => m.status === "completed" || m.status === "skipped").length;
10609
10929
  const percent = total ? Math.round((done / total) * 100) : 0;
10610
10930
  const milestone = total ? `M${Math.min(mission.currentMilestoneIndex + 1, total)}/${total}` : "M0/0";
10611
- return `${mission.id} | ${mission.status} | ${milestone} | ${percent}% | ${compact(mission.goal, 80).replace(/\n/g, " ")}`;
10931
+ return `${mission.id} | ${mission.projectLabel ?? mission.cwd ?? "unknown project"} | ${mission.status} | ${milestone} | ${percent}% | ${compact(mission.goal, 80).replace(/\n/g, " ")}`;
10612
10932
  }
10613
10933
 
10614
10934
  async function chooseMissionFromHistory(ctx: ExtensionContext, missions: MissionState[], title = "Choose a mission"): Promise<MissionState | undefined> {
@@ -10622,12 +10942,15 @@ ${renderMissionProgress(mission, settings)}
10622
10942
  return missions.find((mission) => mission.id === id);
10623
10943
  }
10624
10944
 
10625
- async function chooseResumeMission(ctx: ExtensionContext, resolved: { mission?: MissionState; candidates: MissionState[]; reason?: string }, action: "resume" | "continue" | "next" | "retry" | "repair" | "revalidate" = "resume"): Promise<MissionState | undefined> {
10626
- if (resolved.candidates.length <= 1 || !ctx.hasUI) return resolved.mission;
10945
+ async function chooseResumeMission(ctx: ExtensionContext, resolved: { mission?: MissionState; candidates: MissionState[]; reason?: string; fallbackProject?: boolean }, action: "resume" | "continue" | "next" | "retry" | "repair" | "revalidate" = "resume"): Promise<MissionState | undefined> {
10946
+ if (resolved.fallbackProject && !ctx.hasUI) return undefined;
10947
+ if (!resolved.fallbackProject && (resolved.candidates.length <= 1 || !ctx.hasUI)) return resolved.mission;
10627
10948
  const title = action === "resume" ? "Mission Resume" : `Mission ${action[0].toUpperCase()}${action.slice(1)}`;
10628
10949
  const verb = action === "resume" ? "use" : action;
10629
10950
  const choices = resolved.candidates.map(missionResumeChoiceLabel);
10630
- const intro = action === "resume"
10951
+ const intro = resolved.fallbackProject
10952
+ ? (resolved.reason ?? "Only saved missions from other projects were found. Choose explicitly before continuing.")
10953
+ : action === "resume"
10631
10954
  ? "Choose which mission to use."
10632
10955
  : `I found more than one mission that can ${action}. Choose which one to use.`;
10633
10956
  show(pi, `# ${title}\n\n${intro}\n\n${choices.map((choice, i) => `${i + 1}. ${choice}`).join("\n")}`);
@@ -10678,6 +11001,8 @@ ${renderMissionProgress(mission, settings)}
10678
11001
  if (!route) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION VALIDATION BLOCKED: validator model is disabled, unavailable, or not configured. Do not execute Mission work. Report that Mission validation must be re-entered after validator routing is fixed.` };
10679
11002
  pi.setActiveTools(validationToolsFor(settings));
10680
11003
  updateState({ mode, activeMissionId: mission.id, modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
11004
+ const surfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission active validation gate", mode);
11005
+ if (surfaceBlock) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION VALIDATION BLOCKED: ${surfaceBlock}\n\nDo not validate the mission. Run /mission revalidate after the tool surface is repaired.` };
10681
11006
  return { systemPrompt: `${systemPrompt}\n\n${missionValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation)}` };
10682
11007
  }
10683
11008
  if (mode === "mission_final_validating") {
@@ -10685,13 +11010,17 @@ ${renderMissionProgress(mission, settings)}
10685
11010
  if (!route) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION FINAL VALIDATION BLOCKED: validator model is disabled, unavailable, or not configured. Do not execute Mission work. Report that final validation must be re-entered after validator routing is fixed.` };
10686
11011
  pi.setActiveTools(validationToolsFor(settings));
10687
11012
  updateState({ mode, activeMissionId: mission.id, modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
11013
+ const surfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission active final validation gate", mode);
11014
+ if (surfaceBlock) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION FINAL VALIDATION BLOCKED: ${surfaceBlock}\n\nDo not validate the mission. Run /mission revalidate after the tool surface is repaired.` };
10688
11015
  return { systemPrompt: `${systemPrompt}\n\n${missionFinalValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation)}` };
10689
11016
  }
10690
11017
  if (mode === "mission_repairing") {
10691
11018
  const route = await applyMissionModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
10692
11019
  if (!route) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION REPAIR BLOCKED: executor model is disabled, unavailable, or not configured. Do not validate or continue Mission execution. Report that Mission repair must be re-entered after executor routing is fixed.` };
10693
- pi.setActiveTools(executionToolsFor(settings));
11020
+ pi.setActiveTools(missionRepairToolsFor(settings));
10694
11021
  updateState({ mode, activeMissionId: mission.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
11022
+ const surfaceBlock = verifyWorkflowToolSurface(ctx, "Repair", "mission active repair gate", mode);
11023
+ if (surfaceBlock) return { systemPrompt: `${systemPrompt}\n\nPI WORKFLOW MISSION REPAIR BLOCKED: ${surfaceBlock}\n\nDo not repair the mission. Run /mission repair or /mission resume after the tool surface is repaired.` };
10695
11024
  return { systemPrompt: `${systemPrompt}\n\n${missionRepairPrompt(mission, settings, phasePreflightBlocks.Repair)}` };
10696
11025
  }
10697
11026
  return undefined;
@@ -10873,9 +11202,9 @@ ${renderMissionProgress(mission, settings)}
10873
11202
  }
10874
11203
 
10875
11204
  async function handleMissionResume(ctx: ExtensionContext) {
10876
- const resolved = resolveActiveMissionForResume();
11205
+ const resolved = resolveActiveMissionForResume(ctx);
10877
11206
  let selected = resolved.mission;
10878
- const allMissions = listMissionStates();
11207
+ const allMissions = listMissionStates().sort((a, b) => Number(missionMatchesCurrentProject(ctx, b)) - Number(missionMatchesCurrentProject(ctx, a)) || b.updatedAt.localeCompare(a.updatedAt));
10879
11208
  if (!selected && allMissions.length > 0) selected = allMissions[0];
10880
11209
  if (!selected) return show(pi, `# Mission Resume
10881
11210
 
@@ -10905,6 +11234,7 @@ ${renderMissionStatus(mission)}`);
10905
11234
  return show(pi, `# ${selectedFromHistory ? "Selected Mission" : "Mission Resume"}
10906
11235
 
10907
11236
  Mission: ${mission.id}
11237
+ Project: ${mission.projectLabel ?? mission.cwd ?? "unknown project"}
10908
11238
  Status: ${missionPhaseFromStatus(mission.status)}
10909
11239
  Current Milestone: ${mission.milestones.length ? `M${Math.min(mission.currentMilestoneIndex + 1, mission.milestones.length)}/${mission.milestones.length}` : "M0/0"}
10910
11240
  Resume Available: ${resume.available ? "yes" : "no"}
@@ -10994,7 +11324,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10994
11324
  await beginMissionPlanning(ctx, activeMission);
10995
11325
  }
10996
11326
 
10997
- async function beginMissionPlanning(ctx: ExtensionContext, mission: MissionState, options: { forceClarification?: boolean; forceReason?: string; planningPreflightSatisfied?: boolean; reviewFeedback?: string } = {}) {
11327
+ async function beginMissionPlanning(ctx: ExtensionContext, mission: MissionState, options: { forceClarification?: boolean; forceAdditionalClarification?: boolean; forceReason?: string; planningPreflightSatisfied?: boolean; reviewFeedback?: string } = {}) {
10998
11328
  stopStartupVisual(ctx);
10999
11329
  const settings = loadWorkflowSettings(ctx.cwd);
11000
11330
  clearTypedHandoff(ctx, "Mission planning");
@@ -11009,7 +11339,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11009
11339
  approvedPlan: undefined,
11010
11340
  lastCompletedMissionSummary: undefined,
11011
11341
  }, ctx);
11012
- const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning" };
11342
+ const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning", task: mission.goal };
11013
11343
  if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
11014
11344
  const blocked = saveActiveMission({ ...mission, status: "blocked", lastBlockReason: "Mission planning blocked by forced sub-agent policy availability gate.", nextAction: "Fix mission planning sub-agent policy blocker, then run /mission resume or /mission plan.", lastSummary: "Mission planning blocked before planner handoff." });
11015
11345
  checkpointMission(blocked, "Mission planning blocked before planner handoff.", "Fix mission planning sub-agent policy blocker, then run /mission resume or /mission plan.");
@@ -11029,7 +11359,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11029
11359
  const pending = saveActiveMission({ ...mission, status: "planning", modelsUsed: { ...(mission.modelsUsed ?? {}), planner: modelLabel(route) }, lastSummary: "Mission planning requested from planner model.", nextAction: "Mission planner is active; wait for the milestone plan or stop the turn." });
11030
11360
  const hasAnswers = Boolean(pending.clarificationAnswers?.length);
11031
11361
  const answerSummary = hasAnswers ? formatAnswersForPlanner(pending.clarificationQuestions ?? [], pending.clarificationAnswers ?? []) : undefined;
11032
- const forceClarification = !hasAnswers && options.forceClarification === true;
11362
+ const forceClarification = options.forceAdditionalClarification === true
11363
+ ? options.forceClarification === true
11364
+ : !hasAnswers && options.forceClarification === true;
11033
11365
  updateState({
11034
11366
  mode: "mission_planning",
11035
11367
  activeMissionId: pending.id,
@@ -11043,7 +11375,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11043
11375
  }, ctx);
11044
11376
  queueWorkflowPrompt(
11045
11377
  pi,
11046
- missionPlanPrompt(pending, settings, { forceClarification, forceReason: forceClarification ? options.forceReason : undefined, answerSummary, reviewFeedback: options.reviewFeedback }),
11378
+ missionPlanPrompt(pending, settings, { forceClarification, forceAdditionalClarification: options.forceAdditionalClarification, forceReason: forceClarification ? options.forceReason : undefined, answerSummary, reviewFeedback: options.reviewFeedback }),
11047
11379
  buildQueuedFailureRecovery(ctx, (reason) => recoverMissionTransientHandoffFailure(ctx, "planning", reason)),
11048
11380
  );
11049
11381
  }
@@ -11363,7 +11695,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11363
11695
  lastSummary: `Running approved milestone ${milestone.id}.`,
11364
11696
  });
11365
11697
  checkpointMission(running, `Starting approved milestone ${milestone.id}: ${milestone.title}.`, "Execute current mission milestone, then run mission validation.", milestone.id);
11366
- pi.setActiveTools(executionToolsFor(settings));
11698
+ pi.setActiveTools(missionExecutionToolsFor(settings));
11367
11699
  updateState({ mode: "mission_running", activeMissionId: running.id, task: running.goal, originalTask: running.goal, approvedPlan: missionRunPlan(running), draftPlan: undefined, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, reviewerReport: undefined, modelsUsed: { ...(state.modelsUsed ?? {}), planner: running.modelsUsed?.planner }, missionTokensUsed: 0 }, ctx);
11368
11700
  if (!auto) show(pi, `# MISSION MODE ACTIVE\n\n# Mission Run Started\n\nMission ID: ${running.id}\nStatus: executing\nMilestone: ${milestone.id} — ${milestone.title}\nAutonomy: ${workflowDisplayValue(running.autonomy)}\n\n${renderMissionProgress(running, settings)}`);
11369
11701
  if (!beginForcedSubagentPhase(ctx, "Execution", settings)) {
@@ -11493,8 +11825,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11493
11825
 
11494
11826
  const syncSubagentPhaseUsage = (phase: SubagentPhase): void => {
11495
11827
  const evidence = successfulSubagentEvidenceByPhase[phase];
11828
+ const distinctNames = new Set(Array.from(evidence.values()));
11496
11829
  subagentUsageByPhase[phase] = evidence.size;
11497
- subagentNamesByPhase[phase] = new Set(Array.from(evidence.values()));
11830
+ subagentNamesByPhase[phase] = distinctNames;
11498
11831
  };
11499
11832
 
11500
11833
  const recordSuccessfulSubagentEvidence = (phase: SubagentPhase, observations: SubagentNameObservation[]): void => {
@@ -11521,6 +11854,17 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11521
11854
  return undefined;
11522
11855
  };
11523
11856
 
11857
+ const forcedSubagentGuardLabel = (phase: SubagentPhase): string => {
11858
+ if (state.mode === "standard") return `Standard ${phase}`;
11859
+ if (state.mode === "mission_planning" && phase === "Planning") return "Mission Planning";
11860
+ if (state.mode === "mission_running" && phase === "Execution") return "Mission Execution";
11861
+ if (state.mode === "mission_repairing" && phase === "Repair") return "Mission Repair";
11862
+ if (state.mode === "mission_plan_ready" && phase === "Review") return "Mission Review";
11863
+ if ((state.mode === "mission_validating" || state.mode === "mission_revalidating") && phase === "Validation") return "Mission Validation";
11864
+ if (state.mode === "mission_final_validating" && phase === "Validation") return "Mission Final Validation";
11865
+ return phase;
11866
+ };
11867
+
11524
11868
  const resetSubagentPhaseUsage = (phase: SubagentPhase) => {
11525
11869
  successfulSubagentEvidenceByPhase[phase].clear();
11526
11870
  syncSubagentPhaseUsage(phase);
@@ -11541,34 +11885,128 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11541
11885
  syncSubagentPhaseUsage(phase);
11542
11886
  };
11543
11887
 
11544
- const priorSubagentUsageSatisfiesForced = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number } }): boolean => {
11888
+ type SubagentPhasePolicyOverride = { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string; task?: string; kind?: WorkflowState["standardWorkKind"] };
11889
+
11890
+ const recordSubagentPolicyDecision = (ctx: ExtensionContext, decision: Omit<SubagentPolicyDecision, "createdAt">): SubagentPolicyDecision => {
11891
+ const fullDecision: SubagentPolicyDecision = { ...decision, createdAt: new Date().toISOString() };
11892
+ updateState({
11893
+ subagentPolicyDecisions: {
11894
+ ...(state.subagentPolicyDecisions ?? {}),
11895
+ [decision.phase]: fullDecision,
11896
+ },
11897
+ }, ctx);
11898
+ return fullDecision;
11899
+ };
11900
+
11901
+ const phasePolicyForCurrentMode = (settings: ReturnType<typeof loadWorkflowSettings>, phase: SubagentPhase): SubagentPolicyValue => {
11902
+ return state.mode === "standard" ? standardPhasePolicy(settings, phase) : phasePolicy(settings, phase);
11903
+ };
11904
+
11905
+ const workersForCurrentMode = (settings: ReturnType<typeof loadWorkflowSettings>, phase: SubagentPhase): { deep: number; maximum: number } => {
11906
+ return state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase);
11907
+ };
11908
+
11909
+ const forcedTrivialExemptionDecision = (ctx: ExtensionContext, phase: SubagentPhase, policy: SubagentPolicyValue, workers: { deep: number; maximum: number }, task?: string, kind?: WorkflowState["standardWorkKind"]): SubagentPolicyDecision | undefined => {
11910
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return undefined;
11911
+ const reason = trivialSubagentSkipReason(phase, task, kind);
11912
+ if (!reason) return undefined;
11913
+ return recordSubagentPolicyDecision(ctx, {
11914
+ phase,
11915
+ policy,
11916
+ outcome: "exempt_trivial",
11917
+ reason,
11918
+ task,
11919
+ required: workerTargetForPolicy(policy, workers),
11920
+ observed: 0,
11921
+ background: false,
11922
+ });
11923
+ };
11924
+
11925
+ const rememberAdvisorySubagentDecision = (ctx: ExtensionContext, phase: SubagentPhase, policy: SubagentPolicyValue, outcome: "auto_delegate" | "auto_skip_trivial" | "auto_skip_no_useful_parallel_work" | "unavailable", reason: string, task?: string, workers = workerCount(loadWorkflowSettings(ctx.cwd), phase)): void => {
11926
+ if (!subagentPolicyNeedsInternalDecision(policy)) return;
11927
+ recordSubagentPolicyDecision(ctx, {
11928
+ phase,
11929
+ policy,
11930
+ outcome,
11931
+ reason,
11932
+ task,
11933
+ required: workerTargetForPolicy(policy, workers),
11934
+ observed: subagentUsageByPhase[phase] ?? 0,
11935
+ background: false,
11936
+ });
11937
+ };
11938
+
11939
+ const advisoryWorkerTargetForPolicy = (policy: SubagentPolicyValue, workers: { deep: number; maximum: number }): number => {
11940
+ if (policy === "auto") return Math.max(1, workers.deep);
11941
+ return Math.max(1, workerTargetForPolicy(policy, workers));
11942
+ };
11943
+
11944
+ const advisorySubagentUnavailableReason = (ctx: ExtensionContext, phase: SubagentPhase, policy: SubagentPolicyValue, workers: { deep: number; maximum: number }, label: string): string | undefined => {
11945
+ if (!subagentPolicyNeedsInternalDecision(policy)) return undefined;
11946
+ const target = advisoryWorkerTargetForPolicy(policy, workers);
11947
+ if (target <= 0) return undefined;
11948
+ const settings = loadWorkflowSettings(ctx.cwd);
11949
+ if (settings.subagents.enabled === false) return "subagents.enabled=false";
11950
+ if (!phaseAutoUseAllowed(settings, phase)) return `subagents.autoUseDuring${phase}=false`;
11951
+ if (!phaseParallelAllowed(settings, phase)) return `subagents.allowParallel${phase}=false`;
11952
+ const candidates = chooseForcedSubagents(phase, Math.min(target, 8), label, listEffectiveAgents(ctx.cwd));
11953
+ if (candidates.length === 0) return `no suitable ${phase.toLowerCase()} sub-agent workers are available`;
11954
+ return undefined;
11955
+ };
11956
+
11957
+ const priorSubagentUsageSatisfiesForced = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }, settings: ReturnType<typeof loadWorkflowSettings>, override?: SubagentPhasePolicyOverride): boolean => {
11545
11958
  const policy = override?.policy ?? phasePolicy(settings, phase);
11546
- if (policy !== "forced") return false;
11547
- const required = workerTargetForPolicy("forced", override?.workers ?? workerCount(settings, phase));
11959
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return false;
11960
+ const required = workerTargetForPolicy(policy, override?.workers ?? workerCount(settings, phase));
11548
11961
  return snapshot.count >= required;
11549
11962
  };
11550
11963
 
11551
- const beginForcedSubagentPhase = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): boolean => {
11964
+ const beginForcedSubagentPhase = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, override?: SubagentPhasePolicyOverride): boolean => {
11552
11965
  resetSubagentPhaseUsage(phase);
11553
11966
  phasePreflightBlocks[phase] = undefined;
11554
- const reason = forcedSubagentUnavailableReason(settings, phase, ctx.cwd, override?.policy ?? phasePolicy(settings, phase), override?.workers ?? workerCount(settings, phase));
11967
+ const policy = override?.policy ?? phasePolicy(settings, phase);
11968
+ const workers = override?.workers ?? workerCount(settings, phase);
11969
+ const task = override?.task ?? state.task ?? state.originalTask;
11970
+ if (subagentPolicyNeedsInternalDecision(policy)) {
11971
+ const advisory = advisorySubagentPolicyDecision(phase, policy, task, override?.kind);
11972
+ rememberAdvisorySubagentDecision(ctx, phase, policy, advisory.outcome, advisory.reason, task, workers);
11973
+ }
11974
+ const exemption = forcedTrivialExemptionDecision(ctx, phase, policy, workers, task, override?.kind);
11975
+ if (exemption) {
11976
+ phasePreflightBlocks[phase] = formatSubagentPolicyDecision(exemption);
11977
+ return true;
11978
+ }
11979
+ const reason = forcedSubagentUnavailableReason(settings, phase, ctx.cwd, policy, workers);
11555
11980
  if (!reason) return true;
11981
+ if (subagentPolicyRequiresRequiredEvidence(policy)) {
11982
+ recordSubagentPolicyDecision(ctx, {
11983
+ phase,
11984
+ policy,
11985
+ outcome: "unavailable",
11986
+ reason,
11987
+ task,
11988
+ required: workerTargetForPolicy(policy, workers),
11989
+ observed: subagentUsageByPhase[phase] ?? 0,
11990
+ background: false,
11991
+ });
11992
+ }
11556
11993
  showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, override?.label)}`);
11557
11994
  return false;
11558
11995
  };
11559
11996
 
11560
- const forcedSubagentUsageSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): string | undefined => {
11997
+ const forcedSubagentUsageSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, override?: SubagentPhasePolicyOverride): string | undefined => {
11561
11998
  const settings = loadWorkflowSettings(ctx.cwd);
11562
11999
  const policy = override?.policy ?? phasePolicy(settings, phase);
11563
- if (policy !== "forced") return undefined;
11564
- const required = workerTargetForPolicy("forced", override?.workers ?? workerCount(settings, phase));
12000
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return undefined;
12001
+ if (forcedTrivialExemptionDecision(ctx, phase, policy, override?.workers ?? workerCount(settings, phase), override?.task ?? state.task ?? state.originalTask, override?.kind)) return undefined;
12002
+ const required = workerTargetForPolicy(policy, override?.workers ?? workerCount(settings, phase));
11565
12003
  const observed = subagentUsageByPhase[phase] ?? 0;
11566
12004
  if (observed >= required) return undefined;
11567
12005
  const label = override?.label ?? phase;
11568
12006
  return `${label} stopped because ${label} Policy is forced. Required ${label.toLowerCase()} sub-agent workers: ${required}. Observed: ${observed}. Validation or the next workflow phase will not start until the forced worker requirement is satisfied. Change this in Workflow Sub-agents / Workers, or rerun with the required workers.`;
11569
12007
  };
11570
12008
 
11571
- const blockIfForcedSubagentsMissing = (ctx: ExtensionContext, phase: SubagentPhase, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): boolean => {
12009
+ const blockIfForcedSubagentsMissing = (ctx: ExtensionContext, phase: SubagentPhase, override?: SubagentPhasePolicyOverride): boolean => {
11572
12010
  const reason = forcedSubagentUsageSatisfied(ctx, phase, override);
11573
12011
  if (!reason) return false;
11574
12012
  showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, override?.label)}`);
@@ -11646,19 +12084,36 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11646
12084
  return [`Phase: ${label}`, `Observed workers: ${succeeded.length}`, ...lines, ...(failed.length ? ["Failed workers:", ...failed.map((result) => `- ${result.agent} (${result.agentSource}): ${compact(workflowSubagentResultOutput(result), 500)}`)] : [])].join("\n");
11647
12085
  };
11648
12086
 
11649
- const runForcedSubagentPreflight = async (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, context: ForcedSubagentPreflightContext = {}, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): Promise<{ ok: boolean; block?: string }> => {
12087
+ const runForcedSubagentPreflight = async (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, context: ForcedSubagentPreflightContext = {}, override?: SubagentPhasePolicyOverride): Promise<{ ok: boolean; block?: string }> => {
11650
12088
  resetSubagentPhaseUsage(phase);
11651
12089
  phasePreflightBlocks[phase] = undefined;
11652
12090
  const policy = override?.policy ?? phasePolicy(settings, phase);
11653
12091
  const workers = override?.workers ?? workerCount(settings, phase);
11654
12092
  const label = override?.label ?? context.label ?? phase;
11655
- if (policy !== "forced") return { ok: true };
12093
+ const task = override?.task ?? context.task ?? state.task ?? state.originalTask;
12094
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return { ok: true };
12095
+ const exemption = forcedTrivialExemptionDecision(ctx, phase, policy, workers, task, override?.kind);
12096
+ if (exemption) {
12097
+ const block = formatSubagentPolicyDecision(exemption);
12098
+ phasePreflightBlocks[phase] = block;
12099
+ return { ok: true, block };
12100
+ }
11656
12101
  const reason = forcedSubagentUnavailableReason(settings, phase, ctx.cwd, policy, workers);
11657
12102
  if (reason) {
12103
+ recordSubagentPolicyDecision(ctx, {
12104
+ phase,
12105
+ policy,
12106
+ outcome: "unavailable",
12107
+ reason,
12108
+ task,
12109
+ required: workerTargetForPolicy(policy, workers),
12110
+ observed: subagentUsageByPhase[phase] ?? 0,
12111
+ background: false,
12112
+ });
11658
12113
  showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, label)}`);
11659
12114
  return { ok: false };
11660
12115
  }
11661
- const required = workerTargetForPolicy("forced", workers);
12116
+ const required = workerTargetForPolicy(policy, workers);
11662
12117
  const effectiveAgents = listEffectiveAgents(ctx.cwd);
11663
12118
  const agents = chooseForcedSubagents(phase, required, label, effectiveAgents);
11664
12119
  const names = agents.map((agent) => agent.name);
@@ -11678,7 +12133,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11678
12133
  return { ok: false };
11679
12134
  }
11680
12135
  }
11681
- const useBackground = settings.subagents.allowBackgroundSubagents === true && phase !== "Validation";
12136
+ const useBackground = settings.subagents.allowBackgroundSubagents === true && (phase === "Planning" || phase === "Review");
11682
12137
  const tasks: WorkflowSubagentTask[] = names.map((agent, index) => ({ agent, task: forcedSubagentTaskText(phase, agent, index, required, context), cwd: ctx.cwd, background: useBackground, workflowPhase: phase }));
11683
12138
  stopStartupVisual(ctx);
11684
12139
  const startedAt = new Date().toISOString();
@@ -11735,6 +12190,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11735
12190
  }
11736
12191
  if (isBackground) {
11737
12192
  // Background execution: agents run independently, phase proceeds immediately
12193
+ recordSubagentPolicyDecision(ctx, {
12194
+ phase,
12195
+ policy,
12196
+ outcome: "required",
12197
+ reason: "required workers launched as advisory background preflight",
12198
+ task,
12199
+ required,
12200
+ observed: 0,
12201
+ background: true,
12202
+ });
11738
12203
  return { ok: true };
11739
12204
  }
11740
12205
  const succeeded = run.results.filter((result) => result.exitCode === 0);
@@ -11750,6 +12215,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11750
12215
  showInternal(pi, `# Sub-Agent Policy Blocked\n\n${label} forced preflight did not satisfy required workers. Required: ${required}; succeeded: ${succeeded.length}.\n\n${block}`);
11751
12216
  return { ok: false, block };
11752
12217
  }
12218
+ recordSubagentPolicyDecision(ctx, {
12219
+ phase,
12220
+ policy,
12221
+ outcome: "required",
12222
+ reason: "required workers completed before phase continued",
12223
+ task,
12224
+ required,
12225
+ observed: succeeded.length,
12226
+ background: false,
12227
+ });
11753
12228
  return { ok: true, block };
11754
12229
  };
11755
12230
 
@@ -11757,12 +12232,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11757
12232
  policy: standardPhasePolicy(settings, phase),
11758
12233
  workers: standardWorkerCount(settings, phase),
11759
12234
  label: `Standard ${phase}`,
12235
+ task: state.task ?? state.originalTask ?? "Standard Mode task",
12236
+ kind: state.standardWorkKind,
11760
12237
  });
11761
12238
 
11762
12239
  const standardForcedSubagentSatisfied = (phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, task: string): boolean => {
11763
12240
  const override = standardForcedSubagentOverride(settings, phase);
11764
- if (override.policy !== "forced") return true;
11765
- const required = workerTargetForPolicy("forced", override.workers);
12241
+ if (!subagentPolicyRequiresRequiredEvidence(override.policy)) return true;
12242
+ if (trivialSubagentSkipReason(phase, task, override.kind)) return true;
12243
+ const required = workerTargetForPolicy(override.policy, override.workers);
11766
12244
  const record = state.standardSubagentPreflight?.[phase];
11767
12245
  if (record?.task === task && record.observed >= required) {
11768
12246
  restoreSuccessfulSubagentEvidence(phase, record.observed, new Set(record.agents), `standard:${phase}:${task}`);
@@ -11771,12 +12249,48 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11771
12249
  return false;
11772
12250
  };
11773
12251
 
12252
+ const forcedSubagentVisibleCallBlock = (ctx: ExtensionContext, phase: SubagentPhase, required: number, observed: number, input: unknown, label: string = phase): string | undefined => {
12253
+ const remaining = Math.max(0, required - observed);
12254
+ if (remaining <= 0) return undefined;
12255
+ const callShape = `Use one parallel tasks call shaped like subagent({ workflowPhase: "${phase.toLowerCase()}", tasks: [...] }). Worker count means task entries, not unique agent names; duplicate suitable roles are valid when useful, and distinct role names are not required. Choose phase-suitable workers by task fit. Do not add filler roles. Do not use subagent chain for forced multi-worker evidence.`;
12256
+ const countText = (requested: number, suffix = "") => {
12257
+ const shortBy = Math.max(0, remaining - requested);
12258
+ const shortage = shortBy > 0 ? `; this call is short by ${shortBy}` : "";
12259
+ return `subagent blocked — forced ${label} requires ${required} total worker task${required === 1 ? "" : "s"} before substantive ${phase.toLowerCase()} work; observed ${observed}, requested ${requested}${shortage}.${suffix}`;
12260
+ };
12261
+ if (!isRecord(input)) return countText(0, " The subagent request could not be inspected.");
12262
+ const requestedPhase = parseSubagentWorkflowPhase(input.workflowPhase);
12263
+ if (requestedPhase && requestedPhase !== phase) {
12264
+ return `subagent blocked — forced ${label} requires ${phase.toLowerCase()} workers, but the call requested workflowPhase=${String(input.workflowPhase)}.`;
12265
+ }
12266
+ const observations = extractSubagentObservations(input);
12267
+ const requested = observations.length;
12268
+ const distinctNames = Array.from(new Set(observations.map((observation) => observation.name).filter(Boolean)));
12269
+ if (remaining > 1 && !Array.isArray(input.tasks)) {
12270
+ const shape = Array.isArray(input.chain) && requested >= remaining
12271
+ ? `subagent blocked — forced ${label} requires ${required} total worker task${required === 1 ? "" : "s"} before substantive ${phase.toLowerCase()} work; observed ${observed}, requested ${requested} via chain. The requested count is enough, but chain is sequential and does not satisfy forced multi-worker evidence. ${callShape}`
12272
+ : `${countText(requested)} ${callShape}`;
12273
+ return shape;
12274
+ }
12275
+ if (requested < remaining) {
12276
+ return countText(requested, "");
12277
+ }
12278
+ const availableAgents = listEffectiveAgents(ctx.cwd);
12279
+ const suitableNames = new Set(availableAgents.filter((agent) => subagentSuitableForForcedPhase(forcedAgentProfile(agent), phase, label)).map((agent) => agent.name));
12280
+ const unsuitable = distinctNames.filter((name) => !suitableNames.has(name));
12281
+ if (unsuitable.length > 0) {
12282
+ return `subagent blocked — forced ${label} requested unsuitable or unavailable worker${unsuitable.length === 1 ? "" : "s"} for ${phase.toLowerCase()}: ${unsuitable.join(", ")}.`;
12283
+ }
12284
+ return undefined;
12285
+ };
12286
+
11774
12287
  const planForcedSubagentPreflightReconcile = (ctx: ExtensionContext, phase: SubagentPhase): void => {
11775
12288
  const block = phasePreflightBlocks[phase];
11776
12289
  if (!block) return;
11777
12290
  const settings = loadWorkflowSettings(ctx.cwd);
11778
12291
  const policy = phasePolicy(settings, phase);
11779
- if (policy !== "forced") return;
12292
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return;
12293
+ if (/^Policy decision:\s*exempt_trivial\b/im.test(block)) return;
11780
12294
  const observedMatch = block.match(/Observed workers: (\d+)/);
11781
12295
  if (!observedMatch) return;
11782
12296
  const observed = parseInt(observedMatch[1], 10);
@@ -11788,9 +12302,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
11788
12302
  };
11789
12303
 
11790
12304
  const planForcedSubagentPreflightSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>): boolean => {
11791
- if (phasePolicy(settings, phase) !== "forced") return false;
12305
+ const policy = phasePolicy(settings, phase);
12306
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return false;
12307
+ if (forcedTrivialExemptionDecision(ctx, phase, policy, workerCount(settings, phase), state.task ?? state.originalTask, state.standardWorkKind)) return true;
11792
12308
  planForcedSubagentPreflightReconcile(ctx, phase);
11793
- const required = workerTargetForPolicy("forced", workerCount(settings, phase));
12309
+ const required = workerTargetForPolicy(policy, workerCount(settings, phase));
11794
12310
  return (subagentUsageByPhase[phase] ?? 0) >= required;
11795
12311
  };
11796
12312
 
@@ -12055,6 +12571,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12055
12571
  standardPhaseByToolCall.delete(toolCallId);
12056
12572
  const phaseOverride = parseSubagentWorkflowPhase(args.workflowPhase);
12057
12573
  if (phaseOverride) standardPhaseByToolCall.set(toolCallId, phaseOverride);
12574
+ const phase = phaseOverride ?? phaseForWorkflowMode(state.mode);
12575
+ if (phase) {
12576
+ const settings = loadWorkflowSettings(ctx.cwd);
12577
+ const policy = phasePolicyForCurrentMode(settings, phase);
12578
+ rememberAdvisorySubagentDecision(ctx, phase, policy, "auto_delegate", "visible parent subagent call started", state.task ?? state.originalTask, workersForCurrentMode(settings, phase));
12579
+ }
12058
12580
  const observations = extractSubagentObservations(args, `top:${toolCallId}`);
12059
12581
  registerSubagentObservations(toolCallId, observations, ctx, observations.length === 0);
12060
12582
  if (observations.length === 0) renderWorkflowSubagentActivity(ctx);
@@ -12281,7 +12803,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12281
12803
  repairRetryState: reviewerRepair ? state.repairRetryState : undefined,
12282
12804
  planProgress: initialPlanProgress,
12283
12805
  }, ctx);
12284
- if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings)) {
12806
+ if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings, { task })) {
12285
12807
  pi.setActiveTools(planToolsFor(settings));
12286
12808
  updateState({ mode: "plan_draft", activePlanId, draftPlan: "Planning blocked before the planner could run: forced planning sub-agent requirements are unavailable.", approvedPlan: undefined, lastReviewFailure: "Forced planning sub-agent requirements are unavailable.", executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, reviewerReport: undefined, reviewerVerdict: undefined, currentReviewRetry: 0, workflowReviewRetryCount: 0, maxReviewRetriesPerPlan: undefined, maxReviewRetriesPerWorkflow: undefined, lastReviewAttempt: undefined, lastReviewRepairStatus: "none", reviewHistory: undefined, reviewRepairInProgress: undefined, repairRetryState: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, lastRepairStatus: "none", planStepValidationIndex: undefined, planExecutionStepIndex: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", draftPlan: undefined, approvedPlan: undefined }, settings, { lifecycleStatus: "blocked", nextAction: "fix planning sub-agent policy or revise planning", steps: [] }, undefined) : undefined, lastCompletedPlanSummary: undefined }, ctx);
12287
12809
  return;
@@ -12390,19 +12912,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12390
12912
  }
12391
12913
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
12392
12914
  armWorkflowToolsForPhase(ctx, "Execution", "beginExecution prompt queued");
12393
- let missingExecutionTools = missingRequiredPlanExecutionTools(pi.getActiveTools(), settings);
12394
- if (missingExecutionTools.length) {
12395
- pi.setActiveTools(executionToolsFor(settings));
12396
- missingExecutionTools = missingRequiredPlanExecutionTools(pi.getActiveTools(), settings);
12397
- traceWorkflowTracking(ctx, "defensive-tool-arm", { reason: "required execution tools missing before executor queue", missingExecutionTools });
12398
- }
12399
- if (missingExecutionTools.length) {
12400
- const reason = `Required execution tools are unavailable after execution tool rearm: ${missingExecutionTools.join(", ")}. Execution was not queued.`;
12401
- const revertMode = previousMode === "reviewed" ? "reviewed" as const : "plan_approved" as const;
12402
- updateState({ mode: revertMode, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: revertMode }, settings, { lifecycleStatus: revertMode === "reviewed" ? "reviewed" : "approved", nextAction: "repair execution tool surface" }, state.approvedPlan) : state.planProgress, lastReviewFailure: reason }, ctx);
12403
- pi.setActiveTools(webSafePlanTools(PLAN_TOOLS));
12404
- recordWorkflowInternalEvent(ctx, `Plan execution blocked because required execution tools were unavailable after rearm: ${missingExecutionTools.join(", ")}.`);
12405
- show(pi, `# Execution Blocked\n\n${reason}\n\nRun /plan continue after the execution tool surface is repaired.`);
12915
+ const executionSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "beginExecution prompt queued");
12916
+ if (executionSurfaceBlock) {
12406
12917
  return false;
12407
12918
  }
12408
12919
  queueAgentTurn(pi, executePrompt(executionState, settings, phasePreflightBlocks.Execution), auto ? "workflow-execute-trigger" : "workflow-execute-manual-trigger", buildQueuedPhaseRecovery(ctx, "Execution", (reason) => recoverPlanTransientHandoffFailure(ctx, "execution", reason, { phase: "Execution" })));
@@ -12484,6 +12995,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12484
12995
  return true;
12485
12996
  }
12486
12997
  const settings = loadWorkflowSettings(ctx.cwd);
12998
+ if (!planValidationBoundaryReached(state, settings)) {
12999
+ const reason = "Approved Plan execution is incomplete. Continue execution and complete the remaining Plan steps before validation.";
13000
+ updateState({
13001
+ mode: "reviewed",
13002
+ validationReport: undefined,
13003
+ validationVerdict: undefined,
13004
+ lastValidationFailure: undefined,
13005
+ lastRepairStatus: "none",
13006
+ lastRepairAttempt: undefined,
13007
+ planStepValidationIndex: undefined,
13008
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
13009
+ }, ctx);
13010
+ show(pi, `# Plan Validation Refused\n\n${reason}\n\nUse /plan continue.`);
13011
+ return false;
13012
+ }
12487
13013
  const route = await applyModelForRole(pi, ctx, "validator", { requireEnabled: false, cwd: ctx.cwd });
12488
13014
  if (!route) {
12489
13015
  const reason = "Plan validator model is disabled, unavailable, or not configured.";
@@ -12664,6 +13190,20 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12664
13190
  async function startWorkflowRepair(ctx: ExtensionContext, source: "auto" | "user" = "auto") {
12665
13191
  if (!state.approvedPlan) return show(pi, "# Plan Repair Refused\n\nNo approved plan exists.");
12666
13192
  const settings = loadWorkflowSettings(ctx.cwd);
13193
+ if (!planValidationBoundaryReached(state, settings)) {
13194
+ const reason = "Approved Plan execution is incomplete. Repair is only available after a valid validation failure.";
13195
+ updateState({
13196
+ mode: "reviewed",
13197
+ validationReport: undefined,
13198
+ validationVerdict: undefined,
13199
+ lastValidationFailure: undefined,
13200
+ lastRepairStatus: "none",
13201
+ lastRepairAttempt: undefined,
13202
+ planStepValidationIndex: undefined,
13203
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
13204
+ }, ctx);
13205
+ return show(pi, `# Plan Repair Refused\n\n${reason}\n\nUse /plan continue.`);
13206
+ }
12667
13207
  if (state.concreteRepairableIssue === false) {
12668
13208
  const reason = "No concrete repairable issue — manual verification only.";
12669
13209
  updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: state.lastValidationFailure || state.validationReport, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual verification or revalidate" }) : state.planProgress }, ctx);
@@ -12757,14 +13297,43 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12757
13297
  }
12758
13298
 
12759
13299
  async function handleWorkflowRetryCommand(ctx: ExtensionContext, action: "retry" | "repair" | "revalidate") {
13300
+ const settings = loadWorkflowSettings(ctx.cwd);
12760
13301
  if (action === "revalidate") {
12761
13302
  if (!state.approvedPlan && (state.mode === "idle" || state.mode === "cancelled" || state.mode === "awaiting_plan_input")) {
12762
13303
  const latest = latestRecoverablePlan();
12763
13304
  if (latest) recoverPlanFromHistory(ctx, latest);
12764
13305
  }
13306
+ if (state.approvedPlan && !planValidationBoundaryReached(state, settings)) {
13307
+ const reason = "Approved Plan execution is incomplete. Continue execution and complete the remaining Plan steps before validation.";
13308
+ updateState({
13309
+ mode: "reviewed",
13310
+ validationReport: undefined,
13311
+ validationVerdict: undefined,
13312
+ lastValidationFailure: undefined,
13313
+ lastRepairStatus: "none",
13314
+ lastRepairAttempt: undefined,
13315
+ planStepValidationIndex: undefined,
13316
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
13317
+ }, ctx);
13318
+ return show(pi, `# Plan Revalidate Refused\n\n${reason}\n\nUse /plan continue.`);
13319
+ }
12765
13320
  return beginValidation(ctx, true, true);
12766
13321
  }
12767
13322
  if (await handlePlanReviewRepairRecovery(ctx, action)) return;
13323
+ if (state.approvedPlan && !planValidationBoundaryReached(state, settings)) {
13324
+ const reason = "Approved Plan execution is incomplete. Repair is only available after a valid validation failure.";
13325
+ updateState({
13326
+ mode: "reviewed",
13327
+ validationReport: undefined,
13328
+ validationVerdict: undefined,
13329
+ lastValidationFailure: undefined,
13330
+ lastRepairStatus: "none",
13331
+ lastRepairAttempt: undefined,
13332
+ planStepValidationIndex: undefined,
13333
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }, state.approvedPlan) : state.planProgress,
13334
+ }, ctx);
13335
+ return show(pi, `# Plan ${action}\n\n${reason}\n\nUse /plan continue.`);
13336
+ }
12768
13337
  if (state.mode === "executing" || state.mode === "validating" || state.mode === "repairing" || state.mode === "revalidating") return show(pi, `# Plan ${action}\n\nCannot ${action}: workflow is currently ${state.mode}. Wait for the active gate first.`);
12769
13338
  const hasValidationFailure = Boolean(state.lastValidationFailure || state.validationReport || state.validationVerdict === "FAIL" || state.validationVerdict === "PARTIAL PASS");
12770
13339
  if (!hasValidationFailure) return show(pi, `# Plan ${action}\n\nCannot ${action}: no validation failure is recorded.`);
@@ -12978,6 +13547,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12978
13547
  async function resumeInterruptedPlanValidation(ctx: ExtensionContext, title: string): Promise<boolean> {
12979
13548
  const settings = loadWorkflowSettings(ctx.cwd);
12980
13549
  const revalidate = state.mode === "revalidating";
13550
+ if (!planValidationBoundaryReached(state, settings)) {
13551
+ show(pi, `# ${title}\n\nRecovered interrupted Plan validation runtime, but approved Plan execution is incomplete. Re-arming executor for the current incomplete step.`);
13552
+ updateState({
13553
+ mode: "reviewed",
13554
+ validationReport: undefined,
13555
+ validationVerdict: undefined,
13556
+ lastValidationFailure: undefined,
13557
+ lastRepairStatus: "none",
13558
+ lastRepairAttempt: undefined,
13559
+ planStepValidationIndex: undefined,
13560
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "executor" }, state.approvedPlan) : state.planProgress,
13561
+ }, ctx);
13562
+ await beginExecution(ctx, true);
13563
+ return true;
13564
+ }
12981
13565
  if (!planValidationGateActive(settings)) {
12982
13566
  show(pi, `# ${title}\n\nRecovered interrupted Plan validation runtime. Validation is disabled or unavailable for this workflow.`);
12983
13567
  await completePlanWithoutValidation(ctx, "Validation skipped: validation gate is disabled for this workflow.");
@@ -13035,6 +13619,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13035
13619
  restorePlanRuntimeSnapshot(ctx, snapshot);
13036
13620
  const settings = loadWorkflowSettings(ctx.cwd);
13037
13621
  const validationAvailable = planValidationGateActive(settings);
13622
+ if ((state.mode === "executed" || state.mode === "validated" || state.mode === "validating" || state.mode === "revalidating") && planExecutionIncomplete(state, settings)) {
13623
+ show(pi, `# ${title}\n\nRecovered Plan runtime from session history, but approved Plan execution is incomplete. Continuing execution from the current incomplete step.`);
13624
+ updateState({
13625
+ mode: "reviewed",
13626
+ validationReport: undefined,
13627
+ validationVerdict: undefined,
13628
+ lastValidationFailure: undefined,
13629
+ lastRepairStatus: "none",
13630
+ lastRepairAttempt: undefined,
13631
+ planStepValidationIndex: undefined,
13632
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "executor" }, state.approvedPlan) : state.planProgress,
13633
+ }, ctx);
13634
+ await beginExecution(ctx, true);
13635
+ return true;
13636
+ }
13038
13637
  if (state.mode === "executed" && validationAvailable) {
13039
13638
  show(pi, `# ${title}\n\nRecovered executed Plan runtime from session history. Continuing to validation.`);
13040
13639
  await beginValidation(ctx, true, false, "immediate");
@@ -13113,7 +13712,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13113
13712
  }, ctx);
13114
13713
  }
13115
13714
 
13116
- function planRecoveryGuidance(current: WorkflowState, validationAvailable: boolean, activeRuntimeStale = false): string {
13715
+ function planRecoveryGuidance(current: WorkflowState, validationAvailable: boolean, activeRuntimeStale = false, settings = loadWorkflowSettings()): string {
13716
+ if ((current.mode === "executed" || current.mode === "validated" || current.mode === "validating" || current.mode === "revalidating") && planExecutionIncomplete(current, settings)) {
13717
+ return "Approved Plan execution is incomplete. Next action: /plan continue to complete the remaining steps before validation or repair.";
13718
+ }
13117
13719
  if (current.mode === "idle" || current.mode === "cancelled" || current.mode === "awaiting_plan_input") {
13118
13720
  const latest = latestRecoverablePlan();
13119
13721
  if (latest) return `No active Plan Mode workflow is loaded. Recoverable approved plan found.\n\n${recoverablePlanDetails(latest)}\n\nNext action: /plan resume to restore it, /plan continue to run it, or /p <task> to start a new plan.`;
@@ -13136,7 +13738,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13136
13738
  return `No safe automatic continuation is available for state: ${current.mode}.`;
13137
13739
  }
13138
13740
 
13139
- function resolveActivePlanForResume(): { plan?: SavedWorkflowPlan; candidates: SavedWorkflowPlan[]; source: "active" | "history" | "none"; reason?: string } {
13741
+ function planMatchesCurrentProject(ctx: ExtensionContext, plan?: SavedWorkflowPlan): boolean {
13742
+ return Boolean(plan?.projectPath && plan.projectPath === ctx.cwd);
13743
+ }
13744
+
13745
+ function resolveActivePlanForResume(ctx: ExtensionContext): { plan?: SavedWorkflowPlan; candidates: SavedWorkflowPlan[]; source: "active" | "history" | "fallback" | "none"; reason?: string } {
13140
13746
  const candidates = listWorkflowPlans()
13141
13747
  .filter((plan) => plan.finalPlan?.trim() && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised"))
13142
13748
  .sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
@@ -13145,10 +13751,17 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13145
13751
  const add = (plan?: SavedWorkflowPlan) => {
13146
13752
  if (plan?.finalPlan?.trim() && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised") && !ordered.some((p) => p.id === plan.id)) ordered.push(plan);
13147
13753
  };
13148
- add(latest);
13149
- candidates.forEach(add);
13754
+ const currentProject = [latest, ...candidates]
13755
+ .filter((plan): plan is SavedWorkflowPlan => Boolean(plan?.finalPlan?.trim()) && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised") && planMatchesCurrentProject(ctx, plan))
13756
+ .sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
13757
+ const otherProject = [latest, ...candidates]
13758
+ .filter((plan): plan is SavedWorkflowPlan => Boolean(plan?.finalPlan?.trim()) && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised") && !planMatchesCurrentProject(ctx, plan))
13759
+ .sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
13760
+ currentProject.forEach(add);
13761
+ otherProject.forEach(add);
13150
13762
  if (state.approvedPlan?.trim() && !isMissionWorkflowMode(state)) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
13151
- if (ordered.length > 0) return { plan: ordered[0], candidates: ordered, source: "history" };
13763
+ if (currentProject.length > 0) return { plan: currentProject[0], candidates: ordered, source: "history" };
13764
+ if (otherProject.length > 0) return { plan: otherProject[0], candidates: ordered, source: "fallback", reason: `No recoverable Plan Mode workflow found for current project ${basename(ctx.cwd)}; showing saved plans from other projects.` };
13152
13765
  return { candidates: [], source: "none", reason: "No active or recoverable approved Plan Mode workflow found." };
13153
13766
  }
13154
13767
 
@@ -13202,7 +13815,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13202
13815
  }
13203
13816
 
13204
13817
  function planResumeChoiceLabel(plan: SavedWorkflowPlan): string {
13205
- return `${plan.id} | ${plan.approvalStatus} | ${plan.planningMode} | ${compact(plan.originalTask || "Saved plan", 80).replace(/\n/g, " ")}`;
13818
+ return `${plan.id} | ${plan.projectLabel ?? plan.projectPath ?? "unknown project"} | ${plan.approvalStatus} | ${plan.planningMode} | ${compact(plan.originalTask || "Saved plan", 80).replace(/\n/g, " ")}`;
13206
13819
  }
13207
13820
 
13208
13821
  function planResumeCanContinueCurrent(current: WorkflowState): boolean {
@@ -13230,12 +13843,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13230
13843
  return selectable.find((plan) => plan.id === id);
13231
13844
  }
13232
13845
 
13233
- async function choosePlanForContinueWhenNoCurrentPlan(ctx: ExtensionContext, resolved: { plan?: SavedWorkflowPlan; candidates: SavedWorkflowPlan[]; source: "active" | "history" | "none"; reason?: string }, action: "resume" | "continue" = "resume"): Promise<SavedWorkflowPlan | undefined> {
13846
+ async function choosePlanForContinueWhenNoCurrentPlan(ctx: ExtensionContext, resolved: { plan?: SavedWorkflowPlan; candidates: SavedWorkflowPlan[]; source: "active" | "history" | "fallback" | "none"; reason?: string }, action: "resume" | "continue" = "resume"): Promise<SavedWorkflowPlan | undefined> {
13234
13847
  if (resolved.source === "active") return undefined;
13235
- if (resolved.candidates.length <= 1 || !ctx.hasUI) return resolved.plan;
13848
+ if (resolved.source === "fallback" && !ctx.hasUI) return undefined;
13849
+ if (resolved.source !== "fallback" && (resolved.candidates.length <= 1 || !ctx.hasUI)) return resolved.plan;
13236
13850
  const title = action === "resume" ? "Plan Resume" : "Plan Continue";
13237
13851
  const choices = resolved.candidates.map(planResumeChoiceLabel);
13238
- const intro = action === "resume"
13852
+ const intro = resolved.source === "fallback"
13853
+ ? (resolved.reason ?? "Only saved plans from other projects were found. Choose explicitly before continuing.")
13854
+ : action === "resume"
13239
13855
  ? "Choose which plan to use."
13240
13856
  : "I found more than one saved plan. Choose which one to continue.";
13241
13857
  show(pi, `# ${title}\n\n${intro}\n\n${choices.map((choice, i) => `${i + 1}. ${choice}`).join("\n")}`);
@@ -13250,10 +13866,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13250
13866
  const validationAvailable = planValidationGateActive(settings);
13251
13867
  const currentAdvancedSnapshot = findAdvancedPlanRuntimeSnapshotForCurrentState(ctx);
13252
13868
  if (currentAdvancedSnapshot) restorePlanRuntimeSnapshotForResume(ctx, currentAdvancedSnapshot);
13253
- const resolved = resolveActivePlanForResume();
13254
- const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim());
13869
+ const resolved = resolveActivePlanForResume(ctx);
13870
+ const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim()).sort((a, b) => Number(planMatchesCurrentProject(ctx, b)) - Number(planMatchesCurrentProject(ctx, a)) || (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
13255
13871
  const activeRuntimeStale = planActiveRuntimeIsRecoverablyStale(ctx);
13256
- if (resolved.source === "none" && allPlans.length === 0) return show(pi, `# Plan Resume\n\n${resolved.reason ?? planRecoveryGuidance(state, validationAvailable, activeRuntimeStale)}\n\nUse /workflow plans list to see saved plans.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
13872
+ if (resolved.source === "none" && allPlans.length === 0) return show(pi, `# Plan Resume\n\n${resolved.reason ?? planRecoveryGuidance(state, validationAvailable, activeRuntimeStale, settings)}\n\nUse /workflow plans list to see saved plans.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
13257
13873
  let selected = resolved.plan;
13258
13874
  if (resolved.source === "history" && selected) {
13259
13875
  const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, selected);
@@ -13273,10 +13889,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13273
13889
  // Secondary surface for a saved plan chosen through "Choose Another Plan".
13274
13890
  const showSelectedSavedPlanResumeMenu = async (plan: SavedWorkflowPlan): Promise<void> => {
13275
13891
  const canContinue = plan.approvalStatus === "approved" || plan.approvalStatus === "revised";
13892
+ const canRevalidatePlan = canContinue && planValidationBoundaryReached({
13893
+ ...state,
13894
+ approvedPlan: plan.finalPlan,
13895
+ planProgress: plan.planProgress,
13896
+ planStepValidationIndex: plan.planStepValidationIndex,
13897
+ }, settings);
13276
13898
  const summary = `# Selected Plan\n\n${recoverablePlanDetails(plan)}\n\nStatus: ${plan.approvalStatus}`;
13277
13899
  show(pi, `${summary}\n\nChoose what to do.`);
13278
13900
  if (!ctx.hasUI) return;
13279
- const choices = [...(canContinue ? ["Continue Plan", "Revalidate Plan"] : []), "Reuse Plan", "Amend Plan", "List Plan Status", "List Plans", "Cancel"];
13901
+ const choices = [...(canContinue ? ["Continue Plan"] : []), ...(canRevalidatePlan ? ["Revalidate Plan"] : []), "Reuse Plan", "Amend Plan", "List Plan Status", "List Plans", "Cancel"];
13280
13902
  const choice = await ctx.ui.select("Selected Plan", choices);
13281
13903
  if (choice === "Continue Plan") {
13282
13904
  const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, plan);
@@ -13299,17 +13921,18 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13299
13921
  // Primary /plan resume surface: restore state, then let the user choose.
13300
13922
  const showActivePlanResumeActionMenu = async (currentPlan?: SavedWorkflowPlan): Promise<void> => {
13301
13923
  const details = currentPlan ? `\n\n${recoverablePlanDetails(currentPlan)}` : "";
13302
- const summary = `# Plan Resume\n\n${currentPlan ? "Plan loaded from saved history." : state.approvedPlan ? "Current plan is loaded." : "No current plan is loaded."}${details}\n\n${planRecoveryGuidance(state, validationAvailable, planActiveRuntimeIsRecoverablyStale(ctx))}\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`;
13924
+ const summary = `# Plan Resume\n\n${currentPlan ? state.approvedPlan ? "Plan loaded from saved history." : "Saved plan is available." : state.approvedPlan ? "Current plan is loaded." : "No current plan is loaded."}${details}\n\n${resolved.source === "fallback" ? `${resolved.reason}\n\n` : ""}${planRecoveryGuidance(state, validationAvailable, planActiveRuntimeIsRecoverablyStale(ctx), settings)}\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`;
13303
13925
  show(pi, `${summary}\n\nChoose what to do.`);
13304
13926
  if (!ctx.hasUI) return;
13305
13927
  const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
13306
13928
  const canContinueCurrent = planResumeCanContinueCurrent(state);
13307
13929
  const reviewRepairAvailable = planReviewRepairRecoveryAvailable(state);
13308
- const validationRepairAvailable = Boolean(state.lastValidationFailure || state.validationReport || state.validationVerdict === "FAIL" || state.validationVerdict === "PARTIAL PASS");
13930
+ const validationBoundaryReached = planValidationBoundaryReached(state, settings);
13931
+ const validationRepairAvailable = validationBoundaryReached && Boolean(state.lastValidationFailure || state.validationReport || state.validationVerdict === "FAIL" || state.validationVerdict === "PARTIAL PASS");
13309
13932
  const choice = await ctx.ui.select("Plan Resume", [
13310
13933
  ...(reviewRepairAvailable ? ["Review Repair / Recover Notes"] : []),
13311
13934
  ...(state.approvedPlan && validationRepairAvailable ? ["Repair / Retry"] : []),
13312
- ...(state.approvedPlan ? ["Revalidate"] : []),
13935
+ ...(state.approvedPlan && validationBoundaryReached ? ["Revalidate"] : []),
13313
13936
  ...(canContinueCurrent ? ["Continue Current Plan"] : []),
13314
13937
  ...(hasAlternatives ? ["Choose Another Plan"] : []),
13315
13938
  "List Plan Status", "List Plans", "Cancel",
@@ -13368,7 +13991,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13368
13991
  const status = renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd);
13369
13992
 
13370
13993
  if (!state.approvedPlan && (state.mode === "idle" || state.mode === "cancelled" || state.mode === "awaiting_plan_input")) {
13371
- const resolved = resolveActivePlanForResume();
13994
+ const resolved = resolveActivePlanForResume(ctx);
13372
13995
  const selected = await choosePlanForContinueWhenNoCurrentPlan(ctx, resolved, "continue");
13373
13996
  if (!selected) return show(pi, `# ${title}\n\n${resolved.reason ?? "No plan selected. Continue cancelled."}\n\nUse /workflow plans list to see saved plans.\n\n${status}`);
13374
13997
  const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, selected);
@@ -13439,6 +14062,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13439
14062
  await beginExecution(ctx, true);
13440
14063
  return;
13441
14064
  }
14065
+ if ((state.mode === "executed" || state.mode === "validated") && planExecutionIncomplete(state, settings)) {
14066
+ show(pi, `# ${title}\n\nApproved Plan execution is incomplete. Continuing execution from the current incomplete step.`);
14067
+ updateState({
14068
+ mode: "reviewed",
14069
+ validationReport: undefined,
14070
+ validationVerdict: undefined,
14071
+ lastValidationFailure: undefined,
14072
+ lastRepairStatus: "none",
14073
+ lastRepairAttempt: undefined,
14074
+ planStepValidationIndex: undefined,
14075
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "executor" }, state.approvedPlan) : state.planProgress,
14076
+ }, ctx);
14077
+ await beginExecution(ctx, true);
14078
+ return;
14079
+ }
13442
14080
  if (state.mode === "executed") {
13443
14081
  if (validationAvailable) {
13444
14082
  show(pi, `# ${title}\n\nExecution is complete. Continuing to validation.`);
@@ -13615,7 +14253,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13615
14253
  const settings = loadWorkflowSettings(ctx.cwd);
13616
14254
  clearTypedHandoff(ctx, "Mission final repair");
13617
14255
  const failure = mission.lastFinalValidationFailure || mission.lastValidationFailure || mission.lastBlockReason || "No final validation failure details recorded.";
13618
- const unsafe = validationFailureRequiresApproval(failure, settings);
14256
+ const unsafe = validationFailureRequiresApproval(failure, settings, {
14257
+ concreteRepairableIssue: mission.concreteRepairableIssue,
14258
+ evidenceGap: mission.evidenceGap,
14259
+ });
13619
14260
  if (unsafe) {
13620
14261
  const blocked = saveActiveMission({ ...mission, status: "blocked", lastRepairStatus: "blocked", lastBlockReason: `Final repair requires approval: ${unsafe}`, nextAction: "Inspect final validation failure or approve broader repair before retrying.", lastSummary: `Mission final repair blocked: ${unsafe}` });
13621
14262
  checkpointMission(blocked, `Mission final repair blocked before attempt. Reason: ${unsafe}`, "Inspect final validation failure or approve broader repair before retrying.", undefined, { validationResult: blocked.lastFinalValidationResult ?? blocked.lastValidationResult });
@@ -13654,7 +14295,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13654
14295
  repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair final validation failure, then revalidate." }),
13655
14296
  });
13656
14297
  checkpointMission(repairing, `Final validation repair started. Retry ${retry}/${maxRetries}. Failure: ${compact(failure, 500)}`, "Repair final validation failure, then revalidate.", undefined, { validationResult: repairing.lastFinalValidationResult ?? repairing.lastValidationResult });
13657
- pi.setActiveTools(executionToolsFor(settings));
14298
+ pi.setActiveTools(missionRepairToolsFor(settings));
13658
14299
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
13659
14300
  saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
13660
14301
  if (source !== "auto") show(pi, `# Mission Final Repair Started\n\nRetry: ${retry} of ${maxRetries}\n\n${renderMissionProgress(repairing, settings)}`);
@@ -13679,7 +14320,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
13679
14320
  return show(pi, `# Mission Runtime Budget Reached\n\n${runtimeBlocked}\n\n${renderMissionStatus(activeMission ?? paused)}`);
13680
14321
  }
13681
14322
  const failure = mission.lastValidationFailure || mission.lastBlockReason || "No validation failure details recorded.";
13682
- const unsafe = validationFailureRequiresApproval(failure, settings);
14323
+ const unsafe = validationFailureRequiresApproval(failure, settings, {
14324
+ concreteRepairableIssue: mission.concreteRepairableIssue,
14325
+ evidenceGap: mission.evidenceGap,
14326
+ });
13683
14327
  if (unsafe) {
13684
14328
  const milestone = mission.milestones[mission.currentMilestoneIndex];
13685
14329
  const blocked = saveActiveMission({ ...mission, status: "blocked", lastRepairStatus: "blocked", lastBlockReason: `Repair requires approval: ${unsafe}`, nextAction: "Inspect validation failure or approve broader repair before retrying.", lastSummary: `Mission repair blocked: ${unsafe}` });
@@ -13744,7 +14388,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13744
14388
  repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), milestoneId: milestone?.id, retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair current validation failure, then revalidate." }),
13745
14389
  });
13746
14390
  checkpointMission(repairing, `${manualRetryOverride ? "Manual repair retry override" : "Repair started"} for ${milestone?.id ?? "current milestone"}. Retry ${retry}/${maxRetries} per milestone; mission retry ${missionRetry}/${maxMissionRetries} total. Failure: ${compact(failure, 500)}`, "Repair current validation failure, then revalidate.", milestone?.id, { validationResult: repairing.lastValidationResult });
13747
- pi.setActiveTools(executionToolsFor(settings));
14391
+ pi.setActiveTools(missionRepairToolsFor(settings));
13748
14392
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
13749
14393
  saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
13750
14394
  if (source !== "auto") show(pi, `# Mission Repair Started\n\nRetry: ${retry} of ${maxRetries} per milestone\nMission Retry: ${missionRetry} of ${maxMissionRetries} total\nMilestone: ${milestone?.id ?? "current"} — ${milestone?.title ?? "unknown"}\n\n${renderMissionProgress(repairing, settings)}`);
@@ -13842,7 +14486,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13842
14486
  const retry = missionFinalValidationRetryCount(mission);
13843
14487
  const maxRetries = missionMaxFinalValidationRetries(mission, settings);
13844
14488
  const failure = `Final mission validation ${verdict}. ${compact(validationText, 1200)}`;
13845
- const unsafe = validationFailureRequiresApproval(failure, settings);
14489
+ const unsafe = validationFailureRequiresApproval(failure, settings, {
14490
+ concreteRepairableIssue: mission.concreteRepairableIssue,
14491
+ evidenceGap: mission.evidenceGap,
14492
+ });
13846
14493
  const nextRetry = retry + 1;
13847
14494
  const failed = saveActiveMission({ ...mission, status: "validating", finalValidationRetryCount: nextRetry, maxFinalValidationRetries: maxRetries, lastFinalValidationResult: verdict ?? "UNKNOWN", lastFinalValidationFailure: failure, lastValidationFailure: failure, lastBlockReason: "", nextAction: "Final mission validation failed; evaluating safe repair retry.", lastSummary: `Final mission validation ${verdict}.` });
13848
14495
  checkpointMission(failed, `Final mission validation failed. Retry ${retry}/${maxRetries}. ${compact(validationText, 500)}`, "Evaluate safe final repair retry before completion.", undefined, { validationResult: verdict });
@@ -13925,7 +14572,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13925
14572
  lastSummary: `Validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`,
13926
14573
  });
13927
14574
  checkpointMission(failed, `Validation failed for ${milestone?.id ?? "current milestone"}. Retry ${retry}/${maxRetries} per milestone; mission retry ${missionRetry}/${maxMissionRetries} total. ${compact(validationText, 500)}`, "Evaluate safe repair retry before blocking or advancing.", milestone?.id, { validationResult: verdict });
13928
- const unsafe = validationFailureRequiresApproval(failure, settings);
14575
+ const unsafe = validationFailureRequiresApproval(failure, settings, {
14576
+ concreteRepairableIssue: mission.concreteRepairableIssue,
14577
+ evidenceGap: mission.evidenceGap,
14578
+ });
13929
14579
  const shouldBlock = mission.autonomy === "manual"
13930
14580
  || settings.missions.autoRepairValidationFailures === false
13931
14581
  || settings.missions.pauseAfterValidationFailure === true
@@ -14001,7 +14651,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
14001
14651
  const answerSummary = formatAnswersForPlanner(questions, answers);
14002
14652
  const settings = loadWorkflowSettings(ctx.cwd);
14003
14653
  const priorPlanning = snapshotSubagentPhaseUsage("Planning");
14004
- if (!beginForcedSubagentPhase(ctx, "Planning", settings)) {
14654
+ if (!beginForcedSubagentPhase(ctx, "Planning", settings, { task: state.task ?? "Plan with clarification answers" })) {
14005
14655
  updateState({ mode: "planning", clarifyingAnswers: answers, draftPlan: state.draftPlan, approvedPlan: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "planning", draftPlan: state.draftPlan, approvedPlan: undefined }, settings, { lifecycleStatus: "blocked", nextAction: "fix forced planning sub-agent policy or rerun planning", steps: [] }, undefined) : state.planProgress }, ctx);
14006
14656
  return;
14007
14657
  }
@@ -14074,7 +14724,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
14074
14724
  }
14075
14725
  const questions = mission.clarificationQuestions?.length ? mission.clarificationQuestions : (state.clarifyingQuestions ?? []);
14076
14726
  const settings = loadWorkflowSettings(ctx.cwd);
14077
- const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning" };
14727
+ const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning", task: mission.goal };
14078
14728
  const priorPlanning = snapshotSubagentPhaseUsage("Planning");
14079
14729
  if (!beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
14080
14730
  const blocked = saveActiveMission({ ...mission, status: "blocked", clarificationQuestions: questions, clarificationAnswers: answers, lastBlockReason: "Mission clarification resume blocked by forced Mission Planning sub-agent policy.", nextAction: "Fix Mission Planning sub-agent workers, then run /mission resume or /mission plan.", lastSummary: "Mission clarification answers recorded, but forced Mission Planning availability gate did not pass." });
@@ -14181,15 +14831,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
14181
14831
  } else if (choice === "Revise Mission Plan") {
14182
14832
  await beginMissionPlanning(ctx, { ...mission, clarificationAnswers: mission.clarificationAnswers ?? [] });
14183
14833
  } else if (choice === "Answer More Clarifications") {
14184
- if (mission.clarificationQuestions?.length) {
14185
- updateState({ mode: "mission_awaiting_clarification", activeMissionId: mission.id, task: mission.goal, originalTask: mission.goal, clarifyingQuestions: mission.clarificationQuestions, clarifyingAnswers: mission.clarificationAnswers }, ctx);
14186
- show(pi, `# Mission Clarification\n\n${renderClarificationQuestionsForUser(mission.clarificationQuestions)}`);
14187
- } else {
14188
- const next = saveActiveMission({ ...mission, status: "draft", lastSummary: "Additional mission clarification requested before approval.", nextAction: "Generate dynamic mission clarification questions before approval." });
14189
- checkpointMission(next, "Additional mission clarification requested before approval.", "Generate dynamic mission-specific clarification questions.");
14190
- recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
14191
- await beginMissionPlanning(ctx, next, { forceClarification: true, forceReason: "user requested additional mission clarification before approval" });
14192
- }
14834
+ const next = saveActiveMission({ ...mission, status: "draft", lastSummary: "Additional mission clarification requested before approval.", nextAction: "Generate dynamic mission clarification questions before approval." });
14835
+ checkpointMission(next, "Additional mission clarification requested before approval.", "Generate dynamic mission-specific clarification questions.");
14836
+ recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
14837
+ await beginMissionPlanning(ctx, next, { forceClarification: true, forceAdditionalClarification: true, forceReason: "user requested additional mission clarification before approval" });
14193
14838
  } else if (choice === "Cancel Mission") {
14194
14839
  const stopped = saveActiveMission({ ...mission, status: "stopped", lastSummary: "Mission cancelled before approval." });
14195
14840
  checkpointMission(stopped, "Mission cancelled before approval.", "No further mission action queued.");
@@ -14809,7 +15454,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
14809
15454
  else if (choice === "Parallel Agent Settings") await showParallelismSettingsMenu(ctx);
14810
15455
  else if (choice === "Background Sub-agents") {
14811
15456
  const enabled = await chooseBool(ctx, "subagents.allowBackgroundSubagents?");
14812
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.allowBackgroundSubagents = enabled; }); workflowUiNotify(ctx, `subagents.allowBackgroundSubagents set to ${enabled} in ${r.file}. Background sub-agents run in Planning/Review/Validation phases without blocking the parent.`, "info"); }
15457
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.allowBackgroundSubagents = enabled; }); workflowUiNotify(ctx, `subagents.allowBackgroundSubagents set to ${enabled} in ${r.file}. Background sub-agents are advisory and may run in Planning/Review preflight without blocking the parent.`, "info"); }
14813
15458
  } else if (choice === "Activity Indicator") {
14814
15459
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
14815
15460
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); workflowUiNotify(ctx, `subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
@@ -15729,11 +16374,11 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15729
16374
  }
15730
16375
 
15731
16376
  function presetUsage(): string {
15732
- return "# Workflow Presets\n\nQuick use:\n- /workflow presets opens the selector\n- Ctrl+Shift+U cycles saved presets from the footer only while Plan/Mission/Standard Mode is active\n- /workflow presets list\n- /workflow presets apply <name>\n- /workflow presets next\n- /workflow presets prev\n- /workflow presets save <name>\n- /workflow presets create <name> from simple|standard|deep|maximum\n- /workflow presets edit <name>\n- /workflow presets rename <old> to <new>\n- /workflow presets delete <name>";
16377
+ return `# Workflow Presets\n\nQuick use:\n- /workflow presets opens the selector\n- ${workflowPresetCycleShortcutLabel()} cycles saved presets from the footer only while Plan/Mission/Standard Mode is active\n- /workflow presets list\n- /workflow presets apply <name>\n- /workflow presets next\n- /workflow presets prev\n- /workflow presets save <name>\n- /workflow presets create <name> from simple|standard|deep|maximum\n- /workflow presets edit <name>\n- /workflow presets rename <old> to <new>\n- /workflow presets delete <name>`;
15733
16378
  }
15734
16379
 
15735
16380
  function presetActionMessage(title: string, name: string, resultFile?: string, scope?: string): string {
15736
- return `# ${title}\n\nPreset: ${name}\n${scope ? `Scope: ${scope}\n` : ""}${resultFile ? `File: ${resultFile}\n` : ""}\nScope note: presets apply across Standard Mode, Plan Mode, Mission Mode, shared sub-agents, and selected UI settings.\nUse selector: /workflow presets\nCycle saved presets: Ctrl+Shift+U while Plan/Mission/Standard Mode is active\nFooter: idle displays Plan/Mission entry shortcuts only; active workflows display compact widget/preset hints and workflow switch hints by default.\n\nModel/provider choices and shared compaction settings are preserved.`;
16381
+ return `# ${title}\n\nPreset: ${name}\n${scope ? `Scope: ${scope}\n` : ""}${resultFile ? `File: ${resultFile}\n` : ""}\nScope note: presets apply across Standard Mode, Plan Mode, Mission Mode, shared sub-agents, and selected UI settings.\nUse selector: /workflow presets\nCycle saved presets: ${workflowPresetCycleShortcutLabel()} while Plan/Mission/Standard Mode is active\nFooter: idle displays Plan/Mission entry shortcuts only; active workflows display compact widget/preset hints and workflow switch hints by default.\n\nModel/provider choices and shared compaction settings are preserved.`;
15737
16382
  }
15738
16383
 
15739
16384
  function parsePresetCreate(rest: string): { name: string; template?: string } {
@@ -15761,7 +16406,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15761
16406
  const { names, options } = presetDisplayOptions(settings);
15762
16407
  if (!ctx.hasUI) return show(pi, renderWorkflowPresets(settings));
15763
16408
  if (names.length === 0) return show(pi, "# Workflow Presets\n\nNo presets available.");
15764
- const choice = await ctx.ui.select(`Workflow Presets — active: ${activeWorkflowPresetLabel(settings)} — Ctrl+Shift+U cycles during Plan/Mission`, options);
16409
+ const choice = await ctx.ui.select(`Workflow Presets — active: ${activeWorkflowPresetLabel(settings)} — ${workflowPresetCycleShortcutLabel()} cycles during Plan/Mission`, options);
15765
16410
  if (!choice) return;
15766
16411
  const selected = names[options.indexOf(choice)];
15767
16412
  if (!selected) return;
@@ -15890,7 +16535,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15890
16535
  const r = createWorkflowPreset(ctx.cwd, undefined, parsed.name, parsed.template);
15891
16536
  setWorkflowUi(ctx, state, activeSubagents);
15892
16537
  const safe = normalizeWorkflowPresetName(parsed.name);
15893
- return show(pi, `# Workflow Preset Created\n\nPreset: ${parsed.name}\nSaved As: ${safe}\n${parsed.template ? `Template: ${parsed.template}\n` : ""}Scope: ${r.scope}\nFile: ${r.file}\n\nThe preset was saved but not applied.\nScope note: presets apply across Standard Mode, Plan Mode, Mission Mode, shared sub-agents, and selected UI settings.\nApply now: /workflow presets apply ${safe}\nOpen selector: /workflow presets\nCycle saved presets: Ctrl+Shift+U while Plan/Mission/Standard Mode is active`);
16538
+ return show(pi, `# Workflow Preset Created\n\nPreset: ${parsed.name}\nSaved As: ${safe}\n${parsed.template ? `Template: ${parsed.template}\n` : ""}Scope: ${r.scope}\nFile: ${r.file}\n\nThe preset was saved but not applied.\nScope note: presets apply across Standard Mode, Plan Mode, Mission Mode, shared sub-agents, and selected UI settings.\nApply now: /workflow presets apply ${safe}\nOpen selector: /workflow presets\nCycle saved presets: ${workflowPresetCycleShortcutLabel()} while Plan/Mission/Standard Mode is active`);
15894
16539
  }
15895
16540
  if (action === "configure" || action === "edit") {
15896
16541
  if (!rest) return show(pi, "# Workflow Presets\n\nUsage: /workflow presets edit <name>");
@@ -16153,30 +16798,96 @@ Pi Version: v${VERSION}
16153
16798
  if (process.env.PI_SUBAGENT_WORKER === "1") return;
16154
16799
  const settings = loadWorkflowSettings(ctx.cwd);
16155
16800
  let phase = phaseForWorkflowMode(state.mode);
16801
+ const command = event.toolName === "bash" ? String((event.input as { command?: unknown }).command ?? "") : undefined;
16156
16802
  if (state.mode === "standard") {
16157
16803
  if (event.toolName === "bash") {
16158
- const command = String((event.input as { command?: unknown }).command ?? "");
16159
16804
  if (standardForcedSubagentSafeBash(command)) return;
16160
16805
  }
16161
16806
  if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") phase = "Execution";
16162
16807
  }
16163
- const planValidationPhase = phase === "Validation" && (state.mode === "validating" || state.mode === "revalidating");
16164
- if (phase !== "Execution" && phase !== "Repair" && !planValidationPhase) return;
16808
+ if (event.toolName === WORKFLOW_EXECUTION_RESULT_TOOL || event.toolName === MISSION_MILESTONE_RESULT_TOOL) phase = "Execution";
16809
+ if (event.toolName === WORKFLOW_REPAIR_RESULT_TOOL) phase = "Repair";
16810
+ if (event.toolName === WORKFLOW_VALIDATION_RESULT_TOOL) phase = "Validation";
16811
+ if (event.toolName === WORKFLOW_REVIEW_RESULT_TOOL) phase = "Review";
16812
+ if (!phase) return;
16165
16813
  const policy = state.mode === "standard" ? standardPhasePolicy(settings, phase) : phasePolicy(settings, phase);
16166
- if (policy !== "forced") return;
16814
+ if (subagentPolicyNeedsInternalDecision(policy)) {
16815
+ if (event.toolName === "subagent") {
16816
+ const workers = state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase);
16817
+ const target = advisoryWorkerTargetForPolicy(policy, workers);
16818
+ const observed = subagentUsageByPhase[phase] ?? 0;
16819
+ const task = state.task ?? state.originalTask;
16820
+ recordSubagentPolicyDecision(ctx, {
16821
+ phase,
16822
+ policy,
16823
+ outcome: "auto_delegate",
16824
+ reason: "visible parent sub-agent call started after advisory consideration",
16825
+ task,
16826
+ required: target,
16827
+ observed,
16828
+ background: false,
16829
+ });
16830
+ }
16831
+ return;
16832
+ }
16833
+ if (!subagentPolicyRequiresRequiredEvidence(policy)) return;
16167
16834
  if (state.mode === "standard" && standardForcedSubagentSatisfied(phase, settings, state.task ?? state.originalTask ?? "Standard Mode task")) return;
16168
- const required = workerTargetForPolicy("forced", state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase));
16835
+ const required = workerTargetForPolicy(policy, state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase));
16169
16836
  const observed = subagentUsageByPhase[phase] ?? 0;
16170
16837
  if (observed >= required) return;
16171
- if (planValidationPhase) {
16172
- if (event.toolName === "subagent") return;
16173
- return { block: true, reason: `${event.toolName} blocked — validation workers required (${observed}/${required})` };
16838
+ if (phase === "Validation" && event.toolName === WORKFLOW_VALIDATION_RESULT_TOOL) {
16839
+ recordSubagentPolicyDecision(ctx, {
16840
+ phase,
16841
+ policy,
16842
+ outcome: "allow_user_explicit_finalization",
16843
+ reason: "typed validation verdict is finalization; forced validation workers must be enforced before validation work, not after verdict",
16844
+ task: state.task ?? state.originalTask,
16845
+ required,
16846
+ observed,
16847
+ background: false,
16848
+ });
16849
+ return;
16174
16850
  }
16175
- if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") {
16176
- if (phase === "Repair") return;
16177
- const label = state.mode === "standard" ? "Standard Mode" : phase;
16178
- return { block: true, reason: `${event.toolName} blocked — execution workers required (${observed}/${required})` };
16851
+ if (event.toolName === "subagent") {
16852
+ const label = forcedSubagentGuardLabel(phase);
16853
+ const block = forcedSubagentVisibleCallBlock(ctx, phase, required, observed, event.input, label);
16854
+ if (block) return { block: true, reason: block };
16855
+ recordSubagentPolicyDecision(ctx, {
16856
+ phase,
16857
+ policy,
16858
+ outcome: "required",
16859
+ reason: "visible sub-agent call requested enough forced worker task entries for the remaining requirement",
16860
+ task: state.task ?? state.originalTask,
16861
+ required,
16862
+ observed,
16863
+ background: false,
16864
+ });
16865
+ return;
16179
16866
  }
16867
+ const actionDecision = forcedSubagentActionDecision({
16868
+ phase,
16869
+ policy,
16870
+ task: state.task ?? state.originalTask,
16871
+ kind: state.standardWorkKind,
16872
+ toolName: event.toolName,
16873
+ command,
16874
+ });
16875
+ if (actionDecision.allowBeforeEvidence) {
16876
+ recordSubagentPolicyDecision(ctx, {
16877
+ phase,
16878
+ policy,
16879
+ outcome: actionDecision.outcome,
16880
+ reason: actionDecision.reason,
16881
+ task: state.task ?? state.originalTask,
16882
+ required,
16883
+ observed,
16884
+ background: false,
16885
+ });
16886
+ return;
16887
+ }
16888
+ const label = forcedSubagentGuardLabel(phase);
16889
+ const noun = phase === "Planning" ? "planning" : phase === "Repair" ? "repair" : phase === "Review" ? "review" : phase === "Validation" ? "validation" : "execution";
16890
+ return { block: true, reason: `${event.toolName} blocked — forced ${noun} workers required (${observed}/${required}) for ${label}` };
16180
16891
  });
16181
16892
 
16182
16893
  function workflowAutoCompactionModeEligible(mode: string): boolean {
@@ -16385,7 +17096,7 @@ Pi Version: v${VERSION}
16385
17096
  const settings = loadWorkflowSettings(ctx.cwd);
16386
17097
  const override = standardForcedSubagentOverride(settings, phase);
16387
17098
  const task = state.task ?? state.originalTask ?? "Standard Mode task";
16388
- if (override.policy === "forced") rememberStandardSubagentPreflight(ctx, phase, task, workerTargetForPolicy("forced", override.workers));
17099
+ if (subagentPolicyRequiresRequiredEvidence(override.policy)) rememberStandardSubagentPreflight(ctx, phase, task, workerTargetForPolicy(override.policy, override.workers));
16389
17100
  }
16390
17101
  }
16391
17102
  finishSubagentActivity(event.toolCallId, Boolean(event.isError), ctx);
@@ -16643,7 +17354,7 @@ Pi Version: v${VERSION}
16643
17354
  const shouldRespectDraftPlanningRail = (action === "continue" || action === "next") && currentMission?.status === "draft" && currentMission.milestones.length === 0;
16644
17355
  const needsMissionResolution = action === "retry" || action === "repair" || action === "revalidate" || ((action === "continue" || action === "next") && !shouldRespectDraftPlanningRail);
16645
17356
  const resolvedMission = needsMissionResolution
16646
- ? await chooseResumeMission(ctx, resolveActiveMissionForResume(), action as "continue" | "next" | "retry" | "repair" | "revalidate")
17357
+ ? await chooseResumeMission(ctx, resolveActiveMissionForResume(ctx), action as "continue" | "next" | "retry" | "repair" | "revalidate")
16647
17358
  : undefined;
16648
17359
  const mission = resolvedMission ?? currentMission;
16649
17360
  if (!mission) return show(pi, `# Mission ${action}\n\nNo mission selected. ${action} cancelled.`);
@@ -16791,48 +17502,55 @@ Pi Version: v${VERSION}
16791
17502
  pi.registerCommand("m retry", { description: "Retry validation repair flow for current mission.", handler: async (_args, ctx) => handleMissionCommand("retry", ctx) });
16792
17503
  pi.registerCommand("m revalidate", { description: "Revalidate current mission milestone.", handler: async (_args, ctx) => handleMissionCommand("revalidate", ctx) });
16793
17504
 
17505
+ const workflowShortcutHandlers: Record<WorkflowShortcutActionId, (ctx: ExtensionContext) => Promise<void> | void> = {
17506
+ "workflow.widget.top.toggle": async (ctx) => {
17507
+ if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Top:disabled"); return; }
17508
+ const slot = activeTopWidgetSlot();
17509
+ if (!slot) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return; }
17510
+ toggleWorkflowWidget(ctx, slot);
17511
+ setWorkflowWidgetShortcutStatus(ctx, slot);
17512
+ },
17513
+ "workflow.widget.bottom.toggle": async (ctx) => {
17514
+ if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Bottom:disabled"); return; }
17515
+ if (isPlanWorkflowUiMode(state) && planBottomRelevant(state)) {
17516
+ toggleWorkflowWidget(ctx, "planBottom");
17517
+ return setWorkflowWidgetShortcutStatus(ctx, "planBottom");
17518
+ }
17519
+ if (isMissionWorkflowMode(state) && missionBottomRelevant(state, activeMissionForUi(state))) {
17520
+ toggleWorkflowWidget(ctx, "missionBottom");
17521
+ return setWorkflowWidgetShortcutStatus(ctx, "missionBottom");
17522
+ }
17523
+ if (isStandardWorkflowMode(state)) {
17524
+ toggleWorkflowWidget(ctx, "standardBottom");
17525
+ return setWorkflowWidgetShortcutStatus(ctx, "standardBottom");
17526
+ }
17527
+ workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return;
17528
+ },
17529
+ "workflow.presets.cycle": async (ctx) => {
17530
+ if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Preset:disabled"); return; }
17531
+ if (!isPlanWorkflowUiMode(state) && !isMissionWorkflowMode(state) && !isStandardWorkflowMode(state)) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return; }
17532
+ await cycleWorkflowPreset(ctx, 1);
17533
+ },
17534
+ "workflow.standard.toggle": async (ctx) => {
17535
+ if (isStandardWorkflowMode(state)) return exitStandardModeToIdle(ctx);
17536
+ await enterStandardMode(ctx);
17537
+ },
17538
+ "workflow.plan.toggle": async (ctx) => {
17539
+ if (isPlanWorkflowUiMode(state)) return exitPlanModeToIdle(ctx);
17540
+ await enterPlanModeAwaitingInput(ctx);
17541
+ },
17542
+ "workflow.mission.toggle": async (ctx) => {
17543
+ if (isMissionWorkflowMode(state)) return exitMissionModeToIdle(ctx);
17544
+ await enterMissionModeAwaitingInput(ctx);
17545
+ },
17546
+ };
16794
17547
 
16795
-
16796
- pi.registerShortcut("ctrl+shift+t", { description: "Toggle active Plan/Mission/Standard top workflow widget", handler: async (ctx) => {
16797
- if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Top:disabled"); return; }
16798
- const slot = activeTopWidgetSlot();
16799
- if (!slot) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return; }
16800
- toggleWorkflowWidget(ctx, slot);
16801
- setWorkflowWidgetShortcutStatus(ctx, slot);
16802
- }});
16803
- pi.registerShortcut("ctrl+shift+b", { description: "Toggle active Plan/Mission/Standard bottom workflow widget", handler: async (ctx) => {
16804
- if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Bottom:disabled"); return; }
16805
- if (isPlanWorkflowUiMode(state) && planBottomRelevant(state)) {
16806
- toggleWorkflowWidget(ctx, "planBottom");
16807
- return setWorkflowWidgetShortcutStatus(ctx, "planBottom");
16808
- }
16809
- if (isMissionWorkflowMode(state) && missionBottomRelevant(state, activeMissionForUi(state))) {
16810
- toggleWorkflowWidget(ctx, "missionBottom");
16811
- return setWorkflowWidgetShortcutStatus(ctx, "missionBottom");
16812
- }
16813
- if (isStandardWorkflowMode(state)) {
16814
- toggleWorkflowWidget(ctx, "standardBottom");
16815
- return setWorkflowWidgetShortcutStatus(ctx, "standardBottom");
16816
- }
16817
- workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return;
16818
- }});
16819
- pi.registerShortcut("ctrl+shift+u", { description: "Cycle workflow presets during Plan/Mission/Standard Mode", handler: async (ctx) => {
16820
- if (workflowWidgetUi(loadGlobalSettings()).enableWidgetShortcuts === false) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd), "Preset:disabled"); return; }
16821
- if (!isPlanWorkflowUiMode(state) && !isMissionWorkflowMode(state) && !isStandardWorkflowMode(state)) { workflowEditorHintText = workflowEditorHintTextFor(state, loadWorkflowSettings(ctx.cwd)); return; }
16822
- await cycleWorkflowPreset(ctx, 1);
16823
- }});
16824
- pi.registerShortcut("ctrl+shift+s", { description: "Toggle Standard Mode", handler: async (ctx) => {
16825
- if (isStandardWorkflowMode(state)) return exitStandardModeToIdle(ctx);
16826
- await enterStandardMode(ctx);
16827
- }});
16828
- pi.registerShortcut("ctrl+shift+l", { description: "Enter Plan Mode", handler: async (ctx) => {
16829
- if (isPlanWorkflowUiMode(state)) return exitPlanModeToIdle(ctx);
16830
- await enterPlanModeAwaitingInput(ctx);
16831
- }});
16832
- pi.registerShortcut("ctrl+shift+m", { description: "Toggle Mission Mode", handler: async (ctx) => {
16833
- if (isMissionWorkflowMode(state)) return exitMissionModeToIdle(ctx);
16834
- await enterMissionModeAwaitingInput(ctx);
16835
- }});
17548
+ for (const shortcut of WORKFLOW_SHORTCUTS) {
17549
+ pi.registerShortcut(workflowShortcutKey(shortcut.id), {
17550
+ description: shortcut.description,
17551
+ handler: workflowShortcutHandlers[shortcut.id],
17552
+ });
17553
+ }
16836
17554
 
16837
17555
  const handleWorkflowPlansCommand = async (args: string, _ctx: ExtensionContext): Promise<void> => {
16838
17556
  const parts = args.trim().split(/\s+/).filter(Boolean);
@@ -17823,6 +18541,10 @@ Public workflow commands:
17823
18541
  if (route) updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), [standardRole]: modelLabel(route) } }, ctx);
17824
18542
  }
17825
18543
  pi.setActiveTools(standardToolsFor(settings));
18544
+ const standardSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "standard before_agent_start", "standard");
18545
+ if (standardSurfaceBlock) {
18546
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW STANDARD MODE BLOCKED: ${standardSurfaceBlock}\n\nDo not execute the user request. Retry after the Standard tool surface is repaired.` };
18547
+ }
17826
18548
  standardTodoCreatedThisTurn = false;
17827
18549
  const rawPrompt = event.prompt?.trim() ?? "";
17828
18550
  const answeredClarification = Boolean(state.standardClarificationAnswer?.trim() && state.standardClarificationTask?.trim() && !state.standardClarificationPending);
@@ -17841,7 +18563,8 @@ Public workflow commands:
17841
18563
  return { systemPrompt: `${event.systemPrompt}\n\n${standardClarificationRequestPrompt(task, settings, gate.reason)}` };
17842
18564
  }
17843
18565
  }
17844
- if (task && (standardWork.phase === "Execution" || standardWork.phase === "Repair")) {
18566
+ const standardTodoMustInitialize = standardRequiredTodoMissing(state, settings, task);
18567
+ if (task && !standardTodoMustInitialize && (standardWork.phase === "Execution" || standardWork.phase === "Repair")) {
17845
18568
  const override = standardForcedSubagentOverride(settings, standardWork.phase);
17846
18569
  if (!beginForcedSubagentPhase(ctx, standardWork.phase, settings, override)) {
17847
18570
  pi.setActiveTools(clarificationToolsFor(settings, false));
@@ -17860,7 +18583,7 @@ Public workflow commands:
17860
18583
  if (!route) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE ROUTING FAILED: the configured planner model is unavailable. Do not execute the user request; explain the routing error and ask the user to check /workflow-settings list.` };
17861
18584
  const task = event.prompt || "Create an implementation plan for the user's requested task.";
17862
18585
  const gate = clarificationGate(task, settings, false, false);
17863
- if (!beginForcedSubagentPhase(ctx, "Planning", settings)) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Do not execute the user request; report the sub-agent policy blocker and wait for settings or worker availability to be fixed.` };
18586
+ if (!beginForcedSubagentPhase(ctx, "Planning", settings, { task })) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Do not execute the user request; report the sub-agent policy blocker and wait for settings or worker availability to be fixed.` };
17864
18587
  activeMission = undefined;
17865
18588
  const activePlanId = createWorkflowPlanId(ctx.cwd);
17866
18589
  updateState({ mode: "planning", activePlanId, activeMissionId: undefined, task, originalTask: task, draftPlan: undefined, approvedPlan: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: gate.force ? true : undefined, clarificationRequiredBeforePlan: gate.force || undefined, clarificationRequirementReason: gate.forceReason, clarificationSkipReason: gate.skipReason, clarificationQualityRetryCount: undefined, planningDepth: settings.planning.depth, clarificationMode: settings.planning.clarificationMode, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, maxValidationRetriesPerPlan: undefined, maxValidationRetriesPerWorkflow: undefined, lastValidationFailure: undefined, lastRepairAttempt: undefined, repairHistory: undefined, lastRepairStatus: "none", planRuntime: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "planning", task, draftPlan: undefined, approvedPlan: undefined }, settings, { lifecycleStatus: "planning", nextAction: "planner" }, undefined) : undefined, modelsUsed: { planner: modelLabel(route) }, planHistoryId: undefined, approvedPlanHistoryId: undefined }, ctx);
@@ -17908,6 +18631,10 @@ Public workflow commands:
17908
18631
  if (mission?.currentStep === "reviewer") {
17909
18632
  const settings = loadWorkflowSettings(ctx.cwd);
17910
18633
  armWorkflowToolsForPhase(ctx, "Review", "mission review before_agent_start");
18634
+ const reviewSurfaceBlock = verifyWorkflowToolSurface(ctx, "Review", "mission review before_agent_start");
18635
+ if (reviewSurfaceBlock) {
18636
+ return { systemPrompt: `${event.systemPrompt}\n\nPI MISSION REVIEW BLOCKED: ${reviewSurfaceBlock}\n\nDo not approve, execute, validate, repair, call workflow_plan_result, call mission_plan_result, or call workflow_progress. Report that Mission review stayed pending because required Review tools were unavailable, then stop.` };
18637
+ }
17911
18638
  const surfaceProblem = missionReviewRequiredToolSurfaceProblem(pi.getActiveTools());
17912
18639
  if (surfaceProblem) {
17913
18640
  blockMissionReviewToolSurface(ctx, mission, surfaceProblem, "mission review before_agent_start", false);
@@ -17935,7 +18662,14 @@ Public workflow commands:
17935
18662
  const prompt = await missionActiveGatePrompt(ctx, mission, persistedGateMode, event.systemPrompt);
17936
18663
  if (prompt) return prompt;
17937
18664
  }
17938
- pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
18665
+ if (state.reviewHandoffSuppression?.kind === "mission_typed_review_to_approval") {
18666
+ clearReviewHandoffSuppression(ctx, "mission execution turn started after typed Mission review handoff");
18667
+ }
18668
+ pi.setActiveTools(missionExecutionToolsFor(loadWorkflowSettings(ctx.cwd)));
18669
+ const missionExecutionSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "mission execution before_agent_start");
18670
+ if (missionExecutionSurfaceBlock) {
18671
+ return { systemPrompt: `${event.systemPrompt}\n\nPI MISSION EXECUTION BLOCKED: ${missionExecutionSurfaceBlock}\n\nDo not execute the milestone. Run /mission continue after the tool surface is repaired.` };
18672
+ }
17939
18673
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRuntimePrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
17940
18674
  }
17941
18675
 
@@ -17943,7 +18677,11 @@ Public workflow commands:
17943
18677
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
17944
18678
  const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
17945
18679
  if (!mission) return { systemPrompt: event.systemPrompt };
17946
- pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
18680
+ pi.setActiveTools(missionRepairToolsFor(loadWorkflowSettings(ctx.cwd)));
18681
+ const missionRepairSurfaceBlock = verifyWorkflowToolSurface(ctx, "Repair", "mission repair before_agent_start");
18682
+ if (missionRepairSurfaceBlock) {
18683
+ return { systemPrompt: `${event.systemPrompt}\n\nPI MISSION REPAIR BLOCKED: ${missionRepairSurfaceBlock}\n\nDo not repair the mission. Run /mission repair or /mission resume after the tool surface is repaired.` };
18684
+ }
17947
18685
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRepairPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
17948
18686
  }
17949
18687
 
@@ -17951,6 +18689,10 @@ Public workflow commands:
17951
18689
  const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
17952
18690
  if (!mission) return { systemPrompt: event.systemPrompt };
17953
18691
  pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
18692
+ const missionValidationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission validation before_agent_start");
18693
+ if (missionValidationSurfaceBlock) {
18694
+ return { systemPrompt: `${event.systemPrompt}\n\nPI MISSION VALIDATION BLOCKED: ${missionValidationSurfaceBlock}\n\nDo not validate the mission. Run /mission revalidate after the tool surface is repaired.` };
18695
+ }
17954
18696
  return { systemPrompt: `${event.systemPrompt}\n\n${missionValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
17955
18697
  }
17956
18698
 
@@ -17958,6 +18700,10 @@ Public workflow commands:
17958
18700
  const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
17959
18701
  if (!mission) return { systemPrompt: event.systemPrompt };
17960
18702
  pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
18703
+ const missionFinalValidationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission final validation before_agent_start");
18704
+ if (missionFinalValidationSurfaceBlock) {
18705
+ return { systemPrompt: `${event.systemPrompt}\n\nPI MISSION FINAL VALIDATION BLOCKED: ${missionFinalValidationSurfaceBlock}\n\nDo not validate the mission. Run /mission revalidate after the tool surface is repaired.` };
18706
+ }
17961
18707
  return { systemPrompt: `${event.systemPrompt}\n\n${missionFinalValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
17962
18708
  }
17963
18709
 
@@ -17973,13 +18719,13 @@ Public workflow commands:
17973
18719
  const questions = state.clarifyingQuestions ?? parseClarifyingQuestions(state.draftPlan ?? "");
17974
18720
  const answerSummary = formatAnswersForPlanner(questions, parsed);
17975
18721
  const settings = loadWorkflowSettings(ctx.cwd);
17976
- if (!beginForcedSubagentPhase(ctx, "Planning", settings)) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Report the blocker and wait.` };
18722
+ if (!beginForcedSubagentPhase(ctx, "Planning", settings, { task: state.task ?? state.originalTask })) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Report the blocker and wait.` };
17977
18723
  updateState({ mode: "planning", clarifyingAnswers: parsed, modelsUsed: { ...(state.modelsUsed ?? {}), planner: modelLabel(route) } }, ctx);
17978
18724
  return { systemPrompt: `${event.systemPrompt}\n\n${planPrompt(state.task ?? "Plan with answers", state.approvedPlan, answerSummary, settings, { feedbackKind: "clarification" })}` };
17979
18725
  }
17980
18726
  // Not a shorthand answer — treat as free-text clarification
17981
18727
  const settings = loadWorkflowSettings(ctx.cwd);
17982
- if (!beginForcedSubagentPhase(ctx, "Planning", settings)) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Report the blocker and wait.` };
18728
+ if (!beginForcedSubagentPhase(ctx, "Planning", settings, { task: state.task ?? state.originalTask })) return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN MODE BLOCKED: forced planning sub-agent requirements are unavailable. Report the blocker and wait.` };
17983
18729
  updateState({ mode: "planning", clarifyingAnswers: input ? [{ index: 1, letter: "D", custom: input }] : state.clarifyingAnswers, modelsUsed: { ...(state.modelsUsed ?? {}), planner: modelLabel(route) } }, ctx);
17984
18730
  return { systemPrompt: `${event.systemPrompt}\n\n${planPrompt(state.task ?? "Plan with answers", state.approvedPlan, input, settings, { feedbackKind: "clarification" })}` };
17985
18731
  }
@@ -18002,6 +18748,10 @@ Public workflow commands:
18002
18748
  }
18003
18749
  if (state.mode === "reviewing") {
18004
18750
  pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
18751
+ const reviewSurfaceBlock = verifyWorkflowToolSurface(ctx, "Review", "plan review before_agent_start");
18752
+ if (reviewSurfaceBlock) {
18753
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW REVIEW BLOCKED: ${reviewSurfaceBlock}\n\nDo not review the approved plan. Run /plan continue after the review tool surface is repaired.` };
18754
+ }
18005
18755
  return { systemPrompt: `${event.systemPrompt}\n\n${reviewerPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
18006
18756
  }
18007
18757
  if (state.mode === "reviewed") {
@@ -18012,23 +18762,26 @@ Public workflow commands:
18012
18762
  const settings = loadWorkflowSettings(ctx.cwd);
18013
18763
  clearReviewHandoffSuppression(ctx, "execution turn started after typed Plan review handoff");
18014
18764
  pi.setActiveTools(executionToolsFor(settings));
18015
- const missingExecutionTools = missingRequiredPlanExecutionTools(pi.getActiveTools(), settings);
18016
- traceWorkflowTracking(ctx, "before-agent-execution-tool-snapshot", { activeTools: pi.getActiveTools(), missingExecutionTools });
18017
- if (missingExecutionTools.length) {
18018
- const reason = `Required execution tools are unavailable after before_agent_start execution tool rearm: ${missingExecutionTools.join(", ")}. Execution prompt was not injected.`;
18019
- updateState({ mode: "plan_approved", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "plan_approved" }, settings, { lifecycleStatus: "blocked", nextAction: "repair execution tool surface" }, state.approvedPlan) : state.planProgress, lastReviewFailure: reason }, ctx);
18020
- pi.setActiveTools(webSafePlanTools(PLAN_TOOLS));
18021
- recordWorkflowInternalEvent(ctx, `Plan execution blocked at before_agent_start because required execution tools were unavailable after rearm: ${missingExecutionTools.join(", ")}.`);
18022
- return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW EXECUTION BLOCKED: ${reason}\n\nDo not execute the approved plan. Run /plan continue after the execution tool surface is repaired.` };
18765
+ const executionSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "plan execution before_agent_start");
18766
+ if (executionSurfaceBlock) {
18767
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW EXECUTION BLOCKED: ${executionSurfaceBlock}\n\nDo not execute the approved plan. Run /plan continue after the execution tool surface is repaired.` };
18023
18768
  }
18024
18769
  return { systemPrompt: `${event.systemPrompt}\n\n${executePrompt(state, settings, phasePreflightBlocks.Execution)}` };
18025
18770
  }
18026
18771
  if (state.mode === "repairing") {
18027
18772
  pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
18773
+ const repairSurfaceBlock = verifyWorkflowToolSurface(ctx, "Repair", "plan repair before_agent_start");
18774
+ if (repairSurfaceBlock) {
18775
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW REPAIR BLOCKED: ${repairSurfaceBlock}\n\nDo not repair the approved plan. Run /plan repair after the tool surface is repaired.` };
18776
+ }
18028
18777
  return { systemPrompt: `${event.systemPrompt}\n\n${workflowRepairPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
18029
18778
  }
18030
18779
  if (state.mode === "validating" || state.mode === "revalidating") {
18031
18780
  pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
18781
+ const validationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "plan validation before_agent_start");
18782
+ if (validationSurfaceBlock) {
18783
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW VALIDATION BLOCKED: ${validationSurfaceBlock}\n\nDo not validate the approved plan. Run /plan revalidate after the tool surface is repaired.` };
18784
+ }
18032
18785
  return { systemPrompt: `${event.systemPrompt}\n\n${validatePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Validation)}` };
18033
18786
  }
18034
18787
  if (state.mode === "validated") {
@@ -18061,6 +18814,11 @@ Public workflow commands:
18061
18814
  return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
18062
18815
  }
18063
18816
 
18817
+ if (missionReviewParentSuppressed()) {
18818
+ traceWorkflowTracking(ctx, "typed-mission-review-parent-message-suppressed", { mode: state.mode, activeMissionId: state.activeMissionId });
18819
+ return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
18820
+ }
18821
+
18064
18822
  if (text && planReviewRepairActive(state) && state.lastWorkflowHandoff?.type === WORKFLOW_REVIEW_RESULT_TOOL) {
18065
18823
  const firstTextIndex = message.content.findIndex((block) => block.type === "text");
18066
18824
  if (firstTextIndex >= 0) {
@@ -18145,6 +18903,12 @@ Public workflow commands:
18145
18903
  return;
18146
18904
  }
18147
18905
 
18906
+ if (missionReviewParentSuppressed()) {
18907
+ traceWorkflowTracking(ctx, "typed-mission-review-parent-agent-end-suppressed", { mode: state.mode, activeMissionId: state.activeMissionId });
18908
+ clearReviewHandoffSuppression(ctx, "typed Mission review parent turn ended after accepted approval handoff");
18909
+ return;
18910
+ }
18911
+
18148
18912
  if (!text) {
18149
18913
  const interruptionReason = workflowTransientInterruptionReason(event, "Workflow turn stopped before producing assistant output.");
18150
18914
  if (recoverContextInterruptedWorkflowTurn(event, ctx)) return;
@@ -18163,14 +18927,6 @@ Public workflow commands:
18163
18927
 
18164
18928
  if ((state.mode === "validating" || state.mode === "revalidating") && state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) {
18165
18929
  traceWorkflowTracking(ctx, "typed-plan-validation-agent-end-boundary", { source: "typed_validation_tool_agent_end", mode: state.mode, verdict: state.validationVerdict, nextAction: "route validation verdict" });
18166
- planForcedSubagentPreflightReconcile(ctx, "Validation");
18167
- const forcedValidationBlock = forcedSubagentUsageSatisfied(ctx, "Validation");
18168
- if (forcedValidationBlock) {
18169
- const report = state.validationReport ?? text;
18170
- if (queuePlanValidationHandoffRetry(ctx, report, forcedValidationBlock, "typed validation accepted without forced Validation evidence")) return;
18171
- blockPlanValidationHandoff(ctx, report, forcedValidationBlock);
18172
- return;
18173
- }
18174
18930
  const report = state.validationReport ?? text;
18175
18931
  const verdict = state.validationVerdict ?? normalizeValidationVerdict(extractVerdict(report), report);
18176
18932
  const validationStatus = planValidationStatusForVerdict(verdict);
@@ -18327,11 +19083,34 @@ Public workflow commands:
18327
19083
  return;
18328
19084
  }
18329
19085
 
19086
+ if (state.mode === "revalidating" && state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) {
19087
+ traceWorkflowTracking(ctx, "typed-plan-stale-repair-agent-end-boundary", { source: "typed_repair_tool_agent_end", mode: state.mode, nextAction: "discard stale repair handoff before validation prose fallback" });
19088
+ updateState({ lastWorkflowHandoff: undefined }, ctx);
19089
+ return;
19090
+ }
19091
+
18330
19092
  if (state.mode === "executed" && state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) {
18331
19093
  const validationAvailable = planValidationModelAvailable(settings);
18332
19094
  const validateAfterExecution = planAutoValidationEnabled(settings);
18333
19095
  const validationGateActive = planValidationGateActive(settings);
18334
19096
  traceWorkflowTracking(ctx, "typed-execution-agent-end-validation-boundary", { source: "typed_execution_tool_agent_end", mode: "executed", validationAvailable, validateAfterExecution, validationGateActive });
19097
+ if (planExecutionIncomplete(state, settings)) {
19098
+ const reason = "Typed execution completed while approved Plan progress still has incomplete steps. Resume execution to complete remaining steps before validation.";
19099
+ updateState({
19100
+ mode: "reviewed",
19101
+ planExecutionStepIndex: undefined,
19102
+ planStepValidationIndex: undefined,
19103
+ validationReport: undefined,
19104
+ validationVerdict: undefined,
19105
+ lastValidationFailure: undefined,
19106
+ lastRepairStatus: "none",
19107
+ lastRepairAttempt: undefined,
19108
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
19109
+ }, ctx);
19110
+ queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason });
19111
+ showBlockedPlanRecoveryMenu(ctx);
19112
+ return;
19113
+ }
18335
19114
  if (validationAvailable && validateAfterExecution) {
18336
19115
  schedulePlanValidationAfterExecution(ctx, "typed execution agent end", "begin validation after typed execution agent end", "Typed execution completed but validation handoff failed");
18337
19116
  } else if (validationAvailable && settings.workflow.offerValidationAfterExecute !== false) {
@@ -18743,7 +19522,7 @@ Public workflow commands:
18743
19522
  return;
18744
19523
  }
18745
19524
  if (state.reviewRepairInProgress) {
18746
- recordWorkflowInternalEvent(ctx, "Reviewer-requested Plan repair text completed; accepting structurally valid repair without forced Planning retry.");
19525
+ recordWorkflowInternalEvent(ctx, "Reviewer-requested Plan repair text completed; enforcing forced Planning evidence before accepting repair.");
18747
19526
  }
18748
19527
  const requiredClarificationStillMissing = state.clarificationRequiredBeforePlan === true && !state.clarifyingAnswers?.length;
18749
19528
  if (requiredClarificationStillMissing) {
@@ -18762,6 +19541,23 @@ Public workflow commands:
18762
19541
  recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
18763
19542
  return;
18764
19543
  }
19544
+ planForcedSubagentPreflightReconcile(ctx, "Planning");
19545
+ const forcedPlanningBlock = forcedSubagentUsageSatisfied(ctx, "Planning");
19546
+ if (forcedPlanningBlock) {
19547
+ updateState({
19548
+ mode: "plan_draft",
19549
+ activePlanId: state.activePlanId ?? ensureActivePlanId(ctx),
19550
+ draftPlan: text,
19551
+ approvedPlan: undefined,
19552
+ lastReviewFailure: forcedPlanningBlock,
19553
+ planProgress: workflowPlanProgressEnabled(settings)
19554
+ ? mergePlanProgress({ ...state, mode: "validated", draftPlan: text, approvedPlan: undefined }, settings, { lifecycleStatus: "blocked", nextAction: "planning blocked by forced sub-agent policy", steps: [] }, undefined)
19555
+ : state.planProgress,
19556
+ }, ctx);
19557
+ showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage("Planning", forcedPlanningBlock, "Planning")}`);
19558
+ recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
19559
+ return;
19560
+ }
18765
19561
  const planOrchestratorProblem = orchestrationProblem(ctx, "plan");
18766
19562
  const planContractProblem = planDraftContractProblem(text);
18767
19563
  if (planOrchestratorProblem || planContractProblem) {
@@ -18905,8 +19701,19 @@ Public workflow commands:
18905
19701
  planForcedSubagentPreflightReconcile(ctx, "Execution");
18906
19702
  if (!allTrackedStepsCompleted && blockIfForcedSubagentsMissing(ctx, "Execution")) {
18907
19703
  const reason = "Execution stopped before all approved Plan steps were tracked and the forced execution worker requirement was not satisfied.";
18908
- updateState({ mode: "validated", executionSummary: text, validationReport: reason, validationVerdict: "UNKNOWN", planExecutionStepIndex: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "fix execution progress/sub-agent blocker then /plan continue" }) : state.planProgress }, ctx);
18909
- queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { validationText: reason, verdict: "UNKNOWN", reason });
19704
+ updateState({
19705
+ mode: "reviewed",
19706
+ executionSummary: text,
19707
+ validationReport: undefined,
19708
+ validationVerdict: undefined,
19709
+ lastValidationFailure: undefined,
19710
+ lastRepairStatus: "none",
19711
+ lastRepairAttempt: undefined,
19712
+ planExecutionStepIndex: undefined,
19713
+ planStepValidationIndex: undefined,
19714
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "fix execution progress/sub-agent blocker then /plan continue" }) : state.planProgress,
19715
+ }, ctx);
19716
+ queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { reason });
18910
19717
  return;
18911
19718
  }
18912
19719
  const progressWarning = !allTrackedStepsCompleted && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true && workflowPlanProgressEnabled(settings) && state.planProgress?.steps.length
@@ -18946,6 +19753,23 @@ Public workflow commands:
18946
19753
  return;
18947
19754
  }
18948
19755
  }
19756
+ if (planExecutionIncomplete(state, settings)) {
19757
+ const reason = "Execution summary arrived while approved Plan progress still has incomplete steps. Resume execution to complete remaining steps before validation.";
19758
+ updateState({
19759
+ mode: "reviewed",
19760
+ planExecutionStepIndex: undefined,
19761
+ planStepValidationIndex: undefined,
19762
+ validationReport: undefined,
19763
+ validationVerdict: undefined,
19764
+ lastValidationFailure: undefined,
19765
+ lastRepairStatus: "none",
19766
+ lastRepairAttempt: undefined,
19767
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
19768
+ }, ctx);
19769
+ queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason });
19770
+ showBlockedPlanRecoveryMenu(ctx);
19771
+ return;
19772
+ }
18949
19773
  if (validationAvailable && validateAfterExecution) {
18950
19774
  schedulePlanValidationAfterExecution(ctx, "execution complete", "begin validation after execution", "Execution completed but validation handoff failed");
18951
19775
  } else if (validationAvailable && settings.workflow.offerValidationAfterExecute !== false) {
@@ -18970,10 +19794,10 @@ Public workflow commands:
18970
19794
  const repairWorkers = workerCount(settings, "Repair");
18971
19795
  const repairRequired = workerTargetForPolicy(repairPolicy, repairWorkers);
18972
19796
  const availableRepairAgents = listEffectiveAgents(ctx.cwd);
18973
- const repairSuitable = repairPolicy === "forced" ? chooseForcedSubagents("Repair", repairRequired, "Repair", availableRepairAgents) : [];
19797
+ const repairSuitable = subagentPolicyRequiresRequiredEvidence(repairPolicy) ? chooseForcedSubagents("Repair", repairRequired, "Repair", availableRepairAgents) : [];
18974
19798
  const hasWriteCapableSubagents = repairSuitable.some(a => subagentToolsAllowMutation(a.tools));
18975
19799
  if (hasWriteCapableSubagents) {
18976
- const required = workerTargetForPolicy("forced", workerCount(settings, "Repair"));
19800
+ const required = workerTargetForPolicy(repairPolicy, workerCount(settings, "Repair"));
18977
19801
  const observed = subagentUsageByPhase.Repair ?? 0;
18978
19802
  updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: compact(text, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: state.currentValidationRetry ?? 0, status: "blocked", repairSummary: compact(text, 800), nextAction: `Repair summary saved, but revalidation blocked by forced repair sub-agent policy (${observed}/${required} workers).` }), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "repair summary saved; forced repair sub-agents missing" }) : state.planProgress }, ctx);
18979
19803
  return;
@@ -18990,6 +19814,22 @@ Public workflow commands:
18990
19814
  recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
18991
19815
  return;
18992
19816
  }
19817
+ if (!planValidationBoundaryReached(state, settings)) {
19818
+ updateState({
19819
+ mode: "reviewed",
19820
+ executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary received before validation boundary (not revalidated):\n${text}`.trim(),
19821
+ lastRepairStatus: "none",
19822
+ lastRepairAttempt: undefined,
19823
+ repairRetryState: { ...(state.repairRetryState ?? {}), validation: undefined },
19824
+ validationReport: undefined,
19825
+ validationVerdict: undefined,
19826
+ lastValidationFailure: undefined,
19827
+ planStepValidationIndex: undefined,
19828
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
19829
+ }, ctx);
19830
+ show(pi, "# Plan Repair Ignored\n\nApproved Plan execution is incomplete. Repair/revalidation is not available until the configured validation boundary is reached.\n\nUse /plan continue.");
19831
+ return;
19832
+ }
18993
19833
  updateState({ mode: "revalidating", executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary:\n${text}`.trim(), lastRepairStatus: "completed", repairRetryState: { ...(state.repairRetryState ?? {}), validation: state.repairRetryState?.validation ? { ...state.repairRetryState.validation, status: "completed", inProgress: false, lastAttempt: compact(text, 1200) } : undefined }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "revalidating", lastRepairStatus: "completed" }, settings, { lifecycleStatus: "revalidating", validationStatus: "running", repairStatus: "completed", nextAction: "validation result" }) : state.planProgress, lastRepairAttempt: compact(text, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: state.currentValidationRetry ?? 0, status: "completed", repairSummary: compact(text, 800), nextAction: "Revalidate repaired Plan Mode workflow." }) }, ctx);
18994
19834
  deferWorkflowAction(pi, "begin revalidation after repair", async () => {
18995
19835
  const revalidationStarted = await beginValidation(ctx, true, true);
@@ -19001,11 +19841,19 @@ Public workflow commands:
19001
19841
  // ── Validation complete ──
19002
19842
  if (state.mode === "validating" || state.mode === "revalidating") {
19003
19843
  if (state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) return;
19004
- planForcedSubagentPreflightReconcile(ctx, "Validation");
19005
- const forcedValidationBlock = forcedSubagentUsageSatisfied(ctx, "Validation");
19006
- if (forcedValidationBlock) {
19007
- if (queuePlanValidationHandoffRetry(ctx, text, forcedValidationBlock, "validation completed without forced Validation evidence")) return;
19008
- blockPlanValidationHandoff(ctx, text, forcedValidationBlock);
19844
+ if (!planValidationBoundaryReached(state, settings)) {
19845
+ const reason = "Approved Plan execution is incomplete. Validation output was ignored until the configured validation boundary is reached.";
19846
+ updateState({
19847
+ mode: "reviewed",
19848
+ validationReport: undefined,
19849
+ validationVerdict: undefined,
19850
+ lastValidationFailure: undefined,
19851
+ lastRepairStatus: "none",
19852
+ lastRepairAttempt: undefined,
19853
+ planStepValidationIndex: undefined,
19854
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
19855
+ }, ctx);
19856
+ show(pi, `# Plan Validation Ignored\n\n${reason}\n\nUse /plan continue.`);
19009
19857
  return;
19010
19858
  }
19011
19859
  const verdict = normalizeValidationVerdict(extractVerdict(text), text);