pi-workflow-engine 0.13.0 → 1.1.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.
Files changed (98) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +152 -416
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +1 -1
  3. package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +17 -14
  4. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +7 -1
  5. package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +48 -62
  6. package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +46 -67
  7. package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +14 -37
  8. package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +12 -13
  9. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +15 -21
  10. package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +13 -43
  11. package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +17 -5
  12. package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +131 -117
  13. package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +11 -43
  14. package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +4 -18
  15. package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +3 -0
  16. package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +33 -86
  17. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +1 -5
  18. package/.pi/extensions/pi-workflow-engine/src/classify.ts +107 -0
  19. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +25 -80
  20. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +16 -23
  21. package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +1 -4
  22. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +58 -117
  23. package/.pi/extensions/pi-workflow-engine/src/engine.ts +41 -80
  24. package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +2 -10
  25. package/.pi/extensions/pi-workflow-engine/src/git.ts +55 -0
  26. package/.pi/extensions/pi-workflow-engine/src/guards.ts +8 -0
  27. package/.pi/extensions/pi-workflow-engine/src/host-tools.ts +76 -0
  28. package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +7 -19
  29. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +171 -106
  30. package/.pi/extensions/pi-workflow-engine/src/journal.ts +0 -12
  31. package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +37 -8
  32. package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +15 -66
  33. package/.pi/extensions/pi-workflow-engine/src/model-ref.ts +19 -0
  34. package/.pi/extensions/pi-workflow-engine/src/options.ts +97 -16
  35. package/.pi/extensions/pi-workflow-engine/src/perf.ts +43 -72
  36. package/.pi/extensions/pi-workflow-engine/src/pi-compat.ts +2 -2
  37. package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +14 -4
  38. package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +2 -0
  39. package/.pi/extensions/pi-workflow-engine/src/progress.ts +79 -80
  40. package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +28 -20
  41. package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +2 -7
  42. package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +0 -1
  43. package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +29 -31
  44. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +75 -139
  45. package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +14 -31
  46. package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +3 -4
  47. package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +1 -1
  48. package/.pi/extensions/pi-workflow-engine/src/review/review-diff-lines.ts +54 -0
  49. package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +7 -9
  50. package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +51 -117
  51. package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +1 -1
  52. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +8 -13
  53. package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +3 -7
  54. package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +158 -241
  55. package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +28 -38
  56. package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +35 -44
  57. package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +13 -14
  58. package/.pi/extensions/pi-workflow-engine/src/text.ts +29 -0
  59. package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +17 -4
  60. package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +16 -28
  61. package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +34 -14
  62. package/.pi/extensions/pi-workflow-engine/src/types.ts +30 -0
  63. package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +193 -0
  64. package/.pi/extensions/pi-workflow-engine/src/ui/research-result-renderer.ts +177 -0
  65. package/.pi/extensions/pi-workflow-engine/src/ui/result-layout.ts +209 -0
  66. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +17 -44
  67. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +703 -235
  68. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-picker.ts +48 -0
  69. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +239 -129
  70. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +461 -0
  71. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +91 -0
  72. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +316 -98
  73. package/.pi/extensions/pi-workflow-engine/src/usage.ts +41 -34
  74. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +162 -22
  75. package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +19 -24
  76. package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +17 -37
  77. package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +79 -0
  78. package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +4 -14
  79. package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +77 -63
  80. package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +42 -29
  81. package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +15 -14
  82. package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +27 -31
  83. package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +1 -5
  84. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +3 -6
  85. package/.pi/extensions/pi-workflow-engine/src/worktree.ts +48 -110
  86. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +64 -93
  87. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +25 -67
  88. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +19 -53
  89. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +21 -49
  90. package/.pi/extensions/pi-workflow-engine/workflows/research.ts +74 -66
  91. package/README.md +1 -1
  92. package/USAGE.md +79 -20
  93. package/package.json +1 -2
  94. package/skills/workflow-code-review-actions/SKILL.md +12 -23
  95. package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +0 -81
  96. package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -24
  97. package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +0 -52
  98. package/.pi/settings.json +0 -3
package/USAGE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `pi-workflow-engine` adds zero-dependency Dynamax workflows to pi: opt into live, parallel subagents for the tasks that need a custom investigation instead of one long prompt. Most users only need these surfaces:
4
4
 
5
- This release requires **pi 0.80.10 or newer**. pi remains host-provided rather
5
+ This release requires **pi 1.0.0 or newer**. pi remains host-provided rather
6
6
  than bundled by this package, and the extension rejects older runtimes at load
7
7
  time with an update message.
8
8
 
@@ -36,7 +36,7 @@ Useful flags:
36
36
  /workflow code-review --concurrency=4 # cap concurrent subagents
37
37
  /workflow code-review --max-agents=20 # cap total live agent calls in the run
38
38
  /workflow code-review --agent-timeout-ms=600000 # abort one live agent after 10 minutes
39
- /workflow code-review --agent-retries=2 # retry classified transient provider failures
39
+ /workflow code-review --agent-retries=2 # restart agents whose in-session retries are exhausted
40
40
  /workflow code-review --budget=50000 # output-token ceiling for subagents
41
41
  /workflow code-review --resume <run-id> # replay matching completed agent calls
42
42
  /workflow code-review --resume <run-id> --resume-edited # reuse unchanged calls after a workflow edit
