@mediadatafusion/pi-workflow-suite 0.0.9 → 0.0.11

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.
@@ -10,13 +10,13 @@ import { CustomEditor, VERSION, compact as piCompact, estimateTokens as piEstima
10
10
  import { Type } from "typebox";
11
11
  import { activeWorkflowPresetLabel, applyMissionModelForRole, applyModelForRole, applyStandardModelForRole, applyWorkflowPreset, compactionModeLabel, createProjectSettingsOverride, createWorkflowPreset, defaultWorkflowSettings, deleteWorkflowPreset, effectivePlanApprovalRequired, effectiveReviewAutoRun, effectiveValidateAfterExecution, effectiveValidationAutoRun, effectiveRepairGate, formatRole, getDefaultWriteTarget, loadEffectiveSettings, loadGlobalSettings, loadWorkflowSettings, normalizeWorkflowPresetName, parseMissionModelRole, parseRole, parseThinkingLevel, renameWorkflowPreset, renderActiveWorkflowPresetSummary, renderStandardModelStrategy, renderWorkflowModels, renderWorkflowPresets, resolveWorkflowPresetName, roleIsConfigured, saveCurrentWorkflowPreset, setMissionModelForRole, setMissionThinkingForRole, setModelForRole, setRoleEnabled, setStandardModelForRole, setStandardThinkingForRole, setThinkingForRole, standardModelSource, standardModelSourceLabel, standardTodoTriggerModeLabel, updateSettings, workflowCompactionCheckModeLabel, workflowPresetCatalog, workflowPresetLabel, workflowPresetNames, workflowPresetPickerLabel, workflowRoleLabel, workflowSettingsConsistencyDiagnostics, WORKFLOW_MANUAL_PRESET, WORKFLOW_SETTINGS_FILE, type MissionModelRole, type RoleModelSettings, type WorkflowRole, type WorkflowSettingsScope, type WorkflowStartupLogo, type WorkflowStartupLogoColorStyle, type WorkflowStartupLogoFont, type WorkflowStartupLogoShadowDirection, type WorkflowStartupVisual, type CustomBrandBaseVisual, type StandardClarificationMode, type StandardModelRole, type StandardTodoTriggerMode, type WorkflowAgentScope } from "./workflow-model-router.js";
12
12
  import { renderHandoffProjectContext, renderWorkflowStatus, renderWorkflowSummary } from "./workflow-summary.js";
13
- import { BASE_EXECUTE_TOOLS, EXECUTE_TOOLS, PLAN_TOOLS, WORKFLOW_DIAGRAM_TOOL, WORKFLOW_PLAN_RESULT_TOOL, WORKFLOW_REVIEW_RESULT_TOOL, WORKFLOW_EXECUTION_RESULT_TOOL, WORKFLOW_VALIDATION_RESULT_TOOL, WORKFLOW_REPAIR_RESULT_TOOL, MISSION_PLAN_RESULT_TOOL, MISSION_MILESTONE_RESULT_TOOL, STANDARD_HANDOFF_RESULT_TOOL, isBlockedExecuteCommand, registerToolGuard, VALIDATOR_TOOLS } from "./workflow-tool-guard.js";
13
+ import { BASE_EXECUTE_TOOLS, EXECUTE_TOOLS, PLAN_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, clearOldWorkflowPlans, compact, createMissionState, createStandardRuntimeId, createWorkflowPlanId, emptyState, extractVerdict, isMissionRuntimeActiveStatus, 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
16
  import { runWorkflowSubagents, workflowSubagentResultOutput, type WorkflowSubagentResult, type WorkflowSubagentTask } from "./subagent/runner.js";
17
17
  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
18
  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 } from "./workflow-subagent-policy.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
20
  import { renderWorkflowSettingsCapabilityMatrix, workflowSettingsCapabilitiesByStatus } from "./workflow-settings-capabilities.js";
21
21
  import { Box, Markdown, Spacer, Text, hyperlink, type Component } from "@earendil-works/pi-tui";
22
22
 
@@ -693,8 +693,27 @@ function nativeMissionFinalDeliverableSummary(summary: NonNullable<WorkflowState
693
693
  return professionalFinalSummaryMarkdown(summary.finalReport?.trim() || visibleMissionCompletionReport(summary));
694
694
  }
695
695
 
696
- function recordWorkflowInternalEvent(_ctx: ExtensionContext | undefined, _content: string): void {
697
- // Internal lifecycle state is exposed through widgets/status and explicit slash commands, not transcript cards.
696
+ let workflowTraceWriteCount = 0;
697
+
698
+ function recordWorkflowInternalEvent(ctx: ExtensionContext | undefined, content: string): void {
699
+ try {
700
+ const cwd = ctx?.cwd ?? process.cwd();
701
+ const traceDir = join(cwd, ".pi");
702
+ const traceFile = join(traceDir, "workflow-suite-trace.log");
703
+ const ts = new Date().toISOString();
704
+ const line = `[${ts}] ${content}\n`;
705
+ if (!existsSync(traceDir)) mkdirSync(traceDir, { recursive: true });
706
+ writeFileSync(traceFile, line, { flag: "a" });
707
+ workflowTraceWriteCount += 1;
708
+ if (workflowTraceWriteCount % 50 === 0) {
709
+ const lines = readFileSync(traceFile, "utf8").split("\n").filter(Boolean);
710
+ if (lines.length > 500) {
711
+ writeFileSync(traceFile, lines.slice(-200).join("\n") + "\n", "utf8");
712
+ }
713
+ }
714
+ } catch {
715
+ // trace file write failure must never affect workflow behavior
716
+ }
698
717
  }
699
718
 
700
719
  function showStartupCard(pi: ExtensionAPI, content: string): void {
@@ -720,11 +739,7 @@ function endWorkflowPreflightUi(ctx: ExtensionContext): void {
720
739
 
721
740
  function queueWorkflowPrompt(pi: ExtensionAPI, content: string): void {
722
741
  workflowScheduledAgentTurns += 1;
723
- try {
724
- pi.sendMessage({ customType: "workflow-suite-instructions", content, display: false }, { triggerTurn: true, deliverAs: "followUp" });
725
- } finally {
726
- setTimeout(() => { workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1); }, 0);
727
- }
742
+ queueGuardedAgentTurn(pi, content, "workflow-suite-instructions");
728
743
  }
729
744
 
730
745
  function modelLabel(model: RoleModelSettings): string {
@@ -737,7 +752,7 @@ function effectiveStandardModelRole(settings: ReturnType<typeof loadWorkflowSett
737
752
 
738
753
  let workflowScheduledAgentTurns = 0;
739
754
 
740
- const WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS = [250, 750, 1500] as const;
755
+ const WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS = [250, 750, 1500, 3000, 5000, 8000, 13000] as const;
741
756
 
742
757
  function workflowTurnSendErrorMessage(error: unknown): string {
743
758
  return error instanceof Error ? error.message : String(error ?? "");
@@ -747,8 +762,16 @@ function workflowTurnSendErrorIsBusy(error: unknown): boolean {
747
762
  return /already processing|currently processing|wait for completion/i.test(workflowTurnSendErrorMessage(error));
748
763
  }
749
764
 
750
- function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: string, attempt = 0): void {
751
- const delay = attempt === 0 ? WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS[0] : WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS[Math.min(attempt, WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS.length - 1)];
765
+ function workflowTurnSendErrorIsConnection(error: unknown): boolean {
766
+ return /websocket|connection[_\s-]*(?:error|lost|closed|dropped|refused|reset)|network[_\s-]*error|failed to fetch|econnrefused|econnreset|etimedout|enotfound|eai_again/i.test(workflowTurnSendErrorMessage(error));
767
+ }
768
+
769
+ const WORKFLOW_CONNECTION_RETRY_DELAYS_MS = [750, 3000] as const;
770
+
771
+ function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: string, attempt = 0, connectionAttempt = 0): void {
772
+ const delay = connectionAttempt > 0
773
+ ? WORKFLOW_CONNECTION_RETRY_DELAYS_MS[Math.min(connectionAttempt - 1, WORKFLOW_CONNECTION_RETRY_DELAYS_MS.length - 1)]
774
+ : attempt === 0 ? WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS[0] : WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS[Math.min(attempt, WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS.length - 1)];
752
775
  setTimeout(() => {
753
776
  try {
754
777
  pi.sendMessage({ customType, content, display: false }, { triggerTurn: true, deliverAs: "followUp" });
@@ -756,7 +779,12 @@ function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: st
756
779
  } catch (error) {
757
780
  if (workflowTurnSendErrorIsBusy(error) && attempt < WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS.length - 1) {
758
781
  recordWorkflowInternalEvent(undefined, `Workflow agent turn delayed because Pi is still processing: ${customType}`);
759
- queueGuardedAgentTurn(pi, content, customType, attempt + 1);
782
+ queueGuardedAgentTurn(pi, content, customType, attempt + 1, connectionAttempt);
783
+ return;
784
+ }
785
+ if (workflowTurnSendErrorIsConnection(error) && connectionAttempt < WORKFLOW_CONNECTION_RETRY_DELAYS_MS.length) {
786
+ recordWorkflowInternalEvent(undefined, `Workflow agent turn retrying after connection error: ${customType} (retry ${connectionAttempt + 1}/${WORKFLOW_CONNECTION_RETRY_DELAYS_MS.length})`);
787
+ queueGuardedAgentTurn(pi, content, customType, attempt, connectionAttempt + 1);
760
788
  return;
761
789
  }
762
790
  workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
@@ -771,18 +799,15 @@ function queueAgentTurn(pi: ExtensionAPI, content: string, customType: string):
771
799
  }
772
800
 
773
801
  function queueNativeFinalSummary(pi: ExtensionAPI, summary: string, customType: "workflow-plan-final-summary" | "workflow-mission-final-summary"): void {
774
- workflowScheduledAgentTurns += 1;
775
- setTimeout(() => {
776
- try {
777
- pi.sendMessage({ customType, content: `Print this comprehensive final deliverable summary exactly as written. Do not call tools. Do not shorten it. Do not replace it with a status receipt. Do not add Workflow Suite card wrappers or hidden handoff commentary. Preserve the provided simple section headings exactly so the terminal can render styled headings without showing raw hashtag markers. Use professional presentation only: no emoji, no Markdown tables, no box-art tables, and no dash-bullet lists.\n\n${professionalFinalSummaryMarkdown(summary)}`, display: false }, { triggerTurn: true, deliverAs: "followUp" });
778
- } finally {
779
- workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
780
- }
781
- }, 0);
802
+ try {
803
+ pi.sendMessage({ customType, content: professionalFinalSummaryMarkdown(summary), display: true }, { triggerTurn: false });
804
+ } catch (error) {
805
+ recordWorkflowInternalEvent(undefined, `Workflow final summary display failed (state already persisted): ${workflowTurnSendErrorMessage(error)}`);
806
+ }
782
807
  }
783
808
 
784
809
  function visibleDeferredHandoffFailure(label: string): boolean {
785
- return /\b(menu|handoff)\b/i.test(label);
810
+ return /\b(menu|handoff|repair|revalidation|execution|phase|approval|mission)\b/i.test(label);
786
811
  }
787
812
 
788
813
  function deferWorkflowAction(pi: ExtensionAPI, label: string, action: () => Promise<void> | void): void {
@@ -831,7 +856,7 @@ function scheduleWorkflowHandoff(pi: ExtensionAPI, snapshot: WorkflowHandoffSnap
831
856
  // validationSubagentsAllowed imported from workflow-subagent-policy.ts
832
857
 
833
858
  function planToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
834
- const tools = webSafePlanTools([...PLAN_TOOLS, WORKFLOW_PLAN_RESULT_TOOL, MISSION_PLAN_RESULT_TOOL]);
859
+ const tools = webSafePlanTools([...PLAN_TOOLS, WORKFLOW_PLAN_RESULT_TOOL, MISSION_PLAN_RESULT_TOOL, WORKFLOW_PROGRESS_TOOL]);
835
860
  return planningSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
836
861
  }
837
862
 
@@ -925,7 +950,27 @@ function validationToolsFor(settings: ReturnType<typeof loadWorkflowSettings>):
925
950
 
926
951
 
927
952
  function workflowMermaidGuidance(): string {
928
- return "Mermaid diagrams are rendered by Workflow Suite as a uniform dark-mode visual component. When the response explains a user-facing workflow, export/share path, request lifecycle, architecture, data flow, multi-step sequence, state transition, dependencies, validation flow, or implementation phases, include a meaningful Mermaid diagram plus concise prose unless the user requested prose only or the response is trivial. In execution-capable workflow phases, call workflow_diagram with the Mermaid source so the platform renders it inline instead of leaving only a raw code block. Use the best type for the job: flowchart for pipelines/data flow, sequenceDiagram for actor/system interactions, stateDiagram for mode/status transitions, classDiagram or erDiagram for structures, and xychart-beta for simple metrics. Do not hardcode random Mermaid style/classDef/light-theme overrides unless the user explicitly asks; keep labels concise, printable, readable, and consistent with the dark diagram standard. Split crowded flows into multiple diagrams rather than shrinking labels. Avoid tiny joke diagrams for non-trivial explanations.";
953
+ return `Mermaid diagrams are rendered by Workflow Suite as a uniform dark-mode visual component.
954
+
955
+ Diagram policy (MANDATORY for all workflow phases):
956
+ - CREATE DIAGRAMS INLINE: call workflow_diagram immediately after the paragraph that introduces the concept being diagrammed. Do NOT batch diagrams at the beginning or end of your response.
957
+ - TRIGGER RULE: When explaining any of the following, you must include at least one meaningful Mermaid diagram plus concise prose:
958
+ * user-facing workflow (e.g., data flow, build pipeline, CI/CD, multi-step sequence)
959
+ * architecture (e.g., system components, service topology, dependency graph)
960
+ * request lifecycle (e.g., API request/response flow, auth flow, error handling path)
961
+ * state transitions (e.g., mode changes, status lifecycle, phase progression)
962
+ * export/share path (e.g., file export pipeline, sharing flow)
963
+ * implementation phases (e.g., plan execution order, sub-agent coordination)
964
+ * data flow (e.g., transformation pipeline, ETL, data mapping)
965
+ - Choose the best diagram type:
966
+ * flowchart -- for pipelines, data flow, architecture overviews
967
+ * sequenceDiagram -- for request/response, actor interactions, system exchanges
968
+ * stateDiagram -- for status/mode/phase transitions
969
+ * classDiagram or erDiagram -- for structures, relationships, schemas
970
+ * xychart-beta -- for simple metrics or progress tracking
971
+ - Split crowded flows into multiple diagrams rather than shrinking labels.
972
+ - Use concise, readable labels. Do not hardcode random Mermaid style/classDef/light-theme overrides.
973
+ - Skip diagrams only when: response is genuinely trivial, user explicitly requested prose-only, or the response is pure code/configuration with no structural concept to diagram.`;
929
974
  }
930
975
 
931
976
  function workflowRuntimeWebResearchGuidance(): string {
@@ -1062,7 +1107,7 @@ function workflowMermaidSegmentsHaveDiagram(markdown: string): boolean {
1062
1107
  function markdownWithoutWorkflowMermaidBlocks(markdown: string): string {
1063
1108
  return parseWorkflowMermaidSegments(markdown).map((segment) => {
1064
1109
  if (segment.type === "markdown") return segment.text;
1065
- return `\n\n[Workflow diagram rendered below with terminal preview and openable artifact link: ${segment.diagramType}]\n\n`;
1110
+ return "";
1066
1111
  }).join("").replace(/\n{4,}/g, "\n\n\n").trim();
1067
1112
  }
1068
1113
 
@@ -1521,6 +1566,9 @@ function planPrompt(task: string, priorPlan?: string, feedback?: string, setting
1521
1566
 
1522
1567
  ${professionalOutputGuidance("Plan Mode")}
1523
1568
 
1569
+ Diagram guidance:
1570
+ - ${workflowMermaidGuidance()}
1571
+
1524
1572
  Task:
1525
1573
  ${task}
1526
1574
 
@@ -1554,9 +1602,6 @@ No preamble. No visible chain-of-thought. No "I see that..." No "Let me think ab
1554
1602
  Initial analysis requirement:
1555
1603
  - ${analysisInstruction}
1556
1604
 
1557
- Diagram guidance:
1558
- - ${workflowMermaidGuidance()}
1559
-
1560
1605
  Web research guidance:
1561
1606
  ${workflowRuntimeWebResearchGuidance()}
1562
1607
 
@@ -1740,6 +1785,36 @@ function phaseWorkerLine(settings: ReturnType<typeof loadWorkflowSettings>, phas
1740
1785
  return `${phase} Workers: ${activeWorkerTargetLabel(policy, workerCount(settings, phase))}`;
1741
1786
  }
1742
1787
 
1788
+ function subagentCapabilityTable(): string {
1789
+ return `## Available Sub-Agent Types
1790
+
1791
+ | Agent | Tools | Best For |
1792
+ |---|---|---|
1793
+ | general-worker | read, grep, find, ls, bash | File creation via bash, general execution, repair work |
1794
+ | implementation-planning | read, grep, find, ls | Pure analysis, step breakdown, implementation strategy (no bash) |
1795
+ | codebase-research | read, grep, find, ls, bash | Code inspection, dependency tracing, architecture review |
1796
+ | quality-validation | read, grep, find, ls, bash | Regression checks, build verification, independent review |
1797
+ | workflow-orchestrator | read, grep, find, ls, bash, subagent | Fan-out research coordination, multi-worker orchestration |
1798
+
1799
+ Sub-agents can create and modify files via bash commands even without edit/write tools. The main executor owns final file edits and integration, but sub-agents can prepare files, run builds, and verify changes via bash.
1800
+ Prefer general-worker or codebase-research for work that needs file creation or command execution. Use implementation-planning only for pure read-only analysis tasks.`;
1801
+ }
1802
+
1803
+ function planStepStatusDisplay(state: WorkflowState): string {
1804
+ const steps = state.planProgress?.steps;
1805
+ if (!steps?.length) return "";
1806
+ const lines = steps.map((step, i) => {
1807
+ const statusLabel = step.status === "active" ? "ACTIVE"
1808
+ : step.status === "completed" ? "completed"
1809
+ : step.status === "failed" ? "FAILED"
1810
+ : step.status === "skipped" ? "skipped"
1811
+ : step.status === "blocked" ? "BLOCKED"
1812
+ : "pending";
1813
+ return ` ${i + 1}. ${step.title} — ${statusLabel}`;
1814
+ });
1815
+ return `Current Plan Step Status:\n${lines.join("\n")}`;
1816
+ }
1817
+
1743
1818
  function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(), preflightBlock?: string): string {
1744
1819
  const policy = settings.subagents.executionPolicy ?? "auto";
1745
1820
  const workers = workerCount(settings, "Execution");
@@ -1756,21 +1831,63 @@ function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
1756
1831
  : preflightSatisfied && policy === "forced"
1757
1832
  ? forcedSubagentPolicySatisfiedGuidance("execution")
1758
1833
  : policy === "forced"
1759
- ? `FORCED SUB-AGENT POLICY: before any file write, bash command, or final execution summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s). Use them in parallel where safe for read-only file inspection, implementation strategy, patch planning, regression search, and validation preparation. If subagent execution fails, stop and report the exact blocker. Do not apply simultaneous conflicting edits.`
1834
+ ? `FORCED SUB-AGENT POLICY: before any file write, bash command, or final execution summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s). 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.`
1760
1835
  : policy === "maximum"
1761
- ? `For non-trivial implementation, use execution sub-agents before editing for read-only analysis, implementation preparation, 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.`
1836
+ ? `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.`
1762
1837
  : policy === "deep"
1763
- ? `For non-trivial implementation, use at least ${workers.deep} execution sub-agent before editing for read-only analysis, implementation preparation, patch planning, or validation preparation. Explain any skip. Do not apply simultaneous conflicting edits.`
1764
- : "Execution sub-agents are strongly encouraged for speed and quality. Use them for file inspection, implementation prep, risk discovery, patch planning, or validation prep; skip only for trivial changes and explain why.";
1838
+ ? `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.`
1839
+ : "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.";
1840
+ const automatableEvidenceGuidance = `Automatable evidence contract:
1841
+ - Before calling workflow_execution_result, you MUST gather all automatable validation evidence that the approved plan requires.
1842
+ - 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.
1843
+ - For API/backend projects: run build, test, lint, typecheck; verify server startup, endpoint responses, and persistence where required by the plan.
1844
+ - For library/CLI projects: run build, test, lint, typecheck; verify CLI output or module exports where required.
1845
+ - Your execution summary must distinguish "Automated Evidence Completed" from "Truly Manual Evidence Remaining". Do not label automatable runtime checks as "manual verification needed".
1846
+ - "Manual verification needed" is only valid for genuinely non-automatable human judgment (visual design approval, subjective UX assessment, external service accounts you cannot access). If a check can be automated with available tools and settings, you must perform it.`;
1847
+
1765
1848
  return `You are in PI WORKFLOW EXECUTE MODE.
1766
1849
 
1850
+ YOUR TOOLS: edit, write, bash, workflow_progress, workflow_diagram, workflow_execution_result. You, the main executor, own all file writes, edits, and bash commands directly. Sub-agents are supplementary analysis only — they do not own file creation, and their read-only reports do not mean you are read-only. Even when sub-agents report they cannot write files, you must proceed with your own file writes, edits, and bash commands. For straightforward implementation steps, create files directly yourself without launching sub-agents.
1851
+
1852
+ CRITICAL: Use write and edit to create and modify files on disk. Do NOT output file contents as markdown code blocks in your chat response. If you find yourself typing a code block of file contents, STOP and use the write tool instead.
1853
+
1767
1854
  ${professionalOutputGuidance("Plan execution")}
1768
1855
 
1856
+ Diagram guidance:
1857
+ ${workflowMermaidGuidance()}
1858
+
1769
1859
  Approved plan is the execution contract. Do only this plan. No unrelated refactors. Do not commit. Do not push. Do not switch branches.
1770
1860
 
1771
- Before editing, restate the approved plan, list expected files to change, and run the required/appropriate execution sub-agents. Do not edit until forced sub-agent requirements are satisfied. Then implement the plan. After implementation, summarize changed files, sub-agent findings used, and recommend validation.
1861
+ When the task is complex or you need research before editing, launch sub-agents for analysis and inspection. Run them before writing files when their findings would change your implementation. Otherwise, proceed directly with file creation. After implementation, gather all automatable validation evidence, summarize changed files, sub-agent work/findings used, and recommend validation.
1862
+
1863
+ ${automatableEvidenceGuidance}
1864
+
1865
+ Plan Step Progress — the guard REQUIRES workflow_progress calls for EVERY step:
1866
+
1867
+ Step activation: workflow_progress({ step: N, status: "active" })
1868
+ REQUIRED before any edit, write, or meaningful bash for step N. The guard blocks file work without it.
1869
+
1870
+ Step completion: workflow_progress({ step: N, status: "completed" })
1871
+ REQUIRED after finishing step N. This advances the widget to the next step. Without it, the widget stays stuck.
1872
+
1873
+ Failed/blocked step: workflow_progress({ step: N, status: "failed" }) with reason in details.
1874
+
1875
+ Multi-step pattern — repeat for EVERY step, in order:
1876
+ Step 1: activate → do step 1 work → complete step 1
1877
+ Step 2: activate → do step 2 work → complete step 2
1878
+ Step 3: activate → do step 3 work → complete step 3
1879
+
1880
+ After you complete a step, the guard will require workflow_progress({ step: N+1, status: "active" }) before file writes for the next step. Listen to the guard — it tells you exactly which step needs activation.
1772
1881
 
1773
- Before your final execution summary, call workflow_execution_result with status, completedSteps, changedFiles, commands, blockers, and summary. The typed tool result is the primary handoff control plane. Your final execution summary must be validator-grade evidence, not just prose. Include Acceptance criteria coverage, exact files changed, Commands run with exit status, Checks skipped with reason, remaining manual verification, and any evidence from execution sub-agents.
1882
+ ${planStepStatusDisplay(state)}
1883
+
1884
+ - Sub-agents may launch before a step is activated, but the step must be active before writing files.
1885
+ - If you delegate step work to a sub-agent, you still own the parent workflow_progress calls. Start the sub-agent task with Plan step N: and require it to report Plan Step N Progress: completed | blocked | incomplete with evidence. After the sub-agent finishes, YOU must call workflow_progress to complete the step.
1886
+ - If you accidentally call workflow_plan_result during execution, do not retry it; continue with workflow_progress, write/edit/bash, and workflow_execution_result.
1887
+ Actual Plan execution tools can also activate the current step as a fallback, and text markers remain supported: WORKFLOW_STEP_STARTED:N, WORKFLOW_STEP_COMPLETED:N, WORKFLOW_STEP_FAILED:N. These fallbacks do not replace the required workflow_progress calls.
1888
+ - For per-step gated execution, track only the current allowed step and stop after that step.
1889
+
1890
+ Before your final execution summary, call workflow_execution_result with status, completedSteps, changedFiles, commands, blockers, and summary. completedSteps should list every approved Plan step completed in this execution turn. The typed tool result is the primary handoff control plane. Your final execution summary must be validator-grade evidence, not just prose. Include Acceptance criteria coverage, exact files changed, Commands run with exit status, Checks skipped with reason, Automated Evidence Completed (list what was verified automatically), Truly Manual Evidence Remaining (only genuinely non-automatable checks), and any evidence from execution sub-agents.
1774
1891
 
1775
1892
  Execution scope:
1776
1893
  - ${stepExecutionGuidance}
@@ -1784,13 +1901,13 @@ Sub-agent execution policy:
1784
1901
  - ${subagentGuidance}
1785
1902
  ${requiredSubagentPreflightSection(preflightBlock)}
1786
1903
  - Parallel File Edits controls simultaneous file writes only; it must not disable multiple execution agents.
1787
- - Execution agents may run in parallel for analysis, file inspection, implementation preparation, patch planning, regression search, and validation preparation.
1788
- - Main executor owns final edits. Sub-agents may propose patches or findings, but file writes must follow editConcurrencyMode=${settings.subagents.editConcurrencyMode ?? "sequential"}. With sequential mode, serialize writes through the main executor.
1904
+ - Execution agents may run in parallel for analysis, file inspection, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation.
1905
+ - 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.
1906
+ - 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.
1789
1907
  - ${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, required sub-agents must run before any edit/write/bash or final summary, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
1790
1908
  - Final execution summary must include Sub-Agent Usage Summary with agents used and findings applied, or a forced-policy blocker.
1791
1909
 
1792
- Diagram guidance:
1793
- ${workflowMermaidGuidance()}
1910
+ ${subagentCapabilityTable()}
1794
1911
 
1795
1912
  Web research guidance:
1796
1913
  ${workflowRuntimeWebResearchGuidance()}
@@ -1820,13 +1937,44 @@ function validatePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
1820
1937
  : policy === "deep"
1821
1938
  ? `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.`
1822
1939
  : "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.";
1940
+ const automatableEvidenceVerifierGuidance = `Automatable evidence verification:
1941
+ - Before marking Manual Verification Required: yes, verify that the missing evidence is genuinely non-automatable.
1942
+ - 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, mark Concrete Repairable Issue: yes and Evidence Gap: yes, then return FAIL rather than PARTIAL PASS.
1943
+ - PARTIAL PASS with Manual Verification Required: yes is valid only after you confirm the remaining check is truly human-only (visual design approval, subjective UX, external service credentials you cannot access, etc.).
1944
+
1945
+ To verify web app runtime behavior:
1946
+ - For projects with npm dev server: npm run dev -- --port 3017 &
1947
+ - For static HTML/CSS/JS projects (no package.json scripts): python3 -m http.server 8017 &
1948
+ - Wait for the server: sleep 2
1949
+ - Query endpoints: curl -fsS http://localhost:PORT/path
1950
+ - Check the process: ps aux | grep "server"
1951
+ - Stop the server when done: kill $!
1952
+ - Discard unwanted output: >/dev/null 2>&1
1953
+ Use single-line bash calls for each step.
1954
+
1955
+ CRITICAL: You MUST exhaust all automatable checks before returning PARTIAL PASS.
1956
+ DO NOT mark evidence as "could not verify" without actually trying to verify it.
1957
+ Start a server, curl the endpoints, check file accessibility — THEN report what you
1958
+ could and could not confirm. "No browser available" is not a reason to skip
1959
+ server-side checks that ARE automatable.
1960
+
1961
+ Headless browser verification: use the workflow_browser_check tool with the dev server URL to verify console errors, page errors, DOM elements, and localStorage behavior. This tool uses Puppeteer from the Pi runtime and works regardless of the target project's dependencies.
1962
+
1963
+ You MUST fill in EVERY structured output field, especially:
1964
+ - Concrete Repairable Issue: yes/no (with reason)
1965
+ - Evidence Gap: yes/no (with exact missing evidence)
1966
+ - Manual Verification Required: yes/no (with exact manual check)
1967
+ - Automated Evidence Completed: list everything verified automatically (not "none" or "n/a")`;
1968
+
1823
1969
  return `You are in PI WORKFLOW VALIDATOR MODE.
1824
1970
 
1825
1971
  ${professionalOutputGuidance("Plan validation")}
1826
1972
 
1827
- Use read-only tools only. Do not edit or write. You may run safe read-only bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when they are appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent validator, not the executor. Do not repair, do not re-grade your own repair, and do not accept executor claims without evidence. Compare the implementation against the approved plan. Inspect changed files, diffs, and commands run with exit status when available.
1973
+ Do not edit project source files. You may write temporary evidence-gathering scripts and files when needed for automated checks. You may run safe bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when they are appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent validator, not the executor. Do not repair, do not re-grade your own repair, and do not accept executor claims without evidence. Compare the implementation against the approved plan. Inspect changed files, diffs, and commands run with exit status when available.
1974
+
1975
+ ${automatableEvidenceVerifierGuidance}
1828
1976
 
1829
- Before your final validation report, call workflow_validation_result with scope=plan, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only.
1977
+ Before your final validation report, call workflow_validation_result with scope=plan, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only. After calling workflow_validation_result, print a concise validation summary for the user before stopping.
1830
1978
 
1831
1979
  Validation scope:
1832
1980
  - ${validationScope}
@@ -1841,10 +1989,11 @@ ${requiredSubagentPreflightSection(preflightBlock)}
1841
1989
  - 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.
1842
1990
  - ${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>."}
1843
1991
  - PASS only when the approved plan is fully satisfied with no blocking unresolved risk.
1844
- - PARTIAL PASS when implementation appears mostly plan-compliant but manual/visual/browser verification remains or evidence is incomplete without a concrete repairable issue.
1845
- - FAIL only for concrete missing requirements, unexpected changes, regressions, broken checks, or unsafe/out-of-scope work that needs repair.
1992
+ - 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.
1993
+ - 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.
1994
+ - PARTIAL PASS is only for genuinely human-only verification (visual design approval, subjective UX assessment, external services you cannot access) after all automatable evidence has been gathered. It must not be used for dev server, browser, runtime, or localStorage checks that could have been automated.
1846
1995
  - Manual visual-verification caveats alone are not repairable failures; classify them as PARTIAL PASS and recommend manual QA/revalidation, not repair.
1847
- - If PARTIAL PASS includes concrete repairable issues in code, content, citations, sources, generated files, indexes, metadata, or validation artifacts, list them clearly under Missing Requirements or Recommended Next Action so repair mode can address them.
1996
+ - If concrete repairable issues remain in code, content, citations, sources, generated files, indexes, metadata, artifacts, or validation artifacts, mark Concrete Repairable Issue: yes, list them clearly under Missing Requirements or Recommended Next Action, and prefer FAIL over PARTIAL PASS.
1848
1997
  - Evidence gaps are not repairable defects unless you identify a concrete missing requirement or artifact. Mark Evidence Gap: yes and Concrete Repairable Issue: no when the correct next action is evidence collection or revalidation rather than repair.
1849
1998
 
1850
1999
  Project rules priority:
@@ -1883,6 +2032,10 @@ yes/no and short reason
1883
2032
  yes/no and exact missing evidence
1884
2033
  ## Manual Verification Required
1885
2034
  yes/no and exact manual check
2035
+ ## Automated Evidence Completed
2036
+ What runtime/browser/build/test evidence was verified automatically.
2037
+ ## Truly Manual Evidence Remaining
2038
+ Only genuinely non-automatable human-only checks, not checks that could have been automated.
1886
2039
  ## Missing Requirements
1887
2040
  ## Unexpected Changes
1888
2041
  ## Regression Risks
@@ -1913,7 +2066,10 @@ You are in PI WORKFLOW REPAIR MODE.
1913
2066
 
1914
2067
  ${professionalOutputGuidance("Plan repair")}
1915
2068
 
1916
- Approved plan remains the execution contract. Repair only concrete validator-identified failures below. Do not expand scope. Do not commit. Do not push. Do not deploy. Do not change secrets, auth/session files, database schema/data, or environment files unless the user explicitly approved that exact repair. Repair mode cannot declare the work PASS; only a subsequent validator/revalidator can do that. If the validation finding is only a manual/visual/browser verification caveat or says no automated repair is needed, do not make changes; summarize that manual QA/revalidation is required.
2069
+ Diagram guidance:
2070
+ ${workflowMermaidGuidance()}
2071
+
2072
+ Approved plan remains the execution contract. Repair only concrete validator-identified failures below. Do not expand scope. Do not commit. Do not push. Do not deploy. Do not change secrets, auth/session files, database schema/data, or environment files unless the user explicitly approved that exact repair. Repair mode cannot declare the work PASS; only a subsequent validator/revalidator can do that. If no safe concrete edit is appropriate because the finding involves only manual/visual/browser verification or an evidence gap, produce a no-op repair summary recommending revalidation. Do not make unsafe or out-of-scope edits solely to produce file changes.
1917
2073
 
1918
2074
  Repair retry: ${state.currentValidationRetry ?? 0}/${workflowMaxValidationRetries(state, settings)} per plan
1919
2075
  Workflow repair retry: ${workflowValidationRetryCount(state)}/${workflowMaxValidationRetriesTotal(state, settings)} total
@@ -1930,12 +2086,12 @@ Sub-agent repair policy:
1930
2086
  - ${subagentGuidance}
1931
2087
  ${requiredSubagentPreflightSection(preflightBlock)}
1932
2088
  - 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.
2089
+ - 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.
1933
2090
  - ${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>."}
1934
2091
  - Do not re-grade validation. Do not claim PASS/FAIL for repaired work; recommend revalidation.
1935
2092
  - If no concrete repairable issue exists, perform no-op repair summary and recommend manual verification or /plan revalidate.
1936
2093
 
1937
- Diagram guidance:
1938
- ${workflowMermaidGuidance()}
2094
+ ${subagentCapabilityTable()}
1939
2095
 
1940
2096
  Web research guidance:
1941
2097
  ${workflowRuntimeWebResearchGuidance()}
@@ -1975,6 +2131,8 @@ function reviewerPrompt(state: WorkflowState, settings = loadWorkflowSettings(),
1975
2131
  : "Reviewer sub-agents are strongly encouraged. Use them to challenge scope, files, risk, and validation plan; skip only for trivial review and explain why.";
1976
2132
  return `You are in PI WORKFLOW REVIEWER MODE.
1977
2133
 
2134
+ CRITICAL: Call workflow_review_result as your FIRST action in this turn. Do not output any text, analysis, or diagrams before the tool call. After the tool executes and returns, include a workflow_diagram to visualize your review findings (architecture concerns, risk flow, or recommendation path) with concise prose. Place the diagram inline -- not batched at the end.
2135
+
1978
2136
  ${professionalOutputGuidance("Plan review")}
1979
2137
 
1980
2138
  Use read-only tools only. Do not edit, write, or run bash. Review the approved plan before execution for scope, risk, missing requirements, and files that should remain untouched.
@@ -1998,23 +2156,41 @@ ${requiredSubagentPreflightSection(preflightBlock)}
1998
2156
  Diagram guidance:
1999
2157
  ${workflowMermaidGuidance()}
2000
2158
 
2159
+ Review checklist:
2160
+ - Plan scope is clear, bounded, and aligned with the user's request.
2161
+ - Implementation steps are ordered correctly with no circular dependencies.
2162
+ - Required files are identified and files to avoid are listed.
2163
+ - Validation strategy covers all deliverables with concrete acceptance criteria.
2164
+ - Risk assessment covers security, data loss, breaking changes, and deployment concerns.
2165
+ - The plan does not authorize destructive, secret, auth/session/log/runtime-state, database, deployment, push, or out-of-scope work without explicit approval.
2166
+ - Test and build verification is included where applicable.
2167
+
2001
2168
  Approved plan:
2002
2169
  ${state.approvedPlan}
2003
2170
 
2004
2171
  Output exactly:
2005
2172
  # Reviewer Report
2006
2173
  ## Verdict
2007
- PASS, NOTES, NEEDS REPAIR, FAIL, or BLOCKED
2008
-
2009
- Use PASS when the plan is approved to proceed. Do not write APPROVED, APPROVE, OK, or PROCEED as the verdict label.
2174
+ PASS — plan is complete, safe, properly scoped, and ready for execution.
2175
+ NOTES — plan is sound with non-blocking observations for the executor.
2176
+ NEEDS REPAIR plan has concrete gaps (missing steps, unclear files, weak validation, scope creep, risks not addressed).
2177
+ FAIL — plan has serious blockers (safety violations, missing security constraints, broken dependencies, impossible steps).
2178
+ BLOCKED — plan cannot proceed without external resolution.
2179
+
2180
+ Do not write APPROVED, APPROVE, OK, or PROCEED as the verdict label.
2181
+
2182
+ Verdict criteria:
2183
+ - PASS only when: all checklist items are satisfied and no repairable issues remain.
2184
+ - NOTES when: minor observations exist (suggested file order, additional test ideas, optional improvements).
2185
+ - NEEDS REPAIR when: concrete missing requirements, unclear scope boundaries, insufficient validation, or unaddressed risks.
2186
+ - FAIL when: safety/security violations, circular dependencies, impossible steps, or work that exceeds approved scope without authorization.
2187
+ - BLOCKED when: plan requires unavailable resources or external dependencies that cannot be resolved by repair.
2010
2188
  ## Reason
2011
2189
  ## Scope Risks
2012
2190
  ## Missing Information
2013
2191
  ## Files To Be Careful With
2014
2192
  ## Required Plan Revisions
2015
- ## Recommended Execution Notes
2016
-
2017
- Use PASS only when execution may safely start. Use NOTES for non-blocking observations. Use NEEDS REPAIR when the plan should be repaired before execution. Use FAIL or BLOCKED only for serious blockers that should not auto-advance.`;
2193
+ ## Recommended Execution Notes`;
2018
2194
  }
2019
2195
 
2020
2196
  // ── Help text ────────────────────────────────────────────────────
@@ -2429,6 +2605,7 @@ const PLAN_COMPLETIONS = [
2429
2605
  { value: "revalidate", label: "revalidate", description: "Revalidate the approved plan." },
2430
2606
  { value: "archive", label: "archive", description: "Archive the current plan and return to ready Plan Mode." },
2431
2607
  { value: "abandon", label: "abandon", description: "Archive/abandon the current plan and return to ready Plan Mode." },
2608
+ { value: "complete", label: "complete", description: "Complete the plan workflow when validation has passed." },
2432
2609
  { value: "cancel", label: "cancel", description: "Cancel Plan Mode and clear active plan state." },
2433
2610
  { value: "exit", label: "exit", description: "Exit Plan Mode." },
2434
2611
  ];
@@ -2516,7 +2693,7 @@ function missionPhaseFromStatus(status?: MissionState["status"]): string {
2516
2693
  return workflowDisplayValue(status);
2517
2694
  }
2518
2695
 
2519
- function workflowDisplayMode(state: WorkflowState): string {
2696
+ function workflowDisplayMode(state: WorkflowState, mission?: MissionState | null): string {
2520
2697
  const labels: Partial<Record<WorkflowState["mode"], string>> = {
2521
2698
  idle: "IDLE",
2522
2699
  standard: "STANDARD",
@@ -2552,7 +2729,23 @@ function workflowDisplayMode(state: WorkflowState): string {
2552
2729
  mission_blocked: "BLOCKED",
2553
2730
  mission_stopped: "STOPPED",
2554
2731
  };
2555
- return labels[state.mode] ?? workflowDisplayText(state.mode).toUpperCase();
2732
+ const label = labels[state.mode];
2733
+ if (label) {
2734
+ if (state.mode === "validated") {
2735
+ if (state.validationVerdict === "PASS") return "COMPLETED";
2736
+ if (state.validationVerdict === "PARTIAL PASS" && state.concreteRepairableIssue === false) return "COMPLETED";
2737
+ return "BLOCKED";
2738
+ }
2739
+ if (state.mode === "mission_plan_ready" && mission) {
2740
+ if (mission.currentStep === "reviewer") return "REVIEWING";
2741
+ if (mission.reviewRepairInProgress) return "REPAIRING";
2742
+ const v = mission.reviewerVerdict;
2743
+ if (v === "NEEDS REPAIR" || v === "FAIL" || v === "BLOCKED") return "REVIEW BLOCKED";
2744
+ if (v === "PASS" || v === "NOTES") return "REVIEWED";
2745
+ }
2746
+ return label;
2747
+ }
2748
+ return workflowDisplayText(state.mode).toUpperCase();
2556
2749
  }
2557
2750
 
2558
2751
  function workflowFooterModeToken(state: WorkflowState): string {
@@ -2755,7 +2948,7 @@ Forced Policy Rule: forced is a hard requirement and uses the Maximum / Forced t
2755
2948
  }
2756
2949
 
2757
2950
  function renderPermissionSummary(settings: ReturnType<typeof loadWorkflowSettings>): string {
2758
- const repoLock = settings.safety.repoLockEnabled === true ? "enabled for this project - built-in path tools are scoped to the current repository plus the Pi runtime directory for installed tools/skills; bash uses conservative path guardrails; sub-agent child processes are guided but not OS-sandboxed" : "disabled for this project - tools may access paths allowed by Pi and the active process permissions";
2951
+ const repoLock = settings.safety.repoLockEnabled === true ? "enabled for this project - normal file tools, conservative bash path checks, and sub-agents are scoped to the current repository; project .pi is readable for context but protected from normal edits; sub-agent child processes inherit the repository boundary but are not OS-sandboxed" : "disabled for this project - tools may access paths allowed by Pi and the active process permissions";
2759
2952
  return `Repo Lock: ${repoLock}
2760
2953
  Main Plan Permissions: read-only; bash ${settings.safety.disableBashInPlanMode === false ? "allowed for safe commands" : "blocked"}; edit/write blocked
2761
2954
  Main Execution Permissions: read/edit/write/safe bash; destructive bash ${settings.safety.blockDestructiveCommands !== false ? "blocked" : "allowed by setting"}
@@ -2893,6 +3086,7 @@ Standard Model Source: ${standardModelSourceLabel(standardModelSource(settings))
2893
3086
  Use Shared Executor Model: ${settings.standard.useSharedExecutorModel !== false ? "enabled" : "disabled"}
2894
3087
  Use Standard-Specific Models: ${settings.standard.useStandardSpecificModels === true ? "enabled" : "disabled"}
2895
3088
  Standard Model Role: ${workflowRoleLabel(effectiveStandardModelRole(settings))}
3089
+ Standard Token Budget: ${(settings.standard.maxTokens ?? 0) === 0 ? "unlimited" : settings.standard.maxTokens!.toLocaleString()}
2896
3090
 
2897
3091
  ## Plan Mode Flow
2898
3092
  Planning Depth: ${settings.planning.depth}
@@ -2903,8 +3097,11 @@ Clarification Timing: ${settings.planning.clarificationTiming ?? "after_initial_
2903
3097
  Clarification Quality Gate: ${settings.planning.clarificationQualityGate !== false ? "enabled" : "disabled"}
2904
3098
  Allow Clarification Without Analysis: ${settings.planning.allowClarificationWithoutAnalysis === true ? "enabled" : "disabled"}
2905
3099
  Use Sub-agents Before Clarification: ${settings.planning.useSubagentsBeforeClarification !== false ? "enabled" : "disabled"}
2906
- Plan Progress Enabled: ${workflow.planProgressEnabled !== false ? "enabled" : "disabled"}
2907
- Plan Runtime Enabled: ${workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}
3100
+ Progress Tracking: ${workflow.planProgressEnabled !== false ? "enabled" : "disabled"}
3101
+ Show Runtime Timer: ${workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}
3102
+ Progress Bar Display: ${workflow.planShowProgressBar !== false ? "graphical" : "numeric"}
3103
+ Plan Runtime Budget: ${(settings.planning.maxRuntimeHours ?? 0) === 0 ? "unlimited" : settings.planning.maxRuntimeHours!.toLocaleString() + " hours"}
3104
+ Plan Token Budget: ${(settings.planning.maxTokens ?? 0) === 0 ? "unlimited" : settings.planning.maxTokens!.toLocaleString()}
2908
3105
  Require Approval Before Execution: ${(workflow.requireApprovalBeforeExecution ?? workflow.requirePlanApprovalBeforeExecute) !== false ? "enabled" : "disabled"}
2909
3106
  Require Approval Per Step: ${workflow.requireApprovalPerStep === true ? "enabled" : "disabled"}
2910
3107
  Validate After Each Step: ${workflow.validateAfterEachStep === true ? "enabled" : "disabled"}
@@ -2961,8 +3158,8 @@ Allow Full Auto: ${settings.missions.allowFullAuto ? "enabled" : "disabled"}
2961
3158
  Auto Run After Approval: ${settings.missions.autoRunAfterApproval === true ? "enabled" : "disabled"}
2962
3159
  Offer Reviewer Before Approval: ${settings.missions.offerReviewerBeforeApprove === true ? "enabled" : "disabled"}
2963
3160
  Auto Run Reviewer Before Approval: ${settings.missions.autoRunReviewerBeforeApprove === true ? "enabled" : "disabled"}
2964
- Auto Repair Mission Review Failures: ${settings.missions.autoRepairReviewFailures === true ? "enabled" : "disabled"}
2965
- Mission Review Retry Mode: ${settings.missions.reviewRetryMode ?? "off"}
3161
+ Auto Repair Mission Review Failures: ${settings.missions.autoRepairReviewFailures !== false ? "enabled" : "disabled"}
3162
+ Mission Review Retry Mode: ${settings.missions.reviewRetryMode === "off" ? "off" : (settings.missions.reviewRetryMode ?? "safe_only (default)")}
2966
3163
  Max Mission Review Retries: ${settings.missions.maxReviewRetriesPerMission ?? 0}
2967
3164
  Continue Across Milestones: ${settings.missions.continueAcrossMilestones !== false ? "enabled" : "disabled"}
2968
3165
  Pause Between Milestones: ${settings.missions.pauseBetweenMilestones === true ? "enabled" : "disabled"}
@@ -3000,6 +3197,7 @@ Progress Widget: ${settings.missions.progressWidgetEnabled !== false ? "enabled"
3000
3197
  Progress Output Mode: ${settings.missions.progressOutputMode ?? "compact"}
3001
3198
  Progress Bar Display: ${settings.missions.showProgressBar !== false ? "enabled" : "disabled"}
3002
3199
  Max Runtime Hours: ${settings.missions.maxRuntimeHours}
3200
+ Mission Token Budget: ${(settings.missions.maxTokens ?? 0) === 0 ? "unlimited" : settings.missions.maxTokens!.toLocaleString()}
3003
3201
  Checkpoint Interval Minutes: ${settings.missions.checkpointIntervalMinutes}
3004
3202
  Mission Heartbeat: ${settings.missions.heartbeatEnabled !== false ? "enabled" : "disabled"}
3005
3203
  Mission Watchdog: ${settings.missions.watchdogEnabled === true ? "enabled" : "disabled"}
@@ -3073,15 +3271,7 @@ Parallel Edit Conflict Protection Required: ${settings.subagents.requireParallel
3073
3271
  Parallel File Write Enforcement: partial - settings guide prompts and CLI safety gates; main workflow writes remain serialized unless scoped conflict protection is implemented and enabled.
3074
3272
  Note: Parallel File Edits controls simultaneous file writes only. It must not disable multiple read-only/planning/execution/review/validation agents.
3075
3273
 
3076
- ## Global Safety
3077
- Repo Lock (Project): ${settings.safety.repoLockEnabled === true ? "enabled" : "disabled"}
3078
- Repo Lock Scope: project override only; toggling writes to the active repo .pi/workflow-settings.json; file tools are limited to the active repo plus Pi runtime tools/skills/prompts
3079
- Disable Bash In Plan Mode: ${settings.safety.disableBashInPlanMode !== false ? "enabled" : "disabled"}
3080
- Disable Bash In Validator Mode: ${settings.safety.disableBashInValidatorMode !== false ? "enabled" : "disabled"}
3081
- Block Destructive Commands: ${settings.safety.blockDestructiveCommands !== false ? "enabled" : "disabled"}
3082
-
3083
- ## Workflow Permissions
3084
- ${renderPermissionSummary(settings)}
3274
+ ${renderSafetySettings(settings)}
3085
3275
 
3086
3276
  ## Compaction
3087
3277
  Compaction Mode: ${compactionModeLabel(settings.context.compactionMode)}
@@ -3101,6 +3291,19 @@ ${workflowCompactionCheckRuntimeStatus()}
3101
3291
  Last Custom Compaction Status: ${customCompactionRuntimeStatus()}`;
3102
3292
  }
3103
3293
 
3294
+ function renderSafetySettings(settings: ReturnType<typeof loadWorkflowSettings>): string {
3295
+ return `## Global Safety
3296
+ Repo Lock (Project): ${settings.safety.repoLockEnabled === true ? "enabled" : "disabled"}
3297
+ Repo Lock Scope: project override only; toggling writes to the active repo .pi/workflow-settings.json; normal file tools and sub-agents are limited to the active repo, while protected project .pi paths are read-only through normal tools
3298
+ Disable Bash In Plan Mode: ${settings.safety.disableBashInPlanMode !== false ? "enabled" : "disabled"}
3299
+ Disable Bash In Validator Mode: ${settings.safety.disableBashInValidatorMode !== false ? "enabled (unsafe bash blocked; safe evidence bash allowed)" : "disabled"}
3300
+ Block Destructive Commands: ${settings.safety.blockDestructiveCommands !== false ? "enabled" : "disabled"}
3301
+ Allow Package Install In Execution: ${settings.safety.allowPackageInstallInExecution !== false ? "enabled" : "disabled"}
3302
+
3303
+ ## Workflow Permissions
3304
+ ${renderPermissionSummary(settings)}`;
3305
+ }
3306
+
3104
3307
  function formatDurationMs(ms: number): string {
3105
3308
  const safe = Math.max(0, ms);
3106
3309
  const days = Math.floor(safe / 86_400_000);
@@ -3243,15 +3446,24 @@ function planRuntimeSummaryLine(state: WorkflowState): string {
3243
3446
  }
3244
3447
 
3245
3448
  function planValidationState(state: WorkflowState): PlanValidationStatus {
3246
- if (state.planProgress?.validationStatus) return state.planProgress.validationStatus;
3247
3449
  if (state.mode === "validating" || state.mode === "revalidating") return "running";
3450
+ if (state.planProgress?.validationStatus && state.planProgress.validationStatus !== "unknown") return state.planProgress.validationStatus;
3248
3451
  if (state.validationVerdict === "PASS") return "pass";
3249
- if (state.validationVerdict === "PARTIAL PASS") return "unknown";
3452
+ if (state.validationVerdict === "PARTIAL PASS") return "partial pass";
3250
3453
  if (state.validationVerdict === "FAIL") return "fail";
3251
3454
  if (state.validationVerdict === "UNKNOWN") return "unknown";
3252
3455
  return "pending";
3253
3456
  }
3254
3457
 
3458
+ function planLastValidationLabel(state: WorkflowState): string {
3459
+ const progressLast = state.planProgress?.lastValidationStatus;
3460
+ if (progressLast && progressLast !== "unknown") return progressLast;
3461
+ if (state.validationVerdict === "PASS") return "pass";
3462
+ if (state.validationVerdict === "PARTIAL PASS") return "partial pass";
3463
+ if (state.validationVerdict === "FAIL") return "fail";
3464
+ return progressLast ?? "none";
3465
+ }
3466
+
3255
3467
  function lifecycleStatusForState(state: WorkflowState): PlanProgressState["lifecycleStatus"] {
3256
3468
  if (state.mode === "planning") return "planning";
3257
3469
  if (state.mode === "awaiting_clarification") return "awaiting_clarification";
@@ -3372,8 +3584,13 @@ function mergePlanProgress(state: WorkflowState, settings: ReturnType<typeof loa
3372
3584
  const current = state.planProgress ?? createPlanProgress(planText, settings, lifecycleStatusForState(state));
3373
3585
  const hasPlanText = Boolean(planText?.trim());
3374
3586
  const extractedSteps = hasPlanText ? extractApprovedPlanSteps(planText) : [];
3375
- const currentMatchesPlan = hasPlanText && current.steps.length === extractedSteps.length && current.steps.every((step, index) => step.title === extractedSteps[index]?.title);
3376
- const hydratedSteps = current.steps.length && (!hasPlanText || currentMatchesPlan) ? current.steps : extractedSteps;
3587
+ const sameStepCount = current.steps.length === extractedSteps.length && current.steps.length > 0;
3588
+ const titlesMatch = sameStepCount && current.steps.every((step, index) => step.title === extractedSteps[index]?.title);
3589
+ const hydratedSteps = current.steps.length && (!hasPlanText || titlesMatch)
3590
+ ? current.steps
3591
+ : sameStepCount
3592
+ ? current.steps.map((step, index) => ({ ...step, title: extractedSteps[index]?.title ?? step.title }))
3593
+ : extractedSteps;
3377
3594
  const steps = patch.steps ?? hydratedSteps;
3378
3595
  const lifecycleStatus = patch.lifecycleStatus ?? (current.lifecycleStatus === "blocked" && planReviewBlockReason(state) ? "blocked" : lifecycleStatusForState(state));
3379
3596
  const validationStatus = patch.validationStatus ?? planValidationState(state);
@@ -3406,7 +3623,7 @@ function planNextActionText(state: WorkflowState): string {
3406
3623
  if (state.mode === "executing" || state.mode === "executed") return "validator";
3407
3624
  if (state.mode === "validating" || state.mode === "revalidating") return "validation result";
3408
3625
  if (state.mode === "repairing") return "repair executor then validator";
3409
- if (state.mode === "validated") return state.validationVerdict === "PASS" ? "new planning request" : state.validationVerdict === "PARTIAL PASS" ? (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? "") === "repairable" ? "repair/revalidate or revise" : "manual verification or revalidate") : "repair/revalidate or revise";
3626
+ if (state.mode === "validated") return state.validationVerdict === "PASS" ? "new planning request" : state.validationVerdict === "PARTIAL PASS" ? (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? "", { concreteRepairableIssue: state.concreteRepairableIssue, manualVerificationRequired: state.manualVerificationRequired }) === "repairable" ? "repair/revalidate or revise" : "manual verification or revalidate") : "repair/revalidate or revise";
3410
3627
  return "workflow";
3411
3628
  }
3412
3629
 
@@ -3475,6 +3692,10 @@ function planProgressPercent(progress?: PlanProgressState): number {
3475
3692
  function planProgressBar(progress: PlanProgressState | undefined, settings: ReturnType<typeof loadWorkflowSettings>): string {
3476
3693
  if (!workflowPlanProgressEnabled(settings)) return "disabled";
3477
3694
  if (!progress?.steps.length) return "[active]";
3695
+ if (settings.workflow.planShowProgressBar === false) {
3696
+ const pct = planProgressPercent(progress);
3697
+ return pct > 0 ? `${pct}%` : "[active]";
3698
+ }
3478
3699
  const percent = planProgressPercent(progress);
3479
3700
  const cells = progress.steps.map((step, index) => {
3480
3701
  if (step.status === "completed" || step.status === "skipped") return "■■■";
@@ -3610,7 +3831,7 @@ function missionReadyWidget(): string[] {
3610
3831
  }
3611
3832
 
3612
3833
  function missionStateFallbackWidget(state: WorkflowState, mission: MissionState | undefined, settings: ReturnType<typeof loadWorkflowSettings>): string[] {
3613
- const status = mission ? workflowHeaderState(mission.status) : workflowDisplayMode(state);
3834
+ const status = mission ? workflowHeaderState(mission.status) : workflowDisplayMode(state, mission);
3614
3835
  const missionId = mission?.id ?? state.activeMissionId ?? "none";
3615
3836
  const next = mission?.nextAction ?? (state.mode === "mission_blocked" ? "use /mission status or /mission resume" : "use /mission status for next action");
3616
3837
  const repairRetry = mission?.currentValidationRetry ?? 0;
@@ -3717,6 +3938,7 @@ function classifyStandardWork(task: string | undefined, answerSummary?: string):
3717
3938
  if (/\b(validate|validation|verify|test|lint|build|typecheck|quality review|review)\b/.test(text)) return { phase: "Validation", kind: "validation" };
3718
3939
  if (/\b(edit|write|modify|change|implement|add|remove|delete|refactor|commit|push|deploy|migrate|apply|update files?|create files?)\b/.test(text)) return { phase: "Execution", kind: "mutation" };
3719
3940
  if (/\b(read.?only|scan|inspect|summari[sz]e|status|action items?|checklist|report|docs? only|no code|do not edit|without editing)\b/.test(text)) return { phase: "Planning", kind: "read_only" };
3941
+ recordWorkflowInternalEvent(undefined, `Standard work classifier fell back to default (read_only Planning) for: ${(task ?? "").slice(0, 120)}`);
3720
3942
  return { phase: "Planning", kind: "read_only" };
3721
3943
  }
3722
3944
 
@@ -4136,6 +4358,9 @@ function standardPrompt(state: WorkflowState, settings: ReturnType<typeof loadWo
4136
4358
 
4137
4359
  ${professionalOutputGuidance("Standard Mode")}
4138
4360
 
4361
+ Diagram guidance:
4362
+ ${workflowMermaidGuidance()}
4363
+
4139
4364
  Standard Mode is a peer Workflow Suite mode for direct active work. It uses shared Workflow Suite capabilities without requiring Plan Mode approval gates or Mission Mode milestones.
4140
4365
 
4141
4366
  Standard presentation matrix:
@@ -4158,9 +4383,6 @@ Rules:
4158
4383
  Standard To Do policy:
4159
4384
  ${todoInstruction}
4160
4385
 
4161
- Diagram guidance:
4162
- ${workflowMermaidGuidance()}
4163
-
4164
4386
  Web research guidance:
4165
4387
  ${workflowRuntimeWebResearchGuidance()}`;
4166
4388
  }
@@ -4249,7 +4471,7 @@ function planProgressWidget(state: WorkflowState, settings: ReturnType<typeof lo
4249
4471
  `Current: ${workflowWidgetSummaryText("Planning", 56)}`,
4250
4472
  `Next: ${workflowWidgetSummaryText(progress?.nextAction ?? planNextActionText(state), 56)}`,
4251
4473
  ];
4252
- const validationLine = planValidationRelevant(state) ? `Validation: ${planValidationState({ ...state, planProgress: progress })} | Last: ${progress?.lastValidationStatus ?? state.validationVerdict ?? "none"}` : undefined;
4474
+ const validationLine = planValidationRelevant(state) ? `Validation: ${planValidationState({ ...state, planProgress: progress })} | Last: ${planLastValidationLabel(state)}` : undefined;
4253
4475
  const repairLine = planRepairRelevant(state) ? `Repair Retry: ${state.currentValidationRetry ?? 0} of ${workflowMaxValidationRetries(state, settings)} | Repair: ${widgetRepairDetail(state.lastRepairStatus, state.lastRepairAttempt)}` : undefined;
4254
4476
  if (validationLine || repairLine) lines.push(joinWidgetParts(validationLine, repairLine));
4255
4477
  return lines;
@@ -4269,7 +4491,7 @@ function planProgressWidget(state: WorkflowState, settings: ReturnType<typeof lo
4269
4491
  `Current: ${workflowWidgetSummaryText(planCurrentActionText(state), 56)}`,
4270
4492
  `Next: ${workflowWidgetSummaryText(planNextActionText(state), 56)}`,
4271
4493
  ];
4272
- const validationLine = planValidationRelevant(state) ? `Validation: ${planValidationState(state)} | Last: ${progress?.lastValidationStatus ?? state.validationVerdict ?? "none"}` : undefined;
4494
+ const validationLine = planValidationRelevant(state) ? `Validation: ${planValidationState(state)} | Last: ${planLastValidationLabel(state)}` : undefined;
4273
4495
  const repairLine = planRepairRelevant(state) ? `Repair Retry: ${state.currentValidationRetry ?? 0} of ${workflowMaxValidationRetries(state, settings)} | Repair: ${widgetRepairDetail(state.lastRepairStatus, state.lastRepairAttempt)}` : undefined;
4274
4496
  if (validationLine || repairLine) lines.push(joinWidgetParts(validationLine, repairLine));
4275
4497
  return lines;
@@ -4281,8 +4503,12 @@ function planProgressWidget(state: WorkflowState, settings: ReturnType<typeof lo
4281
4503
  `Status: ${progress.lifecycleStatus} | Steps: ${stepNumber} of ${progress.steps.length}`,
4282
4504
  `Current: ${planCurrentStepLabel(progress, workflowWidgetSummaryText(planCurrentActionText({ ...state, planProgress: progress }), 56))}`,
4283
4505
  `Next: ${planNextStepLabel(progress, workflowWidgetSummaryText(progress.nextAction ?? planNextActionText(state), 56))}`,
4284
- `Validation: ${planValidationState(state)} | Last: ${progress.lastValidationStatus ?? state.validationVerdict ?? "none"} | Repair Retry: ${progress.repairRetry ?? state.currentValidationRetry ?? 0} of ${progress.maxRepairRetries ?? workflowMaxValidationRetries(state, settings)} | Repair: ${widgetRepairDetail(repairStatus, state.lastRepairAttempt)}`,
4506
+ `Validation: ${planValidationState(state)} | Last: ${planLastValidationLabel(state)} | Repair Retry: ${progress.repairRetry ?? state.currentValidationRetry ?? 0} of ${progress.maxRepairRetries ?? workflowMaxValidationRetries(state, settings)} | Repair: ${widgetRepairDetail(repairStatus, state.lastRepairAttempt)}`,
4285
4507
  ];
4508
+ if (settings.ui.debugPlanStepTracking && progress?.steps.length) {
4509
+ const stepSummary = progress.steps.map(s => `${s.id}=${s.status}`).join(" ");
4510
+ lines.push(`[DEBUG] steps: ${stepSummary} | idx=${progress.currentStepIndex} | lifecycle=${progress.lifecycleStatus} | pct=${planProgressPercent(progress)}%`);
4511
+ }
4286
4512
  return lines;
4287
4513
  }
4288
4514
 
@@ -5565,12 +5791,12 @@ function enabledRoleLine(role: WorkflowRole, settings: ReturnType<typeof loadWor
5565
5791
 
5566
5792
  function workflowWidget(state: WorkflowState, cwd: string): string[] {
5567
5793
  const settings = loadWorkflowSettings(cwd);
5568
- const lines = [`Mode: ${workflowDisplayMode(state)}`];
5794
+ const mission = isMissionWorkflowMode(state) ? activeMissionForUi(state) : undefined;
5795
+ const lines = [`Mode: ${workflowDisplayMode(state, mission)}`];
5569
5796
  if (isStandardWorkflowMode(state)) {
5570
5797
  lines.push(`Standard: ${state.standardTodo?.items?.length ? `${standardTodoDoneCount(state.standardTodo)} of ${state.standardTodo.items.length} To Do done` : "ready"}`);
5571
5798
  lines.push(standardModelDisplayLine(settings));
5572
5799
  } else if (isMissionWorkflowMode(state)) {
5573
- const mission = activeMissionForUi(state);
5574
5800
  lines.push(`Mission: ${missionTopStatus(mission)} | Mission ID: ${mission?.id ?? ""}`);
5575
5801
  const role = workflowRoleForState(state);
5576
5802
  const roleLine = role ? enabledRoleLine(role, settings) : undefined;
@@ -5799,7 +6025,7 @@ function setWorkflowProgressWidgets(ctx: ExtensionContext, state: WorkflowState,
5799
6025
  const missionProgressLines = progressHeaderLines("MISSION", missionHeaderStatus, missionProgressBar(mission, settings), missionRuntimeSummaryLine(mission));
5800
6026
  const missionLines = colorWorkflowProgressWidgetLines([
5801
6027
  ...missionProgressLines,
5802
- `Status: ${workflowDisplayMode(state).toLowerCase()} | Milestone: ${mission.milestones.length ? `${Math.min(mission.currentMilestoneIndex + 1, mission.milestones.length)} of ${mission.milestones.length}` : "0 of 0"}`,
6028
+ `Status: ${workflowDisplayMode(state, mission).toLowerCase()} | Milestone: ${mission.milestones.length ? `${Math.min(mission.currentMilestoneIndex + 1, mission.milestones.length)} of ${mission.milestones.length}` : "0 of 0"}`,
5803
6029
  `Current: ${workflowWidgetSummaryText(currentText, 56)}`,
5804
6030
  `Next: ${workflowActionSummaryText(nextText, 56)}`,
5805
6031
  `Validation: ${workflowDisplayValue(missionCurrentValidationState(mission))} | Last: ${workflowDisplayValue(mission.lastValidationResult ?? "none")} | Repair Retry: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} | Repair: ${workflowDisplayValue(currentMissionRepairStatus(mission))}`,
@@ -5879,11 +6105,20 @@ function setWorkflowUi(ctx: ExtensionContext, state: WorkflowState, activeSubage
5879
6105
  }
5880
6106
 
5881
6107
  if (isMissionWorkflowMode(state) && workflowWidgetVisible(settings, "missionTop")) {
6108
+ const mission = activeMissionForUi(state);
5882
6109
  const text = state.mode === "awaiting_mission_input"
5883
6110
  ? (state.lastCompletedMissionSummary ? "MISSION MODE READY | enter your next mission goal, or use /mission resume" : "MISSION MODE ACTIVE | enter your mission goal, or use /mission exit to leave")
5884
6111
  : state.mode === "mission_awaiting_clarification"
5885
6112
  ? "MISSION CLARIFICATION NEEDED | answer below, e.g. 1A, 2C, 3D: custom answer"
5886
- : "MISSION MODE ACTIVE | use /mission status for next action";
6113
+ : state.mode === "mission_plan_ready" && mission?.currentStep === "reviewer"
6114
+ ? "MISSION REVIEW IN PROGRESS | reviewer is checking the milestone plan"
6115
+ : state.mode === "mission_plan_ready" && mission?.reviewRepairInProgress
6116
+ ? "MISSION REPAIRING PLAN | addressing reviewer feedback"
6117
+ : state.mode === "mission_plan_ready" && (mission?.reviewerVerdict === "PASS" || mission?.reviewerVerdict === "NOTES")
6118
+ ? "MISSION PLAN READY | review passed, use /mission approve"
6119
+ : state.mode === "mission_plan_ready" && (mission?.reviewerVerdict === "NEEDS REPAIR" || mission?.reviewerVerdict === "FAIL" || mission?.reviewerVerdict === "BLOCKED")
6120
+ ? "MISSION REVIEW NEEDS REPAIR | use /mission status for next action"
6121
+ : "MISSION MODE ACTIVE | use /mission status for next action";
5887
6122
  const separatorIndex = text.indexOf(" | ");
5888
6123
  const label = separatorIndex >= 0 ? text.slice(0, separatorIndex) : text;
5889
6124
  const detail = separatorIndex >= 0 ? text.slice(separatorIndex + 3) : undefined;
@@ -5930,7 +6165,7 @@ function missionPlanNeedsClarification(text?: string): boolean {
5930
6165
  }
5931
6166
 
5932
6167
  function planOutputWasAborted(text: string | undefined): boolean {
5933
- return /Operation aborted|Subagent was aborted|timed out|stale watchdog stopped/i.test(text ?? "");
6168
+ return /Operation aborted|Subagent was aborted|timed out|stale watchdog stopped|WebSocket\s*error|connection\s*(?:error|lost|closed|refused|reset)|network\s*error|failed to fetch|ECONNREFUSED|ECONNRESET|ETIMEDOUT/i.test(text ?? "");
5934
6169
  }
5935
6170
 
5936
6171
  function planDraftContractProblem(text: string): string | undefined {
@@ -6198,7 +6433,11 @@ function missionCurrentValidationState(mission: MissionState): string {
6198
6433
  if (mission.status === "validating" || mission.status === "revalidating") return "running";
6199
6434
  if (mission.status === "repairing") return mission.lastValidationResult ? mission.lastValidationResult.toLowerCase() : "failed; repairing";
6200
6435
  if (current.status === "completed" || current.status === "skipped") return checkpoint?.validationResult ? checkpoint.validationResult.toLowerCase() : "passed";
6201
- if (current.status === "failed" || mission.status === "blocked" || mission.status === "failed") return checkpoint?.validationResult ? checkpoint.validationResult.toLowerCase() : "failed";
6436
+ if (current.status === "failed" || mission.status === "blocked" || mission.status === "failed") {
6437
+ if (checkpoint?.validationResult) return checkpoint.validationResult.toLowerCase();
6438
+ if (mission.lastValidationResult) return "failed";
6439
+ return "not run";
6440
+ }
6202
6441
  return checkpoint?.validationResult ? checkpoint.validationResult.toLowerCase() : "pending";
6203
6442
  }
6204
6443
 
@@ -6338,7 +6577,7 @@ function maxMissionValidationRetries(mission: MissionState, settings: ReturnType
6338
6577
  }
6339
6578
 
6340
6579
  function maxMissionReviewRetries(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>): number {
6341
- return Math.max(0, Math.min(10, mission.maxReviewRetriesPerMission ?? settings.missions.maxReviewRetriesPerMission ?? 0));
6580
+ return Math.max(0, Math.min(10, mission.maxReviewRetriesPerMission ?? settings.missions.maxReviewRetriesPerMission ?? settings.missions.maxValidationRetriesPerMission ?? 8));
6342
6581
  }
6343
6582
 
6344
6583
  function missionValidationRetryCount(mission: MissionState): number {
@@ -6393,7 +6632,7 @@ function simplePresetActive(settings: ReturnType<typeof loadWorkflowSettings>):
6393
6632
  }
6394
6633
 
6395
6634
  function repairFailureApprovalReason(text: string, options: { retryMode?: string; requireApprovalForDestructiveRepair: boolean; requireApprovalForOutOfScopeRepair: boolean; outOfScopeReason: string; safeOnlyReason: string }): string | undefined {
6396
- const actionable = actionableSafetyText(text);
6635
+ const actionable = actionableSafetyText(text).replace(/\bworkflow_\w+\b/gi, "[tool]");
6397
6636
  const lines = actionable.split(/\n+/).map((line) => line.trim()).filter(Boolean);
6398
6637
  const hasActionableLine = (risk: RegExp, action: RegExp) => lines.some((line) => risk.test(line) && action.test(line));
6399
6638
  const hasDatabaseOperationLine = lines.some((line) => {
@@ -6405,8 +6644,15 @@ function repairFailureApprovalReason(text: string, options: { retryMode?: string
6405
6644
  if (options.requireApprovalForDestructiveRepair && hasActionableLine(/\b(delete|drop|destroy|reset|clean|force|overwrite|destructive|rm\s+-rf|deploy|push)\b/i, /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should|perform|run|execute|apply)\b/i)) return "repair may require destructive, database, deployment, push, or other high-risk action.";
6406
6645
  if (options.requireApprovalForDestructiveRepair && hasDatabaseOperationLine) return "repair may require destructive, database, deployment, push, or other high-risk action.";
6407
6646
  if (hasActionableLine(/\b(secret|token|credential|password|api key|\.env|\.factory|\.cursor)\b/i, /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should)\b/i)) return "repair may require secret, auth, session, or protected runtime file changes.";
6408
- if (hasActionableLine(/\b(auth|session)\b/i, /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should)\b/i) && !/\b(no user session|without a user session|server-to-server with no user session|cookie auth)\b/i.test(actionable)) return "repair may require secret, auth, session, or protected runtime file changes.";
6409
- if (options.requireApprovalForOutOfScopeRepair && /\b(out[- ]of[- ]scope|outside scope|scope expansion|new requirement|unapproved|unrelated)\b/i.test(actionable)) return options.outOfScopeReason;
6647
+ if (hasActionableLine(/\b(auth|session)\b/i, /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should)\b/i) && !/\b(no user session|without a user session|server-to-server with no user session|cookie auth|tool namespace|typed handoff|tool is not available|handoff tool|this session'?s tool)\b/i.test(actionable)) return "repair may require secret, auth, session, or protected runtime file changes.";
6648
+ if (options.requireApprovalForOutOfScopeRepair) {
6649
+ const lines = actionable.split(/\n+/).map((line) => line.trim()).filter(Boolean);
6650
+ const scopeExpansion = lines.some((line) =>
6651
+ /\b(out[- ]of[- ]scope|outside scope|scope expansion|new requirement|unapproved)\b/i.test(line)
6652
+ || (/\bunrelated\b/i.test(line) && /\b(change|modif|refactor|rewrite|replace|updat|creat|add|remov|needs to|must|should|requires?|performs?|execute|apply)\b/i.test(line) && !/\b(no |not |without |avoid |unchanged|preserve|kept |did not|does not)\b/i.test(line))
6653
+ );
6654
+ if (scopeExpansion) return options.outOfScopeReason;
6655
+ }
6410
6656
  if ((options.retryMode ?? "safe_only") === "safe_only" && hasActionableLine(/\b(manual approval|needs approval|ask user|unsafe|permission)\b/i, /\b(repair|fix|change|modify|touch|read|print|expose|update|write|requires?|needs?|must|should|perform|run|execute|apply)\b/i)) return options.safeOnlyReason;
6411
6657
  return undefined;
6412
6658
  }
@@ -6424,13 +6670,20 @@ function validationFailureRequiresApproval(text: string, settings: ReturnType<ty
6424
6670
 
6425
6671
  function repairSummaryRequiresApproval(text: string, settings: ReturnType<typeof loadWorkflowSettings>): string | undefined {
6426
6672
  if (settings.missions.validationRetryMode === "off") return "validationRetryMode=off.";
6427
- const actionable = actionableSafetyText(text);
6673
+ const actionable = actionableSafetyText(text).replace(/\bworkflow_\w+\b/gi, "[tool]");
6428
6674
  const lines = actionable.split(/\n+/).map((line) => line.trim()).filter(Boolean);
6429
6675
  const explicitIncomplete = lines.some((line) => /\b(repair|fix|change|modify|update|write|execute|run|apply)\b/i.test(line) && /\b(requires?|needs?|must|cannot|could not|unable|blocked|approval|permission|unsafe|out[- ]of[- ]scope|outside scope)\b/i.test(line));
6430
6676
  if (!explicitIncomplete) return undefined;
6431
6677
  const highRiskAction = lines.some((line) => /\b(delete|drop|destroy|reset|clean|force|overwrite|destructive|rm\s+-rf|migration|migrate|database|db\b|deploy|push|secret|token|credential|password|api key|auth|session|\.env|\.factory|\.cursor)\b/i.test(line) && /\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));
6432
6678
  if (highRiskAction) return "repair summary requests approval for destructive, protected, or high-risk action.";
6433
- if (settings.missions.requireApprovalForOutOfScopeRepair !== false && /\b(out[- ]of[- ]scope|outside scope|scope expansion|new requirement|unapproved|unrelated)\b/i.test(actionable)) return "repair summary says further work is outside the approved milestone scope.";
6679
+ if (settings.missions.requireApprovalForOutOfScopeRepair !== false) {
6680
+ const lines = actionable.split(/\n+/).map((line) => line.trim()).filter(Boolean);
6681
+ const scopeExpansion = lines.some((line) =>
6682
+ /\b(out[- ]of[- ]scope|outside scope|scope expansion|new requirement|unapproved)\b/i.test(line)
6683
+ || (/\bunrelated\b/i.test(line) && /\b(change|modif|refactor|rewrite|replace|updat|creat|add|remov|needs to|must|should|requires?|performs?|execute|apply)\b/i.test(line) && !/\b(no |not |without |avoid |unchanged|preserve|kept |did not|does not)\b/i.test(line))
6684
+ );
6685
+ if (scopeExpansion) return "repair summary says further work is outside the approved milestone scope.";
6686
+ }
6434
6687
  if ((settings.missions.validationRetryMode ?? "safe_only") === "safe_only" && /\b(manual approval|needs approval|ask user|unsafe|permission)\b/i.test(actionable)) return "repair summary says approval is required before continuing.";
6435
6688
  return undefined;
6436
6689
  }
@@ -6457,6 +6710,7 @@ type RepairRetryDecision = {
6457
6710
  maxRetriesPerWorkflow: number;
6458
6711
  totalRetry: number;
6459
6712
  maxTotalRetries: number;
6713
+ manualOverride?: boolean;
6460
6714
  };
6461
6715
 
6462
6716
  function clampRepairCount(value: unknown, fallback: number, max = 100): number {
@@ -6513,6 +6767,41 @@ function repairRetryGateConfig(settings: ReturnType<typeof loadWorkflowSettings>
6513
6767
  };
6514
6768
  }
6515
6769
 
6770
+ function missionRepairRetryConfig(settings: ReturnType<typeof loadWorkflowSettings>, gate: "review" | "validation" | "finalValidation"): RepairRetryGateConfig {
6771
+ const m = settings.missions;
6772
+ if (gate === "review") {
6773
+ return {
6774
+ autoRepairFailures: m.autoRepairReviewFailures !== false,
6775
+ retryMode: (m.reviewRetryMode === "off" && m.autoRepairReviewFailures !== false) ? "safe_only" : (m.reviewRetryMode === "off" ? "off" : (m.reviewRetryMode ?? "safe_only")) as RepairRetryGateConfig["retryMode"],
6776
+ maxRetriesPerItem: clampRepairCount(m.maxReviewRetriesPerMission, 2, 10),
6777
+ maxRetriesPerWorkflow: clampRepairCount(m.maxReviewRetriesPerMission, 2, 10),
6778
+ pauseAfterFailure: false,
6779
+ requireApprovalForOutOfScopeRepair: m.requireApprovalForOutOfScopeRepair !== false,
6780
+ requireApprovalForDestructiveRepair: m.requireApprovalForDestructiveRepair !== false,
6781
+ };
6782
+ }
6783
+ if (gate === "validation") {
6784
+ return {
6785
+ autoRepairFailures: m.autoRepairValidationFailures !== false,
6786
+ retryMode: (m.validationRetryMode === "off" ? "off" : (m.validationRetryMode ?? "safe_only")) as RepairRetryGateConfig["retryMode"],
6787
+ maxRetriesPerItem: clampRepairCount(m.maxValidationRetriesPerMilestone, 2, 10),
6788
+ maxRetriesPerWorkflow: clampRepairCount(m.maxValidationRetriesPerMission, 4, 100),
6789
+ pauseAfterFailure: m.pauseAfterValidationFailure === true,
6790
+ requireApprovalForOutOfScopeRepair: m.requireApprovalForOutOfScopeRepair !== false,
6791
+ requireApprovalForDestructiveRepair: m.requireApprovalForDestructiveRepair !== false,
6792
+ };
6793
+ }
6794
+ return {
6795
+ autoRepairFailures: m.autoRepairFinalValidationFailures === true,
6796
+ retryMode: (m.autoRepairFinalValidationFailures === true ? "safe_only" : "off") as RepairRetryGateConfig["retryMode"],
6797
+ maxRetriesPerItem: clampRepairCount(m.maxFinalValidationRetries, 2, 10),
6798
+ maxRetriesPerWorkflow: clampRepairCount(m.maxFinalValidationRetries, 2, 10),
6799
+ pauseAfterFailure: false,
6800
+ requireApprovalForOutOfScopeRepair: m.requireApprovalForOutOfScopeRepair !== false,
6801
+ requireApprovalForDestructiveRepair: m.requireApprovalForDestructiveRepair !== false,
6802
+ };
6803
+ }
6804
+
6516
6805
  function workflowMaxRepairTotal(settings: ReturnType<typeof loadWorkflowSettings>): number {
6517
6806
  const repairRetry = (settings.workflow as typeof settings.workflow & { repairRetry?: { maxTotalRetries?: number } }).repairRetry;
6518
6807
  return clampRepairCount(repairRetry?.maxTotalRetries, 6, 100);
@@ -6553,6 +6842,7 @@ function actionableSafetyText(text: string): string {
6553
6842
  return scoped
6554
6843
  .filter((line) => !/^(?:[-*]\s*)?(do not|don't|no\b|not\b|avoid\b|without\b|unchanged\b|preserve\b|kept\b|did not\b|does not\b)/i.test(line))
6555
6844
  .filter((line) => !/\b(no secrets exposed|no secret|do not read|do not print|not touched|not modified|not to touch|files? not to touch|unchanged|preserved|avoided|did not deploy|did not push|no database|no db|no migration|no destructive)\b/i.test(line))
6845
+ .filter((line) => !/\b(reset|clean|force)\b.{0,40}\b(button|function|feature|path|code|form|input|ui|element|component|verified|checked|tested|confirmed|functionality|validation|Enter)\b/i.test(line))
6556
6846
  .join("\n")
6557
6847
  .toLowerCase();
6558
6848
  }
@@ -6572,12 +6862,16 @@ function workflowValidationFailureRequiresApproval(text: string, settings: Retur
6572
6862
  return repairRetryFailureRequiresApproval(text, repairRetryGateConfig(settings, "validation"));
6573
6863
  }
6574
6864
 
6575
- function resolveRepairRetryDecision(args: { gate: RepairRetryGate; verdict: string; report: string; settings: ReturnType<typeof loadWorkflowSettings>; state: WorkflowState; itemRetry: number; workflowRetry: number; requiredContextAvailable: boolean; }): RepairRetryDecision {
6865
+ function resolveRepairRetryDecision(args: { gate: RepairRetryGate; verdict: string; report: string; settings: ReturnType<typeof loadWorkflowSettings>; state: WorkflowState; itemRetry: number; workflowRetry: number; requiredContextAvailable: boolean; manualOverride?: boolean; }): RepairRetryDecision {
6576
6866
  const config = repairRetryGateConfig(args.settings, args.gate);
6577
6867
  const maxTotalRetries = workflowMaxRepairTotal(args.settings);
6578
6868
  const totalRetry = workflowRepairTotalRetryCount(args.state);
6579
6869
  const unsafe = repairRetryFailureRequiresApproval(args.report, config);
6580
6870
  const unknown = args.verdict === "UNKNOWN";
6871
+ const manualBlockers = unknown || !args.requiredContextAvailable || Boolean(unsafe);
6872
+ if (args.manualOverride && !manualBlockers) {
6873
+ return { allowed: true, reason: undefined, gate: args.gate, action: "repair", retry: args.itemRetry + 1, maxRetriesPerItem: Math.max(config.maxRetriesPerItem, args.itemRetry + 1), workflowRetry: args.workflowRetry + 1, maxRetriesPerWorkflow: Math.max(config.maxRetriesPerWorkflow, args.workflowRetry + 1), totalRetry, maxTotalRetries: Math.max(maxTotalRetries, totalRetry + 1), manualOverride: true };
6874
+ }
6581
6875
  const reason = unknown ? `${args.gate} output could not be parsed.`
6582
6876
  : !args.requiredContextAvailable ? `repair blocked: no approved ${args.gate === "review" ? "plan" : "plan"} available.`
6583
6877
  : !config.autoRepairFailures ? `repairRetry.gates.${args.gate}.autoRepairFailures=false.`
@@ -6719,8 +7013,13 @@ function missionReviewPrompt(mission: MissionState, settings = loadWorkflowSetti
6719
7013
  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";
6720
7014
  return `You are in PI MISSION MODE REVIEWER MODE.
6721
7015
 
7016
+ CRITICAL: Call workflow_review_result as your FIRST action in this turn. Do not output any text, analysis, or diagrams before the tool call. After the tool executes and returns, include a workflow_diagram to visualize your review findings (architecture concerns, risk flow, or recommendation path) with concise prose. Place the diagram inline -- not batched at the end.
7017
+
6722
7018
  ${professionalOutputGuidance("Mission review")}
6723
7019
 
7020
+ Diagram guidance:
7021
+ ${workflowMermaidGuidance()}
7022
+
6724
7023
  Use read-only tools only. Do not edit, write, or run bash. Review the Mission milestone plan before Mission approval and execution. Reviewer is not validation. Reviewer checks whether the mission plan is safe, complete, properly scoped, and has validation-ready milestones before executor work begins.
6725
7024
 
6726
7025
  Mission ID: ${mission.id}
@@ -6749,9 +7048,6 @@ ${requiredSubagentPreflightSection(preflightBlock)}
6749
7048
  - ${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>."}
6750
7049
  - The reviewer must not rubber-stamp execution; surface missing requirements before the mission is approved.
6751
7050
 
6752
- Diagram guidance:
6753
- ${workflowMermaidGuidance()}
6754
-
6755
7051
  Review checklist:
6756
7052
  - Milestones are parser-safe, ordered, and scoped to the mission goal.
6757
7053
  - Each milestone has clear objective, steps, acceptance criteria, required evidence, and risks.
@@ -6763,7 +7059,18 @@ Review checklist:
6763
7059
  Output exactly:
6764
7060
  # Review Report
6765
7061
  ## Verdict
6766
- PASS, NOTES, NEEDS REPAIR, FAIL, BLOCKED, or UNKNOWN
7062
+ PASS plan is complete, safe, scoped correctly, and ready for approval.
7063
+ NOTES — plan is acceptable but has non-blocking observations for the executor.
7064
+ NEEDS REPAIR — plan has concrete gaps that should be repaired before approval (missing requirements, unclear milestones, insufficient validation).
7065
+ FAIL — plan has serious issues that block safe execution (missing safety constraints, out-of-scope work, broken dependencies).
7066
+ BLOCKED — plan cannot proceed without external resolution (missing credentials, unavailable services, blocked dependencies).
7067
+
7068
+ Verdict criteria:
7069
+ - PASS only when: no repairable issues remain and the plan is ready for approval.
7070
+ - NOTES when: plan is sound but has minor observations the executor should consider.
7071
+ - NEEDS REPAIR when: milestones lack acceptance criteria, validation plan is weak, scope is unclear, or concrete missing requirements are identified.
7072
+ - FAIL when: plan authorizes destructive/secret/auth/database/deploy/push work without explicit approval, or safety constraints are absent.
7073
+ - BLOCKED when: plan requires unavailable resources or external dependencies that cannot be resolved by repair.
6767
7074
  ## Reason
6768
7075
  ## Mission Plan Coverage
6769
7076
  ## Milestone Quality
@@ -6788,20 +7095,20 @@ function missionRuntimePrompt(mission: MissionState, settings: ReturnType<typeof
6788
7095
  const last = mission.checkpoints[mission.checkpoints.length - 1];
6789
7096
  const policy = settings.subagents.executionPolicy ?? "auto";
6790
7097
  const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Execution", "Mission Execution", preflightBlock);
6791
- return `You are PI MISSION MODE RUNTIME.\n\n${professionalOutputGuidance("Mission runtime")}\n\nMISSION MODE ACTIVE\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nMission status: ${mission.status}\nCurrent milestone index: ${mission.currentMilestoneIndex}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nCurrent milestone objective: ${milestone?.objective ?? "none"}\n\nMilestone steps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nCompleted milestones:\n${completed.map((m) => `- ${m.id}: ${m.title}`).join("\n") || "- none"}\n\nRemaining milestones:\n${remaining.map((m) => `- ${m.id}: ${m.title} (${m.status})`).join("\n") || "- none"}\n\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\nLast stop reason: ${mission.lastStopReason || "none"}\nLast block reason: ${mission.lastBlockReason || "none"}\nNext action: ${mission.nextAction || missionNextActionText(mission, settings)}\n\nSafety rules:\n- This is Mission Mode, not Plan Mode. Do not produce a generic implementation plan.\n- Execute only the current approved mission milestone.\n- Do not continue to later milestones unless Mission Mode starts them.\n- Stop on unexpected risk, destructive action, secret/auth/session/log/runtime-state edit, deployment, push, database mutation, or out-of-scope work.\n- Never edit auth files, sessions, logs, .env, .factory, .cursor, or mission runtime state.\n- Keep file writes sequential unless settings explicitly allow safe scoped parallel edits.\n- Use execution sub-agents aggressively for speed, read-only inspection, risk discovery, implementation strategy, and validation preparation when consistent with executionPolicy=${policy}; if forced, do not edit until required workers have reported.\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nWeb research guidance:\n${workflowRuntimeWebResearchGuidance()}\n\nCheckpoint requirements:\n- Produce a checkpoint-ready summary with files changed, risks, validation needs, and next action.\n- Include validator-grade evidence: Acceptance criteria coverage, Commands run with exit status, Checks skipped with reason, files changed/inspected, and remaining manual verification.\n- Do not edit mission runtime state directly; Mission Mode will checkpoint through the workflow runtime.\n\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Produce validation-ready execution summary for the mission validator."}\n\n${missionRunPlan(mission)}`;
7098
+ return `You are PI MISSION MODE RUNTIME.\n\n${professionalOutputGuidance("Mission runtime")}\n\nMISSION MODE ACTIVE\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nMission status: ${mission.status}\nCurrent milestone index: ${mission.currentMilestoneIndex}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nCurrent milestone objective: ${milestone?.objective ?? "none"}\n\nMilestone steps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nCompleted milestones:\n${completed.map((m) => `- ${m.id}: ${m.title}`).join("\n") || "- none"}\n\nRemaining milestones:\n${remaining.map((m) => `- ${m.id}: ${m.title} (${m.status})`).join("\n") || "- none"}\n\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\nLast stop reason: ${mission.lastStopReason || "none"}\nLast block reason: ${mission.lastBlockReason || "none"}\nNext action: ${mission.nextAction || missionNextActionText(mission, settings)}\n\nSafety rules:\n- This is Mission Mode, not Plan Mode. Do not produce a generic implementation plan.\n- Execute only the current approved mission milestone.\n- Do not continue to later milestones unless Mission Mode starts them.\n- Stop on unexpected risk, destructive action, secret/auth/session/log/runtime-state edit, deployment, push, database mutation, or out-of-scope work.\n- Never edit auth files, sessions, logs, .env, .factory, .cursor, or mission runtime state.\n- Keep file writes sequential unless settings explicitly allow safe scoped parallel edits.\n- Use execution sub-agents aggressively for speed, read-only inspection, risk discovery, implementation strategy, and validation preparation when consistent with executionPolicy=${policy}; if forced, do not edit until required workers have reported.\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\n${subagentCapabilityTable()}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nWeb research guidance:\n${workflowRuntimeWebResearchGuidance()}\n\nPre-handoff verification:\n- Before calling mission_milestone_result, verify all Expected Files/Systems from the milestone plan exist and contain correct content.\n- Verify all Acceptance Criteria are satisfied with evidence.\n- Build, lint, and typecheck must pass (or be explicitly skipped with reason).\n- If any deliverable is missing, create it now before handoff.\n\nCheckpoint requirements:\n- Before your final milestone summary, call mission_milestone_result with milestoneId, status, summary, blockers, and evidence. The typed tool result is the primary Mission execution handoff.\n- Produce a checkpoint-ready summary with files changed, risks, validation needs, and next action.\n- Include validator-grade evidence: Acceptance criteria coverage, Commands run with exit status, Checks skipped with reason, files changed/inspected, and remaining manual verification.\n- Do not edit mission runtime state directly; Mission Mode will checkpoint through the workflow runtime.\n\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Produce validation-ready execution summary for the mission validator."}\n\n${missionRunPlan(mission)}`;
6792
7099
  }
6793
7100
 
6794
7101
  function missionValidationPrompt(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, executionSummary?: string, preflightBlock?: string): string {
6795
7102
  const milestone = mission.milestones[mission.currentMilestoneIndex];
6796
7103
  const last = mission.checkpoints[mission.checkpoints.length - 1];
6797
7104
  const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Validation", "Mission Validation", preflightBlock);
6798
- return `You are PI MISSION MODE VALIDATOR.\n\n${professionalOutputGuidance("Mission validation")}\n\nValidate the completed work against the current mission milestone only. Use read-only tools only. Do not edit or write. You may run safe read-only bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent validator, not the mission executor; do not repair and do not accept executor claims without evidence.\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nMission status: ${mission.status}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nMilestone objective: ${milestone?.objective ?? "none"}\nValidation retry: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission validation repair retries: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast validation failure: ${mission.lastValidationFailure || "none"}\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\nMilestone validation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Validate milestone completion and risks."}\n\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\nLast stop reason: ${mission.lastStopReason || "none"}\nLast block reason: ${mission.lastBlockReason || "none"}\nNext action: ${mission.nextAction || missionNextActionText(mission, settings)}\n\nExecution summary:\n${executionSummary ?? "(none recorded)"}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nWeb research guidance:\n${workflowRuntimeWebResearchGuidance()}\n\nVerdict guidance:\n- PASS only when the milestone is fully satisfied with no blocking unresolved risk.\n- PARTIAL PASS when the work appears mostly milestone-compliant but manual/visual/browser verification remains or evidence is incomplete without a concrete repairable issue.\n- FAIL only for concrete missing requirements, unexpected changes, regressions, broken checks, or unsafe/out-of-scope work that needs repair.\n- Manual visual-verification caveats alone are not repairable failures; recommend manual QA/revalidation instead of repair. If PARTIAL PASS includes concrete repairable issues in code, content, citations, sources, generated files, indexes, metadata, or validation artifacts, list them clearly under Missing Requirements or Recommended Next Action so repair mode can address them.\n- Evidence gaps are not repairable defects unless you identify a concrete missing requirement or artifact. Mark Evidence Gap: yes and Concrete Repairable Issue: no when the correct next action is evidence collection or revalidation rather than repair.\n\nOutput exactly:\n# Validation Report\n## Verdict\nPASS, PARTIAL PASS, or FAIL\n## Reason\n## Mission Coverage\n## Milestone Requirements Reviewed\n## Changed Files Reviewed\n## Commands Run With Exit Status\n## Checks Skipped With Reason\n## Concrete Repairable Issue\nyes/no and short reason\n## Evidence Gap\nyes/no and exact missing evidence\n## Manual Verification Required\nyes/no and exact manual check\n## Missing Requirements\n## Unexpected Changes\n## Regression Risks\n## Test And Build Status\n## Recommended Next Action`;
7105
+ return `You are PI MISSION MODE VALIDATOR.\n\n${professionalOutputGuidance("Mission validation")}\n\nValidate the completed work against the current mission milestone only. Do not edit project source files. You may write temporary evidence-gathering scripts and files when needed for automated checks. You may run safe read-only bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent validator, not the mission executor; do not repair and do not accept executor claims without evidence.\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nMission status: ${mission.status}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nMilestone objective: ${milestone?.objective ?? "none"}\nValidation retry: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission validation repair retries: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast validation failure: ${mission.lastValidationFailure || "none"}\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\nMilestone validation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Validate milestone completion and risks."}\n\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\nLast stop reason: ${mission.lastStopReason || "none"}\nLast block reason: ${mission.lastBlockReason || "none"}\nNext action: ${mission.nextAction || missionNextActionText(mission, settings)}\n\nExecution summary:\n${executionSummary ?? "(none recorded)"}\n\n${subagentCapabilityTable()}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nWeb research guidance:\n${workflowRuntimeWebResearchGuidance()}\n\nVerdict guidance:\n- PASS only when the milestone is fully satisfied with no blocking unresolved risk.\n- 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.\n- 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.\n- PARTIAL PASS is only for genuinely human-only verification (visual design approval, subjective UX assessment, external services you cannot access) after all automatable evidence has been gathered. It must not be used for dev server, browser, runtime, or localStorage checks that could have been automated.\n- Manual visual-verification caveats alone are not repairable failures; recommend manual QA/revalidation instead of repair.\n- If concrete repairable issues remain in code, content, citations, sources, generated files, indexes, metadata, artifacts, or validation artifacts, mark Concrete Repairable Issue: yes, list them clearly under Missing Requirements or Recommended Next Action, and prefer FAIL over PARTIAL PASS.\n- Evidence gaps are not repairable defects unless you identify a concrete missing requirement or artifact. Mark Evidence Gap: yes and Concrete Repairable Issue: no when the correct next action is evidence collection or revalidation rather than repair.\n\nTo verify web app runtime behavior:\n- For projects with npm dev server: npm run dev -- --port 3017 &\n- For static HTML/CSS/JS projects (no package.json scripts): python3 -m http.server 8017 &\n- Wait for the server: sleep 2\n- Query endpoints: curl -fsS http://localhost:PORT/path\n- Verify HTML structure: curl -fsS http://localhost:PORT/ | grep -c "<required-element"\n- Check the process: ps aux | grep "server"\n- Stop the server when done: kill $!\n- Discard unwanted output: >/dev/null 2>&1\nUse single-line bash calls for each step.\n\nCRITICAL: You MUST exhaust all automatable checks before returning PARTIAL PASS.\nDO NOT mark evidence as "could not verify" without actually trying to verify it.\nStart a server, curl the endpoints, check file accessibility — THEN report what you\ncould and could not confirm. "No browser available" is not a reason to skip\nserver-side checks that ARE automatable.\n\nHeadless browser verification: use the workflow_browser_check tool with the dev server URL to verify console errors, page errors, DOM elements, and localStorage behavior. This tool uses Puppeteer from the Pi runtime and works regardless of the target project's dependencies.\n\nBefore your final validation report, call workflow_validation_result with scope=milestone, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only. After calling workflow_validation_result, print a concise validation summary for the user before stopping.\n\nYou MUST fill in EVERY structured output field, especially:\n- Concrete Repairable Issue: yes/no (with reason)\n- Evidence Gap: yes/no (with exact missing evidence)\n- Manual Verification Required: yes/no (with exact manual check)\n- Automated Evidence Completed: list everything verified automatically (not "none" or "n/a")\n\nOutput exactly:\n# Validation Report\n## Verdict\nPASS, PARTIAL PASS, or FAIL\n## Reason\n## Mission Coverage\n## Milestone Requirements Reviewed\n## Changed Files Reviewed\n## Commands Run With Exit Status\n## Checks Skipped With Reason\n## Concrete Repairable Issue\nyes/no and short reason\n## Evidence Gap\nyes/no and exact missing evidence\n## Manual Verification Required\nyes/no and exact manual check\n## Automated Evidence Completed\nWhat runtime/browser/build/test evidence was verified automatically.\n## Truly Manual Evidence Remaining\nOnly genuinely non-automatable human-only checks, not checks that could have been automated.\n## Missing Requirements\n## Unexpected Changes\n## Regression Risks\n## Test And Build Status\n## Recommended Next Action`;
6799
7106
  }
6800
7107
 
6801
7108
  function missionFinalValidationPrompt(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, executionSummary?: string, preflightBlock?: string): string {
6802
7109
  const completed = mission.milestones.filter((m) => m.status === "completed" || m.status === "skipped");
6803
7110
  const last = mission.checkpoints[mission.checkpoints.length - 1];
6804
- const base = readPromptFile("mission-final-validation.md", "You are PI MISSION MODE FINAL VALIDATOR. Validate the whole mission with read-only tools only.");
7111
+ const base = readPromptFile("mission-final-validation.md", "You are PI MISSION MODE FINAL VALIDATOR. Validate the whole mission. Do not edit project source files; you may write temporary evidence-gathering scripts and files when needed for automated checks.");
6805
7112
  const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Validation", "Mission Final Validation", preflightBlock);
6806
7113
  return `${base}
6807
7114
 
@@ -6809,7 +7116,7 @@ You are PI MISSION MODE FINAL VALIDATOR.
6809
7116
 
6810
7117
  ${professionalOutputGuidance("Mission final validation")}
6811
7118
 
6812
- Run a comprehensive final validation of the whole mission after all milestones passed. Use read-only tools only. Do not edit or write. You may run safe read-only bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent final validator, not the executor or repair agent.
7119
+ Run a comprehensive final validation of the whole mission after all milestones passed. Do not edit project source files. You may write temporary evidence-gathering scripts and files when needed for automated checks. You may run safe read-only bash evidence commands such as git status, git diff, git log, package-script discovery, and existing typecheck/test/build commands when appropriate and safe. Do not run mutating, install, deploy, push, reset, clean, database, secret, or settings/state commands. You are the independent final validator, not the executor or repair agent.
6813
7120
 
6814
7121
  Mission ID: ${mission.id}
6815
7122
  Mission goal:
@@ -6822,6 +7129,8 @@ Last final validation failure: ${mission.lastFinalValidationFailure || "none"}
6822
7129
 
6823
7130
  ${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}
6824
7131
 
7132
+ ${subagentCapabilityTable()}
7133
+
6825
7134
  Diagram guidance:
6826
7135
  - ${workflowMermaidGuidance()}
6827
7136
 
@@ -6838,7 +7147,31 @@ Last checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}`
6838
7147
  Execution summary:
6839
7148
  ${executionSummary ?? "(none recorded)"}
6840
7149
 
6841
- Validate the complete mission goal across all milestones, not just the last milestone. Confirm milestone results compose into the original mission outcome. Surface integration gaps, missing cross-milestone requirements, regressions, and unresolved repair risks. Use PASS only for complete success, PARTIAL PASS for manual/visual/browser verification caveats or evidence gaps without concrete repairable issues, and FAIL only for concrete repairable defects or unsafe/out-of-scope work. Evidence gaps are not repairable defects unless a concrete missing requirement or artifact is identified.
7150
+ Validate the complete mission goal across all milestones, not just the last milestone. Confirm milestone results compose into the original mission outcome. Surface integration gaps, missing cross-milestone requirements, regressions, and unresolved repair risks. Use PASS only for complete success. Use FAIL when concrete code/content/citation/source/file/metadata/artifact fixes, concrete repairable defects, or unsafe/out-of-scope work remain. Use FAIL when automatable runtime evidence (build, test, dev server, browser, localStorage, API response) was not gathered and the checks are performable with available tools. Use PARTIAL PASS only for genuinely human-only verification after all automatable evidence has been gathered. Evidence gaps are not repairable defects unless a concrete missing requirement or artifact is identified.\n\nBefore your final validation report, call workflow_validation_result with scope=mission, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only. After calling workflow_validation_result, print a concise validation summary for the user before stopping.
7151
+
7152
+ To verify web app runtime behavior:
7153
+ - For projects with npm dev server: npm run dev -- --port 3017 &
7154
+ - For static HTML/CSS/JS projects (no package.json scripts): python3 -m http.server 8017 &
7155
+ - Wait for the server: sleep 2
7156
+ - Query endpoints: curl -fsS http://localhost:PORT/path
7157
+ - Check the process: ps aux | grep "server"
7158
+ - Stop the server when done: kill $!
7159
+ - Discard unwanted output: >/dev/null 2>&1
7160
+ Use single-line bash calls for each step.
7161
+
7162
+ Headless browser verification: use the workflow_browser_check tool with the dev server URL to verify console errors, page errors, DOM elements, and localStorage behavior. This tool uses Puppeteer from the Pi runtime and works regardless of the target project's dependencies.
7163
+
7164
+ CRITICAL: You MUST exhaust all automatable checks before returning PARTIAL PASS.
7165
+ DO NOT mark evidence as "could not verify" without actually trying to verify it.
7166
+ Start a server, curl the endpoints, check file accessibility — THEN report what you
7167
+ could and could not confirm. "No browser available" is not a reason to skip
7168
+ server-side checks that ARE automatable.
7169
+
7170
+ You MUST fill in EVERY structured output field, especially:
7171
+ - Concrete Repairable Issue: yes/no (with reason)
7172
+ - Evidence Gap: yes/no (with exact missing evidence)
7173
+ - Manual Verification Required: yes/no (with exact manual check)
7174
+ - Automated Evidence Completed: list everything verified automatically (not "none" or "n/a")
6842
7175
 
6843
7176
  Output exactly:
6844
7177
  # Final Mission Validation Report
@@ -6857,6 +7190,10 @@ yes/no and short reason
6857
7190
  yes/no and exact missing evidence
6858
7191
  ## Manual Verification Required
6859
7192
  yes/no and exact manual check
7193
+ ## Automated Evidence Completed
7194
+ What runtime/browser/build/test evidence was verified automatically.
7195
+ ## Truly Manual Evidence Remaining
7196
+ Only genuinely non-automatable human-only checks, not checks that could have been automated.
6860
7197
  ## Missing Requirements
6861
7198
  ## Unexpected Changes
6862
7199
  ## Test And Build Status
@@ -6868,7 +7205,7 @@ function missionRepairPrompt(mission: MissionState, settings: ReturnType<typeof
6868
7205
  const last = mission.checkpoints[mission.checkpoints.length - 1];
6869
7206
  const repairPrompt = readPromptFile("mission-repair.md", "Repair only the current mission milestone validation failure safely, then summarize for revalidation.");
6870
7207
  const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Repair", "Mission Repair", preflightBlock);
6871
- return `${repairPrompt}\n\n${professionalOutputGuidance("Mission repair")}\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nApproved milestone scope:\nObjective: ${milestone?.objective ?? "none"}\nSteps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nRisks:\n${(milestone?.risks ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nValidation failure details:\n${mission.lastValidationFailure || mission.lastBlockReason || "none recorded"}\n\nRetry count: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission retry count: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\n\nSafety rules:\n- Only fix concrete validator-identified issues directly related to the failed milestone validation.\n- Do not re-grade validation or claim PASS; only Mission validation can pass repaired work.\n- If the validation finding is only manual/visual/browser verification or says no automated repair is needed, do not make changes; summarize manual QA/revalidation readiness.\n- Do not expand scope beyond the current approved milestone.\n- Do not perform destructive actions.\n- Do not edit secrets, auth/session/log/runtime-state files, .env, .factory, or .cursor.\n- Do not deploy. Do not push. Do not mutate databases.\n- Stop and report if repair requires destructive, out-of-scope, secret, database, deployment, or other risky action.\n\nSettings:\n- validationRetryMode: ${settings.missions.validationRetryMode ?? "safe_only"}\n- requireApprovalForOutOfScopeRepair: ${settings.missions.requireApprovalForOutOfScopeRepair !== false}\n- requireApprovalForDestructiveRepair: ${settings.missions.requireApprovalForDestructiveRepair !== false}\n- autoRepairValidationFailures: ${settings.missions.autoRepairValidationFailures !== false}\n- pauseAfterValidationFailure: ${settings.missions.pauseAfterValidationFailure === true}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nRevalidation requirement:\n- Summarize whether the current milestone is ready for validator revalidation.\n- Do not mark the milestone complete yourself; Mission Mode will re-run validation according to repair/retry settings.\n\nOutput:\n# Mission Repair Summary\n## Repair Scope\n## Work Completed\n## Files Changed\n## Remaining Risks\n## Revalidation Readiness\n## Next Action`;
7208
+ return `${repairPrompt}\n\n${professionalOutputGuidance("Mission repair")}\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nApproved milestone scope:\nObjective: ${milestone?.objective ?? "none"}\nSteps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nRisks:\n${(milestone?.risks ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nValidation failure details:\n${mission.lastValidationFailure || mission.lastBlockReason || "none recorded"}\n\nRetry count: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission retry count: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\n\nSafety rules:\n- Only fix concrete validator-identified issues directly related to the failed milestone validation.\n- Do not re-grade validation or claim PASS; only Mission validation can pass repaired work.\n- If the validation finding is only manual/visual/browser verification or says no automated repair is needed, do not make changes; summarize manual QA/revalidation readiness.\n- Do not expand scope beyond the current approved milestone.\n- Do not perform destructive actions.\n- Do not edit secrets, auth/session/log/runtime-state files, .env, .factory, or .cursor.\n- Do not deploy. Do not push. Do not mutate databases.\n- Stop and report if repair requires destructive, out-of-scope, secret, database, deployment, or other risky action.\n\nSettings:\n- validationRetryMode: ${settings.missions.validationRetryMode ?? "safe_only"}\n- requireApprovalForOutOfScopeRepair: ${settings.missions.requireApprovalForOutOfScopeRepair !== false}\n- requireApprovalForDestructiveRepair: ${settings.missions.requireApprovalForDestructiveRepair !== false}\n- autoRepairValidationFailures: ${settings.missions.autoRepairValidationFailures !== false}\n- pauseAfterValidationFailure: ${settings.missions.pauseAfterValidationFailure === true}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\n${subagentCapabilityTable()}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nRevalidation requirement:\n- Summarize whether the current milestone is ready for validator revalidation.\n- Do not mark the milestone complete yourself; Mission Mode will re-run validation according to repair/retry settings.\n\nOutput:\n# Mission Repair Summary\n## Repair Scope\n## Work Completed\n## Files Changed\n## Remaining Risks\n## Revalidation Readiness\n## Next Action`;
6872
7209
  }
6873
7210
 
6874
7211
  function parseMissionMilestones(text: string): MissionMilestone[] {
@@ -7145,6 +7482,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
7145
7482
 
7146
7483
  const applyWorkflowPlanResult = (ctx: ExtensionContext, params: Record<string, unknown>) => {
7147
7484
  const settings = loadWorkflowSettings(ctx.cwd);
7485
+ if (state.mode !== "planning") {
7486
+ const guidance = state.mode === "executing" || state.mode === "repairing"
7487
+ ? "workflow_plan_result is ignored because Plan is already executing or repairing. Continue with workflow_progress, write/edit/bash, and workflow_execution_result."
7488
+ : `workflow_plan_result is ignored because the current workflow mode is ${state.mode}. Use it only during Plan planning.`;
7489
+ return { content: [{ type: "text", text: guidance }], details: { accepted: true, ignored: true, mode: state.mode } };
7490
+ }
7148
7491
  recordTypedHandoff(ctx, WORKFLOW_PLAN_RESULT_TOOL as WorkflowTypedHandoffType, params);
7149
7492
  const decision = String(params.decision ?? "").toLowerCase();
7150
7493
  if (decision === "clarify") {
@@ -7248,23 +7591,26 @@ export default function workflowModes(pi: ExtensionAPI): void {
7248
7591
  const completedSteps = normalizePlanCompletedSteps(params.completedSteps);
7249
7592
  const validationAvailable = planValidationModelAvailable(settings);
7250
7593
  const validateAfterExecution = planAutoValidationEnabled(settings);
7594
+ const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex;
7251
7595
  const progressEnabled = workflowPlanProgressEnabled(settings);
7252
7596
  const progressedPlanProgress = progressEnabled ? planProgressWithCompletedSteps(state, settings, completedSteps) : state.planProgress;
7253
7597
  const progressedState = progressedPlanProgress ? { ...state, planProgress: progressedPlanProgress } : state;
7254
7598
  if (status !== "completed") {
7255
- updateState({ mode: state.reviewerReport ? "reviewed" : "plan_approved", executionSummary: summary, planProgress: progressEnabled && progressedPlanProgress ? mergePlanProgress(progressedState, settings, { lifecycleStatus: "blocked", validationStatus: "pending", nextAction: "continue execution" }) : state.planProgress }, ctx);
7599
+ updateState({ mode: state.reviewerReport ? "reviewed" : "plan_approved", executionSummary: summary, planExecutionStepIndex: undefined, planProgress: progressEnabled && progressedPlanProgress ? mergePlanProgress(progressedState, settings, { lifecycleStatus: "blocked", validationStatus: "pending", nextAction: "continue execution" }) : state.planProgress }, ctx);
7256
7600
  queuePlanTerminalSummary(ctx, "blocked", "Plan execution stopped", { reason: summary });
7257
7601
  showBlockedPlanRecoveryMenu(ctx);
7258
7602
  return { ...typedToolAck(), details: { accepted: true, status, validationStarted: false } };
7259
7603
  }
7260
- if (progressEnabled && progressedPlanProgress?.steps.length && settings.workflow.validateAfterEachStep !== true && planProgressHasOpenSteps(progressedPlanProgress)) {
7261
- const reason = completedSteps.length
7262
- ? "workflow_execution_result did not report all approved Plan steps as completed."
7263
- : "workflow_execution_result reported completed status without completedSteps for the approved Plan steps.";
7264
- queueAgentTurn(pi, `The workflow_execution_result handoff was incomplete for Plan progress tracking. Do not redo work. Do not edit, write, patch, or run mutating commands. Submit a corrected workflow_execution_result now. Include status and completedSteps for every approved Plan step that was completed, plus changedFiles, commands, blockers, and summary. Reason: ${reason}`, "workflow-execution-handoff-correction");
7265
- return { ...typedToolAck(false), details: { accepted: false, status, validationStarted: false, reason }, isError: true };
7604
+ const hasOpenSteps = progressEnabled && progressedPlanProgress?.steps.length && settings.workflow.validateAfterEachStep !== true && planProgressHasOpenSteps(progressedPlanProgress);
7605
+ if (hasOpenSteps && !completedSteps.length) {
7606
+ updateState({ mode: state.reviewerReport ? "reviewed" : "plan_approved", executionSummary: summary, planExecutionStepIndex: undefined, planProgress: progressEnabled ? mergePlanProgress(progressedState, settings, { lifecycleStatus: "blocked", validationStatus: "pending", nextAction: "continue execution" }) : state.planProgress }, ctx);
7607
+ 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." });
7608
+ showBlockedPlanRecoveryMenu(ctx);
7609
+ return { ...typedToolAck(), details: { accepted: true, status: "blocked", validationStarted: false } };
7266
7610
  }
7267
- updateState({ mode: "executed", executionSummary: summary, planProgress: progressEnabled ? mergePlanProgress({ ...progressedState, mode: "executed" }, settings, { lifecycleStatus: "executing", nextAction: validationAvailable && validateAfterExecution ? "validator" : "finish workflow" }) : progressedPlanProgress }, ctx);
7611
+ const progressWarning = hasOpenSteps ? "Execution completed with incomplete Plan step metadata; validator must verify approved Plan coverage." : undefined;
7612
+ const executionSummary = progressWarning ? `${summary}\n\nPlan Progress Warning: ${progressWarning}` : summary;
7613
+ 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);
7268
7614
  if (validationAvailable && validateAfterExecution) deferWorkflowAction(pi, "begin validation after typed execution", async () => { await beginValidation(ctx, true); });
7269
7615
  else if (!planValidationGateActive(settings)) deferWorkflowAction(pi, "complete plan after typed execution without validation", () => completePlanWithoutValidation(ctx, validationAvailable ? "Validation skipped: validation is disabled by workflow settings." : "Validation skipped: validator disabled or not configured."));
7270
7616
  else deferWorkflowAction(pi, "show post execution menu after typed execution", () => showPostExecutionMenu(ctx, validationAvailable));
@@ -7302,6 +7648,17 @@ export default function workflowModes(pi: ExtensionAPI): void {
7302
7648
  if (typeof params.concreteRepairableIssue === "boolean") lines.push(`Concrete Repairable Issue: ${params.concreteRepairableIssue ? "yes" : "no"}`);
7303
7649
  if (typeof params.evidenceGap === "boolean") lines.push(`Evidence Gap: ${params.evidenceGap ? "yes" : "no"}`);
7304
7650
  if (typeof params.manualVerificationRequired === "boolean") lines.push(`Manual Verification Required: ${params.manualVerificationRequired ? "yes" : "no"}`);
7651
+ const issues = Array.isArray(params.issues) ? params.issues : [];
7652
+ if (issues.length > 0) {
7653
+ lines.push("Issues:");
7654
+ for (const issue of issues) {
7655
+ const item = issue && typeof issue === "object" ? issue as Record<string, unknown> : {};
7656
+ const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Validation issue";
7657
+ const detail = typeof item.detail === "string" && item.detail.trim() ? ` — ${item.detail.trim()}` : "";
7658
+ const repairable = typeof item.repairable === "boolean" ? ` Repairable: ${item.repairable ? "yes" : "no"}.` : "";
7659
+ lines.push(`- ${title}${detail}${repairable}`);
7660
+ }
7661
+ }
7305
7662
  return lines.join("\n");
7306
7663
  };
7307
7664
 
@@ -7309,6 +7666,9 @@ export default function workflowModes(pi: ExtensionAPI): void {
7309
7666
  const settings = loadWorkflowSettings(ctx.cwd);
7310
7667
  const verdict = typedValidationVerdict(params.verdict);
7311
7668
  const report = typedValidationReport(params);
7669
+ const concreteRepairableIssue = typeof params.concreteRepairableIssue === "boolean" ? params.concreteRepairableIssue : undefined;
7670
+ const manualVerificationRequired = typeof params.manualVerificationRequired === "boolean" ? params.manualVerificationRequired : undefined;
7671
+ const evidenceGap = typeof params.evidenceGap === "boolean" ? params.evidenceGap : undefined;
7312
7672
  const planValidationMode = state.mode === "validating" || state.mode === "revalidating";
7313
7673
  const missionValidationMode = state.mode === "mission_validating" || state.mode === "mission_revalidating" || state.mode === "mission_final_validating";
7314
7674
  if (!planValidationMode && !missionValidationMode) {
@@ -7322,7 +7682,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7322
7682
  await handleMissionFinalValidationFailure(ctx, mission, verdict, report);
7323
7683
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7324
7684
  }
7325
- const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: verdict === "PASS" ? "" : report, lastValidationResult: verdict, nextAction: "Mission completed after typed final validation.", lastSummary: `Typed final mission validation ${verdict}.` });
7685
+ const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: verdict === "PASS" ? "" : report, lastValidationResult: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: "Mission completed after typed final validation.", lastSummary: `Typed final mission validation ${verdict}.` });
7326
7686
  checkpointMission(completed, `Typed final mission validation ${verdict}.`, "Mission completed after final validation.", undefined, { validationResult: verdict });
7327
7687
  completeMissionToAwaitingInput(ctx, completed, report, verdict, settings);
7328
7688
  return { ...typedToolAck(), details: { accepted: true, verdict } };
@@ -7331,7 +7691,10 @@ export default function workflowModes(pi: ExtensionAPI): void {
7331
7691
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
7332
7692
  if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
7333
7693
  if (verdict !== "PASS") {
7334
- await handleMissionValidationFailure(ctx, mission, verdict, report);
7694
+ const missionWithBooleans = { ...mission, concreteRepairableIssue, manualVerificationRequired, evidenceGap };
7695
+ deferWorkflowAction(pi, "handle mission validation failure after typed verdict", async () => {
7696
+ await handleMissionValidationFailure(ctx, missionWithBooleans, verdict, report);
7697
+ });
7335
7698
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7336
7699
  }
7337
7700
  const index = mission.currentMilestoneIndex;
@@ -7340,25 +7703,36 @@ export default function workflowModes(pi: ExtensionAPI): void {
7340
7703
  const nextIndex = Math.min(index + 1, Math.max(0, milestones.length - 1));
7341
7704
  const done = index >= milestones.length - 1;
7342
7705
  if (done && settings.missions.finalValidationEnabled === true) {
7343
- const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", nextAction: "Run final comprehensive mission validation.", lastSummary: `Typed validation ${verdict}; final validation queued.` });
7706
+ const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: "Run final comprehensive mission validation.", lastSummary: `Typed validation ${verdict}; final validation queued.` });
7344
7707
  checkpointMission(finalMission, `Typed validation ${verdict}; final mission validation queued.`, "Run final comprehensive validation for the whole mission.", milestone?.id, { validationResult: verdict });
7345
- updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: report, validationVerdict: verdict }, ctx);
7346
- await beginMissionFinalValidation(ctx, activeMission ?? finalMission, true);
7708
+ updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap }, ctx);
7709
+ deferWorkflowAction(pi, "begin final mission validation after typed PASS", async () => {
7710
+ await beginMissionFinalValidation(ctx, activeMission ?? finalMission, true);
7711
+ });
7347
7712
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7348
7713
  }
7349
7714
  const shouldPause = mission.autonomy === "manual" || mission.pauseBetweenMilestones === true || mission.continueAcrossMilestones === false;
7350
7715
  const nextStatus = done ? "completed" : shouldPause ? "paused" : "approved";
7351
7716
  const nextMode = done ? "awaiting_mission_input" : shouldPause ? "mission_paused" : "mission_approved";
7352
- const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", nextAction: done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`, lastSummary: `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.` });
7717
+ const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`, lastSummary: `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.` });
7353
7718
  checkpointMission(nextMission, `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`, nextMission.nextAction ?? "Continue mission.", milestone?.id, { validationResult: verdict });
7354
7719
  if (done) completeMissionToAwaitingInput(ctx, nextMission, report, verdict, settings);
7355
- else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: report, validationVerdict: verdict }, ctx);
7356
- if (!done && !shouldPause) await beginMissionRun(ctx, activeMission ?? nextMission, "continue", true);
7720
+ else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap }, ctx);
7721
+ if (!done && !shouldPause) {
7722
+ await beginMissionRun(ctx, activeMission ?? nextMission, "continue", true);
7723
+ }
7357
7724
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7358
7725
  }
7359
7726
  const validationStatus = planValidationStatusForVerdict(verdict);
7360
- updateState({ mode: "validated", validationReport: report, validationVerdict: verdict, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: verdict === "PASS" ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: verdict === "PASS" ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
7361
- if (verdict !== "PASS") deferWorkflowAction(pi, "handle typed validation failure", () => handleWorkflowValidationFailure(ctx, verdict, report));
7727
+ const noDefectPartialPass = verdict === "PARTIAL PASS" && concreteRepairableIssue === false;
7728
+ updateState({ mode: "validated", validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap, lastValidationCompletedAt: new Date().toISOString(), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: (verdict === "PASS" || noDefectPartialPass) ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: (verdict === "PASS" || noDefectPartialPass) ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
7729
+ if (verdict !== "PASS") {
7730
+ if (noDefectPartialPass) {
7731
+ await completePlanWorkflow(ctx, report, verdict);
7732
+ } else {
7733
+ deferWorkflowAction(pi, "handle typed validation failure", () => handleWorkflowValidationFailure(ctx, verdict, report));
7734
+ }
7735
+ }
7362
7736
  else if ((settings.workflow as typeof settings.workflow & { returnToPlanModeAfterWorkflow?: boolean }).returnToPlanModeAfterWorkflow !== false) deferWorkflowAction(pi, "complete plan workflow after typed validation", () => completePlanWorkflow(ctx, report, verdict));
7363
7737
  else deferWorkflowAction(pi, "show final menu after typed validation", () => showFinalMenu(ctx));
7364
7738
  return { ...typedToolAck(), details: { accepted: true, verdict } };
@@ -7404,7 +7778,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
7404
7778
  if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
7405
7779
  const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: verdict, lastReviewAttempt: verdict, lastSummary: `Mission reviewer verdict ${verdict}.` });
7406
7780
  updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
7407
- if (verdict !== "PASS" && verdict !== "NOTES") deferWorkflowAction(pi, "handle typed mission reviewer failure", () => handleMissionReviewFailure(ctx, reviewed, verdict, text));
7781
+ if (verdict !== "PASS" && verdict !== "NOTES") {
7782
+ deferWorkflowAction(pi, "handle typed mission reviewer failure", () => handleMissionReviewFailure(ctx, reviewed, verdict, text));
7783
+ } else {
7784
+ deferWorkflowAction(pi, "resume mission approval after reviewer pass", () => handleMissionApprovalHandoff(ctx, reviewed, "menu"));
7785
+ }
7408
7786
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7409
7787
  }
7410
7788
  const settings = loadWorkflowSettings(ctx.cwd);
@@ -7514,6 +7892,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
7514
7892
  }
7515
7893
  if (state.mode === "validating" || state.mode === "revalidating" || state.mode === "repairing") {
7516
7894
  const settings = loadWorkflowSettings(ctx.cwd);
7895
+ if (state.mode === "repairing" && repairTextIndicatesRevalidationReady(stoppedText)) {
7896
+ updateState({ mode: "revalidating", executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary:\n${stoppedText}`.trim(), lastRepairStatus: "completed", lastRepairAttempt: compact(stoppedText, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: state.currentValidationRetry ?? 0, status: "completed", validationFailure: compact(state.lastValidationFailure ?? state.validationReport ?? "", 800), repairSummary: compact(stoppedText, 800), nextAction: "Revalidate interrupted completed repair." }), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "revalidating", lastRepairStatus: "completed" }, settings, { lifecycleStatus: "revalidating", validationStatus: "running", repairStatus: "completed", nextAction: "validation result" }, state.approvedPlan) : state.planProgress }, ctx);
7897
+ deferWorkflowAction(pi, "begin revalidation after interrupted plan repair", async () => { await beginValidation(ctx, true, true); });
7898
+ recordWorkflowInternalEvent(ctx, "Plan repair interruption recovered as completed repair pending revalidation.");
7899
+ return true;
7900
+ }
7517
7901
  updateState({ mode: "plan_approved", validationReport: state.mode === "validating" || state.mode === "revalidating" ? stoppedText : state.validationReport, lastRepairAttempt: state.mode === "repairing" ? stoppedText : state.lastRepairAttempt, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "plan_approved" }, settings, { lifecycleStatus: "approved", nextAction: "rerun stopped gate" }, state.approvedPlan) : state.planProgress }, ctx);
7518
7902
  queuePlanTerminalSummary(ctx, "blocked", "Plan workflow gate stopped", { validationText: state.mode === "validating" || state.mode === "revalidating" ? stoppedText : state.validationReport, reason: "Workflow gate stopped before completion." });
7519
7903
  recordWorkflowInternalEvent(ctx, "Workflow gate stopped before completion.");
@@ -7550,7 +7934,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7550
7934
 
7551
7935
  const workflowContextInterruptionEvidence = (event: unknown, ctx: ExtensionContext): boolean => {
7552
7936
  const eventText = workflowContextInterruptionEventText(event);
7553
- if (/model_context_window_exceeded|context[_\s-]*window|context[_\s-]*length|maximum context|too many tokens|token limit|finish[_\s-]*reason/i.test(eventText)) return true;
7937
+ if (/model_context_window_exceeded|context[_\s-]*window|context[_\s-]*length|maximum context|too many tokens|token limit|finish[_\s-]*reason|websocket|connection[_\s-]*(?:error|lost|closed|dropped)|network[_\s-]*error|failed to fetch|econnrefused|econnreset|etimedout/i.test(eventText)) return true;
7554
7938
  const settings = loadWorkflowSettings(ctx.cwd);
7555
7939
  let nearTrigger = false;
7556
7940
  try {
@@ -7599,6 +7983,23 @@ export default function workflowModes(pi: ExtensionAPI): void {
7599
7983
  updateState({ mode: "mission_blocked", activeMissionId: blocked.id, task: blocked.goal, originalTask: blocked.goal, draftPlan: stoppedText }, ctx);
7600
7984
  return true;
7601
7985
  }
7986
+ if (state.mode === "mission_repairing" && repairTextIndicatesRevalidationReady(stoppedText)) {
7987
+ const repaired = saveActiveMission({
7988
+ ...mission,
7989
+ status: "revalidating",
7990
+ lastStopReason: "",
7991
+ lastRepairStatus: "completed",
7992
+ lastRepairAttempt: compact(stoppedText, 1200),
7993
+ nextAction: `Revalidate ${milestone?.id ?? "current milestone"} after interrupted completed repair.`,
7994
+ lastSummary: `Mission repair completed for ${milestone?.id ?? "current milestone"}; interrupted output recovered for revalidation.`,
7995
+ repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), milestoneId: milestone?.id, retry: mission.currentValidationRetry ?? 0, status: "completed", repairSummary: compact(stoppedText, 800), nextAction: "Revalidate interrupted completed repair." }),
7996
+ });
7997
+ checkpointMission(repaired, `Mission repair interruption recovered as completed for ${milestone?.id ?? "current milestone"}. ${compact(stoppedText, 500)}`, "Revalidation started after interrupted repair.", milestone?.id, { validationResult: repaired.lastValidationResult });
7998
+ updateState({ mode: "mission_revalidating", activeMissionId: repaired.id, task: repaired.goal, originalTask: repaired.goal, executionSummary: stoppedText }, ctx);
7999
+ deferWorkflowAction(pi, "begin mission revalidation after interrupted repair", () => beginMissionValidation(ctx, true, true));
8000
+ recordWorkflowInternalEvent(ctx, "Mission repair interruption recovered as completed repair pending revalidation.");
8001
+ return true;
8002
+ }
7602
8003
  const paused = saveActiveMission({
7603
8004
  ...mission,
7604
8005
  status: "paused",
@@ -7632,29 +8033,71 @@ export default function workflowModes(pi: ExtensionAPI): void {
7632
8033
  const diagrams = parseWorkflowMermaidSegments(text).filter((segment): segment is Extract<WorkflowMarkdownSegment, { type: "mermaid" }> => segment.type === "mermaid").slice(0, WORKFLOW_MERMAID_MAX_BLOCKS_PER_MESSAGE);
7633
8034
  for (const diagram of diagrams) {
7634
8035
  const details = await renderWorkflowMermaidPng(diagram.source, workflowDiagramTitle(diagram.source, diagram.index));
7635
- piApi.sendMessage({ customType: WORKFLOW_MERMAID_MESSAGE_TYPE, content: details.title, display: true, details }, { triggerTurn: false });
8036
+ piApi.sendMessage({ customType: WORKFLOW_MERMAID_MESSAGE_TYPE, content: details.title, display: false, details }, { triggerTurn: false });
7636
8037
  }
7637
8038
  };
7638
8039
 
7639
- const markPlanStep = (ctx: ExtensionContext | undefined, stepNumber: number, status: PlanStepStatus): boolean => {
8040
+ type PlanProgressUpdateSource = "workflow_progress" | "marker" | "tool_fallback";
8041
+ type MarkPlanStepResult = { tracked: boolean; progress?: PlanProgressState; step?: PlanProgressState["steps"][number]; reason?: string };
8042
+
8043
+ const markPlanStep = (ctx: ExtensionContext | undefined, stepNumber: number, status: PlanStepStatus, source: PlanProgressUpdateSource = "tool_fallback"): MarkPlanStepResult => {
7640
8044
  const settings = loadWorkflowSettings(ctx?.cwd ?? process.cwd());
7641
- if (!workflowPlanProgressEnabled(settings)) return false;
8045
+ if (!workflowPlanProgressEnabled(settings)) return { tracked: false, reason: "disabled" };
7642
8046
  const progress = mergePlanProgress(state, settings);
7643
- if (!progress.steps.length) return false;
8047
+ if (!progress.steps.length) return { tracked: false, reason: "no_steps" };
7644
8048
  const index = Math.max(0, Math.min(progress.steps.length - 1, Math.floor(stepNumber) - 1));
8049
+ const current = progress.steps[index];
8050
+ if (!current) return { tracked: false, progress, reason: "invalid_step" };
8051
+ if (status === "active" && (current.status === "completed" || current.status === "skipped")) return { tracked: false, progress, step: current, reason: "already_closed" };
8052
+ if (status === "completed" && current.status === "completed") return { tracked: false, progress, step: current, reason: "already_completed" };
7645
8053
  const steps = progress.steps.map((step, i) => {
7646
8054
  if (status === "active" && i !== index && step.status === "active") return { ...step, status: "pending" as PlanStepStatus };
7647
8055
  if (i === index) return { ...step, status };
7648
8056
  return step;
7649
8057
  });
7650
- updateState({ planProgress: { ...progress, steps, currentStepIndex: currentStepIndexForSteps(steps, index), nextAction: planNextActionText(state) } }, ctx);
7651
- return true;
8058
+ if (status === "completed" || status === "skipped") {
8059
+ const nextOpen = steps.findIndex((step, i) => i > index && step.status === "pending");
8060
+ if (nextOpen >= 0 && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true) {
8061
+ steps[nextOpen] = { ...steps[nextOpen], status: "active" as PlanStepStatus };
8062
+ }
8063
+ }
8064
+ const updatedProgress = { ...progress, steps, currentStepIndex: currentStepIndexForSteps(steps, index), nextAction: planNextActionText(state) };
8065
+ const patch: Partial<WorkflowState> = source === "workflow_progress"
8066
+ ? { planProgress: updatedProgress, planProgressLastToolStep: index + 1, planProgressLastToolStatus: status, planProgressLastToolAt: new Date().toISOString() }
8067
+ : { planProgress: updatedProgress };
8068
+ updateState(patch, ctx);
8069
+ return { tracked: true, progress: updatedProgress, step: steps[index] };
8070
+ };
8071
+
8072
+ const planProgressBashShouldActivateStep = (command: string): boolean => {
8073
+ const trimmed = command.trim();
8074
+ return Boolean(trimmed) && !isBlockedExecuteCommand(trimmed) && !standardSafeReadOnlyBash(trimmed);
8075
+ };
8076
+
8077
+ const activateCurrentPlanStepForTool = (ctx: ExtensionContext, event: { toolName: string; input?: unknown }): void => {
8078
+ if (state.mode !== "executing" && state.mode !== "repairing") return;
8079
+ if (!state.planProgress?.steps?.length) return;
8080
+ if (state.planProgress.steps.some((step) => step.status === "active")) return;
8081
+ if (state.planProgress.steps.every((step) => step.status === "completed" || step.status === "skipped")) return;
8082
+ const toolActivates = event.toolName === "edit"
8083
+ || event.toolName === "write"
8084
+ || event.toolName === "subagent"
8085
+ || (event.toolName === "bash" && planProgressBashShouldActivateStep(String((event.input as { command?: unknown } | undefined)?.command ?? "")));
8086
+ if (!toolActivates) return;
8087
+ const index = currentStepIndexForSteps(state.planProgress.steps, state.planProgress.currentStepIndex);
8088
+ markPlanStep(ctx, index + 1, "active");
7652
8089
  };
7653
8090
 
7654
8091
  const applyPlanProgressMarkers = (ctx: ExtensionContext, text: string): void => {
7655
- for (const match of text.matchAll(/WORKFLOW_STEP_STARTED:\s*(\d+)/gi)) markPlanStep(ctx, Number(match[1]), "active");
7656
- for (const match of text.matchAll(/WORKFLOW_STEP_COMPLETED:\s*(\d+)/gi)) markPlanStep(ctx, Number(match[1]), "completed");
7657
- for (const match of text.matchAll(/WORKFLOW_STEP_FAILED:\s*(\d+)/gi)) markPlanStep(ctx, Number(match[1]), "failed");
8092
+ const ordered = new Set<string>();
8093
+ for (const match of text.matchAll(/WORKFLOW_STEP_(STARTED|COMPLETED|FAILED):\s*(\d+)/gi)) {
8094
+ const key = `${match[0].trim().toLowerCase()}-${match.index}`;
8095
+ if (ordered.has(key)) continue;
8096
+ ordered.add(key);
8097
+ const statusMap: Record<string, PlanStepStatus> = { started: "active", completed: "completed", failed: "failed" };
8098
+ const status = statusMap[match[1].toLowerCase()];
8099
+ if (status) markPlanStep(ctx, Number(match[2]), status, "marker");
8100
+ }
7658
8101
  };
7659
8102
 
7660
8103
  pi.registerTool({
@@ -7756,10 +8199,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
7756
8199
  pi.registerTool({
7757
8200
  name: WORKFLOW_DIAGRAM_TOOL,
7758
8201
  label: "Workflow Diagram",
7759
- description: "Render a meaningful Mermaid diagram inline in the Workflow Suite transcript.",
8202
+ description: "Create and render a Mermaid diagram inline with your prose. Call this tool at the point in your response where the diagram should appear -- not batched at the end.",
7760
8203
  promptSnippet: "workflow_diagram({ title, source }) — render a Mermaid flowchart, sequenceDiagram, stateDiagram, classDiagram, erDiagram, or xychart-beta inline",
7761
8204
  promptGuidelines: [
7762
- "For non-trivial workflow, lifecycle, data-flow, export/share, state-transition, dependency, or architecture explanations, call workflow_diagram with Mermaid source so the platform renders the diagram inline.",
8205
+ "CALL workflow_diagram AS YOU WRITE: Place each call immediately after the paragraph that introduces the concept being diagrammed. Prose introduces, then diagram visualizes -- this creates a natural explanatory flow.",
8206
+ "NEVER batch diagrams at the beginning or end of your response. Inline placement is required, not optional.",
7763
8207
  "Workflow Suite renders diagrams with a uniform dark-mode visual standard. Do not include random Mermaid style/classDef/light-theme overrides unless the user explicitly asks.",
7764
8208
  "Use meaningful, concise labels and the correct Mermaid type. Keep diagrams readable in the rendered card; split crowded flows instead of shrinking labels. Do not use tiny joke diagrams for substantive flows.",
7765
8209
  ],
@@ -7775,7 +8219,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7775
8219
  return { content: [{ type: "text", text: "Invalid or unsupported Mermaid diagram. Use flowchart, graph, sequenceDiagram, stateDiagram, classDiagram, erDiagram, or xychart-beta." }], details: { title, source, diagramType: diagramType ?? "unsupported", status: "fallback", terminalDiagram: workflowMermaidFallbackTerminalDiagram(source, diagramType ?? "unsupported"), error: "unsupported diagram type" } };
7776
8220
  }
7777
8221
  const details = await renderWorkflowMermaidPng(source, title);
7778
- return { content: [{ type: "text", text: details.status === "rendered" ? `Rendered workflow diagram: ${title}` : `Workflow diagram fallback: ${title}` }], details };
8222
+ return { content: [{ type: "text", text: details.status === "rendered" ? title : `Diagram fallback: ${title}` }], details };
7779
8223
  },
7780
8224
  renderResult(result, _options, theme) {
7781
8225
  return workflowMermaidRenderer({ content: result.content, details: result.details }, { expanded: false }, theme);
@@ -7796,12 +8240,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
7796
8240
  if (!Number.isFinite(step) || step < 1) {
7797
8241
  return { content: [{ type: "text", text: "Invalid workflow progress step." }], details: { step, status, tracked: false }, isError: true };
7798
8242
  }
7799
- const tracked = markPlanStep(ctx, step, status);
7800
- if (!tracked) {
7801
- return { content: [{ type: "text", text: "No Plan Mode progress steps are active. Progress not tracked." }], details: { step, status, tracked: false } };
8243
+ const result = markPlanStep(ctx, step, status, "workflow_progress");
8244
+ if (!result.tracked) {
8245
+ return { content: [{ type: "text", text: "No Plan Mode progress steps are active. Progress not tracked." }], details: { step, status, tracked: false, reason: result.reason } };
7802
8246
  }
7803
- const item = state.planProgress?.steps?.[Math.floor(step) - 1];
7804
- return { content: [{ type: "text", text: `Step ${step}: ${item?.title ?? "unknown"} → ${status}` }], details: { step, status, text: item?.title, tracked: true } };
8247
+ const item = result.step;
8248
+ return { content: [{ type: "text", text: `Step ${step}: ${item?.title ?? "unknown"} → ${status}` }], details: { step, status, text: item?.title, tracked: true, currentStepIndex: result.progress?.currentStepIndex } };
7805
8249
  },
7806
8250
  } as ToolDefinition);
7807
8251
 
@@ -8138,7 +8582,27 @@ ${reportExcerpt(validation, 2400)}
8138
8582
  - Run /workflow status to confirm the final state.
8139
8583
  - If blocked, inspect the reason above, then use /plan repair, /plan revalidate, /plan revise <feedback>, or /plan cancel as appropriate.
8140
8584
  - If completed, start the next request with /plan or review the saved record with /workflow plans show ${state.planHistoryId ?? "latest"}.`);
8141
- return { stoppedAt: new Date().toISOString(), kind: "plan", status, title, summary };
8585
+ const snapshot: NonNullable<WorkflowState["lastPlanStopSummary"]>["blockedPlanSnapshot"] = status === "blocked" && state.approvedPlan?.trim()
8586
+ ? {
8587
+ task: state.task,
8588
+ originalTask: state.originalTask,
8589
+ approvedPlan: state.approvedPlan,
8590
+ planHistoryId: state.planHistoryId,
8591
+ approvedPlanHistoryId: state.approvedPlanHistoryId,
8592
+ executionSummary: state.executionSummary,
8593
+ validationReport: state.validationReport,
8594
+ validationVerdict: state.validationVerdict,
8595
+ lastValidationFailure: state.lastValidationFailure,
8596
+ lastRepairAttempt: state.lastRepairAttempt,
8597
+ repairHistory: state.repairHistory,
8598
+ lastRepairStatus: state.lastRepairStatus,
8599
+ currentValidationRetry: state.currentValidationRetry,
8600
+ workflowValidationRetryCount: state.workflowValidationRetryCount,
8601
+ planRuntime: state.planRuntime,
8602
+ planProgress: state.planProgress,
8603
+ }
8604
+ : undefined;
8605
+ return { stoppedAt: new Date().toISOString(), kind: "plan", status, title, summary, blockedPlanSnapshot: snapshot };
8142
8606
  };
8143
8607
 
8144
8608
  const queuePlanTerminalSummary = (ctx: ExtensionContext, status: "completed" | "blocked", title: string, options: { validationText?: string; verdict?: WorkflowState["validationVerdict"]; reason?: string } = {}): NonNullable<WorkflowState["lastPlanStopSummary"]> => {
@@ -8265,6 +8729,7 @@ ${reportExcerpt(validation, 2400)}
8265
8729
  repairHistory: undefined,
8266
8730
  lastRepairStatus: "none",
8267
8731
  planStepValidationIndex: undefined,
8732
+ planExecutionStepIndex: undefined,
8268
8733
  planRuntime: undefined,
8269
8734
  planProgress: undefined,
8270
8735
  lastCompletedPlanSummary: completedPlanSummary,
@@ -8551,6 +9016,12 @@ ${renderMissionProgress(mission, settings)}
8551
9016
  return;
8552
9017
  }
8553
9018
  if (mission.status === "paused" || mission.status === "stopped") {
9019
+ if (missionRepairCompletedPendingRevalidation(mission)) {
9020
+ const revalidating = saveActiveMission({ ...mission, status: "revalidating", lastRepairStatus: mission.lastRepairStatus === "blocked" ? "completed" : (mission.lastRepairStatus ?? "completed"), lastStopReason: "", nextAction: `Revalidate ${mission.milestones[mission.currentMilestoneIndex]?.id ?? "current milestone"} after completed repair.`, lastSummary: "Mission resumed to revalidation after completed repair." });
9021
+ checkpointMission(revalidating, "Mission resume detected completed repair pending revalidation.", "Resume current mission revalidation gate.", mission.milestones[mission.currentMilestoneIndex]?.id, { validationResult: revalidating.lastValidationResult });
9022
+ await beginMissionValidation(ctx, true, true);
9023
+ return;
9024
+ }
8554
9025
  const approved = saveActiveMission({ ...mission, status: "approved", lastStopReason: "", nextAction: "Run /mission continue to proceed.", lastSummary: "Mission resumed by user." });
8555
9026
  const checkpointed = checkpointMission(approved, "Mission resume requested from resume menu.", "Resume current mission milestone in Mission Mode.");
8556
9027
  await beginMissionRun(ctx, checkpointed, "resume");
@@ -8587,7 +9058,9 @@ ${renderMissionProgress(mission, settings)}
8587
9058
  return show(pi, `# Mission Repair ${stale ? "Stale" : "Active"}\n\n${guidance}\n\n${renderMissionStatus(mission)}`);
8588
9059
  }
8589
9060
  if (mission.status === "running" || mission.status === "checkpointing") {
8590
- return show(pi, `# Mission Already Active\n\nMission is currently ${mission.status}. Resume will not restart milestone execution.\n\n${renderMissionStatus(mission)}`);
9061
+ const checkpointed = checkpointMission(mission, "Mission resume re-triggering execution from active state.", "Execute current mission milestone, then run mission validation.");
9062
+ await beginMissionRun(ctx, checkpointed, "resume");
9063
+ return;
8591
9064
  }
8592
9065
  return show(pi, `# Mission Resume\n\nNo safe automatic resume route exists for mission status: ${mission.status}.\n\n${renderMissionStatus(mission)}`);
8593
9066
  }
@@ -8833,10 +9306,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8833
9306
  const settings = loadWorkflowSettings(ctx.cwd);
8834
9307
  const retry = mission.currentReviewRetry ?? 0;
8835
9308
  const missionRetry = missionReviewRetryCount(mission);
8836
- const maxRetries = maxMissionReviewRetries(mission, settings);
9309
+ const config = missionRepairRetryConfig(settings, "review");
9310
+ const maxRetries = Math.max(config.maxRetriesPerItem, maxMissionReviewRetries(mission, settings));
8837
9311
  const failure = `Reviewer ${verdict}. ${compact(reviewText, 1200)}`;
8838
9312
  const canRepairPlan = Boolean(mission.planText?.trim()) && (verdict === "NEEDS REPAIR" || verdict === "FAIL" || verdict === "BLOCKED");
8839
- const repairEnabled = settings.missions.autoRepairReviewFailures === true && settings.missions.reviewRetryMode !== "off";
9313
+ const repairEnabled = config.autoRepairFailures && config.retryMode !== "off";
8840
9314
  if (!repairEnabled || !canRepairPlan || retry >= maxRetries) {
8841
9315
  const reason = !repairEnabled ? "Mission review repair disabled." : !canRepairPlan ? "Mission plan repair context unavailable." : "Mission review repair retry limit exhausted.";
8842
9316
  const blocked = saveActiveMission({ ...mission, status: "planned", reviewerReport: reviewText, reviewerVerdict: verdict, lastReviewFailure: failure, lastReviewAttempt: reason, lastReviewRepairStatus: "blocked", reviewRepairInProgress: false, reviewHistory: appendMissionReviewHistory(mission, { timestamp: new Date().toISOString(), retry, status: "blocked", reviewFailure: compact(failure, 800), nextAction: reason }), nextAction: "Revise the mission plan, rerun /mission review, or approve manually if safe.", lastSummary: "Mission reviewer blocked approval until the milestone plan is addressed." });
@@ -8868,7 +9342,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8868
9342
  }
8869
9343
  const settings = loadWorkflowSettings(ctx.cwd);
8870
9344
  const autoAction = mission.autonomy !== "manual" && settings.missions.autoRunAfterApproval === true ? "Mission approved; starting current milestone automatically." : "Mission approved; choose Run Mission Now from the action menu or use /mission continue.";
8871
- const next = saveActiveMission({ ...mission, status: "approved", approvalRequired: false, lastSummary: "Mission plan approved; dynamic handoff is ready.", nextAction: autoAction, lastStopReason: "", lastBlockReason: "" });
9345
+ const next = saveActiveMission({ ...mission, status: "approved", approvalRequired: false, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, lastSummary: "Mission plan approved; dynamic handoff is ready.", nextAction: autoAction, lastStopReason: "", lastBlockReason: "" });
8872
9346
  updateState({
8873
9347
  mode: "mission_approved",
8874
9348
  activeMissionId: next.id,
@@ -8896,8 +9370,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8896
9370
  }
8897
9371
 
8898
9372
  function missionContinuationBlock(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, action: "run" | "continue" | "next" | "resume"): string | undefined {
8899
- const runtimeBlocked = missionRuntimeBudgetBlock(mission, settings);
8900
- if (runtimeBlocked) return runtimeBlocked;
9373
+ if (action !== "resume") {
9374
+ const runtimeBlocked = missionRuntimeBudgetBlock(mission, settings);
9375
+ if (runtimeBlocked) return runtimeBlocked;
9376
+ }
8901
9377
  if (mission.autonomy === "full_auto" && !missionAllowsFullAuto(mission, settings)) return "Cannot continue: full auto requested but allowFullAuto=false.";
8902
9378
  if (mission.status === "draft") return mission.milestones.length === 0
8903
9379
  ? "Cannot continue: mission is draft and has no approved milestone plan. Run /mission plan, then /mission approve, then /mission continue."
@@ -8965,9 +9441,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8965
9441
  return;
8966
9442
  }
8967
9443
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route), planner: running.modelsUsed?.planner } }, ctx);
8968
- if (auto) {
8969
- queueAgentTurn(pi, `Execute current mission milestone ${milestone.id}: ${milestone.title}.`, "mission-run-trigger");
8970
- } else queueWorkflowPrompt(pi, missionRuntimePrompt(running, settings, phasePreflightBlocks.Execution));
9444
+ saveActiveMission({ ...running, modelsUsed: { ...(running.modelsUsed ?? {}), executor: modelLabel(route) } });
9445
+ queueAgentTurn(pi, missionRuntimePrompt(running, settings, phasePreflightBlocks.Execution), auto ? "mission-run-trigger" : "mission-run-manual-trigger");
8971
9446
  }
8972
9447
 
8973
9448
  async function handleMissionApprovalHandoff(ctx: ExtensionContext, mission: MissionState, source: "menu" | "command" = "command") {
@@ -9090,6 +9565,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9090
9565
  (["Planning", "Execution", "Repair", "Review", "Validation"] as SubagentPhase[]).forEach(resetSubagentPhaseUsage);
9091
9566
  };
9092
9567
 
9568
+ const snapshotSubagentPhaseUsage = (phase: SubagentPhase) => ({
9569
+ count: subagentUsageByPhase[phase] ?? 0,
9570
+ names: new Set(subagentNamesByPhase[phase]),
9571
+ });
9572
+
9573
+ const restoreSubagentPhaseUsage = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }) => {
9574
+ subagentUsageByPhase[phase] = snapshot.count;
9575
+ subagentNamesByPhase[phase] = snapshot.names;
9576
+ };
9577
+
9578
+ const priorSubagentUsageSatisfiesForced = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number } }): boolean => {
9579
+ const policy = override?.policy ?? phasePolicy(settings, phase);
9580
+ if (policy !== "forced") return false;
9581
+ const required = workerTargetForPolicy("forced", override?.workers ?? workerCount(settings, phase));
9582
+ return snapshot.count >= required;
9583
+ };
9584
+
9093
9585
  const beginForcedSubagentPhase = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): boolean => {
9094
9586
  resetSubagentPhaseUsage(phase);
9095
9587
  phasePreflightBlocks[phase] = undefined;
@@ -9157,10 +9649,18 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9157
9649
  const forcedSubagentTaskText = (phase: SubagentPhase, agent: string, index: number, required: number, context: ForcedSubagentPreflightContext): string => {
9158
9650
  const label = context.label ?? phase;
9159
9651
  const mission = context.mission;
9160
- const repoLock = loadWorkflowSettings().safety.repoLockEnabled === true ? " Repo Lock is enabled: keep project file discovery and commands inside the current repository. Access to the Pi runtime directory is allowed for installed Workflow Suite tools, agents, skills, and prompts only; do not inspect sibling repositories or unrelated home-directory paths." : "";
9652
+ const repoLock = loadWorkflowSettings().safety.repoLockEnabled === true ? " Repo Lock is enabled: keep project file discovery and commands inside the current repository. Project .pi files may be read for context, but do not edit protected .pi control/config paths through normal tools. Do not inspect sibling repositories, unrelated home-directory paths, or the live Pi runtime." : "";
9161
9653
  const base = `Forced ${label} sub-agent preflight worker ${index + 1}/${required}. You are running before the main ${label} agent. Stay read-only unless your agent contract explicitly allows otherwise; do not edit, commit, push, deploy, mutate databases, or touch secrets/runtime state.${repoLock} Return concise findings for the main ${label} agent.`;
9162
9654
  if (phase === "Planning") return `${base}\n\nPlanning target:\n${context.task ?? mission?.goal ?? state.task ?? state.originalTask ?? "(not recorded)"}\n\n${mission ? `Mission ID: ${mission.id}\nAutonomy: ${mission.autonomy}\nExisting milestones: ${mission.milestones.length}\n` : ""}\nFocus for ${agent}: identify project rules, likely files/systems, ambiguity, risks, validation needs, off-limits files, and specific recommendations for the final plan.`;
9163
- if (phase === "Execution") return `${base}\n\nApproved execution contract:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}\n\nFocus for ${agent}: inspect likely files, identify implementation hazards, propose safe edit order, regression risks, and validation checks. Do not perform the implementation.`;
9655
+ if (phase === "Execution") {
9656
+ const settings = loadWorkflowSettings();
9657
+ const stepGated = settings.workflow.validateAfterEachStep === true || settings.workflow.requireApprovalPerStep === true;
9658
+ const progress = state.planProgress;
9659
+ const stepIndex = progress?.currentStepIndex ?? state.planExecutionStepIndex ?? 0;
9660
+ const step = progress?.steps?.[stepIndex];
9661
+ const stepBoundary = stepGated && step ? `\n\nCurrent Plan step boundary:\nStep ${stepIndex + 1}: ${step.title}\nInspect and reason only within this current-step boundary. Do not complete later Plan steps.` : "";
9662
+ return `${base}\n\nApproved execution contract:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}${stepBoundary}\n\nFocus for ${agent}: inspect likely files, identify implementation hazards, perform scoped work allowed by your configured tools and agent contract, propose safe edit order, surface regression risks, and recommend validation checks. Stay inside the approved Plan scope, avoid conflicting parallel edits, and do not mark Plan steps complete for the parent executor.`;
9663
+ }
9164
9664
  if (phase === "Repair") return `${base}\n\nApproved scope:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}\n\nValidation failure / repair target:\n${context.validationFailure ?? state.lastValidationFailure ?? state.validationReport ?? mission?.lastValidationFailure ?? "(not recorded)"}\n\nFocus for ${agent}: identify concrete repair target, files to inspect, safe patch strategy, and revalidation checks. Do not repair directly.`;
9165
9665
  if (phase === "Review") return `${base}\n\nApproved plan to challenge before execution:\n${context.approvedPlan ?? state.approvedPlan ?? "(not recorded)"}\n\nFocus for ${agent}: challenge scope, missing requirements, risks, off-limits files, execution ordering, and validation coverage.`;
9166
9666
  return `${base}\n\nApproved plan / milestone:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}\n\nExecution summary:\n${context.executionSummary ?? state.executionSummary ?? "(not recorded)"}\n\nFocus for ${agent}: independently validate requirement coverage, changed-file risks, tests/build evidence, manual QA caveats, and concrete PASS/PARTIAL PASS/FAIL evidence.`;
@@ -9271,6 +9771,22 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9271
9771
  return false;
9272
9772
  };
9273
9773
 
9774
+ const planForcedSubagentPreflightReconcile = (ctx: ExtensionContext, phase: SubagentPhase): void => {
9775
+ const block = phasePreflightBlocks[phase];
9776
+ if (!block) return;
9777
+ const settings = loadWorkflowSettings(ctx.cwd);
9778
+ const policy = phasePolicy(settings, phase);
9779
+ if (policy !== "forced") return;
9780
+ const observedMatch = block.match(/Observed workers: (\d+)/);
9781
+ if (!observedMatch) return;
9782
+ const observed = parseInt(observedMatch[1], 10);
9783
+ if (observed <= 0) return;
9784
+ const current = subagentUsageByPhase[phase] ?? 0;
9785
+ if (observed > current) {
9786
+ subagentUsageByPhase[phase] = observed;
9787
+ }
9788
+ };
9789
+
9274
9790
  const rememberStandardSubagentPreflight = (ctx: ExtensionContext, phase: SubagentPhase, task: string, required: number) => {
9275
9791
  const agents = Array.from(subagentNamesByPhase[phase] ?? []);
9276
9792
  updateState({
@@ -9466,6 +9982,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9466
9982
  repairHistory: undefined,
9467
9983
  lastRepairStatus: "none",
9468
9984
  planStepValidationIndex: undefined,
9985
+ planExecutionStepIndex: undefined,
9469
9986
  planRuntime: undefined,
9470
9987
  planProgress: undefined,
9471
9988
  planHistoryId: undefined,
@@ -9512,9 +10029,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9512
10029
  repairHistory: undefined,
9513
10030
  lastRepairStatus: "none",
9514
10031
  planStepValidationIndex: undefined,
10032
+ planExecutionStepIndex: undefined,
9515
10033
  planProgress: workflowPlanProgressEnabled(settings) ? createPlanProgress(planText, settings, "approved") : undefined,
9516
10034
  }, ctx);
9517
10035
  persistCurrentPlan(ctx, "approved", saveReason);
10036
+ pi.setActiveTools(executionToolsFor(settings));
9518
10037
  deferWorkflowAction(pi, "begin next phase after plan approval", async () => {
9519
10038
  const started = await continueAfterPlanApproval(ctx, true);
9520
10039
  if (!started) show(pi, "# Handoff Blocked\n\nPlan was approved, but automatic continuation could not start. Use /plan continue after fixing the blocker.");
@@ -9543,7 +10062,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9543
10062
  }, ctx);
9544
10063
  if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings)) {
9545
10064
  pi.setActiveTools(planToolsFor(settings));
9546
- 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, 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);
10065
+ 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);
9547
10066
  return;
9548
10067
  }
9549
10068
  const route = await applyModelForRole(pi, ctx, "planner", { cwd: ctx.cwd });
@@ -9616,25 +10135,25 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9616
10135
  const steps = settings.workflow.validateAfterEachStep === true || settings.workflow.requireApprovalPerStep === true
9617
10136
  ? baseSteps.map((step, index) => index === activeIndex ? { ...step, status: "active" as PlanStepStatus } : step.status === "active" ? { ...step, status: "pending" as PlanStepStatus } : step)
9618
10137
  : baseSteps;
9619
- updateState({ mode: "executing", planProgress: { ...currentProgress, steps, currentStepIndex: currentStepIndexForSteps(steps, activeIndex) } }, ctx);
9620
- if (!beginForcedSubagentPhase(ctx, "Execution", settings)) {
10138
+ updateState({ mode: "executing", planExecutionStepIndex: activeIndex, planProgress: { ...currentProgress, steps, currentStepIndex: currentStepIndexForSteps(steps, activeIndex) }, planProgressLastToolStep: undefined, planProgressLastToolStatus: undefined, planProgressLastToolAt: undefined }, ctx);
10139
+ const executionPreflightExists = phasePreflightBlocks.Execution != null;
10140
+ if (!executionPreflightExists && !beginForcedSubagentPhase(ctx, "Execution", settings)) {
9621
10141
  const reason = "Execution blocked by forced sub-agent policy availability gate.";
10142
+ pi.setActiveTools(planToolsFor(settings));
9622
10143
  updateState({ mode: previousMode === "reviewed" ? "reviewed" : "plan_approved", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: previousMode === "reviewed" ? "reviewed" : "plan_approved" }, settings, { lifecycleStatus: "blocked", nextAction: "fix execution policy blocker" }, state.approvedPlan) : state.planProgress }, ctx);
9623
10144
  queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { reason });
