@mediadatafusion/pi-workflow-suite 0.0.14 → 0.0.16

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.
@@ -8,7 +8,7 @@ import { StringEnum } from "@earendil-works/pi-ai";
8
8
  import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
9
9
  import { CustomEditor, VERSION, compact as piCompact, estimateTokens as piEstimateTokens, findCutPoint as piFindCutPoint, getAgentDir, getMarkdownTheme, type ExtensionAPI, type ExtensionContext, type FileOperations, type SessionEntry, type ToolDefinition } from "@earendil-works/pi-coding-agent";
10
10
  import { Type } from "typebox";
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";
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_CUSTOM_PRESET_MARKER, 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
13
  import { BASE_EXECUTE_TOOLS, EXECUTE_TOOLS, PLAN_TOOLS, REVIEW_TOOLS, WORKFLOW_DIAGRAM_TOOL, WORKFLOW_PLAN_RESULT_TOOL, WORKFLOW_REVIEW_RESULT_TOOL, WORKFLOW_EXECUTION_RESULT_TOOL, WORKFLOW_VALIDATION_RESULT_TOOL, WORKFLOW_REPAIR_RESULT_TOOL, WORKFLOW_PROGRESS_TOOL, MISSION_PLAN_RESULT_TOOL, MISSION_MILESTONE_RESULT_TOOL, STANDARD_HANDOFF_RESULT_TOOL, isBlockedExecuteCommand, registerToolGuard, standardSafeReadOnlyBash, VALIDATOR_TOOLS } from "./workflow-tool-guard.js";
14
14
  import { refreshRuntimeWebTools, registerWorkflowWebTools, runtimeWebResearchGuidance, webSafePlanTools, withRuntimeWebTools } from "./workflow-web-tools.js";
@@ -1750,7 +1750,7 @@ ${requiredSubagentPreflightSection(options.preflightBlock)}
1750
1750
 
1751
1751
  ${subagentCapabilityTable()}
1752
1752
 
1753
- ${priorPlan ? `Current draft/approved plan to revise:\n${priorPlan}\n\n` : ""}${feedbackBlock}${options.forceClarification ? `CLARIFICATION IS REQUIRED BEFORE FINAL PLANNING. Reason: ${options.forceReason ?? "always_for_nontrivial classified this as non-trivial"}\nYour job in this turn is to perform lightweight task analysis, then generate only high-value clarification questions from the actual task. Do not produce an implementation plan in this turn. ${subagentsBeforeClarification ? "If planning depth/policy calls for it and sub-agent use is available, use read-only planning/research sub-agents before asking clarification so the questions are context-aware." : "Do not call sub-agents in this clarification-generation turn unless forced by sub-agent policy."} Do not use generic reusable workflow boilerplate questions.\n\n` : ""}${options.qualityGateFeedback ? `The previous clarification output failed the clarification quality gate: ${options.qualityGateFeedback}\nRegenerate better task-specific clarification questions${options.forceClarification ? ". Do not produce a final plan in this retry." : ", or use PLAN_DECISION: plan if no high-value question exists."}\n\n` : ""}MANDATORY STRUCTURED HANDOFF: Before your final response, call workflow_plan_result with decision=clarify, plan, or blocked. The tool payload is the primary control plane; visible markdown is fallback display only. If workflow_plan_result is unavailable, output the parser-safe PLAN_DECISION fallback once and STOP immediately; do not call subagent as an acknowledgement, do not retry invalid tool names, and do not continue analysis after the fallback.
1753
+ ${priorPlan ? `Current draft/approved plan to revise:\n${priorPlan}\n\n` : ""}${feedbackBlock}${options.forceClarification ? `CLARIFICATION IS REQUIRED BEFORE FINAL PLANNING. Reason: ${options.forceReason ?? "always_for_nontrivial classified this as non-trivial"}\nYour job in this turn is to perform lightweight task analysis, then generate only high-value clarification questions from the actual task. Do not produce an implementation plan in this turn. ${subagentsBeforeClarification ? "If planning depth/policy calls for it and sub-agent use is available, use read-only planning/research sub-agents before asking clarification so the questions are context-aware." : "Do not call sub-agents in this clarification-generation turn unless forced by sub-agent policy."} Do not use generic reusable workflow boilerplate questions.\n\n` : ""}${options.qualityGateFeedback ? `The previous clarification output failed the clarification quality gate: ${options.qualityGateFeedback}\nRegenerate better task-specific clarification questions${options.forceClarification ? ". Do not produce a final plan in this retry." : ", or use PLAN_DECISION: plan if no high-value question exists."}\n\n` : ""}MANDATORY STRUCTURED HANDOFF: Before your final response, call workflow_plan_result with decision=clarify, plan, or blocked. The tool payload is the primary control plane. After workflow_plan_result returns, print the user-facing markdown for that decision and stop: the approval-ready # Implementation Plan for decision=plan, the clarification questions for decision=clarify, or the blocker summary for decision=blocked. If workflow_plan_result is unavailable, output the parser-safe PLAN_DECISION fallback once and STOP immediately; do not call subagent as an acknowledgement, do not retry invalid tool names, and do not continue analysis after the fallback.
1754
1754
 
1755
1755
  LEGACY FALLBACK: Your VERY FIRST LINE must be exactly one of:
1756
1756
  ${options.forceClarification ? "PLAN_DECISION: clarify" : "PLAN_DECISION: clarify\nPLAN_DECISION: plan"}
@@ -1918,7 +1918,7 @@ function artifactSafetyPolicyBlock(audience: ArtifactSafetyAudience = "general")
1918
1918
  : audience === "validation"
1919
1919
  ? `Validation requirement: remain read-only. Prefer text evidence over temporary files; if evidence files are unavoidable, do not write them at repository root and never repair or relocate files during validation.`
1920
1920
  : audience === "repair"
1921
- ? `Repair requirement: repair may move only current-task-created misplaced recoverable files when safe and in scope. Disclose moved, preserved, deleted, root, and possibly user-owned files in workflow_repair_result; deletion requires explicit safety evidence or approval.`
1921
+ ? `Repair requirement: keep repairing concrete in-scope failures until revalidation is ready. Repair may move only current-task-created misplaced recoverable files when safe and in scope. Disclose moved, preserved, deleted, root, and possibly user-owned files in workflow_repair_result; set needsUserApproval only when a concrete hard-safety action or artifact disposition must stop automatic revalidation. Advisory follow-up, credential rotation recommendations, preserved ambiguous files, manual QA still needed, and pre-existing debt belong in summary/safety notes, not needsUserApproval. Deletion requires explicit safety evidence or approval.`
1922
1922
  : audience === "subagent"
1923
1923
  ? `Sub-agent requirement: support workers default to read-only inspection. They must not create, delete, move, or clean files unless explicitly authorized with path-approved scope by the parent workflow.`
1924
1924
  : `General requirement: keep artifact placement explicit, preserve recoverable content, and report uncertain cleanup for user approval.`;
@@ -1927,7 +1927,7 @@ function artifactSafetyPolicyBlock(audience: ArtifactSafetyAudience = "general")
1927
1927
 