@@ -109,7 +109,9 @@ command instead of selecting another model.
109
109
  as external web search, browsing, or HTTP(S) page extraction. Local grep/find
110
110
  tools do not satisfy this check. The workflow does not bundle a scraper or a
111
111
  paid API; install or enable the web capability you prefer, then restart or
112
- reload pi so subagent sessions can see it.
112
+ reload pi so subagent sessions can see it. An MCP web search server also
113
+ qualifies when the host agent runs `research` through the `workflow` tool,
114
+ because only those runs can reach the host's MCP tools.
113
115
 
114
116
  The workflow bounds decomposition to four lanes and verification to twelve
115
117
  claims, while the engine's concurrency, total-agent, timeout, cancellation, and
@@ -124,7 +126,7 @@ the journal's deterministic replay surface.
124
126
 
125
127
  Use `dynamax` when the built-in workflows are not specific enough and you want the main pi agent to author a temporary workflow for the current question. It is the fastest path from "this needs multiple angles" to a live fan-out, verifier pass, and synthesized result.
126
128
 
127
- Interactive shortcut: run `/workflow` with no arguments, choose `✍ Author temporary one-shot workflow…`, and type a brief. pi will send that brief back to the host agent as a `dynamax` request so it can author and run an inline workflow with the `workflow` tool's `script` argument.
129
+ Interactive shortcut: run `/workflow` with no arguments, choose `+ author a one-off workflow` (the last row, below the registered workflows), and type a brief. Escape closes the picker without running anything. pi will send that brief back to the host agent as a `dynamax` request so it can author and run an inline workflow with the `workflow` tool's `script` argument.
128
130
 
129
131
  That inline workflow should investigate, fan out, verify, and summarize. It is **not** where code edits happen. If you want changes made, use the workflow result as evidence and then ask pi to edit separately.
130
132
 
@@ -146,7 +148,7 @@ Sticky mode and inspector shortcut:
146
148
 
147
149
  The workflow inspector and code-review results viewer are also registered as first-class shortcuts shown by `/hotkeys`. The defaults are `ctrl+shift+m` for the inspector and `ctrl+shift+r` for the latest review results. `ctrl+o` is intentionally not used because pi already uses it for tool-output expansion and tree filtering.
148
150
 
149
- When only the literal `dynamax` token is used, the opt-in is one-shot: the next agent run receives the workflow permission reminder, stays visibly active for that run, then clears after the run ends. When `/workflow:dynamax on` is used, the opt-in is sticky for the current pi session until `/workflow:dynamax off`. Sticky mode, one-shot pending mode, and active Dynamax workflow runs show compact TUI status with `/workflow:dynamax on|off` and the inspector shortcut; off mode clears that status line.
151
+ When only the literal `dynamax` token is used, the opt-in is one-shot: the next agent run receives a `<dynamax>` system prompt section that permits the workflow tool (pi appends it as a section patch, so the cached prompt head is kept), stays visibly active for that run, then clears after the run ends. When `/workflow:dynamax on` is used, the opt-in is sticky for the current pi session until `/workflow:dynamax off`. The footer status states the mode compactly: `◆ dynamax` while sticky, `◆ dynamax · next prompt` while a one-shot waits for its prompt, and `◆ dynamax · this turn` while that run is active; off mode clears it. A running workflow names itself in its own status next to it.
150
152
 
151
153
  ### Prompt editor cue
152
154
 
@@ -160,7 +162,7 @@ The default effect is `shine`. Set `PI_DYNAMAX_EFFECT=static` for a motionless
160
162
  cue or `PI_DYNAMAX_EFFECT=off` to disable it. A non-empty `NO_COLOR` also
161
163
  disables the default cue; an explicit `PI_DYNAMAX_EFFECT` value takes precedence.
162
164
 