9624
10145
  return false;
9625
10146
  }
9626
10147
  const route = isMissionWorkflowMode(state) ? await applyMissionModelForRole(pi, ctx, "executor", { cwd: ctx.cwd }) : await applyModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
9627
10148
  if (!route) {
10149
+ pi.setActiveTools(planToolsFor(settings));
9628
10150
  updateState({ mode: previousMode === "reviewed" ? "reviewed" : "plan_approved" }, ctx);
9629
10151
  show(pi, "# Execution Blocked\n\nExecutor is enabled for this workflow path but its model could not be applied. Check /workflow-settings list, provider availability, and credentials, then run /plan continue.");
9630
10152
  return false;
9631
10153
  }
9632
10154
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
9633
- if (auto) {
9634
- queueAgentTurn(pi, "Execute the approved plan. Follow each step precisely.", "workflow-execute-trigger");
9635
- } else {
9636
- queueWorkflowPrompt(pi, executePrompt(state, settings, phasePreflightBlocks.Execution));
9637
- }
10155
+ pi.setActiveTools(executionToolsFor(settings));
10156
+ queueAgentTurn(pi, executePrompt(state, settings, phasePreflightBlocks.Execution), auto ? "workflow-execute-trigger" : "workflow-execute-manual-trigger");
9638
10157
  return true;