1928
1928
  function repairArtifactDispositionOutput(): string {
1929
1929
  return `## Artifact Disposition
1930
- List movedFiles, preservedFiles, deletedFiles, rootArtifacts, possiblyUserOwnedFiles, and needsUserApproval. State "none" for empty categories. Deletion is acceptable only when explicitly approved or clearly current-task-generated, unrecoverable, and non-user-owned; otherwise preserve or request approval.`;
1930
+ List movedFiles, preservedFiles, deletedFiles, rootArtifacts, possiblyUserOwnedFiles, and needsUserApproval. State "none" for empty categories. Set needsUserApproval only for a concrete disposition/action that should block automatic revalidation. Do not set it for advisory-only follow-up, credential rotation recommendations, preserved ambiguous files, manual QA still needed, or pre-existing debt. Deletion is acceptable only when explicitly approved or clearly current-task-generated, unrecoverable, and non-user-owned; otherwise preserve or request approval.`;
1931
1931
  }
1932
1932
 
1933
1933
  function phasePromptPolicyBlock(settings: ReturnType<typeof loadWorkflowSettings>, phase: SubagentPhase, label: string = phase, preflightBlock?: string): string {
@@ -2286,7 +2286,7 @@ ${professionalOutputGuidance("Plan repair")}
2286
2286
  Diagram guidance:
2287
2287
  ${workflowMermaidGuidance()}
2288
2288
 
2289
- 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.
2289
+ Approved plan remains the execution contract. Repair concrete validator-identified failures below and keep going while fixes are in-scope and non-destructive. 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. Do not mark advisory-only follow-up as needsUserApproval; preserve it in the summary and allow revalidation.
2290
2290
 
2291
2291
  ${artifactSafetyPolicyBlock("repair")}
2292
2292
 
@@ -3020,7 +3020,7 @@ function workflowActivePhaseDisplay(value: string | undefined): WorkflowActivePh
3020
3020
  }
3021
3021
 
3022
3022
  function standardActivePresentation(state: WorkflowState): boolean {
3023
- return state.standardRuntime?.active === true || state.standardTodo?.status === "active" || Boolean(state.mode === "standard" && state.standardActivePhase);
3023
+ return state.standardRuntime?.active === true;
3024
3024
  }
3025
3025
 
3026
3026
  function standardDisplayPhase(state: WorkflowState): string {
@@ -3209,6 +3209,16 @@ function compactionTriggerOverrideLabel(settings: ReturnType<typeof loadWorkflow
3209
3209
  return typeof value === "number" ? `${value}%` : "none";
3210
3210
  }
3211
3211
 
3212
+ function resetCompactionContextToPiDefault(context: ReturnType<typeof loadWorkflowSettings>["context"]): void {
3213
+ context.compactionMode = "pi_default";
3214
+ context.customCompactionEnabled = false;
3215
+ context.compactionAgent = "";
3216
+ context.customCompactionReserveTokens = DEFAULT_PI_COMPACTION_RESERVE_TOKENS;
3217
+ context.customCompactionKeepRecentTokens = DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS;
3218
+ context.workflowCompactionCheckMode = "boundary";
3219
+ delete context.compactionTriggerPercent;
3220
+ }
3221
+
3212
3222
  function effectiveWorkflowCompactionTriggerPercent(settings: ReturnType<typeof loadWorkflowSettings>, ctx: ExtensionContext, usage: { contextWindow?: number }): number {
3213
3223
  const configured = compactionTriggerPercent(settings);
3214
3224
  if (!customModelCompactionConfigured(settings)) return configured;
@@ -4268,7 +4278,7 @@ function standardTodoWidget(state: WorkflowState, settings: ReturnType<typeof lo
4268
4278
  const done = standardTodoDoneCount(todo);
4269
4279
  const current = standardTodoCurrentIndex(todo);
4270
4280
  const next = todo.items.findIndex((item, index) => index > current && item.status === "pending");
4271
- const status = todo.status === "active" ? "executing" : todo.status;
4281
+ const status = todo.status === "active" ? standardDisplayPhase(state) : todo.status;
4272
4282
  return [
4273
4283
  ...progressHeaderLines("STANDARD", workflowHeaderState(status), standardTodoProgressBar(todo, settings), standardTodoRuntimeSummaryLine(state), `To Do: ${done} of ${todo.items.length}`),
4274
4284
  `Current: ${standardTodoItemLabel(todo, current, "Standard assistance")}`,
@@ -6533,25 +6543,32 @@ function subagentActivityLines(ctx: ExtensionContext, activeSubagents: ActiveSub
6533
6543
  return [title, ...fgLines, ...bgLines];
6534
6544
  }
6535
6545
 
6546
+ function composeBottomProgressWidgetLines(leftLines: string[], rightLines: string[] = [], width: number): string[] {
6547
+ const safeWidth = Math.max(0, width);
6548
+ if (!rightLines.length) return leftLines.map((line) => truncateVisibleText(line, safeWidth));
6549
+ const reservedRightWidth = Math.min(safeWidth, Math.max(28, Math.min(36, Math.max(...rightLines.map(visibleTextWidth)))));
6550
+ const gapWidth = safeWidth > reservedRightWidth ? 1 : 0;
6551
+ const leftWidth = Math.max(0, safeWidth - reservedRightWidth - gapWidth);
6552
+ const rowCount = Math.max(leftLines.length, rightLines.length);
6553
+ return Array.from({ length: rowCount }, (_, index) => {
6554
+ const left = leftLines[index];
6555
+ const right = rightLines[index];
6556
+ const clippedLeft = left ? truncateVisibleText(left, leftWidth) : "";
6557
+ const clippedRight = right ? truncateVisibleText(right, reservedRightWidth) : "";
6558
+ if (!right) return truncateVisibleText(clippedLeft, safeWidth);
6559
+ if (!clippedLeft) {
6560
+ const rightOnlyGap = Math.max(0, safeWidth - visibleTextWidth(clippedRight));
6561
+ return truncateVisibleText(`${" ".repeat(rightOnlyGap)}${clippedRight}`, safeWidth);
6562
+ }
6563
+ const desiredGap = Math.max(gapWidth, safeWidth - visibleTextWidth(clippedLeft) - visibleTextWidth(clippedRight));
6564
+ return truncateVisibleText(`${clippedLeft}${" ".repeat(desiredGap)}${clippedRight}`, safeWidth);
6565
+ });
6566
+ }
6567
+
6536
6568
  function bottomProgressWidget(leftLines: string[], rightLines: string[] = []): (tui: unknown, theme: unknown) => { render(width: number): string[]; invalidate(): void } {
6537
6569
  return () => ({
6538
6570
  render(width: number) {
6539
- const safeWidth = Math.max(0, width);
6540
- if (!rightLines.length) return leftLines.map((line) => truncateVisibleText(line, safeWidth));
6541
- const reservedRightWidth = Math.min(safeWidth, Math.max(28, Math.min(36, Math.max(...rightLines.map(visibleTextWidth)))));
6542
- const gapWidth = safeWidth > reservedRightWidth ? 1 : 0;
6543
- const leftWidth = Math.max(0, safeWidth - reservedRightWidth - gapWidth);
6544
- const rowCount = Math.max(leftLines.length, rightLines.length);
6545
- return Array.from({ length: rowCount }, (_, index) => {
6546
- const left = leftLines[index];
6547
- const right = rightLines[index];
6548
- const clippedLeft = left ? truncateVisibleText(left, leftWidth) : "";
6549
- const clippedRight = right ? truncateVisibleText(right, reservedRightWidth) : "";
6550
- if (!right) return truncateVisibleText(clippedLeft, safeWidth);
6551
- if (!clippedLeft) return truncateVisibleText(clippedRight, safeWidth);
6552
- const desiredGap = Math.max(gapWidth, safeWidth - visibleTextWidth(clippedLeft) - visibleTextWidth(clippedRight));
6553
- return truncateVisibleText(`${clippedLeft}${" ".repeat(desiredGap)}${clippedRight}`, safeWidth);
6554
- });
6571
+ return composeBottomProgressWidgetLines(leftLines, rightLines, width);
6555
6572
  },
6556
6573
  invalidate() {},
6557
6574
  });
@@ -6885,7 +6902,6 @@ function workflowArtifactOverlaps(possiblyUserOwnedPath: string, mutatedPath: st
6885
6902
  }
6886
6903
 
6887
6904
  function repairArtifactSafetyBlock(params: Record<string, unknown>): string | undefined {
6888
- if (params.needsUserApproval === true) return "Typed repair result requires user approval for artifact disposition.";
6889
6905
  const deletedFiles = workflowStringArray(params.deletedFiles);
6890
6906
  if (deletedFiles.length > 0) return "Typed repair result reported deleted files; user review is required before revalidation.";
6891
6907
  const possiblyUserOwned = workflowStringArray(params.possiblyUserOwnedFiles);
@@ -6898,6 +6914,12 @@ function repairArtifactSafetyBlock(params: Record<string, unknown>): string | un
6898
6914
  const mutatedArtifact = mutatedArtifacts.find((artifact) => possiblyUserOwned.some((path) => workflowArtifactOverlaps(path, artifact)));
6899
6915
  if (mutatedArtifact) return `Typed repair result changed or moved possibly user-owned file: ${mutatedArtifact}`;
6900
6916
  }
6917
+ if (params.needsUserApproval === true) {
6918
+ const movedFiles = workflowStringArray(params.movedFiles);
6919
+ if (movedFiles.length > 0) return "Typed repair result requires user approval for moved file disposition.";
6920
+ const rootArtifacts = workflowStringArray(params.rootArtifacts);
6921
+ if (rootArtifacts.length > 0) return "Typed repair result requires user approval for root artifact disposition.";
6922
+ }
6901
6923
  return undefined;
6902
6924
  }
6903
6925
 
@@ -8040,7 +8062,74 @@ function missionRepairPrompt(mission: MissionState, settings: ReturnType<typeof
8040
8062
  const last = mission.checkpoints[mission.checkpoints.length - 1];
8041
8063
  const repairPrompt = readPromptFile("mission-repair.md", "Repair only the current mission milestone validation failure safely, then summarize for revalidation.");
8042
8064
  const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Repair", "Mission Repair", preflightBlock);
8043
- 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\n${artifactSafetyPolicyBlock("repair")}\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${repairArtifactDispositionOutput()}\n## Remaining Risks\n## Revalidation Readiness\n## Next Action`;
8065
+ return `${repairPrompt}
8066
+
8067
+ ${professionalOutputGuidance("Mission repair")}
8068
+
8069
+ Mission ID: ${mission.id}
8070
+ Mission goal:
8071
+ ${mission.goal}
8072
+
8073
+ Autonomy: ${mission.autonomy}
8074
+ Current milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}
8075
+ Approved milestone scope:
8076
+ Objective: ${milestone?.objective ?? "none"}
8077
+ Steps:
8078
+ ${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}
8079
+ Validation requirements:
8080
+ ${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}
8081
+ Risks:
8082
+ ${(milestone?.risks ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}
8083
+
8084
+ Validation failure details:
8085
+ ${mission.lastValidationFailure || mission.lastBlockReason || "none recorded"}
8086
+
8087
+ Retry count: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone
8088
+ Mission retry count: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total
8089
+ Last repair attempt: ${mission.lastRepairAttempt || "none"}
8090
+ Last checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}
8091
+
8092
+ Safety rules:
8093
+ - Only fix concrete validator-identified issues directly related to the failed milestone validation.
8094
+ - Do not re-grade validation or claim PASS; only Mission validation can pass repaired work.
8095
+ - 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.
8096
+ - Do not expand scope beyond the current approved milestone.
8097
+ - Do not perform destructive actions.
8098
+ - Do not edit secrets, auth/session/log/runtime-state files, .env, .factory, or .cursor.
8099
+ - Do not deploy. Do not push. Do not mutate databases.
8100
+ - Stop and report if repair requires destructive, out-of-scope, secret, database, deployment, or other risky action.
8101
+ - Keep repairing concrete in-scope failures while fixes are non-destructive.
8102
+ - Do not mark advisory-only follow-up as needsUserApproval; preserve credential rotation recommendations, preserved ambiguous files, manual QA still needed, and pre-existing debt in the summary/safety notes and allow revalidation.
8103
+
8104
+ ${artifactSafetyPolicyBlock("repair")}
8105
+
8106
+ Settings:
8107
+ - validationRetryMode: ${settings.missions.validationRetryMode ?? "safe_only"}
8108
+ - requireApprovalForOutOfScopeRepair: ${settings.missions.requireApprovalForOutOfScopeRepair !== false}
8109
+ - requireApprovalForDestructiveRepair: ${settings.missions.requireApprovalForDestructiveRepair !== false}
8110
+ - autoRepairValidationFailures: ${settings.missions.autoRepairValidationFailures !== false}
8111
+ - pauseAfterValidationFailure: ${settings.missions.pauseAfterValidationFailure === true}
8112
+
8113
+ ${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}
8114
+
8115
+ ${subagentCapabilityTable()}
8116
+
8117
+ Diagram guidance:
8118
+ ${workflowMermaidGuidance()}
8119
+
8120
+ Revalidation requirement:
8121
+ - Summarize whether the current milestone is ready for validator revalidation.
8122
+ - Do not mark the milestone complete yourself; Mission Mode will re-run validation according to repair/retry settings.
8123
+
8124
+ Output:
8125
+ # Mission Repair Summary
8126
+ ## Repair Scope
8127
+ ## Work Completed
8128
+ ## Files Changed
8129
+ ${repairArtifactDispositionOutput()}
8130
+ ## Remaining Risks
8131
+ ## Revalidation Readiness
8132
+ ## Next Action`;
8044
8133
  }
