@mediadatafusion/pi-workflow-suite 0.0.10 → 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 {
@@ -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,11 +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
- queueAgentTurn(pi, `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)}`, customType);
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
+ }
775
807
  }
776
808
 
777
809
  function visibleDeferredHandoffFailure(label: string): boolean {
778
- return /\b(menu|handoff)\b/i.test(label);
810
+ return /\b(menu|handoff|repair|revalidation|execution|phase|approval|mission)\b/i.test(label);
779
811
  }
780
812
 
781
813
  function deferWorkflowAction(pi: ExtensionAPI, label: string, action: () => Promise<void> | void): void {
@@ -824,7 +856,7 @@ function scheduleWorkflowHandoff(pi: ExtensionAPI, snapshot: WorkflowHandoffSnap
824
856
  // validationSubagentsAllowed imported from workflow-subagent-policy.ts
825
857
 
826
858
  function planToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
827
- 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]);
828
860
  return planningSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
829
861
  }
830
862
 
@@ -918,7 +950,27 @@ function validationToolsFor(settings: ReturnType<typeof loadWorkflowSettings>):
918
950
 
919
951
 
920
952
  function workflowMermaidGuidance(): string {
921
- 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.`;
922
974
  }
923
975
 
924
976
  function workflowRuntimeWebResearchGuidance(): string {
@@ -1055,7 +1107,7 @@ function workflowMermaidSegmentsHaveDiagram(markdown: string): boolean {
1055
1107
  function markdownWithoutWorkflowMermaidBlocks(markdown: string): string {
1056
1108
  return parseWorkflowMermaidSegments(markdown).map((segment) => {
1057
1109
  if (segment.type === "markdown") return segment.text;
1058
- return `\n\n[Workflow diagram rendered below with terminal preview and openable artifact link: ${segment.diagramType}]\n\n`;
1110
+ return "";
1059
1111
  }).join("").replace(/\n{4,}/g, "\n\n\n").trim();
1060
1112
  }
1061
1113
 
@@ -1514,6 +1566,9 @@ function planPrompt(task: string, priorPlan?: string, feedback?: string, setting
1514
1566
 
1515
1567
  ${professionalOutputGuidance("Plan Mode")}
1516
1568
 
1569
+ Diagram guidance:
1570
+ - ${workflowMermaidGuidance()}
1571
+
1517
1572
  Task:
1518
1573
  ${task}
1519
1574
 
@@ -1547,9 +1602,6 @@ No preamble. No visible chain-of-thought. No "I see that..." No "Let me think ab
1547
1602
  Initial analysis requirement:
1548
1603
  - ${analysisInstruction}
1549
1604
 
1550
- Diagram guidance:
1551
- - ${workflowMermaidGuidance()}
1552
-
1553
1605
  Web research guidance:
1554
1606
  ${workflowRuntimeWebResearchGuidance()}
1555
1607
 
@@ -1733,6 +1785,36 @@ function phaseWorkerLine(settings: ReturnType<typeof loadWorkflowSettings>, phas
1733
1785
  return `${phase} Workers: ${activeWorkerTargetLabel(policy, workerCount(settings, phase))}`;
1734
1786
  }
1735
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
+
1736
1818
  function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(), preflightBlock?: string): string {
1737
1819
  const policy = settings.subagents.executionPolicy ?? "auto";
1738
1820
  const workers = workerCount(settings, "Execution");
@@ -1749,28 +1831,63 @@ function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
1749
1831
  : preflightSatisfied && policy === "forced"
1750
1832
  ? forcedSubagentPolicySatisfiedGuidance("execution")
1751
1833
  : policy === "forced"
1752
- ? `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.`
1753
1835
  : policy === "maximum"
1754
- ? `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.`
1755
1837
  : policy === "deep"
1756
- ? `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.`
1757
- : "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
+
1758
1848
  return `You are in PI WORKFLOW EXECUTE MODE.
1759
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
+
1760
1854
  ${professionalOutputGuidance("Plan execution")}
1761
1855
 
1856
+ Diagram guidance:
1857
+ ${workflowMermaidGuidance()}
1858
+
1762
1859
  Approved plan is the execution contract. Do only this plan. No unrelated refactors. Do not commit. Do not push. Do not switch branches.
1763
1860
 
1764
- 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}
1765
1864
 
1766
- Plan progress tracking is mandatory for every numbered approved Plan step:
1767
- - Before starting a step, call workflow_progress with that step number and status "active".
1768
- - After completing a step, call workflow_progress with that step number and status "completed".
1769
- - If a step fails or blocks, call workflow_progress with that step number and status "failed" or "blocked" and stop with the blocker.
1770
- - For full-plan execution, progress through the approved steps in order so Current and Next advance visibly.
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.
1881
+
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.
1771
1888
  - For per-step gated execution, track only the current allowed step and stop after that step.
1772
1889
 
1773
- Before your final execution summary, call workflow_execution_result with status, completedSteps, changedFiles, commands, blockers, and summary. completedSteps must list every approved Plan step number 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, remaining manual verification, and any evidence from execution sub-agents.
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.
1828
1974
 
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.
1975
+ ${automatableEvidenceVerifierGuidance}
1976
+
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}
@@ -1842,7 +1990,8 @@ ${requiredSubagentPreflightSection(preflightBlock)}
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
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.
1845
- - PARTIAL PASS is only for manual/visual/browser verification caveats or evidence gaps without a concrete repairable issue.
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
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.
@@ -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 {
@@ -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; normal file tools and sub-agents are limited to the active repo, while protected project .pi paths are read-only through normal tools
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,8 +3446,8 @@ 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
3452
  if (state.validationVerdict === "PARTIAL PASS") return "partial pass";
3250
3453
  if (state.validationVerdict === "FAIL") return "fail";
@@ -3252,6 +3455,15 @@ function planValidationState(state: WorkflowState): PlanValidationStatus {
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- 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)}`;
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- 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- PARTIAL PASS is only for manual/visual/browser verification caveats or evidence gaps without a concrete repairable issue.\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\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. Use FAIL when concrete code/content/citation/source/file/metadata/artifact fixes, concrete repairable defects, or unsafe/out-of-scope work remain. Use PARTIAL PASS only for manual/visual/browser verification caveats or evidence gaps without concrete repairable issues. 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") {
@@ -7258,14 +7601,16 @@ export default function workflowModes(pi: ExtensionAPI): void {
7258
7601
  showBlockedPlanRecoveryMenu(ctx);
7259
7602
  return { ...typedToolAck(), details: { accepted: true, status, validationStarted: false } };
7260
7603
  }
7261
- if (progressEnabled && progressedPlanProgress?.steps.length && settings.workflow.validateAfterEachStep !== true && planProgressHasOpenSteps(progressedPlanProgress)) {
7262
- const reason = completedSteps.length
7263
- ? "workflow_execution_result did not report all approved Plan steps as completed."
7264
- : "workflow_execution_result reported completed status without completedSteps for the approved Plan steps.";
7265
- 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");
7266
- 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 } };
7267
7610
  }
7268
- updateState({ mode: "executed", executionSummary: summary, planStepValidationIndex: settings.workflow.validateAfterEachStep === true ? executedStepIndex : state.planStepValidationIndex, planExecutionStepIndex: undefined, 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);
7269
7614
  if (validationAvailable && validateAfterExecution) deferWorkflowAction(pi, "begin validation after typed execution", async () => { await beginValidation(ctx, true); });
7270
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."));
7271
7616
  else deferWorkflowAction(pi, "show post execution menu after typed execution", () => showPostExecutionMenu(ctx, validationAvailable));
@@ -7321,6 +7666,9 @@ export default function workflowModes(pi: ExtensionAPI): void {
7321
7666
  const settings = loadWorkflowSettings(ctx.cwd);
7322
7667
  const verdict = typedValidationVerdict(params.verdict);
7323
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;
7324
7672
  const planValidationMode = state.mode === "validating" || state.mode === "revalidating";
7325
7673
  const missionValidationMode = state.mode === "mission_validating" || state.mode === "mission_revalidating" || state.mode === "mission_final_validating";
7326
7674
  if (!planValidationMode && !missionValidationMode) {
@@ -7334,7 +7682,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7334
7682
  await handleMissionFinalValidationFailure(ctx, mission, verdict, report);
7335
7683
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7336
7684
  }
7337
- 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}.` });
7338
7686
  checkpointMission(completed, `Typed final mission validation ${verdict}.`, "Mission completed after final validation.", undefined, { validationResult: verdict });
7339
7687
  completeMissionToAwaitingInput(ctx, completed, report, verdict, settings);
7340
7688
  return { ...typedToolAck(), details: { accepted: true, verdict } };
@@ -7343,7 +7691,10 @@ export default function workflowModes(pi: ExtensionAPI): void {
7343
7691
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
7344
7692
  if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
7345
7693
  if (verdict !== "PASS") {
7346
- 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
+ });
7347
7698
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7348
7699
  }
7349
7700
  const index = mission.currentMilestoneIndex;
@@ -7352,25 +7703,36 @@ export default function workflowModes(pi: ExtensionAPI): void {
7352
7703
  const nextIndex = Math.min(index + 1, Math.max(0, milestones.length - 1));
7353
7704
  const done = index >= milestones.length - 1;
7354
7705
  if (done && settings.missions.finalValidationEnabled === true) {
7355
- 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.` });
7356
7707
  checkpointMission(finalMission, `Typed validation ${verdict}; final mission validation queued.`, "Run final comprehensive validation for the whole mission.", milestone?.id, { validationResult: verdict });
7357
- updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: report, validationVerdict: verdict }, ctx);
7358
- 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
+ });
7359
7712
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7360
7713
  }
7361
7714
  const shouldPause = mission.autonomy === "manual" || mission.pauseBetweenMilestones === true || mission.continueAcrossMilestones === false;
7362
7715
  const nextStatus = done ? "completed" : shouldPause ? "paused" : "approved";
7363
7716
  const nextMode = done ? "awaiting_mission_input" : shouldPause ? "mission_paused" : "mission_approved";
7364
- 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"}.` });
7365
7718
  checkpointMission(nextMission, `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`, nextMission.nextAction ?? "Continue mission.", milestone?.id, { validationResult: verdict });
7366
7719
  if (done) completeMissionToAwaitingInput(ctx, nextMission, report, verdict, settings);
7367
- else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: report, validationVerdict: verdict }, ctx);
7368
- 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
+ }
7369
7724
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7370
7725
  }