9639
10158
  }
9640
10159
 
@@ -9660,6 +10179,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9660
10179
  return false;
9661
10180
  }
9662
10181
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), reviewer: modelLabel(route) } }, ctx);
10182
+ pi.setActiveTools(reviewToolsFor(settings));
9663
10183
  if (auto) {
9664
10184
  queueAgentTurn(pi, "Review the approved plan before execution begins.", "workflow-review-trigger");
9665
10185
  } else {
@@ -9669,6 +10189,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9669
10189
  }
9670
10190
 
9671
10191
  async function continueAfterPlanApproval(ctx: ExtensionContext, auto = false): Promise<boolean> {
10192
+ if (!state.approvedPlan) {
10193
+ show(pi, "# Workflow Handoff Failed\n\nNo approved plan exists. Run `/plan <task>` to create and approve a new plan.");
10194
+ updateState({ mode: "awaiting_plan_input" }, ctx);
10195
+ return false;
10196
+ }
9672
10197
  const settings = loadWorkflowSettings(ctx.cwd);
9673
10198
  if (settings.models.reviewer.enabled && roleIsConfigured(settings.models.reviewer)) {
9674
10199
  if (settings.workflow.autoRunReviewerBeforeExecute === true) {
@@ -9699,7 +10224,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9699
10224
  clearTypedHandoff(ctx, revalidate ? "Plan revalidation" : "Plan validation");
9700
10225
  pi.setActiveTools(validationToolsFor(settings));
9701
10226
  updateState({ mode: revalidate ? "revalidating" : "validating", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: revalidate ? "revalidating" : "validating" }, settings, { lifecycleStatus: revalidate ? "revalidating" : "validating", validationStatus: "running", nextAction: "validation result" }, state.approvedPlan) : state.planProgress }, ctx);
9702
- if (!beginForcedSubagentPhase(ctx, "Validation", settings)) {
10227
+ const validationPreflightExists = phasePreflightBlocks.Validation != null;
10228
+ if (!validationPreflightExists && !beginForcedSubagentPhase(ctx, "Validation", settings)) {
9703
10229
  const reason = "Validation blocked by forced sub-agent policy availability gate.";
9704
10230
  updateState({ mode: "validated", lastRepairStatus: revalidate ? "blocked" : (state.lastRepairStatus ?? "none"), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "fix validation policy blocker then /plan revalidate" }, state.approvedPlan) : state.planProgress, lastRepairAttempt: revalidate ? reason : state.lastRepairAttempt }, ctx);
9705
10231
  queuePlanTerminalSummary(ctx, "blocked", "Plan validation blocked", { reason });
@@ -9715,8 +10241,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9715
10241
  return false;
9716
10242
  }