8045
8134
 
8046
8135
  function parseMissionMilestones(text: string): MissionMilestone[] {
@@ -8570,7 +8659,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
8570
8659
  const reason = workflowTypedSummary(params, "Structured Standard handoff recorded.");
8571
8660
  const patch: Partial<WorkflowState> = { standardLastAutoCheckAt: new Date().toISOString(), standardLastClarificationDecision: clarificationDecision ? standardClarificationDisplay(clarificationDecision) : undefined, standardLastClarificationReason: reason, standardLastTodoDecision: todoDecision ? standardTodoDisplay(todoDecision) : undefined, standardLastTodoReason: reason };
8572
8661
  const todoItems = workflowStringArray(params.todoItems);
8573
- if (todoItems.length && (todoDecision === "create" || todoDecision === "required")) patch.standardTodo = createStandardTodoFromTitles(state.task ?? state.originalTask ?? "Standard Mode task", todoItems) ?? state.standardTodo;
8662
+ if (todoItems.length && (todoDecision === "create" || todoDecision === "required") && !state.standardTodo?.items.length) patch.standardTodo = createStandardTodoFromTitles(state.task ?? state.originalTask ?? "Standard Mode task", todoItems) ?? state.standardTodo;
8574
8663
  if (clarificationDecision === "ask") {
8575
8664
  const questions = normalizeTypedClarificationQuestions(params.questions, Math.max(1, Math.min(2, settings.standard.maxClarificationQuestions ?? 1)));
8576
8665
  Object.assign(patch, { standardClarificationPending: true, standardClarificationStage: "awaiting_answer", standardClarificationRequirementReason: reason, standardClarifyingQuestions: questions.length ? questions : undefined });
@@ -8883,6 +8972,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
8883
8972
  return;
8884
8973
  }
8885
8974
  if (state.mode === "standard") {
8975
+ finalizeStandardTerminalStateIfIdle(ctx, `user wait: ${reason}`);
8886
8976
  setStandardRuntimeActive(ctx, false);
8887
8977
  traceWorkflowTracking(ctx, "standard-runtime-user-wait", { reason });
8888
8978
  return;
@@ -8906,6 +8996,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
8906
8996
  traceWorkflowTracking(ctx, "standard-runtime-pause-deferred", { reason });
8907
8997
  return;
8908
8998
  }
8999
+ finalizeStandardTerminalStateIfIdle(ctx, `idle pause: ${reason}`);
8909
9000
  setStandardRuntimeActive(ctx, false);
8910
9001
  };
8911
9002
  const scheduleStandardRuntimeIdleCheck = (ctx: ExtensionContext, reason: string): void => {
@@ -8913,6 +9004,46 @@ export default function workflowModes(pi: ExtensionAPI): void {
8913
9004
  if (state.mode === "standard") pauseStandardRuntimeIfIdle(ctx, reason);
8914
9005
  }, 0);
8915
9006
  };