7371
7726
  const validationStatus = planValidationStatusForVerdict(verdict);
7372
- 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);
7373
- 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
+ }
7374
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));
7375
7737
  else deferWorkflowAction(pi, "show final menu after typed validation", () => showFinalMenu(ctx));
7376
7738
  return { ...typedToolAck(), details: { accepted: true, verdict } };
@@ -7416,7 +7778,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
7416
7778
  if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
7417
7779
  const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: verdict, lastReviewAttempt: verdict, lastSummary: `Mission reviewer verdict ${verdict}.` });
7418
7780
  updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
7419
- 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
+ }
7420
7786
  return { ...typedToolAck(), details: { accepted: true, verdict } };
7421
7787
  }
7422
7788
  const settings = loadWorkflowSettings(ctx.cwd);
@@ -7568,7 +7934,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7568
7934
 
7569
7935
  const workflowContextInterruptionEvidence = (event: unknown, ctx: ExtensionContext): boolean => {
7570
7936
  const eventText = workflowContextInterruptionEventText(event);
7571
- 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;
7572
7938
  const settings = loadWorkflowSettings(ctx.cwd);
7573
7939
  let nearTrigger = false;
7574
7940
  try {
@@ -7667,16 +8033,23 @@ export default function workflowModes(pi: ExtensionAPI): void {
7667
8033
  const diagrams = parseWorkflowMermaidSegments(text).filter((segment): segment is Extract<WorkflowMarkdownSegment, { type: "mermaid" }> => segment.type === "mermaid").slice(0, WORKFLOW_MERMAID_MAX_BLOCKS_PER_MESSAGE);
7668
8034
  for (const diagram of diagrams) {
7669
8035
  const details = await renderWorkflowMermaidPng(diagram.source, workflowDiagramTitle(diagram.source, diagram.index));
7670
- 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 });
7671
8037
  }
7672
8038
  };
7673
8039
 
7674
- 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 => {
7675
8044
  const settings = loadWorkflowSettings(ctx?.cwd ?? process.cwd());
7676
- if (!workflowPlanProgressEnabled(settings)) return false;
8045
+ if (!workflowPlanProgressEnabled(settings)) return { tracked: false, reason: "disabled" };
7677
8046
  const progress = mergePlanProgress(state, settings);
7678
- if (!progress.steps.length) return false;
8047
+ if (!progress.steps.length) return { tracked: false, reason: "no_steps" };
7679
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" };
7680
8053
  const steps = progress.steps.map((step, i) => {
7681
8054
  if (status === "active" && i !== index && step.status === "active") return { ...step, status: "pending" as PlanStepStatus };
7682
8055
  if (i === index) return { ...step, status };
@@ -7688,14 +8061,43 @@ export default function workflowModes(pi: ExtensionAPI): void {
7688
8061
  steps[nextOpen] = { ...steps[nextOpen], status: "active" as PlanStepStatus };
7689
8062
  }
7690
8063
  }
7691
- updateState({ planProgress: { ...progress, steps, currentStepIndex: currentStepIndexForSteps(steps, index), nextAction: planNextActionText(state) } }, ctx);
7692
- return true;
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");
7693
8089
  };
7694
8090
 
7695
8091
  const applyPlanProgressMarkers = (ctx: ExtensionContext, text: string): void => {
7696
- for (const match of text.matchAll(/WORKFLOW_STEP_STARTED:\s*(\d+)/gi)) markPlanStep(ctx, Number(match[1]), "active");
7697
- for (const match of text.matchAll(/WORKFLOW_STEP_COMPLETED:\s*(\d+)/gi)) markPlanStep(ctx, Number(match[1]), "completed");
7698
- 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
+ }
7699
8101
  };
7700
8102
 
7701
8103
  pi.registerTool({
@@ -7797,10 +8199,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
7797
8199
  pi.registerTool({
7798
8200
  name: WORKFLOW_DIAGRAM_TOOL,
7799
8201
  label: "Workflow Diagram",
7800
- 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.",
7801
8203
  promptSnippet: "workflow_diagram({ title, source }) — render a Mermaid flowchart, sequenceDiagram, stateDiagram, classDiagram, erDiagram, or xychart-beta inline",
7802
8204
  promptGuidelines: [
7803
- "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.",
7804
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.",
7805
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.",
7806
8209
  ],
@@ -7816,7 +8219,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
7816
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" } };
7817
8220
  }
7818
8221
  const details = await renderWorkflowMermaidPng(source, title);
7819
- 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 };
7820
8223
  },
7821
8224
  renderResult(result, _options, theme) {
7822
8225
  return workflowMermaidRenderer({ content: result.content, details: result.details }, { expanded: false }, theme);
@@ -7837,12 +8240,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
7837
8240
  if (!Number.isFinite(step) || step < 1) {
7838
8241
  return { content: [{ type: "text", text: "Invalid workflow progress step." }], details: { step, status, tracked: false }, isError: true };
7839
8242
  }
7840
- const tracked = markPlanStep(ctx, step, status);
7841
- if (!tracked) {
7842
- 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 } };
7843
8246
  }
7844
- const item = state.planProgress?.steps?.[Math.floor(step) - 1];
7845
- 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 } };
7846
8249
  },
7847
8250
  } as ToolDefinition);
7848
8251
 
@@ -8179,7 +8582,27 @@ ${reportExcerpt(validation, 2400)}
8179
8582
  - Run /workflow status to confirm the final state.
8180
8583
  - If blocked, inspect the reason above, then use /plan repair, /plan revalidate, /plan revise <feedback>, or /plan cancel as appropriate.
8181
8584
  - If completed, start the next request with /plan or review the saved record with /workflow plans show ${state.planHistoryId ?? "latest"}.`);
8182
- 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 };
8183
8606
  };
8184
8607
 
8185
8608
  const queuePlanTerminalSummary = (ctx: ExtensionContext, status: "completed" | "blocked", title: string, options: { validationText?: string; verdict?: WorkflowState["validationVerdict"]; reason?: string } = {}): NonNullable<WorkflowState["lastPlanStopSummary"]> => {
@@ -8635,7 +9058,9 @@ ${renderMissionProgress(mission, settings)}
8635
9058
  return show(pi, `# Mission Repair ${stale ? "Stale" : "Active"}\n\n${guidance}\n\n${renderMissionStatus(mission)}`);
8636
9059
  }
8637
9060
  if (mission.status === "running" || mission.status === "checkpointing") {
8638
- 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;
8639
9064
  }
8640
9065
  return show(pi, `# Mission Resume\n\nNo safe automatic resume route exists for mission status: ${mission.status}.\n\n${renderMissionStatus(mission)}`);
8641
9066
  }
@@ -8881,10 +9306,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8881
9306
  const settings = loadWorkflowSettings(ctx.cwd);
8882
9307
  const retry = mission.currentReviewRetry ?? 0;
8883
9308
  const missionRetry = missionReviewRetryCount(mission);
8884
- const maxRetries = maxMissionReviewRetries(mission, settings);
9309
+ const config = missionRepairRetryConfig(settings, "review");
9310
+ const maxRetries = Math.max(config.maxRetriesPerItem, maxMissionReviewRetries(mission, settings));
8885
9311
  const failure = `Reviewer ${verdict}. ${compact(reviewText, 1200)}`;
8886
9312
  const canRepairPlan = Boolean(mission.planText?.trim()) && (verdict === "NEEDS REPAIR" || verdict === "FAIL" || verdict === "BLOCKED");
8887
- const repairEnabled = settings.missions.autoRepairReviewFailures === true && settings.missions.reviewRetryMode !== "off";
9313
+ const repairEnabled = config.autoRepairFailures && config.retryMode !== "off";
8888
9314
  if (!repairEnabled || !canRepairPlan || retry >= maxRetries) {
8889
9315
  const reason = !repairEnabled ? "Mission review repair disabled." : !canRepairPlan ? "Mission plan repair context unavailable." : "Mission review repair retry limit exhausted.";
8890
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." });
@@ -8916,7 +9342,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8916
9342
  }
8917
9343
  const settings = loadWorkflowSettings(ctx.cwd);
8918
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.";
8919
- 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: "" });
8920
9346
  updateState({
8921
9347
  mode: "mission_approved",
8922
9348
  activeMissionId: next.id,
@@ -8944,8 +9370,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
8944
9370
  }
8945
9371
 
8946
9372
  function missionContinuationBlock(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, action: "run" | "continue" | "next" | "resume"): string | undefined {
8947
- const runtimeBlocked = missionRuntimeBudgetBlock(mission, settings);
8948
- if (runtimeBlocked) return runtimeBlocked;
9373
+ if (action !== "resume") {
9374
+ const runtimeBlocked = missionRuntimeBudgetBlock(mission, settings);
9375
+ if (runtimeBlocked) return runtimeBlocked;
9376
+ }
8949
9377
  if (mission.autonomy === "full_auto" && !missionAllowsFullAuto(mission, settings)) return "Cannot continue: full auto requested but allowFullAuto=false.";
8950
9378
  if (mission.status === "draft") return mission.milestones.length === 0
8951
9379
  ? "Cannot continue: mission is draft and has no approved milestone plan. Run /mission plan, then /mission approve, then /mission continue."
@@ -9013,6 +9441,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9013
9441
  return;
9014
9442
  }
9015
9443
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route), planner: running.modelsUsed?.planner } }, ctx);
9444
+ saveActiveMission({ ...running, modelsUsed: { ...(running.modelsUsed ?? {}), executor: modelLabel(route) } });
9016
9445
  queueAgentTurn(pi, missionRuntimePrompt(running, settings, phasePreflightBlocks.Execution), auto ? "mission-run-trigger" : "mission-run-manual-trigger");
9017
9446
  }
9018
9447
 
@@ -9136,6 +9565,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9136
9565
  (["Planning", "Execution", "Repair", "Review", "Validation"] as SubagentPhase[]).forEach(resetSubagentPhaseUsage);
9137
9566
  };