9717
10243
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
10244
+ pi.setActiveTools(validationToolsFor(settings));
9718
10245
  if (auto) {
9719
- queueAgentTurn(pi, revalidate ? "Revalidate the completed repair against the approved plan." : "Validate the completed execution against the approved plan.", revalidate ? "workflow-revalidate-trigger" : "workflow-validate-trigger");
10246
+ queueAgentTurn(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation), revalidate ? "workflow-revalidate-trigger" : "workflow-validate-trigger");
9720
10247
  } else {
9721
10248
  queueWorkflowPrompt(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation));
9722
10249
  }
@@ -9791,6 +10318,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9791
10318
  return;
9792
10319
  }
9793
10320
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
10321
+ saveActiveMission({ ...validating, modelsUsed: { ...(validating.modelsUsed ?? {}), validator: modelLabel(route) } });
9794
10322
  const prompt = missionValidationPrompt(validating, settings, state.executionSummary, phasePreflightBlocks.Validation);
9795
10323
  if (auto) {
9796
10324
  queueAgentTurn(pi, revalidate ? "Revalidate the completed mission milestone." : "Validate the completed mission milestone.", revalidate ? "mission-revalidate-trigger" : "mission-validate-trigger");
@@ -9802,7 +10330,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9802
10330
  }