9007
+ const completeStandardTodoForTerminalHandoff = (todo: StandardTodoState | undefined): StandardTodoState | undefined => {
9008
+ if (!todo?.items.length || todo.status === "completed" || todo.status === "blocked") return todo;
9009
+ const hasBlocked = todo.items.some((item) => item.status === "blocked");
9010
+ const items = todo.items.map((item) => item.status === "blocked" ? item : item.status === "skipped" ? item : { ...item, status: "completed" as const });
9011
+ return {
9012
+ ...todo,
9013
+ updatedAt: new Date().toISOString(),
9014
+ status: hasBlocked ? "blocked" : "completed",
9015
+ currentItemIndex: Math.max(0, items.length - 1),
9016
+ items,
9017
+ };
9018
+ };
9019
+ const standardTerminalStatePatch = (completeTodo: boolean): Partial<WorkflowState> => {
9020
+ const terminalTodo = completeTodo ? completeStandardTodoForTerminalHandoff(state.standardTodo) : state.standardTodo;
9021
+ return {
9022
+ lastWorkflowHandoff: undefined,
9023
+ standardActivePhase: undefined,
9024
+ standardWorkKind: undefined,
9025
+ standardClarificationPending: false,
9026
+ standardClarificationStage: state.standardClarificationStage === "drafting" || state.standardClarificationStage === "awaiting_answer" ? undefined : state.standardClarificationStage,
9027
+ standardTodo: terminalTodo,
9028
+ standardRuntime: state.standardRuntime ? { ...state.standardRuntime, active: false, runtimeCounter: "paused" } : state.standardRuntime,
9029
+ };
9030
+ };
9031
+ const finalizeStandardTerminalStateIfIdle = (ctx: ExtensionContext, reason: string): boolean => {
9032
+ if (state.mode !== "standard") return false;
9033
+ if (workflowHasActiveOrPendingWork()) {
9034
+ traceWorkflowTracking(ctx, "standard-terminal-finalize-deferred", { reason });
9035
+ return false;
9036
+ }
9037
+ const hasAcceptedFinalHandoff = state.lastWorkflowHandoff?.type === STANDARD_HANDOFF_RESULT_TOOL
9038
+ && state.standardClarificationStage !== "awaiting_answer";
9039
+ const hasStalePausedPresentation = state.standardRuntime?.active === false
9040
+ && (Boolean(state.standardActivePhase) || state.standardTodo?.status === "active");
9041
+ if (hasStalePausedPresentation && (state.standardClarificationPending || state.standardClarificationStage === "awaiting_answer" || state.standardClarificationStage === "drafting")) return false;
9042
+ if (!hasAcceptedFinalHandoff && !hasStalePausedPresentation) return false;
9043
+ updateState(standardTerminalStatePatch(hasAcceptedFinalHandoff), ctx);
9044
+ traceWorkflowTracking(ctx, "standard-terminal-finalized", { reason, fromHandoff: hasAcceptedFinalHandoff, stalePresentation: hasStalePausedPresentation });
9045
+ return true;
9046
+ };
8916
9047
  const stopSubagentActivityTicker = (): void => {
8917
9048
  if (workflowSubagentActivityTimer) clearInterval(workflowSubagentActivityTimer);
8918
9049
  workflowSubagentActivityTimer = undefined;
@@ -9048,20 +9179,42 @@ export default function workflowModes(pi: ExtensionAPI): void {
9048
9179
  return eventText ? transientFailureReason(fallback, eventText) : fallback;
9049
9180
  };
9050
9181
 
9182
+ const workflowContextRecoveryCanSend = (ctx: ExtensionContext): boolean => {
9183
+ if (workflowActiveToolExecutions > 0 || workflowAutoCompactionRunning) return false;
9184
+ try { return ctx.isIdle(); } catch { return true; }
9185
+ };
9186
+
9187
+ const queueContextInterruptedWorkflowPrompt = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, content: string, recover: (reason: string) => void): boolean => {
9188
+ if (workflowScheduledAgentTurns > 0) {
9189
+ recordWorkflowInternalEvent(ctx, `Context-window workflow recovery already has a queued workflow turn for ${phase}; preserving active state.`);
9190
+ return true;
9191
+ }
9192
+ if (workflowDeferredAutoCompaction) {
9193
+ clearDeferredWorkflowCompaction();
9194
+ rememberWorkflowCompactionCheck(`deferred proactive compaction cleared before context-window ${phase} recovery`);
9195
+ }
9196
+ recordWorkflowInternalEvent(ctx, `Context-window workflow interruption queued ${phase} recovery turn.`);
9197
+ queueWorkflowPrompt(pi, content, {
9198
+ ...buildQueuedPhaseRecovery(ctx, phase, recover),
9199
+ initialDelayMs: workflowAutoCompactionRunning ? 3000 : 2000,
9200
+ isIdle: () => workflowContextRecoveryCanSend(ctx),
9201
+ });
9202
+ return true;
9203
+ };
9204
+
9051
9205
  const recoverContextInterruptedWorkflowTurn = (event: unknown, ctx: ExtensionContext): boolean => {
9052
- if (workflowActiveToolExecutions > 0) return false;
9053
9206
  if (!planRuntimeTurnIsActive() && !missionRuntimeTurnIsActive()) return false;
9054
9207
  if (!workflowContextInterruptionEvidence(event, ctx)) return false;
9055
9208
  const settings = loadWorkflowSettings(ctx.cwd);
9056
9209
  state = persistWorkflowState(state, ctx);
9057
- if (state.mode === "executing") { queueWorkflowPrompt(pi, executePrompt(state, settings, phasePreflightBlocks.Execution), buildQueuedPhaseRecovery(ctx, "Execution", (reason) => recoverPlanTransientHandoffFailure(ctx, "execution", reason, { phase: "Execution" }))); return true; }
9058
- if (state.mode === "validating" || state.mode === "revalidating") { queueWorkflowPrompt(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation), buildQueuedPhaseRecovery(ctx, "Validation", (reason) => recoverPlanTransientHandoffFailure(ctx, state.mode === "revalidating" ? "revalidation" : "validation", reason, { phase: "Validation" }))); return true; }
9059
- if (state.mode === "repairing") { queueWorkflowPrompt(pi, workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair), buildQueuedPhaseRecovery(ctx, "Repair", (reason) => recoverPlanTransientHandoffFailure(ctx, "repair", reason, { phase: "Repair", preserveRetry: true }))); return true; }
9210
+ if (state.mode === "executing") return queueContextInterruptedWorkflowPrompt(ctx, "Execution", executePrompt(state, settings, phasePreflightBlocks.Execution), (reason) => recoverPlanTransientHandoffFailure(ctx, "execution", reason, { phase: "Execution" }));
9211
+ if (state.mode === "validating" || state.mode === "revalidating") return queueContextInterruptedWorkflowPrompt(ctx, "Validation", validatePrompt(state, settings, phasePreflightBlocks.Validation), (reason) => recoverPlanTransientHandoffFailure(ctx, state.mode === "revalidating" ? "revalidation" : "validation", reason, { phase: "Validation" }));
9212
+ if (state.mode === "repairing") return queueContextInterruptedWorkflowPrompt(ctx, "Repair", workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair), (reason) => recoverPlanTransientHandoffFailure(ctx, "repair", reason, { phase: "Repair", preserveRetry: true }));
9060
9213
  const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