9138
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
+
9139
9585
  const beginForcedSubagentPhase = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): boolean => {
9140
9586
  resetSubagentPhaseUsage(phase);
9141
9587
  phasePreflightBlocks[phase] = undefined;
@@ -9206,7 +9652,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9206
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." : "";
9207
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.`;
9208
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.`;
9209
- 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
+ }
9210
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.`;
9211
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.`;
9212
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.`;
@@ -9317,6 +9771,22 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9317
9771
  return false;
9318
9772
  };
9319
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
+
9320
9790
  const rememberStandardSubagentPreflight = (ctx: ExtensionContext, phase: SubagentPhase, task: string, required: number) => {
9321
9791
  const agents = Array.from(subagentNamesByPhase[phase] ?? []);
9322
9792
  updateState({
@@ -9563,6 +10033,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9563
10033
  planProgress: workflowPlanProgressEnabled(settings) ? createPlanProgress(planText, settings, "approved") : undefined,
9564
10034
  }, ctx);
9565
10035
  persistCurrentPlan(ctx, "approved", saveReason);
10036
+ pi.setActiveTools(executionToolsFor(settings));
9566
10037
  deferWorkflowAction(pi, "begin next phase after plan approval", async () => {
9567
10038
  const started = await continueAfterPlanApproval(ctx, true);
9568
10039
  if (!started) show(pi, "# Handoff Blocked\n\nPlan was approved, but automatic continuation could not start. Use /plan continue after fixing the blocker.");
@@ -9664,20 +10135,24 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9664
10135
  const steps = settings.workflow.validateAfterEachStep === true || settings.workflow.requireApprovalPerStep === true
9665
10136
  ? baseSteps.map((step, index) => index === activeIndex ? { ...step, status: "active" as PlanStepStatus } : step.status === "active" ? { ...step, status: "pending" as PlanStepStatus } : step)
9666
10137
  : baseSteps;
9667
- updateState({ mode: "executing", planExecutionStepIndex: activeIndex, planProgress: { ...currentProgress, steps, currentStepIndex: currentStepIndexForSteps(steps, activeIndex) } }, ctx);
9668
- 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)) {
9669
10141
  const reason = "Execution blocked by forced sub-agent policy availability gate.";
10142
+ pi.setActiveTools(planToolsFor(settings));
9670
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);
9671
10144
  queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { reason });
9672
10145
  return false;
9673
10146
  }
9674
10147
  const route = isMissionWorkflowMode(state) ? await applyMissionModelForRole(pi, ctx, "executor", { cwd: ctx.cwd }) : await applyModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
9675
10148
  if (!route) {
10149
+ pi.setActiveTools(planToolsFor(settings));
9676
10150
  updateState({ mode: previousMode === "reviewed" ? "reviewed" : "plan_approved" }, ctx);
9677
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.");
9678
10152
  return false;
9679
10153
  }
9680
10154
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
10155
+ pi.setActiveTools(executionToolsFor(settings));
9681
10156
  queueAgentTurn(pi, executePrompt(state, settings, phasePreflightBlocks.Execution), auto ? "workflow-execute-trigger" : "workflow-execute-manual-trigger");
9682
10157
  return true;
9683
10158
  }
@@ -9704,6 +10179,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9704
10179
  return false;
9705
10180
  }
9706
10181
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), reviewer: modelLabel(route) } }, ctx);
10182
+ pi.setActiveTools(reviewToolsFor(settings));
9707
10183
  if (auto) {
9708
10184
  queueAgentTurn(pi, "Review the approved plan before execution begins.", "workflow-review-trigger");
9709
10185
  } else {
@@ -9713,6 +10189,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9713
10189
  }
9714
10190
 
9715
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
+ }
9716
10197
  const settings = loadWorkflowSettings(ctx.cwd);
9717
10198
  if (settings.models.reviewer.enabled && roleIsConfigured(settings.models.reviewer)) {
9718
10199
  if (settings.workflow.autoRunReviewerBeforeExecute === true) {
@@ -9743,7 +10224,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9743
10224
  clearTypedHandoff(ctx, revalidate ? "Plan revalidation" : "Plan validation");
9744
10225
  pi.setActiveTools(validationToolsFor(settings));
9745
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);
9746
- if (!beginForcedSubagentPhase(ctx, "Validation", settings)) {
10227
+ const validationPreflightExists = phasePreflightBlocks.Validation != null;
10228
+ if (!validationPreflightExists && !beginForcedSubagentPhase(ctx, "Validation", settings)) {
9747
10229
  const reason = "Validation blocked by forced sub-agent policy availability gate.";
9748
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);
9749
10231
  queuePlanTerminalSummary(ctx, "blocked", "Plan validation blocked", { reason });
@@ -9759,8 +10241,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9759
10241
  return false;
9760
10242
  }
9761
10243
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
10244
+ pi.setActiveTools(validationToolsFor(settings));
9762
10245
  if (auto) {
9763
- 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");
9764
10247
  } else {
9765
10248
  queueWorkflowPrompt(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation));
9766
10249
  }
@@ -9835,6 +10318,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9835
10318
  return;
9836
10319
  }
9837
10320
  updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
10321
+ saveActiveMission({ ...validating, modelsUsed: { ...(validating.modelsUsed ?? {}), validator: modelLabel(route) } });
9838
10322
  const prompt = missionValidationPrompt(validating, settings, state.executionSummary, phasePreflightBlocks.Validation);
9839
10323
  if (auto) {
9840
10324
  queueAgentTurn(pi, revalidate ? "Revalidate the completed mission milestone." : "Validate the completed mission milestone.", revalidate ? "mission-revalidate-trigger" : "mission-validate-trigger");
@@ -9846,7 +10330,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9846
10330
  }
9847
10331
 
9848
10332
  function showBlockedPlanRecoveryMenu(ctx: ExtensionContext): void {
9849
- deferWorkflowAction(pi, "show final menu after blocked plan stop", () => showFinalMenu(ctx));
10333
+ deferWorkflowAction(pi, "show plan resume after blocked plan stop", () => handlePlanResume(ctx));
9850
10334
  }
9851
10335
 
9852
10336
  function showBlockedMissionRecoveryMenu(ctx: ExtensionContext): void {
@@ -9860,6 +10344,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9860
10344
  function planRepairCompletedPendingRevalidation(): boolean {
9861
10345
  if (state.mode === "executing" || state.mode === "validating" || state.mode === "repairing" || state.mode === "revalidating") return false;
9862
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
+ }
9863
10353
  const latestRepair = [...(state.repairHistory ?? [])].reverse().find((entry) => entry.status === "completed" || entry.status === "blocked" || entry.status === "running");
9864
10354
  return state.lastRepairStatus === "completed"
9865
10355
  || latestRepair?.status === "completed"
@@ -9882,6 +10372,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9882
10372
  async function startWorkflowRepair(ctx: ExtensionContext, source: "auto" | "user" = "auto") {
9883
10373
  if (!state.approvedPlan) return show(pi, "# Plan Repair Refused\n\nNo approved plan exists.");
9884
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
+ }
9885
10381
  clearTypedHandoff(ctx, "Plan repair");
9886
10382
  const failure = state.lastValidationFailure || state.validationReport || "No validation failure details recorded.";
9887
10383
  const currentRetry = state.currentValidationRetry ?? 0;
@@ -9893,14 +10389,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9893
10389
  await beginValidation(ctx, true, true);
9894
10390
  return;
9895
10391
  }
9896
- if (classifyValidationFailure(state.validationVerdict, failure) === "manual_only") {
9897
- const reason = "Repair refused: validation recorded only manual/visual/browser verification without a concrete repairable issue.";
9898
- 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);
9899
- show(pi, `# Plan Repair Refused\n\n${reason}\n\nRun manual QA, then /plan revalidate. Use /plan revise <feedback> if scope changed.`);
9900
- showBlockedPlanRecoveryMenu(ctx);
9901
- return;
9902
- }
9903
- 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" });
9904
10393
  if (!decision.allowed) {
9905
10394
  const reason = decision.reason ?? "repair requires approval or safety gate.";
9906
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);
@@ -9908,7 +10397,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9908
10397
  showBlockedPlanRecoveryMenu(ctx);
9909
10398
  return;
9910
10399
  }
9911
- if (!beginForcedSubagentPhase(ctx, "Repair", settings)) {
10400
+ const repairPreflightExists = phasePreflightBlocks.Repair != null;
10401
+ if (!repairPreflightExists && !beginForcedSubagentPhase(ctx, "Repair", settings)) {
9912
10402
  const reason = "Repair blocked by forced sub-agent policy availability gate.";
9913
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);
9914
10404
  queuePlanTerminalSummary(ctx, "blocked", "Plan repair blocked", { validationText: failure, reason });
@@ -9921,24 +10411,41 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9921
10411
  showBlockedPlanRecoveryMenu(ctx);
9922
10412
  return;
9923
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
+ }
9924
10424
  const retry = currentRetry + 1;
9925
10425
  const totalRetry = workflowRetry + 1;
10426
+ const effectiveMaxRetries = Math.max(maxRetries, retry);
10427
+ const effectiveMaxWorkflow = Math.max(maxWorkflowRetries, totalRetry);
9926
10428
  updateState({
9927
10429
  mode: "repairing",
9928
10430
  currentValidationRetry: retry,
9929
10431
  workflowValidationRetryCount: totalRetry,
9930
- maxValidationRetriesPerPlan: maxRetries,
9931
- maxValidationRetriesPerWorkflow: maxWorkflowRetries,
10432
+ maxValidationRetriesPerPlan: effectiveMaxRetries,
10433
+ maxValidationRetriesPerWorkflow: effectiveMaxWorkflow,
9932
10434
  lastRepairStatus: "running",
9933
- 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) } },
9934
- 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,
9935
- 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}.`,
9936
10441
  lastValidationFailure: failure,
9937
10442
  repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }),
9938
10443
  modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) },
9939
10444
  }, ctx);
9940
10445
  pi.setActiveTools(executionToolsFor(settings));
9941
- 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.`);
9942
10449
  queueWorkflowPrompt(pi, workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair));
9943
10450
  }
9944
10451
 
