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