9061
- if (state.mode === "mission_running" && mission) { queueWorkflowPrompt(pi, missionRuntimePrompt(mission, settings, phasePreflightBlocks.Execution), buildQueuedPhaseRecovery(ctx, "Execution", (reason) => recoverMissionTransientHandoffFailure(ctx, "run", reason, { phase: "Execution" }))); return true; }
9062
- if ((state.mode === "mission_validating" || state.mode === "mission_revalidating") && mission) { queueWorkflowPrompt(pi, missionValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation), buildQueuedPhaseRecovery(ctx, "Validation", (reason) => recoverMissionTransientHandoffFailure(ctx, state.mode === "mission_revalidating" ? "revalidation" : "validation", reason, { phase: "Validation" }))); return true; }
9063
- if (state.mode === "mission_repairing" && mission) { queueWorkflowPrompt(pi, missionRepairPrompt(mission, settings, phasePreflightBlocks.Repair), buildQueuedPhaseRecovery(ctx, "Repair", (reason) => recoverMissionTransientHandoffFailure(ctx, "repair", reason, { phase: "Repair", preserveRetry: true }))); return true; }
9064
- if (state.mode === "mission_final_validating" && mission) { queueWorkflowPrompt(pi, missionFinalValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation), buildQueuedPhaseRecovery(ctx, "Validation", (reason) => recoverMissionTransientHandoffFailure(ctx, "final_validation", reason, { phase: "Validation" }))); return true; }
9214
+ if (state.mode === "mission_running" && mission) return queueContextInterruptedWorkflowPrompt(ctx, "Execution", missionRuntimePrompt(mission, settings, phasePreflightBlocks.Execution), (reason) => recoverMissionTransientHandoffFailure(ctx, "run", reason, { phase: "Execution" }));
9215
+ if ((state.mode === "mission_validating" || state.mode === "mission_revalidating") && mission) return queueContextInterruptedWorkflowPrompt(ctx, "Validation", missionValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation), (reason) => recoverMissionTransientHandoffFailure(ctx, state.mode === "mission_revalidating" ? "revalidation" : "validation", reason, { phase: "Validation" }));
9216
+ if (state.mode === "mission_repairing" && mission) return queueContextInterruptedWorkflowPrompt(ctx, "Repair", missionRepairPrompt(mission, settings, phasePreflightBlocks.Repair), (reason) => recoverMissionTransientHandoffFailure(ctx, "repair", reason, { phase: "Repair", preserveRetry: true }));
9217
+ if (state.mode === "mission_final_validating" && mission) return queueContextInterruptedWorkflowPrompt(ctx, "Validation", missionFinalValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation), (reason) => recoverMissionTransientHandoffFailure(ctx, "final_validation", reason, { phase: "Validation" }));
9065
9218
  return false;
9066
9219
  };
9067
9220
 
@@ -12630,6 +12783,56 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12630
12783
  return !workflowHasActiveOrPendingValidationWork(ctx);
12631
12784
  }
12632
12785
 
12786
+ function planActiveRuntimePhase(mode = state.mode): WorkflowPendingToolPhase | undefined {
12787
+ if (mode === "reviewing") return "Review";
12788
+ if (mode === "executing") return "Execution";
12789
+ if (mode === "validating" || mode === "revalidating") return "Validation";
12790
+ if (mode === "repairing") return "Repair";
12791
+ return undefined;
12792
+ }
12793
+
12794
+ function requiredPlanRuntimePhaseTools(phase: WorkflowPendingToolPhase, settings: ReturnType<typeof loadWorkflowSettings>): string[] {
12795
+ if (phase === "Execution") return requiredPlanExecutionTools(settings);
12796
+ if (phase === "Review") return [WORKFLOW_REVIEW_RESULT_TOOL];
12797
+ if (phase === "Validation") return [WORKFLOW_VALIDATION_RESULT_TOOL];
12798
+ if (phase === "Repair") return ["edit", "write", "bash", WORKFLOW_PROGRESS_TOOL, WORKFLOW_REPAIR_RESULT_TOOL];
12799
+ return [WORKFLOW_PLAN_RESULT_TOOL];
12800
+ }
12801
+
12802
+ function missingPlanRuntimePhaseTools(phase: WorkflowPendingToolPhase, settings: ReturnType<typeof loadWorkflowSettings>): string[] {
12803
+ const active = new Set(pi.getActiveTools());
12804
+ return requiredPlanRuntimePhaseTools(phase, settings).filter((tool) => !active.has(tool));
12805
+ }
12806
+
12807
+ function workflowRuntimeIsIdle(ctx: ExtensionContext): boolean {
12808
+ try { return ctx.isIdle(); } catch { return false; }
12809
+ }
12810
+
12811
+ function planActiveRuntimeHasPendingWork(ctx: ExtensionContext): boolean {
12812
+ return workflowActiveToolExecutions > 0
12813
+ || workflowScheduledAgentTurns > 0
12814
+ || runningSubagentActivity()
12815
+ || Boolean(pendingWorkflowToolPhase)
12816
+ || workflowHasQueuedMessages(ctx)
12817
+ || !workflowRuntimeIsIdle(ctx);
12818
+ }
12819
+
12820
+ function planActiveRuntimeIsRecoverablyStale(ctx: ExtensionContext): boolean {
12821
+ const phase = planActiveRuntimePhase();
12822
+ if (!phase) return false;
12823
+ if (state.mode === "validating" || state.mode === "revalidating") return planValidationRuntimeIsStale(ctx);
12824
+ if (planActiveRuntimeHasPendingWork(ctx)) return false;
12825
+ return true;
12826
+ }
12827
+
12828
+ function planActiveRuntimeStaleReason(ctx: ExtensionContext): string {
12829
+ const settings = loadWorkflowSettings(ctx.cwd);
12830
+ const phase = planActiveRuntimePhase();
12831
+ const missing = phase ? missingPlanRuntimePhaseTools(phase, settings) : [];
12832
+ const missingText = missing.length ? ` Missing active ${phase} tools: ${missing.join(", ")}.` : "";
12833
+ return `Persisted Plan mode is ${state.mode}, but no active or queued workflow work is present.${missingText}`;
12834
+ }
12835
+
12633
12836
  async function resumeInterruptedPlanValidation(ctx: ExtensionContext, title: string): Promise<boolean> {
12634
12837
  const settings = loadWorkflowSettings(ctx.cwd);
12635
12838
  const revalidate = state.mode === "revalidating";
@@ -12646,6 +12849,46 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12646
12849
  return true;
12647
12850
  }
12648
12851
 
12852
+ async function resumeInterruptedPlanExecution(ctx: ExtensionContext, title: string): Promise<boolean> {
12853
+ const reason = planActiveRuntimeStaleReason(ctx);
12854
+ recordWorkflowInternalEvent(ctx, `Recovered stale Plan execution runtime. ${reason}`);
12855
+ show(pi, `# ${title}\n\nRecovered interrupted Plan execution runtime. Re-arming executor for the current active step.\n\n${workflowDisplayText(reason)}`);
12856
+ await beginExecution(ctx, true);
12857
+ return true;
12858
+ }
12859
+
12860
+ async function resumeInterruptedPlanReview(ctx: ExtensionContext, title: string): Promise<boolean> {
12861
+ const reason = planActiveRuntimeStaleReason(ctx);
12862
+ recordWorkflowInternalEvent(ctx, `Recovered stale Plan review runtime. ${reason}`);
12863
+ show(pi, `# ${title}\n\nRecovered interrupted Plan review runtime. Re-running reviewer.\n\n${workflowDisplayText(reason)}`);
12864
+ await beginReview(ctx, true);
12865
+ return true;
12866
+ }
12867
+
12868
+ async function resumeInterruptedPlanRepair(ctx: ExtensionContext, title: string): Promise<boolean> {
12869
+ const reason = planActiveRuntimeStaleReason(ctx);
12870
+ const settings = loadWorkflowSettings(ctx.cwd);
12871
+ const route = await applyModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
12872
+ if (!route) {
12873
+ show(pi, `# ${title}\n\nRecovered interrupted Plan repair runtime, but the executor model could not be applied. Configure executor, then run /plan repair or /plan retry.\n\n${workflowDisplayText(reason)}`);
12874
+ return true;
12875
+ }
12876
+ clearPendingWorkflowToolPhase(ctx, "Repair", "resumeInterruptedPlanRepair");
12877
+ armWorkflowToolsForPhase(ctx, "Repair", "resumeInterruptedPlanRepair");
12878
+ updateState({
12879
+ mode: "repairing",
12880
+ lastRepairStatus: "running",
12881
+ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) },
12882
+ planProgress: workflowPlanProgressEnabled(settings)
12883
+ ? mergePlanProgress({ ...state, mode: "repairing", lastRepairStatus: "running" }, settings, { lifecycleStatus: "repairing", repairStatus: "running", nextAction: "repair executor then validator" }, state.approvedPlan)
12884
+ : state.planProgress,
12885
+ }, ctx);
12886
+ recordWorkflowInternalEvent(ctx, `Recovered stale Plan repair runtime. ${reason}`);
12887
+ show(pi, `# ${title}\n\nRecovered interrupted Plan repair runtime. Re-arming repair executor.\n\n${workflowDisplayText(reason)}`);
12888
+ queueWorkflowPrompt(pi, workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair), buildQueuedPhaseRecovery(ctx, "Repair", (failureReason) => recoverPlanTransientHandoffFailure(ctx, "repair", failureReason, { phase: "Repair", preserveRetry: true })));
12889
+ return true;
12890
+ }
12891
+
12649
12892
  async function continueRestoredPlanRuntime(ctx: ExtensionContext, title: string, snapshot: WorkflowState): Promise<boolean> {
12650
12893
  restorePlanRuntimeSnapshot(ctx, snapshot);
12651
12894
  const settings = loadWorkflowSettings(ctx.cwd);
@@ -12670,9 +12913,20 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12670
12913
  return true;
12671
12914
  }