163
- The implementation follows pi 0.80.10's [official `CustomEditor`
165
+ The implementation follows pi's [official `CustomEditor`
164
166
  pattern](https://pi.dev/docs/latest/extensions#custom-editor): it decorates only
165
167
  the editor's rendered lines, retaining the wrapped editor's input, cursor,
166
168
  deletion, paste, multiline, undo, completion, IME, and app-keybinding behavior.
@@ -172,7 +174,7 @@ is used so highlighting is never silently disabled. Pi editor registration is
172
174
  still last-writer-wins; the previous editor is restored on shutdown only while
173
175
  the Dynamax decorator still owns the registration.
174
176
 
175
- When the host agent calls the `workflow` tool from a TUI session, pi opens the live workflow inspector for that run. The compact workflow widget still shows the latest moving status above the editor, but the inspector is the richer view for phases, agents, findings, and logs.
177
+ When the host agent calls the `workflow` tool, the inspector stays closed so the agent's reply remains in view; the compact workflow widget shows the moving status above the editor. Press the inspector shortcut or run `/workflow:inspector` for the richer view of phases, agents, findings, and logs, live or after the run.
176
178
 
177
179
  Configure either shortcut by creating `~/.pi/agent/extensions/pi-workflow-engine.json`:
178
180
 
@@ -241,9 +243,11 @@ Workflow results are rendered in pi with:
241
243
 
242
244
  These workflow usage totals are separate from `--perf`, which is internal timing only. They are also separate from pi's built-in footer and `/session` totals, which may still show host-session usage only unless pi core adds a first-class extension usage API. If a model has no pricing configured, token totals can be non-zero while displayed cost remains `$0.000`.
243
245
 
244
- The result, inspector overview, compact widget, and status line all use the same usage summary. Providers with a complete breakdown show `fresh`, `cache read`, `cache write`, and `output` separately. Zero cache components are omitted. If a provider reports only some components, the summary labels mixed-run component totals with `≥` and preserves the provider's aggregate `total`; if it reports no components, the summary falls back to aggregate `tokens`. Costs are summed from provider session totals and are never inferred from local pricing.
246
+ Results, the inspector overview, the runs browser and the live widget header show usage in the compact form `↑34k ↓1.2k · $0.02` (input including cache, output, and cost when known); the footer status line carries no usage. The result text handed to the host agent keeps the full breakdown: providers with a complete breakdown show `fresh`, `cache read`, `cache write`, and `output` separately. Zero cache components are omitted. If a provider reports only some components, the summary labels mixed-run component totals with `≥` and preserves the provider's aggregate `total`; if it reports no components, the summary falls back to aggregate `tokens`. Costs are summed from provider session totals and are never inferred from local pricing.
245
247
 
246
- During a run, pi shows live phases and subagent status. Use `--inspect` if you want a larger live view while the workflow is active, then `/workflow:inspector` if you want to bring the last completed inspector back up afterward.
248
+ During a run, a widget above the editor shows the workflow's progress bar, finished/total agents, elapsed time and usage; phases in chronological order, with finished phases collapsed to one line and the active phase listing its running, failed and done agents; the declared phases not reached yet; the workflow's counters; and the inspector shortcut. The footer status line reads `◆ review · Find 6/10 · 12s`. Use `--inspect` if you want a larger live view while the workflow is active, then `/workflow:inspector` if you want to bring the last completed inspector back up afterward.
249
+
250
+ The inspector has Overview, Agents, Findings, and Logs sections, plus Result for a retained run opened from `/workflow:runs`. Switch sections with `tab`/`shift+tab`, `←`/`→`, or `1`–`5`; move with `↑`/`↓` or `j`/`k`, jump with `g`/`G`, expand an agent or finding with `enter`, and close with `q` or `esc`. Expanded agents show the full error, last tool, model, and token usage.
247
251
 
248
252
  ### Durable run records
249
253
 
@@ -257,7 +261,7 @@ Persistence is intentionally bounded and private by default. Raw workflow args,
257
261
 
258
262
  The host-facing `workflow` tool accepts `background: true`. It returns as soon as the run ID and origin metadata are durably recorded, while the normal workflow engine continues in-process with the same concurrency, budget, worktree, progress, usage, and resume rules. Synchronous execution remains the default, and the `/workflow` slash command remains synchronous.
259
263
 
260
- Background runs are detached from the initiating tool-call signal, so the host agent can finish that turn and continue the conversation. When the run completes or fails, the extension waits for the originating session to become idle and inserts one concise `workflow-result` message containing the run ID, terminal state, and summary. Delivery state is stored with the run record; on reload, the extension checks both that state and the session message history before retrying, which suppresses duplicates even if persistence failed immediately after message insertion.
264
+ Background runs are detached from the initiating tool-call signal, so the host agent can finish that turn and continue the conversation. While one runs, it shows a single line above the editor (`◆ background · code-review 56b57550 · Find 6/10 · 12s`) instead of the full live widget. When the run completes or fails, the extension waits for the originating session to become idle and inserts one concise `workflow-result` message containing the run ID, terminal state, and summary. Delivery state is stored with the run record; on reload, the extension checks both that state and the session message history before retrying, which suppresses duplicates even if persistence failed immediately after message insertion.
261
265
 
262
266
  Provider usage-window continuation is a separate, explicit background option:
263
267
 
@@ -281,7 +285,7 @@ A graceful pi session shutdown aborts active background work and records it as `
281
285
 
282
286
  ### Recent runs and lifecycle actions
283
287
 
284
- `/workflow:runs` opens pi's native selection UI with a bounded list of recent project runs. Each option includes an accessible state label, workflow name, age, duration, usage summary, and full run ID. Choose a run, then choose one of the lifecycle actions currently valid for it: inspect, stop, resume, or restart. The background activity line is present only while this session owns active background work.
288
+ `/workflow:runs` opens a runs browser in the TUI: one aligned row per recent project run (state glyph and word, workflow, duration, age, cost when known, short run ID) above a details pane for the selected run with its wall-clock times, phases, agent and token counts, resume lineage, pause details, outcome, and why a relaunch is unavailable when it is. Keys act on the selected run: `enter` or `i` inspect, `r` resume, `s` stop (press twice), `R` restart; only the actions currently valid for that run are listed in the footer, and the others explain why they are unavailable. The browser refreshes while a run is active and reopens on the same run after an action. RPC sessions keep pi's native two-step selection (run, then action). The background activity line is present only while this session owns active background work.
285
289
 
286
290
  The same operations are available without the selection UI:
287
291
 
@@ -293,9 +297,9 @@ The same operations are available without the selection UI:
293
297
  /workflow:inspector <run-id>
294
298
  ```
295
299
 
296
- Inspect is always safe. Stop is available for a background run active in the current session and for a retained paused run with a pending resume timer. Resume is limited to paused registered workflows whose source fingerprint is unchanged and whose invocation had no redacted arguments; it starts a new background run ID with `resumeFromRunId` journal replay. Restart is limited to completed, failed, or stopped registered workflows that had no arguments and also creates a new background run ID. Inline workflows and argument-bearing invocations are intentionally not relaunched because their executable input was not persisted. In `print` and `json` modes, list and inspect return formatted text without opening TUI components, while resume/restart report that background execution requires TUI or RPC mode.
300
+ Inspect is always safe. Stop is available for a background run active in the current session and for a retained paused run with a pending resume timer. Resume is limited to paused registered workflows whose source fingerprint is unchanged and whose invocation had no redacted arguments; it starts a new background run ID with `resumeFromRunId` journal replay. A paused run already resumed by a queued, running, paused, or completed run is listed as `resumed as <run-id>` and offers no second resume; once every resume of it has failed or been stopped, manual resume is offered again, while automatic provider-limit resume never fires twice for the same paused run. Restart is limited to completed, failed, or stopped registered workflows that had no arguments and also creates a new background run ID. Inline workflows and argument-bearing invocations are intentionally not relaunched because their executable input was not persisted. In `print` and `json` modes, list and inspect return formatted text without opening TUI components, while resume/restart report that background execution requires TUI or RPC mode.
297
301
 
298
- Code-review findings are rendered as a formatted result message by default. pi no longer asks whether to open the findings viewer. Use `--result-viewer` or `--review-viewer` when you want to inspect findings interactively, press `enter` to expand/collapse the nicely formatted finding text, and use `1`-`9` to jump directly to a visible finding. The viewer is centred, scales to the terminal, and shows the visible finding/detail ranges while scrolling. `/workflow:results` or `ctrl+shift+r` reopens the most recent validated code-review report in the current pi session without rerunning the workflow; selections reset when it reopens.
302
+ Code-review findings are rendered as a formatted result message by default. pi no longer asks whether to open the findings viewer. Use `--result-viewer` or `--review-viewer` when you want to inspect findings interactively. The viewer lists findings most severe first with the selected finding's full detail beside the list (below it in narrow terminals); `space` or `enter` selects a finding, `a` toggles all, `f` generates patch previews and `c` posts PR comments for the selection, and `pgup`/`pgdn` scroll a long detail. The viewer is sized to its content and shows scroll hints only when something is clipped. `/workflow:results` or `ctrl+shift+r` reopens the most recent validated code-review report in the current pi session without rerunning the workflow; selections reset when it reopens.
299
303
 
300
304
  The Fix action revalidates the exact reviewed PR/ref/index/working-tree snapshot, then runs each selected finding through its own worktree-isolated agent and returns the finding ID, validation summary, patch, and changed status. Failed attempts do not discard successful previews, no patch is applied to your active tree automatically, and a moved or unverifiable review target is rejected rather than patched against the wrong code. The original review and all Fix previews retained in that pi session share one output-token budget. Reopening the viewer does not reset it, finalized preview usage is always deducted, and only one preview can run at a time.
301
305
 
@@ -421,6 +425,11 @@ overrides its profile.
421
425
 
422
426
  `tools` is a strict allowlist. If you set `tools: ["read", "bash"]`, extension tools such as `ast-grep`, `mgrep`, `fffind`, or `ffgrep` are hidden from that subagent. Add `toolHints: ["search"]` to dynamically expose installed grep/find/search-like tools while keeping the concrete base allowlist portable. Use `toolHints: ["external-search"]` for installed web search, browsing, and URL-extraction tools; `requireToolHints: true` fails before prompting unless each requested capability matched. The built-in advisory workflows use `tools: ["read", "bash", "grep", "find", "ls"]` plus `toolHints: ["search"]`.
423
427
 
428
+ Subagents are SDK sessions, so pi's built-in codemode and MCP extensions do not load in them. Two opt-ins cover that:
429
+
430
+ - **Codemode.** Add `"codemode"` to an agent's `tools` to let it batch or filter many tool calls in one script, for example `tools: ["read", "grep", "codemode"]`. Scripts can call only that agent's other allowed tools, and never `final_answer`. Classifier calls a script makes count toward the run's usage and budget.
431
+ - **Host MCP tools.** In runs started by the `workflow` tool, subagents reach the host's MCP tools over its existing connections and through its permission handlers. Name a tool in `tools` (for example `"mcp__linear__create_issue"`), or use `toolHints: ["external-search"]` to pick up MCP web research tools; servers that declare a tool not read-only or closed-world are excluded from that hint. Bridged tools act in the host session, so the local `search` hint never bridges them, and an isolated worktree agent that names a local MCP tool sees the main checkout through it, not its worktree. `/workflow` commands and background runs outlive the host tool call that makes this possible: an agent there that names an MCP tool fails before prompting rather than running without it.
432
+
424
433
  Subagents receive no skills by default. Opt in per agent with `skills: ["skill-name"]`; if `tools` is also restricted, the engine automatically keeps `read` available so the subagent can load the selected `SKILL.md`. When `skills` is omitted, clear prompt text such as `/skill:name`, `include skill name`, or `use the name skill` is also treated as an opt-in. Pass `skills: []` to suppress that inference.
425
434
 
426
435
  Set `model` only when a subagent should use a specific model. Bare ids keep the Anthropic shorthand; `provider/id` targets built-in, custom, or local providers. Explicit models override a requested profile; calls with neither inherit the host/session default. Malformed or unknown explicit refs fail fast.
@@ -459,18 +468,37 @@ export default async function run({ workflow }: WorkflowApi) {
459
468
 
460
469
  Nesting is one level only: calling `workflow()` from inside a sub-workflow rejects. Resolution throws on an unknown name. Inside `parallel()` or `pipeline()`, recoverable branch errors become `null` results, so filter nulls before synthesis; a genuine run abort still rejects.
461
470
 
471
+ ### Classify between stages
472
+
473
+ `classify(context, opts?)` answers typed `choice`, `score`, or `bool` questions about JSON `state` with one of pi's classifier models (for example TypeSafe Jev or Cloudflare Clef), using the host's credentials and no chat session. It suits cheap gates between stages, such as deduplicating findings or deciding which items need a follow-up agent.
474
+
475
+ ```ts
476
+ const answers = await api.classify(
477
+ {
478
+ state: { finding },
479
+ questions: {
480
+ followUp: { type: "bool", instructions: "Does this finding need verification?", criteria: { true: "Plausible but unproven", false: "Clearly settled" } },
481
+ },
482
+ },
483
+ { label: `triage:${finding.id}` },
484
+ );
485
+ if (answers.followUp?.type === "bool" && answers.followUp.probability > 0.5) { /* verify it */ }
486
+ ```
487
+
488
+ Without `opts.model` ("provider/id"), the first classifier with working credentials is used. Calls share the run's concurrency cap, count toward usage and the budget, and are not journaled, so a resumed run classifies again. When no classifier is configured the call rejects with a recoverable error, so guard it or let `parallel()` turn it into `null`.
489
+
462
490
  ### Where workflows live
463
491
 
464
492
  Guaranteed built-ins are statically registered:
465
493
 
466
494
  1. Add `.pi/extensions/pi-workflow-engine/workflows/<name>.ts`.
467
495
  2. Import it in `.pi/extensions/pi-workflow-engine/src/workflows.ts`.
468
- 3. Add it to `BUILTIN_WORKFLOWS`.
496
+ 3. Add a `defineBuiltinWorkflow(mod, "<name>.ts")` entry to `BUILTIN_WORKFLOW_DEFINITIONS`.
469
497
 
470
498
  Drop-in workflows are also discovered best-effort from:
471
499
 
472
- - `.pi/extensions/pi-workflow-engine/workflows/*.ts`
473
- - `~/.pi/agent/workflows/*.ts`
500
+ - `~/.pi/agent/workflows/*.ts`: the user-level drop-in directory, which survives `pi update`.
501
+ - The extension's own `workflows/*.ts` directory: `.pi/extensions/pi-workflow-engine/workflows/` in a working copy or a project-vendored copy. Files added to an installed package are replaced by `pi update`.
474
502
 
475
503
  Use `/workflow <name> --refresh` after adding a drop-in file.
476
504
 
@@ -517,7 +545,7 @@ Only tune these when a workflow is too slow, too expensive, or too noisy:
517
545
  | `--parallel-limit=N` / `PI_WORKFLOW_PARALLEL_SUBMISSION_LIMIT=N` | Limit eager `parallel()` submission. |
518
546
  | `--max-agents=N` / `PI_WORKFLOW_MAX_AGENTS=N` | Cap live model calls across the complete run, including sub-workflows. Default `64`; values are clamped to `1`–`10000`. Replay hits do not consume the cap. The `workflow` tool field is `maxAgents`. |
519
547
  | `--agent-timeout-ms=N` / `PI_WORKFLOW_AGENT_TIMEOUT_MS=N` | Abort one live agent after this duration. Default `1800000` (30 minutes); values are clamped to `1000`–`86400000`. The session is aborted and its isolated worktree is cleaned up. The `workflow` tool field is `agentTimeoutMs`. |
520
- | `--agent-retries=N` / `PI_WORKFLOW_AGENT_RETRIES=N` | Retry terminal assistant failures that pi classifies as transient provider or transport errors. Default `0`; values are clamped to `0`–`10`. Backoff starts at one second, doubles, and caps at 30 seconds. The `workflow` tool field is `agentRetries`. |
548
+ | `--agent-retries=N` / `PI_WORKFLOW_AGENT_RETRIES=N` | Restart the whole agent after a transient provider or transport failure that pi's in-session turn retry could not recover. Default `0`; values are clamped to `0`–`10`. Backoff starts at one second, doubles, and caps at 30 seconds. The `workflow` tool field is `agentRetries`. |
521
549
  | `--budget=N` / `PI_WORKFLOW_BUDGET=N` | Set an output-token ceiling across recorded subagent attempts, including failed provider attempts. `agent()` throws `WorkflowBudgetExceededError` before starting another model request once the ceiling is reached; agents already running may overshoot because the engine does not reserve per-agent estimates. |
522
550
  | `--perf` / `PI_WORKFLOW_PERF=1` | Include internal timing aggregates. Usage/cost totals are reported separately from perf. |
523
551
  | `PI_WORKFLOW_LANE_ITEM_LIMIT=N` | Cap retained progress lane items. |
@@ -525,17 +553,29 @@ Only tune these when a workflow is too slow, too expensive, or too noisy:
525
553
  These controls bound different dimensions: concurrency limits how many agents
526
554
  are active together, `max-agents` limits how many live model calls the run may
527
555
  start in total, `agent-timeout-ms` bounds one active call (including retry
528
- backoff), and `budget` limits recorded output tokens. Every retry consumes
556
+ backoff), and `budget` limits recorded output tokens. Every agent restart consumes
529
557
  another live-agent admission and remains inside the original timeout, budget,
530
558
  and abort scope. Usage from failed attempts is retained, so it can exhaust the
531
559
  budget before the next retry begins. Host cancellation takes precedence over
532
560
  limit and timeout failures.
533
561
 
534
- Workflow subagents disable pi's session-level automatic retry so this run-level
535
- policy is authoritative and visible in the existing agent progress row. Invalid
562
+ Transient provider failures are retried in two layers. First, each subagent
563
+ inherits pi's session-level automatic retry from your pi `retry` settings
564
+ (enabled by default: 3 retries with 2s, 4s, 8s backoff). It retries the failed
565
+ turn in place, keeping the agent's earlier tool work; each attempt is logged in
566
+ the workflow progress log, and usage from failed attempts is still recorded.
567
+ Like the agent's other turns, these in-place retries are not budget-checked, and
568
+ a retryable rate-limit or usage-limit error is retried in-session before a
569
+ background run's usage-limit pause takes effect. Only when those retries are
570
+ exhausted does `--agent-retries` restart the agent from scratch. Invalid
536
571
  models, schema-contract failures, exhausted budgets, agent limits, worktree
537
572
  failures, timeouts, and host aborts are never provider-retried.
538
573
 
574
+ pi-workflow-engine 0.12.0 and 0.13.0 wrote `"retry": {"enabled": false}` into
575
+ `~/.pi/agent/settings.json` whenever a workflow ran, which disables automatic
576
+ retry for the host session and every subagent. Workflow runs log a warning while
577
+ that setting is in effect; remove it unless you disabled retry on purpose.
578
+
539
579
  ## Common fixes
540
580
 
541
581
  - **Unknown workflow**: confirm the extension is loaded with `pi list`; use `--refresh` for new drop-ins.
@@ -561,6 +601,25 @@ fails, the result retains verified records and reports incomplete coverage.
561
601
  Synthesis selects or merges IDs; the workflow reconstructs evidence-bearing
562
602
  fields from those records.
563
603
 
604
+ ### Duplicate candidates
605
+
606
+ Lenses often report the same defect in different words. When a TypeSafe Jev
607
+ classifier is reachable (TypeSafe directly, OpenRouter, Cloudflare Workers AI,
608
+ Vercel AI Gateway, or OpenCode Zen), the four advisory workflows ask it whether
609
+ each pair of candidates in the same file describes one defect. Before
610
+ verification, a candidate joins a group only when the classifier is at least 80%
611
+ sure it matches every member, so each defect gets one verifier. Merged
612
+ candidates keep all of their source IDs, locations, impacts, and evidence, and
613
+ the verifier sees the other wordings as "Also reported as" evidence. If the
614
+ first comparison fails (no Jev credentials, a bad key, a rate limit), the merge
615
+ is skipped.
616
+
617
+ Measured on GPT-6.1 Sol code reviews with planted bugs, the merge cut verifier
618
+ agents and cost by about 40% (e.g. 29 → 18 agents, $0.60 → $0.37 list price on
619
+ a small diff; 22 → 14 agents, $1.74 → $0.89 on a 160 KB diff). In every
620
+ instrumented run, each planted bug that a finder proposed reached the final
621
+ report, with or without merging.
622
+
564
623
  ### Selective adversarial challenges
565
624
 
566
625
  Add `--challenge` to an advisory workflow to challenge up to three uncertain or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-workflow-engine",
3
- "version": "0.13.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Zero-dependency Dynamax workflows for pi: live parallel subagents, typed handoffs, synthesized results.",
6
6
  "keywords": [
@@ -22,7 +22,6 @@
22
22
  },
23
23
  "files": [
24
24
  ".pi/extensions/pi-workflow-engine",
25
- ".pi/settings.json",
26
25
  "assets/preview.png",
27
26
  "skills",
28
27
  "README.md",
@@ -1,38 +1,27 @@
1
1
  ---
2
2
  name: workflow-code-review-actions
3
- description: "Act on selected code-review findings from pi-workflow-engine: make minimal fixes or post GitHub PR inline comments using gh, GitHub MCP/tools, or project-specific tools."
3
+ description: "Act on selected code-review findings from pi-workflow-engine: post GitHub PR inline comments using gh, GitHub MCP/tools, or project-specific tools."
4
4
  ---
5
5
 
6
- Use this skill when the parent agent receives selected code-review finding JSON from `pi-workflow-engine`.
6
+ Use this skill when the parent agent receives selected code-review finding JSON from `pi-workflow-engine`. The engine hands comments to the parent agent only when its own `gh` posting could not resolve the PR or failed for some findings.
7
+
8
+ Fixes are never handed off: the results viewer generates isolated, independently validated patch previews for selected findings. Do not edit files in response to a code-review handoff.
7
9
 
8
10
  ## Inputs
9
11
 
10
- The prompt will include compact JSON with:
12
+ The prompt will include the verified reviewed PR head SHA, the reason the engine could not post the comments itself, and compact JSON with:
11
13
 
12
- - `context`: workflow name, target, diff command, changed files, and optional summary.
14
+ - `context`: workflow name, target, `diffTarget` (with the PR `number`), display `diffCommand`, changed files, optional summary, and optional snapshot identity.
13
15
  - `issues`: selected findings with `id`, `summary`, `category`, `severity`, `confidence`, `location`, `impact`, `evidence`, and `recommendation`.
14
16
 
15
- ## Fix mode
16
-
17
- When mode is `fix selected code-review findings`:
18
-
19
- 1. Inspect the selected issue JSON before editing.
20
- 2. Make minimal edits that address only the selected findings.
21
- 3. Preserve unrelated user changes and avoid broad refactors.
22
- 4. Run focused validation if available for the touched files or behavior.
23
- 5. Summarize changed files and validation results.
24
- 6. Do not post GitHub PR comments in fix mode.
25
-
26
- ## Comment mode
27
-
28
- When mode is `post inline GitHub PR comments`:
17
+ ## Posting comments
29
18
 
30
- 1. Do not edit files or make code changes.
31
- 2. Prefer installed GitHub MCP/tools if visible in the active tool list.
19
+ 1. Prefer installed GitHub MCP/tools if visible in the active tool list.
20
+ 2. Before posting, resolve the current PR head and require it to equal the verified reviewed head from the prompt; stop if it differs.
32
21
  3. If no GitHub MCP/tools are available, use `gh`:
33
- - Resolve the PR with `gh pr view --json number,headRefOid,url,headRepositoryOwner,headRepository`.
34
- - Resolve the repository with `gh repo view --json nameWithOwner` when owner/name is missing.
35
- - Post each inline comment with `gh api repos/{owner}/{repo}/pulls/{number}/comments` and include `commit_id`, `path`, `line`, and `side=RIGHT`.
22
+ - Resolve the PR with `gh pr view <context.diffTarget.number> --json headRefOid,url`.
23
+ - Take owner/repo from the PR `url`, which names the base repository, so fork PRs are commented upstream. Do not use `headRepositoryOwner`/`headRepository`; on a fork PR they name the fork. Fall back to `gh repo view --json nameWithOwner` only if the URL is missing.
24
+ - Post each inline comment with `gh api repos/{owner}/{repo}/pulls/{number}/comments` and include `commit_id` (the verified head), `path`, `line`, and `side=RIGHT`.
36
25
  4. Keep each comment concise: summary, severity/confidence/category, impact, evidence, and recommendation.
37
26
  5. Report posted, skipped, and failed counts.
38
27
 
@@ -1,81 +0,0 @@
1
- import type { PerfSink } from "./perf.ts";
2
- import { runBoundedProcess, type BoundedProcessFailure } from "./process-runner.ts";
3
- import { parseAllowedDiffCommand, reviewDiffCommand, type ReviewDiffTarget } from "./review-diff-target.ts";
4
-
5
- export {
6
- formatReviewDiffTarget,
7
- GitReviewDiffTargetSchema,
8
- isReviewDiffTarget,
9
- parseAllowedDiffCommand,
10
- PullRequestReviewDiffTargetSchema,
11
- reviewDiffCommand,
12
- ReviewDiffTargetSchema,
13
- reviewGitDiffBaseline,
14
- type GitDiffBaselineTarget,
15
- type GitReviewDiffTarget,
16
- type PullRequestReviewDiffTarget,
17
- type ReviewDiffTarget,
18
- } from "./review-diff-target.ts";
19
-
20
- interface DiffCaptureResultBase {
21
- readonly stdout: string;
22
- readonly durationMs: number;
23
- readonly bytes: number;
24
- }
25
-
26
- export type DiffCaptureResult =
27
- | (DiffCaptureResultBase & { readonly ok: true; readonly error?: undefined; readonly failure?: undefined })
28
- | (DiffCaptureResultBase & { readonly ok: false; readonly error: string; readonly failure: DiffCaptureFailure });
29
-
30
- export type DiffCaptureFailure = BoundedProcessFailure | { readonly kind: "invalid-target"; readonly message: string };
31
-
32
- export interface DiffCaptureOptions {
33
- readonly cwd: string;
34
- readonly signal?: AbortSignal;
35
- readonly timeoutMs: number;
36
- readonly maxBufferBytes: number;
37
- readonly perf?: PerfSink;
38
- readonly env?: NodeJS.ProcessEnv;
39
- readonly killGraceMs?: number;
40
- }
41
-
42
- export async function captureDiff(command: string, options: DiffCaptureOptions): Promise<DiffCaptureResult> {
43
- const parsed = parseAllowedDiffCommand(command);
44
- if ("error" in parsed) {
45
- return {
46
- ok: false,
47
- stdout: "",
48
- durationMs: 0,
49
- bytes: 0,
50
- error: parsed.error,
51
- failure: { kind: "invalid-target", message: parsed.error },
52
- };
53
- }
54
-
55
- return await captureDiffTarget(parsed, options);
56
- }
57
-
58
- export async function captureDiffTarget(target: ReviewDiffTarget, options: DiffCaptureOptions): Promise<DiffCaptureResult> {
59
- const command = reviewDiffCommand(target);
60
- const result = await runBoundedProcess({
61
- file: command.file,
62
- args: command.args,
63
- cwd: options.cwd,
64
- env: diffCaptureEnv(options.env),
65
- signal: options.signal,
66
- timeoutMs: options.timeoutMs,
67
- maxBufferBytes: options.maxBufferBytes,
68
- killGraceMs: options.killGraceMs,
69
- abortError: "diff capture aborted",
70
- timeoutError: `diff capture timed out after ${options.timeoutMs}ms`,
71
- maxBufferError: `diff capture exceeded ${options.maxBufferBytes} bytes`,
72
- exitError: (stderr, code, signal) => stderr.trim() || `diff command exited with code ${code ?? `signal ${signal ?? "unknown"}`}`,
73
- });
74
- options.perf?.observe("diff.capture_ms", result.durationMs);
75
- options.perf?.observe("diff.bytes", result.bytes);
76
- return result;
77
- }
78
-
79
- function diffCaptureEnv(env: NodeJS.ProcessEnv | undefined): NodeJS.ProcessEnv {
80
- return { ...(env ?? process.env), GIT_EXTERNAL_DIFF: "", GIT_DIFF_OPTS: "" };
81
- }
@@ -1,24 +0,0 @@
1
- const DIFF_EMBED_CAP = 60_000;
2
-
3
- export function buildCodeReviewScopeBlock(input: {
4
- readonly diffCommand: string;
5
- readonly files: readonly string[];
6
- readonly summary: string;
7
- readonly conventions?: string;
8
- readonly diffText: string;
9
- readonly target: string;
10
- }): string {
11
- const diffBlock = input.diffText
12
- ? `\n## Diff (review is bounded to these changed lines)\n\`\`\`diff\n${
13
- input.diffText.length > DIFF_EMBED_CAP
14
- ? `${input.diffText.slice(0, DIFF_EMBED_CAP)}\n... (truncated — run \`${input.diffCommand}\` for the full diff)`
15
- : input.diffText
16
- }\n\`\`\`\n`
17
- : "";
18
- return (
19
- `## Diff command\n${input.diffCommand}\n\n## Changed files\n${input.files.map((file) => `- ${file}`).join("\n")}\n\n` +
20
- `## Summary\n${input.summary}\n\n## Conventions\n${input.conventions ?? "(none noted)"}\n` +
21
- diffBlock +
22
- (input.target ? `\n## User instructions (verbatim)\n${input.target}\n` : "")
23
- );
24
- }
@@ -1,52 +0,0 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import type { WorkflowRunOptions } from "../types.ts";
3
- import { toReviewIssues, type ReviewIssue, type ReviewIssueSelection } from "./review-issues.ts";
4
- import { isReviewReport, type ReviewReport } from "./review-report.ts";
5
- import { ReviewResultsViewer } from "./review-results-viewer.ts";
6
- import { WORKFLOW_VIEWER_OVERLAY_OPTIONS } from "../ui/workflow-viewer-layout.ts";
7
-
8
- export type ReviewResultsPresentationDecision =
9
- | {
10
- readonly kind: "send";
11
- readonly reason: "not-code-review" | "not-advisory" | "no-findings" | "disabled" | "not-tui" | "not-requested";
12
- }
13
- | { readonly kind: "open"; readonly issues: readonly ReviewIssue[] };
14
-
15
- export interface ReviewResultsDecisionInput {
16
- readonly workflowName: string;
17
- readonly result: unknown;
18
- readonly mode?: string;
19
- readonly hasUI: boolean;
20
- readonly resultViewer?: WorkflowRunOptions["resultViewer"];
21
- }
22
-
23
- export function codeReviewReport(workflowName: string, result: unknown): ReviewReport | undefined {
24
- return workflowName === "code-review" && isReviewReport(result) ? result : undefined;
25
- }
26
-
27
- export interface ReviewResultsViewerContext {
28
- readonly ui: Pick<ExtensionContext["ui"], "custom">;
29
- }
30
-
31
- export function decideReviewResultsPresentation(input: ReviewResultsDecisionInput): ReviewResultsPresentationDecision {
32
- if (input.workflowName !== "code-review") return { kind: "send", reason: "not-code-review" };
33
- const report = codeReviewReport(input.workflowName, input.result);
34
- if (!report) return { kind: "send", reason: "not-advisory" };
35
- if (report.findings.length === 0) return { kind: "send", reason: "no-findings" };
36
- if (input.resultViewer === "skip") return { kind: "send", reason: "disabled" };
37
- if (input.mode !== "tui" || !input.hasUI) return { kind: "send", reason: "not-tui" };
38
-
39
- if (input.resultViewer !== "open") return { kind: "send", reason: "not-requested" };
40
-
41
- return { kind: "open", issues: toReviewIssues(input.workflowName, report) };
42
- }
43
-
44
- export async function showReviewResultsViewer(
45
- ctx: ReviewResultsViewerContext,
46
- issues: readonly ReviewIssue[],
47
- ): Promise<ReviewIssueSelection> {
48
- return await ctx.ui.custom<ReviewIssueSelection>(
49
- (tui, theme, _keybindings, done) => new ReviewResultsViewer(issues, "code-review", tui, theme, done),
50
- WORKFLOW_VIEWER_OVERLAY_OPTIONS,
51
- );
52
- }
package/.pi/settings.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "extensions": ["extensions/pi-workflow-engine/index.ts"]
3
- }