9803
10331
 
9804
10332
  function showBlockedPlanRecoveryMenu(ctx: ExtensionContext): void {
9805
- deferWorkflowAction(pi, "show final menu after blocked plan stop", () => showFinalMenu(ctx));
10333
+ deferWorkflowAction(pi, "show plan resume after blocked plan stop", () => handlePlanResume(ctx));
9806
10334
  }
9807
10335
 
9808
10336
  function showBlockedMissionRecoveryMenu(ctx: ExtensionContext): void {
@@ -9816,6 +10344,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9816
10344
  function planRepairCompletedPendingRevalidation(): boolean {
9817
10345
  if (state.mode === "executing" || state.mode === "validating" || state.mode === "repairing" || state.mode === "revalidating") return false;
9818
10346
  if (state.validationVerdict === "PASS") return false;
10347
+ const validationCompletedAt = state.lastValidationCompletedAt
10348
+ ?? (state.lastWorkflowHandoff?.type === "workflow_validation_result" ? state.lastWorkflowHandoff.createdAt : undefined);
10349
+ if (validationCompletedAt) {
10350
+ const latestCompletedRepair = [...(state.repairHistory ?? [])].reverse().find((entry) => entry.status === "completed");
10351
+ if (latestCompletedRepair?.timestamp && validationCompletedAt > latestCompletedRepair.timestamp) return false;
10352
+ }
9819
10353
  const latestRepair = [...(state.repairHistory ?? [])].reverse().find((entry) => entry.status === "completed" || entry.status === "blocked" || entry.status === "running");
9820
10354
  return state.lastRepairStatus === "completed"
9821
10355
  || latestRepair?.status === "completed"
@@ -9838,6 +10372,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9838
10372
  async function startWorkflowRepair(ctx: ExtensionContext, source: "auto" | "user" = "auto") {
9839
10373
  if (!state.approvedPlan) return show(pi, "# Plan Repair Refused\n\nNo approved plan exists.");
9840
10374
  const settings = loadWorkflowSettings(ctx.cwd);
10375
+ if (state.concreteRepairableIssue === false) {
10376
+ const reason = "No concrete repairable issue — manual verification only.";
10377
+ 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);
10378
+ show(pi, `# Repair Not Needed\n\n${reason}\n\nUse /plan revalidate after manual QA or /plan revise to update scope.`);
10379
+ return;
10380
+ }
9841
10381
  clearTypedHandoff(ctx, "Plan repair");
9842
10382
  const failure = state.lastValidationFailure || state.validationReport || "No validation failure details recorded.";
9843
10383
  const currentRetry = state.currentValidationRetry ?? 0;
@@ -9849,14 +10389,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9849
10389
  await beginValidation(ctx, true, true);
9850
10390
  return;
9851
10391
  }
9852
- if (classifyValidationFailure(state.validationVerdict, failure) === "manual_only") {
9853
- const reason = "Repair refused: validation recorded only manual/visual/browser verification without a concrete repairable issue.";
9854
- updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual verification or revalidate" }) : state.planProgress }, ctx);
9855
- show(pi, `# Plan Repair Refused\n\n${reason}\n\nRun manual QA, then /plan revalidate. Use /plan revise <feedback> if scope changed.`);
9856
- showBlockedPlanRecoveryMenu(ctx);
9857
- return;
9858
- }
9859
- const decision = resolveRepairRetryDecision({ gate: "validation", verdict: state.validationVerdict ?? "FAIL", report: failure, settings, state, itemRetry: currentRetry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan) });
10392
+ const decision = resolveRepairRetryDecision({ gate: "validation", verdict: state.validationVerdict ?? "FAIL", report: failure, settings, state, itemRetry: currentRetry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan), manualOverride: source !== "auto" });
9860
10393
  if (!decision.allowed) {
9861
10394
  const reason = decision.reason ?? "repair requires approval or safety gate.";
9862
10395
  updateState({ mode: "validated", lastRepairStatus: "blocked", lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual repair or revise plan" }) : state.planProgress, lastRepairAttempt: reason, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry, workflowRetryCount: workflowRetry, maxRetriesPerItem: decision.maxRetriesPerItem, maxRetriesPerWorkflow: decision.maxRetriesPerWorkflow, lastFailure: failure, lastAttempt: reason, status: "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
@@ -9864,7 +10397,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9864
10397
  showBlockedPlanRecoveryMenu(ctx);
9865
10398
  return;
9866
10399
  }
9867
- if (!beginForcedSubagentPhase(ctx, "Repair", settings)) {
10400
+ const repairPreflightExists = phasePreflightBlocks.Repair != null;
10401
+ if (!repairPreflightExists && !beginForcedSubagentPhase(ctx, "Repair", settings)) {
9868
10402
  const reason = "Repair blocked by forced sub-agent policy availability gate.";
9869
10403
  updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: reason }) : state.planProgress, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
9870
10404
  queuePlanTerminalSummary(ctx, "blocked", "Plan repair blocked", { validationText: failure, reason });
@@ -9877,24 +10411,41 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9877
10411
  showBlockedPlanRecoveryMenu(ctx);
9878
10412
  return;
9879
10413
  }
10414
+ const manualOverride = decision.manualOverride === true;
10415
+ const verdict = state.validationVerdict ?? "UNKNOWN";
10416
+ if (!manualOverride && verdict !== "FAIL" && !validationReportHasRepairableIssue(state.validationReport ?? state.lastValidationFailure ?? "")) {
10417
+ const reason = "No concrete repairable issue found; manual/browser verification is required before the plan can proceed.";
10418
+ updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: reason }) : state.planProgress, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
10419
+ queuePlanTerminalSummary(ctx, "blocked", "Plan repair blocked", { validationText: failure, reason });
10420
+ show(pi, `# Plan Repair Blocked\n\n${reason}\n\nNo repair retry was consumed. Perform manual verification, then use:\n- /plan revalidate\n- /plan repair (manual override)\n- /plan revise <feedback>`);
10421
+ showBlockedPlanRecoveryMenu(ctx);
10422
+ return;
10423
+ }
9880
10424
  const retry = currentRetry + 1;
9881
10425
  const totalRetry = workflowRetry + 1;
10426
+ const effectiveMaxRetries = Math.max(maxRetries, retry);
10427
+ const effectiveMaxWorkflow = Math.max(maxWorkflowRetries, totalRetry);
9882
10428
  updateState({
9883
10429
  mode: "repairing",
9884
10430
  currentValidationRetry: retry,
9885
10431
  workflowValidationRetryCount: totalRetry,
9886
- maxValidationRetriesPerPlan: maxRetries,
9887
- maxValidationRetriesPerWorkflow: maxWorkflowRetries,
10432
+ maxValidationRetriesPerPlan: effectiveMaxRetries,
10433
+ maxValidationRetriesPerWorkflow: effectiveMaxWorkflow,
9888
10434
  lastRepairStatus: "running",
9889
- repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: totalRetry, maxRetriesPerItem: maxRetries, maxRetriesPerWorkflow: maxWorkflowRetries, lastFailure: failure, lastAttempt: `Plan repair retry ${retry}/${maxRetries}; workflow retry ${totalRetry}/${maxWorkflowRetries} started by ${source}.`, status: "running", inProgress: true, history: [...(state.repairRetryState?.validation?.history ?? []), { timestamp: new Date().toISOString(), retry, status: "running" as const, failure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }].slice(-20) } },
9890
- planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "repairing", currentValidationRetry: retry, lastRepairStatus: "running" }, settings, { lifecycleStatus: "repairing", validationStatus: "fail", repairRetry: retry, maxRepairRetries: maxRetries, repairStatus: "running", nextAction: "repair executor then validator" }, state.approvedPlan) : state.planProgress,
9891
- lastRepairAttempt: `Plan repair retry ${retry}/${maxRetries}; workflow retry ${totalRetry}/${maxWorkflowRetries} started by ${source}.`,
10435
+ repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: totalRetry, maxRetriesPerItem: effectiveMaxRetries, maxRetriesPerWorkflow: effectiveMaxWorkflow, lastFailure: failure, lastAttempt: `${manualOverride ? "Manual repair retry override" : `Plan repair retry`} ${retry}/${effectiveMaxRetries}; workflow retry ${totalRetry}/${effectiveMaxWorkflow} started by ${source}.`, status: "running", inProgress: true, history: [...(state.repairRetryState?.validation?.history ?? []), { timestamp: new Date().toISOString(), retry, status: "running" as const, failure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }].slice(-20) } },
10436
+ planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "repairing", currentValidationRetry: retry, lastRepairStatus: "running" }, settings, { lifecycleStatus: "repairing", validationStatus: "fail", repairRetry: retry, maxRepairRetries: effectiveMaxRetries, repairStatus: "running", nextAction: "repair executor then validator" }, state.approvedPlan) : state.planProgress,
10437
+ planProgressLastToolStep: undefined,
10438
+ planProgressLastToolStatus: undefined,
10439
+ planProgressLastToolAt: undefined,
10440
+ lastRepairAttempt: `${manualOverride ? "Manual repair retry override" : `Plan repair retry`} ${retry}/${effectiveMaxRetries}; workflow retry ${totalRetry}/${effectiveMaxWorkflow} started by ${source}.`,
9892
10441
  lastValidationFailure: failure,
9893
10442
  repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }),
9894
10443
  modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) },
9895
10444
  }, ctx);
9896
10445
  pi.setActiveTools(executionToolsFor(settings));
9897
- if (source !== "auto") show(pi, `# Plan Repair Started\n\nRetry: ${retry} of ${maxRetries} per plan\nWorkflow Retry: ${totalRetry} of ${maxWorkflowRetries} total\n\nRepairing only the approved-plan validation failure.`);
10446
+ if (source !== "auto") show(pi, manualOverride
10447
+ ? `# Plan Repair Started (Manual Override)\n\nRetry: ${retry} (capped at ${maxRetries}) per plan\nWorkflow Retry: ${totalRetry} (capped at ${maxWorkflowRetries}) total\n\nManual override bypassed automatic retry limits. Repairing only the approved-plan validation failure.`
10448
+ : `# Plan Repair Started\n\nRetry: ${retry} of ${effectiveMaxRetries} per plan\nWorkflow Retry: ${totalRetry} of ${effectiveMaxWorkflow} total\n\nRepairing only the approved-plan validation failure.`);
9898
10449
  queueWorkflowPrompt(pi, workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair));
9899
10450
  }
9900
10451
 
@@ -9929,6 +10480,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9929
10480
  function recoverPlanFromHistory(ctx: ExtensionContext, plan: SavedWorkflowPlan): void {
9930
10481
  const settings = loadWorkflowSettings(ctx.cwd);
9931
10482
  activeMission = undefined;
10483
+ const hasPersistedProgress = plan.planProgress && plan.planProgress.steps.some((s) => s.status === "completed" || s.status === "active");
9932
10484
  updateState({
9933
10485
  mode: "plan_approved",
9934
10486
  activeMissionId: undefined,
@@ -9944,23 +10496,25 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9944
10496
  reviewerReport: undefined,
9945
10497
  reviewerVerdict: undefined,
9946
10498
  currentReviewRetry: 0,
9947
- workflowReviewRetryCount: 0,
10499
+ workflowReviewRetryCount: plan.reviewHistory?.length ?? 0,
9948
10500
  maxReviewRetriesPerPlan: undefined,
9949
10501
  maxReviewRetriesPerWorkflow: undefined,
9950
10502
  lastReviewFailure: undefined,
9951
10503
  lastReviewAttempt: undefined,
9952
10504
  lastReviewRepairStatus: "none",
9953
- reviewHistory: undefined,
10505
+ reviewHistory: plan.reviewHistory,
9954
10506
  reviewRepairInProgress: undefined,
9955
10507
  lastValidationFailure: undefined,
9956
10508
  lastRepairAttempt: undefined,
9957
- repairHistory: undefined,
10509
+ repairHistory: plan.repairHistory,
9958
10510
  lastRepairStatus: "none",
9959
- currentValidationRetry: 0,
9960
- workflowValidationRetryCount: 0,
9961
- planStepValidationIndex: undefined,
9962
- planRuntime: undefined,
9963
- planProgress: workflowPlanProgressEnabled(settings) ? createPlanProgress(plan.finalPlan, settings, "approved") : undefined,
10511
+ currentValidationRetry: plan.currentValidationRetry ?? 0,
10512
+ workflowValidationRetryCount: plan.workflowValidationRetryCount ?? 0,
10513
+ planStepValidationIndex: plan.planStepValidationIndex,
10514
+ planExecutionStepIndex: plan.planExecutionStepIndex,
10515
+ planRuntime: plan.planRuntime,
10516
+ repairRetryState: plan.repairRetryState,
10517
+ planProgress: hasPersistedProgress ? plan.planProgress : (workflowPlanProgressEnabled(settings) ? createPlanProgress(plan.finalPlan, settings, "approved") : undefined),
9964
10518
  }, ctx);
9965
10519
  }
9966
10520
 
@@ -9981,7 +10535,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9981
10535
  if (current.mode === "validating" || current.mode === "revalidating") return "Validator is already running. Wait for validation result.";
9982
10536
  if (current.mode === "repairing") return "Executor is already running in repair mode. Revalidation will start after repair completes.";
9983
10537
  if (current.mode === "validated" && current.validationVerdict === "PASS") return "Validation passed. Next action: /plan continue to complete and return to ready state.";
9984
- if (current.mode === "validated" && current.validationVerdict === "PARTIAL PASS") return classifyValidationFailure(current.validationVerdict, current.validationReport ?? current.lastValidationFailure ?? "") === "repairable" ? "Validation partially passed with repairable issues. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>." : "Validation partially passed. Next action: complete manual verification, then /plan revalidate or revise if scope changed.";
10538
+ if (current.mode === "validated" && current.validationVerdict === "PARTIAL PASS") return classifyValidationFailure(current.validationVerdict, current.validationReport ?? current.lastValidationFailure ?? "", { concreteRepairableIssue: current.concreteRepairableIssue, manualVerificationRequired: current.manualVerificationRequired }) === "repairable" ? "Validation partially passed with repairable issues. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>." : "Validation partially passed. Next action: complete manual verification, then /plan revalidate or revise if scope changed.";
9985
10539
  if (current.mode === "validated" && (current.validationVerdict === "FAIL" || current.lastValidationFailure)) return "Validation failed. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>.";
9986
10540
  if (current.mode === "validated" && current.approvedPlan && validationAvailable) return "Validation status is unknown. Next action: /plan revalidate.";
9987
10541
  return `No safe automatic continuation is available for state: ${current.mode}.`;
@@ -9998,11 +10552,38 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9998
10552
  };
9999
10553
  add(latest);
10000
10554
  candidates.forEach(add);
10001
- if (state.approvedPlan?.trim()) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
10555
+ if (state.approvedPlan?.trim() && !isMissionWorkflowMode(state)) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
10002
10556
  if (ordered.length > 0) return { plan: ordered[0], candidates: ordered, source: "history" };
10003
10557
  return { candidates: [], source: "none", reason: "No active or recoverable approved Plan Mode workflow found." };
10004
10558
  }
10005
10559
 
10560
+ function restorePlanFromStopSnapshot(ctx: ExtensionContext): boolean {
10561
+ const snapshot = state.lastPlanStopSummary?.blockedPlanSnapshot;
10562
+ if (!snapshot?.approvedPlan?.trim()) return false;
10563
+ const settings = loadWorkflowSettings(ctx.cwd);
10564
+ updateState({
10565
+ mode: "plan_approved",
10566
+ activeMissionId: undefined,
10567
+ task: snapshot.task ?? "Recovered blocked plan",
10568
+ originalTask: snapshot.originalTask ?? "Recovered blocked plan",
10569
+ approvedPlan: snapshot.approvedPlan,
10570
+ planHistoryId: snapshot.planHistoryId,
10571
+ approvedPlanHistoryId: snapshot.approvedPlanHistoryId,
10572
+ executionSummary: snapshot.executionSummary,
10573
+ validationReport: snapshot.validationReport,
10574
+ validationVerdict: snapshot.validationVerdict,
10575
+ lastValidationFailure: snapshot.lastValidationFailure,
10576
+ lastRepairAttempt: snapshot.lastRepairAttempt,
10577
+ repairHistory: snapshot.repairHistory,
10578
+ lastRepairStatus: snapshot.lastRepairStatus,
10579
+ currentValidationRetry: snapshot.currentValidationRetry,
10580
+ workflowValidationRetryCount: snapshot.workflowValidationRetryCount,
10581
+ planRuntime: snapshot.planRuntime,
10582
+ planProgress: snapshot.planProgress ?? (workflowPlanProgressEnabled(settings) ? createPlanProgress(snapshot.approvedPlan, settings, "approved") : undefined),
10583
+ }, ctx);
10584
+ return true;
10585
+ }
10586
+
10006
10587
  function planResumeChoiceLabel(plan: SavedWorkflowPlan): string {
10007
10588
  return `${plan.id} | ${plan.approvalStatus} | ${plan.planningMode} | ${compact(plan.originalTask || "Saved plan", 80).replace(/\n/g, " ")}`;
10008
10589
  }
@@ -10041,11 +10622,14 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10041
10622
  const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim());
10042
10623
  if (resolved.source === "none" && allPlans.length === 0) return show(pi, `# Plan Resume\n\n${resolved.reason ?? planRecoveryGuidance(state, validationAvailable)}\n\nUse /workflow plans list to see saved plans.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
10043
10624
  let selected = resolved.plan;
10044
- if (resolved.source === "history" && selected) recoverPlanFromHistory(ctx, selected);
10625
+ if (resolved.source === "history" && selected) {
10626
+ const restoredFromSnapshot = restorePlanFromStopSnapshot(ctx);
10627
+ if (!restoredFromSnapshot) recoverPlanFromHistory(ctx, selected);
10628
+ }
10045
10629
 
10046
10630
  const reusePlan = async (plan: SavedWorkflowPlan, amend: boolean): Promise<void> => {
10047
10631
  activeMission = undefined;
10048
- updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task: plan.originalTask ?? "Reuse saved plan", originalTask: plan.originalTask ?? "Reuse saved plan", draftPlan: undefined, approvedPlan: undefined, validationReport: undefined, validationVerdict: undefined, executionSummary: undefined, reviewerReport: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, planHistoryId: undefined, approvedPlanHistoryId: undefined, planStepValidationIndex: undefined, planRuntime: undefined, planProgress: undefined }, ctx);
10632
+ updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task: plan.originalTask ?? "Reuse saved plan", originalTask: plan.originalTask ?? "Reuse saved plan", draftPlan: undefined, approvedPlan: undefined, validationReport: undefined, validationVerdict: undefined, executionSummary: undefined, reviewerReport: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, planHistoryId: undefined, approvedPlanHistoryId: undefined, planStepValidationIndex: undefined, planExecutionStepIndex: undefined, planRuntime: undefined, planProgress: undefined }, ctx);
10049
10633
  await beginPlanning(ctx, plan.originalTask ?? "Reuse saved plan", plan.finalPlan, amend ? "Amend this saved plan into a new approval-ready plan. Keep what still applies and update what should change." : "Reuse this saved plan as the starting point for a new approval-ready plan.");
10050
10634
  };
10051
10635
 
@@ -10070,8 +10654,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10070
10654
  show(pi, `${summary}\n\nChoose what to do.`);
10071
10655
  if (!ctx.hasUI) return;
10072
10656
  const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
10073
- const choice = await ctx.ui.select("Plan Resume", [...(state.approvedPlan ? ["Continue Current Plan"] : []), ...(hasAlternatives ? ["Choose Another Plan"] : []), "List Plan Status", "List Plans", "Cancel"]);
10074
- if (choice === "Continue Current Plan") await handlePlanContinue(ctx);
10657
+ const choice = await ctx.ui.select("Plan Resume", [
10658
+ ...(state.approvedPlan ? ["Repair / Retry", "Revalidate"] : []),
10659
+ ...(state.approvedPlan ? ["Continue Current Plan"] : []),
10660
+ ...(hasAlternatives ? ["Choose Another Plan"] : []),
10661
+ "List Plan Status", "List Plans", "Cancel",
10662
+ ]);
10663
+ if (choice === "Repair / Retry") await startWorkflowRepair(ctx, "user");
10664
+ else if (choice === "Revalidate") await beginValidation(ctx, true, true);
10665
+ else if (choice === "Continue Current Plan") await handlePlanContinue(ctx);
10075
10666
  else if (choice === "Choose Another Plan") {
10076
10667
  const other = await choosePlanFromHistory(ctx, allPlans.filter((plan) => plan.id !== currentPlan?.id), "Choose a plan to resume:");
10077
10668
  if (other) await showSelectedPlanMenu(other);
@@ -10092,8 +10683,13 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10092
10683
  const resolved = resolveActivePlanForResume();
10093
10684
  const selected = await chooseResumePlan(ctx, resolved, "continue");
10094
10685
  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}`);
10095
- recoverPlanFromHistory(ctx, selected);
10096
- show(pi, `# ${title}\n\nRecovered approved plan from history.\n\n${recoverablePlanDetails(selected)}\n\nContinuing through configured workflow gates.`);
10686
+ const restoredFromSnapshot = restorePlanFromStopSnapshot(ctx);
10687
+ if (!restoredFromSnapshot) {
10688
+ recoverPlanFromHistory(ctx, selected);
10689
+ show(pi, `# ${title}\n\nRecovered approved plan from history.\n\n${recoverablePlanDetails(selected)}\n\nContinuing through configured workflow gates.`);
10690
+ } else {
10691
+ show(pi, `# ${title}\n\nRestored blocked Plan from the saved stop snapshot.\n\nContinuing through configured workflow gates.`);
10692
+ }
10097
10693
  }
10098
10694
 
10099
10695
  if (state.mode === "awaiting_plan_input") return show(pi, `# ${title}\n\nPlan Mode is ready and waiting for a planning request. Next action: enter a task with /p <task> or type the request as a normal message.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
@@ -10140,15 +10736,19 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10140
10736
  return;
10141
10737
  }
10142
10738
  if (state.validationVerdict === "PARTIAL PASS") {
10143
- if (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? "") === "repairable") {
10739
+ if (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? "", {
10740
+ concreteRepairableIssue: state.concreteRepairableIssue,
10741
+ manualVerificationRequired: state.manualVerificationRequired,
10742
+ }) === "repairable") {
10144
10743
  show(pi, `# ${title}\n\nValidation partially passed but includes concrete repairable issues. Starting safe repair under the configured validation repair policy.`);
10145
10744
  await startWorkflowRepair(ctx, "user");
10146
10745
  return;
10147
10746
  }
10148
- return show(pi, `# ${title}\n\nValidation partially passed with remaining manual verification only. Continue will not auto-repair because no concrete repairable issue was identified.\n\nNext actions:\n- complete manual QA, then /plan revalidate\n- /plan revise <feedback> if scope changed\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
10747
+ await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
10748
+ return;
10149
10749
  }
10150
10750
  if (state.validationVerdict === "FAIL" || state.lastValidationFailure) {
10151
- return show(pi, `# ${title}\n\nValidation failed. Continue will not automatically repair a failed plan.\n\nNext actions:\n- /plan repair\n- /plan retry\n- /plan revalidate\n- /plan revise <feedback>\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
10751
+ return show(pi, `# ${title}\n\nValidation failed. Continue will not automatically repair a failed plan.\n\nNext actions:\n- /plan repair\n- /plan retry\n- /plan revalidate\n- /plan revise <feedback>\n\nRun /plan status to view full workflow details.`);
10152
10752
  }
10153
10753
  if (state.approvedPlan && validationAvailable) {
10154
10754
  show(pi, `# ${title}\n\nValidation status is unknown. Revalidating the approved plan.`);
@@ -10157,7 +10757,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10157
10757
  }
10158
10758
  }
10159
10759
 
10160
- return show(pi, `# ${title}\n\nNo safe automatic continuation is available for state: ${state.mode}.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
10760
+ return show(pi, `# ${title}\n\nNo safe automatic continuation is available for state: ${state.mode}.\n\nRun /plan status to view full workflow details.`);
10161
10761
  }
10162
10762
 
10163
10763
  function appendWorkflowReviewHistory(entry: NonNullable<WorkflowState["reviewHistory"]>[number]): WorkflowState["reviewHistory"] {
@@ -10189,18 +10789,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10189
10789
  const retry = state.currentValidationRetry ?? 0;
10190
10790
  const workflowRetry = workflowValidationRetryCount(state);
10191
10791
  const failure = `Validation ${verdict}. ${compact(validationText, 1200)}`;
10192
- const failureClass = classifyValidationFailure(verdict, validationText);
10792
+ const failureClass = classifyValidationFailure(verdict, validationText, {
10793
+ concreteRepairableIssue: state.concreteRepairableIssue,
10794
+ manualVerificationRequired: state.manualVerificationRequired,
10795
+ });
10193
10796
  if (failureClass === "manual_only") {
10194
10797
  const reason = "Validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10195
- updateState({ mode: "validated", validationReport: validationText, validationVerdict: verdict, lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason }), lastValidationFailure: failure, lastRepairStatus: "none", lastRepairAttempt: reason, maxValidationRetriesPerPlan: maxRetries, maxValidationRetriesPerWorkflow: maxWorkflowRetries, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: workflowRetry, maxRetriesPerItem: maxRetries, maxRetriesPerWorkflow: maxWorkflowRetries, lastFailure: failure, lastAttempt: reason, status: "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", lastValidationStatus: "unknown", repairStatus: "none", nextAction: "manual verification or revalidate" }) : state.planProgress }, ctx);
10798
+ const validationStatus = planValidationStatusForVerdict(verdict);
10799
+ updateState({ mode: "validated", validationReport: validationText, validationVerdict: verdict, lastValidationCompletedAt: new Date().toISOString(), lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason }), lastValidationFailure: failure, lastRepairStatus: "none", lastRepairAttempt: reason, maxValidationRetriesPerPlan: maxRetries, maxValidationRetriesPerWorkflow: maxWorkflowRetries, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: workflowRetry, maxRetriesPerItem: maxRetries, maxRetriesPerWorkflow: maxWorkflowRetries, lastFailure: failure, lastAttempt: reason, status: "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "blocked", validationStatus, lastValidationStatus: validationStatus, repairStatus: "none", nextAction: "manual verification, revalidate, or revise" }) : state.planProgress }, ctx);
10196
10800
  queuePlanTerminalSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason });
10197
- show(pi, `# Plan Validation Needs Manual Verification\n\nVerdict: ${verdict}\nReason: ${reason}\n\nAutomatic repair was not started because no concrete repairable issue was identified.\n\nChoose from the action menu, or use:\n- /plan revalidate\n- /plan revise <feedback>\n- /plan repair (only after a concrete validator-identified failure)`);
10198
- showBlockedPlanRecoveryMenu(ctx);
10801
+ show(pi, `# Plan Validation - Manual Verification Required\n\nVerdict: ${verdict}\nReason: ${reason}\n\nNo repair retry was consumed. The implementation is code-complete but requires manual browser QA.\n\nChoose from the action menu, or use:\n- /plan complete (accept as done)\n- /plan revalidate\n- /plan revise <feedback>\n- /plan repair (manual override)`);
10802
+ deferWorkflowAction(pi, "show plan resume after manual-only validation", () => handlePlanResume(ctx));
10199
10803
  return;
10200
10804
  }
10201
10805
  const validationStatus = planValidationStatusForVerdict(verdict);
10202
10806
  const decision = resolveRepairRetryDecision({ gate: "validation", verdict: verdict ?? "UNKNOWN", report: failure, settings, state, itemRetry: retry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan) });
10203
10807
  updateState({ mode: "validated", validationReport: validationText, validationVerdict: verdict, lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan validation blocked", { validationText, verdict, reason: failure }), lastValidationFailure: failure, maxValidationRetriesPerPlan: maxRetries, maxValidationRetriesPerWorkflow: maxWorkflowRetries, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: workflowRetry, maxRetriesPerItem: decision.maxRetriesPerItem, maxRetriesPerWorkflow: decision.maxRetriesPerWorkflow, lastFailure: failure, lastAttempt: decision.reason, status: decision.allowed ? "none" : "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: "repair/revalidate or revise" }) : state.planProgress }, ctx);
10808
+ show(pi, `# Plan Validation Result\n\nVerdict: ${verdict}\n${verdict === "PARTIAL PASS" ? "No concrete repairable issue was found. Validation gaps are limited to manual/browser verification." : `Repair policy: ${decision.allowed ? "auto-repair starting" : decision.reason ?? "repair blocked"}.`}\n\n${compact(validationText, 800)}`);
10204
10809
  if (!decision.allowed) {
10205
10810
  const reason = decision.reason ?? "repair requires approval or safety gate.";
10206
10811
  updateState({ mode: "validated", lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan repair blocked", { validationText, verdict, reason }), lastRepairStatus: "blocked", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", validationStatus, repairStatus: "blocked", nextAction: reason }) : state.planProgress, lastRepairAttempt: reason, lastValidationFailure: failure }, ctx);
@@ -10261,6 +10866,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10261
10866
  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 });
10262
10867
  pi.setActiveTools(executionToolsFor(settings));
10263
10868
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
10869
+ saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
10264
10870
  if (source !== "auto") show(pi, `# Mission Final Repair Started\n\nRetry: ${retry} of ${maxRetries}\n\n${renderMissionProgress(repairing, settings)}`);
10265
10871
  queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
10266
10872
  }
@@ -10350,6 +10956,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10350
10956
  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 });
10351
10957
  pi.setActiveTools(executionToolsFor(settings));
10352
10958
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
10959
+ saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
10353
10960
  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)}`);
10354
10961
  if (source === "auto") queueAgentTurn(pi, "Repair the current mission milestone validation failure, then submit workflow_repair_result.", "mission-repair-trigger");
10355
10962
  else queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
@@ -10421,6 +11028,17 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10421
11028
 
10422
11029
  async function handleMissionFinalValidationFailure(ctx: ExtensionContext, mission: MissionState, verdict: WorkflowState["validationVerdict"], validationText: string) {
10423
11030
  const settings = loadWorkflowSettings(ctx.cwd);
11031
+ const failureClass = classifyValidationFailure(verdict, validationText, {
11032
+ concreteRepairableIssue: mission.concreteRepairableIssue,
11033
+ manualVerificationRequired: mission.manualVerificationRequired,
11034
+ });
11035
+ if (failureClass === "manual_only") {
11036
+ const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: "", lastValidationResult: verdict, lastValidationFailure: "", lastBlockReason: "", nextAction: "Mission completed all milestones and final validation (no concrete repairable issues).", lastSummary: `Final mission validation ${verdict} — no concrete repairable issue, completing.` });
11037
+ checkpointMission(completed, `Final mission validation ${verdict} — no concrete repairable issue. ${compact(validationText, 500)}`, "Mission completed after final validation.", undefined, { validationResult: verdict });
11038
+ completeMissionToAwaitingInput(ctx, completed, validationText, verdict, settings);
11039
+ recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
11040
+ return;
11041
+ }
10424
11042
  const retry = missionFinalValidationRetryCount(mission);
10425
11043
  const maxRetries = missionMaxFinalValidationRetries(mission, settings);
10426
11044
  const failure = `Final mission validation ${verdict}. ${compact(validationText, 1200)}`;
@@ -10428,16 +11046,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10428
11046
  const nextRetry = retry + 1;
10429
11047
  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}.` });
10430
11048
  checkpointMission(failed, `Final mission validation failed. Retry ${retry}/${maxRetries}. ${compact(validationText, 500)}`, "Evaluate safe final repair retry before completion.", undefined, { validationResult: verdict });
10431
- if (classifyValidationFailure(verdict, validationText) === "manual_only") {
10432
- const reason = "Final validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10433
- const blocked = saveActiveMission({ ...failed, status: "blocked", lastRepairStatus: "none", lastBlockReason: reason, nextAction: "Perform manual verification, then run /mission revalidate.", lastSummary: "Mission blocked pending manual final validation verification." });
10434
- checkpointMission(blocked, `Mission final validation pending manual verification. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", undefined, { validationResult: verdict });
10435
- updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
10436
- queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
10437
- recordWorkflowInternalEvent(ctx, "Mission final validation manual-verification blocker suppressed from transcript.");
10438
- showBlockedMissionRecoveryMenu(ctx);
10439
- return;
10440
- }
10441
11049
  const shouldBlock = settings.missions.autoRepairFinalValidationFailures !== true || retry >= maxRetries || Boolean(unsafe);
10442
11050
  if (shouldBlock) {
10443
11051
  const reason = settings.missions.autoRepairFinalValidationFailures !== true ? "missions.autoRepairFinalValidationFailures=false."
@@ -10464,6 +11072,36 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10464
11072
  const retry = mission.currentValidationRetry ?? 0;
10465
11073
  const missionRetry = missionValidationRetryCount(mission);
10466
11074
  const failure = `Validation ${verdict} for ${milestone?.id ?? "current milestone"}. ${compact(validationText, 1200)}`;
11075
+ const failureClass = classifyValidationFailure(verdict, validationText, {
11076
+ concreteRepairableIssue: mission.concreteRepairableIssue,
11077
+ manualVerificationRequired: mission.manualVerificationRequired,
11078
+ });
11079
+ if (failureClass === "manual_only") {
11080
+ const milestones = mission.milestones.map((m, i) => i === index ? { ...m, status: "completed" as const } : m);
11081
+ const nextIndex = Math.min(index + 1, Math.max(0, milestones.length - 1));
11082
+ const done = index >= milestones.length - 1;
11083
+ if (done && settings.missions.finalValidationEnabled === true) {
11084
+ const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", concreteRepairableIssue: false, manualVerificationRequired: true, nextAction: "Run final comprehensive mission validation.", lastSummary: `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue; final validation queued.` });
11085
+ checkpointMission(finalMission, `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue; final mission validation queued. ${compact(validationText, 500)}`, "Run final comprehensive validation for the whole mission.", milestone?.id, { validationResult: verdict });
11086
+ updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: validationText, validationVerdict: verdict }, ctx);
11087
+ deferWorkflowAction(pi, "begin final mission validation after no-defect partial pass", async () => {
11088
+ await beginMissionFinalValidation(ctx, activeMission ?? finalMission, true);
11089
+ });
11090
+ return;
11091
+ }
11092
+ const shouldPause = mission.autonomy === "manual" || mission.pauseBetweenMilestones === true || mission.continueAcrossMilestones === false;
11093
+ const nextStatus = done ? "completed" : shouldPause ? "paused" : "approved";
11094
+ const nextMode = done ? "awaiting_mission_input" : shouldPause ? "mission_paused" : "mission_approved";
11095
+ const nextAction = done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`;
11096
+ const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", concreteRepairableIssue: false, manualVerificationRequired: true, evidenceGap: false, nextAction, lastSummary: `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue, advancing.` });
11097
+ checkpointMission(nextMission, `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue. ${compact(validationText, 500)}`, nextAction, milestone?.id, { validationResult: verdict });
11098
+ if (done) completeMissionToAwaitingInput(ctx, nextMission, validationText, verdict, settings);
11099
+ else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: validationText, validationVerdict: verdict }, ctx);
11100
+ if (!done && !shouldPause) {
11101
+ await beginMissionRun(ctx, activeMission ?? nextMission, "continue", true);
11102
+ }
11103
+ return;
11104
+ }
10467
11105
  const failedMilestones = mission.milestones.map((m, i) => i === index ? { ...m, status: "active" as const } : m);
10468
11106
  const failed = saveActiveMission({
10469
11107
  ...mission,
@@ -10479,27 +11117,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10479
11117
  lastSummary: `Validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`,
10480
11118
  });
10481
11119
  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 });
10482
- const failureClass = classifyValidationFailure(verdict, validationText);
10483
- if (failureClass === "manual_only") {
10484
- const reason = "Mission validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10485
- const blocked = saveActiveMission({ ...failed, status: "blocked", lastRepairStatus: "none", lastBlockReason: reason, nextAction: "Perform manual verification, then run /mission revalidate.", lastSummary: "Mission blocked pending manual validation verification." });
10486
- checkpointMission(blocked, `Mission validation pending manual verification for ${milestone?.id ?? "current milestone"}. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", milestone?.id, { validationResult: verdict });
10487
- updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
10488
- queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
10489
- recordWorkflowInternalEvent(ctx, "Mission validation manual-verification blocker suppressed from transcript.");
10490
- showBlockedMissionRecoveryMenu(ctx);
10491
- return;
10492
- }
10493
- if (verdict === "PARTIAL PASS" && failureClass === "ambiguous" && !simplePresetActive(settings)) {
10494
- const reason = "Mission validation returned PARTIAL PASS with incomplete evidence but no concrete repairable issue. Automatic repair is not appropriate for this preset.";
10495
- const blocked = saveActiveMission({ ...failed, status: "blocked", lastRepairStatus: "none", lastBlockReason: reason, nextAction: "Clarify the validation gap, perform manual verification, or run /mission revalidate.", lastSummary: "Mission blocked pending clearer partial-pass validation evidence." });
10496
- checkpointMission(blocked, `Mission validation PARTIAL PASS needs clearer evidence for ${milestone?.id ?? "current milestone"}. Reason: ${reason}`, blocked.nextAction ?? "Clarify validation evidence or revalidate.", milestone?.id, { validationResult: verdict });
10497
- updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
10498
- queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
10499
- recordWorkflowInternalEvent(ctx, "Mission validation evidence blocker suppressed from transcript.");
10500
- showBlockedMissionRecoveryMenu(ctx);
10501
- return;
10502
- }
10503
11120
  const unsafe = validationFailureRequiresApproval(failure, settings);
10504
11121
  const shouldBlock = mission.autonomy === "manual"
10505
11122
  || settings.missions.autoRepairValidationFailures === false
@@ -10574,16 +11191,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10574
11191
  const questions = state.clarifyingQuestions ?? [];
10575
11192
  const answerSummary = formatAnswersForPlanner(questions, answers);
10576
11193
  const settings = loadWorkflowSettings(ctx.cwd);
11194
+ const priorPlanning = snapshotSubagentPhaseUsage("Planning");
10577
11195
  if (!beginForcedSubagentPhase(ctx, "Planning", settings)) {
10578
11196
  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);
10579
11197
  return;
10580
11198
  }
11199
+ const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings);
11200
+ if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
10581
11201
  updateState({
10582
11202
  mode: "planning",
10583
11203
  clarifyingAnswers: answers,
10584
11204
  lastWorkflowHandoff: undefined,
10585
11205
  }, ctx);
10586
- await beginPlanning(ctx, state.task ?? "Plan with clarification answers", state.approvedPlan, answerSummary, { feedbackKind: "clarification" });
11206
+ await beginPlanning(ctx, state.task ?? "Plan with clarification answers", state.approvedPlan, answerSummary, { feedbackKind: "clarification", planningPreflightSatisfied });
10587
11207
  }
10588
11208
 
10589
11209
  async function runStandardClarificationSelect(ctx: ExtensionContext, questions: ClarificationQuestion[]): Promise<ClarificationAnswer[] | undefined> {
@@ -10645,16 +11265,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10645
11265
  const questions = mission.clarificationQuestions?.length ? mission.clarificationQuestions : (state.clarifyingQuestions ?? []);
10646
11266
  const settings = loadWorkflowSettings(ctx.cwd);
10647
11267
  const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning" };
11268
+ const priorPlanning = snapshotSubagentPhaseUsage("Planning");
10648
11269
  if (!beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
10649
11270
  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." });
10650
11271
  checkpointMission(blocked, "Mission clarification resume blocked by forced Mission Planning sub-agent policy.", "Fix Mission Planning sub-agent workers, then run /mission resume or /mission plan.");
10651
11272
  updateState({ mode: "mission_blocked", activeMissionId: blocked.id, task: blocked.goal, originalTask: blocked.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, draftPlan: blocked.planText, approvedPlan: undefined }, ctx);
10652
11273
  return;
10653
11274
  }
11275
+ const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings, planningOverride);
11276
+ if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
10654
11277
  const next = saveActiveMission({ ...mission, status: "draft", clarificationQuestions: questions, clarificationAnswers: answers, lastSummary: "Mission clarification answered. Resuming milestone planning." });
10655
11278
  checkpointMission(next, "Mission clarification answers recorded.", "Resume dynamic milestone planning with clarification applied.");
10656
11279
  updateState({ mode: "mission_planning", activeMissionId: next.id, task: next.goal, originalTask: next.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, lastWorkflowHandoff: undefined }, ctx);
10657
- await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers });
11280
+ await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers }, { planningPreflightSatisfied });
10658
11281
  }
10659
11282
 
10660
11283
  function showMissionClarificationFallback(reason: string): void {
@@ -10937,11 +11560,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10937
11560
  const verdict = state.validationVerdict;
10938
11561
  const passed = verdict === "PASS";
10939
11562
  const partial = verdict === "PARTIAL PASS";
10940
- const repairablePartial = partial && classifyValidationFailure(verdict, state.validationReport ?? state.lastValidationFailure ?? "") === "repairable";
11563
+ const repairablePartial = partial && classifyValidationFailure(verdict, state.validationReport ?? state.lastValidationFailure ?? "", { concreteRepairableIssue: state.concreteRepairableIssue, manualVerificationRequired: state.manualVerificationRequired }) === "repairable";
10941
11564
  const failed = verdict === "FAIL" || verdict === "UNKNOWN";
10942
11565
  const title = repairablePartial ? "Validation found repairable issues. Choose next action:" : partial ? "Validation needs manual verification. Choose next action:" : failed ? "Validation failed. Choose next action:" : "Validation complete. Choose next action:";
10943
11566
  const choices = [
10944
11567
  ...(failed || repairablePartial ? ["Run Safe Repair"] : []),
11568
+ ...(partial && !repairablePartial ? ["Repair / Retry"] : []),
10945
11569
  ...(!passed ? ["Revalidate"] : []),
10946
11570
  "List Summary",
10947
11571
  partial && !repairablePartial ? "Revise / Add Manual Verification Feedback" : "Revise / Fix Issues",
@@ -10950,7 +11574,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10950
11574
  ...(!passed ? ["Cancel"] : []),
10951
11575
  ];
10952
11576
  const choice = await ctx.ui.select(title, choices);
10953
- if (choice === "Run Safe Repair") await startWorkflowRepair(ctx, "user");
11577
+ if (choice === "Run Safe Repair" || choice === "Repair / Retry") await startWorkflowRepair(ctx, "user");
10954
11578
  else if (choice === "Revalidate") await beginValidation(ctx, true, true);
10955
11579
  else if (choice === "List Summary") show(pi, renderWorkflowSummary(state, ctx.cwd));
10956
11580
  else if (choice === "Revise / Fix Issues" || choice === "Revise / Add Manual Verification Feedback") {
@@ -11035,7 +11659,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11035
11659
  if (choice === "Set planning.depth") {
11036
11660
  const depth = parsePlanningDepth((await ctx.ui.select("Planning depth", ["fast", "standard", "deep", "maximum"])) ?? "");
11037
11661
  if (depth) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.depth = depth; }); ctx.ui.notify(`planning.depth set to ${depth} in ${r.file}`, "info"); }
11038
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11662
+ } else if (choice === "List Current Settings") {
11663
+ const s = loadWorkflowSettings(ctx.cwd);
11664
+ show(pi, `# Planning Settings\n\nplanning.depth: ${s.planning.depth}`);
11665
+ }
11039
11666
  }
11040
11667
  }
11041
11668
 
@@ -11053,7 +11680,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11053
11680
  } else if (choice === "Set maxClarificationQuestions") {
11054
11681
  const count = parsePositiveInt((await ctx.ui.select("Max clarification questions", ["1", "2", "3", "4", "5"])) ?? "");
11055
11682
  if (count) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxClarificationQuestions = count; }); ctx.ui.notify(`maxClarificationQuestions set to ${count} in ${r.file}`, "info"); }
11056
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11683
+ } else if (choice === "List Current Settings") {
11684
+ const s = loadWorkflowSettings(ctx.cwd);
11685
+ show(pi, `# Clarification Settings\n\nClarification Mode: ${s.planning.clarificationMode}\nInteractive Clarification: ${s.planning.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Clarification Questions: ${s.planning.maxClarificationQuestions}`);
11686
+ }
11057
11687
  }
11058
11688
  }
11059
11689
 
@@ -11061,9 +11691,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11061
11691
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11062
11692
  const keys = subagentPhaseSettingKeys(phase);
11063
11693
  while (ctx.hasUI) {
11064
- const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "List Current Settings", "Back"]);
11694
+ const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "Back"]);
11065
11695
  if (!choice || choice === "Back") return;
11066
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11067
11696
  if (choice.endsWith("Policy")) {
11068
11697
  const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11069
11698
  if (policy) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.subagents as typeof s.subagents & Record<string, string>)[keys.policyKey] = policy; }); ctx.ui.notify(`subagents.${keys.policyKey} set to ${policy} in ${r.file}`, "info"); }
@@ -11079,9 +11708,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11079
11708
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11080
11709
  const keys = subagentPhaseSettingKeys(phase);
11081
11710
  while (ctx.hasUI) {
11082
- const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "List Current Settings", "Back"]);
11711
+ const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "Back"]);
11083
11712
  if (!choice || choice === "Back") return;
11084
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11085
11713
  if (choice.endsWith("Policy")) {
11086
11714
  const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11087
11715
  if (policy) {
@@ -11126,9 +11754,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11126
11754
  async function showStandardParallelismSettingsMenu(ctx: ExtensionContext) {
11127
11755
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11128
11756
  while (ctx.hasUI) {
11129
- const choice = await ctx.ui.select("Standard Parallelism Settings", ["Set allowParallelPlanning", "Set allowParallelExecution", "Set allowParallelRepair", "Set allowParallelReview", "Set allowParallelValidation", "List Current Settings", "Back"]);
11757
+ const choice = await ctx.ui.select("Standard Parallelism Settings", ["Set allowParallelPlanning", "Set allowParallelExecution", "Set allowParallelRepair", "Set allowParallelReview", "Set allowParallelValidation", "Back"]);
11130
11758
  if (!choice || choice === "Back") return;
11131
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11132
11759
  const key = choice.replace("Set ", "");
11133
11760
  const enabled = await chooseBool(ctx, `standard.subagents.${key}?`);
11134
11761
  if (enabled !== undefined) {
@@ -11162,16 +11789,18 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11162
11789
  else if (choice === "Activity Indicator") {
11163
11790
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11164
11791
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
11165
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11792
+ } else if (choice === "List Current Settings") {
11793
+ const s = loadWorkflowSettings(ctx.cwd);
11794
+ show(pi, `# Plan Sub-agents / Workers\n\n${renderPlanSubagentWorkerSettings(s)}`);
11795
+ }
11166
11796
  }
11167
11797
  }
11168
11798
 
11169
11799
  async function showMissionPlanningSubagentSettingsMenu(ctx: ExtensionContext) {
11170
11800
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11171
11801
  while (ctx.hasUI) {
11172
- const choice = await ctx.ui.select("Mission Planning Policy / Workers", ["Mission Planning Policy", "Mission Planning Deep Workers", "Mission Planning Maximum / Forced Workers", "List Current Settings", "Back"]);
11802
+ const choice = await ctx.ui.select("Mission Planning Policy / Workers", ["Mission Planning Policy", "Mission Planning Deep Workers", "Mission Planning Maximum / Forced Workers", "Back"]);
11173
11803
  if (!choice || choice === "Back") return;
11174
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11175
11804
  if (choice === "Mission Planning Policy") {
11176
11805
  const policy = parseSubagentPolicy((await ctx.ui.select("Mission planning policy", ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11177
11806
  if (policy) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.subagentPolicy = policy; }); ctx.ui.notify(`missions.subagentPolicy set to ${policy} in ${r.file}`, "info"); }
@@ -11213,7 +11842,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11213
11842
  else if (choice === "Activity Indicator") {
11214
11843
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11215
11844
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
11216
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11845
+ } else if (choice === "List Current Settings") {
11846
+ const s = loadWorkflowSettings(ctx.cwd);
11847
+ show(pi, `# Shared Sub-agents / Workers\n\nSub-agents: ${s.subagents.enabled !== false ? "enabled" : "disabled"}\nActivity Indicator: ${s.subagents.activityIndicatorEnabled !== false ? "enabled" : "disabled"}\nAuto-use Planning: ${(s.subagents as Record<string, boolean>).autoUseDuringPlanning !== false ? "on" : "off"}\nAuto-use Execution: ${(s.subagents as Record<string, boolean>).autoUseDuringExecution !== false ? "on" : "off"}\nAuto-use Repair: ${(s.subagents as Record<string, boolean>).autoUseDuringRepair !== false ? "on" : "off"}\nAuto-use Review: ${(s.subagents as Record<string, boolean>).autoUseDuringReview !== false ? "on" : "off"}\nAuto-use Validation: ${(s.subagents as Record<string, boolean>).autoUseDuringValidation !== false ? "on" : "off"}`);
11848
+ }
11217
11849
  }
11218
11850
  }
11219
11851
 
@@ -11235,7 +11867,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11235
11867
  else if (choice === "Activity Indicator") {
11236
11868
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11237
11869
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
11238
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11870
+ } else if (choice === "List Current Settings") {
11871
+ const s = loadWorkflowSettings(ctx.cwd);
11872
+ show(pi, `# Standard Sub-agents / Workers\n\nSub-agents: ${s.standard.allowSubagents !== false ? "enabled" : "disabled"}\nAgent Scope: ${s.standard.subagentScope ?? "user"}\nActivity Indicator: ${s.subagents.activityIndicatorEnabled !== false ? "enabled" : "disabled"}\nAuto-use Planning: ${(s.subagents as Record<string, boolean>).autoUseDuringPlanning !== false ? "on" : "off"}\nAuto-use Execution: ${(s.subagents as Record<string, boolean>).autoUseDuringExecution !== false ? "on" : "off"}\nAuto-use Repair: ${(s.subagents as Record<string, boolean>).autoUseDuringRepair !== false ? "on" : "off"}\nAuto-use Review: ${(s.subagents as Record<string, boolean>).autoUseDuringReview !== false ? "on" : "off"}\nAuto-use Validation: ${(s.subagents as Record<string, boolean>).autoUseDuringValidation !== false ? "on" : "off"}`);
11873
+ }
11239
11874
  }
11240
11875
  }
11241
11876
 
@@ -11257,7 +11892,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11257
11892
  else if (choice === "Activity Indicator") {
11258
11893
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11259
11894
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
11260
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11895
+ } else if (choice === "List Current Settings") {
11896
+ const s = loadWorkflowSettings(ctx.cwd);
11897
+ show(pi, `# Mission Sub-agents / Workers\n\n${renderMissionSubagentWorkerSettings(s)}`);
11898
+ }
11261
11899
  }
11262
11900
  }
11263
11901
 
@@ -11273,7 +11911,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11273
11911
  } else if (choice === "Set editConcurrencyMode") {
11274
11912
  const mode = parseEditConcurrencyMode((await ctx.ui.select("Edit concurrency mode", ["sequential", "scoped", "blocked"])) ?? "");
11275
11913
  if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.editConcurrencyMode = mode; }); ctx.ui.notify(`subagents.editConcurrencyMode set to ${mode} in ${r.file}`, "info"); }
11276
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11914
+ } else if (choice === "List Current Settings") {
11915
+ const s = loadWorkflowSettings(ctx.cwd);
11916
+ show(pi, `# Parallelism Settings\n\nParallel Read-Only: ${s.subagents.allowParallelReadOnly !== false ? "enabled" : "disabled"}\nParallel Planning: ${s.subagents.allowParallelPlanning !== false ? "enabled" : "disabled"}\nParallel Execution: ${s.subagents.allowParallelExecution !== false ? "enabled" : "disabled"}\nParallel Repair: ${s.subagents.allowParallelRepair !== false ? "enabled" : "disabled"}\nParallel Review: ${s.subagents.allowParallelReview !== false ? "enabled" : "disabled"}\nParallel Validation: ${s.subagents.allowParallelValidation !== false ? "enabled" : "disabled"}\nParallel File Edits: ${s.subagents.allowParallelEdits !== false ? "enabled" : "disabled"}\nEdit Concurrency Mode: ${s.subagents.editConcurrencyMode ?? "sequential"}\nConflict Protection: ${s.subagents.requireParallelEditConflictProtection !== false ? "enabled" : "disabled"}`);
11917
+ }
11277
11918
  }
11278
11919
  }
11279
11920
 
@@ -11306,7 +11947,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11306
11947
  async function selectCompactionModel(ctx: ExtensionContext) {
11307
11948
  const selected = await selectProviderAndModel(ctx, "Compaction model");
11308
11949
  if (!selected) return;
11309
- const result = updateSettings(ctx.cwd, "global", (s) => {
11950
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11310
11951
  s.context.compactionMode = "custom_model";
11311
11952
  s.context.compactionModelProvider = selected.provider;
11312
11953
  s.context.compactionModel = selected.model;
@@ -11324,7 +11965,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11324
11965
  const modelChoice = await ctx.ui.select(`Compaction model for ${provider}:`, providerModels.map((model) => `${provider}/${model}`));
11325
11966
  if (!modelChoice) return;
11326
11967
  const model = modelChoice.replace(`${provider}/`, "");
11327
- const result = updateSettings(ctx.cwd, "global", (s) => {
11968
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11328
11969
  s.context.compactionMode = "custom_model";
11329
11970
  s.context.compactionModel = model;
11330
11971
  s.context.customCompactionEnabled = true;
@@ -11338,7 +11979,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11338
11979
  if (!choice) return;
11339
11980
  const agent = choice === "Custom agent name" ? (await ctx.ui.input("Custom compaction agent:", "agent-name"))?.trim() : choice;
11340
11981
  if (!agent) return;
11341
- const result = updateSettings(ctx.cwd, "global", (s) => {
11982
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11342
11983
  s.context.compactionMode = "custom_agent";
11343
11984
  s.context.compactionAgent = agent;
11344
11985
  s.context.customCompactionEnabled = true;
@@ -11355,56 +11996,52 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11355
11996
  const mode = parseCompactionMode((await ctx.ui.select("Compaction mode", ["Pi default", "Custom model", "Disabled"])) ?? "");
11356
11997
  if (!mode) continue;
11357
11998
  if (mode === "custom_model") { await selectCompactionModel(ctx); continue; }
11358
- const r = updateSettings(ctx.cwd, "global", (s) => {
11999
+ const r = updateSettings(ctx.cwd, undefined, (s) => {
11359
12000
  s.context.compactionMode = mode;
11360
12001
  s.context.customCompactionEnabled = false;
11361
12002
  });
11362
12003
  ctx.ui.notify(`Compaction mode set to ${compactionModeLabel(mode)} in ${r.file}`, "info");
11363
- } else if (choice === "Compaction Check Mode") {
11364
- const checkMode = parseWorkflowCompactionCheckMode((await ctx.ui.select("Compaction check mode", ["Boundary only", "In-session"])) ?? "");
11365
- if (!checkMode) continue;
11366
- const r = updateSettings(ctx.cwd, "global", (s) => { s.context.workflowCompactionCheckMode = checkMode; });
11367
- ctx.ui.notify(`Compaction check mode set to ${workflowCompactionCheckModeLabel(checkMode)} in ${r.file}`, "info");
11368
12004
  } else if (choice === "Compaction Provider") {
11369
12005
  await selectCompactionModel(ctx);
11370
12006
  } else if (choice === "Compaction Model") {
11371
12007
  await selectCompactionModelForCurrentProvider(ctx);
11372
- } else if (choice === "Compaction Agent") {
11373
- await selectCompactionAgent(ctx);
11374
12008
  } else if (choice === "Custom Compaction Enabled") {
11375
12009
  const enabled = await chooseBool(ctx, "Custom compaction enabled?");
11376
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.customCompactionEnabled = enabled; }); ctx.ui.notify(`Custom compaction set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Custom model routing uses the session compaction hook when configured; Pi default fallback remains enabled.`, "info"); }
12010
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionEnabled = enabled; }); ctx.ui.notify(`Custom compaction set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Custom model routing uses the session compaction hook when configured; Pi default fallback remains enabled.`, "info"); }
11377
12011
  } else if (choice === "Workflow Auto Trigger Enabled") {
11378
12012
  const enabled = await chooseBool(ctx, "Workflow proactive auto-compaction trigger enabled?");
11379
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.autoCompactionEnabled = enabled; }); ctx.ui.notify(`Workflow auto trigger set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Pi default auto-compaction remains available as fallback.`, "info"); }
12013
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.autoCompactionEnabled = enabled; }); ctx.ui.notify(`Workflow auto trigger set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Pi default auto-compaction remains available as fallback.`, "info"); }
11380
12014
  } else if (choice === "Workflow Trigger Percent") {
11381
12015
  const raw = String((await ctx.ui.input("Workflow compaction trigger percent (50-95, default, or reset)", String(compactionTriggerPercent(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
11382
12016
  if (raw === "default" || raw === "reset") {
11383
12017
  const fallback = defaultCompactionTriggerPercent();
11384
- const r = updateSettings(ctx.cwd, "global", (s) => { s.context.compactionTriggerPercent = fallback; });
12018
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionTriggerPercent = fallback; });
11385
12019
  ctx.ui.notify(`Workflow compaction trigger percent reset to default ${fallback}% in ${r.file}`, "info");
11386
12020
  } else {
11387
12021
  const count = Number(raw);
11388
- if (Number.isInteger(count) && count >= 50 && count <= 95) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.compactionTriggerPercent = count; }); ctx.ui.notify(`Workflow compaction trigger percent set to ${count}% in ${r.file}`, "info"); }
12022
+ if (Number.isInteger(count) && count >= 50 && count <= 95) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionTriggerPercent = count; }); ctx.ui.notify(`Workflow compaction trigger percent set to ${count}% in ${r.file}`, "info"); }
11389
12023
  else ctx.ui.notify("Trigger percent must be an integer from 50 to 95, default, or reset.", "error");
11390
12024
  }
11391
12025
  } else if (choice === "Workflow Trigger Cooldown") {
11392
12026
  const count = Number((await ctx.ui.input("Minimum minutes between Workflow Suite proactive compaction attempts", String(compactionCooldownMinutes(loadWorkflowSettings(ctx.cwd)))) ?? ""));
11393
- if (Number.isInteger(count) && count >= 0 && count <= 240) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.compactionCooldownMinutes = count; }); ctx.ui.notify(`Compaction cooldown set to ${count} minute(s) in ${r.file}. This is the minimum wait between proactive compaction attempts, not a delay before compaction starts.`, "info"); }
12027
+ if (Number.isInteger(count) && count >= 0 && count <= 240) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionCooldownMinutes = count; }); ctx.ui.notify(`Compaction cooldown set to ${count} minute(s) in ${r.file}. This is the minimum wait between proactive compaction attempts, not a delay before compaction starts.`, "info"); }
11394
12028
  else ctx.ui.notify("Cooldown must be an integer from 0 to 240 minutes.", "error");
11395
12029
  } else if (choice === "Custom Reserve Tokens") {
11396
12030
  const raw = String((await ctx.ui.input("Custom compaction reserve tokens (4096-65536, default, or reset)", String(customCompactionReserveTokens(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
11397
12031
  const count = Number(raw);
11398
- if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.customCompactionReserveTokens = DEFAULT_PI_COMPACTION_RESERVE_TOKENS; }); ctx.ui.notify(`Custom compaction reserve tokens reset to ${DEFAULT_PI_COMPACTION_RESERVE_TOKENS.toLocaleString()} in ${r.file}`, "info"); }
11399
- else if (Number.isInteger(count) && count >= 4096 && count <= 65536) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.customCompactionReserveTokens = count; }); ctx.ui.notify(`Custom compaction reserve tokens set to ${count.toLocaleString()} in ${r.file}`, "info"); }
12032
+ if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionReserveTokens = DEFAULT_PI_COMPACTION_RESERVE_TOKENS; }); ctx.ui.notify(`Custom compaction reserve tokens reset to ${DEFAULT_PI_COMPACTION_RESERVE_TOKENS.toLocaleString()} in ${r.file}`, "info"); }
12033
+ else if (Number.isInteger(count) && count >= 4096 && count <= 65536) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionReserveTokens = count; }); ctx.ui.notify(`Custom compaction reserve tokens set to ${count.toLocaleString()} in ${r.file}`, "info"); }
11400
12034
  else ctx.ui.notify("Reserve tokens must be an integer from 4096 to 65536, default, or reset.", "error");
11401
12035
  } else if (choice === "Custom Keep Recent Tokens") {
11402
12036
  const raw = String((await ctx.ui.input("Custom compaction keep-recent tokens (1000-200000, default, or reset)", String(customCompactionKeepRecentTokens(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
11403
12037
  const count = Number(raw);
11404
- if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.customCompactionKeepRecentTokens = DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS; }); ctx.ui.notify(`Custom compaction keep-recent tokens reset to ${DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS.toLocaleString()} in ${r.file}`, "info"); }
11405
- else if (Number.isInteger(count) && count >= 1000 && count <= 200000) { const r = updateSettings(ctx.cwd, "global", (s) => { s.context.customCompactionKeepRecentTokens = count; }); ctx.ui.notify(`Custom compaction keep-recent tokens set to ${count.toLocaleString()} in ${r.file}`, "info"); }
12038
+ if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionKeepRecentTokens = DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS; }); ctx.ui.notify(`Custom compaction keep-recent tokens reset to ${DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS.toLocaleString()} in ${r.file}`, "info"); }
12039
+ else if (Number.isInteger(count) && count >= 1000 && count <= 200000) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionKeepRecentTokens = count; }); ctx.ui.notify(`Custom compaction keep-recent tokens set to ${count.toLocaleString()} in ${r.file}`, "info"); }
11406
12040
  else ctx.ui.notify("Keep-recent tokens must be an integer from 1000 to 200000, default, or reset.", "error");
11407
- } else if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
12041
+ } else if (choice === "List Current Settings") {
12042
+ const s = loadWorkflowSettings(ctx.cwd);
12043
+ show(pi, `# Shared Compaction\n\nCompaction Mode: ${s.context.compactionMode ?? "pi_default"}\nCustom Compaction: ${s.context.customCompactionEnabled ? "enabled" : "disabled"}\nProvider: ${s.context.compactionModelProvider ?? "none"}\nModel: ${s.context.compactionModel ?? "none"}\nAuto Trigger: ${s.context.autoCompactionEnabled !== false ? "enabled" : "disabled"}\nTrigger Percent: ${s.context.compactionTriggerPercent ?? "default"}\nCooldown: ${s.context.compactionCooldownMinutes ?? 0} min\nReserve Tokens: ${s.context.customCompactionReserveTokens?.toLocaleString() ?? "default"}\nKeep Recent Tokens: ${s.context.customCompactionKeepRecentTokens?.toLocaleString() ?? "default"}`);
12044
+ }
11408
12045
  }
11409
12046
  }
11410
12047
 