12672
12915
  if (state.mode === "executing") {
12916
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) return resumeInterruptedPlanExecution(ctx, title);
12673
12917
  show(pi, `# ${title}\n\nRecovered active Plan execution runtime from session history. Wait for execution completion.`);
12674
12918
  return true;
12675
12919
  }
12920
+ if (state.mode === "reviewing") {
12921
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) return resumeInterruptedPlanReview(ctx, title);
12922
+ show(pi, `# ${title}\n\nRecovered active Plan review runtime from session history. Wait for reviewer completion.`);
12923
+ return true;
12924
+ }
12925
+ if (state.mode === "repairing") {
12926
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) return resumeInterruptedPlanRepair(ctx, title);
12927
+ show(pi, `# ${title}\n\nRecovered active Plan repair runtime from session history. Wait for repair completion.`);
12928
+ return true;
12929
+ }
12676
12930
  return false;
12677
12931
  }
12678
12932
 
@@ -12717,7 +12971,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12717
12971
  }, ctx);
12718
12972
  }
12719
12973
 
12720
- function planRecoveryGuidance(current: WorkflowState, validationAvailable: boolean): string {
12974
+ function planRecoveryGuidance(current: WorkflowState, validationAvailable: boolean, activeRuntimeStale = false): string {
12721
12975
  if (current.mode === "idle" || current.mode === "cancelled" || current.mode === "awaiting_plan_input") {
12722
12976
  const latest = latestRecoverablePlan();
12723
12977
  if (latest) return `No active Plan Mode workflow is loaded. Recoverable approved plan found.\n\n${recoverablePlanDetails(latest)}\n\nNext action: /plan resume to restore it, /plan continue to run it, or /p <task> to start a new plan.`;
@@ -12727,12 +12981,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12727
12981
  if (current.mode === "awaiting_clarification") return "Plan is waiting for clarification answers. Next action: reply with choices like 1A, 2C, or use /clarify questions.";
12728
12982
  if (current.mode === "plan_draft") return "Draft plan is ready. Next action: /plan approve, /plan revise <feedback>, or /plan cancel.";
12729
12983
  if (current.mode === "plan_approved") return "Approved plan is loaded. Next action: /plan continue, /plan revalidate, /plan revise <feedback>, or /plan cancel.";
12730
- if (current.mode === "reviewing") return "Reviewer is already running. Wait for reviewer completion.";
12984
+ if (current.mode === "reviewing") return activeRuntimeStale ? "Reviewer appears interrupted. Next action: /plan continue to rearm reviewer." : "Reviewer is already running. Wait for reviewer completion.";
12731
12985
  if (current.mode === "reviewed") return "Reviewer is complete. Next action: /plan continue to execute.";
12732
- if (current.mode === "executing") return "Executor is already running. Wait for execution completion.";
12986
+ if (current.mode === "executing") return activeRuntimeStale ? "Execution appears interrupted. Next action: /plan continue to rearm executor." : "Executor is already running. Wait for execution completion.";
12733
12987
  if (current.mode === "executed") return validationAvailable ? "Execution is complete. Next action: /plan continue to validate." : "Execution is complete and validation is not active for this workflow. Next action: /plan continue to finish.";
12734
- if (current.mode === "validating" || current.mode === "revalidating") return "Validator is already running. Wait for validation result.";
12735
- if (current.mode === "repairing") return "Executor is already running in repair mode. Revalidation will start after repair completes.";
12988
+ if (current.mode === "validating" || current.mode === "revalidating") return activeRuntimeStale ? "Validation appears interrupted. Next action: /plan continue to rearm validator." : "Validator is already running. Wait for validation result.";
12989
+ if (current.mode === "repairing") return activeRuntimeStale ? "Repair appears interrupted. Next action: /plan continue to rearm repair executor." : "Executor is already running in repair mode. Revalidation will start after repair completes.";
12736
12990
  if (current.mode === "validated" && current.validationVerdict === "PASS") return "Validation passed. Next action: /plan continue to complete and return to ready state.";
12737
12991
  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.";
12738
12992
  if (current.mode === "validated" && (current.validationVerdict === "FAIL" || current.lastValidationFailure)) return "Validation failed. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>.";
@@ -12856,7 +13110,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12856
13110
  if (currentAdvancedSnapshot) restorePlanRuntimeSnapshotForResume(ctx, currentAdvancedSnapshot);
12857
13111
  const resolved = resolveActivePlanForResume();
12858
13112
  const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim());
12859
- 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)}`);
13113
+ const activeRuntimeStale = planActiveRuntimeIsRecoverablyStale(ctx);
13114
+ if (resolved.source === "none" && allPlans.length === 0) return show(pi, `# Plan Resume\n\n${resolved.reason ?? planRecoveryGuidance(state, validationAvailable, activeRuntimeStale)}\n\nUse /workflow plans list to see saved plans.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
12860
13115
  let selected = resolved.plan;
12861
13116
  if (resolved.source === "history" && selected) {
12862
13117
  const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, selected);
@@ -12902,7 +13157,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12902
13157
  // Primary /plan resume surface: restore state, then let the user choose.
12903
13158
  const showActivePlanResumeActionMenu = async (currentPlan?: SavedWorkflowPlan): Promise<void> => {
12904
13159
  const details = currentPlan ? `\n\n${recoverablePlanDetails(currentPlan)}` : "";
12905
- const summary = `# Plan Resume\n\n${currentPlan ? "Plan loaded from saved history." : state.approvedPlan ? "Current plan is loaded." : "No current plan is loaded."}${details}\n\n${planRecoveryGuidance(state, validationAvailable)}\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`;
13160
+ const summary = `# Plan Resume\n\n${currentPlan ? "Plan loaded from saved history." : state.approvedPlan ? "Current plan is loaded." : "No current plan is loaded."}${details}\n\n${planRecoveryGuidance(state, validationAvailable, planActiveRuntimeIsRecoverablyStale(ctx))}\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`;
12906
13161
  show(pi, `${summary}\n\nChoose what to do.`);
12907
13162
  if (!ctx.hasUI) return;
12908
13163
  const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
@@ -12990,8 +13245,20 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12990
13245
  if (state.mode === "planning") return show(pi, `# ${title}\n\nPlanner is already running. Wait for the draft plan or clarification request.\n\n${status}`);
12991
13246
  if (state.mode === "awaiting_clarification") return show(pi, `# ${title}\n\nPlan is waiting for clarification answers. Next action: reply with choices like 1A, 2C, or use /clarify questions.\n\n${status}`);
12992
13247
  if (state.mode === "plan_draft") return show(pi, `# ${title}\n\nDraft plan is ready. Next action: /plan approve, /plan revise <feedback>, or /plan cancel.\n\n${status}`);
12993
- if (state.mode === "reviewing") return show(pi, `# ${title}\n\nReviewer is already running. Wait for reviewer completion.\n\n${status}`);
12994
- if (state.mode === "executing") return show(pi, `# ${title}\n\nExecutor is already running. Wait for execution completion.\n\n${status}`);
13248
+ if (state.mode === "reviewing") {
13249
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) {
13250
+ await resumeInterruptedPlanReview(ctx, title);
13251
+ return;
13252
+ }
13253
+ return show(pi, `# ${title}\n\nReviewer is already running. Wait for reviewer completion.\n\n${status}`);
13254
+ }
13255
+ if (state.mode === "executing") {
13256
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) {
13257
+ await resumeInterruptedPlanExecution(ctx, title);
13258
+ return;
13259
+ }
13260
+ return show(pi, `# ${title}\n\nExecutor is already running. Wait for execution completion.\n\n${status}`);
13261
+ }
12995
13262
  if (state.mode === "validating" || state.mode === "revalidating") {
12996
13263
  if (planValidationRuntimeIsStale(ctx)) {
12997
13264
  await resumeInterruptedPlanValidation(ctx, title);
@@ -12999,7 +13266,13 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
12999
13266
  }
13000
13267
  return show(pi, `# ${title}\n\nValidator is already running. Wait for validation result.\n\n${status}`);
13001
13268
  }
