- check-sf-extension-inventory.mjs: expand parseDirectRegisteredCommands()
scan to include 7 more files (guards/inturn.js, notifications/notify.js,
permissions/index.js, ui/usage-bar.js, commands/legacy/audit.js,
commands/legacy/create-extension.js, commands/legacy/create-slash-command.js)
and filter results by BASE_RUNTIME_COMMAND_NAMES to exclude doc-string false
positives ("name" in create-slash-command.js template text)
- extension-manifest.json: remove 'clear' (subcommand of logs/notifications,
never a top-level pi.registerCommand)
- packages/pi-agent-core/src/db/sf-db.ts: fix 23 noVoidTypeReturn errors
- openDatabase: void → boolean (caller uses return value at line 5625)
- claimEscalationOverride: void → boolean (caller checks at escalation.js:243)
- resolveSelfFeedbackEntry: void → boolean (caller checks at self-feedback.js:387)
- copyWorktreeDb: void → boolean (caller checks at reconcileWorktreeDb)
- compactUokMessages: void → {before,after} (caller returns value at message-bus.js:238)
- insertSessionTurn: void → bigint|null (caller uses id at session-recorder.js:104)
- expireStaleMemories: void → number (caller uses count at auto-start.js:1047)
- deleteMemorySourceRow: void → boolean (caller returns value at memory-source-store.js:107)
- deleteMemoryEmbedding: void → boolean (caller returns value at memory-embeddings.js:328)
- updateBacklogItemStatus: remove dead return expression (callers discard value)
- removeBacklogItem: remove dead return expression (callers discard value)
- updateGateCircuitBreaker: remove dead return {total,avgMs,...} (wrong-type
code accidentally merged from getGateLatencyStats, never reachable)
- markUokMessageRead: remove dead return true/false (callers discard value)
- Auto-fix formatting and organizeImports in ~30 source files (biome --write)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
152 lines
4.2 KiB
TypeScript
152 lines
4.2 KiB
TypeScript
/**
|
|
* Pure derivation of the primary workflow action based on workspace state.
|
|
* No React dependencies — fully testable with plain imports.
|
|
*/
|
|
|
|
export interface WorkflowActionInput {
|
|
phase: string;
|
|
autoActive: boolean;
|
|
autoPaused: boolean;
|
|
onboardingLocked: boolean;
|
|
commandInFlight: string | null;
|
|
bootStatus: string;
|
|
hasMilestones: boolean;
|
|
/** When set, suppresses the action bar if the welcome screen is handling initialization. */
|
|
projectDetectionKind?: string | null;
|
|
}
|
|
|
|
export interface WorkflowAction {
|
|
label: string;
|
|
command: string;
|
|
variant: "default" | "destructive";
|
|
}
|
|
|
|
export interface WorkflowActionResult {
|
|
primary: WorkflowAction | null;
|
|
secondaries: { label: string; command: string }[];
|
|
disabled: boolean;
|
|
disabledReason?: string;
|
|
/** When true, the action represents the all-milestones-complete "New Milestone" state. */
|
|
isNewMilestone: boolean;
|
|
}
|
|
|
|
export function deriveWorkflowAction(
|
|
input: WorkflowActionInput,
|
|
): WorkflowActionResult {
|
|
const {
|
|
phase,
|
|
autoActive,
|
|
autoPaused,
|
|
onboardingLocked,
|
|
commandInFlight,
|
|
bootStatus,
|
|
hasMilestones,
|
|
projectDetectionKind,
|
|
} = input;
|
|
|
|
// When the project welcome screen is active, it handles the initialization CTA.
|
|
// Suppress the action bar to avoid duplicate/confusing buttons.
|
|
if (
|
|
projectDetectionKind &&
|
|
projectDetectionKind !== "active-sf" &&
|
|
projectDetectionKind !== "empty-sf"
|
|
) {
|
|
return {
|
|
primary: null,
|
|
secondaries: [],
|
|
disabled: true,
|
|
disabledReason: "Project setup pending",
|
|
isNewMilestone: false,
|
|
};
|
|
}
|
|
|
|
// Determine disabled state and reason
|
|
let disabled = false;
|
|
let disabledReason: string | undefined;
|
|
|
|
if (commandInFlight !== null) {
|
|
disabled = true;
|
|
disabledReason = "Command in progress";
|
|
} else if (bootStatus !== "ready") {
|
|
disabled = true;
|
|
disabledReason = "Workspace not ready";
|
|
} else if (onboardingLocked) {
|
|
disabled = true;
|
|
disabledReason = "Setup required";
|
|
}
|
|
|
|
// Derive primary action
|
|
let primary: WorkflowAction | null = null;
|
|
const secondaries: { label: string; command: string }[] = [];
|
|
let isNewMilestone = false;
|
|
|
|
if (autoActive && !autoPaused) {
|
|
primary = {
|
|
label: "Stop Autonomous",
|
|
command: "/stop",
|
|
variant: "destructive",
|
|
};
|
|
} else if (autoPaused) {
|
|
primary = {
|
|
label: "Resume Autonomous",
|
|
command: "/autonomous",
|
|
variant: "default",
|
|
};
|
|
} else {
|
|
// Auto is not active
|
|
if (phase === "complete") {
|
|
// All milestones done — surface a distinct "New Milestone" action
|
|
primary = {
|
|
label: "New Milestone",
|
|
command: "/new-milestone",
|
|
variant: "default",
|
|
};
|
|
isNewMilestone = true;
|
|
} else if (phase === "planning") {
|
|
primary = { label: "Plan", command: "/discuss", variant: "default" };
|
|
} else if (phase === "executing" || phase === "summarizing") {
|
|
primary = {
|
|
label: "Start Autonomous",
|
|
command: "/autonomous",
|
|
variant: "default",
|
|
};
|
|
} else if (phase === "pre-planning" && !hasMilestones) {
|
|
primary = {
|
|
label: "Initialize Project",
|
|
command: "/init",
|
|
variant: "default",
|
|
};
|
|
} else if (phase === "blocked") {
|
|
primary = { label: "Blocked", command: "/discuss", variant: "default" };
|
|
disabled = true;
|
|
disabledReason = "Project is blocked — check blockers";
|
|
} else if (phase === "paused") {
|
|
primary = {
|
|
label: "Resume",
|
|
command: "/autonomous",
|
|
variant: "default",
|
|
};
|
|
} else if (phase === "validating-milestone") {
|
|
primary = { label: "Validate", command: "/discuss", variant: "default" };
|
|
} else if (phase === "completing-milestone") {
|
|
primary = {
|
|
label: "Complete Milestone",
|
|
command: "/discuss",
|
|
variant: "default",
|
|
};
|
|
} else if (phase === "needs-discussion") {
|
|
primary = { label: "Discuss", command: "/discuss", variant: "default" };
|
|
} else if (phase === "replanning-slice") {
|
|
primary = { label: "Replan", command: "/discuss", variant: "default" };
|
|
} else {
|
|
primary = { label: "Continue", command: "/discuss", variant: "default" };
|
|
}
|
|
|
|
// Add "Step" secondary when auto is not active (not for new milestone — no step concept there)
|
|
if (primary.command !== "/next" && !isNewMilestone) {
|
|
secondaries.push({ label: "Step", command: "/next" });
|
|
}
|
|
}
|
|
|
|
return { primary, secondaries, disabled, disabledReason, isNewMilestone };
|
|
}
|