@@ -11553,12 +12190,41 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11553
12190
  } else if (choice === "Repair / Validation Retry") {
11554
12191
  await showWorkflowRepairRetrySettingsMenu(ctx);
11555
12192
  } else if (choice === "Plan Progress / Runtime") {
11556
- const setting = await ctx.ui.select("Plan Progress / Runtime", ["Plan Progress Enabled", "Plan Runtime Enabled", "List Current Settings", "Back"]);
12193
+ const setting = await ctx.ui.select("Plan Progress / Runtime", ["Progress Tracking", "Show Runtime Timer", "Progress Bar Display", "Runtime Budget", "Token Budget", "List Current Settings", "Back"]);
11557
12194
  if (!setting || setting === "Back") continue;
11558
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11559
- const key = setting === "Plan Progress Enabled" ? "planProgressEnabled" : "planRuntimeEnabled";
11560
- const enabled = await chooseBool(ctx, `workflow.${key}?`);
11561
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.workflow as typeof s.workflow & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`workflow.${key} set to ${enabled} in ${r.file}`, "info"); }
12195
+ if (setting === "List Current Settings") {
12196
+ const s = loadWorkflowSettings(ctx.cwd);
12197
+ show(pi, `# Plan Progress / Runtime\n\nProgress Tracking: ${s.workflow.planProgressEnabled !== false ? "enabled" : "disabled"}\nShow Runtime Timer: ${s.workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.workflow.planShowProgressBar !== false ? "graphical" : "numeric"}\nRuntime Budget: ${(s.planning.maxRuntimeHours ?? 0) === 0 ? "unlimited" : `${s.planning.maxRuntimeHours ?? 0} hours`}\nToken Budget: ${(s.planning.maxTokens ?? 0) === 0 ? "unlimited" : (s.planning.maxTokens ?? 0).toLocaleString()}`);
12198
+ continue;
12199
+ }
12200
+ if (setting === "Token Budget") {
12201
+ const current = loadWorkflowSettings(ctx.cwd).planning.maxTokens ?? 0;
12202
+ const choice = await ctx.ui.select("Plan Token Budget", ["Default (unlimited)", "Custom..."]);
12203
+ if (choice === "Default (unlimited)") {
12204
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxTokens = 0; });
12205
+ ctx.ui.notify(`planning.maxTokens set to default (unlimited) in ${r.file}`, "info");
12206
+ } else if (choice === "Custom...") {
12207
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom plan token budget", String(current > 0 ? current : ""))) ?? "");
12208
+ if (count !== undefined && count >= 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxTokens = count; }); ctx.ui.notify(`planning.maxTokens set to ${count === 0 ? "unlimited" : count.toLocaleString()} in ${r.file}`, "info"); }
12209
+ }
12210
+ } else if (setting === "Runtime Budget") {
12211
+ const current = loadWorkflowSettings(ctx.cwd).planning.maxRuntimeHours ?? 0;
12212
+ const choice = await ctx.ui.select("Plan Runtime Budget", ["Default (unlimited)", "Custom..."]);
12213
+ if (choice === "Default (unlimited)") {
12214
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxRuntimeHours = 0; });
12215
+ ctx.ui.notify(`planning.maxRuntimeHours set to default (unlimited) in ${r.file}`, "info");
12216
+ } else if (choice === "Custom...") {
12217
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom plan runtime budget (hours)", String(current > 0 ? current : ""))) ?? "");
12218
+ if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxRuntimeHours = count; }); ctx.ui.notify(`planning.maxRuntimeHours set to ${count} hours in ${r.file}`, "info"); }
12219
+ }
12220
+ } else if (setting === "Progress Bar Display") {
12221
+ const enabled = await chooseBool(ctx, "workflow.planShowProgressBar?");
12222
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.workflow.planShowProgressBar = enabled; }); ctx.ui.notify(`workflow.planShowProgressBar set to ${enabled} in ${r.file}`, "info"); }
12223
+ } else {
12224
+ const key = setting === "Progress Tracking" ? "planProgressEnabled" : "planRuntimeEnabled";
12225
+ const enabled = await chooseBool(ctx, `workflow.${key}?`);
12226
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.workflow as typeof s.workflow & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`workflow.${key} set to ${enabled} in ${r.file}`, "info"); }
12227
+ }
11562
12228
  } else if (choice === "Plan History") {
11563
12229
  const setting = await ctx.ui.select("Plan History", ["Save Plans", "Save Plan History", "Plan History Limit", "Back"]);
11564
12230
  const workflow = loadWorkflowSettings(ctx.cwd).workflow as ReturnType<typeof loadWorkflowSettings>["workflow"] & { savePlans?: boolean; savePlanHistory?: boolean; planHistoryLimit?: number };
@@ -11572,7 +12238,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11572
12238
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.workflow as typeof s.workflow & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`workflow.${key} set to ${enabled} in ${r.file}`, "info"); }
11573
12239
  }
11574
12240
  } else if (choice === "List Current Settings") {
11575
- show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
12241
+ const s = loadWorkflowSettings(ctx.cwd);
12242
+ show(pi, `# Plan Mode Settings\n\n## Plan Models\n${renderPlanModelSettings(s)}\n\n## Plan Mode Flow\nPlanning Depth: ${s.planning.depth}\nProgress Tracking: ${s.workflow.planProgressEnabled !== false ? "enabled" : "disabled"}\nShow Runtime Timer: ${s.workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.workflow.planShowProgressBar !== false ? "graphical" : "numeric"}\nRuntime Budget: ${(s.planning.maxRuntimeHours ?? 0) === 0 ? "unlimited" : `${s.planning.maxRuntimeHours ?? 0} hours`}\nToken Budget: ${(s.planning.maxTokens ?? 0) === 0 ? "unlimited" : (s.planning.maxTokens ?? 0).toLocaleString()}\nRequire Approval Before Execution: ${s.workflow.requireApprovalBeforeExecution !== false ? "enabled" : "disabled"}\nRequire Approval Per Step: ${s.workflow.requireApprovalPerStep === true ? "enabled" : "disabled"}\nValidate After Each Step: ${s.workflow.validateAfterEachStep !== false ? "enabled" : "disabled"}\nValidate After Full Execution: ${s.workflow.validateAfterExecution !== false ? "enabled" : "disabled"}\n\n## Plan Sub-agents\n${renderPlanSubagentWorkerSettings(s)}\n\n## Plan Clarification\nClarification Mode: ${s.planning.clarificationMode}\nInteractive: ${s.planning.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.planning.maxClarificationQuestions}\n\n## Plan Review / Validation\nReviewer: ${s.models.reviewer.enabled !== false ? "enabled" : "disabled"}\nValidator: ${s.models.validator.enabled !== false ? "enabled" : "disabled"}\nAuto Run Reviewer: ${s.workflow.autoRunReviewerBeforeExecute !== false ? "enabled" : "disabled"}\nAuto Run Validation: ${s.workflow.autoRunValidationAfterExecute !== false ? "enabled" : "disabled"}\nAuto Repair Review Failures: ${s.workflow.autoRepairReviewFailures === true ? "enabled" : "disabled"}\nRepair / Retry Master Toggle: ${s.workflow.repairRetry?.enabled !== false ? "enabled" : "disabled"}\nMax Total Retries: ${s.workflow.repairRetry?.maxTotalRetries ?? "unlimited"}\n\n## Plan History\nSave Plans: ${(s.workflow as Record<string, unknown>).savePlans !== false ? "enabled" : "disabled"}\nSave Plan History: ${(s.workflow as Record<string, unknown>).savePlanHistory !== false ? "enabled" : "disabled"}\nPlan History Limit: ${(s.workflow as Record<string, unknown>).planHistoryLimit ?? 50}`);
11576
12243
  }
11577
12244
  }
11578
12245
  }
@@ -11580,9 +12247,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11580
12247
  async function showStandardTodoSettingsMenu(ctx: ExtensionContext) {
11581
12248
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11582
12249
  while (ctx.hasUI) {
11583
- const setting = await ctx.ui.select("Standard To Do", ["Automatic To Do Enabled", "To Do Trigger Mode", "To Do Progress Visible", "List Current Settings", "Back"]);
12250
+ const setting = await ctx.ui.select("Standard To Do", ["Automatic To Do Enabled", "To Do Trigger Mode", "To Do Progress Visible", "Back"]);
11584
12251
  if (!setting || setting === "Back") return;
11585
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11586
12252
  if (setting === "To Do Trigger Mode") {
11587
12253
  const mode = parseStandardTodoTriggerMode((await ctx.ui.select("To Do Trigger Mode", ["Disabled", "On request", "Automatic when useful", "Required"])) ?? "");
11588
12254
  if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.todoTriggerMode = mode; s.standard.autoTodoEnabled = mode !== "off"; }); ctx.ui.notify(`To Do Trigger Mode set to ${standardTodoTriggerModeLabel(mode)} in ${r.file}`, "info"); }
@@ -11597,9 +12263,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11597
12263
  async function showStandardClarificationSettingsMenu(ctx: ExtensionContext) {
11598
12264
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11599
12265
  while (ctx.hasUI) {
11600
- const setting = await ctx.ui.select("Standard Clarification", ["Clarification Mode", "Interactive Clarification", "Max Clarification Questions", "Timing", "Quality Gate", "Allow Without Analysis", "Use Sub-agents Before Clarification", "List Current Settings", "Back"]);
12266
+ const setting = await ctx.ui.select("Standard Clarification", ["Clarification Mode", "Interactive Clarification", "Max Clarification Questions", "Timing", "Quality Gate", "Allow Without Analysis", "Use Sub-agents Before Clarification", "Back"]);
11601
12267
  if (!setting || setting === "Back") return;
11602
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11603
12268
  if (setting === "Clarification Mode") {
11604
12269
  const mode = parseStandardClarificationMode((await ctx.ui.select("Standard clarification mode", ["auto", "always_for_nontrivial", "never"])) ?? "");
11605
12270
  if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.clarificationMode = mode; s.standard.clarificationEnabled = mode !== "never"; }); ctx.ui.notify(`standard.clarificationMode set to ${mode} in ${r.file}`, "info"); }
@@ -11623,12 +12288,28 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11623
12288
  async function showStandardProgressRuntimeSettingsMenu(ctx: ExtensionContext) {
11624
12289
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11625
12290
  while (ctx.hasUI) {
11626
- const setting = await ctx.ui.select("Standard Progress / Runtime", ["Status Widget Visible", "To Do Progress Visible", "List Current Settings", "Back"]);
12291
+ const setting = await ctx.ui.select("Standard Progress / Runtime", ["Status Widget Visible", "To Do Progress Visible", "Token Budget", "List Current Settings", "Back"]);
11627
12292
  if (!setting || setting === "Back") return;
11628
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11629
- const key = setting === "Status Widget Visible" ? "statusWidgetVisible" : "todoProgressVisible";
11630
- const enabled = await chooseBool(ctx, `standard.${key}?`);
11631
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.standard as typeof s.standard & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`standard.${key} set to ${enabled} in ${r.file}`, "info"); }
12293
+ if (setting === "List Current Settings") {
12294
+ const s = loadWorkflowSettings(ctx.cwd);
12295
+ show(pi, `# Standard Progress / Runtime\n\nStatus Widget Visible: ${s.standard.statusWidgetVisible !== false ? "enabled" : "disabled"}\nTo Do Progress Visible: ${s.standard.todoProgressVisible !== false ? "enabled" : "disabled"}\nToken Budget: ${(s.standard.maxTokens ?? 0) === 0 ? "unlimited" : (s.standard.maxTokens ?? 0).toLocaleString()}`);
12296
+ continue;
12297
+ }
12298
+ if (setting === "Token Budget") {
12299
+ const current = loadWorkflowSettings(ctx.cwd).standard.maxTokens ?? 0;
12300
+ const choice = await ctx.ui.select("Standard Token Budget", ["Default (unlimited)", "Custom..."]);
12301
+ if (choice === "Default (unlimited)") {
12302
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.maxTokens = 0; });
12303
+ ctx.ui.notify(`standard.maxTokens set to default (unlimited) in ${r.file}`, "info");
12304
+ } else if (choice === "Custom...") {
12305
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom standard token budget", String(current > 0 ? current : ""))) ?? "");
12306
+ if (count !== undefined && count >= 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.maxTokens = count; }); ctx.ui.notify(`standard.maxTokens set to ${count === 0 ? "unlimited" : count.toLocaleString()} in ${r.file}`, "info"); }
12307
+ }
12308
+ } else {
12309
+ const key = setting === "Status Widget Visible" ? "statusWidgetVisible" : "todoProgressVisible";
12310
+ const enabled = await chooseBool(ctx, `standard.${key}?`);
12311
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.standard as typeof s.standard & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`standard.${key} set to ${enabled} in ${r.file}`, "info"); }
12312
+ }
11632
12313
  }
11633
12314
  }
11634
12315
 
@@ -11637,7 +12318,11 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11637
12318
  while (ctx.hasUI) {
11638
12319
  const choice = await ctx.ui.select("Standard Mode Settings", ["Standard Models", "Enable / Disable Standard Mode", "Standard To Do", "Standard Clarification", "Standard Sub-agents / Workers", "Standard Progress / Runtime", "List Current Settings", "Back"]);
11639
12320
  if (!choice || choice === "Back") return;
11640
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
12321
+ if (choice === "List Current Settings") {
12322
+ const s = loadWorkflowSettings(ctx.cwd);
12323
+ show(pi, `# Standard Mode Settings\n\nStandard Mode: ${s.standard.enabled !== false ? "enabled" : "disabled"}\nStatus Widget: ${s.standard.statusWidgetVisible !== false ? "enabled" : "disabled"}\nTo Do Progress: ${s.standard.todoProgressVisible !== false ? "enabled" : "disabled"}\nToken Budget: ${(s.standard.maxTokens ?? 0) === 0 ? "unlimited" : (s.standard.maxTokens ?? 0).toLocaleString()}\nSub-agents: ${s.standard.allowSubagents !== false ? "enabled" : "disabled"}\nAgent Scope: ${s.standard.subagentScope ?? "user"}\n\n## Standard Clarification\nMode: ${s.standard.clarificationMode ?? "auto"}\nInteractive: ${s.standard.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.standard.maxClarificationQuestions ?? 3}\n\n## Standard Models\nExecutor Model: ${s.standard.useSharedExecutorModel !== false ? "shared executor" : "standard-specific"}\nShared Executor: ${s.standard.useSharedExecutorModel !== false ? "enabled" : "disabled"}`);
12324
+ continue;
12325
+ }
11641
12326
  if (choice === "Enable / Disable Standard Mode") {
11642
12327
  const enabled = await chooseBool(ctx, "standard.enabled?");
11643
12328
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.enabled = enabled; }); ctx.ui.notify(`standard.enabled set to ${enabled} in ${r.file}`, "info"); }
@@ -11725,7 +12410,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11725
12410
  "Auto Run After Approval": "autoRunAfterApproval",
11726
12411
  };
11727
12412
  const key = keyMap[setting];
11728
- const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as Record<string, unknown>)[String(key)] = enabled; });
12413
+ const r = updateSettings(ctx.cwd, undefined, (s) => {
12414
+ (s.missions as Record<string, unknown>)[String(key)] = enabled;
12415
+ if (key === "autoRepairReviewFailures" && enabled && s.missions.reviewRetryMode === "off") {
12416
+ s.missions.reviewRetryMode = "safe_only";
12417
+ }
12418
+ });
11729
12419
  ctx.ui.notify(`missions.${key} set to ${enabled} in ${r.file}`, "info");
11730
12420
  }
11731
12421
  }
@@ -11734,7 +12424,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11734
12424
  async function showMissionRepairRetrySettingsMenu(ctx: ExtensionContext) {
11735
12425
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11736
12426
  while (ctx.hasUI) {
11737
- const setting = await ctx.ui.select("Repair / Validation Retry", ["Review Retry Mode", "Max Review Retries Per Mission", "Auto Repair Validation Failures", "Validation Retry Mode", "Max Retries Per Milestone", "Max Retries Per Mission", "Pause After Validation Failure", "Require Approval For Out-Of-Scope Repair", "Require Approval For Destructive Repair", "Require Approval For Destructive Actions", "Auto Repair Final Validation Failures", "Max Final Validation Retries", "Back"]);
12427
+ const setting = await ctx.ui.select("Repair / Validation Retry", ["Review Retry Mode", "Max Review Retries Per Mission", "Auto Repair Review Failures", "Auto Repair Validation Failures", "Validation Retry Mode", "Max Retries Per Milestone", "Max Retries Per Mission", "Pause After Validation Failure", "Require Approval For Out-Of-Scope Repair", "Require Approval For Destructive Repair", "Require Approval For Destructive Actions", "Auto Repair Final Validation Failures", "Max Final Validation Retries", "Back"]);
11738
12428
  if (!setting || setting === "Back") return;
11739
12429
  if (setting === "Max Review Retries Per Mission") {
11740
12430
  const raw = await ctx.ui.select(setting, ["0", "1", "2", "3", "4", "5", "10"]);
@@ -11753,6 +12443,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11753
12443
  if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, string | undefined>)[key] = mode; }); ctx.ui.notify(`missions.${key} set to ${mode} in ${r.file}`, "info"); }
11754
12444
  } else {
11755
12445
  const keyMap: Record<string, string> = {
12446
+ "Auto Repair Review Failures": "autoRepairReviewFailures",
11756
12447
  "Auto Repair Validation Failures": "autoRepairValidationFailures",
11757
12448
  "Pause After Validation Failure": "pauseAfterValidationFailure",
11758
12449
  "Require Approval For Out-Of-Scope Repair": "requireApprovalForOutOfScopeRepair",
@@ -11762,7 +12453,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11762
12453
  };
11763
12454
  const key = keyMap[setting];
11764
12455
  const enabled = await chooseBool(ctx, `${String(key)}?`);
11765
- if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as Record<string, unknown>)[String(key)] = enabled; }); ctx.ui.notify(`missions.${String(key)} set to ${enabled} in ${r.file}`, "info"); }
12456
+ if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as Record<string, unknown>)[String(key)] = enabled; if (key === "autoRepairReviewFailures" && enabled && s.missions.reviewRetryMode === "off") { s.missions.reviewRetryMode = "safe_only"; } }); ctx.ui.notify(`missions.${String(key)} set to ${enabled} in ${r.file}`, "info"); }
11766
12457
  }
11767
12458
  }
11768
12459
  }
@@ -11839,22 +12530,55 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11839
12530
  } else if (choice === "Mission Sub-agents / Workers" || choice === "Mission Worker Targets") {
11840
12531
  await showMissionSubagentWorkerSettingsMenu(ctx);
11841
12532
  } else if (choice === "Mission Progress / Runtime") {
11842
- const setting = await ctx.ui.select("Mission Progress / Runtime", ["Progress Widget", "Progress Bar Display", "Runtime Budget", "Checkpoint Interval", "Heartbeat / Watchdog", "Back"]);
12533
+ const setting = await ctx.ui.select("Mission Progress / Runtime", ["Progress Widget", "Progress Bar Display", "Runtime Budget", "Token Budget", "Checkpoint Interval", "Heartbeat / Watchdog", "List Current Settings", "Back"]);
11843
12534
  if (!setting || setting === "Back") continue;
12535
+ if (setting === "List Current Settings") {
12536
+ const s = loadWorkflowSettings(ctx.cwd);
12537
+ show(pi, `# Mission Progress / Runtime\n\nProgress Widget: ${s.missions.progressWidgetEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.missions.showProgressBar !== false ? "enabled" : "disabled"}\nRuntime Budget: ${s.missions.maxRuntimeHours} hours\nToken Budget: ${(s.missions.maxTokens ?? 0) === 0 ? "unlimited" : (s.missions.maxTokens ?? 0).toLocaleString()}\nCheckpoint Interval: ${s.missions.checkpointIntervalMinutes} minutes\nHeartbeat: ${s.missions.heartbeatEnabled !== false ? "enabled" : "disabled"}\nWatchdog: ${s.missions.watchdogEnabled === true ? "enabled" : "disabled"}${s.missions.watchdogEnabled === true ? ` (stale after ${s.missions.watchdogStaleMinutes ?? 30}m)` : ""}`);
12538
+ continue;
12539
+ }
11844
12540
  if (setting === "Progress Widget" || setting === "Progress Bar Display") {
11845
12541
  const key = setting === "Progress Widget" ? "progressWidgetEnabled" : "showProgressBar";
11846
12542
  const enabled = await chooseBool(ctx, `missions.${key}?`);
11847
12543
  if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`missions.${key} set to ${enabled} in ${r.file}`, "info"); }
11848
12544
  } else if (setting === "Heartbeat / Watchdog") await showHeartbeatWatchdogMenu(ctx);
11849
- else {
11850
- const key = setting === "Runtime Budget" ? "maxRuntimeHours" : "checkpointIntervalMinutes";
11851
- const count = parsePositiveInt((await ctx.ui.input(key, key === "maxRuntimeHours" ? "13" : "30")) ?? "");
11852
- if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, number>)[key] = count; }); ctx.ui.notify(`missions.${key} set to ${count} in ${r.file}`, "info"); }
12545
+ else if (setting === "Token Budget") {
12546
+ const current = loadWorkflowSettings(ctx.cwd).missions.maxTokens ?? 0;
12547
+ const choice = await ctx.ui.select("Mission Token Budget", ["Default (unlimited)", "Custom..."]);
12548
+ if (choice === "Default (unlimited)") {
12549
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxTokens = 0; });
12550
+ ctx.ui.notify(`missions.maxTokens set to default (unlimited) in ${r.file}`, "info");
12551
+ } else if (choice === "Custom...") {
12552
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom mission token budget", String(current > 0 ? current : ""))) ?? "");
12553
+ if (count !== undefined && count >= 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxTokens = count; }); ctx.ui.notify(`missions.maxTokens set to ${count === 0 ? "unlimited" : count.toLocaleString()} in ${r.file}`, "info"); }
12554
+ }
12555
+ } else if (setting === "Runtime Budget") {
12556
+ const current = loadWorkflowSettings(ctx.cwd).missions.maxRuntimeHours;
12557
+ const choice = await ctx.ui.select("Mission Runtime Budget", ["Default (13 hours)", "Custom..."]);
12558
+ if (choice === "Default (13 hours)") {
12559
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxRuntimeHours = 13; });
12560
+ ctx.ui.notify(`missions.maxRuntimeHours set to default (13 hours) in ${r.file}`, "info");
12561
+ } else if (choice === "Custom...") {
12562
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom runtime budget (hours)", String(current !== 13 ? current : ""))) ?? "");
12563
+ if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxRuntimeHours = count; }); ctx.ui.notify(`missions.maxRuntimeHours set to ${count} hours in ${r.file}`, "info"); }
12564
+ }
12565
+ } else {
12566
+ const count = parsePositiveInt((await ctx.ui.input("checkpointIntervalMinutes", "30")) ?? "");
12567
+ if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.checkpointIntervalMinutes = count; }); ctx.ui.notify(`missions.checkpointIntervalMinutes set to ${count} in ${r.file}`, "info"); }
12568
+ }
12569
+ } else if (choice === "Runtime Budget") {
12570
+ const current = loadWorkflowSettings(ctx.cwd).missions.maxRuntimeHours;
12571
+ const choice2 = await ctx.ui.select("Mission Runtime Budget", ["Default (13 hours)", "Custom..."]);
12572
+ if (choice2 === "Default (13 hours)") {
12573
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxRuntimeHours = 13; });
12574
+ ctx.ui.notify(`missions.maxRuntimeHours set to default (13 hours) in ${r.file}`, "info");
12575
+ } else if (choice2 === "Custom...") {
12576
+ const count = parsePositiveInt((await ctx.ui.input("Enter custom runtime budget (hours)", String(current !== 13 ? current : ""))) ?? "");
12577
+ if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.maxRuntimeHours = count; }); ctx.ui.notify(`missions.maxRuntimeHours set to ${count} hours in ${r.file}`, "info"); }
11853
12578
  }
11854
- } else if (choice === "Runtime Budget" || choice === "Checkpoint Interval") {
11855
- const key = choice === "Runtime Budget" ? "maxRuntimeHours" : "checkpointIntervalMinutes";
11856
- const count = parsePositiveInt((await ctx.ui.input(key, key === "maxRuntimeHours" ? "13" : "30")) ?? "");
11857
- if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, number>)[key] = count; }); ctx.ui.notify(`missions.${key} set to ${count} in ${r.file}`, "info"); }
12579
+ } else if (choice === "Checkpoint Interval") {
12580
+ const count = parsePositiveInt((await ctx.ui.input("checkpointIntervalMinutes", "30")) ?? "");
12581
+ if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.checkpointIntervalMinutes = count; }); ctx.ui.notify(`missions.checkpointIntervalMinutes set to ${count} in ${r.file}`, "info"); }
11858
12582
  } else if (choice === "Approval Safety" || choice === "Validation Per Milestone" || choice === "Full Auto Safety" || choice === "Auto Resume") {
11859
12583
  const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["missions"]> = { "Validation Per Milestone": "requireValidationPerMilestone", "Approval Safety": "requireApprovalForDestructiveActions", "Full Auto Safety": "allowFullAuto", "Auto Resume": "autoResume" };
11860
12584
  const key = keyMap[choice];
@@ -11871,7 +12595,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11871
12595
  } else if (choice === "Heartbeat / Watchdog" || choice === "Heartbeat / Watchdog Status") {
11872
12596
  await showHeartbeatWatchdogMenu(ctx);
11873
12597
  } else if (choice === "List Current Settings") {
11874
- show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
12598
+ const s = loadWorkflowSettings(ctx.cwd);
12599
+ show(pi, `# Mission Mode Settings\n\n## Mission Models\n${renderMissionModelStrategy(s)}\n\n## Mission Mode Flow\nMissions: ${s.missions.enabled !== false ? "enabled" : "disabled"}\nDefault Autonomy: ${s.missions.defaultAutonomy}\nAllow Full Auto: ${s.missions.allowFullAuto === true ? "enabled" : "disabled"}\nAuto Run After Approval: ${s.missions.autoRunAfterApproval !== false ? "enabled" : "disabled"}\nContinue Across Milestones: ${s.missions.continueAcrossMilestones !== false ? "enabled" : "disabled"}\nPause Between Milestones: ${s.missions.pauseBetweenMilestones !== false ? "enabled" : "disabled"}\nPlanning Depth: ${s.missions.planningDepth ?? "standard"}\n\n## Mission Clarification\nClarification Mode: ${s.missions.clarificationMode ?? "auto"}\nInteractive: ${s.missions.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.missions.maxClarificationQuestions ?? 3}\n\n## Mission Sub-agents\n${renderMissionSubagentWorkerSettings(s)}\n\n## Mission Progress / Runtime\nProgress Widget: ${s.missions.progressWidgetEnabled !== false ? "enabled" : "disabled"}\nProgress Bar: ${s.missions.showProgressBar !== false ? "enabled" : "disabled"}\nRuntime Budget: ${s.missions.maxRuntimeHours} hours\nToken Budget: ${(s.missions.maxTokens ?? 0) === 0 ? "unlimited" : (s.missions.maxTokens ?? 0).toLocaleString()}\nCheckpoint Interval: ${s.missions.checkpointIntervalMinutes} minutes\n\n## Heartbeat / Watchdog\n${renderHeartbeatWatchdogStatus(s)}`);
11875
12600
  }
11876
12601
  }
11877
12602
  }
@@ -11883,10 +12608,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11883
12608
  async function showGlobalSafetySettingsMenu(ctx: ExtensionContext) {
11884
12609
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11885
12610
  while (ctx.hasUI) {
11886
- const choice = await ctx.ui.select("Global Safety", ["Repo Lock (Project)", "Disable Bash In Plan Mode", "Disable Bash In Validator Mode", "Block Destructive Commands", "List Current Settings", "Back"]);
12611
+ const choice = await ctx.ui.select("Global Safety", ["Repo Lock (Project)", "Disable Bash In Plan Mode", "Disable Bash In Validator Mode", "Block Destructive Commands", "Allow Package Install In Execution", "List Current Settings", "Back"]);
11887
12612
  if (!choice || choice === "Back") return;
11888
- const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["safety"]> = { "Disable Bash In Plan Mode": "disableBashInPlanMode", "Disable Bash In Validator Mode": "disableBashInValidatorMode", "Block Destructive Commands": "blockDestructiveCommands" };
11889
- if (choice === "List Current Settings") show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
12613
+ const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["safety"]> = { "Disable Bash In Plan Mode": "disableBashInPlanMode", "Disable Bash In Validator Mode": "disableBashInValidatorMode", "Block Destructive Commands": "blockDestructiveCommands", "Allow Package Install In Execution": "allowPackageInstallInExecution" };
12614
+ if (choice === "List Current Settings") show(pi, renderSafetySettings(loadWorkflowSettings(ctx.cwd)));
11890
12615
  else if (choice === "Repo Lock (Project)") {
11891
12616
  const enabled = await chooseBool(ctx, "safety.repoLockEnabled for this project only?");
11892
12617
  if (enabled !== undefined) {
@@ -12365,6 +13090,7 @@ Pi Version: v${VERSION}
12365
13090
  const observed = subagentUsageByPhase[phase] ?? 0;
12366
13091
  if (observed >= required) return;
12367
13092
  if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") {
13093
+ if (phase === "Repair") return;
12368
13094
  const label = state.mode === "standard" ? "Standard Mode" : phase;
12369
13095
  return { block: true, reason: `${label} ${event.toolName} is blocked because required forced ${phase.toLowerCase()} sub-agent use has not been observed. Required workers: ${required}; observed successful workers: ${observed}.` };
12370
13096
  }
@@ -12557,6 +13283,7 @@ Pi Version: v${VERSION}
12557
13283
  });
12558
13284
 
12559
13285
  pi.on("tool_execution_start", async (event, ctx) => {
13286
+ activateCurrentPlanStepForTool(ctx, { toolName: event.toolName, input: event.args });
12560
13287
  workflowActiveToolExecutions += 1;
12561
13288
  if (event.toolName === "subagent") beginSubagentActivity(event.toolCallId, event.args as Record<string, unknown>, ctx);
12562
13289
  });
@@ -12627,6 +13354,13 @@ Pi Version: v${VERSION}
12627
13354
  if (sub === "archive" || sub === "abandon") return archiveAndReturnToPlanInput(ctx, `plan ${sub} requested by /plan ${sub}`);
12628
13355
  if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /plan approve"); return; }
12629
13356
  if (sub === "revise") { const feedback = rest.join(" ").trim(); if (!state.draftPlan && !state.approvedPlan) return show(pi, "# No plan to revise."); return beginPlanning(ctx, state.task ?? "Revise", state.draftPlan ?? state.approvedPlan, feedback); }
13357
+ if (sub === "complete") {
13358
+ if (state.mode !== "validated") return show(pi, "# Not in validated state. No plan to complete.");
13359
+ if (state.validationVerdict !== "PASS" && state.validationVerdict !== "PARTIAL PASS") return show(pi, `# Cannot complete\n\nValidation verdict is ${state.validationVerdict ?? "unknown"}. Only PASS or PARTIAL PASS can be completed.`);
13360
+ if (state.validationVerdict === "PARTIAL PASS" && state.concreteRepairableIssue !== false) return show(pi, "# Cannot complete\n\nPARTIAL PASS with concrete repairable issues. Run /plan repair first.");
13361
+ await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
13362
+ return;
13363
+ }
12630
13364
  if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
12631
13365
  await startFreshPlanFromInput(ctx, raw);
12632
13366
  };
@@ -12698,6 +13432,13 @@ Pi Version: v${VERSION}
12698
13432
  if (sub === "resume") return handlePlanResume(ctx);
12699
13433
  if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /p approve"); return; }
12700
13434
  if (sub === "revise") { const feedback = rest.join(" ").trim(); if (!state.draftPlan && !state.approvedPlan) return show(pi, "# No plan to revise."); return beginPlanning(ctx, state.task ?? "Revise", state.draftPlan ?? state.approvedPlan, feedback); }
13435
+ if (sub === "complete") {
13436
+ if (state.mode !== "validated") return show(pi, "# Not in validated state. No plan to complete.");
13437
+ if (state.validationVerdict !== "PASS" && state.validationVerdict !== "PARTIAL PASS") return show(pi, `# Cannot complete\n\nValidation verdict is ${state.validationVerdict ?? "unknown"}. Only PASS or PARTIAL PASS can be completed.`);
13438
+ if (state.validationVerdict === "PARTIAL PASS" && state.concreteRepairableIssue !== false) return show(pi, "# Cannot complete\n\nPARTIAL PASS with concrete repairable issues. Run /plan repair first.");
13439
+ await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
13440
+ return;
13441
+ }
12701
13442
  if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
12702
13443
  await startFreshPlanFromInput(ctx, raw);
12703
13444
  };
@@ -13255,6 +13996,18 @@ Pi Version: v${VERSION}
13255
13996
  const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxClarificationQuestions = count; });
13256
13997
  return show(pi, updatedMessage(result.scope, result.file, "planning.maxClarificationQuestions", String(count)));
13257
13998
  }
13999
+ if (subject === "planning" && key === "maxTokens") {
14000
+ const count = parsePositiveInt(value);
14001
+ if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set planning maxTokens <0 = unlimited | positive integer>`");
14002
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxTokens = count; });
14003
+ return show(pi, updatedMessage(result.scope, result.file, "planning.maxTokens", count === 0 ? "unlimited" : String(count)));
14004
+ }
14005
+ if (subject === "planning" && key === "maxRuntimeHours") {
14006
+ const count = parsePositiveInt(value);
14007
+ if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set planning maxRuntimeHours <0 = unlimited | positive integer>`");
14008
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxRuntimeHours = count; });
14009
+ return show(pi, updatedMessage(result.scope, result.file, "planning.maxRuntimeHours", count === 0 ? "unlimited" : String(count) + " hours"));
14010
+ }
13258
14011
  if (subject === "standard" && (key === "enabled" || key === "autoTodoEnabled" || key === "todoProgressVisible" || key === "clarificationEnabled" || key === "interactiveClarificationEnabled" || key === "clarificationQualityGate" || key === "allowClarificationWithoutAnalysis" || key === "useSubagentsBeforeClarification" || key === "allowSubagents" || key === "statusWidgetVisible" || key === "useSharedExecutorModel" || key === "useStandardSpecificModels")) {
13259
14012
  if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set standard ${key} <true|false>\``);
13260
14013
  const result = updateSettings(ctx.cwd, scope, (s) => {
@@ -13292,6 +14045,12 @@ Pi Version: v${VERSION}
13292
14045
  const result = updateSettings(ctx.cwd, scope, (s) => { s.standard.maxClarificationQuestions = count; });
13293
14046
  return show(pi, updatedMessage(result.scope, result.file, "standard.maxClarificationQuestions", String(count)));
13294
14047
  }
14048
+ if (subject === "standard" && key === "maxTokens") {
14049
+ const count = parsePositiveInt(value);
14050
+ if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set standard maxTokens <0 = unlimited | positive integer>`");
14051
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.standard.maxTokens = count; });
14052
+ return show(pi, updatedMessage(result.scope, result.file, "standard.maxTokens", count === 0 ? "unlimited" : String(count)));
14053
+ }
13295
14054
  if (subject === "standard" && key === "clarificationTiming") {
13296
14055
  const timing = parseClarificationTiming(value);
13297
14056
  if (!timing) return show(pi, "# Error\n\nUsage: `/workflow-settings set standard clarificationTiming <after_initial_analysis|immediate>`");
@@ -13454,11 +14213,11 @@ Pi Version: v${VERSION}
13454
14213
  const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.subagentPolicy = policy; });
13455
14214
  return show(pi, updatedMessage(result.scope, result.file, "missions.subagentPolicy", policy));
13456
14215
  }
13457
- if (subject === "missions" && (key === "maxRuntimeHours" || key === "checkpointIntervalMinutes" || key === "maxClarificationQuestions" || key === "minWorkersForDeep" || key === "minWorkersForMaximum" || key === "watchdogStaleMinutes" || key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries")) {
14216
+ if (subject === "missions" && (key === "maxRuntimeHours" || key === "checkpointIntervalMinutes" || key === "maxClarificationQuestions" || key === "minWorkersForDeep" || key === "minWorkersForMaximum" || key === "watchdogStaleMinutes" || key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" || key === "maxTokens")) {
13458
14217
  const rawCount = value === undefined ? NaN : Number(value);
13459
14218
  const count = Number.isInteger(rawCount) ? rawCount : undefined;
13460
- const max = key === "maxRuntimeHours" ? 168 : key === "checkpointIntervalMinutes" ? 168 : key === "maxClarificationQuestions" ? 6 : key === "watchdogStaleMinutes" ? 1440 : key === "maxValidationRetriesPerMilestone" ? 10 : key === "maxValidationRetriesPerMission" ? 100 : key === "maxFinalValidationRetries" ? 10 : 8;
13461
- const min = key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" ? 0 : 1;
14219
+ const max = key === "maxRuntimeHours" ? 168 : key === "checkpointIntervalMinutes" ? 168 : key === "maxClarificationQuestions" ? 6 : key === "watchdogStaleMinutes" ? 1440 : key === "maxValidationRetriesPerMilestone" ? 10 : key === "maxValidationRetriesPerMission" ? 100 : key === "maxFinalValidationRetries" ? 10 : key === "maxTokens" ? 10000000 : 8;
14220
+ const min = key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" || key === "maxTokens" ? 0 : 1;
13462
14221
  if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set missions ${key} <${min}-${max}>\``);
13463
14222
  const result = updateSettings(ctx.cwd, scope, (s) => { (s.missions as typeof s.missions & Record<string, number | string | boolean>)[key] = count; });
13464
14223
  return show(pi, updatedMessage(result.scope, result.file, `missions.${key}`, String(count)));
@@ -13466,35 +14225,35 @@ Pi Version: v${VERSION}
13466
14225
  if (subject === "context" && key === "compactionMode") {
13467
14226
  const mode = parseCompactionMode(value);
13468
14227
  if (!mode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context compactionMode <pi_default|custom_model|custom_agent|disabled>`");
13469
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.compactionMode = mode; });
14228
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionMode = mode; });
13470
14229
  return show(pi, updatedMessage(result.scope, result.file, "context.compactionMode", compactionModeLabel(mode)));
13471
14230
  }
13472
14231
  if (subject === "context" && key === "customCompactionEnabled") {
13473
14232
  if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context customCompactionEnabled <true|false>`");
13474
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.customCompactionEnabled = bool; });
14233
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.customCompactionEnabled = bool; });
13475
14234
  return show(pi, updatedMessage(result.scope, result.file, "context.customCompactionEnabled", String(bool)));
13476
14235
  }
