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