13002
- if (state.mode === "repairing") return show(pi, `# ${title}\n\nExecutor is already running in repair mode. Revalidation will start after repair completes.\n\n${status}`);
13269
+ if (state.mode === "repairing") {
13270
+ if (planActiveRuntimeIsRecoverablyStale(ctx)) {
13271
+ await resumeInterruptedPlanRepair(ctx, title);
13272
+ return;
13273
+ }
13274
+ return show(pi, `# ${title}\n\nExecutor is already running in repair mode. Revalidation will start after repair completes.\n\n${status}`);
13275
+ }
13003
13276
 
13004
13277
  if (state.mode === "plan_approved") {
13005
13278
  if (planReviewForcedPolicyBlocked()) {
@@ -13742,6 +14015,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13742
14015
  showMissionPlanMenuFallback("Mission plan menu was skipped because the active mission state changed.");
13743
14016
  return;
13744
14017
  }
14018
+ showMissionPlanReadyCard(mission);
13745
14019
  if (!ctx.hasUI) return;
13746
14020
  const reviewChoice = ["PASS", "NOTES"].includes(mission.reviewerVerdict ?? "") ? [] : ["Review Mission Plan"];
13747
14021
  const choices = mission.autonomy === "manual" || mission.autonomy === "approval_gated"
@@ -13750,6 +14024,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13750
14024
  const choice = await ctx.ui.select("Mission plan ready. Choose next action:", choices);
13751
14025
  if (!choice) {
13752
14026
  updateState({ mode: "mission_plan_ready", activeMissionId: mission.id, task: mission.goal, originalTask: mission.goal, draftPlan: mission.planText }, ctx);
14027
+ showMissionPlanMenuFallback("Mission plan menu was dismissed. The mission plan remains ready.");
13753
14028
  return recordWorkflowInternalEvent(ctx, `Mission plan menu dismissed: ${mission.id}`);
13754
14029
  }
13755
14030
  if (choice === "Review Mission Plan") {
@@ -13785,12 +14060,31 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13785
14060
  show(pi, `# Mission Plan Ready\n\n${reason}\n\nUse one of:\n- \`/mission review\`\n- \`/mission approve\`\n- \`/mission revise <feedback>\`\n- \`/mission cancel\``);
13786
14061
  }
13787
14062
 
14063
+ function showMissionPlanReadyCard(mission: MissionState): void {
14064
+ const planText = mission.planText?.trim() ? mission.planText : "(none recorded)";
14065
+ show(pi, `# Mission Plan Ready\n\n${planText}\n\nChoose an action in the Mission menu, or use:\n- \`/mission review\`\n- \`/mission approve\`\n- \`/mission revise <feedback>\`\n- \`/mission cancel\``);
14066
+ }
14067
+
13788
14068
  // ── Menus ──────────────────────────────────────────────────────
13789
14069
 
13790
14070
  function showPlanMenuCommandFallback(reason: string): void {
13791
14071
  show(pi, `# Plan Ready\n\n${reason}\n\nUse one of:\n- \`/plan approve\`\n- \`/plan revise <feedback>\`\n- \`/plan cancel\``);
13792
14072
  }
13793
14073
 
14074
+ function showPlanReadyForApprovalCard(snapshot: WorkflowHandoffSnapshot, options: { includePlanText?: boolean } = {}): void {
14075
+ const planText = snapshot.draftPlan?.trim()
14076
+ ? snapshot.draftPlan
14077
+ : state.draftPlan?.trim()
14078
+ ? state.draftPlan
14079
+ : state.approvedPlan?.trim()
14080
+ ? state.approvedPlan
14081
+ : "(none recorded)";
14082
+ const body = options.includePlanText === true
14083
+ ? `${planText}\n\nChoose an action in the approval menu, or use:`
14084
+ : "Choose an action in the approval menu. The native plan above is the source of truth, and the saved plan remains recoverable with:";
14085
+ show(pi, `# Plan Ready For Approval\n\n${body}\n- \`/plan approve\`\n- \`/plan revise <feedback>\`\n- \`/plan cancel\``);
14086
+ }
14087
+
13794
14088
  function showPlanClarificationFallback(reason: string, questions?: ClarificationQuestion[]): void {
13795
14089
  const rendered = questions?.length ? `\n\n${renderClarificationQuestionsForUser(questions)}` : "";
13796
14090
  show(pi, `# Clarification Needed\n\n${reason}${rendered}\n\nAnswer with: /clarify answer 1A 2C\nSkip with: /clarify answer 1S\nCustom answer: /clarify answer 3D: your text`);
@@ -13845,9 +14139,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13845
14139
  return;
13846
14140
  }
13847
14141
  if (!ctx.hasUI) {
13848
- showPlanMenuCommandFallback(snapshot.fallback);
14142
+ showPlanReadyForApprovalCard(snapshot, { includePlanText: true });
13849
14143
  return;
13850
14144
  }
14145
+ showPlanReadyForApprovalCard(snapshot);
13851
14146
  const settings = loadWorkflowSettings(ctx.cwd);
13852
14147
  const approveLabel = settings.models.reviewer.enabled ? "Approve and Run Workflow" : "Approve and Execute";
13853
14148
  let menuChoice: string | undefined;
@@ -13857,6 +14152,11 @@ ${renderMissionStatus(activeMission ?? paused)}`);
13857
14152
  showPlanMenuCommandFallback(`Interactive approval menu failed to open: ${error instanceof Error ? error.message : String(error)}`);
13858
14153
  return;
13859
14154
  }
14155
+ if (!menuChoice) {
14156
+ updateState({ mode: "plan_draft", activePlanId: snapshot.activePlanId ?? state.activePlanId, draftPlan: snapshot.draftPlan ?? state.draftPlan, task: snapshot.task ?? state.task }, ctx);
14157
+ showPlanMenuCommandFallback("Plan approval menu was dismissed. The plan remains ready for approval.");
14158
+ return;
14159
+ }
13860
14160
  if (menuChoice === "Approve and Execute" || menuChoice === "Approve and Run Workflow") {
13861
14161
  await approveCurrentPlan(ctx, "plan approved by user");
13862
14162
  } else if (menuChoice === "Revise Plan") {
@@ -14404,11 +14704,13 @@ ${renderMissionStatus(activeMission ?? paused)}`);
14404
14704
  if (!mode) continue;
14405
14705
  if (mode === "custom_model") { await selectCompactionModel(ctx); continue; }
14406
14706
  const r = updateSettings(ctx.cwd, undefined, (s) => {
14407
- s.context.compactionMode = mode;
14408
- s.context.customCompactionEnabled = false;
14409
- if (mode === "pi_default") delete s.context.compactionTriggerPercent;
14707
+ if (mode === "pi_default") resetCompactionContextToPiDefault(s.context);
14708
+ else {
14709
+ s.context.compactionMode = mode;
14710
+ s.context.customCompactionEnabled = false;
14711
+ }
14410
14712
  });
14411
- ctx.ui.notify(`Compaction mode set to ${compactionModeLabel(mode)} in ${r.file}${mode === "pi_default" ? "; Workflow trigger percent override removed" : ""}.`, "info");
14713
+ ctx.ui.notify(`Compaction mode set to ${compactionModeLabel(mode)} in ${r.file}${mode === "pi_default" ? "; custom compaction overrides reset, selected provider/model preserved" : ""}.`, "info");
14412
14714
  } else if (choice === "Compaction Provider") {
14413
14715
  await selectCompactionModel(ctx);
14414
14716
  } else if (choice === "Compaction Model") {
@@ -15110,7 +15412,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15110
15412
  }