13477
14236
  if (subject === "context" && key === "autoCompactionEnabled") {
13478
14237
  if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context autoCompactionEnabled <true|false>`");
13479
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.autoCompactionEnabled = bool; });
14238
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.autoCompactionEnabled = bool; });
13480
14239
  return show(pi, updatedMessage(result.scope, result.file, "context.autoCompactionEnabled", String(bool)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13481
14240
  }
13482
14241
  if (subject === "context" && key === "workflowCompactionCheckMode") {
13483
14242
  const checkMode = parseWorkflowCompactionCheckMode(value);
13484
14243
  if (!checkMode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context workflowCompactionCheckMode <boundary|in_session>`");
13485
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.workflowCompactionCheckMode = checkMode; });
14244
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.workflowCompactionCheckMode = checkMode; });
13486
14245
  return show(pi, updatedMessage(result.scope, result.file, "context.workflowCompactionCheckMode", workflowCompactionCheckModeLabel(checkMode)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13487
14246
  }
13488
14247
  if (subject === "context" && (key === "compactionTriggerPercent" || key === "compactionCooldownMinutes" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens")) {
13489
14248
  const normalizedValue = String(value ?? "").trim().toLowerCase();
13490
14249
  if (key === "compactionTriggerPercent" && (normalizedValue === "default" || normalizedValue === "reset")) {
13491
14250
  const fallback = defaultCompactionTriggerPercent();
13492
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.compactionTriggerPercent = fallback; });
14251
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionTriggerPercent = fallback; });
13493
14252
  return show(pi, updatedMessage(result.scope, result.file, "context.compactionTriggerPercent", `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
13494
14253
  }
13495
14254
  if ((key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens") && (normalizedValue === "default" || normalizedValue === "reset")) {
13496
14255
  const fallback = key === "customCompactionReserveTokens" ? DEFAULT_PI_COMPACTION_RESERVE_TOKENS : DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS;
13497
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { (s.context as typeof s.context & Record<string, number>)[key] = fallback; });
14256
+ const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, number>)[key] = fallback; });
13498
14257
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
13499
14258
  }
13500
14259
  const rawCount = value === undefined ? NaN : Number(value);
@@ -13503,12 +14262,12 @@ Pi Version: v${VERSION}
13503
14262
  const max = key === "compactionTriggerPercent" ? 95 : key === "compactionCooldownMinutes" ? 240 : key === "customCompactionReserveTokens" ? 65536 : 200000;
13504
14263
  const usage = key === "compactionTriggerPercent" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens" ? `<${min}-${max}|default|reset>` : `<${min}-${max}>`;
13505
14264
  if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set context ${key} ${usage}\``);
13506
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { (s.context as typeof s.context & Record<string, number>)[key] = count; });
14265
+ const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, number>)[key] = count; });
13507
14266
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, String(count)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13508
14267
  }
13509
14268
  if (subject === "context" && (key === "compactionModelProvider" || key === "compactionModel" || key === "compactionAgent")) {
13510
14269
  const text = parts.slice(offset + 2).join(" ");
13511
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { (s.context as typeof s.context & Record<string, string>)[key] = text; });
14270
+ const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, string>)[key] = text; });
13512
14271
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, text || "(empty)"));
13513
14272
  }
13514
14273
  if (subject === "workflow" && key === "repairRetry.enabled") {
@@ -13576,7 +14335,7 @@ Pi Version: v${VERSION}
13576
14335
  const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.maxReviewRetriesPerMission = count; });
13577
14336
  return show(pi, updatedMessage(result.scope, result.file, "missions.maxReviewRetriesPerMission", String(count)));
13578
14337
  }
13579
- if (subject === "workflow" && (key === "returnToPlanModeAfterWorkflow" || key === "autoRepairValidationFailures" || key === "pauseAfterValidationFailure" || key === "autoRepairReviewFailures" || key === "pauseAfterReviewFailure" || key === "requireApprovalForOutOfScopeRepair" || key === "requireApprovalForDestructiveRepair" || key === "planProgressEnabled" || key === "planRuntimeEnabled" || key === "requireApprovalBeforeExecution" || key === "requireApprovalPerStep" || key === "validateAfterEachStep" || key === "validateAfterExecution")) {
14338
+ if (subject === "workflow" && (key === "returnToPlanModeAfterWorkflow" || key === "autoRepairValidationFailures" || key === "pauseAfterValidationFailure" || key === "autoRepairReviewFailures" || key === "pauseAfterReviewFailure" || key === "requireApprovalForOutOfScopeRepair" || key === "requireApprovalForDestructiveRepair" || key === "planProgressEnabled" || key === "planRuntimeEnabled" || key === "planShowProgressBar" || key === "requireApprovalBeforeExecution" || key === "requireApprovalPerStep" || key === "validateAfterEachStep" || key === "validateAfterExecution")) {
13580
14339
  if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set workflow ${key} <true|false>\``);
13581
14340
  const result = updateSettings(ctx.cwd, scope, (s) => {
13582
14341
  (s.workflow as typeof s.workflow & Record<string, boolean | number | string | undefined>)[key] = bool;
@@ -13804,7 +14563,7 @@ Public workflow commands:
13804
14563
  const archivedId = archiveCurrentPlanIfPresent(ctx, reason);
13805
14564
  const settings = loadWorkflowSettings(ctx.cwd);
13806
14565
  pi.setActiveTools(planToolsFor(settings));
13807
- updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task: undefined, originalTask: undefined, draftPlan: undefined, approvedPlan: undefined, reviewerReport: undefined, reviewerVerdict: undefined, currentReviewRetry: 0, workflowReviewRetryCount: 0, maxReviewRetriesPerPlan: undefined, maxReviewRetriesPerWorkflow: undefined, lastReviewFailure: undefined, lastReviewAttempt: undefined, lastReviewRepairStatus: "none", reviewHistory: undefined, reviewRepairInProgress: undefined, repairRetryState: undefined, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, maxValidationRetriesPerPlan: undefined, maxValidationRetriesPerWorkflow: undefined, lastValidationFailure: undefined, lastRepairAttempt: undefined, repairHistory: undefined, lastRepairStatus: "none", planStepValidationIndex: undefined, planRuntime: undefined, planProgress: undefined, planHistoryId: archivedId ?? state.planHistoryId, approvedPlanHistoryId: undefined, modelsUsed: undefined }, ctx);
14566
+ updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task: undefined, originalTask: undefined, draftPlan: undefined, approvedPlan: undefined, reviewerReport: undefined, reviewerVerdict: undefined, currentReviewRetry: 0, workflowReviewRetryCount: 0, maxReviewRetriesPerPlan: undefined, maxReviewRetriesPerWorkflow: undefined, lastReviewFailure: undefined, lastReviewAttempt: undefined, lastReviewRepairStatus: "none", reviewHistory: undefined, reviewRepairInProgress: undefined, repairRetryState: undefined, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, maxValidationRetriesPerPlan: undefined, maxValidationRetriesPerWorkflow: undefined, lastValidationFailure: undefined, lastRepairAttempt: undefined, repairHistory: undefined, lastRepairStatus: "none", planStepValidationIndex: undefined, planExecutionStepIndex: undefined, planRuntime: undefined, planProgress: undefined, planHistoryId: archivedId ?? state.planHistoryId, approvedPlanHistoryId: undefined, modelsUsed: undefined }, ctx);
13808
14567
  setWorkflowUi(ctx, state, activeSubagents);
13809
14568
  show(pi, `# Plan Archived\n\n${archivedId ? `Archived Plan ID: ${archivedId}\n\n` : ""}Plan Mode is ready for a new planning request.`);
13810
14569
  }
@@ -13812,7 +14571,7 @@ Public workflow commands:
13812
14571
  async function startFreshPlanFromInput(ctx: ExtensionContext, task: string) {
13813
14572
  const archivedId = archiveCurrentPlanIfPresent(ctx, "previous plan archived before starting a new plan");
13814
14573
  activeMission = undefined;
13815
- updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task, originalTask: task, draftPlan: undefined, approvedPlan: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, planningDepth: undefined, clarificationMode: undefined, reviewerReport: undefined, reviewerVerdict: undefined, currentReviewRetry: 0, workflowReviewRetryCount: 0, maxReviewRetriesPerPlan: undefined, maxReviewRetriesPerWorkflow: undefined, lastReviewFailure: undefined, lastReviewAttempt: undefined, lastReviewRepairStatus: "none", reviewHistory: undefined, reviewRepairInProgress: undefined, repairRetryState: undefined, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, maxValidationRetriesPerPlan: undefined, maxValidationRetriesPerWorkflow: undefined, lastValidationFailure: undefined, lastRepairAttempt: undefined, repairHistory: undefined, lastRepairStatus: "none", planStepValidationIndex: undefined, planRuntime: undefined, planProgress: undefined, lastCompletedPlanSummary: state.lastCompletedPlanSummary, modelsUsed: undefined, planHistoryId: undefined, approvedPlanHistoryId: undefined }, ctx);
14574
+ updateState({ mode: "awaiting_plan_input", activeMissionId: undefined, task, originalTask: task, draftPlan: undefined, approvedPlan: undefined, clarifyingQuestions: undefined, clarifyingAnswers: undefined, clarificationAlreadyAsked: undefined, clarificationRequiredBeforePlan: undefined, clarificationRequirementReason: undefined, clarificationSkipReason: undefined, clarificationQualityRetryCount: undefined, planningDepth: undefined, clarificationMode: undefined, reviewerReport: undefined, reviewerVerdict: undefined, currentReviewRetry: 0, workflowReviewRetryCount: 0, maxReviewRetriesPerPlan: undefined, maxReviewRetriesPerWorkflow: undefined, lastReviewFailure: undefined, lastReviewAttempt: undefined, lastReviewRepairStatus: "none", reviewHistory: undefined, reviewRepairInProgress: undefined, repairRetryState: undefined, executionSummary: undefined, validationReport: undefined, validationVerdict: undefined, currentValidationRetry: 0, workflowValidationRetryCount: 0, maxValidationRetriesPerPlan: undefined, maxValidationRetriesPerWorkflow: undefined, lastValidationFailure: undefined, lastRepairAttempt: undefined, repairHistory: undefined, lastRepairStatus: "none", planStepValidationIndex: undefined, planExecutionStepIndex: undefined, planRuntime: undefined, planProgress: undefined, lastCompletedPlanSummary: state.lastCompletedPlanSummary, modelsUsed: undefined, planHistoryId: undefined, approvedPlanHistoryId: undefined }, ctx);
13816
14575
  if (archivedId) recordWorkflowInternalEvent(ctx, `Previous plan archived: ${archivedId}`);
13817
14576
  await beginPlanning(ctx, task);
13818
14577
  }
@@ -13833,6 +14592,23 @@ Public workflow commands:
13833
14592
  else return;
13834
14593
  }
13835
14594
 
14595
+ function isPlanApprovedContinueInput(text: string): boolean {
14596
+ return /^(continue|proceed|execute|run|go|start|start execution|continue plan|run plan|execute plan)$/i.test(text.trim());
14597
+ }
14598
+
14599
+ async function handlePlanApprovedPlainInput(ctx: ExtensionContext, text: string) {
14600
+ if (isPlanApprovedContinueInput(text)) {
14601
+ await handlePlanContinue(ctx);
14602
+ return;
14603
+ }
14604
+ if (!ctx.hasUI) return show(pi, `# Approved Plan Loaded\n\nThe current plan is approved, but plain text is not treated as an execution turn because execution requires a tool-armed handoff.\n\nUse one of:\n- /plan continue\n- /plan revise ${text}\n- /plan cancel\n- /p ${text}`);
14605
+ const choice = await ctx.ui.select("Approved plan is loaded. What should this message do?", ["Continue Current Plan", "Revise Current Plan With This Feedback", "Start New Plan", "Cancel"]);
14606
+ if (choice === "Continue Current Plan") await handlePlanContinue(ctx);
14607
+ else if (choice === "Revise Current Plan With This Feedback") await beginPlanning(ctx, state.task ?? "Revise", state.approvedPlan, text);
14608
+ else if (choice === "Start New Plan") await startFreshPlanFromInput(ctx, text);
14609
+ else return;
14610
+ }
14611
+
13836
14612
  async function handleMissionPlainInputChoice(ctx: ExtensionContext, text: string) {
13837
14613
  const normalized = text.trim().toLowerCase();
13838
14614
  if (normalized === "status") {
@@ -13851,6 +14627,10 @@ Public workflow commands:
13851
14627
  await startFreshPlanFromInput(ctx, text);
13852
14628
  return { action: "handled" as const };
13853
14629
  }
14630
+ if (state.mode === "plan_approved") {
14631
+ await handlePlanApprovedPlainInput(ctx, text);
14632
+ return { action: "handled" as const };
14633
+ }
13854
14634
  if (planInputNeedsChoice(state)) {
13855
14635
  await handlePlanPlainInputChoice(ctx, text);
13856
14636
  return { action: "handled" as const };
@@ -13889,6 +14669,40 @@ Public workflow commands:
13889
14669
  if (workflowSubagentWorkerMode()) return { systemPrompt: event.systemPrompt };
13890
14670
  stopStartupVisual(ctx);
13891
14671
 
14672
+ // ── Token budget enforcement ──────────────────────────
14673
+ const contextTokens = ctx.getContextUsage()?.tokens ?? 0;
14674
+ const settingsForBudget = loadWorkflowSettings(ctx.cwd);
14675
+ if (state.mode === "standard" && (settingsForBudget.standard.maxTokens ?? 0) > 0) {
14676
+ const used = (state.standardTokensUsed ?? 0) + contextTokens;
14677
+ updateState({ standardTokensUsed: used }, ctx);
14678
+ if (used > settingsForBudget.standard.maxTokens!) {
14679
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW STANDARD TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Standard Mode token budget (${settingsForBudget.standard.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this session.` };
14680
+ }
14681
+ }
14682
+ const inPlan = state.mode === "planning" || state.mode === "plan_approved" || state.mode === "reviewed" || state.mode === "executing" || state.mode === "validating" || state.mode === "revalidating" || state.mode === "repairing" || state.mode === "validated" || state.mode === "awaiting_plan_input" || state.mode === "plan_review";
14683
+ if (inPlan && (settingsForBudget.planning.maxTokens ?? 0) > 0) {
14684
+ const used = (state.planTokensUsed ?? 0) + contextTokens;
14685
+ updateState({ planTokensUsed: used }, ctx);
14686
+ if (used > settingsForBudget.planning.maxTokens!) {
14687
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Plan Mode token budget (${settingsForBudget.planning.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this workflow.` };
14688
+ }
14689
+ }
14690
+ if (inPlan && (settingsForBudget.planning.maxRuntimeHours ?? 0) > 0) {
14691
+ const activeMs = planActiveRuntimeMs(state);
14692
+ const maxMs = settingsForBudget.planning.maxRuntimeHours! * 3_600_000;
14693
+ if (activeMs >= maxMs) {
14694
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN RUNTIME BUDGET EXCEEDED: active runtime (${formatDurationMs(activeMs)}) exceeds the configured Plan Mode runtime budget (${settingsForBudget.planning.maxRuntimeHours} hours). The configured budget limit has been reached for this workflow.` };
14695
+ }
14696
+ }
14697
+ const inMission = state.mode === "planning" || state.mode === "plan_ready" || state.mode === "approved" || state.mode === "running" || state.mode === "checkpointing" || state.mode === "validating" || state.mode === "revalidating" || state.mode === "repairing" || state.mode === "paused" || state.mode === "stopped" || state.mode === "awaiting_mission_input" || state.mode === "mission_plan_review";
14698
+ if (inMission && (settingsForBudget.missions.maxTokens ?? 0) > 0) {
14699
+ const used = (state.missionTokensUsed ?? 0) + contextTokens;
14700
+ updateState({ missionTokensUsed: used }, ctx);
14701
+ if (used > settingsForBudget.missions.maxTokens!) {
14702
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MISSION TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Mission Mode token budget (${settingsForBudget.missions.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this mission.` };
14703
+ }
14704
+ }
14705
+
13892
14706
  // Standard Mode: direct active work with optional dynamic To Do tracking.
13893
14707
  if (state.mode === "standard") {
13894
14708
  setStandardRuntimeActive(ctx, true);
@@ -13945,7 +14759,13 @@ Public workflow commands:
13945
14759
 
13946
14760
  // Mission Mode: intercept user message as mission goal
13947
14761
  if (state.mode === "awaiting_mission_input") {
13948
- const goal = event.prompt?.trim() || "Start a mission from the user's requested goal.";
14762
+ const prompt = (event.prompt ?? "").trim();
14763
+ // Guard: skip system-generated content (final summaries, handoff reports, triggered agent turns).
14764
+ // User goals are short natural-language phrases; system content is multi-kilobyte structured markdown.
14765
+ if (prompt.length > 2000 || /^##\s+Final\s+Mission\b/m.test(prompt) || prompt.startsWith("Print this comprehensive")) {
14766
+ return { systemPrompt: event.systemPrompt };
14767
+ }
14768
+ const goal = prompt || "Start a mission from the user's requested goal.";
13949
14769
  await startMissionFromGoal(ctx, goal);
13950
14770
  return { systemPrompt: event.systemPrompt };
13951
14771
  }
@@ -13975,13 +14795,17 @@ Public workflow commands:
13975
14795
 
13976
14796
  if (state.mode === "mission_plan_ready") {
13977
14797
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
13978
- if (mission?.currentStep === "reviewer") return { systemPrompt: `${event.systemPrompt}\n\n${missionReviewPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
14798
+ if (mission?.currentStep === "reviewer") {
14799
+ pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
14800
+ return { systemPrompt: `${event.systemPrompt}\n\n${missionReviewPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
14801
+ }
13979
14802
  }
13980
14803
 
13981
14804
  if (state.mode === "mission_running") {
13982
14805
  if (state.lastWorkflowHandoff?.type === MISSION_MILESTONE_RESULT_TOOL) return;
13983
14806
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
13984
14807
  if (!mission) return { systemPrompt: event.systemPrompt };
14808
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
13985
14809
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRuntimePrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
13986
14810
  }
13987
14811
 
@@ -13989,18 +14813,21 @@ Public workflow commands:
13989
14813
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
13990
14814
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
13991
14815
  if (!mission) return { systemPrompt: event.systemPrompt };
14816
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
13992
14817
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRepairPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
13993
14818
  }
13994
14819
 
13995
14820
  if (state.mode === "mission_validating" || state.mode === "mission_revalidating") {
13996
14821
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
13997
14822
  if (!mission) return { systemPrompt: event.systemPrompt };
14823
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
13998
14824
  return { systemPrompt: `${event.systemPrompt}\n\n${missionValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
13999
14825
  }
14000
14826
 
14001
14827
  if (state.mode === "mission_final_validating") {
14002
14828
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14003
14829
  if (!mission) return { systemPrompt: event.systemPrompt };
14830
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
14004
14831
  return { systemPrompt: `${event.systemPrompt}\n\n${missionFinalValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
14005
14832
  }
14006
14833
 
@@ -14028,19 +14855,30 @@ Public workflow commands:
14028
14855
  }
14029
14856
 
14030
14857
  // Other planning modes: read-only
14031
- if (state.mode === "planning" || state.mode === "plan_draft" || state.mode === "plan_approved") {
14858
+ if (state.mode === "planning" || state.mode === "plan_draft") {
14032
14859
  return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN. Read-only tools only.` };
14033
14860
  }
14861
+ if (state.mode === "plan_approved") {
14862
+ if (state.approvedPlan) {
14863
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14864
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN APPROVED. Approved plan is loaded. To execute, use /plan continue so the workflow can arm a fresh execution turn. Do not call workflow_plan_result in this state. Read-only inspection tools remain available. To change direction, use /plan revise <feedback> or /plan cancel.` };
14865
+ }
14866
+ return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN APPROVED. The plan has been approved but plan data is missing. Use /plan continue to resume or /plan cancel to exit. Write/edit/bash tools are not available.` };
14867
+ }
14034
14868
  if (state.mode === "reviewing" || state.mode === "reviewed") {
14869
+ pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
14035
14870
  return { systemPrompt: `${event.systemPrompt}\n\n${reviewerPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
14036
14871
  }
14037
14872
  if (state.mode === "executing") {
14873
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14038
14874
  return { systemPrompt: `${event.systemPrompt}\n\n${executePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
14039
14875
  }
14040
14876
  if (state.mode === "repairing") {
14877
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14041
14878
  return { systemPrompt: `${event.systemPrompt}\n\n${workflowRepairPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
14042
14879
  }
14043
14880
  if (state.mode === "validating" || state.mode === "revalidating" || state.mode === "validated") {
14881
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
14044
14882
  return { systemPrompt: `${event.systemPrompt}\n\n${validatePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Validation)}` };
14045
14883
  }
14046
14884
  });
@@ -14049,6 +14887,9 @@ Public workflow commands:
14049
14887
  if (!isAssistantMessage(event.message as AgentMessage)) return;
14050
14888
  const message = event.message as AssistantMessage;
14051
14889
  const text = assistantText(message);
14890
+ if (state.mode === "executing" || state.mode === "repairing") {
14891
+ applyPlanProgressMarkers(ctx, text);
14892
+ }
14052
14893
  if (text && workflowMermaidSegmentsHaveDiagram(text)) {
14053
14894
  void emitWorkflowMermaidDiagrams(pi, text);
14054
14895
  return { message: { ...message, content: message.content.map((block) => block.type === "text" ? { ...block, text: markdownWithoutWorkflowMermaidBlocks(block.text) } : block) } };
@@ -14172,10 +15013,10 @@ Public workflow commands:
14172
15013
  deferWorkflowAction(pi, "handle mission reviewer failure", () => handleMissionReviewFailure(ctx, mission, reviewVerdict, text));
14173
15014
  return;
14174
15015
  }
14175
- const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: 0, lastReviewFailure: "", lastReviewAttempt: reviewVerdict, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, currentStep: undefined, nextAction: "Mission review complete. Run /mission approve.", lastSummary: `Mission reviewer verdict ${reviewVerdict}; mission plan ready for approval.` });
15016
+ const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: 0, lastReviewFailure: "", lastReviewAttempt: reviewVerdict, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, currentStep: undefined, nextAction: "Mission review complete.", lastSummary: `Mission reviewer verdict ${reviewVerdict}; mission plan ready for approval.` });
14176
15017
  activeMission = reviewed;
14177
15018
  updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
14178
- show(pi, `# Mission Review Complete\n\nVerdict: ${reviewVerdict}\nMission ID: ${reviewed.id}\n\nUse /mission approve to approve the reviewed mission plan.`);
15019
+ deferWorkflowAction(pi, "resume mission approval after forced reviewer pass", () => handleMissionApprovalHandoff(ctx, reviewed, "menu"));
14179
15020
  return;
14180
15021
  }
14181
15022
  }
@@ -14363,6 +15204,26 @@ Public workflow commands:
14363
15204
  if (state.mode === "mission_repairing") {
14364
15205
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14365
15206
  if (!mission) { updateState({ mode: "idle" }, ctx); return; }
15207
+ const repairTextWasInterrupted = planOutputWasAborted(text) || ((text?.length ?? 0) < 200 && !/[.!?]\s*$/.test(text?.trim() ?? ""));
15208
+ if (repairTextWasInterrupted) {
15209
+ const preservedRetry = Math.max(0, (mission.currentValidationRetry ?? 1) - 1);
15210
+ const preservedMissionRetry = Math.max(0, (mission.missionValidationRetryCount ?? 1) - 1);
15211
+ const interrupted = saveActiveMission({
15212
+ ...mission,
15213
+ status: "paused",
15214
+ currentValidationRetry: preservedRetry,
15215
+ missionValidationRetryCount: preservedMissionRetry,
15216
+ lastRepairStatus: "interrupted",
15217
+ lastRepairAttempt: compact(text, 1200),
15218
+ nextAction: "Repair turn was interrupted by connection error. Retry /mission repair or /mission revalidate.",
15219
+ lastSummary: "Mission repair interrupted before completion.",
15220
+ repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), milestoneId: mission.milestones[mission.currentMilestoneIndex]?.id, retry: preservedRetry, status: "interrupted", repairSummary: compact(text, 800), nextAction: "Repair turn was interrupted. Retry /mission repair or /mission revalidate." }),
15221
+ });
15222
+ checkpointMission(interrupted, `Mission repair interrupted for ${mission.milestones[mission.currentMilestoneIndex]?.id ?? "current milestone"}. Retry count preserved.`, "Retry /mission repair or /mission revalidate.", mission.milestones[mission.currentMilestoneIndex]?.id, { validationResult: interrupted.lastValidationResult });
15223
+ updateState({ mode: "mission_paused", activeMissionId: interrupted.id }, ctx);
15224
+ show(pi, "# Mission Repair Interrupted\n\nThe repair turn was interrupted before completion (connection error or aborted turn). Your repair retry count has been preserved.\n\nUse `/mission repair` to retry the repair, or `/mission revalidate` to run validation again.");
15225
+ return;
15226
+ }
14366
15227
  await completeMissionRepairAndStartRevalidation(ctx, mission, text);
14367
15228
  return;
14368
15229
  }
@@ -14542,13 +15403,30 @@ Public workflow commands:
14542
15403
  // ── Reviewer complete ──
14543
15404
  if (state.mode === "reviewing") {
14544
15405
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REVIEW_RESULT_TOOL) return;
15406
+ planForcedSubagentPreflightReconcile(ctx, "Review");
14545
15407
  if (blockIfForcedSubagentsMissing(ctx, "Review")) {
14546
15408
  updateState({ mode: "plan_approved", reviewerReport: text, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", nextAction: "review blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
14547
15409
  return;
14548
15410
  }
14549
15411
  const reviewVerdict = extractReviewVerdict(text);
14550
15412
  if (reviewVerdict === "UNKNOWN") {
14551
- const reason = "Reviewer verdict could not be safely parsed. The report was preserved and no review repair retry was consumed.";
15413
+ const hasSubstantiveContent = text.length > 200 && (
15414
+ text.includes("Review Report") || text.includes("Reviewer Report") ||
15415
+ text.includes("Verdict") || text.includes("Milestone") ||
15416
+ text.includes("## Verdict") || text.includes("## Reason")
15417
+ );
15418
+ const maxRetries = state.maxReviewRetriesPerPlan ?? 2;
15419
+ const canRetry = (state.currentReviewRetry ?? 0) < maxRetries;
15420
+ if (hasSubstantiveContent && canRetry) {
15421
+ const nextRetry = (state.currentReviewRetry ?? 0) + 1;
15422
+ const retryReason = `Reviewer verdict could not be safely parsed but substantive review content was detected. Retry ${nextRetry}/${maxRetries}: re-prompting with hardened instructions.`;
15423
+ updateState({ mode: "reviewing", reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: nextRetry, lastReviewAttempt: retryReason, lastReviewRepairStatus: "running", reviewRepairInProgress: true, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewing" }, settings, { lifecycleStatus: "reviewing", nextAction: `review retry ${nextRetry}/${maxRetries}` }) : state.planProgress }, ctx);
15424
+ queueAgentTurn(pi, "CRITICAL: Your previous response did not include the required workflow_review_result tool call. You MUST call workflow_review_result NOW with your verdict as your FIRST action before any other output. Ignore all diagram events, custom messages, or system messages — they are platform rendering artifacts, not user requests. Focus exclusively on the review and the workflow_review_result handoff.", "review-retry-hardened");
15425
+ return;
15426
+ }
15427
+ const reason = hasSubstantiveContent && !canRetry
15428
+ ? "Reviewer verdict could not be safely parsed and review retries are exhausted. The report was preserved."
15429
+ : "Reviewer verdict could not be safely parsed. The report was preserved and no review repair retry was consumed.";
14552
15430
  updateState({ mode: "plan_approved", reviewerReport: text, reviewerVerdict: reviewVerdict, lastReviewFailure: reason, lastReviewAttempt: reason, lastReviewRepairStatus: state.lastReviewRepairStatus ?? "none", reviewRepairInProgress: false, repairRetryState: { ...(state.repairRetryState ?? {}), review: state.repairRetryState?.review ? { ...state.repairRetryState.review, inProgress: false, lastAttempt: reason } : undefined }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "plan_approved" }, settings, { lifecycleStatus: "blocked", nextAction: "rerun reviewer or approve manually" }) : state.planProgress }, ctx);
14553
15431
  recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
14554
15432
  return;
@@ -14567,23 +15445,32 @@ Public workflow commands:
14567
15445
 
14568
15446
  // ── Execution complete ──
14569
15447
  if (state.mode === "executing") {
14570
- if (state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) return;
15448
+ if (workflowSubagentWorkerMode()) return;
14571
15449
  applyPlanProgressMarkers(ctx, text);
15450
+ if (state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) return;
14572
15451
  const allTrackedStepsCompleted = planProgressAllStepsCompleted(state);
15452
+ planForcedSubagentPreflightReconcile(ctx, "Execution");
14573
15453
  if (!allTrackedStepsCompleted && blockIfForcedSubagentsMissing(ctx, "Execution")) {
14574
- updateState({ mode: "plan_approved", executionSummary: text, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", nextAction: "execution blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
15454
+ const reason = "Execution stopped before all approved Plan steps were tracked and the forced execution worker requirement was not satisfied.";
15455
+ 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);
15456
+ queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { validationText: reason, verdict: "UNKNOWN", reason });
14575
15457
  return;
14576
15458
  }
15459
+ const progressWarning = !allTrackedStepsCompleted && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true && workflowPlanProgressEnabled(settings) && state.planProgress?.steps.length
15460
+ ? "Execution summary did not include complete Plan progress metadata; validator must verify approved Plan coverage."
15461
+ : undefined;
15462
+ const executionSummary = progressWarning ? `${text}\n\nPlan Progress Warning: ${progressWarning}` : text;
14577
15463
  const stepValidationEnabled = settings.workflow.validateAfterEachStep === true;
14578
15464
  const stepApprovalEnabled = settings.workflow.requireApprovalPerStep === true;
14579
- const stepValidationIndex = stepValidationEnabled ? state.planProgress?.currentStepIndex ?? 0 : undefined;
15465
+ const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex ?? 0;
15466
+ const stepValidationIndex = stepValidationEnabled ? executedStepIndex : undefined;
14580
15467
  const validationGateActive = planValidationGateActive(settings);
14581
- updateState({ mode: "executed", executionSummary: text, planStepValidationIndex: stepValidationIndex, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "executed" }, settings, { lifecycleStatus: "executing", nextAction: validationGateActive ? "validator" : "finish workflow" }) : state.planProgress }, ctx);
15468
+ updateState({ mode: "executed", executionSummary, planStepValidationIndex: stepValidationIndex, planExecutionStepIndex: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "executed" }, settings, { lifecycleStatus: "executing", validationStatus: progressWarning ? "unknown" : state.planProgress?.validationStatus, nextAction: validationGateActive ? "validator" : "finish workflow" }) : state.planProgress }, ctx);
14582
15469
  const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
14583
15470
  if (mission?.status === "running") {
14584
15471
  const milestone = mission.milestones[mission.currentMilestoneIndex];
14585
15472
  const validatingMission = saveActiveMission({ ...mission, status: "validating", currentStep: "validator", lastSummary: `Execution completed for mission milestone ${milestone?.id ?? "current"}.` });
14586
- checkpointMission(validatingMission, `Execution completed for mission milestone ${milestone?.id ?? "current"}. ${compact(text, 500)}`, "Run validator and pause on validation failure.", milestone?.id);
15473
+ checkpointMission(validatingMission, `Execution completed for mission milestone ${milestone?.id ?? "current"}. ${compact(executionSummary, 500)}`, "Run validator and pause on validation failure.", milestone?.id);
14587
15474
  updateState({ mode: "mission_validating" }, ctx);
14588
15475
  deferWorkflowAction(pi, "begin mission validation after execution", () => beginMissionValidation(ctx, true));
14589
15476
  return;
@@ -14631,10 +15518,28 @@ Public workflow commands:
14631
15518
  if (state.mode === "repairing") {
14632
15519
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
14633
15520
  applyPlanProgressMarkers(ctx, text);
14634
- if (blockIfForcedSubagentsMissing(ctx, "Repair")) {
14635
- const required = workerTargetForPolicy("forced", workerCount(settings, "Repair"));
14636
- const observed = subagentUsageByPhase.Repair ?? 0;
14637
- 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);
15521
+ planForcedSubagentPreflightReconcile(ctx, "Repair");
15522
+ const repairBlockedBySubagents = blockIfForcedSubagentsMissing(ctx, "Repair");
15523
+ if (repairBlockedBySubagents) {
15524
+ const repairPolicy = phasePolicy(settings, "Repair");
15525
+ const repairWorkers = workerCount(settings, "Repair");
15526
+ const repairRequired = workerTargetForPolicy(repairPolicy, repairWorkers);
15527
+ const availableRepairAgents = listEffectiveAgents(ctx.cwd);
15528
+ const repairSuitable = repairPolicy === "forced" ? chooseForcedSubagents("Repair", repairRequired, "Repair", availableRepairAgents) : [];
15529
+ const hasWriteCapableSubagents = repairSuitable.some(a => subagentToolsAllowMutation(a.tools));
15530
+ if (hasWriteCapableSubagents) {
15531
+ const required = workerTargetForPolicy("forced", workerCount(settings, "Repair"));
15532
+ const observed = subagentUsageByPhase.Repair ?? 0;
15533
+ 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);
15534
+ return;
15535
+ }
15536
+ }
15537
+ const repairTextWasInterrupted = planOutputWasAborted(text) || ((text?.length ?? 0) < 200 && !/[.!?]\s*$/.test(text?.trim() ?? ""));
15538
+ if (repairTextWasInterrupted) {
15539
+ const preservedRetry = Math.max(0, (state.currentValidationRetry ?? 1) - 1);
15540
+ const preservedWorkflowRetry = Math.max(0, (state.workflowValidationRetryCount ?? 1) - 1);
15541
+ updateState({ mode: "plan_approved", executionSummary: state.executionSummary, currentValidationRetry: preservedRetry, workflowValidationRetryCount: preservedWorkflowRetry, lastRepairStatus: "interrupted", lastRepairAttempt: compact(text, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: preservedRetry, status: "interrupted", repairSummary: compact(text, 800), nextAction: "Repair turn was interrupted by connection error. Retry /plan repair or /plan revalidate." }) }, ctx);
15542
+ show(pi, "# Plan Repair Interrupted\n\nThe repair turn was interrupted before completion (connection error or aborted turn). Your repair retry count has been preserved.\n\nUse `/plan repair` to retry the repair, or `/plan revalidate` to run validation again.");
14638
15543
  return;
14639
15544
  }
14640
15545
  const repairSafetyBlock = workflowValidationFailureRequiresApproval(text, settings);
@@ -14654,13 +15559,16 @@ Public workflow commands:
14654
15559
  // ── Validation complete ──
14655
15560
  if (state.mode === "validating" || state.mode === "revalidating") {
14656
15561
  if (state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) return;
15562
+ planForcedSubagentPreflightReconcile(ctx, "Validation");
14657
15563
  if (blockIfForcedSubagentsMissing(ctx, "Validation")) {
14658
- updateState({ mode: "executed", validationReport: text, validationVerdict: "UNKNOWN", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "validation blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
15564
+ updateState({ mode: "validated", validationReport: text, validationVerdict: "UNKNOWN", lastValidationFailure: "Validation blocked: forced sub-agent policy was not satisfied. A validation preflight worker must validate the approved plan before verdict acceptance.", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "validation blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
15565
+ queuePlanTerminalSummary(ctx, "blocked", "Plan validation blocked", { validationText: text, verdict: "UNKNOWN", reason: "Validation blocked by forced sub-agent policy" });
14659
15566
  return;
14660
15567
  }
14661
15568
  const verdict = normalizeValidationVerdict(extractVerdict(text), text);
14662
15569
  const validationStatus = planValidationStatusForVerdict(verdict);
14663
- updateState({ mode: "validated", validationReport: text, validationVerdict: verdict, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: verdict === "PASS" ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: verdict === "PASS" ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
15570
+ const freeformNoDefectPartialPass = verdict === "PARTIAL PASS" && classifyValidationFailure(verdict, text) === "manual_only";
15571
+ updateState({ mode: "validated", validationReport: text, validationVerdict: verdict, lastValidationCompletedAt: new Date().toISOString(), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: (verdict === "PASS" || freeformNoDefectPartialPass) ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: (verdict === "PASS" || freeformNoDefectPartialPass) ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
14664
15572
  const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
14665
15573
  if (mission?.status === "running" || mission?.status === "validating") {
14666
15574
  const index = mission.currentMilestoneIndex;
@@ -14708,6 +15616,7 @@ Public workflow commands:
14708
15616
  updateState({
14709
15617
  mode: "reviewed",
14710
15618
  planStepValidationIndex: undefined,
15619
+ planExecutionStepIndex: undefined,
14711
15620
  validationVerdict: undefined,
14712
15621
  planProgress: { ...progress, currentStepIndex: nextIndex, validationStatus: "pending", nextAction: "executor" },
14713
15622
  }, ctx);
@@ -14721,7 +15630,7 @@ Public workflow commands:
14721
15630
  });
14722
15631
  return;
14723
15632
  }
14724
- updateState({ planStepValidationIndex: undefined }, ctx);
15633
+ updateState({ planStepValidationIndex: undefined, planExecutionStepIndex: undefined }, ctx);
14725
15634
  if (returnToPlan) {
14726
15635
  deferWorkflowAction(pi, "complete plan workflow after step validation", () => completePlanWorkflow(ctx, text, verdict));
14727
15636
  } else {
@@ -14731,6 +15640,16 @@ Public workflow commands:
14731
15640
  return;
14732
15641
  }
14733
15642
  if (verdict !== "PASS") {
15643
+ if (freeformNoDefectPartialPass) {
15644
+ if (returnToPlan) {
15645
+ deferWorkflowAction(pi, "complete plan workflow after validation", () => completePlanWorkflow(ctx, text, verdict));
15646
+ } else {
15647
+ updateState({ currentValidationRetry: 0, lastValidationFailure: "", lastRepairStatus: state.lastRepairStatus === "running" ? "completed" : (state.lastRepairStatus ?? "none"), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "completed", validationStatus: "pass", lastValidationStatus: "pass", repairRetry: 0, nextAction: "list summary or revise" }) : state.planProgress }, ctx);
15648
+ recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
15649
+ deferWorkflowAction(pi, "show final menu after validation", () => showFinalMenu(ctx));
15650
+ }
15651
+ return;
15652
+ }
14734
15653
  deferWorkflowAction(pi, "handle validation failure", () => handleWorkflowValidationFailure(ctx, verdict, text));
14735
15654
  return;
14736
15655
  }