@@ -9973,6 +10480,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9973
10480
  function recoverPlanFromHistory(ctx: ExtensionContext, plan: SavedWorkflowPlan): void {
9974
10481
  const settings = loadWorkflowSettings(ctx.cwd);
9975
10482
  activeMission = undefined;
10483
+ const hasPersistedProgress = plan.planProgress && plan.planProgress.steps.some((s) => s.status === "completed" || s.status === "active");
9976
10484
  updateState({
9977
10485
  mode: "plan_approved",
9978
10486
  activeMissionId: undefined,
@@ -9988,24 +10496,25 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
9988
10496
  reviewerReport: undefined,
9989
10497
  reviewerVerdict: undefined,
9990
10498
  currentReviewRetry: 0,
9991
- workflowReviewRetryCount: 0,
10499
+ workflowReviewRetryCount: plan.reviewHistory?.length ?? 0,
9992
10500
  maxReviewRetriesPerPlan: undefined,
9993
10501
  maxReviewRetriesPerWorkflow: undefined,
9994
10502
  lastReviewFailure: undefined,
9995
10503
  lastReviewAttempt: undefined,
9996
10504
  lastReviewRepairStatus: "none",
9997
- reviewHistory: undefined,
10505
+ reviewHistory: plan.reviewHistory,
9998
10506
  reviewRepairInProgress: undefined,
9999
10507
  lastValidationFailure: undefined,
10000
10508
  lastRepairAttempt: undefined,
10001
- repairHistory: undefined,
10509
+ repairHistory: plan.repairHistory,
10002
10510
  lastRepairStatus: "none",
10003
- currentValidationRetry: 0,
10004
- workflowValidationRetryCount: 0,
10005
- planStepValidationIndex: undefined,
10006
- planExecutionStepIndex: undefined,
10007
- planRuntime: undefined,
10008
- 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),
10009
10518
  }, ctx);
10010
10519
  }
10011
10520
 
@@ -10026,7 +10535,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10026
10535
  if (current.mode === "validating" || current.mode === "revalidating") return "Validator is already running. Wait for validation result.";
10027
10536
  if (current.mode === "repairing") return "Executor is already running in repair mode. Revalidation will start after repair completes.";
10028
10537
  if (current.mode === "validated" && current.validationVerdict === "PASS") return "Validation passed. Next action: /plan continue to complete and return to ready state.";
10029
- 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.";
10030
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>.";
10031
10540
  if (current.mode === "validated" && current.approvedPlan && validationAvailable) return "Validation status is unknown. Next action: /plan revalidate.";
10032
10541
  return `No safe automatic continuation is available for state: ${current.mode}.`;
@@ -10043,11 +10552,38 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10043
10552
  };
10044
10553
  add(latest);
10045
10554
  candidates.forEach(add);
10046
- 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." };
10047
10556
  if (ordered.length > 0) return { plan: ordered[0], candidates: ordered, source: "history" };
10048
10557
  return { candidates: [], source: "none", reason: "No active or recoverable approved Plan Mode workflow found." };
10049
10558
  }
10050
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
+
10051
10587
  function planResumeChoiceLabel(plan: SavedWorkflowPlan): string {
10052
10588
  return `${plan.id} | ${plan.approvalStatus} | ${plan.planningMode} | ${compact(plan.originalTask || "Saved plan", 80).replace(/\n/g, " ")}`;
10053
10589
  }
@@ -10086,7 +10622,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10086
10622
  const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim());
10087
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)}`);
10088
10624
  let selected = resolved.plan;
10089
- 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
+ }
10090
10629
 
10091
10630
  const reusePlan = async (plan: SavedWorkflowPlan, amend: boolean): Promise<void> => {
10092
10631
  activeMission = undefined;
@@ -10115,8 +10654,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10115
10654
  show(pi, `${summary}\n\nChoose what to do.`);
10116
10655
  if (!ctx.hasUI) return;
10117
10656
  const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
10118
- const choice = await ctx.ui.select("Plan Resume", [...(state.approvedPlan ? ["Continue Current Plan"] : []), ...(hasAlternatives ? ["Choose Another Plan"] : []), "List Plan Status", "List Plans", "Cancel"]);
10119
- 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);
10120
10666
  else if (choice === "Choose Another Plan") {
10121
10667
  const other = await choosePlanFromHistory(ctx, allPlans.filter((plan) => plan.id !== currentPlan?.id), "Choose a plan to resume:");
10122
10668
  if (other) await showSelectedPlanMenu(other);
@@ -10137,8 +10683,13 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10137
10683
  const resolved = resolveActivePlanForResume();
10138
10684
  const selected = await chooseResumePlan(ctx, resolved, "continue");
10139
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}`);
10140
- recoverPlanFromHistory(ctx, selected);
10141
- 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
+ }
10142
10693
  }
10143
10694
 
10144
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)}`);
@@ -10185,15 +10736,19 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10185
10736
  return;
10186
10737
  }
10187
10738
  if (state.validationVerdict === "PARTIAL PASS") {
10188
- 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") {
10189
10743
  show(pi, `# ${title}\n\nValidation partially passed but includes concrete repairable issues. Starting safe repair under the configured validation repair policy.`);
10190
10744
  await startWorkflowRepair(ctx, "user");
10191
10745
  return;
10192
10746
  }
10193
- 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;
10194
10749
  }
10195
10750
  if (state.validationVerdict === "FAIL" || state.lastValidationFailure) {
10196
- 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.`);
10197
10752
  }
10198
10753
  if (state.approvedPlan && validationAvailable) {
10199
10754
  show(pi, `# ${title}\n\nValidation status is unknown. Revalidating the approved plan.`);
@@ -10202,7 +10757,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10202
10757
  }
10203
10758
  }
10204
10759
 
10205
- 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.`);
10206
10761
  }
10207
10762
 
10208
10763
  function appendWorkflowReviewHistory(entry: NonNullable<WorkflowState["reviewHistory"]>[number]): WorkflowState["reviewHistory"] {
@@ -10234,18 +10789,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10234
10789
  const retry = state.currentValidationRetry ?? 0;
10235
10790
  const workflowRetry = workflowValidationRetryCount(state);
10236
10791
  const failure = `Validation ${verdict}. ${compact(validationText, 1200)}`;
10237
- const failureClass = classifyValidationFailure(verdict, validationText);
10792
+ const failureClass = classifyValidationFailure(verdict, validationText, {
10793
+ concreteRepairableIssue: state.concreteRepairableIssue,
10794
+ manualVerificationRequired: state.manualVerificationRequired,
10795
+ });
10238
10796
  if (failureClass === "manual_only") {
10239
10797
  const reason = "Validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10240
- 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);
10241
10800
  queuePlanTerminalSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason });
10242
- 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)`);
10243
- 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));
10244
10803
  return;
10245
10804
  }
10246
10805
  const validationStatus = planValidationStatusForVerdict(verdict);
10247
10806
  const decision = resolveRepairRetryDecision({ gate: "validation", verdict: verdict ?? "UNKNOWN", report: failure, settings, state, itemRetry: retry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan) });
10248
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)}`);
10249
10809
  if (!decision.allowed) {
10250
10810
  const reason = decision.reason ?? "repair requires approval or safety gate.";
10251
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);
@@ -10306,6 +10866,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
10306
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 });
10307
10867
  pi.setActiveTools(executionToolsFor(settings));
10308
10868
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
10869
+ saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
10309
10870
  if (source !== "auto") show(pi, `# Mission Final Repair Started\n\nRetry: ${retry} of ${maxRetries}\n\n${renderMissionProgress(repairing, settings)}`);
10310
10871
  queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
10311
10872
  }
@@ -10395,6 +10956,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10395
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 });
10396
10957
  pi.setActiveTools(executionToolsFor(settings));
10397
10958
  updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
10959
+ saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
10398
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)}`);
10399
10961
  if (source === "auto") queueAgentTurn(pi, "Repair the current mission milestone validation failure, then submit workflow_repair_result.", "mission-repair-trigger");
10400
10962
  else queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
@@ -10466,6 +11028,17 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10466
11028
 
10467
11029
  async function handleMissionFinalValidationFailure(ctx: ExtensionContext, mission: MissionState, verdict: WorkflowState["validationVerdict"], validationText: string) {
10468
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
+ }
10469
11042
  const retry = missionFinalValidationRetryCount(mission);
10470
11043
  const maxRetries = missionMaxFinalValidationRetries(mission, settings);
10471
11044
  const failure = `Final mission validation ${verdict}. ${compact(validationText, 1200)}`;
@@ -10473,16 +11046,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10473
11046
  const nextRetry = retry + 1;
10474
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}.` });
10475
11048
  checkpointMission(failed, `Final mission validation failed. Retry ${retry}/${maxRetries}. ${compact(validationText, 500)}`, "Evaluate safe final repair retry before completion.", undefined, { validationResult: verdict });
10476
- if (classifyValidationFailure(verdict, validationText) === "manual_only") {
10477
- const reason = "Final validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10478
- 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." });
10479
- checkpointMission(blocked, `Mission final validation pending manual verification. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", undefined, { validationResult: verdict });
10480
- updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
10481
- queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
10482
- recordWorkflowInternalEvent(ctx, "Mission final validation manual-verification blocker suppressed from transcript.");
10483
- showBlockedMissionRecoveryMenu(ctx);
10484
- return;
10485
- }
10486
11049
  const shouldBlock = settings.missions.autoRepairFinalValidationFailures !== true || retry >= maxRetries || Boolean(unsafe);
10487
11050
  if (shouldBlock) {
10488
11051
  const reason = settings.missions.autoRepairFinalValidationFailures !== true ? "missions.autoRepairFinalValidationFailures=false."
@@ -10509,6 +11072,36 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10509
11072
  const retry = mission.currentValidationRetry ?? 0;
10510
11073
  const missionRetry = missionValidationRetryCount(mission);
10511
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
+ }
10512
11105
  const failedMilestones = mission.milestones.map((m, i) => i === index ? { ...m, status: "active" as const } : m);
10513
11106
  const failed = saveActiveMission({
10514
11107
  ...mission,
@@ -10524,17 +11117,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10524
11117
  lastSummary: `Validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`,
10525
11118
  });
10526
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 });
10527
- const failureClass = classifyValidationFailure(verdict, validationText);
10528
- if (failureClass === "manual_only") {
10529
- const reason = "Mission validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
10530
- 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." });
10531
- checkpointMission(blocked, `Mission validation pending manual verification for ${milestone?.id ?? "current milestone"}. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", milestone?.id, { validationResult: verdict });
10532
- updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
10533
- queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
10534
- recordWorkflowInternalEvent(ctx, "Mission validation manual-verification blocker suppressed from transcript.");
10535
- showBlockedMissionRecoveryMenu(ctx);
10536
- return;
10537
- }
10538
11120
  const unsafe = validationFailureRequiresApproval(failure, settings);
10539
11121
  const shouldBlock = mission.autonomy === "manual"
10540
11122
  || settings.missions.autoRepairValidationFailures === false
@@ -10609,16 +11191,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10609
11191
  const questions = state.clarifyingQuestions ?? [];
10610
11192
  const answerSummary = formatAnswersForPlanner(questions, answers);
10611
11193
  const settings = loadWorkflowSettings(ctx.cwd);
11194
+ const priorPlanning = snapshotSubagentPhaseUsage("Planning");
10612
11195
  if (!beginForcedSubagentPhase(ctx, "Planning", settings)) {
10613
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);
10614
11197
  return;
10615
11198
  }
