@mediadatafusion/pi-workflow-suite 0.0.20 → 0.0.22
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 +18 -0
- package/README.md +51 -17
- package/VERSION +1 -1
- package/config/prompts/mission-repair.md +1 -0
- package/config/prompts/mission-run.md +1 -0
- package/extensions/workflow-modes.ts +1038 -188
- package/extensions/workflow-state.ts +20 -0
- package/extensions/workflow-subagent-policy.ts +236 -3
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import { WORKFLOW_SHORTCUTS, workflowEntryShortcutLabel as workflowRegistryEntry
|
|
|
17
17
|
import { cleanupOrphanProcesses, clearSubagentResultCache, runWorkflowSubagents, workflowSubagentResultOutput, type WorkflowSubagentResult, type WorkflowSubagentTask } from "./subagent/runner.js";
|
|
18
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";
|
|
19
19
|
import { classifyValidationFailure, normalizeValidationVerdict, validationReportHasRepairableIssue } from "./workflow-validation-classifier.js";
|
|
20
|
-
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";
|
|
21
21
|
import { renderWorkflowSettingsCapabilityMatrix, workflowSettingsCapabilitiesByStatus } from "./workflow-settings-capabilities.js";
|
|
22
22
|
import { Box, Markdown, Spacer, Text, hyperlink, type Component } from "@earendil-works/pi-tui";
|
|
23
23
|
|
|
@@ -908,6 +908,17 @@ type WorkflowQueuedTurnOptions = {
|
|
|
908
908
|
onFinalFailure?: (failure: WorkflowQueuedTurnFailure) => void;
|
|
909
909
|
};
|
|
910
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
|
+
|
|
911
922
|
const WORKFLOW_IDLE_RETRY_DELAYS_MS = [50, 100, 250, 500, 1000, 1500, 2000, 3000, 5000] as const;
|
|
912
923
|
|
|
913
924
|
function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: string, attempt = 0, connectionAttempt = 0, idleAttempt = 0, options: WorkflowQueuedTurnOptions = {}): void {
|
|
@@ -929,6 +940,11 @@ function queueGuardedAgentTurn(pi: ExtensionAPI, content: string, customType: st
|
|
|
929
940
|
pi.sendMessage({ customType, content, display: false }, { triggerTurn: true, deliverAs: "followUp" });
|
|
930
941
|
workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
|
|
931
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
|
+
}
|
|
932
948
|
if (workflowTurnSendErrorIsBusy(error) && attempt < WORKFLOW_AGENT_TURN_RETRY_DELAYS_MS.length - 1) {
|
|
933
949
|
recordWorkflowInternalEvent(undefined, `Workflow agent turn delayed because Pi is still processing: ${customType}`);
|
|
934
950
|
queueGuardedAgentTurn(pi, content, customType, attempt + 1, connectionAttempt, idleAttempt, options);
|
|
@@ -962,6 +978,10 @@ function sendAgentTurnNowOrQueue(pi: ExtensionAPI, content: string, customType:
|
|
|
962
978
|
workflowScheduledAgentTurns = Math.max(0, workflowScheduledAgentTurns - 1);
|
|
963
979
|
} catch (error) {
|
|
964
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
|
+
}
|
|
965
985
|
queueAgentTurn(pi, content, customType, options);
|
|
966
986
|
recordWorkflowInternalEvent(undefined, `Workflow agent turn immediate send fell back to queued delivery: ${customType}: ${workflowTurnSendErrorMessage(error)}`);
|
|
967
987
|
}
|
|
@@ -992,7 +1012,7 @@ function workflowDeferredPhaseTimeoutMs(ctx: ExtensionContext, phase?: WorkflowP
|
|
|
992
1012
|
try {
|
|
993
1013
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
994
1014
|
const policy = phasePolicy(settings, phase);
|
|
995
|
-
if (policy
|
|
1015
|
+
if (!subagentPolicyRequiresRequiredEvidence(policy)) return 30_000;
|
|
996
1016
|
const limits = settings.subagents as typeof settings.subagents & { subagentTimeoutMinutes?: number };
|
|
997
1017
|
const timeoutMinutes = Math.max(1, Math.min(240, Number(limits.subagentTimeoutMinutes ?? 20)));
|
|
998
1018
|
return timeoutMinutes * 60_000 + 60_000;
|
|
@@ -1099,6 +1119,16 @@ function executionToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): s
|
|
|
1099
1119
|
return executionSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
|
|
1100
1120
|
}
|
|
1101
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
|
+
|
|
1102
1132
|
function requiredPlanExecutionTools(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
|
|
1103
1133
|
const tools = [...PLAN_TOOLS, "edit", "write", "bash", WORKFLOW_PROGRESS_TOOL, WORKFLOW_EXECUTION_RESULT_TOOL];
|
|
1104
1134
|
if (executionSubagentsAllowed(settings)) tools.push("subagent");
|
|
@@ -1778,22 +1808,27 @@ function planPrompt(task: string, priorPlan?: string, feedback?: string, setting
|
|
|
1778
1808
|
const subagentsBeforeClarification = useSubagentsBeforeClarification(settings, "plan");
|
|
1779
1809
|
const planningOrchestrationPolicy = (subagents as typeof subagents & { planningOrchestrationPolicy?: string }).planningOrchestrationPolicy ?? "orchestrator_first";
|
|
1780
1810
|
const preflightSatisfied = hasRequiredSubagentPreflight(options.preflightBlock);
|
|
1811
|
+
const forcedPlanningEvidenceRequired = subagentPolicyRequiresRequiredEvidence(subagentPolicy) && !preflightSatisfied;
|
|
1781
1812
|
const orchestratorGuidance = preflightSatisfied
|
|
1782
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."
|
|
1783
1818
|
: planningNeedsOrchestrator(settings, "plan")
|
|
1784
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."
|
|
1785
1820
|
: "Orchestrator is optional for this planning turn. Use workflow-orchestrator if the task is broad, multi-pass, or needs coordinated workers.";
|
|
1786
1821
|
const subagentGuidance = !planningSubagentsAllowed(settings)
|
|
1787
1822
|
? "Sub-agent use is disabled by settings. Do not call subagent."
|
|
1788
|
-
: preflightSatisfied && subagentPolicy
|
|
1823
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(subagentPolicy)
|
|
1789
1824
|
? forcedSubagentPolicySatisfiedGuidance("planning")
|
|
1790
1825
|
: subagentPolicy === "forced"
|
|
1791
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.`
|
|
1792
1827
|
: subagentPolicy === "maximum"
|
|
1793
|
-
? `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.`
|
|
1794
1829
|
: subagentPolicy === "deep"
|
|
1795
|
-
? `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.
|
|
1796
|
-
: "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.";
|
|
1797
1832
|
const feedbackKind = options.feedbackKind ?? (feedback ? "revision" : undefined);
|
|
1798
1833
|
const feedbackBlock = feedbackKind === "clarification" && feedback
|
|
1799
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`
|
|
@@ -1941,7 +1976,7 @@ Sub-agent planning policy:
|
|
|
1941
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.
|
|
1942
1977
|
- In Sub-Agent Usage Summary, list each sub-agent used and what it checked; if none ran, give the exact trivial/unavailable reason.
|
|
1943
1978
|
- If sub-agents are not used when subagents.planningPolicy is auto, deep, or maximum, explain why in Sub-Agent Usage Summary.
|
|
1944
|
-
- ${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>."}`;
|
|
1945
1980
|
}
|
|
1946
1981
|
|
|
1947
1982
|
// SubagentPhase and SubagentPolicyValue imported from workflow-subagent-policy.ts
|
|
@@ -2019,22 +2054,39 @@ function repairArtifactDispositionOutput(): string {
|
|
|
2019
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.`;
|
|
2020
2055
|
}
|
|
2021
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
|
+
|
|
2022
2073
|
function phasePromptPolicyBlock(settings: ReturnType<typeof loadWorkflowSettings>, phase: SubagentPhase, label: string = phase, preflightBlock?: string): string {
|
|
2023
2074
|
const policy = phasePolicy(settings, phase);
|
|
2024
2075
|
const workers = workerCount(settings, phase);
|
|
2076
|
+
const required = Math.max(1, workers.maximum);
|
|
2025
2077
|
const phaseName = phase === "Repair" ? "repair-mode sub-agent workers" : `${phase.toLowerCase()} sub-agent workers`;
|
|
2026
2078
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
2027
2079
|
const guidance = !phaseAutoUseAllowed(settings, phase) || policy === "off"
|
|
2028
2080
|
? `${label} sub-agent use is disabled by settings. Do not call subagent.`
|
|
2029
|
-
: preflightSatisfied && policy
|
|
2081
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
2030
2082
|
? forcedSubagentPolicySatisfiedGuidance(label)
|
|
2031
2083
|
: policy === "forced"
|
|
2032
|
-
? `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.`
|
|
2033
2085
|
: policy === "maximum"
|
|
2034
|
-
? `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.`
|
|
2035
2087
|
: policy === "deep"
|
|
2036
|
-
? `Use ${Math.max(1, workers.deep)} ${phaseName} for non-trivial work; give a concrete skip reason if no worker is useful or available.`
|
|
2037
|
-
: `${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.`;
|
|
2038
2090
|
return `Sub-agent policy for ${label}:
|
|
2039
2091
|
- policy: ${policy}
|
|
2040
2092
|
- workers: ${activeWorkerTargetLabel(policy, workers)}
|
|
@@ -2121,15 +2173,15 @@ function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
|
|
|
2121
2173
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
2122
2174
|
const subagentGuidance = !executionSubagentsAllowed(settings)
|
|
2123
2175
|
? "Execution sub-agents are disabled by settings. Do not call subagent."
|
|
2124
|
-
: preflightSatisfied && policy
|
|
2176
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
2125
2177
|
? forcedSubagentPolicySatisfiedGuidance("execution")
|
|
2126
2178
|
: policy === "forced"
|
|
2127
|
-
? `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.`
|
|
2128
2180
|
: policy === "maximum"
|
|
2129
|
-
? `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.`
|
|
2130
2182
|
: policy === "deep"
|
|
2131
|
-
? `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.
|
|
2132
|
-
:
|
|
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.`;
|
|
2133
2185
|
const automatableEvidenceGuidance = `Automatable evidence contract:
|
|
2134
2186
|
- Before calling workflow_execution_result, you MUST gather all automatable validation evidence that the approved plan requires.
|
|
2135
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.
|
|
@@ -2203,7 +2255,7 @@ ${requiredSubagentPreflightSection(preflightBlock)}
|
|
|
2203
2255
|
- Execution agents may run in parallel for analysis, file inspection, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation.
|
|
2204
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.
|
|
2205
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.
|
|
2206
|
-
- ${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>."}
|
|
2207
2259
|
- Final execution summary must include Sub-Agent Usage Summary with agents used and findings applied, or a forced-policy blocker.
|
|
2208
2260
|
|
|
2209
2261
|
${subagentCapabilityTable()}
|
|
@@ -2227,15 +2279,15 @@ function validatePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
|
|
|
2227
2279
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
2228
2280
|
const subagentGuidance = !validationSubagentsAllowed(settings)
|
|
2229
2281
|
? "Validation sub-agents are disabled by settings. Do not call subagent."
|
|
2230
|
-
: preflightSatisfied && policy
|
|
2282
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
2231
2283
|
? forcedSubagentPolicySatisfiedGuidance("validation")
|
|
2232
2284
|
: policy === "forced"
|
|
2233
|
-
? `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.`
|
|
2234
2286
|
: policy === "maximum"
|
|
2235
|
-
? `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.`
|
|
2236
2288
|
: policy === "deep"
|
|
2237
|
-
? `For non-trivial validation, use at least ${workers.deep} validation sub-agent for independent checks, regression review, or risk analysis. Prefer quality-validation.
|
|
2238
|
-
:
|
|
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.`;
|
|
2239
2291
|
const automatableEvidenceVerifierGuidance = `Automatable evidence verification:
|
|
2240
2292
|
- Before marking Manual Verification Required: yes, verify that the missing evidence is genuinely non-automatable.
|
|
2241
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.
|
|
@@ -2293,7 +2345,7 @@ ${requiredSubagentPreflightSection(preflightBlock)}
|
|
|
2293
2345
|
${subagentCapabilityTable()}
|
|
2294
2346
|
|
|
2295
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.
|
|
2296
|
-
- ${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>."}
|
|
2297
2349
|
- PASS only when the approved plan is fully satisfied with no blocking unresolved risk.
|
|
2298
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.
|
|
2299
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.
|
|
@@ -2357,15 +2409,15 @@ function workflowRepairPrompt(state: WorkflowState, settings = loadWorkflowSetti
|
|
|
2357
2409
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
2358
2410
|
const subagentGuidance = !phaseAutoUseAllowed(settings, "Repair") || policy === "off"
|
|
2359
2411
|
? "Repair sub-agents are disabled by settings. Do not call subagent."
|
|
2360
|
-
: preflightSatisfied && policy
|
|
2412
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
2361
2413
|
? forcedSubagentPolicySatisfiedGuidance("repair")
|
|
2362
2414
|
: policy === "forced"
|
|
2363
|
-
? `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.`
|
|
2364
2416
|
: policy === "maximum"
|
|
2365
|
-
? `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.`
|
|
2366
2418
|
: policy === "deep"
|
|
2367
|
-
? `For non-trivial repair, use ${Math.max(1, workers.deep)} repair/execution worker(s) before edits when useful and
|
|
2368
|
-
:
|
|
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.`;
|
|
2369
2421
|
return `${repairPrompt}
|
|
2370
2422
|
|
|
2371
2423
|
You are in PI WORKFLOW REPAIR MODE.
|
|
@@ -2395,7 +2447,7 @@ Sub-agent repair policy:
|
|
|
2395
2447
|
${requiredSubagentPreflightSection(preflightBlock)}
|
|
2396
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.
|
|
2397
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.
|
|
2398
|
-
- ${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>."}
|
|
2399
2451
|
- Do not re-grade validation. Do not claim PASS/FAIL for repaired work; recommend revalidation.
|
|
2400
2452
|
- If no concrete repairable issue exists, perform no-op repair summary and recommend manual verification or /plan revalidate.
|
|
2401
2453
|
|
|
@@ -2431,15 +2483,15 @@ function reviewerPrompt(state: WorkflowState, settings = loadWorkflowSettings(),
|
|
|
2431
2483
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
2432
2484
|
const subagentGuidance = !reviewSubagentsAllowed(settings)
|
|
2433
2485
|
? "Reviewer sub-agents are disabled by settings. Do not call subagent."
|
|
2434
|
-
: preflightSatisfied && policy
|
|
2486
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
2435
2487
|
? forcedSubagentPolicySatisfiedGuidance("review")
|
|
2436
2488
|
: policy === "forced"
|
|
2437
|
-
? `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.`
|
|
2438
2490
|
: policy === "maximum"
|
|
2439
|
-
? `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.`
|
|
2440
2492
|
: policy === "deep"
|
|
2441
|
-
? `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.
|
|
2442
|
-
:
|
|
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.`;
|
|
2443
2495
|
return `You are in PI WORKFLOW REVIEWER MODE.
|
|
2444
2496
|
|
|
2445
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.
|
|
@@ -2466,7 +2518,7 @@ ${subagentCapabilityTable()}
|
|
|
2466
2518
|
|
|
2467
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.
|
|
2468
2520
|
- Reviewer sub-agents must not perform direct file edits.
|
|
2469
|
-
- ${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>."}
|
|
2470
2522
|
- The reviewer must not rubber-stamp execution; surface missing requirements before the executor starts.
|
|
2471
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.
|
|
2472
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.
|
|
@@ -3901,6 +3953,20 @@ function planProgressHasOpenSteps(progress: PlanProgressState | undefined): bool
|
|
|
3901
3953
|
return Boolean(progress?.steps.some((step) => step.status === "pending" || step.status === "active" || step.status === "failed" || step.status === "blocked"));
|
|
3902
3954
|
}
|
|
3903
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
|
+
|
|
3904
3970
|
function planRuntimeLabel(state: WorkflowState): string {
|
|
3905
3971
|
return `Runtime: ${formatDurationMs(planActiveRuntimeMs(state))} active`;
|
|
3906
3972
|
}
|
|
@@ -4449,8 +4515,13 @@ function classifyStandardWork(task: string | undefined, answerSummary?: string):
|
|
|
4449
4515
|
return { phase: "Planning", kind: "read_only" };
|
|
4450
4516
|
}
|
|
4451
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
|
+
|
|
4452
4523
|
function standardForcedSubagentSafeBash(command: string): boolean {
|
|
4453
|
-
return
|
|
4524
|
+
return forcedSubagentActionDecision({ phase: "Execution", policy: "forced", toolName: "bash", command }).allowBeforeEvidence;
|
|
4454
4525
|
}
|
|
4455
4526
|
|
|
4456
4527
|
function standardTodoUsageText(): string {
|
|
@@ -4849,8 +4920,11 @@ function standardPrompt(state: WorkflowState, settings: ReturnType<typeof loadWo
|
|
|
4849
4920
|
const todoMode = standardTodoMode(settings);
|
|
4850
4921
|
const standardModelRole = effectiveStandardModelRole(settings);
|
|
4851
4922
|
const standardSubagentPolicyBlock = standardSubagentsAllowed(settings)
|
|
4852
|
-
? `Standard sub-agent policy:\n-
|
|
4923
|
+
? `Standard sub-agent policy:\n- current worker bucket: ${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. These workflowPhase labels are Standard worker buckets only; they do not move Standard into Plan/Mission review or validation UI. 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 worker bucket is execution or repair and that bucket policy is forced, satisfy the required sub-agent usage before edit/write/unsafe bash.`
|
|
4853
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
|
+
: "";
|
|
4854
4928
|
const autoCheckInstruction = standardAutoChecksRequired(state, settings)
|
|
4855
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.`
|
|
4856
4930
|
: "Standard auto To Do and clarification checks are disabled; no Standard Auto Checks block is required.";
|
|
@@ -4888,6 +4962,7 @@ Rules:
|
|
|
4888
4962
|
- ${clarificationLine}
|
|
4889
4963
|
- ${autoCheckInstruction}
|
|
4890
4964
|
- ${standardSubagentPolicyBlock}
|
|
4965
|
+
${standardSubagentDecisionInstruction ? `- ${standardSubagentDecisionInstruction}` : ""}
|
|
4891
4966
|
|
|
4892
4967
|
${standardSubagentsAllowed(settings) ? subagentCapabilityTable() : ""}
|
|
4893
4968
|
|
|
@@ -7459,12 +7534,34 @@ function repairFailureApprovalReason(text: string, options: { retryMode?: string
|
|
|
7459
7534
|
return undefined;
|
|
7460
7535
|
}
|
|
7461
7536
|
|
|
7462
|
-
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 {
|
|
7463
7556
|
if (settings.missions.validationRetryMode === "off") return "validationRetryMode=off.";
|
|
7464
|
-
|
|
7557
|
+
const gateOptions = {
|
|
7465
7558
|
retryMode: settings.missions.validationRetryMode,
|
|
7466
7559
|
requireApprovalForDestructiveRepair: settings.missions.requireApprovalForDestructiveRepair !== false,
|
|
7467
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,
|
|
7468
7565
|
outOfScopeReason: "repair appears outside the approved milestone scope.",
|
|
7469
7566
|
safeOnlyReason: "safe_only repair mode requires approval for this validation failure.",
|
|
7470
7567
|
});
|
|
@@ -7936,7 +8033,7 @@ function fallbackMissionMilestones(goal: string): MissionMilestone[] {
|
|
|
7936
8033
|
];
|
|
7937
8034
|
}
|
|
7938
8035
|
|
|
7939
|
-
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 {
|
|
7940
8037
|
const base = readPromptFile("mission-plan.md", "You are PI MISSION MODE PLANNER. Output MISSION_DECISION: plan and milestone headings. Do not execute.");
|
|
7941
8038
|
const baseWithSubagentGuidance = `${base}\n\n${subagentCapabilityTable()}`;
|
|
7942
8039
|
const sub = settings.subagents;
|
|
@@ -7949,12 +8046,24 @@ function missionPlanPrompt(mission: MissionState, settings: ReturnType<typeof lo
|
|
|
7949
8046
|
const subagentsBeforeClarification = useSubagentsBeforeClarification(settings, "mission");
|
|
7950
8047
|
const planningOrchestrationPolicy = (sub as typeof sub & { planningOrchestrationPolicy?: string }).planningOrchestrationPolicy ?? "orchestrator_first";
|
|
7951
8048
|
const preflightSatisfied = hasRequiredSubagentPreflight(options.preflightBlock);
|
|
8049
|
+
const forcedPlanningEvidenceRequired = subagentPolicyRequiresRequiredEvidence(policy) && !preflightSatisfied;
|
|
7952
8050
|
const orchestratorGuidance = preflightSatisfied
|
|
7953
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."
|
|
7954
8056
|
: planningNeedsOrchestrator(settings, "mission")
|
|
7955
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."
|
|
7956
8058
|
: "Orchestrator is optional for this mission planning turn. Use workflow-orchestrator if the mission is broad, multi-pass, or needs coordinated workers.";
|
|
7957
|
-
|
|
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.
|
|
7958
8067
|
|
|
7959
8068
|
Diagram guidance:
|
|
7960
8069
|
${workflowMermaidGuidance()}
|
|
@@ -7973,15 +8082,15 @@ function missionReviewPrompt(mission: MissionState, settings = loadWorkflowSetti
|
|
|
7973
8082
|
const preflightSatisfied = hasRequiredSubagentPreflight(preflightBlock);
|
|
7974
8083
|
const subagentGuidance = !reviewSubagentsAllowed(settings)
|
|
7975
8084
|
? "Reviewer sub-agents are disabled by settings. Do not call subagent."
|
|
7976
|
-
: preflightSatisfied && policy
|
|
8085
|
+
: preflightSatisfied && subagentPolicyRequiresRequiredEvidence(policy)
|
|
7977
8086
|
? forcedSubagentPolicySatisfiedGuidance("review")
|
|
7978
8087
|
: policy === "forced"
|
|
7979
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.`
|
|
7980
8089
|
: policy === "maximum"
|
|
7981
|
-
? `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.`
|
|
7982
8091
|
: policy === "deep"
|
|
7983
|
-
? `For non-trivial review, use at least ${workers.deep} reviewer sub-agent for independent risk review, implementation-scope review, or validation-plan review.
|
|
7984
|
-
: "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.";
|
|
7985
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";
|
|
7986
8095
|
return `You are in PI MISSION MODE REVIEWER MODE.
|
|
7987
8096
|
|
|
@@ -8022,7 +8131,7 @@ ${subagentCapabilityTable()}
|
|
|
8022
8131
|
|
|
8023
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.
|
|
8024
8133
|
- Reviewer sub-agents must not perform direct file edits.
|
|
8025
|
-
- ${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>."}
|
|
8026
8135
|
- The reviewer must not rubber-stamp execution; surface missing requirements before the mission is approved.
|
|
8027
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.
|
|
8028
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.
|
|
@@ -8402,6 +8511,8 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8402
8511
|
if (phase === "Planning") return planToolsFor(settings);
|
|
8403
8512
|
if (phase === "Review") return reviewToolsFor(settings);
|
|
8404
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);
|
|
8405
8516
|
return executionToolsFor(settings);
|
|
8406
8517
|
};
|
|
8407
8518
|
const armWorkflowToolsForPhase = (ctx: ExtensionContext, phase: WorkflowPendingToolPhase, reason: string): void => {
|
|
@@ -8445,6 +8556,124 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8445
8556
|
syncWorkflowRuntimeForActivity(ctx, `pending phase clear: ${phase}`);
|
|
8446
8557
|
traceWorkflowTracking(ctx, "pending-phase-clear", { phase, reason });
|
|
8447
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
|
+
};
|
|
8448
8677
|
let workflowSubagentActivityTimer: ReturnType<typeof setInterval> | undefined;
|
|
8449
8678
|
let workflowSubagentActivityRenderCtx: ExtensionContext | undefined;
|
|
8450
8679
|
let workflowUiClockTimer: ReturnType<typeof setInterval> | undefined;
|
|
@@ -8634,6 +8863,17 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8634
8863
|
|
|
8635
8864
|
const initialPlanParentSuppressed = (): boolean => state.mode === "plan_draft" && state.reviewHandoffSuppression?.kind === "plan_typed_initial_to_approval" && state.lastWorkflowHandoff?.type === WORKFLOW_PLAN_RESULT_TOOL;
|
|
8636
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
|
+
};
|
|
8637
8877
|
|
|
8638
8878
|
const markPlanValidationHandoffDidNotStart = (ctx: ExtensionContext, settings: ReturnType<typeof loadWorkflowSettings>, reason: string): void => {
|
|
8639
8879
|
updateState({
|
|
@@ -8821,7 +9061,10 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8821
9061
|
const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex;
|
|
8822
9062
|
const progressEnabled = workflowPlanProgressEnabled(settings);
|
|
8823
9063
|
const progressedPlanProgress = progressEnabled ? planProgressWithCompletedSteps(state, settings, completedSteps) : state.planProgress;
|
|
8824
|
-
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 };
|
|
8825
9068
|
// Fix C4: lifecycle matching mode for blocked/completion paths
|
|
8826
9069
|
const blockedLifecycle = state.reviewerReport ? "reviewed" : "blocked";
|
|
8827
9070
|
if (status !== "completed") {
|
|
@@ -8830,16 +9073,32 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8830
9073
|
showBlockedPlanRecoveryMenu(ctx);
|
|
8831
9074
|
return { ...typedToolAck(), details: { accepted: true, status, validationStarted: false } };
|
|
8832
9075
|
}
|
|
8833
|
-
const hasOpenSteps = progressEnabled && progressedPlanProgress?.steps.length &&
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
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 });
|
|
8837
9096
|
showBlockedPlanRecoveryMenu(ctx);
|
|
8838
9097
|
return { ...typedToolAck(), details: { accepted: true, status: "blocked", validationStarted: false } };
|
|
8839
9098
|
}
|
|
8840
|
-
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;
|
|
8841
9100
|
const executionSummary = progressWarning ? `${summary}\n\nPlan Progress Warning: ${progressWarning}` : summary;
|
|
8842
|
-
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);
|
|
8843
9102
|
if (validationAvailable && validateAfterExecution) {
|
|
8844
9103
|
setPendingWorkflowToolPhase(ctx, "Validation", "typed execution accepted");
|
|
8845
9104
|
}
|
|
@@ -8905,6 +9164,21 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8905
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 };
|
|
8906
9165
|
}
|
|
8907
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
|
+
}
|
|
8908
9182
|
planForcedSubagentPreflightReconcile(ctx, "Validation");
|
|
8909
9183
|
const forcedValidationBlock = forcedSubagentUsageSatisfied(ctx, "Validation");
|
|
8910
9184
|
if (forcedValidationBlock) {
|
|
@@ -8934,6 +9208,22 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
8934
9208
|
showBlockedPlanRecoveryMenu(ctx);
|
|
8935
9209
|
return { ...typedToolAck(), details: { accepted: true, status } };
|
|
8936
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
|
+
}
|
|
8937
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);
|
|
8938
9228
|
setPendingWorkflowToolPhase(ctx, "Validation", "typed repair completed");
|
|
8939
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 }) });
|
|
@@ -9012,6 +9302,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
9012
9302
|
if (!passed) {
|
|
9013
9303
|
await handleMissionReviewFailure(ctx, reviewed, missionReview.verdict, missionReview.report);
|
|
9014
9304
|
} else {
|
|
9305
|
+
setReviewHandoffSuppression(ctx, "mission_typed_review_to_approval");
|
|
9015
9306
|
deferWorkflowAction(pi, "resume mission approval after reviewer pass", async () => {
|
|
9016
9307
|
const latest = loadMissionState(reviewed.id) ?? activeMission ?? reviewed;
|
|
9017
9308
|
const block = missionReviewContinuationBlock(latest);
|
|
@@ -10347,6 +10638,29 @@ ${reportExcerpt(validation, 2400)}
|
|
|
10347
10638
|
: kind === "run" ? "Run /mission resume or /mission continue."
|
|
10348
10639
|
: kind === "planning" ? "Run /mission resume or rerun /mission plan."
|
|
10349
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
|
+
}
|
|
10350
10664
|
const paused = saveActiveMission({
|
|
10351
10665
|
...mission,
|
|
10352
10666
|
status: "paused",
|
|
@@ -10377,7 +10691,7 @@ ${reportExcerpt(validation, 2400)}
|
|
|
10377
10691
|
initialDelayMs: 2000,
|
|
10378
10692
|
requireIdle: true,
|
|
10379
10693
|
isIdle: () => ctx.isIdle(),
|
|
10380
|
-
onBeforeSend: (customType) =>
|
|
10694
|
+
onBeforeSend: (customType) => armAndVerifyWorkflowToolsForPhase(ctx, phase, `queued turn before send: ${customType}`),
|
|
10381
10695
|
});
|
|
10382
10696
|
|
|
10383
10697
|
const buildQueuedPlanningRecovery = (ctx: ExtensionContext, recover: (reason: string) => void): WorkflowQueuedTurnOptions =>
|
|
@@ -10562,7 +10876,15 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10562
10876
|
return true;
|
|
10563
10877
|
}
|
|
10564
10878
|
|
|
10565
|
-
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 } {
|
|
10566
10888
|
const byState = state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined;
|
|
10567
10889
|
const loadedActive = activeMission && (!state.activeMissionId || activeMission.id === state.activeMissionId) ? activeMission : undefined;
|
|
10568
10890
|
const latest = loadMissionState();
|
|
@@ -10576,18 +10898,24 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10576
10898
|
add(byState);
|
|
10577
10899
|
[loadedActive, latest, ...all]
|
|
10578
10900
|
.filter((mission): mission is MissionState => missionIsResumeCandidate(mission) && mission.id !== byState.id)
|
|
10579
|
-
.sort((a, b) =>
|
|
10901
|
+
.sort((a, b) => Number(missionMatchesCurrentProject(ctx, b)) - Number(missionMatchesCurrentProject(ctx, a)) || sortMissionResumeCandidates(a, b))
|
|
10580
10902
|
.forEach(add);
|
|
10581
10903
|
const inactiveReason = missionIsResumeCandidate(byState) ? undefined : `Current mission is ${(byState as MissionState).status}.`;
|
|
10582
10904
|
return { mission: byState, candidates: ordered, reason: inactiveReason };
|
|
10583
10905
|
}
|
|
10584
10906
|
|
|
10585
|
-
[loadedActive, latest, ...all]
|
|
10586
|
-
.filter(missionIsResumeCandidate)
|
|
10587
|
-
.sort(
|
|
10588
|
-
|
|
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);
|
|
10913
|
+
|
|
10914
|
+
currentProject.forEach(add);
|
|
10915
|
+
otherProject.forEach(add);
|
|
10589
10916
|
|
|
10590
|
-
if (
|
|
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.` };
|
|
10591
10919
|
|
|
10592
10920
|
add(loadedActive);
|
|
10593
10921
|
add(latest);
|
|
@@ -10600,7 +10928,7 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10600
10928
|
const done = mission.milestones.filter((m) => m.status === "completed" || m.status === "skipped").length;
|
|
10601
10929
|
const percent = total ? Math.round((done / total) * 100) : 0;
|
|
10602
10930
|
const milestone = total ? `M${Math.min(mission.currentMilestoneIndex + 1, total)}/${total}` : "M0/0";
|
|
10603
|
-
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, " ")}`;
|
|
10604
10932
|
}
|
|
10605
10933
|
|
|
10606
10934
|
async function chooseMissionFromHistory(ctx: ExtensionContext, missions: MissionState[], title = "Choose a mission"): Promise<MissionState | undefined> {
|
|
@@ -10614,12 +10942,15 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10614
10942
|
return missions.find((mission) => mission.id === id);
|
|
10615
10943
|
}
|
|
10616
10944
|
|
|
10617
|
-
async function chooseResumeMission(ctx: ExtensionContext, resolved: { mission?: MissionState; candidates: MissionState[]; reason?: string }, action: "resume" | "continue" | "next" | "retry" | "repair" | "revalidate" = "resume"): Promise<MissionState | undefined> {
|
|
10618
|
-
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;
|
|
10619
10948
|
const title = action === "resume" ? "Mission Resume" : `Mission ${action[0].toUpperCase()}${action.slice(1)}`;
|
|
10620
10949
|
const verb = action === "resume" ? "use" : action;
|
|
10621
10950
|
const choices = resolved.candidates.map(missionResumeChoiceLabel);
|
|
10622
|
-
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"
|
|
10623
10954
|
? "Choose which mission to use."
|
|
10624
10955
|
: `I found more than one mission that can ${action}. Choose which one to use.`;
|
|
10625
10956
|
show(pi, `# ${title}\n\n${intro}\n\n${choices.map((choice, i) => `${i + 1}. ${choice}`).join("\n")}`);
|
|
@@ -10670,6 +11001,8 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10670
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.` };
|
|
10671
11002
|
pi.setActiveTools(validationToolsFor(settings));
|
|
10672
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.` };
|
|
10673
11006
|
return { systemPrompt: `${systemPrompt}\n\n${missionValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
10674
11007
|
}
|
|
10675
11008
|
if (mode === "mission_final_validating") {
|
|
@@ -10677,13 +11010,17 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10677
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.` };
|
|
10678
11011
|
pi.setActiveTools(validationToolsFor(settings));
|
|
10679
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.` };
|
|
10680
11015
|
return { systemPrompt: `${systemPrompt}\n\n${missionFinalValidationPrompt(mission, settings, state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
10681
11016
|
}
|
|
10682
11017
|
if (mode === "mission_repairing") {
|
|
10683
11018
|
const route = await applyMissionModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
|
|
10684
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.` };
|
|
10685
|
-
pi.setActiveTools(
|
|
11020
|
+
pi.setActiveTools(missionRepairToolsFor(settings));
|
|
10686
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.` };
|
|
10687
11024
|
return { systemPrompt: `${systemPrompt}\n\n${missionRepairPrompt(mission, settings, phasePreflightBlocks.Repair)}` };
|
|
10688
11025
|
}
|
|
10689
11026
|
return undefined;
|
|
@@ -10865,9 +11202,9 @@ ${renderMissionProgress(mission, settings)}
|
|
|
10865
11202
|
}
|
|
10866
11203
|
|
|
10867
11204
|
async function handleMissionResume(ctx: ExtensionContext) {
|
|
10868
|
-
const resolved = resolveActiveMissionForResume();
|
|
11205
|
+
const resolved = resolveActiveMissionForResume(ctx);
|
|
10869
11206
|
let selected = resolved.mission;
|
|
10870
|
-
const allMissions = listMissionStates();
|
|
11207
|
+
const allMissions = listMissionStates().sort((a, b) => Number(missionMatchesCurrentProject(ctx, b)) - Number(missionMatchesCurrentProject(ctx, a)) || b.updatedAt.localeCompare(a.updatedAt));
|
|
10871
11208
|
if (!selected && allMissions.length > 0) selected = allMissions[0];
|
|
10872
11209
|
if (!selected) return show(pi, `# Mission Resume
|
|
10873
11210
|
|
|
@@ -10897,6 +11234,7 @@ ${renderMissionStatus(mission)}`);
|
|
|
10897
11234
|
return show(pi, `# ${selectedFromHistory ? "Selected Mission" : "Mission Resume"}
|
|
10898
11235
|
|
|
10899
11236
|
Mission: ${mission.id}
|
|
11237
|
+
Project: ${mission.projectLabel ?? mission.cwd ?? "unknown project"}
|
|
10900
11238
|
Status: ${missionPhaseFromStatus(mission.status)}
|
|
10901
11239
|
Current Milestone: ${mission.milestones.length ? `M${Math.min(mission.currentMilestoneIndex + 1, mission.milestones.length)}/${mission.milestones.length}` : "M0/0"}
|
|
10902
11240
|
Resume Available: ${resume.available ? "yes" : "no"}
|
|
@@ -10986,7 +11324,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10986
11324
|
await beginMissionPlanning(ctx, activeMission);
|
|
10987
11325
|
}
|
|
10988
11326
|
|
|
10989
|
-
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 } = {}) {
|
|
10990
11328
|
stopStartupVisual(ctx);
|
|
10991
11329
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
10992
11330
|
clearTypedHandoff(ctx, "Mission planning");
|
|
@@ -11001,7 +11339,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11001
11339
|
approvedPlan: undefined,
|
|
11002
11340
|
lastCompletedMissionSummary: undefined,
|
|
11003
11341
|
}, ctx);
|
|
11004
|
-
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 };
|
|
11005
11343
|
if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
|
|
11006
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." });
|
|
11007
11345
|
checkpointMission(blocked, "Mission planning blocked before planner handoff.", "Fix mission planning sub-agent policy blocker, then run /mission resume or /mission plan.");
|
|
@@ -11021,7 +11359,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11021
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." });
|
|
11022
11360
|
const hasAnswers = Boolean(pending.clarificationAnswers?.length);
|
|
11023
11361
|
const answerSummary = hasAnswers ? formatAnswersForPlanner(pending.clarificationQuestions ?? [], pending.clarificationAnswers ?? []) : undefined;
|
|
11024
|
-
const forceClarification =
|
|
11362
|
+
const forceClarification = options.forceAdditionalClarification === true
|
|
11363
|
+
? options.forceClarification === true
|
|
11364
|
+
: !hasAnswers && options.forceClarification === true;
|
|
11025
11365
|
updateState({
|
|
11026
11366
|
mode: "mission_planning",
|
|
11027
11367
|
activeMissionId: pending.id,
|
|
@@ -11035,7 +11375,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11035
11375
|
}, ctx);
|
|
11036
11376
|
queueWorkflowPrompt(
|
|
11037
11377
|
pi,
|
|
11038
|
-
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 }),
|
|
11039
11379
|
buildQueuedFailureRecovery(ctx, (reason) => recoverMissionTransientHandoffFailure(ctx, "planning", reason)),
|
|
11040
11380
|
);
|
|
11041
11381
|
}
|
|
@@ -11355,7 +11695,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11355
11695
|
lastSummary: `Running approved milestone ${milestone.id}.`,
|
|
11356
11696
|
});
|
|
11357
11697
|
checkpointMission(running, `Starting approved milestone ${milestone.id}: ${milestone.title}.`, "Execute current mission milestone, then run mission validation.", milestone.id);
|
|
11358
|
-
pi.setActiveTools(
|
|
11698
|
+
pi.setActiveTools(missionExecutionToolsFor(settings));
|
|
11359
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);
|
|
11360
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)}`);
|
|
11361
11701
|
if (!beginForcedSubagentPhase(ctx, "Execution", settings)) {
|
|
@@ -11485,8 +11825,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11485
11825
|
|
|
11486
11826
|
const syncSubagentPhaseUsage = (phase: SubagentPhase): void => {
|
|
11487
11827
|
const evidence = successfulSubagentEvidenceByPhase[phase];
|
|
11828
|
+
const distinctNames = new Set(Array.from(evidence.values()));
|
|
11488
11829
|
subagentUsageByPhase[phase] = evidence.size;
|
|
11489
|
-
subagentNamesByPhase[phase] =
|
|
11830
|
+
subagentNamesByPhase[phase] = distinctNames;
|
|
11490
11831
|
};
|
|
11491
11832
|
|
|
11492
11833
|
const recordSuccessfulSubagentEvidence = (phase: SubagentPhase, observations: SubagentNameObservation[]): void => {
|
|
@@ -11513,6 +11854,17 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11513
11854
|
return undefined;
|
|
11514
11855
|
};
|
|
11515
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
|
+
|
|
11516
11868
|
const resetSubagentPhaseUsage = (phase: SubagentPhase) => {
|
|
11517
11869
|
successfulSubagentEvidenceByPhase[phase].clear();
|
|
11518
11870
|
syncSubagentPhaseUsage(phase);
|
|
@@ -11533,34 +11885,128 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11533
11885
|
syncSubagentPhaseUsage(phase);
|
|
11534
11886
|
};
|
|
11535
11887
|
|
|
11536
|
-
|
|
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 => {
|
|
11537
11958
|
const policy = override?.policy ?? phasePolicy(settings, phase);
|
|
11538
|
-
if (policy
|
|
11539
|
-
const required = workerTargetForPolicy(
|
|
11959
|
+
if (!subagentPolicyRequiresRequiredEvidence(policy)) return false;
|
|
11960
|
+
const required = workerTargetForPolicy(policy, override?.workers ?? workerCount(settings, phase));
|
|
11540
11961
|
return snapshot.count >= required;
|
|
11541
11962
|
};
|
|
11542
11963
|
|
|
11543
|
-
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 => {
|
|
11544
11965
|
resetSubagentPhaseUsage(phase);
|
|
11545
11966
|
phasePreflightBlocks[phase] = undefined;
|
|
11546
|
-
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);
|
|
11547
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
|
+
}
|
|
11548
11993
|
showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, override?.label)}`);
|
|
11549
11994
|
return false;
|
|
11550
11995
|
};
|
|
11551
11996
|
|
|
11552
|
-
const forcedSubagentUsageSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, override?:
|
|
11997
|
+
const forcedSubagentUsageSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, override?: SubagentPhasePolicyOverride): string | undefined => {
|
|
11553
11998
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
11554
11999
|
const policy = override?.policy ?? phasePolicy(settings, phase);
|
|
11555
|
-
if (policy
|
|
11556
|
-
|
|
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));
|
|
11557
12003
|
const observed = subagentUsageByPhase[phase] ?? 0;
|
|
11558
12004
|
if (observed >= required) return undefined;
|
|
11559
12005
|
const label = override?.label ?? phase;
|
|
11560
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.`;
|
|
11561
12007
|
};
|
|
11562
12008
|
|
|
11563
|
-
const blockIfForcedSubagentsMissing = (ctx: ExtensionContext, phase: SubagentPhase, override?:
|
|
12009
|
+
const blockIfForcedSubagentsMissing = (ctx: ExtensionContext, phase: SubagentPhase, override?: SubagentPhasePolicyOverride): boolean => {
|
|
11564
12010
|
const reason = forcedSubagentUsageSatisfied(ctx, phase, override);
|
|
11565
12011
|
if (!reason) return false;
|
|
11566
12012
|
showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, override?.label)}`);
|
|
@@ -11638,19 +12084,36 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11638
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");
|
|
11639
12085
|
};
|
|
11640
12086
|
|
|
11641
|
-
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 }> => {
|
|
11642
12088
|
resetSubagentPhaseUsage(phase);
|
|
11643
12089
|
phasePreflightBlocks[phase] = undefined;
|
|
11644
12090
|
const policy = override?.policy ?? phasePolicy(settings, phase);
|
|
11645
12091
|
const workers = override?.workers ?? workerCount(settings, phase);
|
|
11646
12092
|
const label = override?.label ?? context.label ?? phase;
|
|
11647
|
-
|
|
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
|
+
}
|
|
11648
12101
|
const reason = forcedSubagentUnavailableReason(settings, phase, ctx.cwd, policy, workers);
|
|
11649
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
|
+
});
|
|
11650
12113
|
showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage(phase, reason, label)}`);
|
|
11651
12114
|
return { ok: false };
|
|
11652
12115
|
}
|
|
11653
|
-
const required = workerTargetForPolicy(
|
|
12116
|
+
const required = workerTargetForPolicy(policy, workers);
|
|
11654
12117
|
const effectiveAgents = listEffectiveAgents(ctx.cwd);
|
|
11655
12118
|
const agents = chooseForcedSubagents(phase, required, label, effectiveAgents);
|
|
11656
12119
|
const names = agents.map((agent) => agent.name);
|
|
@@ -11670,7 +12133,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11670
12133
|
return { ok: false };
|
|
11671
12134
|
}
|
|
11672
12135
|
}
|
|
11673
|
-
const useBackground = settings.subagents.allowBackgroundSubagents === true && phase
|
|
12136
|
+
const useBackground = settings.subagents.allowBackgroundSubagents === true && (phase === "Planning" || phase === "Review");
|
|
11674
12137
|
const tasks: WorkflowSubagentTask[] = names.map((agent, index) => ({ agent, task: forcedSubagentTaskText(phase, agent, index, required, context), cwd: ctx.cwd, background: useBackground, workflowPhase: phase }));
|
|
11675
12138
|
stopStartupVisual(ctx);
|
|
11676
12139
|
const startedAt = new Date().toISOString();
|
|
@@ -11727,6 +12190,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11727
12190
|
}
|
|
11728
12191
|
if (isBackground) {
|
|
11729
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
|
+
});
|
|
11730
12203
|
return { ok: true };
|
|
11731
12204
|
}
|
|
11732
12205
|
const succeeded = run.results.filter((result) => result.exitCode === 0);
|
|
@@ -11742,6 +12215,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11742
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}`);
|
|
11743
12216
|
return { ok: false, block };
|
|
11744
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
|
+
});
|
|
11745
12228
|
return { ok: true, block };
|
|
11746
12229
|
};
|
|
11747
12230
|
|
|
@@ -11749,12 +12232,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11749
12232
|
policy: standardPhasePolicy(settings, phase),
|
|
11750
12233
|
workers: standardWorkerCount(settings, phase),
|
|
11751
12234
|
label: `Standard ${phase}`,
|
|
12235
|
+
task: state.task ?? state.originalTask ?? "Standard Mode task",
|
|
12236
|
+
kind: state.standardWorkKind,
|
|
11752
12237
|
});
|
|
11753
12238
|
|
|
11754
12239
|
const standardForcedSubagentSatisfied = (phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, task: string): boolean => {
|
|
11755
12240
|
const override = standardForcedSubagentOverride(settings, phase);
|
|
11756
|
-
if (override.policy
|
|
11757
|
-
|
|
12241
|
+
if (!subagentPolicyRequiresRequiredEvidence(override.policy)) return true;
|
|
12242
|
+
if (trivialSubagentSkipReason(phase, task, override.kind)) return true;
|
|
12243
|
+
const required = workerTargetForPolicy(override.policy, override.workers);
|
|
11758
12244
|
const record = state.standardSubagentPreflight?.[phase];
|
|
11759
12245
|
if (record?.task === task && record.observed >= required) {
|
|
11760
12246
|
restoreSuccessfulSubagentEvidence(phase, record.observed, new Set(record.agents), `standard:${phase}:${task}`);
|
|
@@ -11763,12 +12249,48 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11763
12249
|
return false;
|
|
11764
12250
|
};
|
|
11765
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
|
+
|
|
11766
12287
|
const planForcedSubagentPreflightReconcile = (ctx: ExtensionContext, phase: SubagentPhase): void => {
|
|
11767
12288
|
const block = phasePreflightBlocks[phase];
|
|
11768
12289
|
if (!block) return;
|
|
11769
12290
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
11770
12291
|
const policy = phasePolicy(settings, phase);
|
|
11771
|
-
if (policy
|
|
12292
|
+
if (!subagentPolicyRequiresRequiredEvidence(policy)) return;
|
|
12293
|
+
if (/^Policy decision:\s*exempt_trivial\b/im.test(block)) return;
|
|
11772
12294
|
const observedMatch = block.match(/Observed workers: (\d+)/);
|
|
11773
12295
|
if (!observedMatch) return;
|
|
11774
12296
|
const observed = parseInt(observedMatch[1], 10);
|
|
@@ -11780,9 +12302,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
11780
12302
|
};
|
|
11781
12303
|
|
|
11782
12304
|
const planForcedSubagentPreflightSatisfied = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>): boolean => {
|
|
11783
|
-
|
|
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;
|
|
11784
12308
|
planForcedSubagentPreflightReconcile(ctx, phase);
|
|
11785
|
-
const required = workerTargetForPolicy(
|
|
12309
|
+
const required = workerTargetForPolicy(policy, workerCount(settings, phase));
|
|
11786
12310
|
return (subagentUsageByPhase[phase] ?? 0) >= required;
|
|
11787
12311
|
};
|
|
11788
12312
|
|
|
@@ -12047,6 +12571,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12047
12571
|
standardPhaseByToolCall.delete(toolCallId);
|
|
12048
12572
|
const phaseOverride = parseSubagentWorkflowPhase(args.workflowPhase);
|
|
12049
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
|
+
}
|
|
12050
12580
|
const observations = extractSubagentObservations(args, `top:${toolCallId}`);
|
|
12051
12581
|
registerSubagentObservations(toolCallId, observations, ctx, observations.length === 0);
|
|
12052
12582
|
if (observations.length === 0) renderWorkflowSubagentActivity(ctx);
|
|
@@ -12273,7 +12803,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12273
12803
|
repairRetryState: reviewerRepair ? state.repairRetryState : undefined,
|
|
12274
12804
|
planProgress: initialPlanProgress,
|
|
12275
12805
|
}, ctx);
|
|
12276
|
-
if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings)) {
|
|
12806
|
+
if (!options.planningPreflightSatisfied && !beginForcedSubagentPhase(ctx, "Planning", settings, { task })) {
|
|
12277
12807
|
pi.setActiveTools(planToolsFor(settings));
|
|
12278
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);
|
|
12279
12809
|
return;
|
|
@@ -12382,19 +12912,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12382
12912
|
}
|
|
12383
12913
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
12384
12914
|
armWorkflowToolsForPhase(ctx, "Execution", "beginExecution prompt queued");
|
|
12385
|
-
|
|
12386
|
-
if (
|
|
12387
|
-
pi.setActiveTools(executionToolsFor(settings));
|
|
12388
|
-
missingExecutionTools = missingRequiredPlanExecutionTools(pi.getActiveTools(), settings);
|
|
12389
|
-
traceWorkflowTracking(ctx, "defensive-tool-arm", { reason: "required execution tools missing before executor queue", missingExecutionTools });
|
|
12390
|
-
}
|
|
12391
|
-
if (missingExecutionTools.length) {
|
|
12392
|
-
const reason = `Required execution tools are unavailable after execution tool rearm: ${missingExecutionTools.join(", ")}. Execution was not queued.`;
|
|
12393
|
-
const revertMode = previousMode === "reviewed" ? "reviewed" as const : "plan_approved" as const;
|
|
12394
|
-
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);
|
|
12395
|
-
pi.setActiveTools(webSafePlanTools(PLAN_TOOLS));
|
|
12396
|
-
recordWorkflowInternalEvent(ctx, `Plan execution blocked because required execution tools were unavailable after rearm: ${missingExecutionTools.join(", ")}.`);
|
|
12397
|
-
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) {
|
|
12398
12917
|
return false;
|
|
12399
12918
|
}
|
|
12400
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" })));
|
|
@@ -12476,6 +12995,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12476
12995
|
return true;
|
|
12477
12996
|
}
|
|
12478
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
|
+
}
|
|
12479
13013
|
const route = await applyModelForRole(pi, ctx, "validator", { requireEnabled: false, cwd: ctx.cwd });
|
|
12480
13014
|
if (!route) {
|
|
12481
13015
|
const reason = "Plan validator model is disabled, unavailable, or not configured.";
|
|
@@ -12656,6 +13190,20 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12656
13190
|
async function startWorkflowRepair(ctx: ExtensionContext, source: "auto" | "user" = "auto") {
|
|
12657
13191
|
if (!state.approvedPlan) return show(pi, "# Plan Repair Refused\n\nNo approved plan exists.");
|
|
12658
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
|
+
}
|
|
12659
13207
|
if (state.concreteRepairableIssue === false) {
|
|
12660
13208
|
const reason = "No concrete repairable issue — manual verification only.";
|
|
12661
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);
|
|
@@ -12749,14 +13297,43 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12749
13297
|
}
|
|
12750
13298
|
|
|
12751
13299
|
async function handleWorkflowRetryCommand(ctx: ExtensionContext, action: "retry" | "repair" | "revalidate") {
|
|
13300
|
+
const settings = loadWorkflowSettings(ctx.cwd);
|
|
12752
13301
|
if (action === "revalidate") {
|
|
12753
13302
|
if (!state.approvedPlan && (state.mode === "idle" || state.mode === "cancelled" || state.mode === "awaiting_plan_input")) {
|
|
12754
13303
|
const latest = latestRecoverablePlan();
|
|
12755
13304
|
if (latest) recoverPlanFromHistory(ctx, latest);
|
|
12756
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
|
+
}
|
|
12757
13320
|
return beginValidation(ctx, true, true);
|
|
12758
13321
|
}
|
|
12759
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
|
+
}
|
|
12760
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.`);
|
|
12761
13338
|
const hasValidationFailure = Boolean(state.lastValidationFailure || state.validationReport || state.validationVerdict === "FAIL" || state.validationVerdict === "PARTIAL PASS");
|
|
12762
13339
|
if (!hasValidationFailure) return show(pi, `# Plan ${action}\n\nCannot ${action}: no validation failure is recorded.`);
|
|
@@ -12970,6 +13547,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
12970
13547
|
async function resumeInterruptedPlanValidation(ctx: ExtensionContext, title: string): Promise<boolean> {
|
|
12971
13548
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
12972
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
|
+
}
|
|
12973
13565
|
if (!planValidationGateActive(settings)) {
|
|
12974
13566
|
show(pi, `# ${title}\n\nRecovered interrupted Plan validation runtime. Validation is disabled or unavailable for this workflow.`);
|
|
12975
13567
|
await completePlanWithoutValidation(ctx, "Validation skipped: validation gate is disabled for this workflow.");
|
|
@@ -13027,6 +13619,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13027
13619
|
restorePlanRuntimeSnapshot(ctx, snapshot);
|
|
13028
13620
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
13029
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
|
+
}
|
|
13030
13637
|
if (state.mode === "executed" && validationAvailable) {
|
|
13031
13638
|
show(pi, `# ${title}\n\nRecovered executed Plan runtime from session history. Continuing to validation.`);
|
|
13032
13639
|
await beginValidation(ctx, true, false, "immediate");
|
|
@@ -13105,7 +13712,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13105
13712
|
}, ctx);
|
|
13106
13713
|
}
|
|
13107
13714
|
|
|
13108
|
-
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
|
+
}
|
|
13109
13719
|
if (current.mode === "idle" || current.mode === "cancelled" || current.mode === "awaiting_plan_input") {
|
|
13110
13720
|
const latest = latestRecoverablePlan();
|
|
13111
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.`;
|
|
@@ -13128,7 +13738,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13128
13738
|
return `No safe automatic continuation is available for state: ${current.mode}.`;
|
|
13129
13739
|
}
|
|
13130
13740
|
|
|
13131
|
-
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 } {
|
|
13132
13746
|
const candidates = listWorkflowPlans()
|
|
13133
13747
|
.filter((plan) => plan.finalPlan?.trim() && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised"))
|
|
13134
13748
|
.sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
|
|
@@ -13137,10 +13751,17 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13137
13751
|
const add = (plan?: SavedWorkflowPlan) => {
|
|
13138
13752
|
if (plan?.finalPlan?.trim() && (plan.approvalStatus === "approved" || plan.approvalStatus === "revised") && !ordered.some((p) => p.id === plan.id)) ordered.push(plan);
|
|
13139
13753
|
};
|
|
13140
|
-
|
|
13141
|
-
|
|
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);
|
|
13142
13762
|
if (state.approvedPlan?.trim() && !isMissionWorkflowMode(state)) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
|
|
13143
|
-
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.` };
|
|
13144
13765
|
return { candidates: [], source: "none", reason: "No active or recoverable approved Plan Mode workflow found." };
|
|
13145
13766
|
}
|
|
13146
13767
|
|
|
@@ -13194,7 +13815,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13194
13815
|
}
|
|
13195
13816
|
|
|
13196
13817
|
function planResumeChoiceLabel(plan: SavedWorkflowPlan): string {
|
|
13197
|
-
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, " ")}`;
|
|
13198
13819
|
}
|
|
13199
13820
|
|
|
13200
13821
|
function planResumeCanContinueCurrent(current: WorkflowState): boolean {
|
|
@@ -13222,12 +13843,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13222
13843
|
return selectable.find((plan) => plan.id === id);
|
|
13223
13844
|
}
|
|
13224
13845
|
|
|
13225
|
-
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> {
|
|
13226
13847
|
if (resolved.source === "active") return undefined;
|
|
13227
|
-
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;
|
|
13228
13850
|
const title = action === "resume" ? "Plan Resume" : "Plan Continue";
|
|
13229
13851
|
const choices = resolved.candidates.map(planResumeChoiceLabel);
|
|
13230
|
-
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"
|
|
13231
13855
|
? "Choose which plan to use."
|
|
13232
13856
|
: "I found more than one saved plan. Choose which one to continue.";
|
|
13233
13857
|
show(pi, `# ${title}\n\n${intro}\n\n${choices.map((choice, i) => `${i + 1}. ${choice}`).join("\n")}`);
|
|
@@ -13242,10 +13866,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13242
13866
|
const validationAvailable = planValidationGateActive(settings);
|
|
13243
13867
|
const currentAdvancedSnapshot = findAdvancedPlanRuntimeSnapshotForCurrentState(ctx);
|
|
13244
13868
|
if (currentAdvancedSnapshot) restorePlanRuntimeSnapshotForResume(ctx, currentAdvancedSnapshot);
|
|
13245
|
-
const resolved = resolveActivePlanForResume();
|
|
13246
|
-
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 ?? ""));
|
|
13247
13871
|
const activeRuntimeStale = planActiveRuntimeIsRecoverablyStale(ctx);
|
|
13248
|
-
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)}`);
|
|
13249
13873
|
let selected = resolved.plan;
|
|
13250
13874
|
if (resolved.source === "history" && selected) {
|
|
13251
13875
|
const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, selected);
|
|
@@ -13265,10 +13889,16 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13265
13889
|
// Secondary surface for a saved plan chosen through "Choose Another Plan".
|
|
13266
13890
|
const showSelectedSavedPlanResumeMenu = async (plan: SavedWorkflowPlan): Promise<void> => {
|
|
13267
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);
|
|
13268
13898
|
const summary = `# Selected Plan\n\n${recoverablePlanDetails(plan)}\n\nStatus: ${plan.approvalStatus}`;
|
|
13269
13899
|
show(pi, `${summary}\n\nChoose what to do.`);
|
|
13270
13900
|
if (!ctx.hasUI) return;
|
|
13271
|
-
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"];
|
|
13272
13902
|
const choice = await ctx.ui.select("Selected Plan", choices);
|
|
13273
13903
|
if (choice === "Continue Plan") {
|
|
13274
13904
|
const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, plan);
|
|
@@ -13291,17 +13921,18 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13291
13921
|
// Primary /plan resume surface: restore state, then let the user choose.
|
|
13292
13922
|
const showActivePlanResumeActionMenu = async (currentPlan?: SavedWorkflowPlan): Promise<void> => {
|
|
13293
13923
|
const details = currentPlan ? `\n\n${recoverablePlanDetails(currentPlan)}` : "";
|
|
13294
|
-
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)}`;
|
|
13295
13925
|
show(pi, `${summary}\n\nChoose what to do.`);
|
|
13296
13926
|
if (!ctx.hasUI) return;
|
|
13297
13927
|
const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
|
|
13298
13928
|
const canContinueCurrent = planResumeCanContinueCurrent(state);
|
|
13299
13929
|
const reviewRepairAvailable = planReviewRepairRecoveryAvailable(state);
|
|
13300
|
-
const
|
|
13930
|
+
const validationBoundaryReached = planValidationBoundaryReached(state, settings);
|
|
13931
|
+
const validationRepairAvailable = validationBoundaryReached && Boolean(state.lastValidationFailure || state.validationReport || state.validationVerdict === "FAIL" || state.validationVerdict === "PARTIAL PASS");
|
|
13301
13932
|
const choice = await ctx.ui.select("Plan Resume", [
|
|
13302
13933
|
...(reviewRepairAvailable ? ["Review Repair / Recover Notes"] : []),
|
|
13303
13934
|
...(state.approvedPlan && validationRepairAvailable ? ["Repair / Retry"] : []),
|
|
13304
|
-
...(state.approvedPlan ? ["Revalidate"] : []),
|
|
13935
|
+
...(state.approvedPlan && validationBoundaryReached ? ["Revalidate"] : []),
|
|
13305
13936
|
...(canContinueCurrent ? ["Continue Current Plan"] : []),
|
|
13306
13937
|
...(hasAlternatives ? ["Choose Another Plan"] : []),
|
|
13307
13938
|
"List Plan Status", "List Plans", "Cancel",
|
|
@@ -13360,7 +13991,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13360
13991
|
const status = renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd);
|
|
13361
13992
|
|
|
13362
13993
|
if (!state.approvedPlan && (state.mode === "idle" || state.mode === "cancelled" || state.mode === "awaiting_plan_input")) {
|
|
13363
|
-
const resolved = resolveActivePlanForResume();
|
|
13994
|
+
const resolved = resolveActivePlanForResume(ctx);
|
|
13364
13995
|
const selected = await choosePlanForContinueWhenNoCurrentPlan(ctx, resolved, "continue");
|
|
13365
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}`);
|
|
13366
13997
|
const advancedSnapshot = findAdvancedPlanRuntimeSnapshot(ctx, selected);
|
|
@@ -13431,6 +14062,21 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13431
14062
|
await beginExecution(ctx, true);
|
|
13432
14063
|
return;
|
|
13433
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
|
+
}
|
|
13434
14080
|
if (state.mode === "executed") {
|
|
13435
14081
|
if (validationAvailable) {
|
|
13436
14082
|
show(pi, `# ${title}\n\nExecution is complete. Continuing to validation.`);
|
|
@@ -13607,7 +14253,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13607
14253
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
13608
14254
|
clearTypedHandoff(ctx, "Mission final repair");
|
|
13609
14255
|
const failure = mission.lastFinalValidationFailure || mission.lastValidationFailure || mission.lastBlockReason || "No final validation failure details recorded.";
|
|
13610
|
-
const unsafe = validationFailureRequiresApproval(failure, settings
|
|
14256
|
+
const unsafe = validationFailureRequiresApproval(failure, settings, {
|
|
14257
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
14258
|
+
evidenceGap: mission.evidenceGap,
|
|
14259
|
+
});
|
|
13611
14260
|
if (unsafe) {
|
|
13612
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}` });
|
|
13613
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 });
|
|
@@ -13646,7 +14295,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13646
14295
|
repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair final validation failure, then revalidate." }),
|
|
13647
14296
|
});
|
|
13648
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 });
|
|
13649
|
-
pi.setActiveTools(
|
|
14298
|
+
pi.setActiveTools(missionRepairToolsFor(settings));
|
|
13650
14299
|
updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
13651
14300
|
saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
|
|
13652
14301
|
if (source !== "auto") show(pi, `# Mission Final Repair Started\n\nRetry: ${retry} of ${maxRetries}\n\n${renderMissionProgress(repairing, settings)}`);
|
|
@@ -13671,7 +14320,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
13671
14320
|
return show(pi, `# Mission Runtime Budget Reached\n\n${runtimeBlocked}\n\n${renderMissionStatus(activeMission ?? paused)}`);
|
|
13672
14321
|
}
|
|
13673
14322
|
const failure = mission.lastValidationFailure || mission.lastBlockReason || "No validation failure details recorded.";
|
|
13674
|
-
const unsafe = validationFailureRequiresApproval(failure, settings
|
|
14323
|
+
const unsafe = validationFailureRequiresApproval(failure, settings, {
|
|
14324
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
14325
|
+
evidenceGap: mission.evidenceGap,
|
|
14326
|
+
});
|
|
13675
14327
|
if (unsafe) {
|
|
13676
14328
|
const milestone = mission.milestones[mission.currentMilestoneIndex];
|
|
13677
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}` });
|
|
@@ -13736,7 +14388,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
13736
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." }),
|
|
13737
14389
|
});
|
|
13738
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 });
|
|
13739
|
-
pi.setActiveTools(
|
|
14391
|
+
pi.setActiveTools(missionRepairToolsFor(settings));
|
|
13740
14392
|
updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
13741
14393
|
saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
|
|
13742
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)}`);
|
|
@@ -13834,7 +14486,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
13834
14486
|
const retry = missionFinalValidationRetryCount(mission);
|
|
13835
14487
|
const maxRetries = missionMaxFinalValidationRetries(mission, settings);
|
|
13836
14488
|
const failure = `Final mission validation ${verdict}. ${compact(validationText, 1200)}`;
|
|
13837
|
-
const unsafe = validationFailureRequiresApproval(failure, settings
|
|
14489
|
+
const unsafe = validationFailureRequiresApproval(failure, settings, {
|
|
14490
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
14491
|
+
evidenceGap: mission.evidenceGap,
|
|
14492
|
+
});
|
|
13838
14493
|
const nextRetry = retry + 1;
|
|
13839
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}.` });
|
|
13840
14495
|
checkpointMission(failed, `Final mission validation failed. Retry ${retry}/${maxRetries}. ${compact(validationText, 500)}`, "Evaluate safe final repair retry before completion.", undefined, { validationResult: verdict });
|
|
@@ -13917,7 +14572,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
13917
14572
|
lastSummary: `Validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`,
|
|
13918
14573
|
});
|
|
13919
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 });
|
|
13920
|
-
const unsafe = validationFailureRequiresApproval(failure, settings
|
|
14575
|
+
const unsafe = validationFailureRequiresApproval(failure, settings, {
|
|
14576
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
14577
|
+
evidenceGap: mission.evidenceGap,
|
|
14578
|
+
});
|
|
13921
14579
|
const shouldBlock = mission.autonomy === "manual"
|
|
13922
14580
|
|| settings.missions.autoRepairValidationFailures === false
|
|
13923
14581
|
|| settings.missions.pauseAfterValidationFailure === true
|
|
@@ -13993,7 +14651,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
13993
14651
|
const answerSummary = formatAnswersForPlanner(questions, answers);
|
|
13994
14652
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
13995
14653
|
const priorPlanning = snapshotSubagentPhaseUsage("Planning");
|
|
13996
|
-
if (!beginForcedSubagentPhase(ctx, "Planning", settings)) {
|
|
14654
|
+
if (!beginForcedSubagentPhase(ctx, "Planning", settings, { task: state.task ?? "Plan with clarification answers" })) {
|
|
13997
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);
|
|
13998
14656
|
return;
|
|
13999
14657
|
}
|
|
@@ -14066,7 +14724,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
14066
14724
|
}
|
|
14067
14725
|
const questions = mission.clarificationQuestions?.length ? mission.clarificationQuestions : (state.clarifyingQuestions ?? []);
|
|
14068
14726
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
14069
|
-
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 };
|
|
14070
14728
|
const priorPlanning = snapshotSubagentPhaseUsage("Planning");
|
|
14071
14729
|
if (!beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
|
|
14072
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." });
|
|
@@ -14173,15 +14831,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
14173
14831
|
} else if (choice === "Revise Mission Plan") {
|
|
14174
14832
|
await beginMissionPlanning(ctx, { ...mission, clarificationAnswers: mission.clarificationAnswers ?? [] });
|
|
14175
14833
|
} else if (choice === "Answer More Clarifications") {
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
const next = saveActiveMission({ ...mission, status: "draft", lastSummary: "Additional mission clarification requested before approval.", nextAction: "Generate dynamic mission clarification questions before approval." });
|
|
14181
|
-
checkpointMission(next, "Additional mission clarification requested before approval.", "Generate dynamic mission-specific clarification questions.");
|
|
14182
|
-
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
14183
|
-
await beginMissionPlanning(ctx, next, { forceClarification: true, forceReason: "user requested additional mission clarification before approval" });
|
|
14184
|
-
}
|
|
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" });
|
|
14185
14838
|
} else if (choice === "Cancel Mission") {
|
|
14186
14839
|
const stopped = saveActiveMission({ ...mission, status: "stopped", lastSummary: "Mission cancelled before approval." });
|
|
14187
14840
|
checkpointMission(stopped, "Mission cancelled before approval.", "No further mission action queued.");
|
|
@@ -14801,7 +15454,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
14801
15454
|
else if (choice === "Parallel Agent Settings") await showParallelismSettingsMenu(ctx);
|
|
14802
15455
|
else if (choice === "Background Sub-agents") {
|
|
14803
15456
|
const enabled = await chooseBool(ctx, "subagents.allowBackgroundSubagents?");
|
|
14804
|
-
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"); }
|
|
14805
15458
|
} else if (choice === "Activity Indicator") {
|
|
14806
15459
|
const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
|
|
14807
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); }
|
|
@@ -16145,30 +16798,97 @@ Pi Version: v${VERSION}
|
|
|
16145
16798
|
if (process.env.PI_SUBAGENT_WORKER === "1") return;
|
|
16146
16799
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
16147
16800
|
let phase = phaseForWorkflowMode(state.mode);
|
|
16801
|
+
const command = event.toolName === "bash" ? String((event.input as { command?: unknown }).command ?? "") : undefined;
|
|
16148
16802
|
if (state.mode === "standard") {
|
|
16149
16803
|
if (event.toolName === "bash") {
|
|
16150
|
-
const command = String((event.input as { command?: unknown }).command ?? "");
|
|
16151
16804
|
if (standardForcedSubagentSafeBash(command)) return;
|
|
16152
16805
|
}
|
|
16153
16806
|
if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") phase = "Execution";
|
|
16807
|
+
else if (event.toolName === "subagent" && isRecord(event.input)) phase = parseSubagentWorkflowPhase(event.input.workflowPhase) ?? phase;
|
|
16154
16808
|
}
|
|
16155
|
-
|
|
16156
|
-
if (
|
|
16809
|
+
if (event.toolName === WORKFLOW_EXECUTION_RESULT_TOOL || event.toolName === MISSION_MILESTONE_RESULT_TOOL) phase = "Execution";
|
|
16810
|
+
if (event.toolName === WORKFLOW_REPAIR_RESULT_TOOL) phase = "Repair";
|
|
16811
|
+
if (event.toolName === WORKFLOW_VALIDATION_RESULT_TOOL) phase = "Validation";
|
|
16812
|
+
if (event.toolName === WORKFLOW_REVIEW_RESULT_TOOL) phase = "Review";
|
|
16813
|
+
if (!phase) return;
|
|
16157
16814
|
const policy = state.mode === "standard" ? standardPhasePolicy(settings, phase) : phasePolicy(settings, phase);
|
|
16158
|
-
if (policy
|
|
16815
|
+
if (subagentPolicyNeedsInternalDecision(policy)) {
|
|
16816
|
+
if (event.toolName === "subagent") {
|
|
16817
|
+
const workers = state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase);
|
|
16818
|
+
const target = advisoryWorkerTargetForPolicy(policy, workers);
|
|
16819
|
+
const observed = subagentUsageByPhase[phase] ?? 0;
|
|
16820
|
+
const task = state.task ?? state.originalTask;
|
|
16821
|
+
recordSubagentPolicyDecision(ctx, {
|
|
16822
|
+
phase,
|
|
16823
|
+
policy,
|
|
16824
|
+
outcome: "auto_delegate",
|
|
16825
|
+
reason: "visible parent sub-agent call started after advisory consideration",
|
|
16826
|
+
task,
|
|
16827
|
+
required: target,
|
|
16828
|
+
observed,
|
|
16829
|
+
background: false,
|
|
16830
|
+
});
|
|
16831
|
+
}
|
|
16832
|
+
return;
|
|
16833
|
+
}
|
|
16834
|
+
if (!subagentPolicyRequiresRequiredEvidence(policy)) return;
|
|
16159
16835
|
if (state.mode === "standard" && standardForcedSubagentSatisfied(phase, settings, state.task ?? state.originalTask ?? "Standard Mode task")) return;
|
|
16160
|
-
const required = workerTargetForPolicy(
|
|
16836
|
+
const required = workerTargetForPolicy(policy, state.mode === "standard" ? standardWorkerCount(settings, phase) : workerCount(settings, phase));
|
|
16161
16837
|
const observed = subagentUsageByPhase[phase] ?? 0;
|
|
16162
16838
|
if (observed >= required) return;
|
|
16163
|
-
if (
|
|
16164
|
-
|
|
16165
|
-
|
|
16839
|
+
if (phase === "Validation" && event.toolName === WORKFLOW_VALIDATION_RESULT_TOOL) {
|
|
16840
|
+
recordSubagentPolicyDecision(ctx, {
|
|
16841
|
+
phase,
|
|
16842
|
+
policy,
|
|
16843
|
+
outcome: "allow_user_explicit_finalization",
|
|
16844
|
+
reason: "typed validation verdict is finalization; forced validation workers must be enforced before validation work, not after verdict",
|
|
16845
|
+
task: state.task ?? state.originalTask,
|
|
16846
|
+
required,
|
|
16847
|
+
observed,
|
|
16848
|
+
background: false,
|
|
16849
|
+
});
|
|
16850
|
+
return;
|
|
16166
16851
|
}
|
|
16167
|
-
if (event.toolName === "
|
|
16168
|
-
|
|
16169
|
-
const
|
|
16170
|
-
return { block: true, reason:
|
|
16852
|
+
if (event.toolName === "subagent") {
|
|
16853
|
+
const label = forcedSubagentGuardLabel(phase);
|
|
16854
|
+
const block = forcedSubagentVisibleCallBlock(ctx, phase, required, observed, event.input, label);
|
|
16855
|
+
if (block) return { block: true, reason: block };
|
|
16856
|
+
recordSubagentPolicyDecision(ctx, {
|
|
16857
|
+
phase,
|
|
16858
|
+
policy,
|
|
16859
|
+
outcome: "required",
|
|
16860
|
+
reason: "visible sub-agent call requested enough forced worker task entries for the remaining requirement",
|
|
16861
|
+
task: state.task ?? state.originalTask,
|
|
16862
|
+
required,
|
|
16863
|
+
observed,
|
|
16864
|
+
background: false,
|
|
16865
|
+
});
|
|
16866
|
+
return;
|
|
16867
|
+
}
|
|
16868
|
+
const actionDecision = forcedSubagentActionDecision({
|
|
16869
|
+
phase,
|
|
16870
|
+
policy,
|
|
16871
|
+
task: state.task ?? state.originalTask,
|
|
16872
|
+
kind: state.standardWorkKind,
|
|
16873
|
+
toolName: event.toolName,
|
|
16874
|
+
command,
|
|
16875
|
+
});
|
|
16876
|
+
if (actionDecision.allowBeforeEvidence) {
|
|
16877
|
+
recordSubagentPolicyDecision(ctx, {
|
|
16878
|
+
phase,
|
|
16879
|
+
policy,
|
|
16880
|
+
outcome: actionDecision.outcome,
|
|
16881
|
+
reason: actionDecision.reason,
|
|
16882
|
+
task: state.task ?? state.originalTask,
|
|
16883
|
+
required,
|
|
16884
|
+
observed,
|
|
16885
|
+
background: false,
|
|
16886
|
+
});
|
|
16887
|
+
return;
|
|
16171
16888
|
}
|
|
16889
|
+
const label = forcedSubagentGuardLabel(phase);
|
|
16890
|
+
const noun = phase === "Planning" ? "planning" : phase === "Repair" ? "repair" : phase === "Review" ? "review" : phase === "Validation" ? "validation" : "execution";
|
|
16891
|
+
return { block: true, reason: `${event.toolName} blocked — forced ${noun} workers required (${observed}/${required}) for ${label}` };
|
|
16172
16892
|
});
|
|
16173
16893
|
|
|
16174
16894
|
function workflowAutoCompactionModeEligible(mode: string): boolean {
|
|
@@ -16377,7 +17097,7 @@ Pi Version: v${VERSION}
|
|
|
16377
17097
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
16378
17098
|
const override = standardForcedSubagentOverride(settings, phase);
|
|
16379
17099
|
const task = state.task ?? state.originalTask ?? "Standard Mode task";
|
|
16380
|
-
if (override.policy
|
|
17100
|
+
if (subagentPolicyRequiresRequiredEvidence(override.policy)) rememberStandardSubagentPreflight(ctx, phase, task, workerTargetForPolicy(override.policy, override.workers));
|
|
16381
17101
|
}
|
|
16382
17102
|
}
|
|
16383
17103
|
finishSubagentActivity(event.toolCallId, Boolean(event.isError), ctx);
|
|
@@ -16635,7 +17355,7 @@ Pi Version: v${VERSION}
|
|
|
16635
17355
|
const shouldRespectDraftPlanningRail = (action === "continue" || action === "next") && currentMission?.status === "draft" && currentMission.milestones.length === 0;
|
|
16636
17356
|
const needsMissionResolution = action === "retry" || action === "repair" || action === "revalidate" || ((action === "continue" || action === "next") && !shouldRespectDraftPlanningRail);
|
|
16637
17357
|
const resolvedMission = needsMissionResolution
|
|
16638
|
-
? await chooseResumeMission(ctx, resolveActiveMissionForResume(), action as "continue" | "next" | "retry" | "repair" | "revalidate")
|
|
17358
|
+
? await chooseResumeMission(ctx, resolveActiveMissionForResume(ctx), action as "continue" | "next" | "retry" | "repair" | "revalidate")
|
|
16639
17359
|
: undefined;
|
|
16640
17360
|
const mission = resolvedMission ?? currentMission;
|
|
16641
17361
|
if (!mission) return show(pi, `# Mission ${action}\n\nNo mission selected. ${action} cancelled.`);
|
|
@@ -17822,6 +18542,10 @@ Public workflow commands:
|
|
|
17822
18542
|
if (route) updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), [standardRole]: modelLabel(route) } }, ctx);
|
|
17823
18543
|
}
|
|
17824
18544
|
pi.setActiveTools(standardToolsFor(settings));
|
|
18545
|
+
const standardSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "standard before_agent_start", "standard");
|
|
18546
|
+
if (standardSurfaceBlock) {
|
|
18547
|
+
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.` };
|
|
18548
|
+
}
|
|
17825
18549
|
standardTodoCreatedThisTurn = false;
|
|
17826
18550
|
const rawPrompt = event.prompt?.trim() ?? "";
|
|
17827
18551
|
const answeredClarification = Boolean(state.standardClarificationAnswer?.trim() && state.standardClarificationTask?.trim() && !state.standardClarificationPending);
|
|
@@ -17840,7 +18564,8 @@ Public workflow commands:
|
|
|
17840
18564
|
return { systemPrompt: `${event.systemPrompt}\n\n${standardClarificationRequestPrompt(task, settings, gate.reason)}` };
|
|
17841
18565
|
}
|
|
17842
18566
|
}
|
|
17843
|
-
|
|
18567
|
+
const standardTodoMustInitialize = standardRequiredTodoMissing(state, settings, task);
|
|
18568
|
+
if (task && !standardTodoMustInitialize && (standardWork.phase === "Execution" || standardWork.phase === "Repair")) {
|
|
17844
18569
|
const override = standardForcedSubagentOverride(settings, standardWork.phase);
|
|
17845
18570
|
if (!beginForcedSubagentPhase(ctx, standardWork.phase, settings, override)) {
|
|
17846
18571
|
pi.setActiveTools(clarificationToolsFor(settings, false));
|
|
@@ -17859,7 +18584,7 @@ Public workflow commands:
|
|
|
17859
18584
|
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.` };
|
|
17860
18585
|
const task = event.prompt || "Create an implementation plan for the user's requested task.";
|
|
17861
18586
|
const gate = clarificationGate(task, settings, false, false);
|
|
17862
|
-
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.` };
|
|
18587
|
+
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.` };
|
|
17863
18588
|
activeMission = undefined;
|
|
17864
18589
|
const activePlanId = createWorkflowPlanId(ctx.cwd);
|
|
17865
18590
|
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);
|
|
@@ -17907,6 +18632,10 @@ Public workflow commands:
|
|
|
17907
18632
|
if (mission?.currentStep === "reviewer") {
|
|
17908
18633
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
17909
18634
|
armWorkflowToolsForPhase(ctx, "Review", "mission review before_agent_start");
|
|
18635
|
+
const reviewSurfaceBlock = verifyWorkflowToolSurface(ctx, "Review", "mission review before_agent_start");
|
|
18636
|
+
if (reviewSurfaceBlock) {
|
|
18637
|
+
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.` };
|
|
18638
|
+
}
|
|
17910
18639
|
const surfaceProblem = missionReviewRequiredToolSurfaceProblem(pi.getActiveTools());
|
|
17911
18640
|
if (surfaceProblem) {
|
|
17912
18641
|
blockMissionReviewToolSurface(ctx, mission, surfaceProblem, "mission review before_agent_start", false);
|
|
@@ -17934,7 +18663,14 @@ Public workflow commands:
|
|
|
17934
18663
|
const prompt = await missionActiveGatePrompt(ctx, mission, persistedGateMode, event.systemPrompt);
|
|
17935
18664
|
if (prompt) return prompt;
|
|
17936
18665
|
}
|
|
17937
|
-
|
|
18666
|
+
if (state.reviewHandoffSuppression?.kind === "mission_typed_review_to_approval") {
|
|
18667
|
+
clearReviewHandoffSuppression(ctx, "mission execution turn started after typed Mission review handoff");
|
|
18668
|
+
}
|
|
18669
|
+
pi.setActiveTools(missionExecutionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18670
|
+
const missionExecutionSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "mission execution before_agent_start");
|
|
18671
|
+
if (missionExecutionSurfaceBlock) {
|
|
18672
|
+
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.` };
|
|
18673
|
+
}
|
|
17938
18674
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionRuntimePrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
|
|
17939
18675
|
}
|
|
17940
18676
|
|
|
@@ -17942,7 +18678,11 @@ Public workflow commands:
|
|
|
17942
18678
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
|
|
17943
18679
|
const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
|
|
17944
18680
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
17945
|
-
pi.setActiveTools(
|
|
18681
|
+
pi.setActiveTools(missionRepairToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18682
|
+
const missionRepairSurfaceBlock = verifyWorkflowToolSurface(ctx, "Repair", "mission repair before_agent_start");
|
|
18683
|
+
if (missionRepairSurfaceBlock) {
|
|
18684
|
+
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.` };
|
|
18685
|
+
}
|
|
17946
18686
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionRepairPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
|
|
17947
18687
|
}
|
|
17948
18688
|
|
|
@@ -17950,6 +18690,10 @@ Public workflow commands:
|
|
|
17950
18690
|
const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
|
|
17951
18691
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
17952
18692
|
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18693
|
+
const missionValidationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission validation before_agent_start");
|
|
18694
|
+
if (missionValidationSurfaceBlock) {
|
|
18695
|
+
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.` };
|
|
18696
|
+
}
|
|
17953
18697
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
17954
18698
|
}
|
|
17955
18699
|
|
|
@@ -17957,6 +18701,10 @@ Public workflow commands:
|
|
|
17957
18701
|
const mission = (state.activeMissionId ? loadMissionState(state.activeMissionId) : undefined) ?? activeMission ?? loadMissionState("latest");
|
|
17958
18702
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
17959
18703
|
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18704
|
+
const missionFinalValidationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "mission final validation before_agent_start");
|
|
18705
|
+
if (missionFinalValidationSurfaceBlock) {
|
|
18706
|
+
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.` };
|
|
18707
|
+
}
|
|
17960
18708
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionFinalValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
17961
18709
|
}
|
|
17962
18710
|
|
|
@@ -17972,13 +18720,13 @@ Public workflow commands:
|
|
|
17972
18720
|
const questions = state.clarifyingQuestions ?? parseClarifyingQuestions(state.draftPlan ?? "");
|
|
17973
18721
|
const answerSummary = formatAnswersForPlanner(questions, parsed);
|
|
17974
18722
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
17975
|
-
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.` };
|
|
18723
|
+
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.` };
|
|
17976
18724
|
updateState({ mode: "planning", clarifyingAnswers: parsed, modelsUsed: { ...(state.modelsUsed ?? {}), planner: modelLabel(route) } }, ctx);
|
|
17977
18725
|
return { systemPrompt: `${event.systemPrompt}\n\n${planPrompt(state.task ?? "Plan with answers", state.approvedPlan, answerSummary, settings, { feedbackKind: "clarification" })}` };
|
|
17978
18726
|
}
|
|
17979
18727
|
// Not a shorthand answer — treat as free-text clarification
|
|
17980
18728
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
17981
|
-
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.` };
|
|
18729
|
+
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.` };
|
|
17982
18730
|
updateState({ mode: "planning", clarifyingAnswers: input ? [{ index: 1, letter: "D", custom: input }] : state.clarifyingAnswers, modelsUsed: { ...(state.modelsUsed ?? {}), planner: modelLabel(route) } }, ctx);
|
|
17983
18731
|
return { systemPrompt: `${event.systemPrompt}\n\n${planPrompt(state.task ?? "Plan with answers", state.approvedPlan, input, settings, { feedbackKind: "clarification" })}` };
|
|
17984
18732
|
}
|
|
@@ -18001,6 +18749,10 @@ Public workflow commands:
|
|
|
18001
18749
|
}
|
|
18002
18750
|
if (state.mode === "reviewing") {
|
|
18003
18751
|
pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18752
|
+
const reviewSurfaceBlock = verifyWorkflowToolSurface(ctx, "Review", "plan review before_agent_start");
|
|
18753
|
+
if (reviewSurfaceBlock) {
|
|
18754
|
+
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.` };
|
|
18755
|
+
}
|
|
18004
18756
|
return { systemPrompt: `${event.systemPrompt}\n\n${reviewerPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
|
|
18005
18757
|
}
|
|
18006
18758
|
if (state.mode === "reviewed") {
|
|
@@ -18011,23 +18763,26 @@ Public workflow commands:
|
|
|
18011
18763
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
18012
18764
|
clearReviewHandoffSuppression(ctx, "execution turn started after typed Plan review handoff");
|
|
18013
18765
|
pi.setActiveTools(executionToolsFor(settings));
|
|
18014
|
-
const
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
const reason = `Required execution tools are unavailable after before_agent_start execution tool rearm: ${missingExecutionTools.join(", ")}. Execution prompt was not injected.`;
|
|
18018
|
-
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);
|
|
18019
|
-
pi.setActiveTools(webSafePlanTools(PLAN_TOOLS));
|
|
18020
|
-
recordWorkflowInternalEvent(ctx, `Plan execution blocked at before_agent_start because required execution tools were unavailable after rearm: ${missingExecutionTools.join(", ")}.`);
|
|
18021
|
-
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.` };
|
|
18766
|
+
const executionSurfaceBlock = verifyWorkflowToolSurface(ctx, "Execution", "plan execution before_agent_start");
|
|
18767
|
+
if (executionSurfaceBlock) {
|
|
18768
|
+
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.` };
|
|
18022
18769
|
}
|
|
18023
18770
|
return { systemPrompt: `${event.systemPrompt}\n\n${executePrompt(state, settings, phasePreflightBlocks.Execution)}` };
|
|
18024
18771
|
}
|
|
18025
18772
|
if (state.mode === "repairing") {
|
|
18026
18773
|
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18774
|
+
const repairSurfaceBlock = verifyWorkflowToolSurface(ctx, "Repair", "plan repair before_agent_start");
|
|
18775
|
+
if (repairSurfaceBlock) {
|
|
18776
|
+
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.` };
|
|
18777
|
+
}
|
|
18027
18778
|
return { systemPrompt: `${event.systemPrompt}\n\n${workflowRepairPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
|
|
18028
18779
|
}
|
|
18029
18780
|
if (state.mode === "validating" || state.mode === "revalidating") {
|
|
18030
18781
|
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
18782
|
+
const validationSurfaceBlock = verifyWorkflowToolSurface(ctx, "Validation", "plan validation before_agent_start");
|
|
18783
|
+
if (validationSurfaceBlock) {
|
|
18784
|
+
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.` };
|
|
18785
|
+
}
|
|
18031
18786
|
return { systemPrompt: `${event.systemPrompt}\n\n${validatePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Validation)}` };
|
|
18032
18787
|
}
|
|
18033
18788
|
if (state.mode === "validated") {
|
|
@@ -18060,6 +18815,11 @@ Public workflow commands:
|
|
|
18060
18815
|
return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
|
|
18061
18816
|
}
|
|
18062
18817
|
|
|
18818
|
+
if (missionReviewParentSuppressed()) {
|
|
18819
|
+
traceWorkflowTracking(ctx, "typed-mission-review-parent-message-suppressed", { mode: state.mode, activeMissionId: state.activeMissionId });
|
|
18820
|
+
return { message: { ...message, content: [{ type: "text" as const, text: "" }] } };
|
|
18821
|
+
}
|
|
18822
|
+
|
|
18063
18823
|
if (text && planReviewRepairActive(state) && state.lastWorkflowHandoff?.type === WORKFLOW_REVIEW_RESULT_TOOL) {
|
|
18064
18824
|
const firstTextIndex = message.content.findIndex((block) => block.type === "text");
|
|
18065
18825
|
if (firstTextIndex >= 0) {
|
|
@@ -18144,6 +18904,12 @@ Public workflow commands:
|
|
|
18144
18904
|
return;
|
|
18145
18905
|
}
|
|
18146
18906
|
|
|
18907
|
+
if (missionReviewParentSuppressed()) {
|
|
18908
|
+
traceWorkflowTracking(ctx, "typed-mission-review-parent-agent-end-suppressed", { mode: state.mode, activeMissionId: state.activeMissionId });
|
|
18909
|
+
clearReviewHandoffSuppression(ctx, "typed Mission review parent turn ended after accepted approval handoff");
|
|
18910
|
+
return;
|
|
18911
|
+
}
|
|
18912
|
+
|
|
18147
18913
|
if (!text) {
|
|
18148
18914
|
const interruptionReason = workflowTransientInterruptionReason(event, "Workflow turn stopped before producing assistant output.");
|
|
18149
18915
|
if (recoverContextInterruptedWorkflowTurn(event, ctx)) return;
|
|
@@ -18162,14 +18928,6 @@ Public workflow commands:
|
|
|
18162
18928
|
|
|
18163
18929
|
if ((state.mode === "validating" || state.mode === "revalidating") && state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) {
|
|
18164
18930
|
traceWorkflowTracking(ctx, "typed-plan-validation-agent-end-boundary", { source: "typed_validation_tool_agent_end", mode: state.mode, verdict: state.validationVerdict, nextAction: "route validation verdict" });
|
|
18165
|
-
planForcedSubagentPreflightReconcile(ctx, "Validation");
|
|
18166
|
-
const forcedValidationBlock = forcedSubagentUsageSatisfied(ctx, "Validation");
|
|
18167
|
-
if (forcedValidationBlock) {
|
|
18168
|
-
const report = state.validationReport ?? text;
|
|
18169
|
-
if (queuePlanValidationHandoffRetry(ctx, report, forcedValidationBlock, "typed validation accepted without forced Validation evidence")) return;
|
|
18170
|
-
blockPlanValidationHandoff(ctx, report, forcedValidationBlock);
|
|
18171
|
-
return;
|
|
18172
|
-
}
|
|
18173
18931
|
const report = state.validationReport ?? text;
|
|
18174
18932
|
const verdict = state.validationVerdict ?? normalizeValidationVerdict(extractVerdict(report), report);
|
|
18175
18933
|
const validationStatus = planValidationStatusForVerdict(verdict);
|
|
@@ -18326,11 +19084,34 @@ Public workflow commands:
|
|
|
18326
19084
|
return;
|
|
18327
19085
|
}
|
|
18328
19086
|
|
|
19087
|
+
if (state.mode === "revalidating" && state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) {
|
|
19088
|
+
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" });
|
|
19089
|
+
updateState({ lastWorkflowHandoff: undefined }, ctx);
|
|
19090
|
+
return;
|
|
19091
|
+
}
|
|
19092
|
+
|
|
18329
19093
|
if (state.mode === "executed" && state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) {
|
|
18330
19094
|
const validationAvailable = planValidationModelAvailable(settings);
|
|
18331
19095
|
const validateAfterExecution = planAutoValidationEnabled(settings);
|
|
18332
19096
|
const validationGateActive = planValidationGateActive(settings);
|
|
18333
19097
|
traceWorkflowTracking(ctx, "typed-execution-agent-end-validation-boundary", { source: "typed_execution_tool_agent_end", mode: "executed", validationAvailable, validateAfterExecution, validationGateActive });
|
|
19098
|
+
if (planExecutionIncomplete(state, settings)) {
|
|
19099
|
+
const reason = "Typed execution completed while approved Plan progress still has incomplete steps. Resume execution to complete remaining steps before validation.";
|
|
19100
|
+
updateState({
|
|
19101
|
+
mode: "reviewed",
|
|
19102
|
+
planExecutionStepIndex: undefined,
|
|
19103
|
+
planStepValidationIndex: undefined,
|
|
19104
|
+
validationReport: undefined,
|
|
19105
|
+
validationVerdict: undefined,
|
|
19106
|
+
lastValidationFailure: undefined,
|
|
19107
|
+
lastRepairStatus: "none",
|
|
19108
|
+
lastRepairAttempt: undefined,
|
|
19109
|
+
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
|
|
19110
|
+
}, ctx);
|
|
19111
|
+
queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason });
|
|
19112
|
+
showBlockedPlanRecoveryMenu(ctx);
|
|
19113
|
+
return;
|
|
19114
|
+
}
|
|
18334
19115
|
if (validationAvailable && validateAfterExecution) {
|
|
18335
19116
|
schedulePlanValidationAfterExecution(ctx, "typed execution agent end", "begin validation after typed execution agent end", "Typed execution completed but validation handoff failed");
|
|
18336
19117
|
} else if (validationAvailable && settings.workflow.offerValidationAfterExecute !== false) {
|
|
@@ -18742,7 +19523,7 @@ Public workflow commands:
|
|
|
18742
19523
|
return;
|
|
18743
19524
|
}
|
|
18744
19525
|
if (state.reviewRepairInProgress) {
|
|
18745
|
-
recordWorkflowInternalEvent(ctx, "Reviewer-requested Plan repair text completed;
|
|
19526
|
+
recordWorkflowInternalEvent(ctx, "Reviewer-requested Plan repair text completed; enforcing forced Planning evidence before accepting repair.");
|
|
18746
19527
|
}
|
|
18747
19528
|
const requiredClarificationStillMissing = state.clarificationRequiredBeforePlan === true && !state.clarifyingAnswers?.length;
|
|
18748
19529
|
if (requiredClarificationStillMissing) {
|
|
@@ -18761,6 +19542,23 @@ Public workflow commands:
|
|
|
18761
19542
|
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
18762
19543
|
return;
|
|
18763
19544
|
}
|
|
19545
|
+
planForcedSubagentPreflightReconcile(ctx, "Planning");
|
|
19546
|
+
const forcedPlanningBlock = forcedSubagentUsageSatisfied(ctx, "Planning");
|
|
19547
|
+
if (forcedPlanningBlock) {
|
|
19548
|
+
updateState({
|
|
19549
|
+
mode: "plan_draft",
|
|
19550
|
+
activePlanId: state.activePlanId ?? ensureActivePlanId(ctx),
|
|
19551
|
+
draftPlan: text,
|
|
19552
|
+
approvedPlan: undefined,
|
|
19553
|
+
lastReviewFailure: forcedPlanningBlock,
|
|
19554
|
+
planProgress: workflowPlanProgressEnabled(settings)
|
|
19555
|
+
? mergePlanProgress({ ...state, mode: "validated", draftPlan: text, approvedPlan: undefined }, settings, { lifecycleStatus: "blocked", nextAction: "planning blocked by forced sub-agent policy", steps: [] }, undefined)
|
|
19556
|
+
: state.planProgress,
|
|
19557
|
+
}, ctx);
|
|
19558
|
+
showInternal(pi, `# Sub-Agent Policy Blocked\n\n${forcedSubagentMessage("Planning", forcedPlanningBlock, "Planning")}`);
|
|
19559
|
+
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
19560
|
+
return;
|
|
19561
|
+
}
|
|
18764
19562
|
const planOrchestratorProblem = orchestrationProblem(ctx, "plan");
|
|
18765
19563
|
const planContractProblem = planDraftContractProblem(text);
|
|
18766
19564
|
if (planOrchestratorProblem || planContractProblem) {
|
|
@@ -18904,8 +19702,19 @@ Public workflow commands:
|
|
|
18904
19702
|
planForcedSubagentPreflightReconcile(ctx, "Execution");
|
|
18905
19703
|
if (!allTrackedStepsCompleted && blockIfForcedSubagentsMissing(ctx, "Execution")) {
|
|
18906
19704
|
const reason = "Execution stopped before all approved Plan steps were tracked and the forced execution worker requirement was not satisfied.";
|
|
18907
|
-
updateState({
|
|
18908
|
-
|
|
19705
|
+
updateState({
|
|
19706
|
+
mode: "reviewed",
|
|
19707
|
+
executionSummary: text,
|
|
19708
|
+
validationReport: undefined,
|
|
19709
|
+
validationVerdict: undefined,
|
|
19710
|
+
lastValidationFailure: undefined,
|
|
19711
|
+
lastRepairStatus: "none",
|
|
19712
|
+
lastRepairAttempt: undefined,
|
|
19713
|
+
planExecutionStepIndex: undefined,
|
|
19714
|
+
planStepValidationIndex: undefined,
|
|
19715
|
+
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,
|
|
19716
|
+
}, ctx);
|
|
19717
|
+
queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { reason });
|
|
18909
19718
|
return;
|
|
18910
19719
|
}
|
|
18911
19720
|
const progressWarning = !allTrackedStepsCompleted && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true && workflowPlanProgressEnabled(settings) && state.planProgress?.steps.length
|
|
@@ -18945,6 +19754,23 @@ Public workflow commands:
|
|
|
18945
19754
|
return;
|
|
18946
19755
|
}
|
|
18947
19756
|
}
|
|
19757
|
+
if (planExecutionIncomplete(state, settings)) {
|
|
19758
|
+
const reason = "Execution summary arrived while approved Plan progress still has incomplete steps. Resume execution to complete remaining steps before validation.";
|
|
19759
|
+
updateState({
|
|
19760
|
+
mode: "reviewed",
|
|
19761
|
+
planExecutionStepIndex: undefined,
|
|
19762
|
+
planStepValidationIndex: undefined,
|
|
19763
|
+
validationReport: undefined,
|
|
19764
|
+
validationVerdict: undefined,
|
|
19765
|
+
lastValidationFailure: undefined,
|
|
19766
|
+
lastRepairStatus: "none",
|
|
19767
|
+
lastRepairAttempt: undefined,
|
|
19768
|
+
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
|
|
19769
|
+
}, ctx);
|
|
19770
|
+
queuePlanTerminalSummary(ctx, "blocked", "Plan execution incomplete", { reason });
|
|
19771
|
+
showBlockedPlanRecoveryMenu(ctx);
|
|
19772
|
+
return;
|
|
19773
|
+
}
|
|
18948
19774
|
if (validationAvailable && validateAfterExecution) {
|
|
18949
19775
|
schedulePlanValidationAfterExecution(ctx, "execution complete", "begin validation after execution", "Execution completed but validation handoff failed");
|
|
18950
19776
|
} else if (validationAvailable && settings.workflow.offerValidationAfterExecute !== false) {
|
|
@@ -18969,10 +19795,10 @@ Public workflow commands:
|
|
|
18969
19795
|
const repairWorkers = workerCount(settings, "Repair");
|
|
18970
19796
|
const repairRequired = workerTargetForPolicy(repairPolicy, repairWorkers);
|
|
18971
19797
|
const availableRepairAgents = listEffectiveAgents(ctx.cwd);
|
|
18972
|
-
const repairSuitable = repairPolicy
|
|
19798
|
+
const repairSuitable = subagentPolicyRequiresRequiredEvidence(repairPolicy) ? chooseForcedSubagents("Repair", repairRequired, "Repair", availableRepairAgents) : [];
|
|
18973
19799
|
const hasWriteCapableSubagents = repairSuitable.some(a => subagentToolsAllowMutation(a.tools));
|
|
18974
19800
|
if (hasWriteCapableSubagents) {
|
|
18975
|
-
const required = workerTargetForPolicy(
|
|
19801
|
+
const required = workerTargetForPolicy(repairPolicy, workerCount(settings, "Repair"));
|
|
18976
19802
|
const observed = subagentUsageByPhase.Repair ?? 0;
|
|
18977
19803
|
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);
|
|
18978
19804
|
return;
|
|
@@ -18989,6 +19815,22 @@ Public workflow commands:
|
|
|
18989
19815
|
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
18990
19816
|
return;
|
|
18991
19817
|
}
|
|
19818
|
+
if (!planValidationBoundaryReached(state, settings)) {
|
|
19819
|
+
updateState({
|
|
19820
|
+
mode: "reviewed",
|
|
19821
|
+
executionSummary: `${state.executionSummary ?? ""}\n\nRepair summary received before validation boundary (not revalidated):\n${text}`.trim(),
|
|
19822
|
+
lastRepairStatus: "none",
|
|
19823
|
+
lastRepairAttempt: undefined,
|
|
19824
|
+
repairRetryState: { ...(state.repairRetryState ?? {}), validation: undefined },
|
|
19825
|
+
validationReport: undefined,
|
|
19826
|
+
validationVerdict: undefined,
|
|
19827
|
+
lastValidationFailure: undefined,
|
|
19828
|
+
planStepValidationIndex: undefined,
|
|
19829
|
+
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
|
|
19830
|
+
}, ctx);
|
|
19831
|
+
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.");
|
|
19832
|
+
return;
|
|
19833
|
+
}
|
|
18992
19834
|
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);
|
|
18993
19835
|
deferWorkflowAction(pi, "begin revalidation after repair", async () => {
|
|
18994
19836
|
const revalidationStarted = await beginValidation(ctx, true, true);
|
|
@@ -19000,11 +19842,19 @@ Public workflow commands:
|
|
|
19000
19842
|
// ── Validation complete ──
|
|
19001
19843
|
if (state.mode === "validating" || state.mode === "revalidating") {
|
|
19002
19844
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) return;
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19845
|
+
if (!planValidationBoundaryReached(state, settings)) {
|
|
19846
|
+
const reason = "Approved Plan execution is incomplete. Validation output was ignored until the configured validation boundary is reached.";
|
|
19847
|
+
updateState({
|
|
19848
|
+
mode: "reviewed",
|
|
19849
|
+
validationReport: undefined,
|
|
19850
|
+
validationVerdict: undefined,
|
|
19851
|
+
lastValidationFailure: undefined,
|
|
19852
|
+
lastRepairStatus: "none",
|
|
19853
|
+
lastRepairAttempt: undefined,
|
|
19854
|
+
planStepValidationIndex: undefined,
|
|
19855
|
+
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewed" }, settings, { lifecycleStatus: "reviewed", validationStatus: "pending", repairStatus: "none", nextAction: "continue execution" }) : state.planProgress,
|
|
19856
|
+
}, ctx);
|
|
19857
|
+
show(pi, `# Plan Validation Ignored\n\n${reason}\n\nUse /plan continue.`);
|
|
19008
19858
|
return;
|
|
19009
19859
|
}
|
|
19010
19860
|
const verdict = normalizeValidationVerdict(extractVerdict(text), text);
|