feat(pi-workflow): improve workflow usability and recovery

This commit is contained in:
jb
2026-08-26 00:20:24 -04:00
parent c291ca083c
commit ea74dbc6a7
23 changed files with 4422 additions and 913 deletions
+17 -9
View File
@@ -74,6 +74,7 @@ type PiWorkflowInput =
| { mode: "analysis-script"; source: string; args?: string };
concurrency?: number;
background?: boolean;
allowDirtySource?: boolean;
idempotencyKey: string;
}
| {
@@ -151,7 +152,9 @@ Feature and feature-script starts require `featureName` as a concise lowercase k
Human-facing dashboard, selector, notification, handoff, compaction, tool, and report prose uses a shared presentation label. It prefers `featureName`; otherwise it normalizes the request to one line and bounds it to 48 characters, preserving whole words where possible and adding `…` when truncated; only a missing request falls back to `runId`. When derived labels collide within an available run list, only those labels receive ` · <prefix>`, where the run-ID prefix is the shortest unique prefix with a minimum of eight characters. This label is never persisted as identity or accepted as a lookup alias. It requires no schema migration and does not change persisted paths. The complete `runId` remains canonical for snapshots, structured fields, command arguments, paths, receipts, leases, and idempotency. Copyable `/workflow continue <run-id>` and `/workflow stop <run-id>` commands retain it, and reports include it as `Run ID` metadata beneath the readable heading.
The `pi_workflow` tool calls a single internal `execute(command)` interface. `/workflow` opens the dashboard and may accept a run ID for direct inspection. `/workflow start <message>` sends the initial request through the assistant so the normal `pi_workflow` tool path owns feature naming and idempotency; in interactive TUI mode, omitting the message opens an input prompt. `/workflow continue` presents eligible pending context transitions for selection, while `/workflow continue <run-id>` continues one directly; `/workflow resume` is an alias for both forms. `/workflow stop [run-id]` aborts a non-terminal run owned by the active parent session and presents a selector when multiple owned runs are active. The misspelled `/worlflow` alias is intentionally unsupported. Continue and stop selectors require interactive TUI mode and provide direct-run-ID guidance otherwise.
The `pi_workflow` tool calls a single internal `execute(command)` interface. `/workflow` opens a drill-down TUI dashboard and may accept a run ID for direct inspection. Run selection, actions, Gates, confirmations, questions, and feedback use bounded popup overlays. Verified plans, reports, prototype evidence, and Assignment transcripts use a sanitized full-screen reader with H1-H3 outline navigation at widths of 80 columns or more and a single document pane from 60 through 79 columns. Below 48x12 it renders only a truthful minimum-size state. RPC, print, and JSON paths remain linear and do not call `ctx.ui.custom()`.
`/workflow start <message>` sends the initial request through the assistant so the normal `pi_workflow` tool path owns feature naming and idempotency; in interactive TUI mode, omitting the message opens an input overlay. `/workflow continue` presents eligible pending context transitions for selection, while `/workflow continue <run-id>` continues one directly; `/workflow resume` is an alias for both forms. Selecting a fresh-session continuation from the dashboard routes directly through the same handler and calls `newSession()` without a second command. `/workflow stop [run-id]` aborts a non-terminal run owned by the active parent session and presents a selector when multiple owned runs are active. The misspelled `/worlflow` alias is intentionally unsupported. Continue and stop selectors require interactive TUI mode and provide direct-run-ID guidance otherwise.
Continuation performs only a pending fresh-session handoff or normal-compaction fallback after the run is safely waiting; it cannot approve plans, select prototypes, or make other Decisions. Stop treats `running` and `waiting` runs as active and excludes terminal runs and runs owned only by historical parent sessions.
@@ -346,7 +349,7 @@ Initialize
- Resolve effective configuration, validate the feature name, and validate the repository without mutation.
- Load, validate, and pin Agent Profiles from the source checkout.
- Verify that eligible discovery, design, implementation, verification, and independent review profiles exist before mutating the repository.
- For feature mode, verify that the configured worktree root is ignored by Git and that the source checkout is clean.
- For feature mode, verify that the configured worktree root is ignored by Git. Capture at most 20 sorted path/status records plus an omitted count for staged, unstaged, or untracked source changes. Unmerged paths always block creation. Ordinary changes require explicit TUI confirmation or `allowDirtySource: true` from a noninteractive caller.
- Persist a run snapshot with workspace state `creating` before invoking Git.
- Create a distinct worktree transactionally, then persist workspace state `ready`.
- On creation failure, roll back what can be removed safely, persist the failure, and leave enough intent for startup reconciliation.
@@ -405,7 +408,8 @@ Mutating Assignments run in the Workflow Run's worktree.
- Mutating child sessions do not receive `bash` or other process-execution tools. Assignment-scoped file operations reject symlink traversal and require canonical containment beneath both the worktree root and the Assignment's owned path roots.
- Plan-approved verification and generation commands run separately through a host executor using fixed executable/argument arrays, never agent-authored shell strings. The only accepted wrapper form is `env PI_OFFLINE=1 <executable> ...`; the executor unwraps it, sets that single variable, and applies the normal executable safety checks.
- The host executor sets `PYTHONDONTWRITEBYTECODE=1` so Python-backed checks do not create out-of-scope `__pycache__` artifacts.
- Every host command has a three-minute execution timeout and reports exit code `124` when terminated, preventing interactive or stalled commands from blocking a run indefinitely.
- Every host command has a three-minute execution timeout and reports exit code `124` when terminated. Internal Git probes and worktree commands have a 30-second timeout. Process-tree termination, including Windows `taskkill`, has its own deadline and reports exit code `125` when cleanup cannot be completed.
- Every child Assignment has a five-minute execution deadline. Timeout aborts the child signal and persists the Assignment as `interrupted` with `assignment-timeout` evidence rather than reporting success.
- The engine records branch, HEAD, worktree identity, and Git diff state before and after every mutating Assignment and host command.
- Out-of-scope changes, branch movement, commits, or worktree metadata changes invalidate the Assignment and open a Recovery Gate before more mutation starts.
- The scheduler reserves capacity synchronously before launching children.
@@ -480,7 +484,7 @@ feature/<feature-name>
`<feature-name>` comes from the validated `featureName` start input and describes the feature rather than the workflow mechanism. Any existing branch with that name rejects a new start. If persisted state associates the branch with a Workflow Run, the error returns its run ID and directs the caller to continue it through `inspect` or `decide`; otherwise it directs the caller to choose another descriptive name. The extension never adds a numeric or run-ID suffix silently.
The configured worktree root must already be ignored by Git. The extension verifies this with Git before persisting creation intent and fails with setup guidance rather than modifying `.gitignore` automatically. A dirty source checkout blocks creation so uncommitted work is never silently omitted.
The configured worktree root must already be ignored by Git. The extension verifies this with Git before persisting creation intent and fails with setup guidance rather than modifying `.gitignore` automatically. A worktree is created from the pinned source `HEAD`; source-only staged, unstaged, and untracked changes are not copied. The engine persists bounded path/status evidence and whether omission was explicitly allowed. TUI callers must confirm a warning that states those changes are omitted. Noninteractive callers must set `allowDirtySource: true`. Conflicts and unmerged paths always block creation.
The engine records an ownership lease for the run and refuses to adopt or share another run's worktree. The completed worktree remains available for inspection and manual integration. Removal is explicit and never part of normal completion.
@@ -512,7 +516,7 @@ Writes are atomic and mode `0600`. Serialization, transcript, source, argument,
The snapshot contains Decisions and prior Assignment outcomes, so a separate event store is unnecessary. Every `start`, `decide`, and `abort` command requires an idempotency key. The launch key and initial result are persisted before workspace creation. State-changing commands use revisions and idempotency keys to reject stale or duplicate transitions. Repeating a completed key returns its recorded result without another transition.
An active run owns a persisted lease containing process identity, active parent-session identity, heartbeat time, and worktree identity. The run retains every parent session ID attached through a handoff so `/workflow` can discover it from either session, but only the active ID may mutate the run. The narrow exception is an explicit Recovery retry from a retained parent ID after the recorded lease process has died; that retry reclaims the stale lease before applying the Decision. Startup and session recovery reconcile stale leases, child processes, workspace-creation intent, Git identity, context handoffs, compaction intents, and assignments left `running`. An idle run that already has an open Recovery Gate is left attached to its current parent so unrelated Pi sessions cannot steal ownership during startup.
An active run owns a persisted lease containing process identity, active parent-session identity, heartbeat time, and worktree identity. The run retains every parent session ID attached through a handoff so `/workflow` can discover it from either session, but only the active ID may mutate the run. Startup or explicit Recovery may adopt a continuable run only when its recorded owner process is dead or its lease is absent. Adoption atomically rotates both `lease.activeParentSessionId` and `implementationContext.activeParentSessionId`, appends the adopting session to `parentSessionIds`, and preserves revision fencing. A live process or live Assignment blocks adoption even when its heartbeat is old. Startup and session recovery reconcile child processes, workspace-creation intent, Git identity, context handoffs, compaction intents, and assignments left `running`.
Fresh-session handoff intent is durable before `newSession()`. A `session_shutdown` with reason `new` bypasses ordinary interruption recovery only when its source session matches the pending intent; that handler atomically claims the event's `targetSessionFile` and marks the intent `claimed-target-awaiting-withSession`. The claimed target's immediately following `session_start` is exempt from recovery only when its reason is `new` and `previousSessionFile` matches the source. The replacement `withSession` callback must then match the claimed target before it atomically consumes the intent, rotates the active parent-session ID, records both session IDs, and resumes delivery. Any later `session_start` caused by `startup`, `reload`, `resume`, or `fork`, or a bounded claim expiry before consumption, follows normal recovery and leaves implementation blocked.
@@ -553,7 +557,9 @@ Children cannot access recursive workflow or subagent tools or `ask_user`. `pi_w
- `store.ts`: atomic revisioned snapshots and bounded artifacts.
- `workspace.ts`: worktree creation, ownership conflict checks, and lifecycle.
- `context-transition.ts`: plan handoffs, new-session replacement, smart/default compaction callbacks, and session attachment.
- `dashboard.ts`: run inspection, continuation handoff, and prototype/report links.
- `dashboard.ts`: drill-down run actions, Gate orchestration, continuation handoff, and artifact selection.
- `tui.ts`: reusable popup inputs/selections and the full-screen verified-document reader.
- `progress.ts`: newest-attached-run projection and the bounded editor widget.
- `agents/*.md`: bundled default profiles.
Pure transition and eligibility logic stay inside the engine and profile modules rather than behind speculative adapter interfaces. Tests may inject filesystem, executor, decision, and script-runtime adapters at the composition root.
@@ -592,7 +598,7 @@ Implement the command interface, legal transitions, closed Gate Decisions, atomi
- `start`, `decide`, and `abort` require idempotency keys; launch intent is persisted before worktree creation, and a repeated completed key returns its prior result without a second transition.
- Stale revisions are rejected for `decide` and `abort`.
- Using the injected scripted Assignment executor, Questions and Plan Approval pause and resume across process restarts with the accepted UI classification intact and no dependency on live Pi child execution.
- Startup reconciliation detects stale leases and transitions abandoned `running` Assignments to `interrupted` exactly once.
- Startup reconciliation detects absent or dead ownership and transitions abandoned `running` Assignments to `interrupted` exactly once.
- Potentially mutating interrupted Assignments require an explicit Recovery Decision; completed Decisions and Assignments are not replayed.
- Feature names are validated before launch persistence; worktree branches use exactly `feature/<feature-name>`, and every existing branch collision rejects new start with recorded-run `inspect`/`decide` guidance or descriptive-name rename guidance, never a silent suffix.
- Workspace creation intent and lease are persisted before Git mutation; creation succeeds transactionally or leaves a reconciled failure that startup can recover.
@@ -618,7 +624,7 @@ Extend the Phase 2 worktree scaffold with the Implementation Context Gate, `/wor
#### Phase 4 Success Criteria
- A dirty source state or unignored worktree root blocks creation before Git mutation.
- An unignored worktree root or unmerged source state blocks creation before Git mutation. Ordinary source changes require bounded evidence and explicit authorization, then create from the persisted `HEAD` without copying those changes.
- Workspace creation intent is persisted first; creation failure rolls back safely or remains reconcilable after restart.
- Every feature run receives a distinct leased worktree and worktrees are retained until explicitly removed.
- A versioned `plan.md` is persisted before the Implementation Context Gate and revised with an accepted prototype when applicable.
@@ -655,7 +661,9 @@ Add the dashboard integration around the Phase 4 `/workflow continue` command, p
#### Phase 6 Success Criteria
- Dashboard state is derived from persisted snapshots and active runs, prior supported snapshot versions remain readable, and the existing `/workflow continue` command is presented only for a valid pending fresh-session or normal-compaction intent.
- Dashboard state is derived from persisted snapshots and active runs, prior supported snapshot versions remain readable, and continuation is presented only for a valid pending fresh-session or normal-compaction intent. Selecting a fresh-session continuation enters `newSession()` immediately.
- Bounded TUI interactions use responsive popup overlays. Verified documents use the dedicated sanitized reader with H1-H3 navigation, cell-width-safe viewports, and the locked 80-column, 60-column, and 48x12 behavior. RPC, print, and JSON remain linear.
- The widget above Pi's editor projects only the newest attached active run and clears after terminal persistence or session shutdown.
- Stable delivery keys, durable custom parent-session message receipts, and outbox reconciliation ensure each background Gate or terminal result is delivered once across reload and recovery.
- Focused tests, typecheck, full test suite, and manual Pi TUI validation of Gates, dashboard navigation, background delivery, and prototype handoff pass.
- Exposure is performed only through `config/pi-links.json`; unmanaged extension links are rejected and link reconciliation check passes before sync.