11199
+ const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings);
11200
+ if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
10616
11201
  updateState({
10617
11202
  mode: "planning",
10618
11203
  clarifyingAnswers: answers,
10619
11204
  lastWorkflowHandoff: undefined,
10620
11205
  }, ctx);
10621
- 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 });
10622
11207
  }
10623
11208
 
10624
11209
  async function runStandardClarificationSelect(ctx: ExtensionContext, questions: ClarificationQuestion[]): Promise<ClarificationAnswer[] | undefined> {
@@ -10680,16 +11265,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10680
11265
  const questions = mission.clarificationQuestions?.length ? mission.clarificationQuestions : (state.clarifyingQuestions ?? []);
10681
11266
  const settings = loadWorkflowSettings(ctx.cwd);
10682
11267
  const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning" };
11268
+ const priorPlanning = snapshotSubagentPhaseUsage("Planning");
10683
11269
  if (!beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
10684
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." });
10685
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.");
10686
11272
  updateState({ mode: "mission_blocked", activeMissionId: blocked.id, task: blocked.goal, originalTask: blocked.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, draftPlan: blocked.planText, approvedPlan: undefined }, ctx);
10687
11273
  return;
10688
11274
  }
11275
+ const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings, planningOverride);
11276
+ if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
10689
11277
  const next = saveActiveMission({ ...mission, status: "draft", clarificationQuestions: questions, clarificationAnswers: answers, lastSummary: "Mission clarification answered. Resuming milestone planning." });
10690
11278
  checkpointMission(next, "Mission clarification answers recorded.", "Resume dynamic milestone planning with clarification applied.");
10691
11279
  updateState({ mode: "mission_planning", activeMissionId: next.id, task: next.goal, originalTask: next.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, lastWorkflowHandoff: undefined }, ctx);
10692
- await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers });
11280
+ await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers }, { planningPreflightSatisfied });
10693
11281
  }
10694
11282
 
10695
11283
  function showMissionClarificationFallback(reason: string): void {
@@ -10972,11 +11560,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10972
11560
  const verdict = state.validationVerdict;
10973
11561
  const passed = verdict === "PASS";
10974
11562
  const partial = verdict === "PARTIAL PASS";
10975
- 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";
10976
11564
  const failed = verdict === "FAIL" || verdict === "UNKNOWN";
10977
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:";
10978
11566
  const choices = [
10979
11567
  ...(failed || repairablePartial ? ["Run Safe Repair"] : []),
11568
+ ...(partial && !repairablePartial ? ["Repair / Retry"] : []),
10980
11569
  ...(!passed ? ["Revalidate"] : []),
10981
11570
  "List Summary",
10982
11571
  partial && !repairablePartial ? "Revise / Add Manual Verification Feedback" : "Revise / Fix Issues",
@@ -10985,7 +11574,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
10985
11574
  ...(!passed ? ["Cancel"] : []),
10986
11575
  ];
10987
11576
  const choice = await ctx.ui.select(title, choices);
10988
- if (choice === "Run Safe Repair") await startWorkflowRepair(ctx, "user");
11577
+ if (choice === "Run Safe Repair" || choice === "Repair / Retry") await startWorkflowRepair(ctx, "user");
10989
11578
  else if (choice === "Revalidate") await beginValidation(ctx, true, true);
10990
11579
  else if (choice === "List Summary") show(pi, renderWorkflowSummary(state, ctx.cwd));
10991
11580
  else if (choice === "Revise / Fix Issues" || choice === "Revise / Add Manual Verification Feedback") {
@@ -11070,7 +11659,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11070
11659
  if (choice === "Set planning.depth") {
11071
11660
  const depth = parsePlanningDepth((await ctx.ui.select("Planning depth", ["fast", "standard", "deep", "maximum"])) ?? "");
11072
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"); }
11073
- } 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
+ }
11074
11666
  }
11075
11667
  }
11076
11668
 
@@ -11088,7 +11680,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11088
11680
  } else if (choice === "Set maxClarificationQuestions") {
11089
11681
  const count = parsePositiveInt((await ctx.ui.select("Max clarification questions", ["1", "2", "3", "4", "5"])) ?? "");
11090
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"); }
11091
- } 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
+ }
11092
11687
  }
11093
11688
  }
11094
11689
 
@@ -11096,9 +11691,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11096
11691
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11097
11692
  const keys = subagentPhaseSettingKeys(phase);
11098
11693
  while (ctx.hasUI) {
11099
- 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"]);
11100
11695
  if (!choice || choice === "Back") return;
11101
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11102
11696
  if (choice.endsWith("Policy")) {
11103
11697
  const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11104
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"); }
@@ -11114,9 +11708,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11114
11708
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11115
11709
  const keys = subagentPhaseSettingKeys(phase);
11116
11710
  while (ctx.hasUI) {
11117
- 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"]);
11118
11712
  if (!choice || choice === "Back") return;
11119
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11120
11713
  if (choice.endsWith("Policy")) {
11121
11714
  const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11122
11715
  if (policy) {
@@ -11161,9 +11754,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11161
11754
  async function showStandardParallelismSettingsMenu(ctx: ExtensionContext) {
11162
11755
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11163
11756
  while (ctx.hasUI) {
11164
- 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"]);
11165
11758
  if (!choice || choice === "Back") return;
11166
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11167
11759
  const key = choice.replace("Set ", "");
11168
11760
  const enabled = await chooseBool(ctx, `standard.subagents.${key}?`);
11169
11761
  if (enabled !== undefined) {
@@ -11197,16 +11789,18 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11197
11789
  else if (choice === "Activity Indicator") {
11198
11790
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11199
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); }
11200
- } 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
+ }
11201
11796
  }
11202
11797
  }
11203
11798
 
11204
11799
  async function showMissionPlanningSubagentSettingsMenu(ctx: ExtensionContext) {
11205
11800
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11206
11801
  while (ctx.hasUI) {
11207
- 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"]);
11208
11803
  if (!choice || choice === "Back") return;
11209
- if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11210
11804
  if (choice === "Mission Planning Policy") {
11211
11805
  const policy = parseSubagentPolicy((await ctx.ui.select("Mission planning policy", ["off", "auto", "deep", "maximum", "forced"])) ?? "");
11212
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"); }
@@ -11248,7 +11842,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11248
11842
  else if (choice === "Activity Indicator") {
11249
11843
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11250
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); }
11251
- } 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
+ }
11252
11849
  }
11253
11850
  }
11254
11851
 
@@ -11270,7 +11867,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11270
11867
  else if (choice === "Activity Indicator") {
11271
11868
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11272
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); }
11273
- } 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
+ }
11274
11874
  }
11275
11875
  }
11276
11876
 
@@ -11292,7 +11892,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11292
11892
  else if (choice === "Activity Indicator") {
11293
11893
  const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
11294
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); }
11295
- } 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
+ }
11296
11899
  }
11297
11900
  }
11298
11901
 
@@ -11308,7 +11911,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11308
11911
  } else if (choice === "Set editConcurrencyMode") {
11309
11912
  const mode = parseEditConcurrencyMode((await ctx.ui.select("Edit concurrency mode", ["sequential", "scoped", "blocked"])) ?? "");
11310
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"); }
11311
- } 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
+ }
11312
11918
  }
11313
11919
  }
11314
11920
 
