@dieulc/workflow 0.7.0 → 0.8.0
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/README.md +110 -28
- package/commit-policy.ts +43 -0
- package/index.ts +2725 -1100
- package/model-default.ts +535 -0
- package/package.json +8 -2
- package/picker.ts +246 -0
- package/profiles.ts +73 -0
- package/questionnaire-layout.ts +340 -0
- package/questionnaire-view.ts +909 -0
- package/review-runtime.ts +260 -74
- package/review-ui.ts +27 -4
- package/review.ts +516 -16
- package/tab-editor.ts +147 -0
- package/tab-policy.ts +42 -0
- package/utils.ts +372 -69
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ Plan↔Build mode with an independent **Review Mode** reviewer, questionnaire, s
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Plan / Build duality** — `Plan` tool gating (`edit`/`write` blocked in plan), `Build` restores full tools;
|
|
7
|
+
- **Plan / Build duality** — `Plan` tool gating (`edit`/`write` blocked in plan), `Build` restores full tools; **Tab** completes the highlighted suggestion while a list is open and otherwise cycles Plan → Build → Default (`Ctrl+Alt+P` always cycles), `--workflow-plan` flag
|
|
8
8
|
- **Review Mode** — an independent reviewer agent that runs **after** Plan Mode finishes (sequential — never in parallel), in its own in-process session. It audits the plan, rewrites it, streams its work into the main transcript, and hands the result to Plannotator. Off by default; see [Review Mode](#review-mode) below.
|
|
9
|
-
- **Questionnaire** — 1–4 questions with headers/options, dedup freeform aliases, inline `↳ Your answer:` editor
|
|
9
|
+
- **Questionnaire** — 1–4 questions with headers/options, dedup freeform aliases, and an inline `↳ Your answer:` editor. Rendered as a bounded bottom-sheet overlay: a short question and the choices stay pinned, while a long question or the optional `context` body (e.g. a proposed commit message) scrolls with the wheel, `PgUp`/`PgDn` or `Ctrl+U`/`Ctrl+D`. The freeform editor takes the pane while typing.
|
|
10
10
|
- **Explore (subagents)** — parallel `pi --mode json` subagents (`max 8`, concurrency 4), `session_shutdown` kill, `activeSubagents`
|
|
11
11
|
- **Todos** — plan-faithful todo list (global step refs + statuses + phase groups) that persists across Plan/Build/Default modes. `workflow_todo` (`list/update/done/pending/add/toggle/sync/clear`) with industry-style wholesale `update` (send the COMPLETE list; omitted items are removed) plus idempotent `done`/`pending`, lenient `[DONE:<ref>]` markers, throttled Claude-Code-style reminders, a tool-result footer, and a stale ⚠ notice — but **no forced turns and no heuristic auto-completion** (reference: Claude Code / OpenCode / Codex practice).
|
|
12
12
|
- **Rewind** — shadow bare-repo checkpoints per cwd (`agent/checkpoints/checkpoint-<slug>.git`), `isDirty` + `createSafetySnapshot` + `restoreCode`
|
|
@@ -35,23 +35,25 @@ Manage it with `pi list`, `pi update --extensions`, `pi remove npm:@dieulc/workf
|
|
|
35
35
|
| `/rewind` | Pick checkpoint to restore |
|
|
36
36
|
| `/todos` | Show todo list |
|
|
37
37
|
| `/btw` | Queue note for next turn |
|
|
38
|
-
| `/role` | Config screen: one model + thinking per role (planner/explorer/builder/reviewer tabs, custom roles included). CLI: `list`, `set <role> <provider/model> [thinking]`, `use <role\|auto>`, `add/remove/reset` |
|
|
39
|
-
| `/profile` | Switch or manage model profiles (named role-model combinations). Picker: activate / edit / rename / delete / New profile… / Default
|
|
38
|
+
| `/role` | Config screen: one model + thinking per role (planner/explorer/builder/reviewer tabs, custom roles included; type to filter models by `provider/id` or display name). CLI: `list`, `set <role> <provider/model> [thinking]`, `use <role\|auto>`, `add/remove/reset` |
|
|
39
|
+
| `/profile` | Switch or manage model profiles (named role-model combinations). Picker: activate / edit / rename / delete / New profile… / Default… (fuzzy type-to-filter; completed actions close the picker). CLI: `list`, `show <name>`, `use <name\|none>`, `create <name>`, `edit <name>`, `rename <old> <new>`, `delete <name>`, `default [name\|none] [global\|project]` |
|
|
40
40
|
| `/review-mode [on\|off] [global\|project]` | Enable/disable Review Mode (default `off`). `status` prints the effective config, naming the settings file that supplied the timeout |
|
|
41
41
|
| `/review-mode timeout <seconds\|unlimited> [global\|project]` | Set or show the per-pass time cap (`unlimited`/`0` = no cap). Writes to global settings by default |
|
|
42
42
|
| `/review-prompt [global]` | Edit the reviewer's guidance (defaults to "align with the existing framework and industry best practice") |
|
|
43
43
|
| `/review-status` | Show review phase, model, plan hash, findings and any error |
|
|
44
|
-
| `/review` | Run one review round
|
|
44
|
+
| `/review` | Run a one-shot review round now — works with Review Mode on or off (mode stays OFF) |
|
|
45
45
|
| `/commit` | Commit staged changes with an AI-summarized message (`-a`: stage tracked changes first). Confirmation-gated; the confirmation arms the commit exemption for that run |
|
|
46
46
|
| `/commit-policy [never\|ask\|allow] [global\|project]` | View or set the commit policy (default `never`: the agent cannot `git add`/`commit`/`push` — you commit, or use `/commit`) |
|
|
47
47
|
| `/write-policy [allow\|ask\|never] [global\|project]` | View or set the write permission for Default mode (default `allow`: the agent writes freely; `ask` prompts per write; `never` blocks writes — see [Write permission](#write-permission)) |
|
|
48
48
|
|
|
49
|
+
Mode commands (`/plan`, `/build`, `/default`) and the `Tab` / `Ctrl+Alt+P` shortcuts are refused while a review round or a paused review owns the turn (Tab still completes a visible suggestion list); see [Controlling a running review](#controlling-a-running-review).
|
|
50
|
+
|
|
49
51
|
## Model Profiles
|
|
50
52
|
|
|
51
53
|
A profile is a **named combination of role model settings** — one `{provider, id, thinking}` each for planner, explorer, builder and reviewer. Profiles layer on top of Model Roles: role identity (tools, system prompts, custom roles) stays global in `roles.json`; a profile only overrides models while it is active.
|
|
52
54
|
|
|
53
55
|
```text
|
|
54
|
-
/profile # picker: activate / edit / rename / delete / New profile… / Default…
|
|
56
|
+
/profile # picker: activate / edit / rename / delete / New profile… / Default… (type to filter)
|
|
55
57
|
/profile create budget # name it, then pick model + thinking per role (same flow as /role)
|
|
56
58
|
/profile use budget # activate for THIS session (none = back to the roles.json flow)
|
|
57
59
|
/profile edit budget # re-open the tabbed model editor
|
|
@@ -69,6 +71,8 @@ A profile is a **named combination of role model settings** — one `{provider,
|
|
|
69
71
|
- **`/role` keeps editing the base roles.** With a profile active, the `/role` picker shows a notice and `/role list` prints the profile's overrides; `/profile edit <name>` is the way to change what actually runs.
|
|
70
72
|
- **Fallback**: no profiles (or none active) → the roles.json flow, identical to before profiles existed. A profile referencing a model missing from the registry is warned about at save time and refused at switch time ("staying on current model"), same as `/role`.
|
|
71
73
|
|
|
74
|
+
**Picker keys**: `/profile` and `/role` are fuzzy-filterable — start typing to narrow models by `provider/id` or display name (e.g. `flash`, `open/beam`, `muse spark`) and profiles by name, marker, description or one of their models. `↑`/`↓` navigate, `Enter` selects/commits (inert when nothing matches), `Tab`/`←`/`→` switch role tabs, and `Ctrl+O` toggles scoped ↔ all while keeping the filter. `Esc` (or `Ctrl+C`) clears a non-empty filter first; a second press backs out or cancels. A completed `/profile` action closes the overlay; Esc-cancel, a failed save and the action menu's Back keep the profile list open, with its filter remembered.
|
|
75
|
+
|
|
72
76
|
**Why**: mix models from different providers in one workflow (frontier planner + cheap explorer), keep one profile per provider to control budget per provider, run concurrent sessions on different providers to work around per-provider concurrency limits, and exploit cheaper starter plans across several providers.
|
|
73
77
|
|
|
74
78
|
Profile config lives in `~/.pi/agent/profiles.json` v1 (user) with optional per-project override at `<cwd>/.pi/profiles.json` (per-profile replace, project wins). Names are case-insensitively unique, ≤ 40 chars, no whitespace (keep them CLI-addressable), and `none`/`auto`/`off` are reserved (`off` is a `/profile use` deactivate keyword, so a profile can never be named it). A missing file is the normal empty state; a corrupt or wrong-version file is never rewritten (warning + in-memory empty config).
|
|
@@ -77,20 +81,23 @@ Profile config lives in `~/.pi/agent/profiles.json` v1 (user) with optional per-
|
|
|
77
81
|
|
|
78
82
|
## Review Mode
|
|
79
83
|
|
|
80
|
-
An independent second agent supervises the plan
|
|
84
|
+
An independent second agent supervises the plan without freezing your session. The handoff renders **immediately** with the author's plan (marked "review pending") while the reviewer works **in the background**; the plan file is updated when the review lands.
|
|
81
85
|
|
|
82
86
|
```text
|
|
83
|
-
Plan Mode (main session) ──plan file──▶
|
|
84
|
-
│
|
|
87
|
+
Plan Mode (main session) ──plan file──▶ handoff renders at once (author's plan)
|
|
88
|
+
│ ╰─▶ Review Mode (child session, background)
|
|
89
|
+
│ │ rewrites the plan file
|
|
90
|
+
│ ▼
|
|
91
|
+
│ "review landed" notice
|
|
85
92
|
▼
|
|
86
|
-
Plannotator webview ◀──
|
|
87
|
-
│ approve ──▶ Build Mode
|
|
93
|
+
Plannotator webview ◀── plan file + `## Review changes`
|
|
94
|
+
│ approve ──▶ Build Mode (review activity suspends, session stays warm)
|
|
88
95
|
└─ feedback ──▶ Plan Mode revises ──▶ Review re-reviews
|
|
89
96
|
```
|
|
90
97
|
|
|
91
|
-
- **Approve** in Plannotator → Build mode;
|
|
98
|
+
- **Approve** in Plannotator → Build mode; review activity suspends but the reviewer session stays warm for the next round.
|
|
92
99
|
- **Feedback** → Plan revises, Review re-reviews the revision, Plannotator reopens.
|
|
93
|
-
- **Any failure** (missing model, unauth'd provider, abort, error) → the author's plan is handed over unchanged with a warning. Review Mode never blocks the plan. A pass whose run ends without a `review_submit_plan` submission fails *immediately* (the settle race), so a silent reviewer cannot hang the
|
|
100
|
+
- **Any failure** (missing model, unauth'd provider, abort, error) → the author's plan is handed over unchanged with a warning. Review Mode never blocks the plan. A pass whose run ends without a `review_submit_plan` submission fails *immediately* (the settle race), so a silent reviewer cannot hang the round. `timeoutMs` defaults to **5 minutes**; set `0` to opt out into unlimited, and a pass that outlives the cap is aborted — though a plan submitted before the deadline is always kept.
|
|
94
101
|
|
|
95
102
|
### Enabling
|
|
96
103
|
|
|
@@ -98,8 +105,9 @@ Plan Mode (main session) ──plan file──▶ Review Mode (child session, se
|
|
|
98
105
|
/review-mode on # global (~/.pi/agent/settings.json)
|
|
99
106
|
/review-mode on project # this repo only (<cwd>/.pi/settings.json)
|
|
100
107
|
/review-mode status
|
|
101
|
-
/review-mode timeout unlimited # remove the per-pass cap (global)
|
|
102
108
|
/review-mode timeout 600 project # 10-minute cap for this repo only
|
|
109
|
+
/review-mode timeout unlimited # remove the cap entirely (not recommended)
|
|
110
|
+
/review-mode timeout # show the current value and where it came from
|
|
103
111
|
```
|
|
104
112
|
|
|
105
113
|
Turning the toggle off stops the reviewer and restores the single-agent plan/build flow exactly as it was. Nothing in Review Mode runs while it is disabled.
|
|
@@ -116,6 +124,8 @@ Review Mode runs in its own session with its own model, taken from the **`review
|
|
|
116
124
|
|
|
117
125
|
If the reviewer role has no model, Review Mode falls back to the planner model and warns rather than silently doing nothing.
|
|
118
126
|
|
|
127
|
+
The model is **snapshotted when a round starts**: a `/role` or `/profile` switch mid-round takes effect **next round**, never mid-pass. The mode band, the status widget and `/review-status` name the snapshotted model — the one actually auditing.
|
|
128
|
+
|
|
119
129
|
Roles load from `~/.pi/agent/roles.json` (with a `<cwd>/.pi/roles.json` project override) **at session start and again whenever either file changes on disk**, so a `/role` change made in another window is picked up without `/reload`. A missing, unreadable, or legacy `roles.json` is never overwritten: the built-in defaults are used in memory and a warning naming the file is shown, leaving it intact. `/review-mode status`, the mode band and the reviewer run all read through the same loader.
|
|
120
130
|
|
|
121
131
|
### Reviewer guidance
|
|
@@ -162,7 +172,7 @@ The child session is created with an explicit tool allowlist: the read-only buil
|
|
|
162
172
|
1. **Review + rewrite** — audits the plan against the repository and the guidance, records findings, submits the revised plan.
|
|
163
173
|
2. **Self-verify** — re-reads the original and the rewrite, confirms every accepted finding was actually addressed, and reports anything that was silently dropped. This pass exists because a rewrite can lose a requirement the author cared about.
|
|
164
174
|
|
|
165
|
-
Pass 2 is skipped when `verify` is false or when pass 1
|
|
175
|
+
Pass 2 is skipped when `verify` is false, or when pass 1 left nothing at severity ≥ 7 with an `accepted` disposition — clean and low-severity reviews resubmit pass 1's plan directly. When pass 2 runs it checks a **unified diff** of the rewrite (not two full plans), so the self-check costs roughly a tenth of the tokens.
|
|
166
176
|
|
|
167
177
|
### Every change is visible
|
|
168
178
|
|
|
@@ -191,28 +201,50 @@ reviewer `opencode-go/muse-spark-1.3-contributor` · verdict **revise**
|
|
|
191
201
|
|
|
192
202
|
Because the two agents run sequentially — Plan Mode finishes before the reviewer starts — their transcripts can never interleave, and there is no overlay or second pane to navigate. Everything shows up in Pi's normal transcript.
|
|
193
203
|
|
|
194
|
-
Mode changes are reported by Pi's **footer status** (`⏸ plan` / `▶ build`, plus todo progress): switching with **Tab** or `Ctrl+Alt+P` is silent and adds nothing to the transcript, while `/plan`, `/build` and `/default` print a single confirmation line.
|
|
204
|
+
Mode changes are reported by Pi's **footer status** (`⏸ plan` / `▶ build`, plus todo progress): switching with **Tab** or `Ctrl+Alt+P` is silent and adds nothing to the transcript, while `/plan`, `/build` and `/default` print a single confirmation line.
|
|
205
|
+
|
|
206
|
+
**Tab precedence.** While the input's suggestion list is open (commands, `@files`, `#documents`), **Tab** applies the highlighted item and leaves the mode alone. With no list open, the workflow's `tab` shortcut cycles Plan → Build → Default. `Ctrl+Alt+P` always cycles. This matches Pi Desktop; it requires `tui.input.tab` to stay bound (the default is `tab`), because a disabled binding leaves nothing for the editor to accept. The one exception is a live review: mode switching is refused with a notice (see [Controlling a running review](#controlling-a-running-review)). Only the review phase adds bands, and the reviewer's stream is rendered in the review color:
|
|
195
207
|
|
|
196
208
|
| Entry | Color | Meaning |
|
|
197
209
|
| --- | --- | --- |
|
|
198
|
-
| `▌ REVIEW MODE` | accent | A review round is running (round + model shown) |
|
|
210
|
+
| `▌ REVIEW MODE` | accent | A review round is running (round + model shown). A resumed round reads `resumed · round N · model` instead of `sequential audit` |
|
|
211
|
+
| `▌ REVIEW PAUSED` | warning | The round stopped for a pause — `/review-resume` continues it, `/review-abort` discards it |
|
|
199
212
|
| action headers | muted | One line per reviewer tool call with its key argument (`▸ read src/app.ts`, `▸ review_bash: git log -5`). Assistant prose and thinking are not streamed, so the transcript stays close to Plan Mode's; status lines are muted |
|
|
200
213
|
| `▌ REVIEW DONE` | success / warning | Round finished, with the verdict or the fallback reason |
|
|
201
214
|
| `▌ REVIEW SUMMARY` | success / warning | The submitted plan's verdict, plan path and findings (severity-colored) |
|
|
202
215
|
|
|
203
|
-
These are **display-only custom entries** (`pi.appendEntry` + `pi.registerEntryRenderer`): they are durable in the transcript and survive `/reload`, but they never enter the LLM context, so Plan Mode and Build Mode cannot see the reviewer's work. Consecutive identical bands are suppressed (the gate is seeded from the newest band on reload), so cycling modes or re-running a review cannot grow the transcript. Long reviews are coalesced (flushed roughly every 400 ms) and capped, with a one-line notice pointing at `/review-status` when output is truncated.
|
|
216
|
+
These are **display-only custom entries** (`pi.appendEntry` + `pi.registerEntryRenderer`): they are durable in the transcript and survive `/reload`, but they never enter the LLM context, so Plan Mode and Build Mode cannot see the reviewer's work. Consecutive identical bands are suppressed (the gate is seeded from the newest band on reload, and a resumed/paused band is never treated as identical to the band it follows), so cycling modes or re-running a review cannot grow the transcript. Long reviews are coalesced (flushed roughly every 400 ms, and always flushed before a band) and capped, with a one-line notice pointing at `/review-status` when output is truncated.
|
|
217
|
+
|
|
218
|
+
**Run narration.** The run-start line is emitted by the harness at the moment it sends the prompt, so a resume cannot masquerade as a fresh start: `reviewer started` (a reviewer session was created), `reviewer resumed` (a paused round continues on the warm session), `reviewer continuing` (a later round reuses the session), `reviewer restarting — new reviewer session` (a reviewer-model switch forced a reboot) and `reviewer verifying — pass 2`. A run ends with `reviewer run ended`.
|
|
204
219
|
|
|
205
220
|
#### Where your next message goes
|
|
206
221
|
|
|
207
222
|
Input follows the **active mode**, decided automatically — there is nothing to focus or toggle:
|
|
208
223
|
|
|
209
224
|
- **Plan Mode** → the message goes to the plan agent.
|
|
210
|
-
- **Review Mode** (a review round is in flight) →
|
|
225
|
+
- **Review Mode** (a review round is in flight) → plain chat **stays with the plan agent**. Use `/review say <message>` to steer the in-flight reviewer explicitly (echoed as `you → reviewer`); `/btw <note>` always queues a note for the next turn, even mid-review.
|
|
211
226
|
- **Neither** (including while the plan sits in Plannotator) → the message goes to the plan agent, so you can still refine the plan.
|
|
212
227
|
|
|
213
|
-
Delivery to the reviewer is best-effort: if it fails, the message
|
|
228
|
+
Delivery to the reviewer is best-effort: if it fails, the message stays in the plan-agent chat rather than being swallowed.
|
|
229
|
+
|
|
230
|
+
**Status line.** While a review is running, the footer status carries a review segment alongside the mode (`⏸ plan … · 🔍 review · pass 2/2 · 3 findings · ● verifying`), the widget above the editor shows the same live phase, and the spinner row reads `🔍 Reviewing plan…`. Pausing switches both to the paused form (`🔍 review paused — /review-resume to continue`); resuming returns them to the running form. The line is **live-only**: it disappears as soon as the round lands, fails or is aborted, and on a mode switch — Plan/Build never keeps a stale `🔍 review … aborted` line. `/review-status` still reports the last round's result. While the review owns the turn, the footer also appends `🔒 modes locked`.
|
|
231
|
+
|
|
232
|
+
### Controlling a running review
|
|
233
|
+
|
|
234
|
+
The handoff **waits for the reviewer**: no browser prompt or inline choice renders until the gated round lands, fails, or you pause/abort it. The reviewed plan (the reviewer writes the plan file mid-round) becomes the handed-off text, with the todo steps re-synced to match. A broken reviewer never blocks the plan — the author's draft is handed over with a warning instead.
|
|
214
235
|
|
|
215
|
-
**
|
|
236
|
+
A handoff opens **only when the current Plan run writes or edits a `.pi/plans/` file**. Switching Build → Plan keeps the previous plan and todos as reference, but they cannot be handed off again until the new run writes a plan. A fresh prompt also supersedes any pending browser/inline handoff prompt, and a stale browser approval/denial from a previous plan is ignored.
|
|
237
|
+
|
|
238
|
+
- **Mode switching is locked** from the moment a review round starts (including the cold boot) until it lands, fails, or is aborted, and for the whole paused state. `Tab` (when no suggestion list is open), `Ctrl+Alt+P`, `/plan`, `/build`, `/default`, browser approve and external plan-mode changes are refused with one notice, and the footer shows `🔒 modes locked`. `/review-abort` and `/review-mode off` end the lock; the post-review handoff prompt is intentionally unlocked because the independent reviewer has finished.
|
|
239
|
+
- **ESC** pauses the round **everywhere it is active** — while the gated handoff waits, while a manual round runs, with no prompt on screen at all. One long-lived terminal listener maps it to `/review-pause` semantics (warm session kept) and stays registered through the paused window: a second ESC while a gated review is paused is swallowed with a hint instead of aborting the parked plan turn (that abort used to poison the turn's signal and stall every `/review-resume`). With no review owning the turn it passes ESC through, so the editor, questionnaire and handoff selector keep it. Headless/RPC hosts have no listener and keep `/review-abort` as their stop path.
|
|
240
|
+
- `/review-pause` is the same pause as a command. Pausing mid-handoff does **not** render the handoff — the gate stays open until you resume or abort.
|
|
241
|
+
- `/review-resume` continues the paused round on the warm session (or starts a fresh round if the plan changed since the pause), and settles the waiting handoff with the result. A resumed round is **running** again — the notice/footer return to the running form, and a second ESC can pause it. It runs on a fresh controller and deliberately does **not** inherit the parked turn's signal, so an interrupted parent turn cannot instantly re-abort every resume; ESC, `/review-pause` and `/review-abort` are its stop paths. The transcript says so explicitly (`▌ REVIEW MODE resumed · round N · model` followed by `reviewer resumed`). A rewrite the reviewer wrote to the plan file before the pause is **not** mistaken for an edit of yours: resume compares the file against a hash snapshot taken at pause time (refreshed when the round settles), so only a change made after the pause starts a fresh round. Its notice names the real outcome (rewritten, unchanged, paused, aborted, skipped or fallback).
|
|
242
|
+
- `/review-abort` stops the round — or discards a paused one — and, when a handoff is waiting, **hands off the author's plan immediately** so the plan is always delivered. The aborted revision is not auto-reviewed again until the plan changes. It also releases the mode-switch lock immediately.
|
|
243
|
+
- A host interrupt (**Ctrl+C**) while a gated review is paused resolves the gate, aborts the reviewer, clears the pause and hands off the author's plan, so the turn can never hang on a parked review.
|
|
244
|
+
- `/review say <message>` talks to the in-flight reviewer.
|
|
245
|
+
- `/review-status` shows phase, round, the snapshotted model, findings, any paused state, and the mode-switch lock with its allowed exits.
|
|
246
|
+
|
|
247
|
+
Pausing is in-memory only: a reload starts fresh.
|
|
216
248
|
|
|
217
249
|
### Isolation
|
|
218
250
|
|
|
@@ -221,7 +253,7 @@ The reviewer is an in-process child session created with `createAgentSession`, a
|
|
|
221
253
|
- `DefaultResourceLoader({ noExtensions: true })` — the child cannot load this (or any other) extension, so Review Mode cannot recurse.
|
|
222
254
|
- `SessionManager.inMemory(...)` — no session file is written; the reviewer never touches the parent's history.
|
|
223
255
|
- Its own `ModelRuntime` — no contention with the parent's model state.
|
|
224
|
-
- Plan Mode's conversation is passed as **pruned** prompt context (oversized tool results elided, workflow-injected markers dropped, nothing after the plan write), not by sharing the session.
|
|
256
|
+
- Plan Mode's conversation is passed as **pruned** prompt context (oversized tool results elided, workflow-injected markers dropped, nothing after the plan write), not by sharing the session. A **Planner-context manifest** — a file inventory with size/mtime/hash tokens plus recent `explore` summaries — rides along so the reviewer verifies instead of re-exploring; the transcript block itself is dieted (15K chars) since anything truncated can be re-read with `read`/`grep`.
|
|
225
257
|
|
|
226
258
|
An environment marker (`PI_WORKFLOW_REVIEW_CHILD`) is set only for the duration of session creation and restored immediately — the child runs in the same process, so leaking it would affect a later `/reload`.
|
|
227
259
|
|
|
@@ -302,6 +334,8 @@ The status line always shows the active policy (`▶ build 2/5 · commits:never`
|
|
|
302
334
|
|
|
303
335
|
The confirmation step is required: with no positive answer no authorization exists and the policy keeps blocking. `-a` staging happens extension-side before the flow, outside the tool gate.
|
|
304
336
|
|
|
337
|
+
The proposed message is passed in the questionnaire's scrollable `context`, not in `question`. The bottom-sheet keeps the short decision line and the ✅ Commit / ✏️ Edit / ❌ Cancel rows pinned while the message scrolls (wheel, `PgUp`/`PgDn`, `Ctrl+U`/`Ctrl+D`), so a long, multi-paragraph commit message can never hide the choices.
|
|
338
|
+
|
|
305
339
|
```text
|
|
306
340
|
/commit-policy # status: mode, which scope supplied it, both settings paths
|
|
307
341
|
/commit-policy never # set globally (~/.pi/agent/settings.json)
|
|
@@ -389,7 +423,52 @@ The default profile lives under `workflow.defaultProfile` (global/project preced
|
|
|
389
423
|
}
|
|
390
424
|
```
|
|
391
425
|
|
|
392
|
-
`/profile default <name|none> [global|project]` writes this key read-modify-write, preserving every other settings key. Sessions that never picked explicitly (fresh or resumed) re-read it at every start, so changing the default applies to them; an explicit `/profile use` in a session pins that session until changed.
|
|
426
|
+
`/profile default <name|none> [global|project]` writes this key read-modify-write, preserving every other settings key. **Activating a profile writes it too**: `/profile use`, the picker's activate entry, "save & activate" in the editor, and `/profile use none` (which clears it) all keep the key pointing at the profile chosen last, so the newest profile selection is the one a new session starts on. Renaming the default profile rewrites the key; deleting it clears the key instead of leaving a dangling name that every new session would warn about. Writes go to the project settings file when it already defines the key, else the global one. Sessions that never picked explicitly (fresh or resumed) re-read it at every start, so changing the default applies to them; an explicit `/profile use` in a session pins that session until changed.
|
|
427
|
+
|
|
428
|
+
### Default model persistence
|
|
429
|
+
|
|
430
|
+
The model you select is remembered as the default for the **next** session. This covers every way of selecting one:
|
|
431
|
+
|
|
432
|
+
- the model selector in terminal Pi (plain Enter, not only `Ctrl+S`),
|
|
433
|
+
- model cycling (`Ctrl+P`, `Ctrl+Alt+Up/Down`),
|
|
434
|
+
- the model picker in Pi Desktop,
|
|
435
|
+
- an explicit `/profile use` (its `defaultModel` tab),
|
|
436
|
+
- an explicit `/role set`, `/role use`, or a `/role` picker save.
|
|
437
|
+
|
|
438
|
+
Entering Plan/Build mode does **not** change the default: the planner/builder model belongs to that mode, so the next session still starts on your Default-mode model. Resumed sessions keep their recorded model, and Workflow's own `explore`/review subagents keep their role model.
|
|
439
|
+
|
|
440
|
+
**Which value wins.** `workflow.modelDefault` is the single authority for the Default-mode model:
|
|
441
|
+
|
|
442
|
+
| Situation | Default-mode model |
|
|
443
|
+
| --- | --- |
|
|
444
|
+
| Resumed session with its own recorded model | that session's model (Pi's precedence) |
|
|
445
|
+
| Otherwise | `workflow.modelDefault` |
|
|
446
|
+
| No record yet (older install) | the active profile's `defaultModel` — a one-time legacy seed |
|
|
447
|
+
|
|
448
|
+
A profile's `defaultModel` is a **seed consumed when the profile is activated**, not live state re-applied at every start. Activating a profile writes both `workflow.defaultProfile` and `workflow.modelDefault`; picking a model afterwards does **not** edit `profiles.json`. That is what makes the *latest* choice win: pick a model, or activate a profile, and whichever happened last is what the next session uses. Editing the **already active** profile's `default` tab also takes effect immediately, without needing "save & activate".
|
|
449
|
+
|
|
450
|
+
A **project-level** default (a `defaultProvider`/`defaultModel` in `<cwd>/.pi/settings.json`) wins inside its project: Pi merges that file over the global one, and the extension leaves it alone instead of overriding it.
|
|
451
|
+
|
|
452
|
+
The value is stored in `settings.json` (global `~/.pi/agent/settings.json`, or the project file `<cwd>/.pi/settings.json` when that file already defines it):
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{
|
|
456
|
+
"defaultProvider": "opencode-go",
|
|
457
|
+
"defaultModel": "deepseek-v4.1-flash",
|
|
458
|
+
"defaultThinkingLevel": "high",
|
|
459
|
+
"workflow": {
|
|
460
|
+
"modelDefault": {
|
|
461
|
+
"provider": "opencode-go",
|
|
462
|
+
"id": "deepseek-v4.1-flash",
|
|
463
|
+
"thinking": "high"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
`workflow.modelDefault` is the authoritative record (our key — it survives Pi upgrades). The three top-level keys are Pi's own native defaults, mirrored in the same write so terminal Pi, Pi Desktop and Pi's settings UI agree. The extension also applies `workflow.modelDefault` on `session_start` for a brand-new session, because Pi's own startup resolution prefers the **first `enabledModels` entry** over a saved default — without that step a default outside your model scope would be ignored.
|
|
470
|
+
|
|
471
|
+
Every other settings key (including `workflow.commitPolicy`, `workflow.writePolicy`, `workflow.defaultProfile`, `enabledModels`) is preserved by the read-modify-write. Clear the default by deleting `workflow.modelDefault` and the three top-level keys, or simply select a different model. `/role list` and the `/profile` picker show the full answer to "what starts next": the model, the file that defines it, and the profile that will be auto-activated.
|
|
393
472
|
|
|
394
473
|
Review Mode settings live under `workflow.reviewMode`:
|
|
395
474
|
|
|
@@ -400,13 +479,16 @@ Review Mode settings live under `workflow.reviewMode`:
|
|
|
400
479
|
| `passes` | `2` | `1` = review+rewrite only; `2` = review, then self-verify |
|
|
401
480
|
| `verify` | `true` | Run the self-verification pass |
|
|
402
481
|
| `fallbackOnError` | `"skip"` | `skip` hands over the author's plan on any failure (`block` is reserved) |
|
|
403
|
-
| `exploreBudget` | `
|
|
404
|
-
| `timeoutMs` | `
|
|
482
|
+
| `exploreBudget` | `1` | Max `review_explore` subagents per round (cost ceiling). `0` whenever the Planner manifest already carries `explore` recon — the reviewer then verifies instead of re-exploring |
|
|
483
|
+
| `timeoutMs` | `300000` | Wall-clock cap for a whole round, run included (5 minutes). `0` = unlimited (explicit opt-out); a pass then ends on `review_pass_done`, run settle, abort or error. A silent reviewer is still caught immediately by the settle race. `/review-mode status` prints the value and the settings file that set it, and `/review-mode timeout <seconds\|unlimited>` changes it without editing JSON |
|
|
484
|
+
| `contextChars` | `15000` | Max chars of shared Plan Mode context in the pass-1 prompt (diet default; the reviewer re-reads truncated parts with `read`/`grep`) |
|
|
485
|
+
| `contextEntries` | `30` | Max pruned entries handed to the context renderer |
|
|
486
|
+
| `contextToolChars` | `1000` | Max chars kept per tool-result string when pruning |
|
|
405
487
|
|
|
406
488
|
```json
|
|
407
489
|
{
|
|
408
490
|
"workflow": {
|
|
409
|
-
"reviewMode": { "enabled": true, "rounds": 1, "passes": 2, "exploreBudget":
|
|
491
|
+
"reviewMode": { "enabled": true, "rounds": 1, "passes": 2, "exploreBudget": 1 }
|
|
410
492
|
}
|
|
411
493
|
}
|
|
412
494
|
```
|
|
@@ -454,7 +536,7 @@ Write Permission settings live under `workflow.writePolicy` (same global/project
|
|
|
454
536
|
|
|
455
537
|
## How it works
|
|
456
538
|
|
|
457
|
-
See `index.ts`, `checkpoint.ts` (shadow bare-repo), `commit-policy.ts` (commit policy config, coercion and write — no runtime imports, unit-testable), `edit-policy.ts` (write-policy config, coercion, path/command matching, the decision table, and the guided-denial helpers — no runtime imports, unit-testable), `permissions.ts` (shell-command mutation classification), `utils.ts` (safe-command gating, todo/plan extraction, plan hashing), `roles.ts` (role registry, one model per role), `profiles.ts` (profile registry, the model overlay and the `workflow.defaultProfile` setting — no Pi runtime needed, unit-testable), and the Review Mode modules: `review.ts` (config, prompt, state, transcript, child session — no runtime imports, so it is unit-testable), `review-providers.ts` (parent→reviewer provider seeding and the provider-agnostic auth preflight), `review-tools.ts` (the reviewer's tool set), `review-runtime.ts` (round orchestration), `review-ui.ts` (the main-transcript entries: line buffer, formatters, renderers). Hot-reload via `/reload` when placed in `~/.pi/agent/extensions/workflow/` or loaded via `pi.extensions` manifest.
|
|
539
|
+
See `index.ts`, `checkpoint.ts` (shadow bare-repo), `commit-policy.ts` (commit policy config, coercion, the `/commit` prompt and write — no runtime imports, unit-testable), `questionnaire-layout.ts` (pure questionnaire viewport/scroll geometry), `questionnaire-view.ts` (the bounded bottom-sheet questionnaire view), `edit-policy.ts` (write-policy config, coercion, path/command matching, the decision table, and the guided-denial helpers — no runtime imports, unit-testable), `permissions.ts` (shell-command mutation classification), `utils.ts` (safe-command gating, todo/plan extraction, plan hashing), `roles.ts` (role registry, one model per role), `profiles.ts` (profile registry, the model overlay and the `workflow.defaultProfile` setting — no Pi runtime needed, unit-testable), `picker.ts` (the fuzzy-filterable picker primitives), and the Review Mode modules: `review.ts` (config, prompt, state, transcript, child session — no runtime imports, so it is unit-testable), `review-providers.ts` (parent→reviewer provider seeding and the provider-agnostic auth preflight), `review-tools.ts` (the reviewer's tool set), `review-runtime.ts` (round orchestration), `review-ui.ts` (the main-transcript entries: line buffer, formatters, renderers). Hot-reload via `/reload` when placed in `~/.pi/agent/extensions/workflow/` or loaded via `pi.extensions` manifest.
|
|
458
540
|
|
|
459
541
|
### Todo protocol (industry-aligned)
|
|
460
542
|
|
|
@@ -498,7 +580,7 @@ What to do:
|
|
|
498
580
|
cd agent/extensions/workflow
|
|
499
581
|
npm install
|
|
500
582
|
npm run typecheck # tsc --noEmit (covers every source file, including the review modules)
|
|
501
|
-
npm test # node --test utils.todo.test.ts permissions.test.ts roles.test.ts profiles.test.ts review.test.ts review-ui.test.ts commit-policy.test.ts edit-policy.test.ts
|
|
583
|
+
npm test # node --test utils.todo.test.ts permissions.test.ts roles.test.ts profiles.test.ts picker.test.ts questionnaire-layout.test.ts questionnaire-view.test.ts model-default.test.ts review.test.ts review-ui.test.ts commit-policy.test.ts edit-policy.test.ts
|
|
502
584
|
```
|
|
503
585
|
|
|
504
586
|
The write policy also has an end-to-end check that drives the real `tool_call` handler against the real `settings.json` (it temporarily writes your global settings and restores them afterwards):
|
package/commit-policy.ts
CHANGED
|
@@ -403,6 +403,8 @@ export function isAuthorizationActive(
|
|
|
403
403
|
export interface CommitConfirmationQuestion {
|
|
404
404
|
question?: string;
|
|
405
405
|
header?: string;
|
|
406
|
+
/** The proposed message lives here (the questionnaire renders it scrollably). */
|
|
407
|
+
context?: string;
|
|
406
408
|
options?: { label?: string; description?: string }[];
|
|
407
409
|
}
|
|
408
410
|
|
|
@@ -629,3 +631,44 @@ export function commitPolicyArgumentCompletions(
|
|
|
629
631
|
}
|
|
630
632
|
return items;
|
|
631
633
|
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* The `/commit` agent instruction.
|
|
637
|
+
*
|
|
638
|
+
* The proposed message is deliberately put in the questionnaire's scrollable
|
|
639
|
+
* `context` field, with a short pinned question. Putting the whole message in
|
|
640
|
+
* `question` is what used to push the choice rows off-screen on long commits.
|
|
641
|
+
*/
|
|
642
|
+
export function buildCommitPrompt(
|
|
643
|
+
statSummary: string,
|
|
644
|
+
stageAll: boolean,
|
|
645
|
+
): string {
|
|
646
|
+
return [
|
|
647
|
+
`The user ran /commit${stageAll ? " -a" : ""} and wants to commit the currently staged changes.`,
|
|
648
|
+
"",
|
|
649
|
+
"STAGED FILES:",
|
|
650
|
+
statSummary,
|
|
651
|
+
"",
|
|
652
|
+
"INSTRUCTIONS:",
|
|
653
|
+
"1. Run `git diff --staged` via the bash tool to see the full diff of the staged changes.",
|
|
654
|
+
"2. Analyze the diff and compose a concise, free-form commit message summarizing the changes.",
|
|
655
|
+
"3. Present the proposal using the questionnaire tool with exactly ONE question:",
|
|
656
|
+
' header: "Commit"',
|
|
657
|
+
' question: "Commit these staged changes?"',
|
|
658
|
+
' context: "<the exact proposed commit message>"',
|
|
659
|
+
' options: [\n { label: "✅ Commit", description: "Commit with this message" },\n { label: "✏️ Edit", description: "Modify the message before committing" },\n { label: "❌ Cancel", description: "Abort without committing" }\n ]',
|
|
660
|
+
" Put the whole proposed message in `context`, exactly as it should be committed (no code fences, no commentary). It renders in a scrollable panel, so the choices stay visible. Never put the diff in `context`. Keep `question` short.",
|
|
661
|
+
' (The questionnaire tool auto-appends a "Type something." freeform option — if the user types a custom message, use that text as the commit message instead.)',
|
|
662
|
+
"4. After the user responds:",
|
|
663
|
+
' - "✅ Commit" → run `git commit -m "<message>"` via the bash tool.',
|
|
664
|
+
' - "✏️ Edit" or custom written text → run `git commit -m "<written message>"` via the bash tool.',
|
|
665
|
+
' - "❌ Cancel" → do NOT commit; report that the commit was cancelled by the user.',
|
|
666
|
+
"5. Report the outcome (commit success output, or the error if it failed).",
|
|
667
|
+
"",
|
|
668
|
+
"IMPORTANT CONSTRAINTS:",
|
|
669
|
+
"- Only commit the staged changes. Do NOT run `git add` or `git commit -a`.",
|
|
670
|
+
"- Leave all unstaged changes untouched.",
|
|
671
|
+
"- If the commit fails (e.g., pre-commit hook, empty message), report the error and do NOT retry.",
|
|
672
|
+
"- Escape special characters in the commit message appropriately for the shell, or use `git commit -F <file>` for complex messages.",
|
|
673
|
+
].join("\n");
|
|
674
|
+
}
|