15111
15413
 
15112
15414
  function presetUsage(): string {
15113
- return "# Workflow Presets\n\nQuick use:\n- /workflow presets opens the selector\n- Ctrl+Shift+U cycles saved presets from the footer only while Plan/Mission/Standard Mode is active\n- /workflow presets list\n- /workflow presets apply <name>\n- /workflow presets next\n- /workflow presets prev\n- /workflow presets manual\n- /workflow presets save <name>\n- /workflow presets create <name> from simple|standard|deep|maximum\n- /workflow presets edit <name>\n- /workflow presets rename <old> to <new>\n- /workflow presets delete <name>";
15415
+ return "# Workflow Presets\n\nQuick use:\n- /workflow presets opens the selector\n- Ctrl+Shift+U cycles saved presets from the footer only while Plan/Mission/Standard Mode is active\n- /workflow presets list\n- /workflow presets apply <name>\n- /workflow presets next\n- /workflow presets prev\n- /workflow presets save <name>\n- /workflow presets create <name> from simple|standard|deep|maximum\n- /workflow presets edit <name>\n- /workflow presets rename <old> to <new>\n- /workflow presets delete <name>";
15114
15416
  }
15115
15417
 
15116
15418
  function presetActionMessage(title: string, name: string, resultFile?: string, scope?: string): string {
@@ -15131,7 +15433,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15131
15433
 
15132
15434
  function presetDisplayOptions(settings: ReturnType<typeof loadWorkflowSettings>): { names: string[]; options: string[] } {
15133
15435
  const catalog = workflowPresetCatalog(settings);
15134
- const active = settings.presets?.activePreset ?? WORKFLOW_MANUAL_PRESET;
15436
+ const active = settings.presets?.activePreset ?? WORKFLOW_CUSTOM_PRESET_MARKER;
15135
15437
  const names = workflowPresetNames(settings);
15136
15438
  const options = names.map((name) => `${name === active ? "●" : "○"} ${workflowPresetPickerLabel(name, catalog[name])}`);
15137
15439
  return { names, options };
@@ -15160,17 +15462,17 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15160
15462
  else show(pi, presetActionMessage("Workflow Preset Applied", label, result.file, result.scope));
15161
15463
  }
15162
15464
 
15163
- async function markWorkflowPresetManual(ctx: ExtensionContext): Promise<void> {
15164
- const result = updateSettings(ctx.cwd, undefined, (s) => { s.presets = { ...(s.presets ?? {}), activePreset: WORKFLOW_MANUAL_PRESET, items: { ...(s.presets?.items ?? {}) } }; });
15465
+ async function markWorkflowPresetCustom(ctx: ExtensionContext): Promise<void> {
15466
+ const result = updateSettings(ctx.cwd, undefined, (s) => { s.presets = { ...(s.presets ?? {}), activePreset: WORKFLOW_CUSTOM_PRESET_MARKER, items: { ...(s.presets?.items ?? {}) } }; });
15165
15467
  setWorkflowUi(ctx, state, activeSubagents);
15166
- ctx.ui.notify(`Workflow preset marker set to Manual settings in ${result.file}; workflow settings unchanged.`, "info");
15468
+ ctx.ui.notify(`Workflow preset marker cleared in ${result.file}; workflow settings unchanged.`, "info");
15167
15469
  }
15168
15470
 
15169
15471
  async function cycleWorkflowPreset(ctx: ExtensionContext, direction: 1 | -1): Promise<void> {
15170
15472
  const settings = loadWorkflowSettings(ctx.cwd);
15171
15473
  const cycle = workflowPresetNames(settings);
15172
- if (!cycle.length) return markWorkflowPresetManual(ctx);
15173
- const active = settings.presets?.activePreset ?? WORKFLOW_MANUAL_PRESET;
15474
+ if (!cycle.length) return markWorkflowPresetCustom(ctx);
15475
+ const active = settings.presets?.activePreset ?? WORKFLOW_CUSTOM_PRESET_MARKER;
15174
15476
  const currentIndex = cycle.indexOf(active);
15175
15477
  const nextIndex = currentIndex === -1 ? (direction === 1 ? 0 : cycle.length - 1) : (currentIndex + direction + cycle.length) % cycle.length;
15176
15478
  await applyWorkflowPresetByName(ctx, cycle[nextIndex], true);
@@ -15245,7 +15547,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
15245
15547
  if (action === "select" || action === "menu" || action === "picker") return showWorkflowPresetSelector(ctx);
15246
15548
  if (action === "list") return show(pi, renderWorkflowPresets(loadWorkflowSettings(ctx.cwd)));
15247
15549
  if (action === "next" || action === "cycle") { await cycleWorkflowPreset(ctx, 1); return; }
15248
- if (action === "manual" || action === "custom") { await markWorkflowPresetManual(ctx); return; }
15550
+ if (action === "custom") { await markWorkflowPresetCustom(ctx); return; }
15249
15551
  if (action === "prev" || action === "previous") { await cycleWorkflowPreset(ctx, -1); return; }
15250
15552
  if (action === "apply" || action === "use") {
15251
15553
  if (!rest) return show(pi, "# Workflow Presets\n\nUsage: /workflow presets apply <name>");
@@ -16708,10 +17010,10 @@ Pi Version: v${VERSION}
16708
17010
  const mode = parseCompactionMode(value);
16709
17011
  if (!mode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context compactionMode <pi_default|custom_model|custom_agent|disabled>`");
16710
17012
  const result = updateSettings(ctx.cwd, scope, (s) => {
16711
- s.context.compactionMode = mode;
16712
- if (mode === "pi_default") delete s.context.compactionTriggerPercent;
17013
+ if (mode === "pi_default") resetCompactionContextToPiDefault(s.context);
17014
+ else s.context.compactionMode = mode;
16713
17015
  });
16714
- const triggerNote = mode === "pi_default" ? "\n\nWorkflow compaction trigger percent override removed; Pi default compaction behavior applies." : "";
17016
+ const triggerNote = mode === "pi_default" ? "\n\nCustom compaction overrides reset; selected provider/model preserved; Pi default automatic compaction behavior applies." : "";
16715
17017
  return show(pi, updatedMessage(result.scope, result.file, "context.compactionMode", compactionModeLabel(mode)) + triggerNote);
16716
17018
  }
16717
17019
  if (subject === "context" && key === "customCompactionEnabled") {
@@ -17432,11 +17734,6 @@ Public workflow commands:
17432
17734
  else if (isMissionWorkflowMode(state)) updateState({ missionTokensUsed: (state.missionTokensUsed ?? 0) + turnTokens }, ctx);
17433
17735
  }
17434
17736
 
17435
- if (initialPlanParentSuppressed()) {
17436
- traceWorkflowTracking(ctx, "typed-initial-plan-parent-message-suppressed", { mode: state.mode, lifecycleStatus: state.planProgress?.lifecycleStatus });
17437
- return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
17438
- }
17439
-
17440
17737
  if (planReviewParentSuppressed()) {
17441
17738
  traceWorkflowTracking(ctx, "typed-plan-review-parent-message-suppressed", { mode: state.mode, lifecycleStatus: state.planProgress?.lifecycleStatus });
17442
17739
  return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
@@ -17744,7 +18041,7 @@ Public workflow commands:
17744
18041
  }
17745
18042
  return;
17746
18043
  }
17747
- updateState({ lastWorkflowHandoff: undefined }, ctx);
18044
+ if (!finalizeStandardTerminalStateIfIdle(ctx, "typed standard final handoff")) updateState({ lastWorkflowHandoff: undefined }, ctx);
17748
18045
  }
17749
18046
  const autoDecision = parseStandardAutoCheckDecision(text);
17750
18047
  const autoCheckPatch = parseStandardAutoChecks(text);