@@ -11341,7 +11947,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11341
11947
  async function selectCompactionModel(ctx: ExtensionContext) {
11342
11948
  const selected = await selectProviderAndModel(ctx, "Compaction model");
11343
11949
  if (!selected) return;
11344
- const result = updateSettings(ctx.cwd, "global", (s) => {
11950
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11345
11951
  s.context.compactionMode = "custom_model";
11346
11952
  s.context.compactionModelProvider = selected.provider;
11347
11953
  s.context.compactionModel = selected.model;
@@ -11359,7 +11965,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11359
11965
  const modelChoice = await ctx.ui.select(`Compaction model for ${provider}:`, providerModels.map((model) => `${provider}/${model}`));
11360
11966
  if (!modelChoice) return;
11361
11967
  const model = modelChoice.replace(`${provider}/`, "");
11362
- const result = updateSettings(ctx.cwd, "global", (s) => {
11968
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11363
11969
  s.context.compactionMode = "custom_model";
11364
11970
  s.context.compactionModel = model;
11365
11971
  s.context.customCompactionEnabled = true;
@@ -11373,7 +11979,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11373
11979
  if (!choice) return;
11374
11980
  const agent = choice === "Custom agent name" ? (await ctx.ui.input("Custom compaction agent:", "agent-name"))?.trim() : choice;
11375
11981
  if (!agent) return;
11376
- const result = updateSettings(ctx.cwd, "global", (s) => {
11982
+ const result = updateSettings(ctx.cwd, undefined, (s) => {
11377
11983
  s.context.compactionMode = "custom_agent";
11378
11984
  s.context.compactionAgent = agent;
11379
11985
  s.context.customCompactionEnabled = true;
@@ -11390,56 +11996,52 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11390
11996
  const mode = parseCompactionMode((await ctx.ui.select("Compaction mode", ["Pi default", "Custom model", "Disabled"])) ?? "");
11391
11997
  if (!mode) continue;
11392
11998
  if (mode === "custom_model") { await selectCompactionModel(ctx); continue; }
11393
- const r = updateSettings(ctx.cwd, "global", (s) => {
11999
+ const r = updateSettings(ctx.cwd, undefined, (s) => {
11394
12000
  s.context.compactionMode = mode;
11395
12001
  s.context.customCompactionEnabled = false;
11396
12002
  });
11397
12003
  ctx.ui.notify(`Compaction mode set to ${compactionModeLabel(mode)} in ${r.file}`, "info");
11398
- } else if (choice === "Compaction Check Mode") {
11399
- const checkMode = parseWorkflowCompactionCheckMode((await ctx.ui.select("Compaction check mode", ["Boundary only", "In-session"])) ?? "");
11400
- if (!checkMode) continue;
11401
- const r = updateSettings(ctx.cwd, "global", (s) => { s.context.workflowCompactionCheckMode = checkMode; });
11402
- ctx.ui.notify(`Compaction check mode set to ${workflowCompactionCheckModeLabel(checkMode)} in ${r.file}`, "info");
11403
12004
  } else if (choice === "Compaction Provider") {
11404
12005
  await selectCompactionModel(ctx);
11405
12006
  } else if (choice === "Compaction Model") {
11406
12007
  await selectCompactionModelForCurrentProvider(ctx);
11407
- } else if (choice === "Compaction Agent") {
11408
- await selectCompactionAgent(ctx);
11409
12008
  } else if (choice === "Custom Compaction Enabled") {
11410
12009
  const enabled = await chooseBool(ctx, "Custom compaction enabled?");
11411
- 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"); }
11412
12011
  } else if (choice === "Workflow Auto Trigger Enabled") {
11413
12012
  const enabled = await chooseBool(ctx, "Workflow proactive auto-compaction trigger enabled?");
11414
- 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"); }
11415
12014
  } else if (choice === "Workflow Trigger Percent") {
11416
12015
  const raw = String((await ctx.ui.input("Workflow compaction trigger percent (50-95, default, or reset)", String(compactionTriggerPercent(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
11417
12016
  if (raw === "default" || raw === "reset") {
11418
12017
  const fallback = defaultCompactionTriggerPercent();
11419
- const r = updateSettings(ctx.cwd, "global", (s) => { s.context.compactionTriggerPercent = fallback; });
12018
+ const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionTriggerPercent = fallback; });
11420
12019
  ctx.ui.notify(`Workflow compaction trigger percent reset to default ${fallback}% in ${r.file}`, "info");
11421
12020
  } else {
11422
12021
  const count = Number(raw);
11423
- 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"); }
11424
12023
  else ctx.ui.notify("Trigger percent must be an integer from 50 to 95, default, or reset.", "error");
11425
12024
  }
11426
12025
  } else if (choice === "Workflow Trigger Cooldown") {
11427
12026
  const count = Number((await ctx.ui.input("Minimum minutes between Workflow Suite proactive compaction attempts", String(compactionCooldownMinutes(loadWorkflowSettings(ctx.cwd)))) ?? ""));
11428
- 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"); }
11429
12028
  else ctx.ui.notify("Cooldown must be an integer from 0 to 240 minutes.", "error");
11430
12029
  } else if (choice === "Custom Reserve Tokens") {
11431
12030
  const raw = String((await ctx.ui.input("Custom compaction reserve tokens (4096-65536, default, or reset)", String(customCompactionReserveTokens(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
11432
12031
  const count = Number(raw);
11433
- 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"); }
11434
- 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"); }
11435
12034
  else ctx.ui.notify("Reserve tokens must be an integer from 4096 to 65536, default, or reset.", "error");
11436
12035
  } else if (choice === "Custom Keep Recent Tokens") {
11437
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();
11438
12037
  const count = Number(raw);
11439
- 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"); }
11440
- 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"); }
11441
12040
  else ctx.ui.notify("Keep-recent tokens must be an integer from 1000 to 200000, default, or reset.", "error");
11442
- } 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
+ }
11443
12045
  }
11444
12046
  }
11445
12047
 
@@ -11588,12 +12190,41 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11588
12190
  } else if (choice === "Repair / Validation Retry") {
11589
12191
  await showWorkflowRepairRetrySettingsMenu(ctx);
11590
12192
  } else if (choice === "Plan Progress / Runtime") {
11591
- 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"]);
11592
12194
  if (!setting || setting === "Back") continue;
11593
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11594
- const key = setting === "Plan Progress Enabled" ? "planProgressEnabled" : "planRuntimeEnabled";
11595
- const enabled = await chooseBool(ctx, `workflow.${key}?`);
11596
- 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
+ }
11597
12228
  } else if (choice === "Plan History") {
11598
12229
  const setting = await ctx.ui.select("Plan History", ["Save Plans", "Save Plan History", "Plan History Limit", "Back"]);
11599
12230
  const workflow = loadWorkflowSettings(ctx.cwd).workflow as ReturnType<typeof loadWorkflowSettings>["workflow"] & { savePlans?: boolean; savePlanHistory?: boolean; planHistoryLimit?: number };
@@ -11607,7 +12238,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11607
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"); }
11608
12239
  }
11609
12240
  } else if (choice === "List Current Settings") {
11610
- 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}`);
11611
12243
  }
11612
12244
  }
11613
12245
  }
@@ -11615,9 +12247,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11615
12247
  async function showStandardTodoSettingsMenu(ctx: ExtensionContext) {
11616
12248
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11617
12249
  while (ctx.hasUI) {
11618
- 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"]);
11619
12251
  if (!setting || setting === "Back") return;
11620
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11621
12252
  if (setting === "To Do Trigger Mode") {
11622
12253
  const mode = parseStandardTodoTriggerMode((await ctx.ui.select("To Do Trigger Mode", ["Disabled", "On request", "Automatic when useful", "Required"])) ?? "");
11623
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"); }
@@ -11632,9 +12263,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11632
12263
  async function showStandardClarificationSettingsMenu(ctx: ExtensionContext) {
11633
12264
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11634
12265
  while (ctx.hasUI) {
11635
- 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"]);
11636
12267
  if (!setting || setting === "Back") return;
11637
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11638
12268
  if (setting === "Clarification Mode") {
11639
12269
  const mode = parseStandardClarificationMode((await ctx.ui.select("Standard clarification mode", ["auto", "always_for_nontrivial", "never"])) ?? "");
11640
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"); }
@@ -11658,12 +12288,28 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11658
12288
  async function showStandardProgressRuntimeSettingsMenu(ctx: ExtensionContext) {
11659
12289
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11660
12290
  while (ctx.hasUI) {
11661
- 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"]);
11662
12292
  if (!setting || setting === "Back") return;
11663
- if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
11664
- const key = setting === "Status Widget Visible" ? "statusWidgetVisible" : "todoProgressVisible";
11665
- const enabled = await chooseBool(ctx, `standard.${key}?`);
11666
- 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
+ }
11667
12313
  }
11668
12314
  }
11669
12315
 
@@ -11672,7 +12318,11 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11672
12318
  while (ctx.hasUI) {
11673
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"]);
11674
12320
  if (!choice || choice === "Back") return;
11675
- 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
+ }
11676
12326
  if (choice === "Enable / Disable Standard Mode") {
11677
12327
  const enabled = await chooseBool(ctx, "standard.enabled?");
11678
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"); }
@@ -11760,7 +12410,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11760
12410
  "Auto Run After Approval": "autoRunAfterApproval",
11761
12411
  };
11762
12412
  const key = keyMap[setting];
11763
- 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
+ });
11764
12419
  ctx.ui.notify(`missions.${key} set to ${enabled} in ${r.file}`, "info");
11765
12420
  }
11766
12421
  }
@@ -11769,7 +12424,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11769
12424
  async function showMissionRepairRetrySettingsMenu(ctx: ExtensionContext) {
11770
12425
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11771
12426
  while (ctx.hasUI) {
11772
- 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"]);
11773
12428
  if (!setting || setting === "Back") return;
11774
12429
  if (setting === "Max Review Retries Per Mission") {
11775
12430
  const raw = await ctx.ui.select(setting, ["0", "1", "2", "3", "4", "5", "10"]);
@@ -11788,6 +12443,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11788
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"); }
11789
12444
  } else {
11790
12445
  const keyMap: Record<string, string> = {
12446
+ "Auto Repair Review Failures": "autoRepairReviewFailures",
11791
12447
  "Auto Repair Validation Failures": "autoRepairValidationFailures",
11792
12448
  "Pause After Validation Failure": "pauseAfterValidationFailure",
11793
12449
  "Require Approval For Out-Of-Scope Repair": "requireApprovalForOutOfScopeRepair",
@@ -11797,7 +12453,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11797
12453
  };
11798
12454
  const key = keyMap[setting];
11799
12455
  const enabled = await chooseBool(ctx, `${String(key)}?`);
11800
- 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"); }
11801
12457
  }
11802
12458
  }
11803
12459
  }
@@ -11874,22 +12530,55 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11874
12530
  } else if (choice === "Mission Sub-agents / Workers" || choice === "Mission Worker Targets") {
11875
12531
  await showMissionSubagentWorkerSettingsMenu(ctx);
11876
12532
  } else if (choice === "Mission Progress / Runtime") {
11877
- 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"]);
11878
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
+ }
11879
12540
  if (setting === "Progress Widget" || setting === "Progress Bar Display") {
11880
12541
  const key = setting === "Progress Widget" ? "progressWidgetEnabled" : "showProgressBar";
11881
12542
  const enabled = await chooseBool(ctx, `missions.${key}?`);
11882
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"); }
11883
12544
  } else if (setting === "Heartbeat / Watchdog") await showHeartbeatWatchdogMenu(ctx);
11884
- else {
11885
- const key = setting === "Runtime Budget" ? "maxRuntimeHours" : "checkpointIntervalMinutes";
11886
- const count = parsePositiveInt((await ctx.ui.input(key, key === "maxRuntimeHours" ? "13" : "30")) ?? "");
11887
- 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"); }
11888
12578
  }
11889
- } else if (choice === "Runtime Budget" || choice === "Checkpoint Interval") {
11890
- const key = choice === "Runtime Budget" ? "maxRuntimeHours" : "checkpointIntervalMinutes";
11891
- const count = parsePositiveInt((await ctx.ui.input(key, key === "maxRuntimeHours" ? "13" : "30")) ?? "");
11892
- 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"); }
11893
12582
  } else if (choice === "Approval Safety" || choice === "Validation Per Milestone" || choice === "Full Auto Safety" || choice === "Auto Resume") {
11894
12583
  const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["missions"]> = { "Validation Per Milestone": "requireValidationPerMilestone", "Approval Safety": "requireApprovalForDestructiveActions", "Full Auto Safety": "allowFullAuto", "Auto Resume": "autoResume" };
11895
12584
  const key = keyMap[choice];
@@ -11906,7 +12595,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11906
12595
  } else if (choice === "Heartbeat / Watchdog" || choice === "Heartbeat / Watchdog Status") {
11907
12596
  await showHeartbeatWatchdogMenu(ctx);
11908
12597
  } else if (choice === "List Current Settings") {
11909
- 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)}`);
11910
12600
  }
11911
12601
  }
11912
12602
  }
@@ -11918,10 +12608,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
11918
12608
  async function showGlobalSafetySettingsMenu(ctx: ExtensionContext) {
11919
12609
  if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
11920
12610
  while (ctx.hasUI) {
11921
- 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"]);
11922
12612
  if (!choice || choice === "Back") return;
11923
- 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" };
11924
- 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)));
11925
12615
  else if (choice === "Repo Lock (Project)") {
11926
12616
  const enabled = await chooseBool(ctx, "safety.repoLockEnabled for this project only?");
11927
12617
  if (enabled !== undefined) {
@@ -12400,6 +13090,7 @@ Pi Version: v${VERSION}
12400
13090
  const observed = subagentUsageByPhase[phase] ?? 0;
12401
13091
  if (observed >= required) return;
12402
13092
  if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") {
13093
+ if (phase === "Repair") return;
12403
13094
  const label = state.mode === "standard" ? "Standard Mode" : phase;
12404
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}.` };
12405
13096
  }
@@ -12592,6 +13283,7 @@ Pi Version: v${VERSION}
12592
13283
  });
12593
13284
 
12594
13285
  pi.on("tool_execution_start", async (event, ctx) => {
13286
+ activateCurrentPlanStepForTool(ctx, { toolName: event.toolName, input: event.args });
12595
13287
  workflowActiveToolExecutions += 1;
12596
13288
  if (event.toolName === "subagent") beginSubagentActivity(event.toolCallId, event.args as Record<string, unknown>, ctx);
12597
13289
  });
@@ -12662,6 +13354,13 @@ Pi Version: v${VERSION}
12662
13354
  if (sub === "archive" || sub === "abandon") return archiveAndReturnToPlanInput(ctx, `plan ${sub} requested by /plan ${sub}`);
12663
13355
  if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /plan approve"); return; }
12664
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
+ }
12665
13364
  if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
12666
13365
  await startFreshPlanFromInput(ctx, raw);
12667
13366
  };
@@ -12733,6 +13432,13 @@ Pi Version: v${VERSION}
12733
13432
  if (sub === "resume") return handlePlanResume(ctx);
12734
13433
  if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /p approve"); return; }
12735
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
+ }
12736
13442
  if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
12737
13443
  await startFreshPlanFromInput(ctx, raw);
12738
13444
  };
@@ -13290,6 +13996,18 @@ Pi Version: v${VERSION}
13290
13996
  const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxClarificationQuestions = count; });
13291
13997
  return show(pi, updatedMessage(result.scope, result.file, "planning.maxClarificationQuestions", String(count)));
13292
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
+ }
13293
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")) {
13294
14012
  if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set standard ${key} <true|false>\``);
13295
14013
  const result = updateSettings(ctx.cwd, scope, (s) => {
@@ -13327,6 +14045,12 @@ Pi Version: v${VERSION}
13327
14045
  const result = updateSettings(ctx.cwd, scope, (s) => { s.standard.maxClarificationQuestions = count; });
13328
14046
  return show(pi, updatedMessage(result.scope, result.file, "standard.maxClarificationQuestions", String(count)));
13329
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
+ }
13330
14054
  if (subject === "standard" && key === "clarificationTiming") {
13331
14055
  const timing = parseClarificationTiming(value);
13332
14056
  if (!timing) return show(pi, "# Error\n\nUsage: `/workflow-settings set standard clarificationTiming <after_initial_analysis|immediate>`");
@@ -13489,11 +14213,11 @@ Pi Version: v${VERSION}
13489
14213
  const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.subagentPolicy = policy; });
13490
14214
  return show(pi, updatedMessage(result.scope, result.file, "missions.subagentPolicy", policy));
13491
14215
  }
13492
- 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")) {
13493
14217
  const rawCount = value === undefined ? NaN : Number(value);
13494
14218
  const count = Number.isInteger(rawCount) ? rawCount : undefined;
13495
- const max = key === "maxRuntimeHours" ? 168 : key === "checkpointIntervalMinutes" ? 168 : key === "maxClarificationQuestions" ? 6 : key === "watchdogStaleMinutes" ? 1440 : key === "maxValidationRetriesPerMilestone" ? 10 : key === "maxValidationRetriesPerMission" ? 100 : key === "maxFinalValidationRetries" ? 10 : 8;
13496
- 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;
13497
14221
  if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set missions ${key} <${min}-${max}>\``);
13498
14222
  const result = updateSettings(ctx.cwd, scope, (s) => { (s.missions as typeof s.missions & Record<string, number | string | boolean>)[key] = count; });
13499
14223
  return show(pi, updatedMessage(result.scope, result.file, `missions.${key}`, String(count)));
@@ -13501,35 +14225,35 @@ Pi Version: v${VERSION}
13501
14225
  if (subject === "context" && key === "compactionMode") {
13502
14226
  const mode = parseCompactionMode(value);
13503
14227
  if (!mode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context compactionMode <pi_default|custom_model|custom_agent|disabled>`");
13504
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.compactionMode = mode; });
14228
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionMode = mode; });
13505
14229
  return show(pi, updatedMessage(result.scope, result.file, "context.compactionMode", compactionModeLabel(mode)));
13506
14230
  }
13507
14231
  if (subject === "context" && key === "customCompactionEnabled") {
13508
14232
  if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context customCompactionEnabled <true|false>`");
13509
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.customCompactionEnabled = bool; });
14233
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.customCompactionEnabled = bool; });
13510
14234
  return show(pi, updatedMessage(result.scope, result.file, "context.customCompactionEnabled", String(bool)));
13511
14235
  }
13512
14236
  if (subject === "context" && key === "autoCompactionEnabled") {
13513
14237
  if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context autoCompactionEnabled <true|false>`");
13514
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.autoCompactionEnabled = bool; });
14238
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.autoCompactionEnabled = bool; });
13515
14239
  return show(pi, updatedMessage(result.scope, result.file, "context.autoCompactionEnabled", String(bool)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13516
14240
  }
13517
14241
  if (subject === "context" && key === "workflowCompactionCheckMode") {
13518
14242
  const checkMode = parseWorkflowCompactionCheckMode(value);
13519
14243
  if (!checkMode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context workflowCompactionCheckMode <boundary|in_session>`");
13520
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.workflowCompactionCheckMode = checkMode; });
14244
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.workflowCompactionCheckMode = checkMode; });
13521
14245
  return show(pi, updatedMessage(result.scope, result.file, "context.workflowCompactionCheckMode", workflowCompactionCheckModeLabel(checkMode)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13522
14246
  }
13523
14247
  if (subject === "context" && (key === "compactionTriggerPercent" || key === "compactionCooldownMinutes" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens")) {
13524
14248
  const normalizedValue = String(value ?? "").trim().toLowerCase();
13525
14249
  if (key === "compactionTriggerPercent" && (normalizedValue === "default" || normalizedValue === "reset")) {
13526
14250
  const fallback = defaultCompactionTriggerPercent();
13527
- const result = updateSettings(ctx.cwd, scope ?? "global", (s) => { s.context.compactionTriggerPercent = fallback; });
14251
+ const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionTriggerPercent = fallback; });
13528
14252
  return show(pi, updatedMessage(result.scope, result.file, "context.compactionTriggerPercent", `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
13529
14253
  }
13530
14254
  if ((key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens") && (normalizedValue === "default" || normalizedValue === "reset")) {
13531
14255
  const fallback = key === "customCompactionReserveTokens" ? DEFAULT_PI_COMPACTION_RESERVE_TOKENS : DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS;
13532
- 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; });
13533
14257
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
13534
14258
  }
13535
14259
  const rawCount = value === undefined ? NaN : Number(value);
@@ -13538,12 +14262,12 @@ Pi Version: v${VERSION}
13538
14262
  const max = key === "compactionTriggerPercent" ? 95 : key === "compactionCooldownMinutes" ? 240 : key === "customCompactionReserveTokens" ? 65536 : 200000;
13539
14263
  const usage = key === "compactionTriggerPercent" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens" ? `<${min}-${max}|default|reset>` : `<${min}-${max}>`;
13540
14264
  if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set context ${key} ${usage}\``);
13541
- 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; });
13542
14266
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, String(count)) + `\n\n${compactionTriggerStatus(result.settings)}`);
13543
14267
  }
