@mediadatafusion/pi-workflow-suite 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +126 -9
- package/VERSION +1 -1
- package/config/prompts/mission-review-prompt.md +42 -0
- package/config/prompts/workflow-reviewer-prompt.md +44 -0
- package/extensions/workflow-model-router.ts +28 -14
- package/extensions/workflow-modes.ts +1184 -311
- package/extensions/workflow-state.ts +62 -5
- package/extensions/workflow-tool-guard.ts +105 -15
- package/package.json +3 -2
|
@@ -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 {
|
|
@@ -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,11 +799,15 @@ function queueAgentTurn(pi: ExtensionAPI, content: string, customType: string):
|
|
|
771
799
|
}
|
|
772
800
|
|
|
773
801
|
function queueNativeFinalSummary(pi: ExtensionAPI, summary: string, customType: "workflow-plan-final-summary" | "workflow-mission-final-summary"): void {
|
|
774
|
-
|
|
802
|
+
try {
|
|
803
|
+
pi.sendMessage({ customType, content: professionalFinalSummaryMarkdown(summary), display: true }, { triggerTurn: false });
|
|
804
|
+
} catch (error) {
|
|
805
|
+
recordWorkflowInternalEvent(undefined, `Workflow final summary display failed (state already persisted): ${workflowTurnSendErrorMessage(error)}`);
|
|
806
|
+
}
|
|
775
807
|
}
|
|
776
808
|
|
|
777
809
|
function visibleDeferredHandoffFailure(label: string): boolean {
|
|
778
|
-
return /\b(menu|handoff)\b/i.test(label);
|
|
810
|
+
return /\b(menu|handoff|repair|revalidation|execution|phase|approval|mission)\b/i.test(label);
|
|
779
811
|
}
|
|
780
812
|
|
|
781
813
|
function deferWorkflowAction(pi: ExtensionAPI, label: string, action: () => Promise<void> | void): void {
|
|
@@ -824,7 +856,7 @@ function scheduleWorkflowHandoff(pi: ExtensionAPI, snapshot: WorkflowHandoffSnap
|
|
|
824
856
|
// validationSubagentsAllowed imported from workflow-subagent-policy.ts
|
|
825
857
|
|
|
826
858
|
function planToolsFor(settings: ReturnType<typeof loadWorkflowSettings>): string[] {
|
|
827
|
-
const tools = webSafePlanTools([...PLAN_TOOLS, WORKFLOW_PLAN_RESULT_TOOL, MISSION_PLAN_RESULT_TOOL]);
|
|
859
|
+
const tools = webSafePlanTools([...PLAN_TOOLS, WORKFLOW_PLAN_RESULT_TOOL, MISSION_PLAN_RESULT_TOOL, WORKFLOW_PROGRESS_TOOL]);
|
|
828
860
|
return planningSubagentsAllowed(settings) ? Array.from(new Set([...tools, "subagent"])) : tools;
|
|
829
861
|
}
|
|
830
862
|
|
|
@@ -918,7 +950,27 @@ function validationToolsFor(settings: ReturnType<typeof loadWorkflowSettings>):
|
|
|
918
950
|
|
|
919
951
|
|
|
920
952
|
function workflowMermaidGuidance(): string {
|
|
921
|
-
return
|
|
953
|
+
return `Mermaid diagrams are rendered by Workflow Suite as a uniform dark-mode visual component.
|
|
954
|
+
|
|
955
|
+
Diagram policy (MANDATORY for all workflow phases):
|
|
956
|
+
- CREATE DIAGRAMS INLINE: call workflow_diagram immediately after the paragraph that introduces the concept being diagrammed. Do NOT batch diagrams at the beginning or end of your response.
|
|
957
|
+
- TRIGGER RULE: When explaining any of the following, you must include at least one meaningful Mermaid diagram plus concise prose:
|
|
958
|
+
* user-facing workflow (e.g., data flow, build pipeline, CI/CD, multi-step sequence)
|
|
959
|
+
* architecture (e.g., system components, service topology, dependency graph)
|
|
960
|
+
* request lifecycle (e.g., API request/response flow, auth flow, error handling path)
|
|
961
|
+
* state transitions (e.g., mode changes, status lifecycle, phase progression)
|
|
962
|
+
* export/share path (e.g., file export pipeline, sharing flow)
|
|
963
|
+
* implementation phases (e.g., plan execution order, sub-agent coordination)
|
|
964
|
+
* data flow (e.g., transformation pipeline, ETL, data mapping)
|
|
965
|
+
- Choose the best diagram type:
|
|
966
|
+
* flowchart -- for pipelines, data flow, architecture overviews
|
|
967
|
+
* sequenceDiagram -- for request/response, actor interactions, system exchanges
|
|
968
|
+
* stateDiagram -- for status/mode/phase transitions
|
|
969
|
+
* classDiagram or erDiagram -- for structures, relationships, schemas
|
|
970
|
+
* xychart-beta -- for simple metrics or progress tracking
|
|
971
|
+
- Split crowded flows into multiple diagrams rather than shrinking labels.
|
|
972
|
+
- Use concise, readable labels. Do not hardcode random Mermaid style/classDef/light-theme overrides.
|
|
973
|
+
- Skip diagrams only when: response is genuinely trivial, user explicitly requested prose-only, or the response is pure code/configuration with no structural concept to diagram.`;
|
|
922
974
|
}
|
|
923
975
|
|
|
924
976
|
function workflowRuntimeWebResearchGuidance(): string {
|
|
@@ -1055,7 +1107,7 @@ function workflowMermaidSegmentsHaveDiagram(markdown: string): boolean {
|
|
|
1055
1107
|
function markdownWithoutWorkflowMermaidBlocks(markdown: string): string {
|
|
1056
1108
|
return parseWorkflowMermaidSegments(markdown).map((segment) => {
|
|
1057
1109
|
if (segment.type === "markdown") return segment.text;
|
|
1058
|
-
return
|
|
1110
|
+
return "";
|
|
1059
1111
|
}).join("").replace(/\n{4,}/g, "\n\n\n").trim();
|
|
1060
1112
|
}
|
|
1061
1113
|
|
|
@@ -1514,6 +1566,9 @@ function planPrompt(task: string, priorPlan?: string, feedback?: string, setting
|
|
|
1514
1566
|
|
|
1515
1567
|
${professionalOutputGuidance("Plan Mode")}
|
|
1516
1568
|
|
|
1569
|
+
Diagram guidance:
|
|
1570
|
+
- ${workflowMermaidGuidance()}
|
|
1571
|
+
|
|
1517
1572
|
Task:
|
|
1518
1573
|
${task}
|
|
1519
1574
|
|
|
@@ -1547,9 +1602,6 @@ No preamble. No visible chain-of-thought. No "I see that..." No "Let me think ab
|
|
|
1547
1602
|
Initial analysis requirement:
|
|
1548
1603
|
- ${analysisInstruction}
|
|
1549
1604
|
|
|
1550
|
-
Diagram guidance:
|
|
1551
|
-
- ${workflowMermaidGuidance()}
|
|
1552
|
-
|
|
1553
1605
|
Web research guidance:
|
|
1554
1606
|
${workflowRuntimeWebResearchGuidance()}
|
|
1555
1607
|
|
|
@@ -1733,6 +1785,36 @@ function phaseWorkerLine(settings: ReturnType<typeof loadWorkflowSettings>, phas
|
|
|
1733
1785
|
return `${phase} Workers: ${activeWorkerTargetLabel(policy, workerCount(settings, phase))}`;
|
|
1734
1786
|
}
|
|
1735
1787
|
|
|
1788
|
+
function subagentCapabilityTable(): string {
|
|
1789
|
+
return `## Available Sub-Agent Types
|
|
1790
|
+
|
|
1791
|
+
| Agent | Tools | Best For |
|
|
1792
|
+
|---|---|---|
|
|
1793
|
+
| general-worker | read, grep, find, ls, bash | File creation via bash, general execution, repair work |
|
|
1794
|
+
| implementation-planning | read, grep, find, ls | Pure analysis, step breakdown, implementation strategy (no bash) |
|
|
1795
|
+
| codebase-research | read, grep, find, ls, bash | Code inspection, dependency tracing, architecture review |
|
|
1796
|
+
| quality-validation | read, grep, find, ls, bash | Regression checks, build verification, independent review |
|
|
1797
|
+
| workflow-orchestrator | read, grep, find, ls, bash, subagent | Fan-out research coordination, multi-worker orchestration |
|
|
1798
|
+
|
|
1799
|
+
Sub-agents can create and modify files via bash commands even without edit/write tools. The main executor owns final file edits and integration, but sub-agents can prepare files, run builds, and verify changes via bash.
|
|
1800
|
+
Prefer general-worker or codebase-research for work that needs file creation or command execution. Use implementation-planning only for pure read-only analysis tasks.`;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function planStepStatusDisplay(state: WorkflowState): string {
|
|
1804
|
+
const steps = state.planProgress?.steps;
|
|
1805
|
+
if (!steps?.length) return "";
|
|
1806
|
+
const lines = steps.map((step, i) => {
|
|
1807
|
+
const statusLabel = step.status === "active" ? "ACTIVE"
|
|
1808
|
+
: step.status === "completed" ? "completed"
|
|
1809
|
+
: step.status === "failed" ? "FAILED"
|
|
1810
|
+
: step.status === "skipped" ? "skipped"
|
|
1811
|
+
: step.status === "blocked" ? "BLOCKED"
|
|
1812
|
+
: "pending";
|
|
1813
|
+
return ` ${i + 1}. ${step.title} — ${statusLabel}`;
|
|
1814
|
+
});
|
|
1815
|
+
return `Current Plan Step Status:\n${lines.join("\n")}`;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1736
1818
|
function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(), preflightBlock?: string): string {
|
|
1737
1819
|
const policy = settings.subagents.executionPolicy ?? "auto";
|
|
1738
1820
|
const workers = workerCount(settings, "Execution");
|
|
@@ -1749,28 +1831,63 @@ function executePrompt(state: WorkflowState, settings = loadWorkflowSettings(),
|
|
|
1749
1831
|
: preflightSatisfied && policy === "forced"
|
|
1750
1832
|
? forcedSubagentPolicySatisfiedGuidance("execution")
|
|
1751
1833
|
: policy === "forced"
|
|
1752
|
-
? `FORCED SUB-AGENT POLICY: before any file write, bash command, or final execution summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s). Use them in parallel where safe for
|
|
1834
|
+
? `FORCED SUB-AGENT POLICY: before any file write, bash command, or final execution summary, you MUST call the subagent tool with at least ${Math.max(1, workers.maximum)} execution/preparation worker(s). Use them in parallel where safe for file inspection, implementation strategy, scoped implementation help, patch planning, regression search, and validation preparation. If subagent execution fails, stop and report the exact blocker. Do not apply simultaneous conflicting edits.`
|
|
1753
1835
|
: policy === "maximum"
|
|
1754
|
-
? `For non-trivial implementation, use execution sub-agents before editing for
|
|
1836
|
+
? `For non-trivial implementation, use execution sub-agents before editing for analysis, implementation preparation, scoped implementation help, patch planning, regression search, and validation preparation. Strongly target at least ${workers.maximum} execution workers; skip only for trivial work or unavailable sub-agent execution and explain why. Do not apply simultaneous conflicting edits.`
|
|
1755
1837
|
: policy === "deep"
|
|
1756
|
-
? `For non-trivial implementation, use at least ${workers.deep} execution sub-agent before editing for
|
|
1757
|
-
: "Execution sub-agents are strongly encouraged for speed and quality. Use them for file inspection, implementation prep, risk discovery, patch planning, or validation prep; skip only for trivial changes and explain why.";
|
|
1838
|
+
? `For non-trivial implementation, use at least ${workers.deep} execution sub-agent before editing for analysis, implementation preparation, scoped implementation help, patch planning, or validation preparation. Explain any skip. Do not apply simultaneous conflicting edits.`
|
|
1839
|
+
: "Execution sub-agents are strongly encouraged for speed and quality. Use them for file inspection, implementation prep, scoped implementation help, risk discovery, patch planning, or validation prep; skip only for trivial changes and explain why.";
|
|
1840
|
+
const automatableEvidenceGuidance = `Automatable evidence contract:
|
|
1841
|
+
- Before calling workflow_execution_result, you MUST gather all automatable validation evidence that the approved plan requires.
|
|
1842
|
+
- For web/app projects (React, Vite, Next.js, Vue, Svelte, etc.): run build and test commands; when the plan requires runtime/browser/localStorage behavior, start a bounded dev or preview server and verify the behavior with available commands (curl, node scripts, or the platform's built-in tools), then stop/cleanup the server.
|
|
1843
|
+
- For API/backend projects: run build, test, lint, typecheck; verify server startup, endpoint responses, and persistence where required by the plan.
|
|
1844
|
+
- For library/CLI projects: run build, test, lint, typecheck; verify CLI output or module exports where required.
|
|
1845
|
+
- Your execution summary must distinguish "Automated Evidence Completed" from "Truly Manual Evidence Remaining". Do not label automatable runtime checks as "manual verification needed".
|
|
1846
|
+
- "Manual verification needed" is only valid for genuinely non-automatable human judgment (visual design approval, subjective UX assessment, external service accounts you cannot access). If a check can be automated with available tools and settings, you must perform it.`;
|
|
1847
|
+
|
|
1758
1848
|
return `You are in PI WORKFLOW EXECUTE MODE.
|
|
1759
1849
|
|
|
1850
|
+
YOUR TOOLS: edit, write, bash, workflow_progress, workflow_diagram, workflow_execution_result. You, the main executor, own all file writes, edits, and bash commands directly. Sub-agents are supplementary analysis only — they do not own file creation, and their read-only reports do not mean you are read-only. Even when sub-agents report they cannot write files, you must proceed with your own file writes, edits, and bash commands. For straightforward implementation steps, create files directly yourself without launching sub-agents.
|
|
1851
|
+
|
|
1852
|
+
CRITICAL: Use write and edit to create and modify files on disk. Do NOT output file contents as markdown code blocks in your chat response. If you find yourself typing a code block of file contents, STOP and use the write tool instead.
|
|
1853
|
+
|
|
1760
1854
|
${professionalOutputGuidance("Plan execution")}
|
|
1761
1855
|
|
|
1856
|
+
Diagram guidance:
|
|
1857
|
+
${workflowMermaidGuidance()}
|
|
1858
|
+
|
|
1762
1859
|
Approved plan is the execution contract. Do only this plan. No unrelated refactors. Do not commit. Do not push. Do not switch branches.
|
|
1763
1860
|
|
|
1764
|
-
|
|
1861
|
+
When the task is complex or you need research before editing, launch sub-agents for analysis and inspection. Run them before writing files when their findings would change your implementation. Otherwise, proceed directly with file creation. After implementation, gather all automatable validation evidence, summarize changed files, sub-agent work/findings used, and recommend validation.
|
|
1862
|
+
|
|
1863
|
+
${automatableEvidenceGuidance}
|
|
1765
1864
|
|
|
1766
|
-
Plan
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1865
|
+
Plan Step Progress — the guard REQUIRES workflow_progress calls for EVERY step:
|
|
1866
|
+
|
|
1867
|
+
Step activation: workflow_progress({ step: N, status: "active" })
|
|
1868
|
+
REQUIRED before any edit, write, or meaningful bash for step N. The guard blocks file work without it.
|
|
1869
|
+
|
|
1870
|
+
Step completion: workflow_progress({ step: N, status: "completed" })
|
|
1871
|
+
REQUIRED after finishing step N. This advances the widget to the next step. Without it, the widget stays stuck.
|
|
1872
|
+
|
|
1873
|
+
Failed/blocked step: workflow_progress({ step: N, status: "failed" }) with reason in details.
|
|
1874
|
+
|
|
1875
|
+
Multi-step pattern — repeat for EVERY step, in order:
|
|
1876
|
+
Step 1: activate → do step 1 work → complete step 1
|
|
1877
|
+
Step 2: activate → do step 2 work → complete step 2
|
|
1878
|
+
Step 3: activate → do step 3 work → complete step 3
|
|
1879
|
+
|
|
1880
|
+
After you complete a step, the guard will require workflow_progress({ step: N+1, status: "active" }) before file writes for the next step. Listen to the guard — it tells you exactly which step needs activation.
|
|
1881
|
+
|
|
1882
|
+
${planStepStatusDisplay(state)}
|
|
1883
|
+
|
|
1884
|
+
- Sub-agents may launch before a step is activated, but the step must be active before writing files.
|
|
1885
|
+
- If you delegate step work to a sub-agent, you still own the parent workflow_progress calls. Start the sub-agent task with Plan step N: and require it to report Plan Step N Progress: completed | blocked | incomplete with evidence. After the sub-agent finishes, YOU must call workflow_progress to complete the step.
|
|
1886
|
+
- If you accidentally call workflow_plan_result during execution, do not retry it; continue with workflow_progress, write/edit/bash, and workflow_execution_result.
|
|
1887
|
+
Actual Plan execution tools can also activate the current step as a fallback, and text markers remain supported: WORKFLOW_STEP_STARTED:N, WORKFLOW_STEP_COMPLETED:N, WORKFLOW_STEP_FAILED:N. These fallbacks do not replace the required workflow_progress calls.
|
|
1771
1888
|
- For per-step gated execution, track only the current allowed step and stop after that step.
|
|
1772
1889
|
|
|
1773
|
-
Before your final execution summary, call workflow_execution_result with status, completedSteps, changedFiles, commands, blockers, and summary. completedSteps
|
|
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.
|
|
1828
1974
|
|
|
1829
|
-
|
|
1975
|
+
${automatableEvidenceVerifierGuidance}
|
|
1976
|
+
|
|
1977
|
+
Before your final validation report, call workflow_validation_result with scope=plan, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only. After calling workflow_validation_result, print a concise validation summary for the user before stopping.
|
|
1830
1978
|
|
|
1831
1979
|
Validation scope:
|
|
1832
1980
|
- ${validationScope}
|
|
@@ -1842,7 +1990,8 @@ ${requiredSubagentPreflightSection(preflightBlock)}
|
|
|
1842
1990
|
- ${preflightSatisfied && policy === "forced" ? "Forced validation policy was satisfied by Workflow Suite preflight; do not rerun required workers solely for policy compliance." : "If validationPolicy is forced, required sub-agents must run before verdict, or stop with: Sub-agent policy is forced, but sub-agent execution is unavailable because <reason>."}
|
|
1843
1991
|
- PASS only when the approved plan is fully satisfied with no blocking unresolved risk.
|
|
1844
1992
|
- FAIL when concrete missing requirements, unexpected changes, regressions, broken checks, unsafe/out-of-scope work, or concrete code/content/citation/source/file/metadata/artifact fixes remain.
|
|
1845
|
-
-
|
|
1993
|
+
- FAIL when automatable runtime evidence (build, test, dev server, browser, localStorage, API response) was not gathered and the checks are performable with available tools. Missing automatable evidence is a concrete repairable issue, not a manual-only caveat.
|
|
1994
|
+
- PARTIAL PASS is only for genuinely human-only verification (visual design approval, subjective UX assessment, external services you cannot access) after all automatable evidence has been gathered. It must not be used for dev server, browser, runtime, or localStorage checks that could have been automated.
|
|
1846
1995
|
- Manual visual-verification caveats alone are not repairable failures; classify them as PARTIAL PASS and recommend manual QA/revalidation, not repair.
|
|
1847
1996
|
- If concrete repairable issues remain in code, content, citations, sources, generated files, indexes, metadata, artifacts, or validation artifacts, mark Concrete Repairable Issue: yes, list them clearly under Missing Requirements or Recommended Next Action, and prefer FAIL over PARTIAL PASS.
|
|
1848
1997
|
- Evidence gaps are not repairable defects unless you identify a concrete missing requirement or artifact. Mark Evidence Gap: yes and Concrete Repairable Issue: no when the correct next action is evidence collection or revalidation rather than repair.
|
|
@@ -1883,6 +2032,10 @@ yes/no and short reason
|
|
|
1883
2032
|
yes/no and exact missing evidence
|
|
1884
2033
|
## Manual Verification Required
|
|
1885
2034
|
yes/no and exact manual check
|
|
2035
|
+
## Automated Evidence Completed
|
|
2036
|
+
What runtime/browser/build/test evidence was verified automatically.
|
|
2037
|
+
## Truly Manual Evidence Remaining
|
|
2038
|
+
Only genuinely non-automatable human-only checks, not checks that could have been automated.
|
|
1886
2039
|
## Missing Requirements
|
|
1887
2040
|
## Unexpected Changes
|
|
1888
2041
|
## Regression Risks
|
|
@@ -1913,7 +2066,10 @@ You are in PI WORKFLOW REPAIR MODE.
|
|
|
1913
2066
|
|
|
1914
2067
|
${professionalOutputGuidance("Plan repair")}
|
|
1915
2068
|
|
|
1916
|
-
|
|
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 {
|
|
@@ -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; normal file tools and sub-agents are limited to the active repo, while protected project .pi paths are read-only through normal tools
|
|
3079
|
-
Disable Bash In Plan Mode: ${settings.safety.disableBashInPlanMode !== false ? "enabled" : "disabled"}
|
|
3080
|
-
Disable Bash In Validator Mode: ${settings.safety.disableBashInValidatorMode !== false ? "enabled" : "disabled"}
|
|
3081
|
-
Block Destructive Commands: ${settings.safety.blockDestructiveCommands !== false ? "enabled" : "disabled"}
|
|
3082
|
-
|
|
3083
|
-
## Workflow Permissions
|
|
3084
|
-
${renderPermissionSummary(settings)}
|
|
3274
|
+
${renderSafetySettings(settings)}
|
|
3085
3275
|
|
|
3086
3276
|
## Compaction
|
|
3087
3277
|
Compaction Mode: ${compactionModeLabel(settings.context.compactionMode)}
|
|
@@ -3101,6 +3291,19 @@ ${workflowCompactionCheckRuntimeStatus()}
|
|
|
3101
3291
|
Last Custom Compaction Status: ${customCompactionRuntimeStatus()}`;
|
|
3102
3292
|
}
|
|
3103
3293
|
|
|
3294
|
+
function renderSafetySettings(settings: ReturnType<typeof loadWorkflowSettings>): string {
|
|
3295
|
+
return `## Global Safety
|
|
3296
|
+
Repo Lock (Project): ${settings.safety.repoLockEnabled === true ? "enabled" : "disabled"}
|
|
3297
|
+
Repo Lock Scope: project override only; toggling writes to the active repo .pi/workflow-settings.json; normal file tools and sub-agents are limited to the active repo, while protected project .pi paths are read-only through normal tools
|
|
3298
|
+
Disable Bash In Plan Mode: ${settings.safety.disableBashInPlanMode !== false ? "enabled" : "disabled"}
|
|
3299
|
+
Disable Bash In Validator Mode: ${settings.safety.disableBashInValidatorMode !== false ? "enabled (unsafe bash blocked; safe evidence bash allowed)" : "disabled"}
|
|
3300
|
+
Block Destructive Commands: ${settings.safety.blockDestructiveCommands !== false ? "enabled" : "disabled"}
|
|
3301
|
+
Allow Package Install In Execution: ${settings.safety.allowPackageInstallInExecution !== false ? "enabled" : "disabled"}
|
|
3302
|
+
|
|
3303
|
+
## Workflow Permissions
|
|
3304
|
+
${renderPermissionSummary(settings)}`;
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3104
3307
|
function formatDurationMs(ms: number): string {
|
|
3105
3308
|
const safe = Math.max(0, ms);
|
|
3106
3309
|
const days = Math.floor(safe / 86_400_000);
|
|
@@ -3243,8 +3446,8 @@ function planRuntimeSummaryLine(state: WorkflowState): string {
|
|
|
3243
3446
|
}
|
|
3244
3447
|
|
|
3245
3448
|
function planValidationState(state: WorkflowState): PlanValidationStatus {
|
|
3246
|
-
if (state.planProgress?.validationStatus) return state.planProgress.validationStatus;
|
|
3247
3449
|
if (state.mode === "validating" || state.mode === "revalidating") return "running";
|
|
3450
|
+
if (state.planProgress?.validationStatus && state.planProgress.validationStatus !== "unknown") return state.planProgress.validationStatus;
|
|
3248
3451
|
if (state.validationVerdict === "PASS") return "pass";
|
|
3249
3452
|
if (state.validationVerdict === "PARTIAL PASS") return "partial pass";
|
|
3250
3453
|
if (state.validationVerdict === "FAIL") return "fail";
|
|
@@ -3252,6 +3455,15 @@ function planValidationState(state: WorkflowState): PlanValidationStatus {
|
|
|
3252
3455
|
return "pending";
|
|
3253
3456
|
}
|
|
3254
3457
|
|
|
3458
|
+
function planLastValidationLabel(state: WorkflowState): string {
|
|
3459
|
+
const progressLast = state.planProgress?.lastValidationStatus;
|
|
3460
|
+
if (progressLast && progressLast !== "unknown") return progressLast;
|
|
3461
|
+
if (state.validationVerdict === "PASS") return "pass";
|
|
3462
|
+
if (state.validationVerdict === "PARTIAL PASS") return "partial pass";
|
|
3463
|
+
if (state.validationVerdict === "FAIL") return "fail";
|
|
3464
|
+
return progressLast ?? "none";
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3255
3467
|
function lifecycleStatusForState(state: WorkflowState): PlanProgressState["lifecycleStatus"] {
|
|
3256
3468
|
if (state.mode === "planning") return "planning";
|
|
3257
3469
|
if (state.mode === "awaiting_clarification") return "awaiting_clarification";
|
|
@@ -3372,8 +3584,13 @@ function mergePlanProgress(state: WorkflowState, settings: ReturnType<typeof loa
|
|
|
3372
3584
|
const current = state.planProgress ?? createPlanProgress(planText, settings, lifecycleStatusForState(state));
|
|
3373
3585
|
const hasPlanText = Boolean(planText?.trim());
|
|
3374
3586
|
const extractedSteps = hasPlanText ? extractApprovedPlanSteps(planText) : [];
|
|
3375
|
-
const
|
|
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- Before your final milestone summary, call mission_milestone_result with milestoneId, status, summary, blockers, and evidence. The typed tool result is the primary Mission execution handoff.\n- Produce a checkpoint-ready summary with files changed, risks, validation needs, and next action.\n- Include validator-grade evidence: Acceptance criteria coverage, Commands run with exit status, Checks skipped with reason, files changed/inspected, and remaining manual verification.\n- Do not edit mission runtime state directly; Mission Mode will checkpoint through the workflow runtime.\n\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Produce validation-ready execution summary for the mission validator."}\n\n${missionRunPlan(mission)}`;
|
|
7098
|
+
return `You are PI MISSION MODE RUNTIME.\n\n${professionalOutputGuidance("Mission runtime")}\n\nMISSION MODE ACTIVE\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nMission status: ${mission.status}\nCurrent milestone index: ${mission.currentMilestoneIndex}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nCurrent milestone objective: ${milestone?.objective ?? "none"}\n\nMilestone steps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nCompleted milestones:\n${completed.map((m) => `- ${m.id}: ${m.title}`).join("\n") || "- none"}\n\nRemaining milestones:\n${remaining.map((m) => `- ${m.id}: ${m.title} (${m.status})`).join("\n") || "- none"}\n\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\nLast stop reason: ${mission.lastStopReason || "none"}\nLast block reason: ${mission.lastBlockReason || "none"}\nNext action: ${mission.nextAction || missionNextActionText(mission, settings)}\n\nSafety rules:\n- This is Mission Mode, not Plan Mode. Do not produce a generic implementation plan.\n- Execute only the current approved mission milestone.\n- Do not continue to later milestones unless Mission Mode starts them.\n- Stop on unexpected risk, destructive action, secret/auth/session/log/runtime-state edit, deployment, push, database mutation, or out-of-scope work.\n- Never edit auth files, sessions, logs, .env, .factory, .cursor, or mission runtime state.\n- Keep file writes sequential unless settings explicitly allow safe scoped parallel edits.\n- Use execution sub-agents aggressively for speed, read-only inspection, risk discovery, implementation strategy, and validation preparation when consistent with executionPolicy=${policy}; if forced, do not edit until required workers have reported.\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\n${subagentCapabilityTable()}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nWeb research guidance:\n${workflowRuntimeWebResearchGuidance()}\n\nPre-handoff verification:\n- Before calling mission_milestone_result, verify all Expected Files/Systems from the milestone plan exist and contain correct content.\n- Verify all Acceptance Criteria are satisfied with evidence.\n- Build, lint, and typecheck must pass (or be explicitly skipped with reason).\n- If any deliverable is missing, create it now before handoff.\n\nCheckpoint requirements:\n- Before your final milestone summary, call mission_milestone_result with milestoneId, status, summary, blockers, and evidence. The typed tool result is the primary Mission execution handoff.\n- Produce a checkpoint-ready summary with files changed, risks, validation needs, and next action.\n- Include validator-grade evidence: Acceptance criteria coverage, Commands run with exit status, Checks skipped with reason, files changed/inspected, and remaining manual verification.\n- Do not edit mission runtime state directly; Mission Mode will checkpoint through the workflow runtime.\n\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- Produce validation-ready execution summary for the mission validator."}\n\n${missionRunPlan(mission)}`;
|
|
6792
7099
|
}
|
|
6793
7100
|
|
|
6794
7101
|
function missionValidationPrompt(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, executionSummary?: string, preflightBlock?: string): string {
|
|
6795
7102
|
const milestone = mission.milestones[mission.currentMilestoneIndex];
|
|
6796
7103
|
const last = mission.checkpoints[mission.checkpoints.length - 1];
|
|
6797
7104
|
const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Validation", "Mission Validation", preflightBlock);
|
|
6798
|
-
return `You are PI MISSION MODE VALIDATOR.\n\n${professionalOutputGuidance("Mission validation")}\n\nValidate the completed work against the current mission milestone only.
|
|
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. Use FAIL when concrete code/content/citation/source/file/metadata/artifact fixes, concrete repairable defects, or unsafe/out-of-scope work remain. Use PARTIAL PASS only for
|
|
7150
|
+
Validate the complete mission goal across all milestones, not just the last milestone. Confirm milestone results compose into the original mission outcome. Surface integration gaps, missing cross-milestone requirements, regressions, and unresolved repair risks. Use PASS only for complete success. Use FAIL when concrete code/content/citation/source/file/metadata/artifact fixes, concrete repairable defects, or unsafe/out-of-scope work remain. Use FAIL when automatable runtime evidence (build, test, dev server, browser, localStorage, API response) was not gathered and the checks are performable with available tools. Use PARTIAL PASS only for genuinely human-only verification after all automatable evidence has been gathered. Evidence gaps are not repairable defects unless a concrete missing requirement or artifact is identified.\n\nBefore your final validation report, call workflow_validation_result with scope=mission, verdict, concreteRepairableIssue, evidenceGap, manualVerificationRequired, issues, and summary. The typed tool result is the primary handoff control plane; markdown verdict text is legacy fallback only. After calling workflow_validation_result, print a concise validation summary for the user before stopping.
|
|
7151
|
+
|
|
7152
|
+
To verify web app runtime behavior:
|
|
7153
|
+
- For projects with npm dev server: npm run dev -- --port 3017 &
|
|
7154
|
+
- For static HTML/CSS/JS projects (no package.json scripts): python3 -m http.server 8017 &
|
|
7155
|
+
- Wait for the server: sleep 2
|
|
7156
|
+
- Query endpoints: curl -fsS http://localhost:PORT/path
|
|
7157
|
+
- Check the process: ps aux | grep "server"
|
|
7158
|
+
- Stop the server when done: kill $!
|
|
7159
|
+
- Discard unwanted output: >/dev/null 2>&1
|
|
7160
|
+
Use single-line bash calls for each step.
|
|
7161
|
+
|
|
7162
|
+
Headless browser verification: use the workflow_browser_check tool with the dev server URL to verify console errors, page errors, DOM elements, and localStorage behavior. This tool uses Puppeteer from the Pi runtime and works regardless of the target project's dependencies.
|
|
7163
|
+
|
|
7164
|
+
CRITICAL: You MUST exhaust all automatable checks before returning PARTIAL PASS.
|
|
7165
|
+
DO NOT mark evidence as "could not verify" without actually trying to verify it.
|
|
7166
|
+
Start a server, curl the endpoints, check file accessibility — THEN report what you
|
|
7167
|
+
could and could not confirm. "No browser available" is not a reason to skip
|
|
7168
|
+
server-side checks that ARE automatable.
|
|
7169
|
+
|
|
7170
|
+
You MUST fill in EVERY structured output field, especially:
|
|
7171
|
+
- Concrete Repairable Issue: yes/no (with reason)
|
|
7172
|
+
- Evidence Gap: yes/no (with exact missing evidence)
|
|
7173
|
+
- Manual Verification Required: yes/no (with exact manual check)
|
|
7174
|
+
- Automated Evidence Completed: list everything verified automatically (not "none" or "n/a")
|
|
6842
7175
|
|
|
6843
7176
|
Output exactly:
|
|
6844
7177
|
# Final Mission Validation Report
|
|
@@ -6857,6 +7190,10 @@ yes/no and short reason
|
|
|
6857
7190
|
yes/no and exact missing evidence
|
|
6858
7191
|
## Manual Verification Required
|
|
6859
7192
|
yes/no and exact manual check
|
|
7193
|
+
## Automated Evidence Completed
|
|
7194
|
+
What runtime/browser/build/test evidence was verified automatically.
|
|
7195
|
+
## Truly Manual Evidence Remaining
|
|
7196
|
+
Only genuinely non-automatable human-only checks, not checks that could have been automated.
|
|
6860
7197
|
## Missing Requirements
|
|
6861
7198
|
## Unexpected Changes
|
|
6862
7199
|
## Test And Build Status
|
|
@@ -6868,7 +7205,7 @@ function missionRepairPrompt(mission: MissionState, settings: ReturnType<typeof
|
|
|
6868
7205
|
const last = mission.checkpoints[mission.checkpoints.length - 1];
|
|
6869
7206
|
const repairPrompt = readPromptFile("mission-repair.md", "Repair only the current mission milestone validation failure safely, then summarize for revalidation.");
|
|
6870
7207
|
const subagentPolicyBlock = phasePromptPolicyBlock(settings, "Repair", "Mission Repair", preflightBlock);
|
|
6871
|
-
return `${repairPrompt}\n\n${professionalOutputGuidance("Mission repair")}\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nApproved milestone scope:\nObjective: ${milestone?.objective ?? "none"}\nSteps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nRisks:\n${(milestone?.risks ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nValidation failure details:\n${mission.lastValidationFailure || mission.lastBlockReason || "none recorded"}\n\nRetry count: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission retry count: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\n\nSafety rules:\n- Only fix concrete validator-identified issues directly related to the failed milestone validation.\n- Do not re-grade validation or claim PASS; only Mission validation can pass repaired work.\n- If the validation finding is only manual/visual/browser verification or says no automated repair is needed, do not make changes; summarize manual QA/revalidation readiness.\n- Do not expand scope beyond the current approved milestone.\n- Do not perform destructive actions.\n- Do not edit secrets, auth/session/log/runtime-state files, .env, .factory, or .cursor.\n- Do not deploy. Do not push. Do not mutate databases.\n- Stop and report if repair requires destructive, out-of-scope, secret, database, deployment, or other risky action.\n\nSettings:\n- validationRetryMode: ${settings.missions.validationRetryMode ?? "safe_only"}\n- requireApprovalForOutOfScopeRepair: ${settings.missions.requireApprovalForOutOfScopeRepair !== false}\n- requireApprovalForDestructiveRepair: ${settings.missions.requireApprovalForDestructiveRepair !== false}\n- autoRepairValidationFailures: ${settings.missions.autoRepairValidationFailures !== false}\n- pauseAfterValidationFailure: ${settings.missions.pauseAfterValidationFailure === true}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nRevalidation requirement:\n- Summarize whether the current milestone is ready for validator revalidation.\n- Do not mark the milestone complete yourself; Mission Mode will re-run validation according to repair/retry settings.\n\nOutput:\n# Mission Repair Summary\n## Repair Scope\n## Work Completed\n## Files Changed\n## Remaining Risks\n## Revalidation Readiness\n## Next Action`;
|
|
7208
|
+
return `${repairPrompt}\n\n${professionalOutputGuidance("Mission repair")}\n\nMission ID: ${mission.id}\nMission goal:\n${mission.goal}\n\nAutonomy: ${mission.autonomy}\nCurrent milestone: ${milestone ? `${milestone.id} — ${milestone.title}` : "none"}\nApproved milestone scope:\nObjective: ${milestone?.objective ?? "none"}\nSteps:\n${(milestone?.steps ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nValidation requirements:\n${(milestone?.validation ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\nRisks:\n${(milestone?.risks ?? []).map((s) => `- ${s}`).join("\n") || "- none recorded"}\n\nValidation failure details:\n${mission.lastValidationFailure || mission.lastBlockReason || "none recorded"}\n\nRetry count: ${mission.currentValidationRetry ?? 0} of ${maxValidationRetries(mission, settings)} per milestone\nMission retry count: ${missionValidationRetryCount(mission)} of ${maxMissionValidationRetries(mission, settings)} total\nLast repair attempt: ${mission.lastRepairAttempt || "none"}\nLast checkpoint: ${last ? `${last.id} at ${last.timestamp} — ${last.summary}` : "none"}\n\nSafety rules:\n- Only fix concrete validator-identified issues directly related to the failed milestone validation.\n- Do not re-grade validation or claim PASS; only Mission validation can pass repaired work.\n- If the validation finding is only manual/visual/browser verification or says no automated repair is needed, do not make changes; summarize manual QA/revalidation readiness.\n- Do not expand scope beyond the current approved milestone.\n- Do not perform destructive actions.\n- Do not edit secrets, auth/session/log/runtime-state files, .env, .factory, or .cursor.\n- Do not deploy. Do not push. Do not mutate databases.\n- Stop and report if repair requires destructive, out-of-scope, secret, database, deployment, or other risky action.\n\nSettings:\n- validationRetryMode: ${settings.missions.validationRetryMode ?? "safe_only"}\n- requireApprovalForOutOfScopeRepair: ${settings.missions.requireApprovalForOutOfScopeRepair !== false}\n- requireApprovalForDestructiveRepair: ${settings.missions.requireApprovalForDestructiveRepair !== false}\n- autoRepairValidationFailures: ${settings.missions.autoRepairValidationFailures !== false}\n- pauseAfterValidationFailure: ${settings.missions.pauseAfterValidationFailure === true}\n\n${subagentPolicyBlock}${requiredSubagentPreflightSection(preflightBlock)}\n\n${subagentCapabilityTable()}\n\nDiagram guidance:\n${workflowMermaidGuidance()}\n\nRevalidation requirement:\n- Summarize whether the current milestone is ready for validator revalidation.\n- Do not mark the milestone complete yourself; Mission Mode will re-run validation according to repair/retry settings.\n\nOutput:\n# Mission Repair Summary\n## Repair Scope\n## Work Completed\n## Files Changed\n## Remaining Risks\n## Revalidation Readiness\n## Next Action`;
|
|
6872
7209
|
}
|
|
6873
7210
|
|
|
6874
7211
|
function parseMissionMilestones(text: string): MissionMilestone[] {
|
|
@@ -7145,6 +7482,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7145
7482
|
|
|
7146
7483
|
const applyWorkflowPlanResult = (ctx: ExtensionContext, params: Record<string, unknown>) => {
|
|
7147
7484
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
7485
|
+
if (state.mode !== "planning") {
|
|
7486
|
+
const guidance = state.mode === "executing" || state.mode === "repairing"
|
|
7487
|
+
? "workflow_plan_result is ignored because Plan is already executing or repairing. Continue with workflow_progress, write/edit/bash, and workflow_execution_result."
|
|
7488
|
+
: `workflow_plan_result is ignored because the current workflow mode is ${state.mode}. Use it only during Plan planning.`;
|
|
7489
|
+
return { content: [{ type: "text", text: guidance }], details: { accepted: true, ignored: true, mode: state.mode } };
|
|
7490
|
+
}
|
|
7148
7491
|
recordTypedHandoff(ctx, WORKFLOW_PLAN_RESULT_TOOL as WorkflowTypedHandoffType, params);
|
|
7149
7492
|
const decision = String(params.decision ?? "").toLowerCase();
|
|
7150
7493
|
if (decision === "clarify") {
|
|
@@ -7258,14 +7601,16 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7258
7601
|
showBlockedPlanRecoveryMenu(ctx);
|
|
7259
7602
|
return { ...typedToolAck(), details: { accepted: true, status, validationStarted: false } };
|
|
7260
7603
|
}
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
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 } };
|
|
7267
7610
|
}
|
|
7268
|
-
|
|
7611
|
+
const progressWarning = hasOpenSteps ? "Execution completed with incomplete Plan step metadata; validator must verify approved Plan coverage." : undefined;
|
|
7612
|
+
const executionSummary = progressWarning ? `${summary}\n\nPlan Progress Warning: ${progressWarning}` : summary;
|
|
7613
|
+
updateState({ mode: "executed", executionSummary, planStepValidationIndex: settings.workflow.validateAfterEachStep === true ? executedStepIndex : state.planStepValidationIndex, planExecutionStepIndex: undefined, planProgress: progressEnabled ? mergePlanProgress({ ...progressedState, mode: "executed" }, settings, { lifecycleStatus: "executing", validationStatus: progressWarning ? "unknown" : progressedState.planProgress?.validationStatus, nextAction: validationAvailable && validateAfterExecution ? "validator" : "finish workflow" }) : progressedPlanProgress }, ctx);
|
|
7269
7614
|
if (validationAvailable && validateAfterExecution) deferWorkflowAction(pi, "begin validation after typed execution", async () => { await beginValidation(ctx, true); });
|
|
7270
7615
|
else if (!planValidationGateActive(settings)) deferWorkflowAction(pi, "complete plan after typed execution without validation", () => completePlanWithoutValidation(ctx, validationAvailable ? "Validation skipped: validation is disabled by workflow settings." : "Validation skipped: validator disabled or not configured."));
|
|
7271
7616
|
else deferWorkflowAction(pi, "show post execution menu after typed execution", () => showPostExecutionMenu(ctx, validationAvailable));
|
|
@@ -7321,6 +7666,9 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7321
7666
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
7322
7667
|
const verdict = typedValidationVerdict(params.verdict);
|
|
7323
7668
|
const report = typedValidationReport(params);
|
|
7669
|
+
const concreteRepairableIssue = typeof params.concreteRepairableIssue === "boolean" ? params.concreteRepairableIssue : undefined;
|
|
7670
|
+
const manualVerificationRequired = typeof params.manualVerificationRequired === "boolean" ? params.manualVerificationRequired : undefined;
|
|
7671
|
+
const evidenceGap = typeof params.evidenceGap === "boolean" ? params.evidenceGap : undefined;
|
|
7324
7672
|
const planValidationMode = state.mode === "validating" || state.mode === "revalidating";
|
|
7325
7673
|
const missionValidationMode = state.mode === "mission_validating" || state.mode === "mission_revalidating" || state.mode === "mission_final_validating";
|
|
7326
7674
|
if (!planValidationMode && !missionValidationMode) {
|
|
@@ -7334,7 +7682,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7334
7682
|
await handleMissionFinalValidationFailure(ctx, mission, verdict, report);
|
|
7335
7683
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
7336
7684
|
}
|
|
7337
|
-
const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: verdict === "PASS" ? "" : report, lastValidationResult: verdict, nextAction: "Mission completed after typed final validation.", lastSummary: `Typed final mission validation ${verdict}.` });
|
|
7685
|
+
const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: verdict === "PASS" ? "" : report, lastValidationResult: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: "Mission completed after typed final validation.", lastSummary: `Typed final mission validation ${verdict}.` });
|
|
7338
7686
|
checkpointMission(completed, `Typed final mission validation ${verdict}.`, "Mission completed after final validation.", undefined, { validationResult: verdict });
|
|
7339
7687
|
completeMissionToAwaitingInput(ctx, completed, report, verdict, settings);
|
|
7340
7688
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
@@ -7343,7 +7691,10 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7343
7691
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
7344
7692
|
if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
|
|
7345
7693
|
if (verdict !== "PASS") {
|
|
7346
|
-
|
|
7694
|
+
const missionWithBooleans = { ...mission, concreteRepairableIssue, manualVerificationRequired, evidenceGap };
|
|
7695
|
+
deferWorkflowAction(pi, "handle mission validation failure after typed verdict", async () => {
|
|
7696
|
+
await handleMissionValidationFailure(ctx, missionWithBooleans, verdict, report);
|
|
7697
|
+
});
|
|
7347
7698
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
7348
7699
|
}
|
|
7349
7700
|
const index = mission.currentMilestoneIndex;
|
|
@@ -7352,25 +7703,36 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7352
7703
|
const nextIndex = Math.min(index + 1, Math.max(0, milestones.length - 1));
|
|
7353
7704
|
const done = index >= milestones.length - 1;
|
|
7354
7705
|
if (done && settings.missions.finalValidationEnabled === true) {
|
|
7355
|
-
const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", nextAction: "Run final comprehensive mission validation.", lastSummary: `Typed validation ${verdict}; final validation queued.` });
|
|
7706
|
+
const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: "Run final comprehensive mission validation.", lastSummary: `Typed validation ${verdict}; final validation queued.` });
|
|
7356
7707
|
checkpointMission(finalMission, `Typed validation ${verdict}; final mission validation queued.`, "Run final comprehensive validation for the whole mission.", milestone?.id, { validationResult: verdict });
|
|
7357
|
-
updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: report, validationVerdict: verdict }, ctx);
|
|
7358
|
-
|
|
7708
|
+
updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap }, ctx);
|
|
7709
|
+
deferWorkflowAction(pi, "begin final mission validation after typed PASS", async () => {
|
|
7710
|
+
await beginMissionFinalValidation(ctx, activeMission ?? finalMission, true);
|
|
7711
|
+
});
|
|
7359
7712
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
7360
7713
|
}
|
|
7361
7714
|
const shouldPause = mission.autonomy === "manual" || mission.pauseBetweenMilestones === true || mission.continueAcrossMilestones === false;
|
|
7362
7715
|
const nextStatus = done ? "completed" : shouldPause ? "paused" : "approved";
|
|
7363
7716
|
const nextMode = done ? "awaiting_mission_input" : shouldPause ? "mission_paused" : "mission_approved";
|
|
7364
|
-
const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", nextAction: done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`, lastSummary: `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.` });
|
|
7717
|
+
const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", concreteRepairableIssue, manualVerificationRequired, evidenceGap, nextAction: done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`, lastSummary: `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.` });
|
|
7365
7718
|
checkpointMission(nextMission, `Typed validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`, nextMission.nextAction ?? "Continue mission.", milestone?.id, { validationResult: verdict });
|
|
7366
7719
|
if (done) completeMissionToAwaitingInput(ctx, nextMission, report, verdict, settings);
|
|
7367
|
-
else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: report, validationVerdict: verdict }, ctx);
|
|
7368
|
-
if (!done && !shouldPause)
|
|
7720
|
+
else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap }, ctx);
|
|
7721
|
+
if (!done && !shouldPause) {
|
|
7722
|
+
await beginMissionRun(ctx, activeMission ?? nextMission, "continue", true);
|
|
7723
|
+
}
|
|
7369
7724
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
7370
7725
|
}
|
|
7371
7726
|
const validationStatus = planValidationStatusForVerdict(verdict);
|
|
7372
|
-
|
|
7373
|
-
|
|
7727
|
+
const noDefectPartialPass = verdict === "PARTIAL PASS" && concreteRepairableIssue === false;
|
|
7728
|
+
updateState({ mode: "validated", validationReport: report, validationVerdict: verdict, concreteRepairableIssue, manualVerificationRequired, evidenceGap, lastValidationCompletedAt: new Date().toISOString(), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: (verdict === "PASS" || noDefectPartialPass) ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: (verdict === "PASS" || noDefectPartialPass) ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
|
|
7729
|
+
if (verdict !== "PASS") {
|
|
7730
|
+
if (noDefectPartialPass) {
|
|
7731
|
+
await completePlanWorkflow(ctx, report, verdict);
|
|
7732
|
+
} else {
|
|
7733
|
+
deferWorkflowAction(pi, "handle typed validation failure", () => handleWorkflowValidationFailure(ctx, verdict, report));
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7374
7736
|
else if ((settings.workflow as typeof settings.workflow & { returnToPlanModeAfterWorkflow?: boolean }).returnToPlanModeAfterWorkflow !== false) deferWorkflowAction(pi, "complete plan workflow after typed validation", () => completePlanWorkflow(ctx, report, verdict));
|
|
7375
7737
|
else deferWorkflowAction(pi, "show final menu after typed validation", () => showFinalMenu(ctx));
|
|
7376
7738
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
@@ -7416,7 +7778,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7416
7778
|
if (!mission) return { ...typedToolAck(false), details: { accepted: false }, isError: true };
|
|
7417
7779
|
const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: verdict, lastReviewAttempt: verdict, lastSummary: `Mission reviewer verdict ${verdict}.` });
|
|
7418
7780
|
updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
|
|
7419
|
-
if (verdict !== "PASS" && verdict !== "NOTES")
|
|
7781
|
+
if (verdict !== "PASS" && verdict !== "NOTES") {
|
|
7782
|
+
deferWorkflowAction(pi, "handle typed mission reviewer failure", () => handleMissionReviewFailure(ctx, reviewed, verdict, text));
|
|
7783
|
+
} else {
|
|
7784
|
+
deferWorkflowAction(pi, "resume mission approval after reviewer pass", () => handleMissionApprovalHandoff(ctx, reviewed, "menu"));
|
|
7785
|
+
}
|
|
7420
7786
|
return { ...typedToolAck(), details: { accepted: true, verdict } };
|
|
7421
7787
|
}
|
|
7422
7788
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
@@ -7568,7 +7934,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7568
7934
|
|
|
7569
7935
|
const workflowContextInterruptionEvidence = (event: unknown, ctx: ExtensionContext): boolean => {
|
|
7570
7936
|
const eventText = workflowContextInterruptionEventText(event);
|
|
7571
|
-
if (/model_context_window_exceeded|context[_\s-]*window|context[_\s-]*length|maximum context|too many tokens|token limit|finish[_\s-]*reason/i.test(eventText)) return true;
|
|
7937
|
+
if (/model_context_window_exceeded|context[_\s-]*window|context[_\s-]*length|maximum context|too many tokens|token limit|finish[_\s-]*reason|websocket|connection[_\s-]*(?:error|lost|closed|dropped)|network[_\s-]*error|failed to fetch|econnrefused|econnreset|etimedout/i.test(eventText)) return true;
|
|
7572
7938
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
7573
7939
|
let nearTrigger = false;
|
|
7574
7940
|
try {
|
|
@@ -7667,16 +8033,23 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7667
8033
|
const diagrams = parseWorkflowMermaidSegments(text).filter((segment): segment is Extract<WorkflowMarkdownSegment, { type: "mermaid" }> => segment.type === "mermaid").slice(0, WORKFLOW_MERMAID_MAX_BLOCKS_PER_MESSAGE);
|
|
7668
8034
|
for (const diagram of diagrams) {
|
|
7669
8035
|
const details = await renderWorkflowMermaidPng(diagram.source, workflowDiagramTitle(diagram.source, diagram.index));
|
|
7670
|
-
piApi.sendMessage({ customType: WORKFLOW_MERMAID_MESSAGE_TYPE, content: details.title, display:
|
|
8036
|
+
piApi.sendMessage({ customType: WORKFLOW_MERMAID_MESSAGE_TYPE, content: details.title, display: false, details }, { triggerTurn: false });
|
|
7671
8037
|
}
|
|
7672
8038
|
};
|
|
7673
8039
|
|
|
7674
|
-
|
|
8040
|
+
type PlanProgressUpdateSource = "workflow_progress" | "marker" | "tool_fallback";
|
|
8041
|
+
type MarkPlanStepResult = { tracked: boolean; progress?: PlanProgressState; step?: PlanProgressState["steps"][number]; reason?: string };
|
|
8042
|
+
|
|
8043
|
+
const markPlanStep = (ctx: ExtensionContext | undefined, stepNumber: number, status: PlanStepStatus, source: PlanProgressUpdateSource = "tool_fallback"): MarkPlanStepResult => {
|
|
7675
8044
|
const settings = loadWorkflowSettings(ctx?.cwd ?? process.cwd());
|
|
7676
|
-
if (!workflowPlanProgressEnabled(settings)) return false;
|
|
8045
|
+
if (!workflowPlanProgressEnabled(settings)) return { tracked: false, reason: "disabled" };
|
|
7677
8046
|
const progress = mergePlanProgress(state, settings);
|
|
7678
|
-
if (!progress.steps.length) return false;
|
|
8047
|
+
if (!progress.steps.length) return { tracked: false, reason: "no_steps" };
|
|
7679
8048
|
const index = Math.max(0, Math.min(progress.steps.length - 1, Math.floor(stepNumber) - 1));
|
|
8049
|
+
const current = progress.steps[index];
|
|
8050
|
+
if (!current) return { tracked: false, progress, reason: "invalid_step" };
|
|
8051
|
+
if (status === "active" && (current.status === "completed" || current.status === "skipped")) return { tracked: false, progress, step: current, reason: "already_closed" };
|
|
8052
|
+
if (status === "completed" && current.status === "completed") return { tracked: false, progress, step: current, reason: "already_completed" };
|
|
7680
8053
|
const steps = progress.steps.map((step, i) => {
|
|
7681
8054
|
if (status === "active" && i !== index && step.status === "active") return { ...step, status: "pending" as PlanStepStatus };
|
|
7682
8055
|
if (i === index) return { ...step, status };
|
|
@@ -7688,14 +8061,43 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7688
8061
|
steps[nextOpen] = { ...steps[nextOpen], status: "active" as PlanStepStatus };
|
|
7689
8062
|
}
|
|
7690
8063
|
}
|
|
7691
|
-
|
|
7692
|
-
|
|
8064
|
+
const updatedProgress = { ...progress, steps, currentStepIndex: currentStepIndexForSteps(steps, index), nextAction: planNextActionText(state) };
|
|
8065
|
+
const patch: Partial<WorkflowState> = source === "workflow_progress"
|
|
8066
|
+
? { planProgress: updatedProgress, planProgressLastToolStep: index + 1, planProgressLastToolStatus: status, planProgressLastToolAt: new Date().toISOString() }
|
|
8067
|
+
: { planProgress: updatedProgress };
|
|
8068
|
+
updateState(patch, ctx);
|
|
8069
|
+
return { tracked: true, progress: updatedProgress, step: steps[index] };
|
|
8070
|
+
};
|
|
8071
|
+
|
|
8072
|
+
const planProgressBashShouldActivateStep = (command: string): boolean => {
|
|
8073
|
+
const trimmed = command.trim();
|
|
8074
|
+
return Boolean(trimmed) && !isBlockedExecuteCommand(trimmed) && !standardSafeReadOnlyBash(trimmed);
|
|
8075
|
+
};
|
|
8076
|
+
|
|
8077
|
+
const activateCurrentPlanStepForTool = (ctx: ExtensionContext, event: { toolName: string; input?: unknown }): void => {
|
|
8078
|
+
if (state.mode !== "executing" && state.mode !== "repairing") return;
|
|
8079
|
+
if (!state.planProgress?.steps?.length) return;
|
|
8080
|
+
if (state.planProgress.steps.some((step) => step.status === "active")) return;
|
|
8081
|
+
if (state.planProgress.steps.every((step) => step.status === "completed" || step.status === "skipped")) return;
|
|
8082
|
+
const toolActivates = event.toolName === "edit"
|
|
8083
|
+
|| event.toolName === "write"
|
|
8084
|
+
|| event.toolName === "subagent"
|
|
8085
|
+
|| (event.toolName === "bash" && planProgressBashShouldActivateStep(String((event.input as { command?: unknown } | undefined)?.command ?? "")));
|
|
8086
|
+
if (!toolActivates) return;
|
|
8087
|
+
const index = currentStepIndexForSteps(state.planProgress.steps, state.planProgress.currentStepIndex);
|
|
8088
|
+
markPlanStep(ctx, index + 1, "active");
|
|
7693
8089
|
};
|
|
7694
8090
|
|
|
7695
8091
|
const applyPlanProgressMarkers = (ctx: ExtensionContext, text: string): void => {
|
|
7696
|
-
|
|
7697
|
-
for (const match of text.matchAll(/
|
|
7698
|
-
|
|
8092
|
+
const ordered = new Set<string>();
|
|
8093
|
+
for (const match of text.matchAll(/WORKFLOW_STEP_(STARTED|COMPLETED|FAILED):\s*(\d+)/gi)) {
|
|
8094
|
+
const key = `${match[0].trim().toLowerCase()}-${match.index}`;
|
|
8095
|
+
if (ordered.has(key)) continue;
|
|
8096
|
+
ordered.add(key);
|
|
8097
|
+
const statusMap: Record<string, PlanStepStatus> = { started: "active", completed: "completed", failed: "failed" };
|
|
8098
|
+
const status = statusMap[match[1].toLowerCase()];
|
|
8099
|
+
if (status) markPlanStep(ctx, Number(match[2]), status, "marker");
|
|
8100
|
+
}
|
|
7699
8101
|
};
|
|
7700
8102
|
|
|
7701
8103
|
pi.registerTool({
|
|
@@ -7797,10 +8199,11 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7797
8199
|
pi.registerTool({
|
|
7798
8200
|
name: WORKFLOW_DIAGRAM_TOOL,
|
|
7799
8201
|
label: "Workflow Diagram",
|
|
7800
|
-
description: "
|
|
8202
|
+
description: "Create and render a Mermaid diagram inline with your prose. Call this tool at the point in your response where the diagram should appear -- not batched at the end.",
|
|
7801
8203
|
promptSnippet: "workflow_diagram({ title, source }) — render a Mermaid flowchart, sequenceDiagram, stateDiagram, classDiagram, erDiagram, or xychart-beta inline",
|
|
7802
8204
|
promptGuidelines: [
|
|
7803
|
-
"
|
|
8205
|
+
"CALL workflow_diagram AS YOU WRITE: Place each call immediately after the paragraph that introduces the concept being diagrammed. Prose introduces, then diagram visualizes -- this creates a natural explanatory flow.",
|
|
8206
|
+
"NEVER batch diagrams at the beginning or end of your response. Inline placement is required, not optional.",
|
|
7804
8207
|
"Workflow Suite renders diagrams with a uniform dark-mode visual standard. Do not include random Mermaid style/classDef/light-theme overrides unless the user explicitly asks.",
|
|
7805
8208
|
"Use meaningful, concise labels and the correct Mermaid type. Keep diagrams readable in the rendered card; split crowded flows instead of shrinking labels. Do not use tiny joke diagrams for substantive flows.",
|
|
7806
8209
|
],
|
|
@@ -7816,7 +8219,7 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7816
8219
|
return { content: [{ type: "text", text: "Invalid or unsupported Mermaid diagram. Use flowchart, graph, sequenceDiagram, stateDiagram, classDiagram, erDiagram, or xychart-beta." }], details: { title, source, diagramType: diagramType ?? "unsupported", status: "fallback", terminalDiagram: workflowMermaidFallbackTerminalDiagram(source, diagramType ?? "unsupported"), error: "unsupported diagram type" } };
|
|
7817
8220
|
}
|
|
7818
8221
|
const details = await renderWorkflowMermaidPng(source, title);
|
|
7819
|
-
return { content: [{ type: "text", text: details.status === "rendered" ?
|
|
8222
|
+
return { content: [{ type: "text", text: details.status === "rendered" ? title : `Diagram fallback: ${title}` }], details };
|
|
7820
8223
|
},
|
|
7821
8224
|
renderResult(result, _options, theme) {
|
|
7822
8225
|
return workflowMermaidRenderer({ content: result.content, details: result.details }, { expanded: false }, theme);
|
|
@@ -7837,12 +8240,12 @@ export default function workflowModes(pi: ExtensionAPI): void {
|
|
|
7837
8240
|
if (!Number.isFinite(step) || step < 1) {
|
|
7838
8241
|
return { content: [{ type: "text", text: "Invalid workflow progress step." }], details: { step, status, tracked: false }, isError: true };
|
|
7839
8242
|
}
|
|
7840
|
-
const
|
|
7841
|
-
if (!tracked) {
|
|
7842
|
-
return { content: [{ type: "text", text: "No Plan Mode progress steps are active. Progress not tracked." }], details: { step, status, tracked: false } };
|
|
8243
|
+
const result = markPlanStep(ctx, step, status, "workflow_progress");
|
|
8244
|
+
if (!result.tracked) {
|
|
8245
|
+
return { content: [{ type: "text", text: "No Plan Mode progress steps are active. Progress not tracked." }], details: { step, status, tracked: false, reason: result.reason } };
|
|
7843
8246
|
}
|
|
7844
|
-
const item =
|
|
7845
|
-
return { content: [{ type: "text", text: `Step ${step}: ${item?.title ?? "unknown"} → ${status}` }], details: { step, status, text: item?.title, tracked: true } };
|
|
8247
|
+
const item = result.step;
|
|
8248
|
+
return { content: [{ type: "text", text: `Step ${step}: ${item?.title ?? "unknown"} → ${status}` }], details: { step, status, text: item?.title, tracked: true, currentStepIndex: result.progress?.currentStepIndex } };
|
|
7846
8249
|
},
|
|
7847
8250
|
} as ToolDefinition);
|
|
7848
8251
|
|
|
@@ -8179,7 +8582,27 @@ ${reportExcerpt(validation, 2400)}
|
|
|
8179
8582
|
- Run /workflow status to confirm the final state.
|
|
8180
8583
|
- If blocked, inspect the reason above, then use /plan repair, /plan revalidate, /plan revise <feedback>, or /plan cancel as appropriate.
|
|
8181
8584
|
- If completed, start the next request with /plan or review the saved record with /workflow plans show ${state.planHistoryId ?? "latest"}.`);
|
|
8182
|
-
|
|
8585
|
+
const snapshot: NonNullable<WorkflowState["lastPlanStopSummary"]>["blockedPlanSnapshot"] = status === "blocked" && state.approvedPlan?.trim()
|
|
8586
|
+
? {
|
|
8587
|
+
task: state.task,
|
|
8588
|
+
originalTask: state.originalTask,
|
|
8589
|
+
approvedPlan: state.approvedPlan,
|
|
8590
|
+
planHistoryId: state.planHistoryId,
|
|
8591
|
+
approvedPlanHistoryId: state.approvedPlanHistoryId,
|
|
8592
|
+
executionSummary: state.executionSummary,
|
|
8593
|
+
validationReport: state.validationReport,
|
|
8594
|
+
validationVerdict: state.validationVerdict,
|
|
8595
|
+
lastValidationFailure: state.lastValidationFailure,
|
|
8596
|
+
lastRepairAttempt: state.lastRepairAttempt,
|
|
8597
|
+
repairHistory: state.repairHistory,
|
|
8598
|
+
lastRepairStatus: state.lastRepairStatus,
|
|
8599
|
+
currentValidationRetry: state.currentValidationRetry,
|
|
8600
|
+
workflowValidationRetryCount: state.workflowValidationRetryCount,
|
|
8601
|
+
planRuntime: state.planRuntime,
|
|
8602
|
+
planProgress: state.planProgress,
|
|
8603
|
+
}
|
|
8604
|
+
: undefined;
|
|
8605
|
+
return { stoppedAt: new Date().toISOString(), kind: "plan", status, title, summary, blockedPlanSnapshot: snapshot };
|
|
8183
8606
|
};
|
|
8184
8607
|
|
|
8185
8608
|
const queuePlanTerminalSummary = (ctx: ExtensionContext, status: "completed" | "blocked", title: string, options: { validationText?: string; verdict?: WorkflowState["validationVerdict"]; reason?: string } = {}): NonNullable<WorkflowState["lastPlanStopSummary"]> => {
|
|
@@ -8635,7 +9058,9 @@ ${renderMissionProgress(mission, settings)}
|
|
|
8635
9058
|
return show(pi, `# Mission Repair ${stale ? "Stale" : "Active"}\n\n${guidance}\n\n${renderMissionStatus(mission)}`);
|
|
8636
9059
|
}
|
|
8637
9060
|
if (mission.status === "running" || mission.status === "checkpointing") {
|
|
8638
|
-
|
|
9061
|
+
const checkpointed = checkpointMission(mission, "Mission resume re-triggering execution from active state.", "Execute current mission milestone, then run mission validation.");
|
|
9062
|
+
await beginMissionRun(ctx, checkpointed, "resume");
|
|
9063
|
+
return;
|
|
8639
9064
|
}
|
|
8640
9065
|
return show(pi, `# Mission Resume\n\nNo safe automatic resume route exists for mission status: ${mission.status}.\n\n${renderMissionStatus(mission)}`);
|
|
8641
9066
|
}
|
|
@@ -8881,10 +9306,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
8881
9306
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
8882
9307
|
const retry = mission.currentReviewRetry ?? 0;
|
|
8883
9308
|
const missionRetry = missionReviewRetryCount(mission);
|
|
8884
|
-
const
|
|
9309
|
+
const config = missionRepairRetryConfig(settings, "review");
|
|
9310
|
+
const maxRetries = Math.max(config.maxRetriesPerItem, maxMissionReviewRetries(mission, settings));
|
|
8885
9311
|
const failure = `Reviewer ${verdict}. ${compact(reviewText, 1200)}`;
|
|
8886
9312
|
const canRepairPlan = Boolean(mission.planText?.trim()) && (verdict === "NEEDS REPAIR" || verdict === "FAIL" || verdict === "BLOCKED");
|
|
8887
|
-
const repairEnabled =
|
|
9313
|
+
const repairEnabled = config.autoRepairFailures && config.retryMode !== "off";
|
|
8888
9314
|
if (!repairEnabled || !canRepairPlan || retry >= maxRetries) {
|
|
8889
9315
|
const reason = !repairEnabled ? "Mission review repair disabled." : !canRepairPlan ? "Mission plan repair context unavailable." : "Mission review repair retry limit exhausted.";
|
|
8890
9316
|
const blocked = saveActiveMission({ ...mission, status: "planned", reviewerReport: reviewText, reviewerVerdict: verdict, lastReviewFailure: failure, lastReviewAttempt: reason, lastReviewRepairStatus: "blocked", reviewRepairInProgress: false, reviewHistory: appendMissionReviewHistory(mission, { timestamp: new Date().toISOString(), retry, status: "blocked", reviewFailure: compact(failure, 800), nextAction: reason }), nextAction: "Revise the mission plan, rerun /mission review, or approve manually if safe.", lastSummary: "Mission reviewer blocked approval until the milestone plan is addressed." });
|
|
@@ -8916,7 +9342,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
8916
9342
|
}
|
|
8917
9343
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
8918
9344
|
const autoAction = mission.autonomy !== "manual" && settings.missions.autoRunAfterApproval === true ? "Mission approved; starting current milestone automatically." : "Mission approved; choose Run Mission Now from the action menu or use /mission continue.";
|
|
8919
|
-
const next = saveActiveMission({ ...mission, status: "approved", approvalRequired: false, lastSummary: "Mission plan approved; dynamic handoff is ready.", nextAction: autoAction, lastStopReason: "", lastBlockReason: "" });
|
|
9345
|
+
const next = saveActiveMission({ ...mission, status: "approved", approvalRequired: false, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, lastSummary: "Mission plan approved; dynamic handoff is ready.", nextAction: autoAction, lastStopReason: "", lastBlockReason: "" });
|
|
8920
9346
|
updateState({
|
|
8921
9347
|
mode: "mission_approved",
|
|
8922
9348
|
activeMissionId: next.id,
|
|
@@ -8944,8 +9370,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
8944
9370
|
}
|
|
8945
9371
|
|
|
8946
9372
|
function missionContinuationBlock(mission: MissionState, settings: ReturnType<typeof loadWorkflowSettings>, action: "run" | "continue" | "next" | "resume"): string | undefined {
|
|
8947
|
-
|
|
8948
|
-
|
|
9373
|
+
if (action !== "resume") {
|
|
9374
|
+
const runtimeBlocked = missionRuntimeBudgetBlock(mission, settings);
|
|
9375
|
+
if (runtimeBlocked) return runtimeBlocked;
|
|
9376
|
+
}
|
|
8949
9377
|
if (mission.autonomy === "full_auto" && !missionAllowsFullAuto(mission, settings)) return "Cannot continue: full auto requested but allowFullAuto=false.";
|
|
8950
9378
|
if (mission.status === "draft") return mission.milestones.length === 0
|
|
8951
9379
|
? "Cannot continue: mission is draft and has no approved milestone plan. Run /mission plan, then /mission approve, then /mission continue."
|
|
@@ -9013,6 +9441,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9013
9441
|
return;
|
|
9014
9442
|
}
|
|
9015
9443
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route), planner: running.modelsUsed?.planner } }, ctx);
|
|
9444
|
+
saveActiveMission({ ...running, modelsUsed: { ...(running.modelsUsed ?? {}), executor: modelLabel(route) } });
|
|
9016
9445
|
queueAgentTurn(pi, missionRuntimePrompt(running, settings, phasePreflightBlocks.Execution), auto ? "mission-run-trigger" : "mission-run-manual-trigger");
|
|
9017
9446
|
}
|
|
9018
9447
|
|
|
@@ -9136,6 +9565,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9136
9565
|
(["Planning", "Execution", "Repair", "Review", "Validation"] as SubagentPhase[]).forEach(resetSubagentPhaseUsage);
|
|
9137
9566
|
};
|
|
9138
9567
|
|
|
9568
|
+
const snapshotSubagentPhaseUsage = (phase: SubagentPhase) => ({
|
|
9569
|
+
count: subagentUsageByPhase[phase] ?? 0,
|
|
9570
|
+
names: new Set(subagentNamesByPhase[phase]),
|
|
9571
|
+
});
|
|
9572
|
+
|
|
9573
|
+
const restoreSubagentPhaseUsage = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }) => {
|
|
9574
|
+
subagentUsageByPhase[phase] = snapshot.count;
|
|
9575
|
+
subagentNamesByPhase[phase] = snapshot.names;
|
|
9576
|
+
};
|
|
9577
|
+
|
|
9578
|
+
const priorSubagentUsageSatisfiesForced = (phase: SubagentPhase, snapshot: { count: number; names: Set<string> }, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number } }): boolean => {
|
|
9579
|
+
const policy = override?.policy ?? phasePolicy(settings, phase);
|
|
9580
|
+
if (policy !== "forced") return false;
|
|
9581
|
+
const required = workerTargetForPolicy("forced", override?.workers ?? workerCount(settings, phase));
|
|
9582
|
+
return snapshot.count >= required;
|
|
9583
|
+
};
|
|
9584
|
+
|
|
9139
9585
|
const beginForcedSubagentPhase = (ctx: ExtensionContext, phase: SubagentPhase, settings: ReturnType<typeof loadWorkflowSettings>, override?: { policy?: SubagentPolicyValue; workers?: { deep: number; maximum: number }; label?: string }): boolean => {
|
|
9140
9586
|
resetSubagentPhaseUsage(phase);
|
|
9141
9587
|
phasePreflightBlocks[phase] = undefined;
|
|
@@ -9206,7 +9652,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9206
9652
|
const repoLock = loadWorkflowSettings().safety.repoLockEnabled === true ? " Repo Lock is enabled: keep project file discovery and commands inside the current repository. Project .pi files may be read for context, but do not edit protected .pi control/config paths through normal tools. Do not inspect sibling repositories, unrelated home-directory paths, or the live Pi runtime." : "";
|
|
9207
9653
|
const base = `Forced ${label} sub-agent preflight worker ${index + 1}/${required}. You are running before the main ${label} agent. Stay read-only unless your agent contract explicitly allows otherwise; do not edit, commit, push, deploy, mutate databases, or touch secrets/runtime state.${repoLock} Return concise findings for the main ${label} agent.`;
|
|
9208
9654
|
if (phase === "Planning") return `${base}\n\nPlanning target:\n${context.task ?? mission?.goal ?? state.task ?? state.originalTask ?? "(not recorded)"}\n\n${mission ? `Mission ID: ${mission.id}\nAutonomy: ${mission.autonomy}\nExisting milestones: ${mission.milestones.length}\n` : ""}\nFocus for ${agent}: identify project rules, likely files/systems, ambiguity, risks, validation needs, off-limits files, and specific recommendations for the final plan.`;
|
|
9209
|
-
if (phase === "Execution")
|
|
9655
|
+
if (phase === "Execution") {
|
|
9656
|
+
const settings = loadWorkflowSettings();
|
|
9657
|
+
const stepGated = settings.workflow.validateAfterEachStep === true || settings.workflow.requireApprovalPerStep === true;
|
|
9658
|
+
const progress = state.planProgress;
|
|
9659
|
+
const stepIndex = progress?.currentStepIndex ?? state.planExecutionStepIndex ?? 0;
|
|
9660
|
+
const step = progress?.steps?.[stepIndex];
|
|
9661
|
+
const stepBoundary = stepGated && step ? `\n\nCurrent Plan step boundary:\nStep ${stepIndex + 1}: ${step.title}\nInspect and reason only within this current-step boundary. Do not complete later Plan steps.` : "";
|
|
9662
|
+
return `${base}\n\nApproved execution contract:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}${stepBoundary}\n\nFocus for ${agent}: inspect likely files, identify implementation hazards, perform scoped work allowed by your configured tools and agent contract, propose safe edit order, surface regression risks, and recommend validation checks. Stay inside the approved Plan scope, avoid conflicting parallel edits, and do not mark Plan steps complete for the parent executor.`;
|
|
9663
|
+
}
|
|
9210
9664
|
if (phase === "Repair") return `${base}\n\nApproved scope:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}\n\nValidation failure / repair target:\n${context.validationFailure ?? state.lastValidationFailure ?? state.validationReport ?? mission?.lastValidationFailure ?? "(not recorded)"}\n\nFocus for ${agent}: identify concrete repair target, files to inspect, safe patch strategy, and revalidation checks. Do not repair directly.`;
|
|
9211
9665
|
if (phase === "Review") return `${base}\n\nApproved plan to challenge before execution:\n${context.approvedPlan ?? state.approvedPlan ?? "(not recorded)"}\n\nFocus for ${agent}: challenge scope, missing requirements, risks, off-limits files, execution ordering, and validation coverage.`;
|
|
9212
9666
|
return `${base}\n\nApproved plan / milestone:\n${context.approvedPlan ?? state.approvedPlan ?? (mission ? missionRunPlan(mission) : "(not recorded)")}\n\nExecution summary:\n${context.executionSummary ?? state.executionSummary ?? "(not recorded)"}\n\nFocus for ${agent}: independently validate requirement coverage, changed-file risks, tests/build evidence, manual QA caveats, and concrete PASS/PARTIAL PASS/FAIL evidence.`;
|
|
@@ -9317,6 +9771,22 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9317
9771
|
return false;
|
|
9318
9772
|
};
|
|
9319
9773
|
|
|
9774
|
+
const planForcedSubagentPreflightReconcile = (ctx: ExtensionContext, phase: SubagentPhase): void => {
|
|
9775
|
+
const block = phasePreflightBlocks[phase];
|
|
9776
|
+
if (!block) return;
|
|
9777
|
+
const settings = loadWorkflowSettings(ctx.cwd);
|
|
9778
|
+
const policy = phasePolicy(settings, phase);
|
|
9779
|
+
if (policy !== "forced") return;
|
|
9780
|
+
const observedMatch = block.match(/Observed workers: (\d+)/);
|
|
9781
|
+
if (!observedMatch) return;
|
|
9782
|
+
const observed = parseInt(observedMatch[1], 10);
|
|
9783
|
+
if (observed <= 0) return;
|
|
9784
|
+
const current = subagentUsageByPhase[phase] ?? 0;
|
|
9785
|
+
if (observed > current) {
|
|
9786
|
+
subagentUsageByPhase[phase] = observed;
|
|
9787
|
+
}
|
|
9788
|
+
};
|
|
9789
|
+
|
|
9320
9790
|
const rememberStandardSubagentPreflight = (ctx: ExtensionContext, phase: SubagentPhase, task: string, required: number) => {
|
|
9321
9791
|
const agents = Array.from(subagentNamesByPhase[phase] ?? []);
|
|
9322
9792
|
updateState({
|
|
@@ -9563,6 +10033,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9563
10033
|
planProgress: workflowPlanProgressEnabled(settings) ? createPlanProgress(planText, settings, "approved") : undefined,
|
|
9564
10034
|
}, ctx);
|
|
9565
10035
|
persistCurrentPlan(ctx, "approved", saveReason);
|
|
10036
|
+
pi.setActiveTools(executionToolsFor(settings));
|
|
9566
10037
|
deferWorkflowAction(pi, "begin next phase after plan approval", async () => {
|
|
9567
10038
|
const started = await continueAfterPlanApproval(ctx, true);
|
|
9568
10039
|
if (!started) show(pi, "# Handoff Blocked\n\nPlan was approved, but automatic continuation could not start. Use /plan continue after fixing the blocker.");
|
|
@@ -9664,20 +10135,24 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9664
10135
|
const steps = settings.workflow.validateAfterEachStep === true || settings.workflow.requireApprovalPerStep === true
|
|
9665
10136
|
? baseSteps.map((step, index) => index === activeIndex ? { ...step, status: "active" as PlanStepStatus } : step.status === "active" ? { ...step, status: "pending" as PlanStepStatus } : step)
|
|
9666
10137
|
: baseSteps;
|
|
9667
|
-
updateState({ mode: "executing", planExecutionStepIndex: activeIndex, planProgress: { ...currentProgress, steps, currentStepIndex: currentStepIndexForSteps(steps, activeIndex) } }, ctx);
|
|
9668
|
-
|
|
10138
|
+
updateState({ mode: "executing", planExecutionStepIndex: activeIndex, planProgress: { ...currentProgress, steps, currentStepIndex: currentStepIndexForSteps(steps, activeIndex) }, planProgressLastToolStep: undefined, planProgressLastToolStatus: undefined, planProgressLastToolAt: undefined }, ctx);
|
|
10139
|
+
const executionPreflightExists = phasePreflightBlocks.Execution != null;
|
|
10140
|
+
if (!executionPreflightExists && !beginForcedSubagentPhase(ctx, "Execution", settings)) {
|
|
9669
10141
|
const reason = "Execution blocked by forced sub-agent policy availability gate.";
|
|
10142
|
+
pi.setActiveTools(planToolsFor(settings));
|
|
9670
10143
|
updateState({ mode: previousMode === "reviewed" ? "reviewed" : "plan_approved", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: previousMode === "reviewed" ? "reviewed" : "plan_approved" }, settings, { lifecycleStatus: "blocked", nextAction: "fix execution policy blocker" }, state.approvedPlan) : state.planProgress }, ctx);
|
|
9671
10144
|
queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { reason });
|
|
9672
10145
|
return false;
|
|
9673
10146
|
}
|
|
9674
10147
|
const route = isMissionWorkflowMode(state) ? await applyMissionModelForRole(pi, ctx, "executor", { cwd: ctx.cwd }) : await applyModelForRole(pi, ctx, "executor", { cwd: ctx.cwd });
|
|
9675
10148
|
if (!route) {
|
|
10149
|
+
pi.setActiveTools(planToolsFor(settings));
|
|
9676
10150
|
updateState({ mode: previousMode === "reviewed" ? "reviewed" : "plan_approved" }, ctx);
|
|
9677
10151
|
show(pi, "# Execution Blocked\n\nExecutor is enabled for this workflow path but its model could not be applied. Check /workflow-settings list, provider availability, and credentials, then run /plan continue.");
|
|
9678
10152
|
return false;
|
|
9679
10153
|
}
|
|
9680
10154
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
10155
|
+
pi.setActiveTools(executionToolsFor(settings));
|
|
9681
10156
|
queueAgentTurn(pi, executePrompt(state, settings, phasePreflightBlocks.Execution), auto ? "workflow-execute-trigger" : "workflow-execute-manual-trigger");
|
|
9682
10157
|
return true;
|
|
9683
10158
|
}
|
|
@@ -9704,6 +10179,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9704
10179
|
return false;
|
|
9705
10180
|
}
|
|
9706
10181
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), reviewer: modelLabel(route) } }, ctx);
|
|
10182
|
+
pi.setActiveTools(reviewToolsFor(settings));
|
|
9707
10183
|
if (auto) {
|
|
9708
10184
|
queueAgentTurn(pi, "Review the approved plan before execution begins.", "workflow-review-trigger");
|
|
9709
10185
|
} else {
|
|
@@ -9713,6 +10189,11 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9713
10189
|
}
|
|
9714
10190
|
|
|
9715
10191
|
async function continueAfterPlanApproval(ctx: ExtensionContext, auto = false): Promise<boolean> {
|
|
10192
|
+
if (!state.approvedPlan) {
|
|
10193
|
+
show(pi, "# Workflow Handoff Failed\n\nNo approved plan exists. Run `/plan <task>` to create and approve a new plan.");
|
|
10194
|
+
updateState({ mode: "awaiting_plan_input" }, ctx);
|
|
10195
|
+
return false;
|
|
10196
|
+
}
|
|
9716
10197
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
9717
10198
|
if (settings.models.reviewer.enabled && roleIsConfigured(settings.models.reviewer)) {
|
|
9718
10199
|
if (settings.workflow.autoRunReviewerBeforeExecute === true) {
|
|
@@ -9743,7 +10224,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9743
10224
|
clearTypedHandoff(ctx, revalidate ? "Plan revalidation" : "Plan validation");
|
|
9744
10225
|
pi.setActiveTools(validationToolsFor(settings));
|
|
9745
10226
|
updateState({ mode: revalidate ? "revalidating" : "validating", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: revalidate ? "revalidating" : "validating" }, settings, { lifecycleStatus: revalidate ? "revalidating" : "validating", validationStatus: "running", nextAction: "validation result" }, state.approvedPlan) : state.planProgress }, ctx);
|
|
9746
|
-
|
|
10227
|
+
const validationPreflightExists = phasePreflightBlocks.Validation != null;
|
|
10228
|
+
if (!validationPreflightExists && !beginForcedSubagentPhase(ctx, "Validation", settings)) {
|
|
9747
10229
|
const reason = "Validation blocked by forced sub-agent policy availability gate.";
|
|
9748
10230
|
updateState({ mode: "validated", lastRepairStatus: revalidate ? "blocked" : (state.lastRepairStatus ?? "none"), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "fix validation policy blocker then /plan revalidate" }, state.approvedPlan) : state.planProgress, lastRepairAttempt: revalidate ? reason : state.lastRepairAttempt }, ctx);
|
|
9749
10231
|
queuePlanTerminalSummary(ctx, "blocked", "Plan validation blocked", { reason });
|
|
@@ -9759,8 +10241,9 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9759
10241
|
return false;
|
|
9760
10242
|
}
|
|
9761
10243
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
|
|
10244
|
+
pi.setActiveTools(validationToolsFor(settings));
|
|
9762
10245
|
if (auto) {
|
|
9763
|
-
queueAgentTurn(pi,
|
|
10246
|
+
queueAgentTurn(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation), revalidate ? "workflow-revalidate-trigger" : "workflow-validate-trigger");
|
|
9764
10247
|
} else {
|
|
9765
10248
|
queueWorkflowPrompt(pi, validatePrompt(state, settings, phasePreflightBlocks.Validation));
|
|
9766
10249
|
}
|
|
@@ -9835,6 +10318,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9835
10318
|
return;
|
|
9836
10319
|
}
|
|
9837
10320
|
updateState({ modelsUsed: { ...(state.modelsUsed ?? {}), validator: modelLabel(route) } }, ctx);
|
|
10321
|
+
saveActiveMission({ ...validating, modelsUsed: { ...(validating.modelsUsed ?? {}), validator: modelLabel(route) } });
|
|
9838
10322
|
const prompt = missionValidationPrompt(validating, settings, state.executionSummary, phasePreflightBlocks.Validation);
|
|
9839
10323
|
if (auto) {
|
|
9840
10324
|
queueAgentTurn(pi, revalidate ? "Revalidate the completed mission milestone." : "Validate the completed mission milestone.", revalidate ? "mission-revalidate-trigger" : "mission-validate-trigger");
|
|
@@ -9846,7 +10330,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9846
10330
|
}
|
|
9847
10331
|
|
|
9848
10332
|
function showBlockedPlanRecoveryMenu(ctx: ExtensionContext): void {
|
|
9849
|
-
deferWorkflowAction(pi, "show
|
|
10333
|
+
deferWorkflowAction(pi, "show plan resume after blocked plan stop", () => handlePlanResume(ctx));
|
|
9850
10334
|
}
|
|
9851
10335
|
|
|
9852
10336
|
function showBlockedMissionRecoveryMenu(ctx: ExtensionContext): void {
|
|
@@ -9860,6 +10344,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9860
10344
|
function planRepairCompletedPendingRevalidation(): boolean {
|
|
9861
10345
|
if (state.mode === "executing" || state.mode === "validating" || state.mode === "repairing" || state.mode === "revalidating") return false;
|
|
9862
10346
|
if (state.validationVerdict === "PASS") return false;
|
|
10347
|
+
const validationCompletedAt = state.lastValidationCompletedAt
|
|
10348
|
+
?? (state.lastWorkflowHandoff?.type === "workflow_validation_result" ? state.lastWorkflowHandoff.createdAt : undefined);
|
|
10349
|
+
if (validationCompletedAt) {
|
|
10350
|
+
const latestCompletedRepair = [...(state.repairHistory ?? [])].reverse().find((entry) => entry.status === "completed");
|
|
10351
|
+
if (latestCompletedRepair?.timestamp && validationCompletedAt > latestCompletedRepair.timestamp) return false;
|
|
10352
|
+
}
|
|
9863
10353
|
const latestRepair = [...(state.repairHistory ?? [])].reverse().find((entry) => entry.status === "completed" || entry.status === "blocked" || entry.status === "running");
|
|
9864
10354
|
return state.lastRepairStatus === "completed"
|
|
9865
10355
|
|| latestRepair?.status === "completed"
|
|
@@ -9882,6 +10372,12 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9882
10372
|
async function startWorkflowRepair(ctx: ExtensionContext, source: "auto" | "user" = "auto") {
|
|
9883
10373
|
if (!state.approvedPlan) return show(pi, "# Plan Repair Refused\n\nNo approved plan exists.");
|
|
9884
10374
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
10375
|
+
if (state.concreteRepairableIssue === false) {
|
|
10376
|
+
const reason = "No concrete repairable issue — manual verification only.";
|
|
10377
|
+
updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: state.lastValidationFailure || state.validationReport, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual verification or revalidate" }) : state.planProgress }, ctx);
|
|
10378
|
+
show(pi, `# Repair Not Needed\n\n${reason}\n\nUse /plan revalidate after manual QA or /plan revise to update scope.`);
|
|
10379
|
+
return;
|
|
10380
|
+
}
|
|
9885
10381
|
clearTypedHandoff(ctx, "Plan repair");
|
|
9886
10382
|
const failure = state.lastValidationFailure || state.validationReport || "No validation failure details recorded.";
|
|
9887
10383
|
const currentRetry = state.currentValidationRetry ?? 0;
|
|
@@ -9893,14 +10389,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9893
10389
|
await beginValidation(ctx, true, true);
|
|
9894
10390
|
return;
|
|
9895
10391
|
}
|
|
9896
|
-
|
|
9897
|
-
const reason = "Repair refused: validation recorded only manual/visual/browser verification without a concrete repairable issue.";
|
|
9898
|
-
updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual verification or revalidate" }) : state.planProgress }, ctx);
|
|
9899
|
-
show(pi, `# Plan Repair Refused\n\n${reason}\n\nRun manual QA, then /plan revalidate. Use /plan revise <feedback> if scope changed.`);
|
|
9900
|
-
showBlockedPlanRecoveryMenu(ctx);
|
|
9901
|
-
return;
|
|
9902
|
-
}
|
|
9903
|
-
const decision = resolveRepairRetryDecision({ gate: "validation", verdict: state.validationVerdict ?? "FAIL", report: failure, settings, state, itemRetry: currentRetry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan) });
|
|
10392
|
+
const decision = resolveRepairRetryDecision({ gate: "validation", verdict: state.validationVerdict ?? "FAIL", report: failure, settings, state, itemRetry: currentRetry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan), manualOverride: source !== "auto" });
|
|
9904
10393
|
if (!decision.allowed) {
|
|
9905
10394
|
const reason = decision.reason ?? "repair requires approval or safety gate.";
|
|
9906
10395
|
updateState({ mode: "validated", lastRepairStatus: "blocked", lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "manual repair or revise plan" }) : state.planProgress, lastRepairAttempt: reason, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry, workflowRetryCount: workflowRetry, maxRetriesPerItem: decision.maxRetriesPerItem, maxRetriesPerWorkflow: decision.maxRetriesPerWorkflow, lastFailure: failure, lastAttempt: reason, status: "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
|
|
@@ -9908,7 +10397,8 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9908
10397
|
showBlockedPlanRecoveryMenu(ctx);
|
|
9909
10398
|
return;
|
|
9910
10399
|
}
|
|
9911
|
-
|
|
10400
|
+
const repairPreflightExists = phasePreflightBlocks.Repair != null;
|
|
10401
|
+
if (!repairPreflightExists && !beginForcedSubagentPhase(ctx, "Repair", settings)) {
|
|
9912
10402
|
const reason = "Repair blocked by forced sub-agent policy availability gate.";
|
|
9913
10403
|
updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: reason }) : state.planProgress, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
|
|
9914
10404
|
queuePlanTerminalSummary(ctx, "blocked", "Plan repair blocked", { validationText: failure, reason });
|
|
@@ -9921,24 +10411,41 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9921
10411
|
showBlockedPlanRecoveryMenu(ctx);
|
|
9922
10412
|
return;
|
|
9923
10413
|
}
|
|
10414
|
+
const manualOverride = decision.manualOverride === true;
|
|
10415
|
+
const verdict = state.validationVerdict ?? "UNKNOWN";
|
|
10416
|
+
if (!manualOverride && verdict !== "FAIL" && !validationReportHasRepairableIssue(state.validationReport ?? state.lastValidationFailure ?? "")) {
|
|
10417
|
+
const reason = "No concrete repairable issue found; manual/browser verification is required before the plan can proceed.";
|
|
10418
|
+
updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: reason, lastValidationFailure: failure, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: reason }) : state.planProgress, repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: currentRetry, status: "blocked", validationFailure: compact(failure, 800), nextAction: reason }) }, ctx);
|
|
10419
|
+
queuePlanTerminalSummary(ctx, "blocked", "Plan repair blocked", { validationText: failure, reason });
|
|
10420
|
+
show(pi, `# Plan Repair Blocked\n\n${reason}\n\nNo repair retry was consumed. Perform manual verification, then use:\n- /plan revalidate\n- /plan repair (manual override)\n- /plan revise <feedback>`);
|
|
10421
|
+
showBlockedPlanRecoveryMenu(ctx);
|
|
10422
|
+
return;
|
|
10423
|
+
}
|
|
9924
10424
|
const retry = currentRetry + 1;
|
|
9925
10425
|
const totalRetry = workflowRetry + 1;
|
|
10426
|
+
const effectiveMaxRetries = Math.max(maxRetries, retry);
|
|
10427
|
+
const effectiveMaxWorkflow = Math.max(maxWorkflowRetries, totalRetry);
|
|
9926
10428
|
updateState({
|
|
9927
10429
|
mode: "repairing",
|
|
9928
10430
|
currentValidationRetry: retry,
|
|
9929
10431
|
workflowValidationRetryCount: totalRetry,
|
|
9930
|
-
maxValidationRetriesPerPlan:
|
|
9931
|
-
maxValidationRetriesPerWorkflow:
|
|
10432
|
+
maxValidationRetriesPerPlan: effectiveMaxRetries,
|
|
10433
|
+
maxValidationRetriesPerWorkflow: effectiveMaxWorkflow,
|
|
9932
10434
|
lastRepairStatus: "running",
|
|
9933
|
-
repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: totalRetry, maxRetriesPerItem:
|
|
9934
|
-
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "repairing", currentValidationRetry: retry, lastRepairStatus: "running" }, settings, { lifecycleStatus: "repairing", validationStatus: "fail", repairRetry: retry, maxRepairRetries:
|
|
9935
|
-
|
|
10435
|
+
repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: totalRetry, maxRetriesPerItem: effectiveMaxRetries, maxRetriesPerWorkflow: effectiveMaxWorkflow, lastFailure: failure, lastAttempt: `${manualOverride ? "Manual repair retry override" : `Plan repair retry`} ${retry}/${effectiveMaxRetries}; workflow retry ${totalRetry}/${effectiveMaxWorkflow} started by ${source}.`, status: "running", inProgress: true, history: [...(state.repairRetryState?.validation?.history ?? []), { timestamp: new Date().toISOString(), retry, status: "running" as const, failure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }].slice(-20) } },
|
|
10436
|
+
planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "repairing", currentValidationRetry: retry, lastRepairStatus: "running" }, settings, { lifecycleStatus: "repairing", validationStatus: "fail", repairRetry: retry, maxRepairRetries: effectiveMaxRetries, repairStatus: "running", nextAction: "repair executor then validator" }, state.approvedPlan) : state.planProgress,
|
|
10437
|
+
planProgressLastToolStep: undefined,
|
|
10438
|
+
planProgressLastToolStatus: undefined,
|
|
10439
|
+
planProgressLastToolAt: undefined,
|
|
10440
|
+
lastRepairAttempt: `${manualOverride ? "Manual repair retry override" : `Plan repair retry`} ${retry}/${effectiveMaxRetries}; workflow retry ${totalRetry}/${effectiveMaxWorkflow} started by ${source}.`,
|
|
9936
10441
|
lastValidationFailure: failure,
|
|
9937
10442
|
repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry, status: "running", validationFailure: compact(failure, 800), nextAction: "Repair approved-plan validation failure, then revalidate." }),
|
|
9938
10443
|
modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) },
|
|
9939
10444
|
}, ctx);
|
|
9940
10445
|
pi.setActiveTools(executionToolsFor(settings));
|
|
9941
|
-
if (source !== "auto") show(pi,
|
|
10446
|
+
if (source !== "auto") show(pi, manualOverride
|
|
10447
|
+
? `# Plan Repair Started (Manual Override)\n\nRetry: ${retry} (capped at ${maxRetries}) per plan\nWorkflow Retry: ${totalRetry} (capped at ${maxWorkflowRetries}) total\n\nManual override bypassed automatic retry limits. Repairing only the approved-plan validation failure.`
|
|
10448
|
+
: `# Plan Repair Started\n\nRetry: ${retry} of ${effectiveMaxRetries} per plan\nWorkflow Retry: ${totalRetry} of ${effectiveMaxWorkflow} total\n\nRepairing only the approved-plan validation failure.`);
|
|
9942
10449
|
queueWorkflowPrompt(pi, workflowRepairPrompt(state, settings, phasePreflightBlocks.Repair));
|
|
9943
10450
|
}
|
|
9944
10451
|
|
|
@@ -9973,6 +10480,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9973
10480
|
function recoverPlanFromHistory(ctx: ExtensionContext, plan: SavedWorkflowPlan): void {
|
|
9974
10481
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
9975
10482
|
activeMission = undefined;
|
|
10483
|
+
const hasPersistedProgress = plan.planProgress && plan.planProgress.steps.some((s) => s.status === "completed" || s.status === "active");
|
|
9976
10484
|
updateState({
|
|
9977
10485
|
mode: "plan_approved",
|
|
9978
10486
|
activeMissionId: undefined,
|
|
@@ -9988,24 +10496,25 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
9988
10496
|
reviewerReport: undefined,
|
|
9989
10497
|
reviewerVerdict: undefined,
|
|
9990
10498
|
currentReviewRetry: 0,
|
|
9991
|
-
workflowReviewRetryCount: 0,
|
|
10499
|
+
workflowReviewRetryCount: plan.reviewHistory?.length ?? 0,
|
|
9992
10500
|
maxReviewRetriesPerPlan: undefined,
|
|
9993
10501
|
maxReviewRetriesPerWorkflow: undefined,
|
|
9994
10502
|
lastReviewFailure: undefined,
|
|
9995
10503
|
lastReviewAttempt: undefined,
|
|
9996
10504
|
lastReviewRepairStatus: "none",
|
|
9997
|
-
reviewHistory:
|
|
10505
|
+
reviewHistory: plan.reviewHistory,
|
|
9998
10506
|
reviewRepairInProgress: undefined,
|
|
9999
10507
|
lastValidationFailure: undefined,
|
|
10000
10508
|
lastRepairAttempt: undefined,
|
|
10001
|
-
repairHistory:
|
|
10509
|
+
repairHistory: plan.repairHistory,
|
|
10002
10510
|
lastRepairStatus: "none",
|
|
10003
|
-
currentValidationRetry: 0,
|
|
10004
|
-
workflowValidationRetryCount: 0,
|
|
10005
|
-
planStepValidationIndex:
|
|
10006
|
-
planExecutionStepIndex:
|
|
10007
|
-
planRuntime:
|
|
10008
|
-
|
|
10511
|
+
currentValidationRetry: plan.currentValidationRetry ?? 0,
|
|
10512
|
+
workflowValidationRetryCount: plan.workflowValidationRetryCount ?? 0,
|
|
10513
|
+
planStepValidationIndex: plan.planStepValidationIndex,
|
|
10514
|
+
planExecutionStepIndex: plan.planExecutionStepIndex,
|
|
10515
|
+
planRuntime: plan.planRuntime,
|
|
10516
|
+
repairRetryState: plan.repairRetryState,
|
|
10517
|
+
planProgress: hasPersistedProgress ? plan.planProgress : (workflowPlanProgressEnabled(settings) ? createPlanProgress(plan.finalPlan, settings, "approved") : undefined),
|
|
10009
10518
|
}, ctx);
|
|
10010
10519
|
}
|
|
10011
10520
|
|
|
@@ -10026,7 +10535,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10026
10535
|
if (current.mode === "validating" || current.mode === "revalidating") return "Validator is already running. Wait for validation result.";
|
|
10027
10536
|
if (current.mode === "repairing") return "Executor is already running in repair mode. Revalidation will start after repair completes.";
|
|
10028
10537
|
if (current.mode === "validated" && current.validationVerdict === "PASS") return "Validation passed. Next action: /plan continue to complete and return to ready state.";
|
|
10029
|
-
if (current.mode === "validated" && current.validationVerdict === "PARTIAL PASS") return classifyValidationFailure(current.validationVerdict, current.validationReport ?? current.lastValidationFailure ?? "") === "repairable" ? "Validation partially passed with repairable issues. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>." : "Validation partially passed. Next action: complete manual verification, then /plan revalidate or revise if scope changed.";
|
|
10538
|
+
if (current.mode === "validated" && current.validationVerdict === "PARTIAL PASS") return classifyValidationFailure(current.validationVerdict, current.validationReport ?? current.lastValidationFailure ?? "", { concreteRepairableIssue: current.concreteRepairableIssue, manualVerificationRequired: current.manualVerificationRequired }) === "repairable" ? "Validation partially passed with repairable issues. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>." : "Validation partially passed. Next action: complete manual verification, then /plan revalidate or revise if scope changed.";
|
|
10030
10539
|
if (current.mode === "validated" && (current.validationVerdict === "FAIL" || current.lastValidationFailure)) return "Validation failed. Next actions: /plan repair, /plan retry, /plan revalidate, or /plan revise <feedback>.";
|
|
10031
10540
|
if (current.mode === "validated" && current.approvedPlan && validationAvailable) return "Validation status is unknown. Next action: /plan revalidate.";
|
|
10032
10541
|
return `No safe automatic continuation is available for state: ${current.mode}.`;
|
|
@@ -10043,11 +10552,38 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10043
10552
|
};
|
|
10044
10553
|
add(latest);
|
|
10045
10554
|
candidates.forEach(add);
|
|
10046
|
-
if (state.approvedPlan?.trim()) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
|
|
10555
|
+
if (state.approvedPlan?.trim() && !isMissionWorkflowMode(state)) return { candidates: ordered, source: "active", reason: "Active approved plan is already loaded." };
|
|
10047
10556
|
if (ordered.length > 0) return { plan: ordered[0], candidates: ordered, source: "history" };
|
|
10048
10557
|
return { candidates: [], source: "none", reason: "No active or recoverable approved Plan Mode workflow found." };
|
|
10049
10558
|
}
|
|
10050
10559
|
|
|
10560
|
+
function restorePlanFromStopSnapshot(ctx: ExtensionContext): boolean {
|
|
10561
|
+
const snapshot = state.lastPlanStopSummary?.blockedPlanSnapshot;
|
|
10562
|
+
if (!snapshot?.approvedPlan?.trim()) return false;
|
|
10563
|
+
const settings = loadWorkflowSettings(ctx.cwd);
|
|
10564
|
+
updateState({
|
|
10565
|
+
mode: "plan_approved",
|
|
10566
|
+
activeMissionId: undefined,
|
|
10567
|
+
task: snapshot.task ?? "Recovered blocked plan",
|
|
10568
|
+
originalTask: snapshot.originalTask ?? "Recovered blocked plan",
|
|
10569
|
+
approvedPlan: snapshot.approvedPlan,
|
|
10570
|
+
planHistoryId: snapshot.planHistoryId,
|
|
10571
|
+
approvedPlanHistoryId: snapshot.approvedPlanHistoryId,
|
|
10572
|
+
executionSummary: snapshot.executionSummary,
|
|
10573
|
+
validationReport: snapshot.validationReport,
|
|
10574
|
+
validationVerdict: snapshot.validationVerdict,
|
|
10575
|
+
lastValidationFailure: snapshot.lastValidationFailure,
|
|
10576
|
+
lastRepairAttempt: snapshot.lastRepairAttempt,
|
|
10577
|
+
repairHistory: snapshot.repairHistory,
|
|
10578
|
+
lastRepairStatus: snapshot.lastRepairStatus,
|
|
10579
|
+
currentValidationRetry: snapshot.currentValidationRetry,
|
|
10580
|
+
workflowValidationRetryCount: snapshot.workflowValidationRetryCount,
|
|
10581
|
+
planRuntime: snapshot.planRuntime,
|
|
10582
|
+
planProgress: snapshot.planProgress ?? (workflowPlanProgressEnabled(settings) ? createPlanProgress(snapshot.approvedPlan, settings, "approved") : undefined),
|
|
10583
|
+
}, ctx);
|
|
10584
|
+
return true;
|
|
10585
|
+
}
|
|
10586
|
+
|
|
10051
10587
|
function planResumeChoiceLabel(plan: SavedWorkflowPlan): string {
|
|
10052
10588
|
return `${plan.id} | ${plan.approvalStatus} | ${plan.planningMode} | ${compact(plan.originalTask || "Saved plan", 80).replace(/\n/g, " ")}`;
|
|
10053
10589
|
}
|
|
@@ -10086,7 +10622,10 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10086
10622
|
const allPlans = listWorkflowPlans().filter((plan) => plan.finalPlan?.trim());
|
|
10087
10623
|
if (resolved.source === "none" && allPlans.length === 0) return show(pi, `# Plan Resume\n\n${resolved.reason ?? planRecoveryGuidance(state, validationAvailable)}\n\nUse /workflow plans list to see saved plans.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
|
|
10088
10624
|
let selected = resolved.plan;
|
|
10089
|
-
if (resolved.source === "history" && selected)
|
|
10625
|
+
if (resolved.source === "history" && selected) {
|
|
10626
|
+
const restoredFromSnapshot = restorePlanFromStopSnapshot(ctx);
|
|
10627
|
+
if (!restoredFromSnapshot) recoverPlanFromHistory(ctx, selected);
|
|
10628
|
+
}
|
|
10090
10629
|
|
|
10091
10630
|
const reusePlan = async (plan: SavedWorkflowPlan, amend: boolean): Promise<void> => {
|
|
10092
10631
|
activeMission = undefined;
|
|
@@ -10115,8 +10654,15 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10115
10654
|
show(pi, `${summary}\n\nChoose what to do.`);
|
|
10116
10655
|
if (!ctx.hasUI) return;
|
|
10117
10656
|
const hasAlternatives = allPlans.some((plan) => plan.id !== currentPlan?.id);
|
|
10118
|
-
const choice = await ctx.ui.select("Plan Resume", [
|
|
10119
|
-
|
|
10657
|
+
const choice = await ctx.ui.select("Plan Resume", [
|
|
10658
|
+
...(state.approvedPlan ? ["Repair / Retry", "Revalidate"] : []),
|
|
10659
|
+
...(state.approvedPlan ? ["Continue Current Plan"] : []),
|
|
10660
|
+
...(hasAlternatives ? ["Choose Another Plan"] : []),
|
|
10661
|
+
"List Plan Status", "List Plans", "Cancel",
|
|
10662
|
+
]);
|
|
10663
|
+
if (choice === "Repair / Retry") await startWorkflowRepair(ctx, "user");
|
|
10664
|
+
else if (choice === "Revalidate") await beginValidation(ctx, true, true);
|
|
10665
|
+
else if (choice === "Continue Current Plan") await handlePlanContinue(ctx);
|
|
10120
10666
|
else if (choice === "Choose Another Plan") {
|
|
10121
10667
|
const other = await choosePlanFromHistory(ctx, allPlans.filter((plan) => plan.id !== currentPlan?.id), "Choose a plan to resume:");
|
|
10122
10668
|
if (other) await showSelectedPlanMenu(other);
|
|
@@ -10137,8 +10683,13 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10137
10683
|
const resolved = resolveActivePlanForResume();
|
|
10138
10684
|
const selected = await chooseResumePlan(ctx, resolved, "continue");
|
|
10139
10685
|
if (!selected) return show(pi, `# ${title}\n\n${resolved.reason ?? "No plan selected. Continue cancelled."}\n\nUse /workflow plans list to see saved plans.\n\n${status}`);
|
|
10140
|
-
|
|
10141
|
-
|
|
10686
|
+
const restoredFromSnapshot = restorePlanFromStopSnapshot(ctx);
|
|
10687
|
+
if (!restoredFromSnapshot) {
|
|
10688
|
+
recoverPlanFromHistory(ctx, selected);
|
|
10689
|
+
show(pi, `# ${title}\n\nRecovered approved plan from history.\n\n${recoverablePlanDetails(selected)}\n\nContinuing through configured workflow gates.`);
|
|
10690
|
+
} else {
|
|
10691
|
+
show(pi, `# ${title}\n\nRestored blocked Plan from the saved stop snapshot.\n\nContinuing through configured workflow gates.`);
|
|
10692
|
+
}
|
|
10142
10693
|
}
|
|
10143
10694
|
|
|
10144
10695
|
if (state.mode === "awaiting_plan_input") return show(pi, `# ${title}\n\nPlan Mode is ready and waiting for a planning request. Next action: enter a task with /p <task> or type the request as a normal message.\n\n${renderWorkflowStatus(state, pi.getActiveTools(), ctx.cwd)}`);
|
|
@@ -10185,15 +10736,19 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10185
10736
|
return;
|
|
10186
10737
|
}
|
|
10187
10738
|
if (state.validationVerdict === "PARTIAL PASS") {
|
|
10188
|
-
if (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? ""
|
|
10739
|
+
if (classifyValidationFailure(state.validationVerdict, state.validationReport ?? state.lastValidationFailure ?? "", {
|
|
10740
|
+
concreteRepairableIssue: state.concreteRepairableIssue,
|
|
10741
|
+
manualVerificationRequired: state.manualVerificationRequired,
|
|
10742
|
+
}) === "repairable") {
|
|
10189
10743
|
show(pi, `# ${title}\n\nValidation partially passed but includes concrete repairable issues. Starting safe repair under the configured validation repair policy.`);
|
|
10190
10744
|
await startWorkflowRepair(ctx, "user");
|
|
10191
10745
|
return;
|
|
10192
10746
|
}
|
|
10193
|
-
|
|
10747
|
+
await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
|
|
10748
|
+
return;
|
|
10194
10749
|
}
|
|
10195
10750
|
if (state.validationVerdict === "FAIL" || state.lastValidationFailure) {
|
|
10196
|
-
return show(pi, `# ${title}\n\nValidation failed. Continue will not automatically repair a failed plan.\n\nNext actions:\n- /plan repair\n- /plan retry\n- /plan revalidate\n- /plan revise <feedback>\n\
|
|
10751
|
+
return show(pi, `# ${title}\n\nValidation failed. Continue will not automatically repair a failed plan.\n\nNext actions:\n- /plan repair\n- /plan retry\n- /plan revalidate\n- /plan revise <feedback>\n\nRun /plan status to view full workflow details.`);
|
|
10197
10752
|
}
|
|
10198
10753
|
if (state.approvedPlan && validationAvailable) {
|
|
10199
10754
|
show(pi, `# ${title}\n\nValidation status is unknown. Revalidating the approved plan.`);
|
|
@@ -10202,7 +10757,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10202
10757
|
}
|
|
10203
10758
|
}
|
|
10204
10759
|
|
|
10205
|
-
return show(pi, `# ${title}\n\nNo safe automatic continuation is available for state: ${state.mode}.\n\
|
|
10760
|
+
return show(pi, `# ${title}\n\nNo safe automatic continuation is available for state: ${state.mode}.\n\nRun /plan status to view full workflow details.`);
|
|
10206
10761
|
}
|
|
10207
10762
|
|
|
10208
10763
|
function appendWorkflowReviewHistory(entry: NonNullable<WorkflowState["reviewHistory"]>[number]): WorkflowState["reviewHistory"] {
|
|
@@ -10234,18 +10789,23 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10234
10789
|
const retry = state.currentValidationRetry ?? 0;
|
|
10235
10790
|
const workflowRetry = workflowValidationRetryCount(state);
|
|
10236
10791
|
const failure = `Validation ${verdict}. ${compact(validationText, 1200)}`;
|
|
10237
|
-
const failureClass = classifyValidationFailure(verdict, validationText
|
|
10792
|
+
const failureClass = classifyValidationFailure(verdict, validationText, {
|
|
10793
|
+
concreteRepairableIssue: state.concreteRepairableIssue,
|
|
10794
|
+
manualVerificationRequired: state.manualVerificationRequired,
|
|
10795
|
+
});
|
|
10238
10796
|
if (failureClass === "manual_only") {
|
|
10239
10797
|
const reason = "Validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
|
|
10240
|
-
|
|
10798
|
+
const validationStatus = planValidationStatusForVerdict(verdict);
|
|
10799
|
+
updateState({ mode: "validated", validationReport: validationText, validationVerdict: verdict, lastValidationCompletedAt: new Date().toISOString(), lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason }), lastValidationFailure: failure, lastRepairStatus: "none", lastRepairAttempt: reason, maxValidationRetriesPerPlan: maxRetries, maxValidationRetriesPerWorkflow: maxWorkflowRetries, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: workflowRetry, maxRetriesPerItem: maxRetries, maxRetriesPerWorkflow: maxWorkflowRetries, lastFailure: failure, lastAttempt: reason, status: "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "blocked", validationStatus, lastValidationStatus: validationStatus, repairStatus: "none", nextAction: "manual verification, revalidate, or revise" }) : state.planProgress }, ctx);
|
|
10241
10800
|
queuePlanTerminalSummary(ctx, "blocked", "Plan validation needs manual verification", { validationText, verdict, reason });
|
|
10242
|
-
show(pi, `# Plan Validation
|
|
10243
|
-
|
|
10801
|
+
show(pi, `# Plan Validation - Manual Verification Required\n\nVerdict: ${verdict}\nReason: ${reason}\n\nNo repair retry was consumed. The implementation is code-complete but requires manual browser QA.\n\nChoose from the action menu, or use:\n- /plan complete (accept as done)\n- /plan revalidate\n- /plan revise <feedback>\n- /plan repair (manual override)`);
|
|
10802
|
+
deferWorkflowAction(pi, "show plan resume after manual-only validation", () => handlePlanResume(ctx));
|
|
10244
10803
|
return;
|
|
10245
10804
|
}
|
|
10246
10805
|
const validationStatus = planValidationStatusForVerdict(verdict);
|
|
10247
10806
|
const decision = resolveRepairRetryDecision({ gate: "validation", verdict: verdict ?? "UNKNOWN", report: failure, settings, state, itemRetry: retry, workflowRetry, requiredContextAvailable: Boolean(state.approvedPlan) });
|
|
10248
10807
|
updateState({ mode: "validated", validationReport: validationText, validationVerdict: verdict, lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan validation blocked", { validationText, verdict, reason: failure }), lastValidationFailure: failure, maxValidationRetriesPerPlan: maxRetries, maxValidationRetriesPerWorkflow: maxWorkflowRetries, repairRetryState: { ...(state.repairRetryState ?? {}), validation: { currentRetry: retry, workflowRetryCount: workflowRetry, maxRetriesPerItem: decision.maxRetriesPerItem, maxRetriesPerWorkflow: decision.maxRetriesPerWorkflow, lastFailure: failure, lastAttempt: decision.reason, status: decision.allowed ? "none" : "blocked", inProgress: false, history: state.repairRetryState?.validation?.history } }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: "repair/revalidate or revise" }) : state.planProgress }, ctx);
|
|
10808
|
+
show(pi, `# Plan Validation Result\n\nVerdict: ${verdict}\n${verdict === "PARTIAL PASS" ? "No concrete repairable issue was found. Validation gaps are limited to manual/browser verification." : `Repair policy: ${decision.allowed ? "auto-repair starting" : decision.reason ?? "repair blocked"}.`}\n\n${compact(validationText, 800)}`);
|
|
10249
10809
|
if (!decision.allowed) {
|
|
10250
10810
|
const reason = decision.reason ?? "repair requires approval or safety gate.";
|
|
10251
10811
|
updateState({ mode: "validated", lastPlanStopSummary: buildPlanStopSummary(ctx, "blocked", "Plan repair blocked", { validationText, verdict, reason }), lastRepairStatus: "blocked", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", lastRepairStatus: "blocked" }, settings, { lifecycleStatus: "blocked", validationStatus, repairStatus: "blocked", nextAction: reason }) : state.planProgress, lastRepairAttempt: reason, lastValidationFailure: failure }, ctx);
|
|
@@ -10306,6 +10866,7 @@ Use /mission status, /mission resume, /mission continue, or /mission retry.`);
|
|
|
10306
10866
|
checkpointMission(repairing, `Final validation repair started. Retry ${retry}/${maxRetries}. Failure: ${compact(failure, 500)}`, "Repair final validation failure, then revalidate.", undefined, { validationResult: repairing.lastFinalValidationResult ?? repairing.lastValidationResult });
|
|
10307
10867
|
pi.setActiveTools(executionToolsFor(settings));
|
|
10308
10868
|
updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
10869
|
+
saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
|
|
10309
10870
|
if (source !== "auto") show(pi, `# Mission Final Repair Started\n\nRetry: ${retry} of ${maxRetries}\n\n${renderMissionProgress(repairing, settings)}`);
|
|
10310
10871
|
queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
|
|
10311
10872
|
}
|
|
@@ -10395,6 +10956,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10395
10956
|
checkpointMission(repairing, `${manualRetryOverride ? "Manual repair retry override" : "Repair started"} for ${milestone?.id ?? "current milestone"}. Retry ${retry}/${maxRetries} per milestone; mission retry ${missionRetry}/${maxMissionRetries} total. Failure: ${compact(failure, 500)}`, "Repair current validation failure, then revalidate.", milestone?.id, { validationResult: repairing.lastValidationResult });
|
|
10396
10957
|
pi.setActiveTools(executionToolsFor(settings));
|
|
10397
10958
|
updateState({ mode: "mission_repairing", activeMissionId: repairing.id, modelsUsed: { ...(state.modelsUsed ?? {}), executor: modelLabel(route) } }, ctx);
|
|
10959
|
+
saveActiveMission({ ...repairing, modelsUsed: { ...(repairing.modelsUsed ?? {}), executor: modelLabel(route) } });
|
|
10398
10960
|
if (source !== "auto") show(pi, `# Mission Repair Started\n\nRetry: ${retry} of ${maxRetries} per milestone\nMission Retry: ${missionRetry} of ${maxMissionRetries} total\nMilestone: ${milestone?.id ?? "current"} — ${milestone?.title ?? "unknown"}\n\n${renderMissionProgress(repairing, settings)}`);
|
|
10399
10961
|
if (source === "auto") queueAgentTurn(pi, "Repair the current mission milestone validation failure, then submit workflow_repair_result.", "mission-repair-trigger");
|
|
10400
10962
|
else queueWorkflowPrompt(pi, missionRepairPrompt(repairing, settings, phasePreflightBlocks.Repair));
|
|
@@ -10466,6 +11028,17 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10466
11028
|
|
|
10467
11029
|
async function handleMissionFinalValidationFailure(ctx: ExtensionContext, mission: MissionState, verdict: WorkflowState["validationVerdict"], validationText: string) {
|
|
10468
11030
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
11031
|
+
const failureClass = classifyValidationFailure(verdict, validationText, {
|
|
11032
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
11033
|
+
manualVerificationRequired: mission.manualVerificationRequired,
|
|
11034
|
+
});
|
|
11035
|
+
if (failureClass === "manual_only") {
|
|
11036
|
+
const completed = saveActiveMission({ ...mission, status: "completed", completedAt: new Date().toISOString(), lastFinalValidationResult: verdict, lastFinalValidationFailure: "", lastValidationResult: verdict, lastValidationFailure: "", lastBlockReason: "", nextAction: "Mission completed all milestones and final validation (no concrete repairable issues).", lastSummary: `Final mission validation ${verdict} — no concrete repairable issue, completing.` });
|
|
11037
|
+
checkpointMission(completed, `Final mission validation ${verdict} — no concrete repairable issue. ${compact(validationText, 500)}`, "Mission completed after final validation.", undefined, { validationResult: verdict });
|
|
11038
|
+
completeMissionToAwaitingInput(ctx, completed, validationText, verdict, settings);
|
|
11039
|
+
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
11040
|
+
return;
|
|
11041
|
+
}
|
|
10469
11042
|
const retry = missionFinalValidationRetryCount(mission);
|
|
10470
11043
|
const maxRetries = missionMaxFinalValidationRetries(mission, settings);
|
|
10471
11044
|
const failure = `Final mission validation ${verdict}. ${compact(validationText, 1200)}`;
|
|
@@ -10473,16 +11046,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10473
11046
|
const nextRetry = retry + 1;
|
|
10474
11047
|
const failed = saveActiveMission({ ...mission, status: "validating", finalValidationRetryCount: nextRetry, maxFinalValidationRetries: maxRetries, lastFinalValidationResult: verdict ?? "UNKNOWN", lastFinalValidationFailure: failure, lastValidationFailure: failure, lastBlockReason: "", nextAction: "Final mission validation failed; evaluating safe repair retry.", lastSummary: `Final mission validation ${verdict}.` });
|
|
10475
11048
|
checkpointMission(failed, `Final mission validation failed. Retry ${retry}/${maxRetries}. ${compact(validationText, 500)}`, "Evaluate safe final repair retry before completion.", undefined, { validationResult: verdict });
|
|
10476
|
-
if (classifyValidationFailure(verdict, validationText) === "manual_only") {
|
|
10477
|
-
const reason = "Final validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
|
|
10478
|
-
const blocked = saveActiveMission({ ...failed, status: "blocked", lastRepairStatus: "none", lastBlockReason: reason, nextAction: "Perform manual verification, then run /mission revalidate.", lastSummary: "Mission blocked pending manual final validation verification." });
|
|
10479
|
-
checkpointMission(blocked, `Mission final validation pending manual verification. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", undefined, { validationResult: verdict });
|
|
10480
|
-
updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
|
|
10481
|
-
queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
|
|
10482
|
-
recordWorkflowInternalEvent(ctx, "Mission final validation manual-verification blocker suppressed from transcript.");
|
|
10483
|
-
showBlockedMissionRecoveryMenu(ctx);
|
|
10484
|
-
return;
|
|
10485
|
-
}
|
|
10486
11049
|
const shouldBlock = settings.missions.autoRepairFinalValidationFailures !== true || retry >= maxRetries || Boolean(unsafe);
|
|
10487
11050
|
if (shouldBlock) {
|
|
10488
11051
|
const reason = settings.missions.autoRepairFinalValidationFailures !== true ? "missions.autoRepairFinalValidationFailures=false."
|
|
@@ -10509,6 +11072,36 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10509
11072
|
const retry = mission.currentValidationRetry ?? 0;
|
|
10510
11073
|
const missionRetry = missionValidationRetryCount(mission);
|
|
10511
11074
|
const failure = `Validation ${verdict} for ${milestone?.id ?? "current milestone"}. ${compact(validationText, 1200)}`;
|
|
11075
|
+
const failureClass = classifyValidationFailure(verdict, validationText, {
|
|
11076
|
+
concreteRepairableIssue: mission.concreteRepairableIssue,
|
|
11077
|
+
manualVerificationRequired: mission.manualVerificationRequired,
|
|
11078
|
+
});
|
|
11079
|
+
if (failureClass === "manual_only") {
|
|
11080
|
+
const milestones = mission.milestones.map((m, i) => i === index ? { ...m, status: "completed" as const } : m);
|
|
11081
|
+
const nextIndex = Math.min(index + 1, Math.max(0, milestones.length - 1));
|
|
11082
|
+
const done = index >= milestones.length - 1;
|
|
11083
|
+
if (done && settings.missions.finalValidationEnabled === true) {
|
|
11084
|
+
const finalMission = saveActiveMission({ ...mission, status: "validating", milestones, currentMilestoneIndex: index, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", concreteRepairableIssue: false, manualVerificationRequired: true, nextAction: "Run final comprehensive mission validation.", lastSummary: `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue; final validation queued.` });
|
|
11085
|
+
checkpointMission(finalMission, `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue; final mission validation queued. ${compact(validationText, 500)}`, "Run final comprehensive validation for the whole mission.", milestone?.id, { validationResult: verdict });
|
|
11086
|
+
updateState({ mode: "mission_final_validating", activeMissionId: finalMission.id, validationReport: validationText, validationVerdict: verdict }, ctx);
|
|
11087
|
+
deferWorkflowAction(pi, "begin final mission validation after no-defect partial pass", async () => {
|
|
11088
|
+
await beginMissionFinalValidation(ctx, activeMission ?? finalMission, true);
|
|
11089
|
+
});
|
|
11090
|
+
return;
|
|
11091
|
+
}
|
|
11092
|
+
const shouldPause = mission.autonomy === "manual" || mission.pauseBetweenMilestones === true || mission.continueAcrossMilestones === false;
|
|
11093
|
+
const nextStatus = done ? "completed" : shouldPause ? "paused" : "approved";
|
|
11094
|
+
const nextMode = done ? "awaiting_mission_input" : shouldPause ? "mission_paused" : "mission_approved";
|
|
11095
|
+
const nextAction = done ? "Mission completed all milestones." : shouldPause ? "Run /mission continue, /mission next, or /mission resume when ready." : `Mission continuing automatically to milestone ${milestones[nextIndex]?.id ?? nextIndex + 1}.`;
|
|
11096
|
+
const nextMission = saveActiveMission({ ...mission, status: nextStatus, milestones, currentMilestoneIndex: nextIndex, currentValidationRetry: 0, lastValidationResult: verdict, lastValidationFailure: "", lastRepairStatus: done ? mission.lastRepairStatus : "none", lastRepairAttempt: done ? mission.lastRepairAttempt : "", concreteRepairableIssue: false, manualVerificationRequired: true, evidenceGap: false, nextAction, lastSummary: `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue, advancing.` });
|
|
11097
|
+
checkpointMission(nextMission, `Milestone ${milestone?.id ?? "current"} ${verdict} — no concrete repairable issue. ${compact(validationText, 500)}`, nextAction, milestone?.id, { validationResult: verdict });
|
|
11098
|
+
if (done) completeMissionToAwaitingInput(ctx, nextMission, validationText, verdict, settings);
|
|
11099
|
+
else updateState({ mode: nextMode, activeMissionId: nextMission.id, validationReport: validationText, validationVerdict: verdict }, ctx);
|
|
11100
|
+
if (!done && !shouldPause) {
|
|
11101
|
+
await beginMissionRun(ctx, activeMission ?? nextMission, "continue", true);
|
|
11102
|
+
}
|
|
11103
|
+
return;
|
|
11104
|
+
}
|
|
10512
11105
|
const failedMilestones = mission.milestones.map((m, i) => i === index ? { ...m, status: "active" as const } : m);
|
|
10513
11106
|
const failed = saveActiveMission({
|
|
10514
11107
|
...mission,
|
|
@@ -10524,17 +11117,6 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10524
11117
|
lastSummary: `Validation ${verdict} for mission milestone ${milestone?.id ?? "current"}.`,
|
|
10525
11118
|
});
|
|
10526
11119
|
checkpointMission(failed, `Validation failed for ${milestone?.id ?? "current milestone"}. Retry ${retry}/${maxRetries} per milestone; mission retry ${missionRetry}/${maxMissionRetries} total. ${compact(validationText, 500)}`, "Evaluate safe repair retry before blocking or advancing.", milestone?.id, { validationResult: verdict });
|
|
10527
|
-
const failureClass = classifyValidationFailure(verdict, validationText);
|
|
10528
|
-
if (failureClass === "manual_only") {
|
|
10529
|
-
const reason = "Mission validation requires manual/visual/browser verification without a concrete repairable issue; automatic repair is not appropriate.";
|
|
10530
|
-
const blocked = saveActiveMission({ ...failed, status: "blocked", lastRepairStatus: "none", lastBlockReason: reason, nextAction: "Perform manual verification, then run /mission revalidate.", lastSummary: "Mission blocked pending manual validation verification." });
|
|
10531
|
-
checkpointMission(blocked, `Mission validation pending manual verification for ${milestone?.id ?? "current milestone"}. Reason: ${reason}`, blocked.nextAction ?? "Perform manual verification.", milestone?.id, { validationResult: verdict });
|
|
10532
|
-
updateState({ mode: "mission_blocked", activeMissionId: blocked.id, validationReport: validationText, validationVerdict: verdict, lastMissionStopSummary: buildMissionStopSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason }) }, ctx);
|
|
10533
|
-
queueMissionTerminalSummary(ctx, blocked, "blocked", "Mission blocked", { validationText, verdict, reason });
|
|
10534
|
-
recordWorkflowInternalEvent(ctx, "Mission validation manual-verification blocker suppressed from transcript.");
|
|
10535
|
-
showBlockedMissionRecoveryMenu(ctx);
|
|
10536
|
-
return;
|
|
10537
|
-
}
|
|
10538
11120
|
const unsafe = validationFailureRequiresApproval(failure, settings);
|
|
10539
11121
|
const shouldBlock = mission.autonomy === "manual"
|
|
10540
11122
|
|| settings.missions.autoRepairValidationFailures === false
|
|
@@ -10609,16 +11191,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10609
11191
|
const questions = state.clarifyingQuestions ?? [];
|
|
10610
11192
|
const answerSummary = formatAnswersForPlanner(questions, answers);
|
|
10611
11193
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
11194
|
+
const priorPlanning = snapshotSubagentPhaseUsage("Planning");
|
|
10612
11195
|
if (!beginForcedSubagentPhase(ctx, "Planning", settings)) {
|
|
10613
11196
|
updateState({ mode: "planning", clarifyingAnswers: answers, draftPlan: state.draftPlan, approvedPlan: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "planning", draftPlan: state.draftPlan, approvedPlan: undefined }, settings, { lifecycleStatus: "blocked", nextAction: "fix forced planning sub-agent policy or rerun planning", steps: [] }, undefined) : state.planProgress }, ctx);
|
|
10614
11197
|
return;
|
|
10615
11198
|
}
|
|
11199
|
+
const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings);
|
|
11200
|
+
if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
|
|
10616
11201
|
updateState({
|
|
10617
11202
|
mode: "planning",
|
|
10618
11203
|
clarifyingAnswers: answers,
|
|
10619
11204
|
lastWorkflowHandoff: undefined,
|
|
10620
11205
|
}, ctx);
|
|
10621
|
-
await beginPlanning(ctx, state.task ?? "Plan with clarification answers", state.approvedPlan, answerSummary, { feedbackKind: "clarification" });
|
|
11206
|
+
await beginPlanning(ctx, state.task ?? "Plan with clarification answers", state.approvedPlan, answerSummary, { feedbackKind: "clarification", planningPreflightSatisfied });
|
|
10622
11207
|
}
|
|
10623
11208
|
|
|
10624
11209
|
async function runStandardClarificationSelect(ctx: ExtensionContext, questions: ClarificationQuestion[]): Promise<ClarificationAnswer[] | undefined> {
|
|
@@ -10680,16 +11265,19 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10680
11265
|
const questions = mission.clarificationQuestions?.length ? mission.clarificationQuestions : (state.clarifyingQuestions ?? []);
|
|
10681
11266
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
10682
11267
|
const planningOverride = { policy: missionSubagentPolicy(settings), workers: missionPlanningWorkerCount(settings), label: "Mission Planning" };
|
|
11268
|
+
const priorPlanning = snapshotSubagentPhaseUsage("Planning");
|
|
10683
11269
|
if (!beginForcedSubagentPhase(ctx, "Planning", settings, planningOverride)) {
|
|
10684
11270
|
const blocked = saveActiveMission({ ...mission, status: "blocked", clarificationQuestions: questions, clarificationAnswers: answers, lastBlockReason: "Mission clarification resume blocked by forced Mission Planning sub-agent policy.", nextAction: "Fix Mission Planning sub-agent workers, then run /mission resume or /mission plan.", lastSummary: "Mission clarification answers recorded, but forced Mission Planning availability gate did not pass." });
|
|
10685
11271
|
checkpointMission(blocked, "Mission clarification resume blocked by forced Mission Planning sub-agent policy.", "Fix Mission Planning sub-agent workers, then run /mission resume or /mission plan.");
|
|
10686
11272
|
updateState({ mode: "mission_blocked", activeMissionId: blocked.id, task: blocked.goal, originalTask: blocked.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, draftPlan: blocked.planText, approvedPlan: undefined }, ctx);
|
|
10687
11273
|
return;
|
|
10688
11274
|
}
|
|
11275
|
+
const planningPreflightSatisfied = priorSubagentUsageSatisfiesForced("Planning", priorPlanning, settings, planningOverride);
|
|
11276
|
+
if (planningPreflightSatisfied) restoreSubagentPhaseUsage("Planning", priorPlanning);
|
|
10689
11277
|
const next = saveActiveMission({ ...mission, status: "draft", clarificationQuestions: questions, clarificationAnswers: answers, lastSummary: "Mission clarification answered. Resuming milestone planning." });
|
|
10690
11278
|
checkpointMission(next, "Mission clarification answers recorded.", "Resume dynamic milestone planning with clarification applied.");
|
|
10691
11279
|
updateState({ mode: "mission_planning", activeMissionId: next.id, task: next.goal, originalTask: next.goal, clarifyingQuestions: questions, clarifyingAnswers: answers, lastWorkflowHandoff: undefined }, ctx);
|
|
10692
|
-
await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers });
|
|
11280
|
+
await beginMissionPlanning(ctx, { ...next, clarificationQuestions: questions, clarificationAnswers: answers }, { planningPreflightSatisfied });
|
|
10693
11281
|
}
|
|
10694
11282
|
|
|
10695
11283
|
function showMissionClarificationFallback(reason: string): void {
|
|
@@ -10972,11 +11560,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10972
11560
|
const verdict = state.validationVerdict;
|
|
10973
11561
|
const passed = verdict === "PASS";
|
|
10974
11562
|
const partial = verdict === "PARTIAL PASS";
|
|
10975
|
-
const repairablePartial = partial && classifyValidationFailure(verdict, state.validationReport ?? state.lastValidationFailure ?? "") === "repairable";
|
|
11563
|
+
const repairablePartial = partial && classifyValidationFailure(verdict, state.validationReport ?? state.lastValidationFailure ?? "", { concreteRepairableIssue: state.concreteRepairableIssue, manualVerificationRequired: state.manualVerificationRequired }) === "repairable";
|
|
10976
11564
|
const failed = verdict === "FAIL" || verdict === "UNKNOWN";
|
|
10977
11565
|
const title = repairablePartial ? "Validation found repairable issues. Choose next action:" : partial ? "Validation needs manual verification. Choose next action:" : failed ? "Validation failed. Choose next action:" : "Validation complete. Choose next action:";
|
|
10978
11566
|
const choices = [
|
|
10979
11567
|
...(failed || repairablePartial ? ["Run Safe Repair"] : []),
|
|
11568
|
+
...(partial && !repairablePartial ? ["Repair / Retry"] : []),
|
|
10980
11569
|
...(!passed ? ["Revalidate"] : []),
|
|
10981
11570
|
"List Summary",
|
|
10982
11571
|
partial && !repairablePartial ? "Revise / Add Manual Verification Feedback" : "Revise / Fix Issues",
|
|
@@ -10985,7 +11574,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
10985
11574
|
...(!passed ? ["Cancel"] : []),
|
|
10986
11575
|
];
|
|
10987
11576
|
const choice = await ctx.ui.select(title, choices);
|
|
10988
|
-
if (choice === "Run Safe Repair") await startWorkflowRepair(ctx, "user");
|
|
11577
|
+
if (choice === "Run Safe Repair" || choice === "Repair / Retry") await startWorkflowRepair(ctx, "user");
|
|
10989
11578
|
else if (choice === "Revalidate") await beginValidation(ctx, true, true);
|
|
10990
11579
|
else if (choice === "List Summary") show(pi, renderWorkflowSummary(state, ctx.cwd));
|
|
10991
11580
|
else if (choice === "Revise / Fix Issues" || choice === "Revise / Add Manual Verification Feedback") {
|
|
@@ -11070,7 +11659,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11070
11659
|
if (choice === "Set planning.depth") {
|
|
11071
11660
|
const depth = parsePlanningDepth((await ctx.ui.select("Planning depth", ["fast", "standard", "deep", "maximum"])) ?? "");
|
|
11072
11661
|
if (depth) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.depth = depth; }); ctx.ui.notify(`planning.depth set to ${depth} in ${r.file}`, "info"); }
|
|
11073
|
-
} else if (choice === "List Current Settings")
|
|
11662
|
+
} else if (choice === "List Current Settings") {
|
|
11663
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11664
|
+
show(pi, `# Planning Settings\n\nplanning.depth: ${s.planning.depth}`);
|
|
11665
|
+
}
|
|
11074
11666
|
}
|
|
11075
11667
|
}
|
|
11076
11668
|
|
|
@@ -11088,7 +11680,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11088
11680
|
} else if (choice === "Set maxClarificationQuestions") {
|
|
11089
11681
|
const count = parsePositiveInt((await ctx.ui.select("Max clarification questions", ["1", "2", "3", "4", "5"])) ?? "");
|
|
11090
11682
|
if (count) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxClarificationQuestions = count; }); ctx.ui.notify(`maxClarificationQuestions set to ${count} in ${r.file}`, "info"); }
|
|
11091
|
-
} else if (choice === "List Current Settings")
|
|
11683
|
+
} else if (choice === "List Current Settings") {
|
|
11684
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11685
|
+
show(pi, `# Clarification Settings\n\nClarification Mode: ${s.planning.clarificationMode}\nInteractive Clarification: ${s.planning.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Clarification Questions: ${s.planning.maxClarificationQuestions}`);
|
|
11686
|
+
}
|
|
11092
11687
|
}
|
|
11093
11688
|
}
|
|
11094
11689
|
|
|
@@ -11096,9 +11691,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11096
11691
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11097
11692
|
const keys = subagentPhaseSettingKeys(phase);
|
|
11098
11693
|
while (ctx.hasUI) {
|
|
11099
|
-
const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "
|
|
11694
|
+
const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "Back"]);
|
|
11100
11695
|
if (!choice || choice === "Back") return;
|
|
11101
|
-
if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11102
11696
|
if (choice.endsWith("Policy")) {
|
|
11103
11697
|
const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
|
|
11104
11698
|
if (policy) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.subagents as typeof s.subagents & Record<string, string>)[keys.policyKey] = policy; }); ctx.ui.notify(`subagents.${keys.policyKey} set to ${policy} in ${r.file}`, "info"); }
|
|
@@ -11114,9 +11708,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11114
11708
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11115
11709
|
const keys = subagentPhaseSettingKeys(phase);
|
|
11116
11710
|
while (ctx.hasUI) {
|
|
11117
|
-
const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "
|
|
11711
|
+
const choice = await ctx.ui.select(`${label} Policy / Workers`, [`${label} Policy`, `${label} Deep Workers`, `${label} Maximum / Forced Workers`, "Back"]);
|
|
11118
11712
|
if (!choice || choice === "Back") return;
|
|
11119
|
-
if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11120
11713
|
if (choice.endsWith("Policy")) {
|
|
11121
11714
|
const policy = parseSubagentPolicy((await ctx.ui.select(`${label} policy`, ["off", "auto", "deep", "maximum", "forced"])) ?? "");
|
|
11122
11715
|
if (policy) {
|
|
@@ -11161,9 +11754,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11161
11754
|
async function showStandardParallelismSettingsMenu(ctx: ExtensionContext) {
|
|
11162
11755
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11163
11756
|
while (ctx.hasUI) {
|
|
11164
|
-
const choice = await ctx.ui.select("Standard Parallelism Settings", ["Set allowParallelPlanning", "Set allowParallelExecution", "Set allowParallelRepair", "Set allowParallelReview", "Set allowParallelValidation", "
|
|
11757
|
+
const choice = await ctx.ui.select("Standard Parallelism Settings", ["Set allowParallelPlanning", "Set allowParallelExecution", "Set allowParallelRepair", "Set allowParallelReview", "Set allowParallelValidation", "Back"]);
|
|
11165
11758
|
if (!choice || choice === "Back") return;
|
|
11166
|
-
if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11167
11759
|
const key = choice.replace("Set ", "");
|
|
11168
11760
|
const enabled = await chooseBool(ctx, `standard.subagents.${key}?`);
|
|
11169
11761
|
if (enabled !== undefined) {
|
|
@@ -11197,16 +11789,18 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11197
11789
|
else if (choice === "Activity Indicator") {
|
|
11198
11790
|
const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
|
|
11199
11791
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
|
|
11200
|
-
} else if (choice === "List Current Settings")
|
|
11792
|
+
} else if (choice === "List Current Settings") {
|
|
11793
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11794
|
+
show(pi, `# Plan Sub-agents / Workers\n\n${renderPlanSubagentWorkerSettings(s)}`);
|
|
11795
|
+
}
|
|
11201
11796
|
}
|
|
11202
11797
|
}
|
|
11203
11798
|
|
|
11204
11799
|
async function showMissionPlanningSubagentSettingsMenu(ctx: ExtensionContext) {
|
|
11205
11800
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11206
11801
|
while (ctx.hasUI) {
|
|
11207
|
-
const choice = await ctx.ui.select("Mission Planning Policy / Workers", ["Mission Planning Policy", "Mission Planning Deep Workers", "Mission Planning Maximum / Forced Workers", "
|
|
11802
|
+
const choice = await ctx.ui.select("Mission Planning Policy / Workers", ["Mission Planning Policy", "Mission Planning Deep Workers", "Mission Planning Maximum / Forced Workers", "Back"]);
|
|
11208
11803
|
if (!choice || choice === "Back") return;
|
|
11209
|
-
if (choice === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11210
11804
|
if (choice === "Mission Planning Policy") {
|
|
11211
11805
|
const policy = parseSubagentPolicy((await ctx.ui.select("Mission planning policy", ["off", "auto", "deep", "maximum", "forced"])) ?? "");
|
|
11212
11806
|
if (policy) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.subagentPolicy = policy; }); ctx.ui.notify(`missions.subagentPolicy set to ${policy} in ${r.file}`, "info"); }
|
|
@@ -11248,7 +11842,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11248
11842
|
else if (choice === "Activity Indicator") {
|
|
11249
11843
|
const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
|
|
11250
11844
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
|
|
11251
|
-
} else if (choice === "List Current Settings")
|
|
11845
|
+
} else if (choice === "List Current Settings") {
|
|
11846
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11847
|
+
show(pi, `# Shared Sub-agents / Workers\n\nSub-agents: ${s.subagents.enabled !== false ? "enabled" : "disabled"}\nActivity Indicator: ${s.subagents.activityIndicatorEnabled !== false ? "enabled" : "disabled"}\nAuto-use Planning: ${(s.subagents as Record<string, boolean>).autoUseDuringPlanning !== false ? "on" : "off"}\nAuto-use Execution: ${(s.subagents as Record<string, boolean>).autoUseDuringExecution !== false ? "on" : "off"}\nAuto-use Repair: ${(s.subagents as Record<string, boolean>).autoUseDuringRepair !== false ? "on" : "off"}\nAuto-use Review: ${(s.subagents as Record<string, boolean>).autoUseDuringReview !== false ? "on" : "off"}\nAuto-use Validation: ${(s.subagents as Record<string, boolean>).autoUseDuringValidation !== false ? "on" : "off"}`);
|
|
11848
|
+
}
|
|
11252
11849
|
}
|
|
11253
11850
|
}
|
|
11254
11851
|
|
|
@@ -11270,7 +11867,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11270
11867
|
else if (choice === "Activity Indicator") {
|
|
11271
11868
|
const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
|
|
11272
11869
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
|
|
11273
|
-
} else if (choice === "List Current Settings")
|
|
11870
|
+
} else if (choice === "List Current Settings") {
|
|
11871
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11872
|
+
show(pi, `# Standard Sub-agents / Workers\n\nSub-agents: ${s.standard.allowSubagents !== false ? "enabled" : "disabled"}\nAgent Scope: ${s.standard.subagentScope ?? "user"}\nActivity Indicator: ${s.subagents.activityIndicatorEnabled !== false ? "enabled" : "disabled"}\nAuto-use Planning: ${(s.subagents as Record<string, boolean>).autoUseDuringPlanning !== false ? "on" : "off"}\nAuto-use Execution: ${(s.subagents as Record<string, boolean>).autoUseDuringExecution !== false ? "on" : "off"}\nAuto-use Repair: ${(s.subagents as Record<string, boolean>).autoUseDuringRepair !== false ? "on" : "off"}\nAuto-use Review: ${(s.subagents as Record<string, boolean>).autoUseDuringReview !== false ? "on" : "off"}\nAuto-use Validation: ${(s.subagents as Record<string, boolean>).autoUseDuringValidation !== false ? "on" : "off"}`);
|
|
11873
|
+
}
|
|
11274
11874
|
}
|
|
11275
11875
|
}
|
|
11276
11876
|
|
|
@@ -11292,7 +11892,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11292
11892
|
else if (choice === "Activity Indicator") {
|
|
11293
11893
|
const enabled = await chooseBool(ctx, "subagents.activityIndicatorEnabled?");
|
|
11294
11894
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.activityIndicatorEnabled = enabled; }); ctx.ui.notify(`subagents.activityIndicatorEnabled set to ${enabled} in ${r.file}`, "info"); renderWorkflowSubagentActivity(ctx); }
|
|
11295
|
-
} else if (choice === "List Current Settings")
|
|
11895
|
+
} else if (choice === "List Current Settings") {
|
|
11896
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11897
|
+
show(pi, `# Mission Sub-agents / Workers\n\n${renderMissionSubagentWorkerSettings(s)}`);
|
|
11898
|
+
}
|
|
11296
11899
|
}
|
|
11297
11900
|
}
|
|
11298
11901
|
|
|
@@ -11308,7 +11911,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11308
11911
|
} else if (choice === "Set editConcurrencyMode") {
|
|
11309
11912
|
const mode = parseEditConcurrencyMode((await ctx.ui.select("Edit concurrency mode", ["sequential", "scoped", "blocked"])) ?? "");
|
|
11310
11913
|
if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.subagents.editConcurrencyMode = mode; }); ctx.ui.notify(`subagents.editConcurrencyMode set to ${mode} in ${r.file}`, "info"); }
|
|
11311
|
-
} else if (choice === "List Current Settings")
|
|
11914
|
+
} else if (choice === "List Current Settings") {
|
|
11915
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
11916
|
+
show(pi, `# Parallelism Settings\n\nParallel Read-Only: ${s.subagents.allowParallelReadOnly !== false ? "enabled" : "disabled"}\nParallel Planning: ${s.subagents.allowParallelPlanning !== false ? "enabled" : "disabled"}\nParallel Execution: ${s.subagents.allowParallelExecution !== false ? "enabled" : "disabled"}\nParallel Repair: ${s.subagents.allowParallelRepair !== false ? "enabled" : "disabled"}\nParallel Review: ${s.subagents.allowParallelReview !== false ? "enabled" : "disabled"}\nParallel Validation: ${s.subagents.allowParallelValidation !== false ? "enabled" : "disabled"}\nParallel File Edits: ${s.subagents.allowParallelEdits !== false ? "enabled" : "disabled"}\nEdit Concurrency Mode: ${s.subagents.editConcurrencyMode ?? "sequential"}\nConflict Protection: ${s.subagents.requireParallelEditConflictProtection !== false ? "enabled" : "disabled"}`);
|
|
11917
|
+
}
|
|
11312
11918
|
}
|
|
11313
11919
|
}
|
|
11314
11920
|
|
|
@@ -11341,7 +11947,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11341
11947
|
async function selectCompactionModel(ctx: ExtensionContext) {
|
|
11342
11948
|
const selected = await selectProviderAndModel(ctx, "Compaction model");
|
|
11343
11949
|
if (!selected) return;
|
|
11344
|
-
const result = updateSettings(ctx.cwd,
|
|
11950
|
+
const result = updateSettings(ctx.cwd, undefined, (s) => {
|
|
11345
11951
|
s.context.compactionMode = "custom_model";
|
|
11346
11952
|
s.context.compactionModelProvider = selected.provider;
|
|
11347
11953
|
s.context.compactionModel = selected.model;
|
|
@@ -11359,7 +11965,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11359
11965
|
const modelChoice = await ctx.ui.select(`Compaction model for ${provider}:`, providerModels.map((model) => `${provider}/${model}`));
|
|
11360
11966
|
if (!modelChoice) return;
|
|
11361
11967
|
const model = modelChoice.replace(`${provider}/`, "");
|
|
11362
|
-
const result = updateSettings(ctx.cwd,
|
|
11968
|
+
const result = updateSettings(ctx.cwd, undefined, (s) => {
|
|
11363
11969
|
s.context.compactionMode = "custom_model";
|
|
11364
11970
|
s.context.compactionModel = model;
|
|
11365
11971
|
s.context.customCompactionEnabled = true;
|
|
@@ -11373,7 +11979,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11373
11979
|
if (!choice) return;
|
|
11374
11980
|
const agent = choice === "Custom agent name" ? (await ctx.ui.input("Custom compaction agent:", "agent-name"))?.trim() : choice;
|
|
11375
11981
|
if (!agent) return;
|
|
11376
|
-
const result = updateSettings(ctx.cwd,
|
|
11982
|
+
const result = updateSettings(ctx.cwd, undefined, (s) => {
|
|
11377
11983
|
s.context.compactionMode = "custom_agent";
|
|
11378
11984
|
s.context.compactionAgent = agent;
|
|
11379
11985
|
s.context.customCompactionEnabled = true;
|
|
@@ -11390,56 +11996,52 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11390
11996
|
const mode = parseCompactionMode((await ctx.ui.select("Compaction mode", ["Pi default", "Custom model", "Disabled"])) ?? "");
|
|
11391
11997
|
if (!mode) continue;
|
|
11392
11998
|
if (mode === "custom_model") { await selectCompactionModel(ctx); continue; }
|
|
11393
|
-
const r = updateSettings(ctx.cwd,
|
|
11999
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => {
|
|
11394
12000
|
s.context.compactionMode = mode;
|
|
11395
12001
|
s.context.customCompactionEnabled = false;
|
|
11396
12002
|
});
|
|
11397
12003
|
ctx.ui.notify(`Compaction mode set to ${compactionModeLabel(mode)} in ${r.file}`, "info");
|
|
11398
|
-
} else if (choice === "Compaction Check Mode") {
|
|
11399
|
-
const checkMode = parseWorkflowCompactionCheckMode((await ctx.ui.select("Compaction check mode", ["Boundary only", "In-session"])) ?? "");
|
|
11400
|
-
if (!checkMode) continue;
|
|
11401
|
-
const r = updateSettings(ctx.cwd, "global", (s) => { s.context.workflowCompactionCheckMode = checkMode; });
|
|
11402
|
-
ctx.ui.notify(`Compaction check mode set to ${workflowCompactionCheckModeLabel(checkMode)} in ${r.file}`, "info");
|
|
11403
12004
|
} else if (choice === "Compaction Provider") {
|
|
11404
12005
|
await selectCompactionModel(ctx);
|
|
11405
12006
|
} else if (choice === "Compaction Model") {
|
|
11406
12007
|
await selectCompactionModelForCurrentProvider(ctx);
|
|
11407
|
-
} else if (choice === "Compaction Agent") {
|
|
11408
|
-
await selectCompactionAgent(ctx);
|
|
11409
12008
|
} else if (choice === "Custom Compaction Enabled") {
|
|
11410
12009
|
const enabled = await chooseBool(ctx, "Custom compaction enabled?");
|
|
11411
|
-
if (enabled !== undefined) { const r = updateSettings(ctx.cwd,
|
|
12010
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.customCompactionEnabled = enabled; }); ctx.ui.notify(`Custom compaction set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Custom model routing uses the session compaction hook when configured; Pi default fallback remains enabled.`, "info"); }
|
|
11412
12011
|
} else if (choice === "Workflow Auto Trigger Enabled") {
|
|
11413
12012
|
const enabled = await chooseBool(ctx, "Workflow proactive auto-compaction trigger enabled?");
|
|
11414
|
-
if (enabled !== undefined) { const r = updateSettings(ctx.cwd,
|
|
12013
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.autoCompactionEnabled = enabled; }); ctx.ui.notify(`Workflow auto trigger set to ${enabled ? "enabled" : "disabled"} in ${r.file}. Pi default auto-compaction remains available as fallback.`, "info"); }
|
|
11415
12014
|
} else if (choice === "Workflow Trigger Percent") {
|
|
11416
12015
|
const raw = String((await ctx.ui.input("Workflow compaction trigger percent (50-95, default, or reset)", String(compactionTriggerPercent(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
|
|
11417
12016
|
if (raw === "default" || raw === "reset") {
|
|
11418
12017
|
const fallback = defaultCompactionTriggerPercent();
|
|
11419
|
-
const r = updateSettings(ctx.cwd,
|
|
12018
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionTriggerPercent = fallback; });
|
|
11420
12019
|
ctx.ui.notify(`Workflow compaction trigger percent reset to default ${fallback}% in ${r.file}`, "info");
|
|
11421
12020
|
} else {
|
|
11422
12021
|
const count = Number(raw);
|
|
11423
|
-
if (Number.isInteger(count) && count >= 50 && count <= 95) { const r = updateSettings(ctx.cwd,
|
|
12022
|
+
if (Number.isInteger(count) && count >= 50 && count <= 95) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionTriggerPercent = count; }); ctx.ui.notify(`Workflow compaction trigger percent set to ${count}% in ${r.file}`, "info"); }
|
|
11424
12023
|
else ctx.ui.notify("Trigger percent must be an integer from 50 to 95, default, or reset.", "error");
|
|
11425
12024
|
}
|
|
11426
12025
|
} else if (choice === "Workflow Trigger Cooldown") {
|
|
11427
12026
|
const count = Number((await ctx.ui.input("Minimum minutes between Workflow Suite proactive compaction attempts", String(compactionCooldownMinutes(loadWorkflowSettings(ctx.cwd)))) ?? ""));
|
|
11428
|
-
if (Number.isInteger(count) && count >= 0 && count <= 240) { const r = updateSettings(ctx.cwd,
|
|
12027
|
+
if (Number.isInteger(count) && count >= 0 && count <= 240) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.context.compactionCooldownMinutes = count; }); ctx.ui.notify(`Compaction cooldown set to ${count} minute(s) in ${r.file}. This is the minimum wait between proactive compaction attempts, not a delay before compaction starts.`, "info"); }
|
|
11429
12028
|
else ctx.ui.notify("Cooldown must be an integer from 0 to 240 minutes.", "error");
|
|
11430
12029
|
} else if (choice === "Custom Reserve Tokens") {
|
|
11431
12030
|
const raw = String((await ctx.ui.input("Custom compaction reserve tokens (4096-65536, default, or reset)", String(customCompactionReserveTokens(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
|
|
11432
12031
|
const count = Number(raw);
|
|
11433
|
-
if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd,
|
|
11434
|
-
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"); }
|
|
11435
12034
|
else ctx.ui.notify("Reserve tokens must be an integer from 4096 to 65536, default, or reset.", "error");
|
|
11436
12035
|
} else if (choice === "Custom Keep Recent Tokens") {
|
|
11437
12036
|
const raw = String((await ctx.ui.input("Custom compaction keep-recent tokens (1000-200000, default, or reset)", String(customCompactionKeepRecentTokens(loadWorkflowSettings(ctx.cwd)))) ?? "")).trim().toLowerCase();
|
|
11438
12037
|
const count = Number(raw);
|
|
11439
|
-
if (raw === "default" || raw === "reset") { const r = updateSettings(ctx.cwd,
|
|
11440
|
-
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"); }
|
|
11441
12040
|
else ctx.ui.notify("Keep-recent tokens must be an integer from 1000 to 200000, default, or reset.", "error");
|
|
11442
|
-
} else if (choice === "List Current Settings")
|
|
12041
|
+
} else if (choice === "List Current Settings") {
|
|
12042
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12043
|
+
show(pi, `# Shared Compaction\n\nCompaction Mode: ${s.context.compactionMode ?? "pi_default"}\nCustom Compaction: ${s.context.customCompactionEnabled ? "enabled" : "disabled"}\nProvider: ${s.context.compactionModelProvider ?? "none"}\nModel: ${s.context.compactionModel ?? "none"}\nAuto Trigger: ${s.context.autoCompactionEnabled !== false ? "enabled" : "disabled"}\nTrigger Percent: ${s.context.compactionTriggerPercent ?? "default"}\nCooldown: ${s.context.compactionCooldownMinutes ?? 0} min\nReserve Tokens: ${s.context.customCompactionReserveTokens?.toLocaleString() ?? "default"}\nKeep Recent Tokens: ${s.context.customCompactionKeepRecentTokens?.toLocaleString() ?? "default"}`);
|
|
12044
|
+
}
|
|
11443
12045
|
}
|
|
11444
12046
|
}
|
|
11445
12047
|
|
|
@@ -11588,12 +12190,41 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11588
12190
|
} else if (choice === "Repair / Validation Retry") {
|
|
11589
12191
|
await showWorkflowRepairRetrySettingsMenu(ctx);
|
|
11590
12192
|
} else if (choice === "Plan Progress / Runtime") {
|
|
11591
|
-
const setting = await ctx.ui.select("Plan Progress / Runtime", ["
|
|
12193
|
+
const setting = await ctx.ui.select("Plan Progress / Runtime", ["Progress Tracking", "Show Runtime Timer", "Progress Bar Display", "Runtime Budget", "Token Budget", "List Current Settings", "Back"]);
|
|
11592
12194
|
if (!setting || setting === "Back") continue;
|
|
11593
|
-
if (setting === "List Current Settings") {
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
12195
|
+
if (setting === "List Current Settings") {
|
|
12196
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12197
|
+
show(pi, `# Plan Progress / Runtime\n\nProgress Tracking: ${s.workflow.planProgressEnabled !== false ? "enabled" : "disabled"}\nShow Runtime Timer: ${s.workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.workflow.planShowProgressBar !== false ? "graphical" : "numeric"}\nRuntime Budget: ${(s.planning.maxRuntimeHours ?? 0) === 0 ? "unlimited" : `${s.planning.maxRuntimeHours ?? 0} hours`}\nToken Budget: ${(s.planning.maxTokens ?? 0) === 0 ? "unlimited" : (s.planning.maxTokens ?? 0).toLocaleString()}`);
|
|
12198
|
+
continue;
|
|
12199
|
+
}
|
|
12200
|
+
if (setting === "Token Budget") {
|
|
12201
|
+
const current = loadWorkflowSettings(ctx.cwd).planning.maxTokens ?? 0;
|
|
12202
|
+
const choice = await ctx.ui.select("Plan Token Budget", ["Default (unlimited)", "Custom..."]);
|
|
12203
|
+
if (choice === "Default (unlimited)") {
|
|
12204
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxTokens = 0; });
|
|
12205
|
+
ctx.ui.notify(`planning.maxTokens set to default (unlimited) in ${r.file}`, "info");
|
|
12206
|
+
} else if (choice === "Custom...") {
|
|
12207
|
+
const count = parsePositiveInt((await ctx.ui.input("Enter custom plan token budget", String(current > 0 ? current : ""))) ?? "");
|
|
12208
|
+
if (count !== undefined && count >= 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxTokens = count; }); ctx.ui.notify(`planning.maxTokens set to ${count === 0 ? "unlimited" : count.toLocaleString()} in ${r.file}`, "info"); }
|
|
12209
|
+
}
|
|
12210
|
+
} else if (setting === "Runtime Budget") {
|
|
12211
|
+
const current = loadWorkflowSettings(ctx.cwd).planning.maxRuntimeHours ?? 0;
|
|
12212
|
+
const choice = await ctx.ui.select("Plan Runtime Budget", ["Default (unlimited)", "Custom..."]);
|
|
12213
|
+
if (choice === "Default (unlimited)") {
|
|
12214
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxRuntimeHours = 0; });
|
|
12215
|
+
ctx.ui.notify(`planning.maxRuntimeHours set to default (unlimited) in ${r.file}`, "info");
|
|
12216
|
+
} else if (choice === "Custom...") {
|
|
12217
|
+
const count = parsePositiveInt((await ctx.ui.input("Enter custom plan runtime budget (hours)", String(current > 0 ? current : ""))) ?? "");
|
|
12218
|
+
if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.planning.maxRuntimeHours = count; }); ctx.ui.notify(`planning.maxRuntimeHours set to ${count} hours in ${r.file}`, "info"); }
|
|
12219
|
+
}
|
|
12220
|
+
} else if (setting === "Progress Bar Display") {
|
|
12221
|
+
const enabled = await chooseBool(ctx, "workflow.planShowProgressBar?");
|
|
12222
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.workflow.planShowProgressBar = enabled; }); ctx.ui.notify(`workflow.planShowProgressBar set to ${enabled} in ${r.file}`, "info"); }
|
|
12223
|
+
} else {
|
|
12224
|
+
const key = setting === "Progress Tracking" ? "planProgressEnabled" : "planRuntimeEnabled";
|
|
12225
|
+
const enabled = await chooseBool(ctx, `workflow.${key}?`);
|
|
12226
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.workflow as typeof s.workflow & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`workflow.${key} set to ${enabled} in ${r.file}`, "info"); }
|
|
12227
|
+
}
|
|
11597
12228
|
} else if (choice === "Plan History") {
|
|
11598
12229
|
const setting = await ctx.ui.select("Plan History", ["Save Plans", "Save Plan History", "Plan History Limit", "Back"]);
|
|
11599
12230
|
const workflow = loadWorkflowSettings(ctx.cwd).workflow as ReturnType<typeof loadWorkflowSettings>["workflow"] & { savePlans?: boolean; savePlanHistory?: boolean; planHistoryLimit?: number };
|
|
@@ -11607,7 +12238,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11607
12238
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.workflow as typeof s.workflow & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`workflow.${key} set to ${enabled} in ${r.file}`, "info"); }
|
|
11608
12239
|
}
|
|
11609
12240
|
} else if (choice === "List Current Settings") {
|
|
11610
|
-
|
|
12241
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12242
|
+
show(pi, `# Plan Mode Settings\n\n## Plan Models\n${renderPlanModelSettings(s)}\n\n## Plan Mode Flow\nPlanning Depth: ${s.planning.depth}\nProgress Tracking: ${s.workflow.planProgressEnabled !== false ? "enabled" : "disabled"}\nShow Runtime Timer: ${s.workflow.planRuntimeEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.workflow.planShowProgressBar !== false ? "graphical" : "numeric"}\nRuntime Budget: ${(s.planning.maxRuntimeHours ?? 0) === 0 ? "unlimited" : `${s.planning.maxRuntimeHours ?? 0} hours`}\nToken Budget: ${(s.planning.maxTokens ?? 0) === 0 ? "unlimited" : (s.planning.maxTokens ?? 0).toLocaleString()}\nRequire Approval Before Execution: ${s.workflow.requireApprovalBeforeExecution !== false ? "enabled" : "disabled"}\nRequire Approval Per Step: ${s.workflow.requireApprovalPerStep === true ? "enabled" : "disabled"}\nValidate After Each Step: ${s.workflow.validateAfterEachStep !== false ? "enabled" : "disabled"}\nValidate After Full Execution: ${s.workflow.validateAfterExecution !== false ? "enabled" : "disabled"}\n\n## Plan Sub-agents\n${renderPlanSubagentWorkerSettings(s)}\n\n## Plan Clarification\nClarification Mode: ${s.planning.clarificationMode}\nInteractive: ${s.planning.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.planning.maxClarificationQuestions}\n\n## Plan Review / Validation\nReviewer: ${s.models.reviewer.enabled !== false ? "enabled" : "disabled"}\nValidator: ${s.models.validator.enabled !== false ? "enabled" : "disabled"}\nAuto Run Reviewer: ${s.workflow.autoRunReviewerBeforeExecute !== false ? "enabled" : "disabled"}\nAuto Run Validation: ${s.workflow.autoRunValidationAfterExecute !== false ? "enabled" : "disabled"}\nAuto Repair Review Failures: ${s.workflow.autoRepairReviewFailures === true ? "enabled" : "disabled"}\nRepair / Retry Master Toggle: ${s.workflow.repairRetry?.enabled !== false ? "enabled" : "disabled"}\nMax Total Retries: ${s.workflow.repairRetry?.maxTotalRetries ?? "unlimited"}\n\n## Plan History\nSave Plans: ${(s.workflow as Record<string, unknown>).savePlans !== false ? "enabled" : "disabled"}\nSave Plan History: ${(s.workflow as Record<string, unknown>).savePlanHistory !== false ? "enabled" : "disabled"}\nPlan History Limit: ${(s.workflow as Record<string, unknown>).planHistoryLimit ?? 50}`);
|
|
11611
12243
|
}
|
|
11612
12244
|
}
|
|
11613
12245
|
}
|
|
@@ -11615,9 +12247,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11615
12247
|
async function showStandardTodoSettingsMenu(ctx: ExtensionContext) {
|
|
11616
12248
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11617
12249
|
while (ctx.hasUI) {
|
|
11618
|
-
const setting = await ctx.ui.select("Standard To Do", ["Automatic To Do Enabled", "To Do Trigger Mode", "To Do Progress Visible", "
|
|
12250
|
+
const setting = await ctx.ui.select("Standard To Do", ["Automatic To Do Enabled", "To Do Trigger Mode", "To Do Progress Visible", "Back"]);
|
|
11619
12251
|
if (!setting || setting === "Back") return;
|
|
11620
|
-
if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11621
12252
|
if (setting === "To Do Trigger Mode") {
|
|
11622
12253
|
const mode = parseStandardTodoTriggerMode((await ctx.ui.select("To Do Trigger Mode", ["Disabled", "On request", "Automatic when useful", "Required"])) ?? "");
|
|
11623
12254
|
if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.todoTriggerMode = mode; s.standard.autoTodoEnabled = mode !== "off"; }); ctx.ui.notify(`To Do Trigger Mode set to ${standardTodoTriggerModeLabel(mode)} in ${r.file}`, "info"); }
|
|
@@ -11632,9 +12263,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11632
12263
|
async function showStandardClarificationSettingsMenu(ctx: ExtensionContext) {
|
|
11633
12264
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11634
12265
|
while (ctx.hasUI) {
|
|
11635
|
-
const setting = await ctx.ui.select("Standard Clarification", ["Clarification Mode", "Interactive Clarification", "Max Clarification Questions", "Timing", "Quality Gate", "Allow Without Analysis", "Use Sub-agents Before Clarification", "
|
|
12266
|
+
const setting = await ctx.ui.select("Standard Clarification", ["Clarification Mode", "Interactive Clarification", "Max Clarification Questions", "Timing", "Quality Gate", "Allow Without Analysis", "Use Sub-agents Before Clarification", "Back"]);
|
|
11636
12267
|
if (!setting || setting === "Back") return;
|
|
11637
|
-
if (setting === "List Current Settings") { show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd))); continue; }
|
|
11638
12268
|
if (setting === "Clarification Mode") {
|
|
11639
12269
|
const mode = parseStandardClarificationMode((await ctx.ui.select("Standard clarification mode", ["auto", "always_for_nontrivial", "never"])) ?? "");
|
|
11640
12270
|
if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.clarificationMode = mode; s.standard.clarificationEnabled = mode !== "never"; }); ctx.ui.notify(`standard.clarificationMode set to ${mode} in ${r.file}`, "info"); }
|
|
@@ -11658,12 +12288,28 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11658
12288
|
async function showStandardProgressRuntimeSettingsMenu(ctx: ExtensionContext) {
|
|
11659
12289
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11660
12290
|
while (ctx.hasUI) {
|
|
11661
|
-
const setting = await ctx.ui.select("Standard Progress / Runtime", ["Status Widget Visible", "To Do Progress Visible", "List Current Settings", "Back"]);
|
|
12291
|
+
const setting = await ctx.ui.select("Standard Progress / Runtime", ["Status Widget Visible", "To Do Progress Visible", "Token Budget", "List Current Settings", "Back"]);
|
|
11662
12292
|
if (!setting || setting === "Back") return;
|
|
11663
|
-
if (setting === "List Current Settings") {
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
12293
|
+
if (setting === "List Current Settings") {
|
|
12294
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12295
|
+
show(pi, `# Standard Progress / Runtime\n\nStatus Widget Visible: ${s.standard.statusWidgetVisible !== false ? "enabled" : "disabled"}\nTo Do Progress Visible: ${s.standard.todoProgressVisible !== false ? "enabled" : "disabled"}\nToken Budget: ${(s.standard.maxTokens ?? 0) === 0 ? "unlimited" : (s.standard.maxTokens ?? 0).toLocaleString()}`);
|
|
12296
|
+
continue;
|
|
12297
|
+
}
|
|
12298
|
+
if (setting === "Token Budget") {
|
|
12299
|
+
const current = loadWorkflowSettings(ctx.cwd).standard.maxTokens ?? 0;
|
|
12300
|
+
const choice = await ctx.ui.select("Standard Token Budget", ["Default (unlimited)", "Custom..."]);
|
|
12301
|
+
if (choice === "Default (unlimited)") {
|
|
12302
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.maxTokens = 0; });
|
|
12303
|
+
ctx.ui.notify(`standard.maxTokens set to default (unlimited) in ${r.file}`, "info");
|
|
12304
|
+
} else if (choice === "Custom...") {
|
|
12305
|
+
const count = parsePositiveInt((await ctx.ui.input("Enter custom standard token budget", String(current > 0 ? current : ""))) ?? "");
|
|
12306
|
+
if (count !== undefined && count >= 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.maxTokens = count; }); ctx.ui.notify(`standard.maxTokens set to ${count === 0 ? "unlimited" : count.toLocaleString()} in ${r.file}`, "info"); }
|
|
12307
|
+
}
|
|
12308
|
+
} else {
|
|
12309
|
+
const key = setting === "Status Widget Visible" ? "statusWidgetVisible" : "todoProgressVisible";
|
|
12310
|
+
const enabled = await chooseBool(ctx, `standard.${key}?`);
|
|
12311
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.standard as typeof s.standard & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`standard.${key} set to ${enabled} in ${r.file}`, "info"); }
|
|
12312
|
+
}
|
|
11667
12313
|
}
|
|
11668
12314
|
}
|
|
11669
12315
|
|
|
@@ -11672,7 +12318,11 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11672
12318
|
while (ctx.hasUI) {
|
|
11673
12319
|
const choice = await ctx.ui.select("Standard Mode Settings", ["Standard Models", "Enable / Disable Standard Mode", "Standard To Do", "Standard Clarification", "Standard Sub-agents / Workers", "Standard Progress / Runtime", "List Current Settings", "Back"]);
|
|
11674
12320
|
if (!choice || choice === "Back") return;
|
|
11675
|
-
if (choice === "List Current Settings") {
|
|
12321
|
+
if (choice === "List Current Settings") {
|
|
12322
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12323
|
+
show(pi, `# Standard Mode Settings\n\nStandard Mode: ${s.standard.enabled !== false ? "enabled" : "disabled"}\nStatus Widget: ${s.standard.statusWidgetVisible !== false ? "enabled" : "disabled"}\nTo Do Progress: ${s.standard.todoProgressVisible !== false ? "enabled" : "disabled"}\nToken Budget: ${(s.standard.maxTokens ?? 0) === 0 ? "unlimited" : (s.standard.maxTokens ?? 0).toLocaleString()}\nSub-agents: ${s.standard.allowSubagents !== false ? "enabled" : "disabled"}\nAgent Scope: ${s.standard.subagentScope ?? "user"}\n\n## Standard Clarification\nMode: ${s.standard.clarificationMode ?? "auto"}\nInteractive: ${s.standard.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.standard.maxClarificationQuestions ?? 3}\n\n## Standard Models\nExecutor Model: ${s.standard.useSharedExecutorModel !== false ? "shared executor" : "standard-specific"}\nShared Executor: ${s.standard.useSharedExecutorModel !== false ? "enabled" : "disabled"}`);
|
|
12324
|
+
continue;
|
|
12325
|
+
}
|
|
11676
12326
|
if (choice === "Enable / Disable Standard Mode") {
|
|
11677
12327
|
const enabled = await chooseBool(ctx, "standard.enabled?");
|
|
11678
12328
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.standard.enabled = enabled; }); ctx.ui.notify(`standard.enabled set to ${enabled} in ${r.file}`, "info"); }
|
|
@@ -11760,7 +12410,12 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11760
12410
|
"Auto Run After Approval": "autoRunAfterApproval",
|
|
11761
12411
|
};
|
|
11762
12412
|
const key = keyMap[setting];
|
|
11763
|
-
const r = updateSettings(ctx.cwd, undefined, (s) => {
|
|
12413
|
+
const r = updateSettings(ctx.cwd, undefined, (s) => {
|
|
12414
|
+
(s.missions as Record<string, unknown>)[String(key)] = enabled;
|
|
12415
|
+
if (key === "autoRepairReviewFailures" && enabled && s.missions.reviewRetryMode === "off") {
|
|
12416
|
+
s.missions.reviewRetryMode = "safe_only";
|
|
12417
|
+
}
|
|
12418
|
+
});
|
|
11764
12419
|
ctx.ui.notify(`missions.${key} set to ${enabled} in ${r.file}`, "info");
|
|
11765
12420
|
}
|
|
11766
12421
|
}
|
|
@@ -11769,7 +12424,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11769
12424
|
async function showMissionRepairRetrySettingsMenu(ctx: ExtensionContext) {
|
|
11770
12425
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11771
12426
|
while (ctx.hasUI) {
|
|
11772
|
-
const setting = await ctx.ui.select("Repair / Validation Retry", ["Review Retry Mode", "Max Review Retries Per Mission", "Auto Repair Validation Failures", "Validation Retry Mode", "Max Retries Per Milestone", "Max Retries Per Mission", "Pause After Validation Failure", "Require Approval For Out-Of-Scope Repair", "Require Approval For Destructive Repair", "Require Approval For Destructive Actions", "Auto Repair Final Validation Failures", "Max Final Validation Retries", "Back"]);
|
|
12427
|
+
const setting = await ctx.ui.select("Repair / Validation Retry", ["Review Retry Mode", "Max Review Retries Per Mission", "Auto Repair Review Failures", "Auto Repair Validation Failures", "Validation Retry Mode", "Max Retries Per Milestone", "Max Retries Per Mission", "Pause After Validation Failure", "Require Approval For Out-Of-Scope Repair", "Require Approval For Destructive Repair", "Require Approval For Destructive Actions", "Auto Repair Final Validation Failures", "Max Final Validation Retries", "Back"]);
|
|
11773
12428
|
if (!setting || setting === "Back") return;
|
|
11774
12429
|
if (setting === "Max Review Retries Per Mission") {
|
|
11775
12430
|
const raw = await ctx.ui.select(setting, ["0", "1", "2", "3", "4", "5", "10"]);
|
|
@@ -11788,6 +12443,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11788
12443
|
if (mode) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, string | undefined>)[key] = mode; }); ctx.ui.notify(`missions.${key} set to ${mode} in ${r.file}`, "info"); }
|
|
11789
12444
|
} else {
|
|
11790
12445
|
const keyMap: Record<string, string> = {
|
|
12446
|
+
"Auto Repair Review Failures": "autoRepairReviewFailures",
|
|
11791
12447
|
"Auto Repair Validation Failures": "autoRepairValidationFailures",
|
|
11792
12448
|
"Pause After Validation Failure": "pauseAfterValidationFailure",
|
|
11793
12449
|
"Require Approval For Out-Of-Scope Repair": "requireApprovalForOutOfScopeRepair",
|
|
@@ -11797,7 +12453,7 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11797
12453
|
};
|
|
11798
12454
|
const key = keyMap[setting];
|
|
11799
12455
|
const enabled = await chooseBool(ctx, `${String(key)}?`);
|
|
11800
|
-
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as Record<string, unknown>)[String(key)] = enabled; }); ctx.ui.notify(`missions.${String(key)} set to ${enabled} in ${r.file}`, "info"); }
|
|
12456
|
+
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as Record<string, unknown>)[String(key)] = enabled; if (key === "autoRepairReviewFailures" && enabled && s.missions.reviewRetryMode === "off") { s.missions.reviewRetryMode = "safe_only"; } }); ctx.ui.notify(`missions.${String(key)} set to ${enabled} in ${r.file}`, "info"); }
|
|
11801
12457
|
}
|
|
11802
12458
|
}
|
|
11803
12459
|
}
|
|
@@ -11874,22 +12530,55 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11874
12530
|
} else if (choice === "Mission Sub-agents / Workers" || choice === "Mission Worker Targets") {
|
|
11875
12531
|
await showMissionSubagentWorkerSettingsMenu(ctx);
|
|
11876
12532
|
} else if (choice === "Mission Progress / Runtime") {
|
|
11877
|
-
const setting = await ctx.ui.select("Mission Progress / Runtime", ["Progress Widget", "Progress Bar Display", "Runtime Budget", "Checkpoint Interval", "Heartbeat / Watchdog", "Back"]);
|
|
12533
|
+
const setting = await ctx.ui.select("Mission Progress / Runtime", ["Progress Widget", "Progress Bar Display", "Runtime Budget", "Token Budget", "Checkpoint Interval", "Heartbeat / Watchdog", "List Current Settings", "Back"]);
|
|
11878
12534
|
if (!setting || setting === "Back") continue;
|
|
12535
|
+
if (setting === "List Current Settings") {
|
|
12536
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12537
|
+
show(pi, `# Mission Progress / Runtime\n\nProgress Widget: ${s.missions.progressWidgetEnabled !== false ? "enabled" : "disabled"}\nProgress Bar Display: ${s.missions.showProgressBar !== false ? "enabled" : "disabled"}\nRuntime Budget: ${s.missions.maxRuntimeHours} hours\nToken Budget: ${(s.missions.maxTokens ?? 0) === 0 ? "unlimited" : (s.missions.maxTokens ?? 0).toLocaleString()}\nCheckpoint Interval: ${s.missions.checkpointIntervalMinutes} minutes\nHeartbeat: ${s.missions.heartbeatEnabled !== false ? "enabled" : "disabled"}\nWatchdog: ${s.missions.watchdogEnabled === true ? "enabled" : "disabled"}${s.missions.watchdogEnabled === true ? ` (stale after ${s.missions.watchdogStaleMinutes ?? 30}m)` : ""}`);
|
|
12538
|
+
continue;
|
|
12539
|
+
}
|
|
11879
12540
|
if (setting === "Progress Widget" || setting === "Progress Bar Display") {
|
|
11880
12541
|
const key = setting === "Progress Widget" ? "progressWidgetEnabled" : "showProgressBar";
|
|
11881
12542
|
const enabled = await chooseBool(ctx, `missions.${key}?`);
|
|
11882
12543
|
if (enabled !== undefined) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, boolean>)[key] = enabled; }); ctx.ui.notify(`missions.${key} set to ${enabled} in ${r.file}`, "info"); }
|
|
11883
12544
|
} else if (setting === "Heartbeat / Watchdog") await showHeartbeatWatchdogMenu(ctx);
|
|
11884
|
-
else {
|
|
11885
|
-
const
|
|
11886
|
-
const
|
|
11887
|
-
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"); }
|
|
11888
12578
|
}
|
|
11889
|
-
} else if (choice === "
|
|
11890
|
-
const
|
|
11891
|
-
|
|
11892
|
-
if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { (s.missions as typeof s.missions & Record<string, number>)[key] = count; }); ctx.ui.notify(`missions.${key} set to ${count} in ${r.file}`, "info"); }
|
|
12579
|
+
} else if (choice === "Checkpoint Interval") {
|
|
12580
|
+
const count = parsePositiveInt((await ctx.ui.input("checkpointIntervalMinutes", "30")) ?? "");
|
|
12581
|
+
if (count !== undefined && count > 0) { const r = updateSettings(ctx.cwd, undefined, (s) => { s.missions.checkpointIntervalMinutes = count; }); ctx.ui.notify(`missions.checkpointIntervalMinutes set to ${count} in ${r.file}`, "info"); }
|
|
11893
12582
|
} else if (choice === "Approval Safety" || choice === "Validation Per Milestone" || choice === "Full Auto Safety" || choice === "Auto Resume") {
|
|
11894
12583
|
const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["missions"]> = { "Validation Per Milestone": "requireValidationPerMilestone", "Approval Safety": "requireApprovalForDestructiveActions", "Full Auto Safety": "allowFullAuto", "Auto Resume": "autoResume" };
|
|
11895
12584
|
const key = keyMap[choice];
|
|
@@ -11906,7 +12595,8 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11906
12595
|
} else if (choice === "Heartbeat / Watchdog" || choice === "Heartbeat / Watchdog Status") {
|
|
11907
12596
|
await showHeartbeatWatchdogMenu(ctx);
|
|
11908
12597
|
} else if (choice === "List Current Settings") {
|
|
11909
|
-
|
|
12598
|
+
const s = loadWorkflowSettings(ctx.cwd);
|
|
12599
|
+
show(pi, `# Mission Mode Settings\n\n## Mission Models\n${renderMissionModelStrategy(s)}\n\n## Mission Mode Flow\nMissions: ${s.missions.enabled !== false ? "enabled" : "disabled"}\nDefault Autonomy: ${s.missions.defaultAutonomy}\nAllow Full Auto: ${s.missions.allowFullAuto === true ? "enabled" : "disabled"}\nAuto Run After Approval: ${s.missions.autoRunAfterApproval !== false ? "enabled" : "disabled"}\nContinue Across Milestones: ${s.missions.continueAcrossMilestones !== false ? "enabled" : "disabled"}\nPause Between Milestones: ${s.missions.pauseBetweenMilestones !== false ? "enabled" : "disabled"}\nPlanning Depth: ${s.missions.planningDepth ?? "standard"}\n\n## Mission Clarification\nClarification Mode: ${s.missions.clarificationMode ?? "auto"}\nInteractive: ${s.missions.interactiveClarificationEnabled !== false ? "enabled" : "disabled"}\nMax Questions: ${s.missions.maxClarificationQuestions ?? 3}\n\n## Mission Sub-agents\n${renderMissionSubagentWorkerSettings(s)}\n\n## Mission Progress / Runtime\nProgress Widget: ${s.missions.progressWidgetEnabled !== false ? "enabled" : "disabled"}\nProgress Bar: ${s.missions.showProgressBar !== false ? "enabled" : "disabled"}\nRuntime Budget: ${s.missions.maxRuntimeHours} hours\nToken Budget: ${(s.missions.maxTokens ?? 0) === 0 ? "unlimited" : (s.missions.maxTokens ?? 0).toLocaleString()}\nCheckpoint Interval: ${s.missions.checkpointIntervalMinutes} minutes\n\n## Heartbeat / Watchdog\n${renderHeartbeatWatchdogStatus(s)}`);
|
|
11910
12600
|
}
|
|
11911
12601
|
}
|
|
11912
12602
|
}
|
|
@@ -11918,10 +12608,10 @@ ${renderMissionStatus(activeMission ?? paused)}`);
|
|
|
11918
12608
|
async function showGlobalSafetySettingsMenu(ctx: ExtensionContext) {
|
|
11919
12609
|
if (!ctx.hasUI) return show(pi, renderFullWorkflowSettings(loadWorkflowSettings(ctx.cwd)));
|
|
11920
12610
|
while (ctx.hasUI) {
|
|
11921
|
-
const choice = await ctx.ui.select("Global Safety", ["Repo Lock (Project)", "Disable Bash In Plan Mode", "Disable Bash In Validator Mode", "Block Destructive Commands", "List Current Settings", "Back"]);
|
|
12611
|
+
const choice = await ctx.ui.select("Global Safety", ["Repo Lock (Project)", "Disable Bash In Plan Mode", "Disable Bash In Validator Mode", "Block Destructive Commands", "Allow Package Install In Execution", "List Current Settings", "Back"]);
|
|
11922
12612
|
if (!choice || choice === "Back") return;
|
|
11923
|
-
const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["safety"]> = { "Disable Bash In Plan Mode": "disableBashInPlanMode", "Disable Bash In Validator Mode": "disableBashInValidatorMode", "Block Destructive Commands": "blockDestructiveCommands" };
|
|
11924
|
-
if (choice === "List Current Settings") show(pi,
|
|
12613
|
+
const keyMap: Record<string, keyof ReturnType<typeof loadWorkflowSettings>["safety"]> = { "Disable Bash In Plan Mode": "disableBashInPlanMode", "Disable Bash In Validator Mode": "disableBashInValidatorMode", "Block Destructive Commands": "blockDestructiveCommands", "Allow Package Install In Execution": "allowPackageInstallInExecution" };
|
|
12614
|
+
if (choice === "List Current Settings") show(pi, renderSafetySettings(loadWorkflowSettings(ctx.cwd)));
|
|
11925
12615
|
else if (choice === "Repo Lock (Project)") {
|
|
11926
12616
|
const enabled = await chooseBool(ctx, "safety.repoLockEnabled for this project only?");
|
|
11927
12617
|
if (enabled !== undefined) {
|
|
@@ -12400,6 +13090,7 @@ Pi Version: v${VERSION}
|
|
|
12400
13090
|
const observed = subagentUsageByPhase[phase] ?? 0;
|
|
12401
13091
|
if (observed >= required) return;
|
|
12402
13092
|
if (event.toolName === "edit" || event.toolName === "write" || event.toolName === "bash") {
|
|
13093
|
+
if (phase === "Repair") return;
|
|
12403
13094
|
const label = state.mode === "standard" ? "Standard Mode" : phase;
|
|
12404
13095
|
return { block: true, reason: `${label} ${event.toolName} is blocked because required forced ${phase.toLowerCase()} sub-agent use has not been observed. Required workers: ${required}; observed successful workers: ${observed}.` };
|
|
12405
13096
|
}
|
|
@@ -12592,6 +13283,7 @@ Pi Version: v${VERSION}
|
|
|
12592
13283
|
});
|
|
12593
13284
|
|
|
12594
13285
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
13286
|
+
activateCurrentPlanStepForTool(ctx, { toolName: event.toolName, input: event.args });
|
|
12595
13287
|
workflowActiveToolExecutions += 1;
|
|
12596
13288
|
if (event.toolName === "subagent") beginSubagentActivity(event.toolCallId, event.args as Record<string, unknown>, ctx);
|
|
12597
13289
|
});
|
|
@@ -12662,6 +13354,13 @@ Pi Version: v${VERSION}
|
|
|
12662
13354
|
if (sub === "archive" || sub === "abandon") return archiveAndReturnToPlanInput(ctx, `plan ${sub} requested by /plan ${sub}`);
|
|
12663
13355
|
if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /plan approve"); return; }
|
|
12664
13356
|
if (sub === "revise") { const feedback = rest.join(" ").trim(); if (!state.draftPlan && !state.approvedPlan) return show(pi, "# No plan to revise."); return beginPlanning(ctx, state.task ?? "Revise", state.draftPlan ?? state.approvedPlan, feedback); }
|
|
13357
|
+
if (sub === "complete") {
|
|
13358
|
+
if (state.mode !== "validated") return show(pi, "# Not in validated state. No plan to complete.");
|
|
13359
|
+
if (state.validationVerdict !== "PASS" && state.validationVerdict !== "PARTIAL PASS") return show(pi, `# Cannot complete\n\nValidation verdict is ${state.validationVerdict ?? "unknown"}. Only PASS or PARTIAL PASS can be completed.`);
|
|
13360
|
+
if (state.validationVerdict === "PARTIAL PASS" && state.concreteRepairableIssue !== false) return show(pi, "# Cannot complete\n\nPARTIAL PASS with concrete repairable issues. Run /plan repair first.");
|
|
13361
|
+
await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
|
|
13362
|
+
return;
|
|
13363
|
+
}
|
|
12665
13364
|
if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
|
|
12666
13365
|
await startFreshPlanFromInput(ctx, raw);
|
|
12667
13366
|
};
|
|
@@ -12733,6 +13432,13 @@ Pi Version: v${VERSION}
|
|
|
12733
13432
|
if (sub === "resume") return handlePlanResume(ctx);
|
|
12734
13433
|
if (sub === "approve") { await approveCurrentPlan(ctx, "plan approved by /p approve"); return; }
|
|
12735
13434
|
if (sub === "revise") { const feedback = rest.join(" ").trim(); if (!state.draftPlan && !state.approvedPlan) return show(pi, "# No plan to revise."); return beginPlanning(ctx, state.task ?? "Revise", state.draftPlan ?? state.approvedPlan, feedback); }
|
|
13435
|
+
if (sub === "complete") {
|
|
13436
|
+
if (state.mode !== "validated") return show(pi, "# Not in validated state. No plan to complete.");
|
|
13437
|
+
if (state.validationVerdict !== "PASS" && state.validationVerdict !== "PARTIAL PASS") return show(pi, `# Cannot complete\n\nValidation verdict is ${state.validationVerdict ?? "unknown"}. Only PASS or PARTIAL PASS can be completed.`);
|
|
13438
|
+
if (state.validationVerdict === "PARTIAL PASS" && state.concreteRepairableIssue !== false) return show(pi, "# Cannot complete\n\nPARTIAL PASS with concrete repairable issues. Run /plan repair first.");
|
|
13439
|
+
await completePlanWorkflow(ctx, state.validationReport ?? state.lastValidationFailure ?? "", state.validationVerdict);
|
|
13440
|
+
return;
|
|
13441
|
+
}
|
|
12736
13442
|
if (sub === "retry" || sub === "repair" || sub === "revalidate") { await handleWorkflowRetryCommand(ctx, sub); return; }
|
|
12737
13443
|
await startFreshPlanFromInput(ctx, raw);
|
|
12738
13444
|
};
|
|
@@ -13290,6 +13996,18 @@ Pi Version: v${VERSION}
|
|
|
13290
13996
|
const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxClarificationQuestions = count; });
|
|
13291
13997
|
return show(pi, updatedMessage(result.scope, result.file, "planning.maxClarificationQuestions", String(count)));
|
|
13292
13998
|
}
|
|
13999
|
+
if (subject === "planning" && key === "maxTokens") {
|
|
14000
|
+
const count = parsePositiveInt(value);
|
|
14001
|
+
if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set planning maxTokens <0 = unlimited | positive integer>`");
|
|
14002
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxTokens = count; });
|
|
14003
|
+
return show(pi, updatedMessage(result.scope, result.file, "planning.maxTokens", count === 0 ? "unlimited" : String(count)));
|
|
14004
|
+
}
|
|
14005
|
+
if (subject === "planning" && key === "maxRuntimeHours") {
|
|
14006
|
+
const count = parsePositiveInt(value);
|
|
14007
|
+
if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set planning maxRuntimeHours <0 = unlimited | positive integer>`");
|
|
14008
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.planning.maxRuntimeHours = count; });
|
|
14009
|
+
return show(pi, updatedMessage(result.scope, result.file, "planning.maxRuntimeHours", count === 0 ? "unlimited" : String(count) + " hours"));
|
|
14010
|
+
}
|
|
13293
14011
|
if (subject === "standard" && (key === "enabled" || key === "autoTodoEnabled" || key === "todoProgressVisible" || key === "clarificationEnabled" || key === "interactiveClarificationEnabled" || key === "clarificationQualityGate" || key === "allowClarificationWithoutAnalysis" || key === "useSubagentsBeforeClarification" || key === "allowSubagents" || key === "statusWidgetVisible" || key === "useSharedExecutorModel" || key === "useStandardSpecificModels")) {
|
|
13294
14012
|
if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set standard ${key} <true|false>\``);
|
|
13295
14013
|
const result = updateSettings(ctx.cwd, scope, (s) => {
|
|
@@ -13327,6 +14045,12 @@ Pi Version: v${VERSION}
|
|
|
13327
14045
|
const result = updateSettings(ctx.cwd, scope, (s) => { s.standard.maxClarificationQuestions = count; });
|
|
13328
14046
|
return show(pi, updatedMessage(result.scope, result.file, "standard.maxClarificationQuestions", String(count)));
|
|
13329
14047
|
}
|
|
14048
|
+
if (subject === "standard" && key === "maxTokens") {
|
|
14049
|
+
const count = parsePositiveInt(value);
|
|
14050
|
+
if (count === undefined || count < 0) return show(pi, "# Error\n\nUsage: `/workflow-settings set standard maxTokens <0 = unlimited | positive integer>`");
|
|
14051
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.standard.maxTokens = count; });
|
|
14052
|
+
return show(pi, updatedMessage(result.scope, result.file, "standard.maxTokens", count === 0 ? "unlimited" : String(count)));
|
|
14053
|
+
}
|
|
13330
14054
|
if (subject === "standard" && key === "clarificationTiming") {
|
|
13331
14055
|
const timing = parseClarificationTiming(value);
|
|
13332
14056
|
if (!timing) return show(pi, "# Error\n\nUsage: `/workflow-settings set standard clarificationTiming <after_initial_analysis|immediate>`");
|
|
@@ -13489,11 +14213,11 @@ Pi Version: v${VERSION}
|
|
|
13489
14213
|
const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.subagentPolicy = policy; });
|
|
13490
14214
|
return show(pi, updatedMessage(result.scope, result.file, "missions.subagentPolicy", policy));
|
|
13491
14215
|
}
|
|
13492
|
-
if (subject === "missions" && (key === "maxRuntimeHours" || key === "checkpointIntervalMinutes" || key === "maxClarificationQuestions" || key === "minWorkersForDeep" || key === "minWorkersForMaximum" || key === "watchdogStaleMinutes" || key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries")) {
|
|
14216
|
+
if (subject === "missions" && (key === "maxRuntimeHours" || key === "checkpointIntervalMinutes" || key === "maxClarificationQuestions" || key === "minWorkersForDeep" || key === "minWorkersForMaximum" || key === "watchdogStaleMinutes" || key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" || key === "maxTokens")) {
|
|
13493
14217
|
const rawCount = value === undefined ? NaN : Number(value);
|
|
13494
14218
|
const count = Number.isInteger(rawCount) ? rawCount : undefined;
|
|
13495
|
-
const max = key === "maxRuntimeHours" ? 168 : key === "checkpointIntervalMinutes" ? 168 : key === "maxClarificationQuestions" ? 6 : key === "watchdogStaleMinutes" ? 1440 : key === "maxValidationRetriesPerMilestone" ? 10 : key === "maxValidationRetriesPerMission" ? 100 : key === "maxFinalValidationRetries" ? 10 : 8;
|
|
13496
|
-
const min = key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" ? 0 : 1;
|
|
14219
|
+
const max = key === "maxRuntimeHours" ? 168 : key === "checkpointIntervalMinutes" ? 168 : key === "maxClarificationQuestions" ? 6 : key === "watchdogStaleMinutes" ? 1440 : key === "maxValidationRetriesPerMilestone" ? 10 : key === "maxValidationRetriesPerMission" ? 100 : key === "maxFinalValidationRetries" ? 10 : key === "maxTokens" ? 10000000 : 8;
|
|
14220
|
+
const min = key === "maxValidationRetriesPerMilestone" || key === "maxValidationRetriesPerMission" || key === "maxFinalValidationRetries" || key === "maxTokens" ? 0 : 1;
|
|
13497
14221
|
if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set missions ${key} <${min}-${max}>\``);
|
|
13498
14222
|
const result = updateSettings(ctx.cwd, scope, (s) => { (s.missions as typeof s.missions & Record<string, number | string | boolean>)[key] = count; });
|
|
13499
14223
|
return show(pi, updatedMessage(result.scope, result.file, `missions.${key}`, String(count)));
|
|
@@ -13501,35 +14225,35 @@ Pi Version: v${VERSION}
|
|
|
13501
14225
|
if (subject === "context" && key === "compactionMode") {
|
|
13502
14226
|
const mode = parseCompactionMode(value);
|
|
13503
14227
|
if (!mode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context compactionMode <pi_default|custom_model|custom_agent|disabled>`");
|
|
13504
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14228
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionMode = mode; });
|
|
13505
14229
|
return show(pi, updatedMessage(result.scope, result.file, "context.compactionMode", compactionModeLabel(mode)));
|
|
13506
14230
|
}
|
|
13507
14231
|
if (subject === "context" && key === "customCompactionEnabled") {
|
|
13508
14232
|
if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context customCompactionEnabled <true|false>`");
|
|
13509
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14233
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.context.customCompactionEnabled = bool; });
|
|
13510
14234
|
return show(pi, updatedMessage(result.scope, result.file, "context.customCompactionEnabled", String(bool)));
|
|
13511
14235
|
}
|
|
13512
14236
|
if (subject === "context" && key === "autoCompactionEnabled") {
|
|
13513
14237
|
if (bool === undefined) return show(pi, "# Error\n\nUsage: `/workflow-settings set context autoCompactionEnabled <true|false>`");
|
|
13514
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14238
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.context.autoCompactionEnabled = bool; });
|
|
13515
14239
|
return show(pi, updatedMessage(result.scope, result.file, "context.autoCompactionEnabled", String(bool)) + `\n\n${compactionTriggerStatus(result.settings)}`);
|
|
13516
14240
|
}
|
|
13517
14241
|
if (subject === "context" && key === "workflowCompactionCheckMode") {
|
|
13518
14242
|
const checkMode = parseWorkflowCompactionCheckMode(value);
|
|
13519
14243
|
if (!checkMode) return show(pi, "# Error\n\nUsage: `/workflow-settings set context workflowCompactionCheckMode <boundary|in_session>`");
|
|
13520
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14244
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.context.workflowCompactionCheckMode = checkMode; });
|
|
13521
14245
|
return show(pi, updatedMessage(result.scope, result.file, "context.workflowCompactionCheckMode", workflowCompactionCheckModeLabel(checkMode)) + `\n\n${compactionTriggerStatus(result.settings)}`);
|
|
13522
14246
|
}
|
|
13523
14247
|
if (subject === "context" && (key === "compactionTriggerPercent" || key === "compactionCooldownMinutes" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens")) {
|
|
13524
14248
|
const normalizedValue = String(value ?? "").trim().toLowerCase();
|
|
13525
14249
|
if (key === "compactionTriggerPercent" && (normalizedValue === "default" || normalizedValue === "reset")) {
|
|
13526
14250
|
const fallback = defaultCompactionTriggerPercent();
|
|
13527
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14251
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { s.context.compactionTriggerPercent = fallback; });
|
|
13528
14252
|
return show(pi, updatedMessage(result.scope, result.file, "context.compactionTriggerPercent", `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
|
|
13529
14253
|
}
|
|
13530
14254
|
if ((key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens") && (normalizedValue === "default" || normalizedValue === "reset")) {
|
|
13531
14255
|
const fallback = key === "customCompactionReserveTokens" ? DEFAULT_PI_COMPACTION_RESERVE_TOKENS : DEFAULT_PI_COMPACTION_KEEP_RECENT_TOKENS;
|
|
13532
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14256
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, number>)[key] = fallback; });
|
|
13533
14257
|
return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, `default ${fallback}`) + `\n\n${compactionTriggerStatus(result.settings)}`);
|
|
13534
14258
|
}
|
|
13535
14259
|
const rawCount = value === undefined ? NaN : Number(value);
|
|
@@ -13538,12 +14262,12 @@ Pi Version: v${VERSION}
|
|
|
13538
14262
|
const max = key === "compactionTriggerPercent" ? 95 : key === "compactionCooldownMinutes" ? 240 : key === "customCompactionReserveTokens" ? 65536 : 200000;
|
|
13539
14263
|
const usage = key === "compactionTriggerPercent" || key === "customCompactionReserveTokens" || key === "customCompactionKeepRecentTokens" ? `<${min}-${max}|default|reset>` : `<${min}-${max}>`;
|
|
13540
14264
|
if (count === undefined || count < min || count > max) return show(pi, `# Error\n\nUsage: \`/workflow-settings set context ${key} ${usage}\``);
|
|
13541
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14265
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, number>)[key] = count; });
|
|
13542
14266
|
return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, String(count)) + `\n\n${compactionTriggerStatus(result.settings)}`);
|
|
13543
14267
|
}
|
|
13544
14268
|
if (subject === "context" && (key === "compactionModelProvider" || key === "compactionModel" || key === "compactionAgent")) {
|
|
13545
14269
|
const text = parts.slice(offset + 2).join(" ");
|
|
13546
|
-
const result = updateSettings(ctx.cwd, scope
|
|
14270
|
+
const result = updateSettings(ctx.cwd, scope, (s) => { (s.context as typeof s.context & Record<string, string>)[key] = text; });
|
|
13547
14271
|
return show(pi, updatedMessage(result.scope, result.file, `context.${key}`, text || "(empty)"));
|
|
13548
14272
|
}
|
|
13549
14273
|
if (subject === "workflow" && key === "repairRetry.enabled") {
|
|
@@ -13611,7 +14335,7 @@ Pi Version: v${VERSION}
|
|
|
13611
14335
|
const result = updateSettings(ctx.cwd, scope, (s) => { s.missions.maxReviewRetriesPerMission = count; });
|
|
13612
14336
|
return show(pi, updatedMessage(result.scope, result.file, "missions.maxReviewRetriesPerMission", String(count)));
|
|
13613
14337
|
}
|
|
13614
|
-
if (subject === "workflow" && (key === "returnToPlanModeAfterWorkflow" || key === "autoRepairValidationFailures" || key === "pauseAfterValidationFailure" || key === "autoRepairReviewFailures" || key === "pauseAfterReviewFailure" || key === "requireApprovalForOutOfScopeRepair" || key === "requireApprovalForDestructiveRepair" || key === "planProgressEnabled" || key === "planRuntimeEnabled" || key === "requireApprovalBeforeExecution" || key === "requireApprovalPerStep" || key === "validateAfterEachStep" || key === "validateAfterExecution")) {
|
|
14338
|
+
if (subject === "workflow" && (key === "returnToPlanModeAfterWorkflow" || key === "autoRepairValidationFailures" || key === "pauseAfterValidationFailure" || key === "autoRepairReviewFailures" || key === "pauseAfterReviewFailure" || key === "requireApprovalForOutOfScopeRepair" || key === "requireApprovalForDestructiveRepair" || key === "planProgressEnabled" || key === "planRuntimeEnabled" || key === "planShowProgressBar" || key === "requireApprovalBeforeExecution" || key === "requireApprovalPerStep" || key === "validateAfterEachStep" || key === "validateAfterExecution")) {
|
|
13615
14339
|
if (bool === undefined) return show(pi, `# Error\n\nUsage: \`/workflow-settings set workflow ${key} <true|false>\``);
|
|
13616
14340
|
const result = updateSettings(ctx.cwd, scope, (s) => {
|
|
13617
14341
|
(s.workflow as typeof s.workflow & Record<string, boolean | number | string | undefined>)[key] = bool;
|
|
@@ -13868,6 +14592,23 @@ Public workflow commands:
|
|
|
13868
14592
|
else return;
|
|
13869
14593
|
}
|
|
13870
14594
|
|
|
14595
|
+
function isPlanApprovedContinueInput(text: string): boolean {
|
|
14596
|
+
return /^(continue|proceed|execute|run|go|start|start execution|continue plan|run plan|execute plan)$/i.test(text.trim());
|
|
14597
|
+
}
|
|
14598
|
+
|
|
14599
|
+
async function handlePlanApprovedPlainInput(ctx: ExtensionContext, text: string) {
|
|
14600
|
+
if (isPlanApprovedContinueInput(text)) {
|
|
14601
|
+
await handlePlanContinue(ctx);
|
|
14602
|
+
return;
|
|
14603
|
+
}
|
|
14604
|
+
if (!ctx.hasUI) return show(pi, `# Approved Plan Loaded\n\nThe current plan is approved, but plain text is not treated as an execution turn because execution requires a tool-armed handoff.\n\nUse one of:\n- /plan continue\n- /plan revise ${text}\n- /plan cancel\n- /p ${text}`);
|
|
14605
|
+
const choice = await ctx.ui.select("Approved plan is loaded. What should this message do?", ["Continue Current Plan", "Revise Current Plan With This Feedback", "Start New Plan", "Cancel"]);
|
|
14606
|
+
if (choice === "Continue Current Plan") await handlePlanContinue(ctx);
|
|
14607
|
+
else if (choice === "Revise Current Plan With This Feedback") await beginPlanning(ctx, state.task ?? "Revise", state.approvedPlan, text);
|
|
14608
|
+
else if (choice === "Start New Plan") await startFreshPlanFromInput(ctx, text);
|
|
14609
|
+
else return;
|
|
14610
|
+
}
|
|
14611
|
+
|
|
13871
14612
|
async function handleMissionPlainInputChoice(ctx: ExtensionContext, text: string) {
|
|
13872
14613
|
const normalized = text.trim().toLowerCase();
|
|
13873
14614
|
if (normalized === "status") {
|
|
@@ -13886,6 +14627,10 @@ Public workflow commands:
|
|
|
13886
14627
|
await startFreshPlanFromInput(ctx, text);
|
|
13887
14628
|
return { action: "handled" as const };
|
|
13888
14629
|
}
|
|
14630
|
+
if (state.mode === "plan_approved") {
|
|
14631
|
+
await handlePlanApprovedPlainInput(ctx, text);
|
|
14632
|
+
return { action: "handled" as const };
|
|
14633
|
+
}
|
|
13889
14634
|
if (planInputNeedsChoice(state)) {
|
|
13890
14635
|
await handlePlanPlainInputChoice(ctx, text);
|
|
13891
14636
|
return { action: "handled" as const };
|
|
@@ -13924,6 +14669,40 @@ Public workflow commands:
|
|
|
13924
14669
|
if (workflowSubagentWorkerMode()) return { systemPrompt: event.systemPrompt };
|
|
13925
14670
|
stopStartupVisual(ctx);
|
|
13926
14671
|
|
|
14672
|
+
// ── Token budget enforcement ──────────────────────────
|
|
14673
|
+
const contextTokens = ctx.getContextUsage()?.tokens ?? 0;
|
|
14674
|
+
const settingsForBudget = loadWorkflowSettings(ctx.cwd);
|
|
14675
|
+
if (state.mode === "standard" && (settingsForBudget.standard.maxTokens ?? 0) > 0) {
|
|
14676
|
+
const used = (state.standardTokensUsed ?? 0) + contextTokens;
|
|
14677
|
+
updateState({ standardTokensUsed: used }, ctx);
|
|
14678
|
+
if (used > settingsForBudget.standard.maxTokens!) {
|
|
14679
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW STANDARD TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Standard Mode token budget (${settingsForBudget.standard.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this session.` };
|
|
14680
|
+
}
|
|
14681
|
+
}
|
|
14682
|
+
const inPlan = state.mode === "planning" || state.mode === "plan_approved" || state.mode === "reviewed" || state.mode === "executing" || state.mode === "validating" || state.mode === "revalidating" || state.mode === "repairing" || state.mode === "validated" || state.mode === "awaiting_plan_input" || state.mode === "plan_review";
|
|
14683
|
+
if (inPlan && (settingsForBudget.planning.maxTokens ?? 0) > 0) {
|
|
14684
|
+
const used = (state.planTokensUsed ?? 0) + contextTokens;
|
|
14685
|
+
updateState({ planTokensUsed: used }, ctx);
|
|
14686
|
+
if (used > settingsForBudget.planning.maxTokens!) {
|
|
14687
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Plan Mode token budget (${settingsForBudget.planning.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this workflow.` };
|
|
14688
|
+
}
|
|
14689
|
+
}
|
|
14690
|
+
if (inPlan && (settingsForBudget.planning.maxRuntimeHours ?? 0) > 0) {
|
|
14691
|
+
const activeMs = planActiveRuntimeMs(state);
|
|
14692
|
+
const maxMs = settingsForBudget.planning.maxRuntimeHours! * 3_600_000;
|
|
14693
|
+
if (activeMs >= maxMs) {
|
|
14694
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW PLAN RUNTIME BUDGET EXCEEDED: active runtime (${formatDurationMs(activeMs)}) exceeds the configured Plan Mode runtime budget (${settingsForBudget.planning.maxRuntimeHours} hours). The configured budget limit has been reached for this workflow.` };
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
const inMission = state.mode === "planning" || state.mode === "plan_ready" || state.mode === "approved" || state.mode === "running" || state.mode === "checkpointing" || state.mode === "validating" || state.mode === "revalidating" || state.mode === "repairing" || state.mode === "paused" || state.mode === "stopped" || state.mode === "awaiting_mission_input" || state.mode === "mission_plan_review";
|
|
14698
|
+
if (inMission && (settingsForBudget.missions.maxTokens ?? 0) > 0) {
|
|
14699
|
+
const used = (state.missionTokensUsed ?? 0) + contextTokens;
|
|
14700
|
+
updateState({ missionTokensUsed: used }, ctx);
|
|
14701
|
+
if (used > settingsForBudget.missions.maxTokens!) {
|
|
14702
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MISSION TOKEN BUDGET EXCEEDED: cumulative estimated tokens (${used.toLocaleString()}) exceeds the configured Mission Mode token budget (${settingsForBudget.missions.maxTokens!.toLocaleString()}). The configured budget limit has been reached for this mission.` };
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
|
|
13927
14706
|
// Standard Mode: direct active work with optional dynamic To Do tracking.
|
|
13928
14707
|
if (state.mode === "standard") {
|
|
13929
14708
|
setStandardRuntimeActive(ctx, true);
|
|
@@ -13980,7 +14759,13 @@ Public workflow commands:
|
|
|
13980
14759
|
|
|
13981
14760
|
// Mission Mode: intercept user message as mission goal
|
|
13982
14761
|
if (state.mode === "awaiting_mission_input") {
|
|
13983
|
-
const
|
|
14762
|
+
const prompt = (event.prompt ?? "").trim();
|
|
14763
|
+
// Guard: skip system-generated content (final summaries, handoff reports, triggered agent turns).
|
|
14764
|
+
// User goals are short natural-language phrases; system content is multi-kilobyte structured markdown.
|
|
14765
|
+
if (prompt.length > 2000 || /^##\s+Final\s+Mission\b/m.test(prompt) || prompt.startsWith("Print this comprehensive")) {
|
|
14766
|
+
return { systemPrompt: event.systemPrompt };
|
|
14767
|
+
}
|
|
14768
|
+
const goal = prompt || "Start a mission from the user's requested goal.";
|
|
13984
14769
|
await startMissionFromGoal(ctx, goal);
|
|
13985
14770
|
return { systemPrompt: event.systemPrompt };
|
|
13986
14771
|
}
|
|
@@ -14010,13 +14795,17 @@ Public workflow commands:
|
|
|
14010
14795
|
|
|
14011
14796
|
if (state.mode === "mission_plan_ready") {
|
|
14012
14797
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14013
|
-
if (mission?.currentStep === "reviewer")
|
|
14798
|
+
if (mission?.currentStep === "reviewer") {
|
|
14799
|
+
pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14800
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${missionReviewPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
|
|
14801
|
+
}
|
|
14014
14802
|
}
|
|
14015
14803
|
|
|
14016
14804
|
if (state.mode === "mission_running") {
|
|
14017
14805
|
if (state.lastWorkflowHandoff?.type === MISSION_MILESTONE_RESULT_TOOL) return;
|
|
14018
14806
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14019
14807
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
14808
|
+
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14020
14809
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionRuntimePrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
|
|
14021
14810
|
}
|
|
14022
14811
|
|
|
@@ -14024,18 +14813,21 @@ Public workflow commands:
|
|
|
14024
14813
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
|
|
14025
14814
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14026
14815
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
14816
|
+
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14027
14817
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionRepairPrompt(mission, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
|
|
14028
14818
|
}
|
|
14029
14819
|
|
|
14030
14820
|
if (state.mode === "mission_validating" || state.mode === "mission_revalidating") {
|
|
14031
14821
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14032
14822
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
14823
|
+
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14033
14824
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
14034
14825
|
}
|
|
14035
14826
|
|
|
14036
14827
|
if (state.mode === "mission_final_validating") {
|
|
14037
14828
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14038
14829
|
if (!mission) return { systemPrompt: event.systemPrompt };
|
|
14830
|
+
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14039
14831
|
return { systemPrompt: `${event.systemPrompt}\n\n${missionFinalValidationPrompt(mission, loadWorkflowSettings(ctx.cwd), state.executionSummary, phasePreflightBlocks.Validation)}` };
|
|
14040
14832
|
}
|
|
14041
14833
|
|
|
@@ -14063,19 +14855,30 @@ Public workflow commands:
|
|
|
14063
14855
|
}
|
|
14064
14856
|
|
|
14065
14857
|
// Other planning modes: read-only
|
|
14066
|
-
if (state.mode === "planning" || state.mode === "plan_draft"
|
|
14858
|
+
if (state.mode === "planning" || state.mode === "plan_draft") {
|
|
14067
14859
|
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN. Read-only tools only.` };
|
|
14068
14860
|
}
|
|
14861
|
+
if (state.mode === "plan_approved") {
|
|
14862
|
+
if (state.approvedPlan) {
|
|
14863
|
+
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14864
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN APPROVED. Approved plan is loaded. To execute, use /plan continue so the workflow can arm a fresh execution turn. Do not call workflow_plan_result in this state. Read-only inspection tools remain available. To change direction, use /plan revise <feedback> or /plan cancel.` };
|
|
14865
|
+
}
|
|
14866
|
+
return { systemPrompt: `${event.systemPrompt}\n\nPI WORKFLOW MODE: PLAN APPROVED. The plan has been approved but plan data is missing. Use /plan continue to resume or /plan cancel to exit. Write/edit/bash tools are not available.` };
|
|
14867
|
+
}
|
|
14069
14868
|
if (state.mode === "reviewing" || state.mode === "reviewed") {
|
|
14869
|
+
pi.setActiveTools(reviewToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14070
14870
|
return { systemPrompt: `${event.systemPrompt}\n\n${reviewerPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Review)}` };
|
|
14071
14871
|
}
|
|
14072
14872
|
if (state.mode === "executing") {
|
|
14873
|
+
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14073
14874
|
return { systemPrompt: `${event.systemPrompt}\n\n${executePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Execution)}` };
|
|
14074
14875
|
}
|
|
14075
14876
|
if (state.mode === "repairing") {
|
|
14877
|
+
pi.setActiveTools(executionToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14076
14878
|
return { systemPrompt: `${event.systemPrompt}\n\n${workflowRepairPrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Repair)}` };
|
|
14077
14879
|
}
|
|
14078
14880
|
if (state.mode === "validating" || state.mode === "revalidating" || state.mode === "validated") {
|
|
14881
|
+
pi.setActiveTools(validationToolsFor(loadWorkflowSettings(ctx.cwd)));
|
|
14079
14882
|
return { systemPrompt: `${event.systemPrompt}\n\n${validatePrompt(state, loadWorkflowSettings(ctx.cwd), phasePreflightBlocks.Validation)}` };
|
|
14080
14883
|
}
|
|
14081
14884
|
});
|
|
@@ -14084,6 +14887,9 @@ Public workflow commands:
|
|
|
14084
14887
|
if (!isAssistantMessage(event.message as AgentMessage)) return;
|
|
14085
14888
|
const message = event.message as AssistantMessage;
|
|
14086
14889
|
const text = assistantText(message);
|
|
14890
|
+
if (state.mode === "executing" || state.mode === "repairing") {
|
|
14891
|
+
applyPlanProgressMarkers(ctx, text);
|
|
14892
|
+
}
|
|
14087
14893
|
if (text && workflowMermaidSegmentsHaveDiagram(text)) {
|
|
14088
14894
|
void emitWorkflowMermaidDiagrams(pi, text);
|
|
14089
14895
|
return { message: { ...message, content: message.content.map((block) => block.type === "text" ? { ...block, text: markdownWithoutWorkflowMermaidBlocks(block.text) } : block) } };
|
|
@@ -14207,10 +15013,10 @@ Public workflow commands:
|
|
|
14207
15013
|
deferWorkflowAction(pi, "handle mission reviewer failure", () => handleMissionReviewFailure(ctx, mission, reviewVerdict, text));
|
|
14208
15014
|
return;
|
|
14209
15015
|
}
|
|
14210
|
-
const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: 0, lastReviewFailure: "", lastReviewAttempt: reviewVerdict, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, currentStep: undefined, nextAction: "Mission review complete.
|
|
15016
|
+
const reviewed = saveActiveMission({ ...mission, reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: 0, lastReviewFailure: "", lastReviewAttempt: reviewVerdict, lastReviewRepairStatus: mission.lastReviewRepairStatus === "running" ? "completed" : (mission.lastReviewRepairStatus ?? "none"), reviewRepairInProgress: false, currentStep: undefined, nextAction: "Mission review complete.", lastSummary: `Mission reviewer verdict ${reviewVerdict}; mission plan ready for approval.` });
|
|
14211
15017
|
activeMission = reviewed;
|
|
14212
15018
|
updateState({ mode: "mission_plan_ready", activeMissionId: reviewed.id, task: reviewed.goal, originalTask: reviewed.goal, draftPlan: reviewed.planText }, ctx);
|
|
14213
|
-
|
|
15019
|
+
deferWorkflowAction(pi, "resume mission approval after forced reviewer pass", () => handleMissionApprovalHandoff(ctx, reviewed, "menu"));
|
|
14214
15020
|
return;
|
|
14215
15021
|
}
|
|
14216
15022
|
}
|
|
@@ -14398,6 +15204,26 @@ Public workflow commands:
|
|
|
14398
15204
|
if (state.mode === "mission_repairing") {
|
|
14399
15205
|
const mission = activeMission ?? loadMissionState(state.activeMissionId ?? "latest");
|
|
14400
15206
|
if (!mission) { updateState({ mode: "idle" }, ctx); return; }
|
|
15207
|
+
const repairTextWasInterrupted = planOutputWasAborted(text) || ((text?.length ?? 0) < 200 && !/[.!?]\s*$/.test(text?.trim() ?? ""));
|
|
15208
|
+
if (repairTextWasInterrupted) {
|
|
15209
|
+
const preservedRetry = Math.max(0, (mission.currentValidationRetry ?? 1) - 1);
|
|
15210
|
+
const preservedMissionRetry = Math.max(0, (mission.missionValidationRetryCount ?? 1) - 1);
|
|
15211
|
+
const interrupted = saveActiveMission({
|
|
15212
|
+
...mission,
|
|
15213
|
+
status: "paused",
|
|
15214
|
+
currentValidationRetry: preservedRetry,
|
|
15215
|
+
missionValidationRetryCount: preservedMissionRetry,
|
|
15216
|
+
lastRepairStatus: "interrupted",
|
|
15217
|
+
lastRepairAttempt: compact(text, 1200),
|
|
15218
|
+
nextAction: "Repair turn was interrupted by connection error. Retry /mission repair or /mission revalidate.",
|
|
15219
|
+
lastSummary: "Mission repair interrupted before completion.",
|
|
15220
|
+
repairHistory: appendRepairHistory(mission, { timestamp: new Date().toISOString(), milestoneId: mission.milestones[mission.currentMilestoneIndex]?.id, retry: preservedRetry, status: "interrupted", repairSummary: compact(text, 800), nextAction: "Repair turn was interrupted. Retry /mission repair or /mission revalidate." }),
|
|
15221
|
+
});
|
|
15222
|
+
checkpointMission(interrupted, `Mission repair interrupted for ${mission.milestones[mission.currentMilestoneIndex]?.id ?? "current milestone"}. Retry count preserved.`, "Retry /mission repair or /mission revalidate.", mission.milestones[mission.currentMilestoneIndex]?.id, { validationResult: interrupted.lastValidationResult });
|
|
15223
|
+
updateState({ mode: "mission_paused", activeMissionId: interrupted.id }, ctx);
|
|
15224
|
+
show(pi, "# Mission Repair Interrupted\n\nThe repair turn was interrupted before completion (connection error or aborted turn). Your repair retry count has been preserved.\n\nUse `/mission repair` to retry the repair, or `/mission revalidate` to run validation again.");
|
|
15225
|
+
return;
|
|
15226
|
+
}
|
|
14401
15227
|
await completeMissionRepairAndStartRevalidation(ctx, mission, text);
|
|
14402
15228
|
return;
|
|
14403
15229
|
}
|
|
@@ -14577,13 +15403,30 @@ Public workflow commands:
|
|
|
14577
15403
|
// ── Reviewer complete ──
|
|
14578
15404
|
if (state.mode === "reviewing") {
|
|
14579
15405
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_REVIEW_RESULT_TOOL) return;
|
|
15406
|
+
planForcedSubagentPreflightReconcile(ctx, "Review");
|
|
14580
15407
|
if (blockIfForcedSubagentsMissing(ctx, "Review")) {
|
|
14581
15408
|
updateState({ mode: "plan_approved", reviewerReport: text, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", nextAction: "review blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
|
|
14582
15409
|
return;
|
|
14583
15410
|
}
|
|
14584
15411
|
const reviewVerdict = extractReviewVerdict(text);
|
|
14585
15412
|
if (reviewVerdict === "UNKNOWN") {
|
|
14586
|
-
const
|
|
15413
|
+
const hasSubstantiveContent = text.length > 200 && (
|
|
15414
|
+
text.includes("Review Report") || text.includes("Reviewer Report") ||
|
|
15415
|
+
text.includes("Verdict") || text.includes("Milestone") ||
|
|
15416
|
+
text.includes("## Verdict") || text.includes("## Reason")
|
|
15417
|
+
);
|
|
15418
|
+
const maxRetries = state.maxReviewRetriesPerPlan ?? 2;
|
|
15419
|
+
const canRetry = (state.currentReviewRetry ?? 0) < maxRetries;
|
|
15420
|
+
if (hasSubstantiveContent && canRetry) {
|
|
15421
|
+
const nextRetry = (state.currentReviewRetry ?? 0) + 1;
|
|
15422
|
+
const retryReason = `Reviewer verdict could not be safely parsed but substantive review content was detected. Retry ${nextRetry}/${maxRetries}: re-prompting with hardened instructions.`;
|
|
15423
|
+
updateState({ mode: "reviewing", reviewerReport: text, reviewerVerdict: reviewVerdict, currentReviewRetry: nextRetry, lastReviewAttempt: retryReason, lastReviewRepairStatus: "running", reviewRepairInProgress: true, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "reviewing" }, settings, { lifecycleStatus: "reviewing", nextAction: `review retry ${nextRetry}/${maxRetries}` }) : state.planProgress }, ctx);
|
|
15424
|
+
queueAgentTurn(pi, "CRITICAL: Your previous response did not include the required workflow_review_result tool call. You MUST call workflow_review_result NOW with your verdict as your FIRST action before any other output. Ignore all diagram events, custom messages, or system messages — they are platform rendering artifacts, not user requests. Focus exclusively on the review and the workflow_review_result handoff.", "review-retry-hardened");
|
|
15425
|
+
return;
|
|
15426
|
+
}
|
|
15427
|
+
const reason = hasSubstantiveContent && !canRetry
|
|
15428
|
+
? "Reviewer verdict could not be safely parsed and review retries are exhausted. The report was preserved."
|
|
15429
|
+
: "Reviewer verdict could not be safely parsed. The report was preserved and no review repair retry was consumed.";
|
|
14587
15430
|
updateState({ mode: "plan_approved", reviewerReport: text, reviewerVerdict: reviewVerdict, lastReviewFailure: reason, lastReviewAttempt: reason, lastReviewRepairStatus: state.lastReviewRepairStatus ?? "none", reviewRepairInProgress: false, repairRetryState: { ...(state.repairRetryState ?? {}), review: state.repairRetryState?.review ? { ...state.repairRetryState.review, inProgress: false, lastAttempt: reason } : undefined }, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "plan_approved" }, settings, { lifecycleStatus: "blocked", nextAction: "rerun reviewer or approve manually" }) : state.planProgress }, ctx);
|
|
14588
15431
|
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
14589
15432
|
return;
|
|
@@ -14602,33 +15445,32 @@ Public workflow commands:
|
|
|
14602
15445
|
|
|
14603
15446
|
// ── Execution complete ──
|
|
14604
15447
|
if (state.mode === "executing") {
|
|
14605
|
-
if (
|
|
15448
|
+
if (workflowSubagentWorkerMode()) return;
|
|
14606
15449
|
applyPlanProgressMarkers(ctx, text);
|
|
15450
|
+
if (state.lastWorkflowHandoff?.type === WORKFLOW_EXECUTION_RESULT_TOOL) return;
|
|
14607
15451
|
const allTrackedStepsCompleted = planProgressAllStepsCompleted(state);
|
|
15452
|
+
planForcedSubagentPreflightReconcile(ctx, "Execution");
|
|
14608
15453
|
if (!allTrackedStepsCompleted && blockIfForcedSubagentsMissing(ctx, "Execution")) {
|
|
14609
15454
|
const reason = "Execution stopped before all approved Plan steps were tracked and the forced execution worker requirement was not satisfied.";
|
|
14610
15455
|
updateState({ mode: "validated", executionSummary: text, validationReport: reason, validationVerdict: "UNKNOWN", planExecutionStepIndex: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "fix execution progress/sub-agent blocker then /plan continue" }) : state.planProgress }, ctx);
|
|
14611
15456
|
queuePlanTerminalSummary(ctx, "blocked", "Plan execution blocked", { validationText: reason, verdict: "UNKNOWN", reason });
|
|
14612
15457
|
return;
|
|
14613
15458
|
}
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
queueAgentTurn(pi, `The Plan execution summary was not accepted because approved Plan step progress was incomplete. Do not redo work or edit files. Submit a corrected workflow_execution_result now with status, completedSteps for every approved Plan step completed, changedFiles, commands, blockers, and summary. Reason: ${reason}`, "workflow-execution-progress-correction");
|
|
14619
|
-
return;
|
|
14620
|
-
}
|
|
15459
|
+
const progressWarning = !allTrackedStepsCompleted && settings.workflow.validateAfterEachStep !== true && settings.workflow.requireApprovalPerStep !== true && workflowPlanProgressEnabled(settings) && state.planProgress?.steps.length
|
|
15460
|
+
? "Execution summary did not include complete Plan progress metadata; validator must verify approved Plan coverage."
|
|
15461
|
+
: undefined;
|
|
15462
|
+
const executionSummary = progressWarning ? `${text}\n\nPlan Progress Warning: ${progressWarning}` : text;
|
|
14621
15463
|
const stepValidationEnabled = settings.workflow.validateAfterEachStep === true;
|
|
14622
15464
|
const stepApprovalEnabled = settings.workflow.requireApprovalPerStep === true;
|
|
14623
15465
|
const executedStepIndex = typeof state.planExecutionStepIndex === "number" ? state.planExecutionStepIndex : state.planProgress?.currentStepIndex ?? 0;
|
|
14624
15466
|
const stepValidationIndex = stepValidationEnabled ? executedStepIndex : undefined;
|
|
14625
15467
|
const validationGateActive = planValidationGateActive(settings);
|
|
14626
|
-
updateState({ mode: "executed", executionSummary
|
|
15468
|
+
updateState({ mode: "executed", executionSummary, planStepValidationIndex: stepValidationIndex, planExecutionStepIndex: undefined, planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "executed" }, settings, { lifecycleStatus: "executing", validationStatus: progressWarning ? "unknown" : state.planProgress?.validationStatus, nextAction: validationGateActive ? "validator" : "finish workflow" }) : state.planProgress }, ctx);
|
|
14627
15469
|
const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
|
|
14628
15470
|
if (mission?.status === "running") {
|
|
14629
15471
|
const milestone = mission.milestones[mission.currentMilestoneIndex];
|
|
14630
15472
|
const validatingMission = saveActiveMission({ ...mission, status: "validating", currentStep: "validator", lastSummary: `Execution completed for mission milestone ${milestone?.id ?? "current"}.` });
|
|
14631
|
-
checkpointMission(validatingMission, `Execution completed for mission milestone ${milestone?.id ?? "current"}. ${compact(
|
|
15473
|
+
checkpointMission(validatingMission, `Execution completed for mission milestone ${milestone?.id ?? "current"}. ${compact(executionSummary, 500)}`, "Run validator and pause on validation failure.", milestone?.id);
|
|
14632
15474
|
updateState({ mode: "mission_validating" }, ctx);
|
|
14633
15475
|
deferWorkflowAction(pi, "begin mission validation after execution", () => beginMissionValidation(ctx, true));
|
|
14634
15476
|
return;
|
|
@@ -14676,10 +15518,28 @@ Public workflow commands:
|
|
|
14676
15518
|
if (state.mode === "repairing") {
|
|
14677
15519
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_REPAIR_RESULT_TOOL) return;
|
|
14678
15520
|
applyPlanProgressMarkers(ctx, text);
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
15521
|
+
planForcedSubagentPreflightReconcile(ctx, "Repair");
|
|
15522
|
+
const repairBlockedBySubagents = blockIfForcedSubagentsMissing(ctx, "Repair");
|
|
15523
|
+
if (repairBlockedBySubagents) {
|
|
15524
|
+
const repairPolicy = phasePolicy(settings, "Repair");
|
|
15525
|
+
const repairWorkers = workerCount(settings, "Repair");
|
|
15526
|
+
const repairRequired = workerTargetForPolicy(repairPolicy, repairWorkers);
|
|
15527
|
+
const availableRepairAgents = listEffectiveAgents(ctx.cwd);
|
|
15528
|
+
const repairSuitable = repairPolicy === "forced" ? chooseForcedSubagents("Repair", repairRequired, "Repair", availableRepairAgents) : [];
|
|
15529
|
+
const hasWriteCapableSubagents = repairSuitable.some(a => subagentToolsAllowMutation(a.tools));
|
|
15530
|
+
if (hasWriteCapableSubagents) {
|
|
15531
|
+
const required = workerTargetForPolicy("forced", workerCount(settings, "Repair"));
|
|
15532
|
+
const observed = subagentUsageByPhase.Repair ?? 0;
|
|
15533
|
+
updateState({ mode: "validated", lastRepairStatus: "blocked", lastRepairAttempt: compact(text, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: state.currentValidationRetry ?? 0, status: "blocked", repairSummary: compact(text, 800), nextAction: `Repair summary saved, but revalidation blocked by forced repair sub-agent policy (${observed}/${required} workers).` }), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated" }, settings, { lifecycleStatus: "blocked", repairStatus: "blocked", nextAction: "repair summary saved; forced repair sub-agents missing" }) : state.planProgress }, ctx);
|
|
15534
|
+
return;
|
|
15535
|
+
}
|
|
15536
|
+
}
|
|
15537
|
+
const repairTextWasInterrupted = planOutputWasAborted(text) || ((text?.length ?? 0) < 200 && !/[.!?]\s*$/.test(text?.trim() ?? ""));
|
|
15538
|
+
if (repairTextWasInterrupted) {
|
|
15539
|
+
const preservedRetry = Math.max(0, (state.currentValidationRetry ?? 1) - 1);
|
|
15540
|
+
const preservedWorkflowRetry = Math.max(0, (state.workflowValidationRetryCount ?? 1) - 1);
|
|
15541
|
+
updateState({ mode: "plan_approved", executionSummary: state.executionSummary, currentValidationRetry: preservedRetry, workflowValidationRetryCount: preservedWorkflowRetry, lastRepairStatus: "interrupted", lastRepairAttempt: compact(text, 1200), repairHistory: appendWorkflowRepairHistory({ timestamp: new Date().toISOString(), retry: preservedRetry, status: "interrupted", repairSummary: compact(text, 800), nextAction: "Repair turn was interrupted by connection error. Retry /plan repair or /plan revalidate." }) }, ctx);
|
|
15542
|
+
show(pi, "# Plan Repair Interrupted\n\nThe repair turn was interrupted before completion (connection error or aborted turn). Your repair retry count has been preserved.\n\nUse `/plan repair` to retry the repair, or `/plan revalidate` to run validation again.");
|
|
14683
15543
|
return;
|
|
14684
15544
|
}
|
|
14685
15545
|
const repairSafetyBlock = workflowValidationFailureRequiresApproval(text, settings);
|
|
@@ -14699,13 +15559,16 @@ Public workflow commands:
|
|
|
14699
15559
|
// ── Validation complete ──
|
|
14700
15560
|
if (state.mode === "validating" || state.mode === "revalidating") {
|
|
14701
15561
|
if (state.lastWorkflowHandoff?.type === WORKFLOW_VALIDATION_RESULT_TOOL) return;
|
|
15562
|
+
planForcedSubagentPreflightReconcile(ctx, "Validation");
|
|
14702
15563
|
if (blockIfForcedSubagentsMissing(ctx, "Validation")) {
|
|
14703
|
-
updateState({ mode: "
|
|
15564
|
+
updateState({ mode: "validated", validationReport: text, validationVerdict: "UNKNOWN", lastValidationFailure: "Validation blocked: forced sub-agent policy was not satisfied. A validation preflight worker must validate the approved plan before verdict acceptance.", planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: "UNKNOWN" }, settings, { lifecycleStatus: "blocked", validationStatus: "unknown", nextAction: "validation blocked by forced sub-agent policy" }) : state.planProgress }, ctx);
|
|
15565
|
+
queuePlanTerminalSummary(ctx, "blocked", "Plan validation blocked", { validationText: text, verdict: "UNKNOWN", reason: "Validation blocked by forced sub-agent policy" });
|
|
14704
15566
|
return;
|
|
14705
15567
|
}
|
|
14706
15568
|
const verdict = normalizeValidationVerdict(extractVerdict(text), text);
|
|
14707
15569
|
const validationStatus = planValidationStatusForVerdict(verdict);
|
|
14708
|
-
|
|
15570
|
+
const freeformNoDefectPartialPass = verdict === "PARTIAL PASS" && classifyValidationFailure(verdict, text) === "manual_only";
|
|
15571
|
+
updateState({ mode: "validated", validationReport: text, validationVerdict: verdict, lastValidationCompletedAt: new Date().toISOString(), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: (verdict === "PASS" || freeformNoDefectPartialPass) ? "completed" : "blocked", validationStatus, lastValidationStatus: validationStatus, nextAction: (verdict === "PASS" || freeformNoDefectPartialPass) ? "new planning request" : "repair/revalidate or revise" }) : state.planProgress }, ctx);
|
|
14709
15572
|
const mission = isMissionWorkflowMode(state) ? activeMission ?? loadMissionState(state.activeMissionId ?? "latest") : undefined;
|
|
14710
15573
|
if (mission?.status === "running" || mission?.status === "validating") {
|
|
14711
15574
|
const index = mission.currentMilestoneIndex;
|
|
@@ -14777,6 +15640,16 @@ Public workflow commands:
|
|
|
14777
15640
|
return;
|
|
14778
15641
|
}
|
|
14779
15642
|
if (verdict !== "PASS") {
|
|
15643
|
+
if (freeformNoDefectPartialPass) {
|
|
15644
|
+
if (returnToPlan) {
|
|
15645
|
+
deferWorkflowAction(pi, "complete plan workflow after validation", () => completePlanWorkflow(ctx, text, verdict));
|
|
15646
|
+
} else {
|
|
15647
|
+
updateState({ currentValidationRetry: 0, lastValidationFailure: "", lastRepairStatus: state.lastRepairStatus === "running" ? "completed" : (state.lastRepairStatus ?? "none"), planProgress: workflowPlanProgressEnabled(settings) ? mergePlanProgress({ ...state, mode: "validated", validationVerdict: verdict }, settings, { lifecycleStatus: "completed", validationStatus: "pass", lastValidationStatus: "pass", repairRetry: 0, nextAction: "list summary or revise" }) : state.planProgress }, ctx);
|
|
15648
|
+
recordWorkflowInternalEvent(ctx, "Internal workflow lifecycle event suppressed.");
|
|
15649
|
+
deferWorkflowAction(pi, "show final menu after validation", () => showFinalMenu(ctx));
|
|
15650
|
+
}
|
|
15651
|
+
return;
|
|
15652
|
+
}
|
|
14780
15653
|
deferWorkflowAction(pi, "handle validation failure", () => handleWorkflowValidationFailure(ctx, verdict, text));
|
|
14781
15654
|
return;
|
|
14782
15655
|
}
|