13544
14268
  if (subject === "context" && (key === "compactionModelProvider" || key === "compactionModel" || key === "compactionAgent")) {
13545
14269
  const text = parts.slice(offset + 2).join(" ");
13546
- 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; });
13547
14271
  return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, text || "(empty)"));
13548
14272
  }
13549
14273
  if (subject === "workflow" && key === "repairRetry.enabled") {
@@ -13611,7 +14335,7 @@ Pi Version: v${VERSION}
13611
14335
  const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.maxReviewRetriesPerMission = count; });
13612
14336
  return show(pi, updatedMessage(result.scope, result.file, "missions.maxReviewRetriesPerMission", String(count)));
13613
14337
  }
13614
- 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")) {
13615
14339
  if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set workflow ${key} <true|false>\``);
13616
14340
  const result = updateSettings(ctx.cwd, scope, (s) => {
13617
14341
  (s.workflow as typeof s.workflow & Record<string, boolean | number | string | undefined>)[key] = bool;
@@ -13868,6 +14592,23 @@ Public workflow commands:
13868
14592
  else return;
13869
14593
  }
13870
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
+
13871
14612
  async function handleMissionPlainInputChoice(ctx: ExtensionContext, text: string) {
13872
14613
  const normalized = text.trim().toLowerCase();
13873
14614
  if (normalized === "status") {
@@ -13886,6 +14627,10 @@ Public workflow commands:
13886
14627
  await startFreshPlanFromInput(ctx, text);
13887
14628
  return { action: "handled" as const };
13888
14629
  }
14630
+ if (state.mode === "plan_approved") {
14631
+ await handlePlanApprovedPlainInput(ctx, text);
14632
+ return { action: "handled" as const };
14633
+ }
13889
14634
  if (planInputNeedsChoice(state)) {
13890
14635
  await handlePlanPlainInputChoice(ctx, text);
13891
14636
  return { action: "handled" as const };
@@ -13924,6 +14669,40 @@ Public workflow commands:
13924
14669
  if (workflowSubagentWorkerMode()) return { systemPrompt: event.systemPrompt };
13925
14670
  stopStartupVisual(ctx);
13926
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
+
13927
14706
  // Standard Mode: direct active work with optional dynamic To Do tracking.
13928
14707
  if (state.mode === "standard") {
13929
14708
  setStandardRuntimeActive(ctx, true);
@@ -13980,7 +14759,13 @@ Public workflow commands:
13980
14759
 
13981
14760
  // Mission Mode: intercept user message as mission goal
13982
14761
  if (state.mode === "awaiting_mission_input") {
13983
- 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.";
13984
14769
  await startMissionFromGoal(ctx, goal);
13985
14770
  return { systemPrompt: event.systemPrompt };
13986
14771
  }
@@ -14010,13 +14795,17 @@ Public workflow commands:
14010
14795
 
14011
14796
  if (state.mode === "mission_plan_ready") {
14012
14797
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14013
- 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
+ }
14014
14802
  }
14015
14803
 
14016
14804
  if (state.mode === "mission_running") {
14017
14805
  if (state.lastWorkflowHandoff?.type === MISSION_MILESTONE_RESULT_TOOL) return;
14018
14806
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14019
14807
  if (!mission) return { systemPrompt: event.systemPrompt };
14808
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14020
14809
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRuntimePrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
14021
14810
  }
14022
14811
 
@@ -14024,18 +14813,21 @@ Public workflow commands:
14024
14813
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
14025
14814
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14026
14815
  if (!mission) return { systemPrompt: event.systemPrompt };
14816
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14027
14817
  return { systemPrompt: `${event.systemPrompt}\n\n${missionRepairPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
14028
14818
  }
14029
14819
 
14030
14820
  if (state.mode === "mission_validating" || state.mode === "mission_revalidating") {
14031
14821
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14032
14822
  if (!mission) return { systemPrompt: event.systemPrompt };
14823
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
14033
14824
  return { systemPrompt: `${event.systemPrompt}\n\n${missionValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
14034
14825
  }
14035
14826
 
14036
14827
  if (state.mode === "mission_final_validating") {
14037
14828
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14038
14829
  if (!mission) return { systemPrompt: event.systemPrompt };
14830
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
14039
14831
  return { systemPrompt: `${event.systemPrompt}\n\n${missionFinalValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
14040
14832
  }
14041
14833
 
@@ -14063,19 +14855,30 @@ Public workflow commands:
14063
14855
  }
14064
14856
 
14065
14857
  // Other planning modes: read-only
14066
- if (state.mode === "planning" || state.mode === "plan_draft" || state.mode === "plan_approved") {
14858
+ if (state.mode === "planning" || state.mode === "plan_draft") {
14067
14859
  return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN. Read-only tools only.` };
14068
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
+ }
14069
14868
  if (state.mode === "reviewing" || state.mode === "reviewed") {
14869
+ pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
14070
14870
  return { systemPrompt: `${event.systemPrompt}\n\n${reviewerPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
14071
14871
  }
14072
14872
  if (state.mode === "executing") {
14873
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14073
14874
  return { systemPrompt: `${event.systemPrompt}\n\n${executePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
14074
14875
  }
14075
14876
  if (state.mode === "repairing") {
14877
+ pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
14076
14878
  return { systemPrompt: `${event.systemPrompt}\n\n${workflowRepairPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
14077
14879
  }
14078
14880
  if (state.mode === "validating" || state.mode === "revalidating" || state.mode === "validated") {
14881
+ pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
14079
14882
  return { systemPrompt: `${event.systemPrompt}\n\n${validatePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Validation)}` };
14080
14883
  }
14081
14884
  });
@@ -14084,6 +14887,9 @@ Public workflow commands:
14084
14887
  if (!isAssistantMessage(event.message as AgentMessage)) return;
14085
14888
  const message = event.message as AssistantMessage;
14086
14889
  const text = assistantText(message);
14890
+ if (state.mode === "executing" || state.mode === "repairing") {
14891
+ applyPlanProgressMarkers(ctx, text);
14892
+ }
14087
14893
  if (text && workflowMermaidSegmentsHaveDiagram(text)) {
14088
14894
  void emitWorkflowMermaidDiagrams(pi, text);
14089
14895
  return { message: { ...message, content: message.content.map((block) => block.type === "text" ? { ...block, text: markdownWithoutWorkflowMermaidBlocks(block.text) } : block) } };
@@ -14207,10 +15013,10 @@ Public workflow commands:
14207
15013
  deferWorkflowAction(pi, "handle mission reviewer failure", () => handleMissionReviewFailure(ctx, mission, reviewVerdict, text));
14208
15014
  return;
14209
15015
  }
14210
- 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.` });
14211
15017
  activeMission = reviewed;
14212
15018
  updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
14213
- 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"));
14214
15020
  return;
14215
15021
  }
14216
15022
  }
@@ -14398,6 +15204,26 @@ Public workflow commands:
14398
15204
  if (state.mode === "mission_repairing") {
14399
15205
  const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
14400
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
+ }
14401
15227
  await completeMissionRepairAndStartRevalidation(ctx, mission, text);
14402
15228
  return;
14403
15229
  }
@@ -14577,13 +15403,30 @@ Public workflow commands:
14577
15403
  // ── Reviewer complete ──
14578
15404
  if (state.mode === "reviewing") {
14579
15405
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REVIEW_RESULT_TOOL) return;
15406
+ planForcedSubagentPreflightReconcile(ctx, "Review");
14580
15407
  if (blockIfForcedSubagentsMissing(ctx, "Review")) {
14581
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);
14582
15409
  return;
14583
15410
  }
14584
15411
  const reviewVerdict = extractReviewVerdict(text);
14585
15412
  if (reviewVerdict === "UNKNOWN") {
14586
- 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.";
14587
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);
14588
15431
  recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
14589
15432
  return;
@@ -14602,33 +15445,32 @@ Public workflow commands:
14602
15445
 
14603
15446
  // ── Execution complete ──
14604
15447
  if (state.mode === "executing") {
14605
- if (state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) return;
15448
+ if (workflowSubagentWorkerMode()) return;
14606
15449
  applyPlanProgressMarkers(ctx, text);
15450
+ if (state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) return;
14607
15451
  const allTrackedStepsCompleted = planProgressAllStepsCompleted(state);
15452
+ planForcedSubagentPreflightReconcile(ctx, "Execution");
14608
15453
  if (!allTrackedStepsCompleted && blockIfForcedSubagentsMissing(ctx, "Execution")) {
14609
15454
  const reason = "Execution stopped before all approved Plan steps were tracked and the forced execution worker requirement was not satisfied.";
14610
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);
14611
15456
  queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { validationText: reason, verdict: "UNKNOWN", reason });
14612
15457
  return;
14613
15458
  }
14614
- if (!allTrackedStepsCompleted && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true && workflowPlanProgressEnabled(settings) && state.planProgress?.steps.length) {
14615
- const reason = "Execution summary ended without workflow_progress markers or workflow_execution_result completedSteps for every approved Plan step.";
14616
- updateState({ mode: "validated", executionSummary: text, validationReport: reason, validationVerdict: "UNKNOWN", planExecutionStepIndex: undefined, planProgress: mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "submit corrected workflow_execution_result or rerun /plan continue" }) }, ctx);
14617
- queuePlanTerminalSummary(ctx, "blocked", "Plan execution progress missing", { validationText: reason, verdict: "UNKNOWN", reason });
14618
- queueAgentTurn(pi, `The Plan execution summary was not accepted because approved Plan step progress was incomplete. Do not redo work or edit files. Submit a corrected workflow_execution_result now with status, completedSteps for every approved Plan step completed, changedFiles, commands, blockers, and summary. Reason: ${reason}`, "workflow-execution-progress-correction");
14619
- return;
14620
- }
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;
14621
15463
  const stepValidationEnabled = settings.workflow.validateAfterEachStep === true;
14622
15464
  const stepApprovalEnabled = settings.workflow.requireApprovalPerStep === true;
14623
15465
  const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex ?? 0;
14624
15466
  const stepValidationIndex = stepValidationEnabled ? executedStepIndex : undefined;
14625
15467
  const validationGateActive = planValidationGateActive(settings);
14626
- updateState({ mode: "executed", executionSummary: text, planStepValidationIndex: stepValidationIndex, planExecutionStepIndex: undefined, 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);
14627
15469
  const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
14628
15470
  if (mission?.status === "running") {
14629
15471
  const milestone = mission.milestones[mission.currentMilestoneIndex];
14630
15472
  const validatingMission = saveActiveMission({ ...mission, status: "validating", currentStep: "validator", lastSummary: `Execution completed for mission milestone ${milestone?.id ?? "current"}.` });
14631
- 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);
14632
15474
  updateState({ mode: "mission_validating" }, ctx);
14633
15475
  deferWorkflowAction(pi, "begin mission validation after execution", () => beginMissionValidation(ctx, true));
14634
15476
  return;
@@ -14676,10 +15518,28 @@ Public workflow commands:
14676
15518
  if (state.mode === "repairing") {
14677
15519
  if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
14678
15520
  applyPlanProgressMarkers(ctx, text);
14679
- if (blockIfForcedSubagentsMissing(ctx, "Repair")) {
14680
- const required = workerTargetForPolicy("forced", workerCount(settings, "Repair"));
14681
- const observed = subagentUsageByPhase.Repair ?? 0;
14682
- 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.");
14683
15543
  return;
14684
15544
  }
14685
15545
  const repairSafetyBlock = workflowValidationFailureRequiresApproval(text, settings);
@@ -14699,13 +15559,16 @@ Public workflow commands:
14699
15559
  // ── Validation complete ──
14700
15560
  if (state.mode === "validating" || state.mode === "revalidating") {
14701
15561
  if (state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) return;
15562
+ planForcedSubagentPreflightReconcile(ctx, "Validation");
14702
15563
  if (blockIfForcedSubagentsMissing(ctx, "Validation")) {
14703
- 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" });
14704
15566
  return;
14705
15567
  }
14706
15568
  const verdict = normalizeValidationVerdict(extractVerdict(text), text);
14707
15569
  const validationStatus = planValidationStatusForVerdict(verdict);
14708
- 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);
14709
15572
  const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
14710
15573
  if (mission?.status === "running" || mission?.status === "validating") {
14711
15574
  const index = mission.currentMilestoneIndex;
@@ -14777,6 +15640,16 @@ Public workflow commands:
14777
15640
  return;
14778
15641
  }
14779
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
+ }
14780
15653
  deferWorkflowAction(pi, "handle validation failure", () => handleWorkflowValidationFailure(ctx, verdict, text));
14781
15654
  return;
14782
15655
  }