pi-bro 0.21.0 → 0.22.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/CHANGELOG.md +53 -0
- package/README.md +80 -37
- package/backend.ts +55 -42
- package/bro.ts +166 -1359
- package/config-ui.ts +325 -0
- package/docs/README.md +2 -0
- package/docs/guided-review.md +131 -0
- package/docs/pig-compatibility.md +1 -0
- package/package.json +13 -4
- package/prompt.ts +19 -4
- package/review-modal.ts +1272 -0
- package/review-source.ts +296 -0
- package/review-ui.ts +519 -0
- package/review.ts +586 -0
- package/settings.ts +505 -0
- package/sources.ts +367 -0
- package/util.ts +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-bro are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.22.0] - 2026-10-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Persist settings with atomic replacement and mode 0o600 (#84):
|
|
10
|
+
- Replace direct `writeFile` with atomic sibling write (`.tmp`) and `rename` to prevent settings corruption on failure, crash, or disk full (`ENOSPC`).
|
|
11
|
+
- Flush writes before replacement (`flush: true`) and clean up temporary files on failure.
|
|
12
|
+
- Enforce `mode: 0o600` on replaced settings files so permissions are never loosened.
|
|
13
|
+
- Resolve symlinks via lstat and realpath before replacement to preserve dotfile-managed configuration symlinks, and reject if an existing symlink target cannot be resolved.
|
|
14
|
+
- Align on-disk state with modal rollback behavior in `/bro config`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Guided Review (#104): open a PR by URL/number in local Pi to automatically get an explanation and quality assessment, captured Git evidence, findings with fix/check suggestions, diffs and optional private questions. Acquisition uses authenticated `gh` outside the active working tree. Findings remain model proposals, not approval or applied changes.
|
|
19
|
+
- Automatic local remembering of questions/drafts/reading position; reopening the same PR restores the captured revision without a model call. Bare `/bro guided-review` lists saved reviews; `resume` remains an alias. Close saves automatically; Stop and close cancels first. Copy finding copies to the system clipboard without submission.
|
|
20
|
+
- Confirmed same-revision regeneration replaces one current guide and preserves original private context; failed/cancelled generation retains it and save errors offer recovery. There is no guide-version UI or prototype-format migration. Review has its own backend override.
|
|
21
|
+
- Bounded live checks demonstrated an unchanged-caller defect and successful regeneration; manual UX accepted. This is not a general accuracy guarantee, live five-backend matrix or complete #104 scope. Notes, examination tracking, branch/update review and feedback delivery are not implemented.
|
|
22
|
+
|
|
23
|
+
- Durable advisor steering across sessions in `~/.pi/agent/bro-advisor.md` (#97):
|
|
24
|
+
- Ingest standing priorities from `bro-advisor.md` alongside session steering (`/bro advisor-steer`).
|
|
25
|
+
- Labeled prompt composition in `buildAdvisorPrompt`: standing priorities appear under `### Standing priorities (durable across sessions)` and session priorities under `### Session priorities (this session only)`.
|
|
26
|
+
- Session-specific priorities take precedence over standing defaults where they conflict.
|
|
27
|
+
- Fail-closed validation rejects files > 64 KB and > 4,000 characters before spawning any backend process.
|
|
28
|
+
- Doctor reports both `Advisor steering (session)` and `Advisor steering (durable)`.
|
|
29
|
+
- Tool result context line and details track `steeringSources: { durable, session }` while preserving backwards-compatible `steeringIncluded` boolean.
|
|
30
|
+
- Retries use a frozen prompt snapshot built prior to `runAdvisorWithRetries`.
|
|
31
|
+
- Clearing session steering leaves durable steering intact.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Doctor treats an unsupported optional review backend as an informational override hint, not a broken installation; healthy Agy/Grok/Codex defaults still report ready.
|
|
36
|
+
- Review is file-only on Claude/Muse: no shell, write or web tools; Agy/Grok/Codex review calls fail closed without switching backend. Other features retain their access behavior.
|
|
37
|
+
- Capture uses depth-1 head/merge-base trees instead of full history; fetch gets a ten-minute bound while evidence stays offline. Interrupted-capture recovery remains.
|
|
38
|
+
- Removed prototype record migrations after one-time backup/normalization of development records; split validation and formatted review modules/tests to the repository tab style.
|
|
39
|
+
|
|
40
|
+
- Guided Review UX consistency pass: origin-preserving Back, focused-action Enter, topic-filtered findings, pane-aware paging, visible focus/overflow, selected-item previews and compact full-width 36×18 layout. Discussion follows conversation/composer/actions order; save errors have shared retry controls and failed questions have draft-safe explicit edit/resend. Request errors are separate from model answers, streaming follows again at the bottom, and acquisition/resume/close messages give clearer orientation. Current review behavior, controls, access/storage and recovery are documented in the user guide; developer/testing guidance records its implementation rules and verification limits. Review itself has no publication action.
|
|
41
|
+
- Remove dead declarations and stale backend naming (#90):
|
|
42
|
+
- Enable native TypeScript unused checks (`noUnusedLocals`, `noUnusedParameters`) in `tsconfig.json`.
|
|
43
|
+
- Remove verified unused declarations across `bro.ts`, `backend.test.ts`, `benchmark/run.ts`, `codex.test.ts`, and `release.test.ts`.
|
|
44
|
+
- Drop pass-through re-exports from `bro.ts` (`agyFailureMessage`, `agySelection`, `advisorFlagErrorHint`, `parseBtwAgyLine`); tests and consumers import them directly from `backend.ts`.
|
|
45
|
+
- Rename multi-backend facilities with obsolete Agy prefixes: `runAgyText` -> `runBackendText` in `bro.ts` and `killAgyGroup` -> `killProcessGroup` in `backend.ts`.
|
|
46
|
+
- Modernize outdated single-backend and pre-extraction comments.
|
|
47
|
+
- Separate Bro responsibilities and unify settings and backend-selection policy (#83):
|
|
48
|
+
- Extract `settings.ts` (schema, typed `EXTERNAL_BACKENDS` metadata, selection policy, pure transitions, and file persistence).
|
|
49
|
+
- Extract `sources.ts` (self-contained document text extraction and public web scraping with SSRF protection).
|
|
50
|
+
- Extract `config-ui.ts` (`/bro config` modal, setting items, and async save serialization).
|
|
51
|
+
- Extract `util.ts` (shared leaf utility helpers).
|
|
52
|
+
- Unify legacy flat and v2 settings parsing into one converged validation pipeline in `settings.ts`, eliminating duplicate validation branches.
|
|
53
|
+
- Initialize clean version 2 settings on fresh installations in `ensureSettingsFile()`.
|
|
54
|
+
- Share pure state transitions (`applyModelChange`, `applyEffortChange`) across default and capability override rows.
|
|
55
|
+
- Fix Agy variant model reselection bug where resolved effort from suffixed variants was lost.
|
|
56
|
+
- Consolidate CLI version probes in `/bro doctor` into a single `checkCliVersion` helper.
|
|
57
|
+
|
|
5
58
|
## [0.21.0] - 2026-10-04
|
|
6
59
|
|
|
7
60
|
### Added
|
package/README.md
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
# pi-bro
|
|
2
2
|
|
|
3
|
-
Turn a dense AI reply, pasted text,
|
|
4
|
-
plain-language explanation — or open a separate side conversation with
|
|
5
|
-
`/bro btw` — without adding anything to your main agent's context.
|
|
3
|
+
Turn a dense AI reply, pasted text, document or webpage into a plain-language explanation. Ask side questions with `/bro btw`, or understand and assess a PR with `/bro guided-review`. These windows do not add their discussions to your main agent's context.
|
|
6
4
|
|
|
7
5
|
`pi-bro` is an extension for [Earendil Pi](https://github.com/earendil-works/pi).
|
|
8
6
|
Experimental [PiG 0.3.0 compatibility](docs/pig-compatibility.md) uses the same npm package and requires Node.js. See the compatibility notes for verified coverage and the PiG RPC tool-exclusion limitation.
|
|
9
7
|
It opens explanations in a separate modal and runs them through a CLI backend
|
|
10
|
-
you already have installed and signed in to. Explain, show, BTW
|
|
11
|
-
advisor all work across five backends:
|
|
8
|
+
you already have installed and signed in to. Explain, show, BTW and advisor use five backend adapters. Guided Review generation/questions require Claude or Muse file-only inspection (live verification varies by feature):
|
|
12
9
|
|
|
13
10
|
- [Google Antigravity CLI](https://antigravity.google/docs/cli-install) (`agy`) — the default for new settings
|
|
14
11
|
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`claude`)
|
|
@@ -75,7 +72,9 @@ text directly captures a new source the same way.
|
|
|
75
72
|
| `/bro doctor` | Check Bro's settings, preferences, and each selected backend, with the effective backend/model/effort per feature. |
|
|
76
73
|
| `/bro mode [brief\|balanced\|faithful]` | View or choose the explanation mode. |
|
|
77
74
|
| `/bro preferences` | View or edit what Bro knows about you and how you like answers; see [Preferences](#preferences). |
|
|
78
|
-
| `/bro config` | Open
|
|
75
|
+
| `/bro config` | Open settings for the shared backend/model/effort, explain mode, show turns, and per-capability (explain/show/btw/advisor/review) overrides. |
|
|
76
|
+
| `/bro guided-review <PR number or URL>` | Automatically explain and assess a captured PR in local Pi, with source evidence and optional private questions. Opening the same PR restores its saved review. |
|
|
77
|
+
| `/bro guided-review` | List saved reviews; `resume` is an alias. |
|
|
79
78
|
| `/bro btw [question]` | Open a side conversation in a modal, seeded with recent main-session context. Starts conversation-only; type `/mode` inside to toggle full permission (read and edit the workspace) without losing the thread. |
|
|
80
79
|
| `/bro advisor` | Quick notice of whether the executor's `bro_advisor` tool is available right now, pointing at `/bro config`, `/bro advisor-steer`, and `/bro doctor`. |
|
|
81
80
|
| `/bro advisor-steer` | View, edit, save, or clear the one persistent steering brief the advisor always sees. |
|
|
@@ -128,6 +127,44 @@ selection may be unavailable or visually extend outside the modal depending on
|
|
|
128
127
|
your terminal mode; press **C** to copy the complete explanation reliably.
|
|
129
128
|
|
|
130
129
|
|
|
130
|
+
## Guided Review
|
|
131
|
+
|
|
132
|
+
**Open a PR → read and investigate → leave when satisfied.** Bro automatically
|
|
133
|
+
prepares an explanation and quality assessment with captured code, ranked
|
|
134
|
+
findings and suggested fixes/checks. Asking private questions is optional.
|
|
135
|
+
Nothing is automatically applied or posted to GitHub.
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
/bro guided-review https://github.com/OWNER/REPO/pull/123
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Local Pi interactive mode, Git, authenticated `gh`, and Claude or Muse configured for review
|
|
142
|
+
are required. A new review makes a model call using that backend's account.
|
|
143
|
+
Opening the same PR restores its saved review without another model call.
|
|
144
|
+
Bare `/bro guided-review` lists saved reviews (`resume` is an alias).
|
|
145
|
+
|
|
146
|
+
Contents holds Overview, topics, Findings, Coverage, Changed files and Your
|
|
147
|
+
questions. Enter opens an item or focuses contextual actions; Enter on a
|
|
148
|
+
highlighted action activates it. Tab changes focus. Esc goes back, then closes
|
|
149
|
+
from Contents. Your work is saved automatically; **Close** leaves normally,
|
|
150
|
+
and **Stop and close** cancels active work before saving and leaving.
|
|
151
|
+
|
|
152
|
+
Optional conveniences:
|
|
153
|
+
- **Copy finding** copies its text to the system clipboard; nothing is submitted.
|
|
154
|
+
- **Regenerate guide** makes a confirmed model call about the **same captured
|
|
155
|
+
revision**. Success replaces the guide while retaining original private
|
|
156
|
+
discussion context; failure keeps it. It does not fetch later commits.
|
|
157
|
+
|
|
158
|
+
Reopening also keeps the captured revision, even after the author pushes.
|
|
159
|
+
Findings are model suggestions, not approval; citations show real locations,
|
|
160
|
+
not proof of the conclusion. Review uses Claude or Muse file-only inspection: shell, writes and web tools are disabled. Agy/Grok/Codex review requests fail clearly; choose a review override in `/bro config`. Saved reviews remain readable with any selection. Repository text can still mislead the model; tool restrictions are not proof of its conclusions.
|
|
161
|
+
Use `pi --tui-mode fullscreen` for pane-local wheel scrolling.
|
|
162
|
+
|
|
163
|
+
See the [Guided Review guide](docs/guided-review.md) for controls, calls and
|
|
164
|
+
costs, source freshness, access/privacy, local storage, recovery and current
|
|
165
|
+
scope. Notes, examination tracking, branch/update review and feedback delivery
|
|
166
|
+
are not implemented.
|
|
167
|
+
|
|
131
168
|
## Bro btw (side conversation)
|
|
132
169
|
|
|
133
170
|
`/bro btw` opens a separate multi-turn conversation in a modal, so you can ask
|
|
@@ -201,18 +238,21 @@ presence, and backend compatibility (for Agy, a minimum CLI version with an
|
|
|
201
238
|
return advice, leaving edits to the executor, but that boundary is a
|
|
202
239
|
behavioral prompt instruction rather than an enforced sandbox constraint, so
|
|
203
240
|
treat its findings as advice to verify, not a guaranteed hands-off review.
|
|
204
|
-
- **Steering**:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
241
|
+
- **Steering**: The advisor reads standing priorities from `~/.pi/agent/bro-advisor.md`
|
|
242
|
+
and session-specific priorities from `/bro advisor-steer`.
|
|
243
|
+
`/bro advisor-steer` opens an editor for one session steering brief — e.g. "quick
|
|
244
|
+
prototype; keep A and B careful, everything else minimal". Session steering takes
|
|
245
|
+
precedence over durable standing defaults where they conflict.
|
|
246
|
+
**Ctrl+S** saves and keeps the editor open; **Enter** or **Shift+Enter** inserts
|
|
247
|
+
a newline; **Ctrl+K** clears both the saved session brief and draft while staying open;
|
|
248
|
+
**Ctrl+C** copies the entire current draft, including unsaved edits; and **Esc**
|
|
249
|
+
closes without saving unsaved edits. Neither steering source is **ever added to Pi's
|
|
250
|
+
conversation or sent to the main model** — the advisor is the only thing that reads them.
|
|
251
|
+
- **Persistence**: standing defaults persist across all sessions in `~/.pi/agent/bro-advisor.md`.
|
|
252
|
+
The session steering brief persists with the Pi session as custom extension data in the
|
|
253
|
+
session file (`bro-advisor-steering`) and is restored on resume or reload. Forking a
|
|
254
|
+
session inherits session steering; edits made after the fork are independent of the
|
|
255
|
+
original branch. Clearing session steering leaves durable steering intact.
|
|
216
256
|
- **Retries**: on an invocation failure (not a completed answer — "I need
|
|
217
257
|
more evidence" is a normal result, not a failure), Bro retries with the
|
|
218
258
|
identical snapshot, steering, and question: once after 5 seconds, once more
|
|
@@ -717,7 +757,7 @@ succeed.
|
|
|
717
757
|
## Settings
|
|
718
758
|
|
|
719
759
|
Use `/bro config` to review or change the shared default backend/model/effort
|
|
720
|
-
and any per-capability (explain/show/btw/advisor) overrides, the explanation
|
|
760
|
+
and any per-capability (explain/show/btw/advisor/review) overrides, the explanation
|
|
721
761
|
mode, and the default show turn count. Changes save immediately. Esc inside a
|
|
722
762
|
picker cancels that pick; Esc on the settings screen closes it, keeping
|
|
723
763
|
whatever was already saved. A failed save (for example, a read-only file) is
|
|
@@ -770,13 +810,13 @@ credentials, and does not copy credentials or rewrite backend configuration.
|
|
|
770
810
|
Each backend and your model provider may retain sessions, logs, and request
|
|
771
811
|
data under their own settings and policies.
|
|
772
812
|
|
|
773
|
-
| | Explain / show | BTW conversation-only | BTW full permission | Advisor |
|
|
774
|
-
| --- | --- | --- | --- | --- |
|
|
775
|
-
| **Agy** | Temporary directory, Agy sandbox | Temporary directory, Agy sandbox | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed |
|
|
776
|
-
| **Claude Code** | Scratch directory, tools/MCP/skills disabled, no session persistence | Workspace, tools disabled | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed |
|
|
777
|
-
| **Grok** | Temporary directory, **prompt instruction only** | Workspace, **prompt instruction only** | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed |
|
|
778
|
-
| **Codex** | Scratch directory, read-only sandbox, ephemeral session | Workspace, read-only sandbox | Workspace, approvals and sandbox bypassed | Fresh workspace process, approvals and sandbox bypassed |
|
|
779
|
-
| **Muse** | Scratch directory, approval/write/shell disabled, no session log | Workspace, approval/write/shell disabled | Workspace, permissions bypassed (`--yolo`) | Fresh workspace process, permissions bypassed (`--yolo`) |
|
|
813
|
+
| | Explain / show | BTW conversation-only | BTW full permission | Advisor | Guided Review |
|
|
814
|
+
| --- | --- | --- | --- | --- | --- |
|
|
815
|
+
| **Agy** | Temporary directory, Agy sandbox | Temporary directory, Agy sandbox | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed | Unavailable; choose Claude/Muse |
|
|
816
|
+
| **Claude Code** | Scratch directory, tools/MCP/skills disabled, no session persistence | Workspace, tools disabled | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed | Restricted Read/Grep/Glob only; confined checkout, no persistence |
|
|
817
|
+
| **Grok** | Temporary directory, **prompt instruction only** | Workspace, **prompt instruction only** | Workspace, permissions bypassed | Fresh workspace process, permissions bypassed | Unavailable; choose Claude/Muse |
|
|
818
|
+
| **Codex** | Scratch directory, read-only sandbox, ephemeral session | Workspace, read-only sandbox | Workspace, approvals and sandbox bypassed | Fresh workspace process, approvals and sandbox bypassed | Unavailable; command execution is not allowed for review |
|
|
819
|
+
| **Muse** | Scratch directory, approval/write/shell disabled, no session log | Workspace, approval/write/shell disabled | Workspace, permissions bypassed (`--yolo`) | Fresh workspace process, permissions bypassed (`--yolo`) | Captured checkout; writes/shell/web disabled, no session log |
|
|
780
820
|
|
|
781
821
|
- Grok always runs with its sandbox off and permissions bypassed; its tools,
|
|
782
822
|
hooks, skills, plugins, and MCP may remain available. "Answer only from the
|
|
@@ -805,7 +845,7 @@ data under their own settings and policies.
|
|
|
805
845
|
### Configuration precedence
|
|
806
846
|
|
|
807
847
|
When resolving backend, model, and reasoning effort:
|
|
808
|
-
1. **Per-capability override**: `overrides.<capability>` (`explain`, `show`, `btw`, or `
|
|
848
|
+
1. **Per-capability override**: `overrides.<capability>` (`explain`, `show`, `btw`, `advisor`, or `review`) pins that capability's complete selection.
|
|
809
849
|
2. **Shared default**: otherwise the capability inherits `default` (root `model`/`effort` in older flat files).
|
|
810
850
|
3. **Agy catalog normalization**: for Agy selections, Bro maps suffixed variant IDs and handles fixed-effort models.
|
|
811
851
|
4. **Initial file creation only**: `PI_BRO_MODEL` has no effect once the settings file exists.
|
|
@@ -826,7 +866,7 @@ When shaping an answer, each part has one owner:
|
|
|
826
866
|
## Preferences
|
|
827
867
|
|
|
828
868
|
Tell Bro about yourself and how you like answers. Bro adds what you write to
|
|
829
|
-
every explain, `/bro show`,
|
|
869
|
+
every explain, `/bro show`, `/bro btw`, and Guided Review prompt as a labelled section,
|
|
830
870
|
alongside its own instructions. It never replaces them: modes, **M**, and the
|
|
831
871
|
source rules keep working. The advisor never receives your preferences; use
|
|
832
872
|
`/bro advisor-steer` for it.
|
|
@@ -860,11 +900,11 @@ that is set):
|
|
|
860
900
|
- **What preferences can change**: wording, tone, technical depth, length in
|
|
861
901
|
BTW, and the answer language. If you name a language, code, commands, paths,
|
|
862
902
|
names, and numbers still stay exactly as written. In `/bro show`,
|
|
863
|
-
preferences only change wording and language. A per-run choice (the mode,
|
|
864
|
-
**M**, a Show steering query, or what a BTW question asks for) wins over a
|
|
903
|
+
preferences only change wording and language. Guided Review also receives preferences for explanation/answer wording; they do not override its evidence or review rules. A per-run choice (the mode,
|
|
904
|
+
**M**, a Show steering query, or what a BTW/review question asks for) wins over a
|
|
865
905
|
standing preference.
|
|
866
906
|
- **Limit**: 4,000 characters, because the text goes with every request. A
|
|
867
|
-
longer file stops explain, Show, and
|
|
907
|
+
longer file stops explain, Show, BTW, and Guided Review with an error until you trim it;
|
|
868
908
|
Bro never cuts it silently. `/bro doctor` reports the problem, and the
|
|
869
909
|
editor still opens the file so you can fix it.
|
|
870
910
|
- **BTW threads**: when your preferences change, the next side question starts
|
|
@@ -886,8 +926,9 @@ read. Move what you want to keep into `/bro preferences`, and choose
|
|
|
886
926
|
seeded main-session conversation text (plus earlier turns when a native
|
|
887
927
|
session is reseeded) to the selected backend. In full permission mode the
|
|
888
928
|
side agent can additionally read and edit the workspace.
|
|
929
|
+
- **Guided Review requests**: PR identity/description, captured diff, relevant private discussion and preferences go to the selected backend, which can inspect captured files using Claude/Muse read-only tools. Main-session context is not attached. Review shell/write/web tools are disabled; Claude file tools are confined with `--restricted`, Muse uses workspace-scoped inspection. Prompt injection can still corrupt conclusions or reveal captured content to the provider; this is not a general confidentiality guarantee. See [Guided Review access and storage](docs/guided-review.md#access-privacy-and-storage).
|
|
889
930
|
- **Preferences**: `bro-preferences.md` is sent with every explain, Show,
|
|
890
|
-
and
|
|
931
|
+
BTW, and Guided Review request to the selected backend. It is never sent to the advisor or
|
|
891
932
|
to Pi's main model.
|
|
892
933
|
- **Advisor requests**: `bro_advisor` sends the executor agent's system
|
|
893
934
|
instructions, active tool list (excluding `bro_advisor`), ordered
|
|
@@ -904,10 +945,12 @@ read. Move what you want to keep into `/bro preferences`, and choose
|
|
|
904
945
|
steering brief to Pi's conversation history or main-agent context. BTW text
|
|
905
946
|
reaches the main editor only through `/insert` or `/insert-all`, and advisor
|
|
906
947
|
results appear as normal tool results in the executor's transcript.
|
|
948
|
+
- **Saved reviews**: Guided Review records, questions, drafts and captured Git source persist under the host's `getAgentDir()/bro-reviews/`. No automatic cleanup or sync is provided; see [storage and removal](docs/guided-review.md#access-privacy-and-storage).
|
|
907
949
|
- **Memory**: the latest explanation (for `/bro open`) and the BTW thread live
|
|
908
950
|
only in process memory and clear when you switch Pi sessions, reload
|
|
909
951
|
extensions, or quit Pi. Backend-native sessions can persist independently.
|
|
910
|
-
|
|
952
|
+
Standing advisor priorities are stored in `~/.pi/agent/bro-advisor.md`, and
|
|
953
|
+
the session steering brief is stored as session-scoped extension data
|
|
911
954
|
(`bro-advisor-steering`) in the session file.
|
|
912
955
|
- **File safety**: `/bro file` reads only regular files whose resolved path is
|
|
913
956
|
inside Pi's current workspace, including after resolving symlinks. Bro's
|
|
@@ -925,7 +968,7 @@ read. Move what you want to keep into `/bro preferences`, and choose
|
|
|
925
968
|
Bro writes it to `/tmp/pi-bro-<uid>/bro-show-<hash>.html` with a restrictive
|
|
926
969
|
Content-Security-Policy, and opens it in your browser only when you press
|
|
927
970
|
**O**. **C** copies the full reply, including the HTML.
|
|
928
|
-
- **Clipboard**: **C**, `/copy`,
|
|
971
|
+
- **Clipboard**: **C**, `/copy`, `/copy-all`, and Guided Review's Copy finding copy text to your system
|
|
929
972
|
clipboard, where your operating system or clipboard manager may retain it.
|
|
930
973
|
|
|
931
974
|
## Troubleshooting and current limits
|
|
@@ -953,8 +996,8 @@ tool before giving it to Bro.
|
|
|
953
996
|
- HTML diagrams open in your default browser; pressing **O** on a remote or
|
|
954
997
|
headless session with no display reports the failure instead of opening
|
|
955
998
|
anything.
|
|
956
|
-
-
|
|
957
|
-
|
|
999
|
+
- Explain keeps only the latest result in memory and does not export directly to files. Guided Review separately persists review records and captured source.
|
|
1000
|
+
- Guided Review is local-Pi interactive only, keeps its captured revision on reopen, and does not publish, review a branch or fetch later PR changes. See [review recovery and limits](docs/guided-review.md#if-something-goes-wrong).
|
|
958
1001
|
- Bro temporarily captures mouse input while its modal is open so mouse-wheel
|
|
959
1002
|
and trackpad scrolling work in regular and fullscreen modes. Native mouse
|
|
960
1003
|
selection may be unavailable or visually extend outside the Bro window;
|
|
@@ -968,7 +1011,7 @@ npm test
|
|
|
968
1011
|
pi --tui-mode fullscreen -e ./bro.ts
|
|
969
1012
|
```
|
|
970
1013
|
|
|
971
|
-
`npm test` uses fake `agy`, `claude`, and `
|
|
1014
|
+
`npm test` uses fake `agy`, `claude`, `grok`, `codex`, and `muse` executables and never calls an
|
|
972
1015
|
external model. See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the code
|
|
973
1016
|
map and invariants, [docs/TESTING.md](docs/TESTING.md) for the manual
|
|
974
1017
|
end-to-end checklist, and [docs/README.md](docs/README.md) for the docs index.
|
package/backend.ts
CHANGED
|
@@ -3,14 +3,17 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
|
-
// Shared internal execution boundary for
|
|
7
|
-
// This implements docs/plans/2026-09-22-shared-backend-design.md
|
|
8
|
-
// Claude Code
|
|
9
|
-
// single-attempt cleanup. Feature code
|
|
10
|
-
// settings.
|
|
6
|
+
// Shared internal execution boundary for explain, show, btw, advisor, and review.
|
|
7
|
+
// This implements docs/plans/2026-09-22-shared-backend-design.md across all supported backends
|
|
8
|
+
// (Agy, Claude Code, Grok, Codex, Muse): it owns CLI selection, process invocation,
|
|
9
|
+
// progress/outcome normalization, continuation, and single-attempt cleanup. Feature code
|
|
10
|
+
// keeps retries, UI, source/session capture and settings.
|
|
11
11
|
|
|
12
|
-
export type BackendFeature = "explain" | "show" | "btw" | "advisor";
|
|
12
|
+
export type BackendFeature = "explain" | "show" | "btw" | "advisor" | "review";
|
|
13
13
|
export type BackendAccess = "restricted" | "workspace-full";
|
|
14
|
+
export function reviewBackendError(backend: string): string | undefined {
|
|
15
|
+
if (backend !== "claude" && backend !== "muse") return "Guided Review requires Claude or Muse for file-only inspection. Set the review backend in /bro config; saved reviews remain readable.";
|
|
16
|
+
}
|
|
14
17
|
export type AgySelection = { model: string; effort?: "low" | "medium" | "high" };
|
|
15
18
|
export const CLAUDE_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const;
|
|
16
19
|
export const GROK_EFFORTS = ["low", "medium", "high", "xhigh"] as const;
|
|
@@ -90,8 +93,8 @@ function unexpectedSignalMessage(exitSignal: NodeJS.Signals | null, cli = "Agy")
|
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
// Sends to the whole POSIX process group when possible so a misbehaving grandchild dies too, not
|
|
93
|
-
// just the immediate
|
|
94
|
-
function
|
|
96
|
+
// just the immediate child process -- child.kill() alone only ever reaches the immediate child.
|
|
97
|
+
function killProcessGroup(child: ChildProcess, signalName: NodeJS.Signals): void {
|
|
95
98
|
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
96
99
|
try {
|
|
97
100
|
process.kill(-child.pid, signalName);
|
|
@@ -104,7 +107,7 @@ function killAgyGroup(child: ChildProcess, signalName: NodeJS.Signals): void {
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
// The three causes that stop an in-flight attempt: user cancellation, the host-imposed deadline,
|
|
107
|
-
// and a protocol failure (malformed/inconsistent
|
|
110
|
+
// and a protocol failure (malformed/inconsistent backend output). Exactly one is latched -- the first
|
|
108
111
|
// to occur -- and it is never relabeled by a later signal (e.g. a cancel arriving after a deadline
|
|
109
112
|
// already fired stays a timeout, not a cancellation).
|
|
110
113
|
type StopCause = "cancelled" | "timeout" | "protocol";
|
|
@@ -139,9 +142,9 @@ export function beginAttempt(child: ChildProcess, signal: AbortSignal, deadlineM
|
|
|
139
142
|
if (cause) return; // latched: the first stop cause wins
|
|
140
143
|
cause = next;
|
|
141
144
|
if (isClosed) return;
|
|
142
|
-
|
|
145
|
+
killProcessGroup(child, "SIGTERM");
|
|
143
146
|
killTimer = setTimeout(() => {
|
|
144
|
-
|
|
147
|
+
killProcessGroup(child, "SIGKILL");
|
|
145
148
|
// Detached descendants (or Windows grandchildren) may retain inherited pipes.
|
|
146
149
|
// Stop waiting on those pipes after escalation; never promote this stop to success.
|
|
147
150
|
child.stdin?.destroy();
|
|
@@ -229,12 +232,12 @@ async function executeArgvPrint(
|
|
|
229
232
|
// stdin protocol rather than risking Linux's per-argument byte limit.
|
|
230
233
|
const stdinPrompt = Buffer.byteLength(request.prompt, "utf8") >= 120_000;
|
|
231
234
|
const isBtw = request.feature === "btw";
|
|
232
|
-
const full =
|
|
233
|
-
const deadlineMs = deadlineMsOverride ?? (
|
|
235
|
+
const full = request.access === "workspace-full";
|
|
236
|
+
const deadlineMs = deadlineMsOverride ?? (full ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
234
237
|
const printTimeout = full ? "10m" : "2m";
|
|
235
|
-
const action = isBtw ? "answer the side question" : "simplify the response";
|
|
236
|
-
const timeoutVerb = isBtw ? "during the side conversation" : "while simplifying the response";
|
|
237
|
-
const emptyTextMessage = isBtw ? "Agy returned no answer for the side question." : "Agy returned no final explanation.";
|
|
238
|
+
const action = request.feature === "review" ? "complete the guided review request" : isBtw ? "answer the side question" : "simplify the response";
|
|
239
|
+
const timeoutVerb = request.feature === "review" ? "during the guided review request" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
240
|
+
const emptyTextMessage = request.feature === "review" ? "Agy returned no response for Guided Review." : isBtw ? "Agy returned no answer for the side question." : "Agy returned no final explanation.";
|
|
238
241
|
|
|
239
242
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
240
243
|
|
|
@@ -257,7 +260,7 @@ async function executeArgvPrint(
|
|
|
257
260
|
...(selection.effort ? ["--effort", selection.effort] : []),
|
|
258
261
|
"--print-timeout",
|
|
259
262
|
printTimeout,
|
|
260
|
-
...(request.continuation ? ["--conversation", request.continuation.id] : []),
|
|
263
|
+
...(isBtw && request.continuation ? ["--conversation", request.continuation.id] : []),
|
|
261
264
|
...(stdinPrompt ? ["--input-format", "stream-json"] : ["--print", request.prompt]),
|
|
262
265
|
],
|
|
263
266
|
{
|
|
@@ -565,12 +568,13 @@ async function executeClaude(
|
|
|
565
568
|
): Promise<BackendOutcome> {
|
|
566
569
|
const isAdvisor = request.feature === "advisor";
|
|
567
570
|
const isBtw = request.feature === "btw";
|
|
571
|
+
const inspect = request.feature === "review";
|
|
568
572
|
const full = request.access === "workspace-full";
|
|
569
|
-
const deadlineMs = deadlineMsOverride ?? (full ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
570
|
-
const action = isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
573
|
+
const deadlineMs = deadlineMsOverride ?? (full || inspect ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
574
|
+
const action = request.feature === "review" ? "complete the guided review request" : isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
571
575
|
|
|
572
576
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
573
|
-
const runDirectory =
|
|
577
|
+
const runDirectory = full || isBtw || inspect ? undefined : await mkdtemp(join(tmpdir(), "pi-bro-"));
|
|
574
578
|
if (signal.aborted) {
|
|
575
579
|
if (runDirectory) await rm(runDirectory, { recursive: true, force: true });
|
|
576
580
|
return { status: "cancelled", message: "Canceled." };
|
|
@@ -588,7 +592,9 @@ async function executeClaude(
|
|
|
588
592
|
"stream-json",
|
|
589
593
|
"--verbose",
|
|
590
594
|
"--include-partial-messages",
|
|
591
|
-
...(
|
|
595
|
+
...(inspect
|
|
596
|
+
? ["--restricted", "--tools", "Read,Grep,Glob", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--permission-mode", "dontAsk"]
|
|
597
|
+
: full
|
|
592
598
|
? ["--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--dangerously-skip-permissions"]
|
|
593
599
|
: ["--tools", "", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--permission-mode", "dontAsk"]),
|
|
594
600
|
...(isBtw && request.continuation ? ["--resume", request.continuation.id] : []),
|
|
@@ -685,7 +691,7 @@ async function executeClaude(
|
|
|
685
691
|
const cause = attempt.causeOf();
|
|
686
692
|
if (cause === "cancelled") return { status: "cancelled", message: "Canceled.", partialText };
|
|
687
693
|
if (cause === "timeout") {
|
|
688
|
-
const during = isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
694
|
+
const during = request.feature === "review" ? "during the guided review request" : isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
689
695
|
return { status: "timeout", message: `Claude timed out ${during}. Run \`/bro doctor\` for setup help.`, partialText };
|
|
690
696
|
}
|
|
691
697
|
if (protocolError) return { status: "failure", message: withDoctor(protocolError), partialText };
|
|
@@ -706,7 +712,7 @@ async function executeClaude(
|
|
|
706
712
|
return { status: "failure", message: withDoctor(`Claude exited without a result event${stderr.trim() ? `: ${stderr.trim()}` : "."}`), partialText };
|
|
707
713
|
}
|
|
708
714
|
const text = final.trim();
|
|
709
|
-
if (!text) return { status: "failure", message: withDoctor(isBtw ? "Claude returned no answer for the side question." : "Claude returned no final answer."), partialText };
|
|
715
|
+
if (!text) return { status: "failure", message: withDoctor(request.feature === "review" ? "Claude returned no response for Guided Review." : isBtw ? "Claude returned no answer for the side question." : "Claude returned no final answer."), partialText };
|
|
710
716
|
if (!isBtw) return { status: "success", text };
|
|
711
717
|
// btw continuation: the session id must be reported, consistent, and (on resume) unchanged.
|
|
712
718
|
const sessionId = resultSessionId ?? initSessionId;
|
|
@@ -767,10 +773,10 @@ async function executeGrok(
|
|
|
767
773
|
): Promise<BackendOutcome> {
|
|
768
774
|
const isAdvisor = request.feature === "advisor";
|
|
769
775
|
const isBtw = request.feature === "btw";
|
|
770
|
-
const deadlineMs = deadlineMsOverride ?? (
|
|
771
|
-
const action = isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
772
|
-
const timeoutVerb = isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
773
|
-
const emptyTextMessage = isAdvisor ? "Grok returned no advice." : isBtw ? "Grok returned no answer for the side question." : "Grok returned no final explanation.";
|
|
776
|
+
const deadlineMs = deadlineMsOverride ?? (request.access === "workspace-full" ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
777
|
+
const action = request.feature === "review" ? "complete the guided review request" : isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
778
|
+
const timeoutVerb = request.feature === "review" ? "during the guided review request" : isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
779
|
+
const emptyTextMessage = request.feature === "review" ? "Grok returned no response for Guided Review." : isAdvisor ? "Grok returned no advice." : isBtw ? "Grok returned no answer for the side question." : "Grok returned no final explanation.";
|
|
774
780
|
const prompt = request.access === "restricted" ? `${GROK_RESTRICTED_PREFIX}\n\n${request.prompt}` : request.prompt;
|
|
775
781
|
|
|
776
782
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
@@ -780,7 +786,7 @@ async function executeGrok(
|
|
|
780
786
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
781
787
|
const promptFile = join(promptDirectory, "prompt.txt");
|
|
782
788
|
await writeFile(promptFile, prompt, { encoding: "utf8", mode: 0o600 });
|
|
783
|
-
if (
|
|
789
|
+
if (request.access !== "workspace-full" && !isBtw) runDirectory = await mkdtemp(join(tmpdir(), "pi-bro-"));
|
|
784
790
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
785
791
|
|
|
786
792
|
const child = spawn(
|
|
@@ -1003,12 +1009,12 @@ async function executeCodex(
|
|
|
1003
1009
|
const isBtw = request.feature === "btw";
|
|
1004
1010
|
const full = request.access === "workspace-full";
|
|
1005
1011
|
const deadlineMs = deadlineMsOverride ?? (full ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
1006
|
-
const action = isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
1007
|
-
const timeoutVerb = isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
1008
|
-
const emptyTextMessage = isAdvisor ? "Codex returned no advice." : isBtw ? "Codex returned no answer for the side question." : "Codex returned no final explanation.";
|
|
1012
|
+
const action = request.feature === "review" ? "complete the guided review request" : isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
1013
|
+
const timeoutVerb = request.feature === "review" ? "during the guided review request" : isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
1014
|
+
const emptyTextMessage = request.feature === "review" ? "Codex returned no response for Guided Review." : isAdvisor ? "Codex returned no advice." : isBtw ? "Codex returned no answer for the side question." : "Codex returned no final explanation.";
|
|
1009
1015
|
|
|
1010
1016
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
1011
|
-
const runDirectory =
|
|
1017
|
+
const runDirectory = full || isBtw ? undefined : await mkdtemp(join(tmpdir(), "pi-bro-"));
|
|
1012
1018
|
if (signal.aborted) {
|
|
1013
1019
|
if (runDirectory) await rm(runDirectory, { recursive: true, force: true });
|
|
1014
1020
|
return { status: "cancelled", message: "Canceled." };
|
|
@@ -1172,11 +1178,12 @@ async function executeMuse(
|
|
|
1172
1178
|
): Promise<BackendOutcome> {
|
|
1173
1179
|
const isAdvisor = request.feature === "advisor";
|
|
1174
1180
|
const isBtw = request.feature === "btw";
|
|
1181
|
+
const inspect = request.feature === "review";
|
|
1175
1182
|
const full = request.access === "workspace-full";
|
|
1176
|
-
const deadlineMs = deadlineMsOverride ?? (full ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
1177
|
-
const action = isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
1178
|
-
const timeoutVerb = isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
1179
|
-
const emptyTextMessage = isAdvisor ? "Muse returned no advice." : isBtw ? "Muse returned no answer for the side question." : "Muse returned no final explanation.";
|
|
1183
|
+
const deadlineMs = deadlineMsOverride ?? (full || inspect ? 610_000 : isBtw ? 130_000 : 125_000);
|
|
1184
|
+
const action = request.feature === "review" ? "complete the guided review request" : isAdvisor ? "complete the advisor consultation" : isBtw ? "answer the side question" : "simplify the response";
|
|
1185
|
+
const timeoutVerb = request.feature === "review" ? "during the guided review request" : isAdvisor ? "during the advisor consultation" : isBtw ? "during the side conversation" : "while simplifying the response";
|
|
1186
|
+
const emptyTextMessage = request.feature === "review" ? "Muse returned no response for Guided Review." : isAdvisor ? "Muse returned no advice." : isBtw ? "Muse returned no answer for the side question." : "Muse returned no final explanation.";
|
|
1180
1187
|
|
|
1181
1188
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
1182
1189
|
const promptDirectory = await mkdtemp(join(tmpdir(), "pi-bro-muse-"));
|
|
@@ -1186,7 +1193,7 @@ async function executeMuse(
|
|
|
1186
1193
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
1187
1194
|
const promptFile = join(promptDirectory, "prompt.txt");
|
|
1188
1195
|
await writeFile(promptFile, request.prompt, { encoding: "utf8", mode: 0o600 });
|
|
1189
|
-
if (!
|
|
1196
|
+
if (!full && !isBtw && !inspect) runDirectory = await mkdtemp(join(tmpdir(), "pi-bro-"));
|
|
1190
1197
|
if (signal.aborted) return { status: "cancelled", message: "Canceled." };
|
|
1191
1198
|
const workspace = runDirectory ?? request.cwd!;
|
|
1192
1199
|
|
|
@@ -1197,6 +1204,7 @@ async function executeMuse(
|
|
|
1197
1204
|
"--workspace", workspace,
|
|
1198
1205
|
...(!isBtw ? ["--no-session-log"] : []),
|
|
1199
1206
|
...(full ? ["--yolo"] : ["--disable-approval", "--disable-write", "--disable-shell"]),
|
|
1207
|
+
...(inspect ? ["--disable-web-tools", "--no-foreign-personal-context"] : []),
|
|
1200
1208
|
...(isBtw && request.continuation ? ["--session-id", request.continuation.id] : []),
|
|
1201
1209
|
"--model", selection.model,
|
|
1202
1210
|
...(selection.effort ? ["--reasoning-effort", selection.effort] : []),
|
|
@@ -1341,7 +1349,7 @@ async function executeMuse(
|
|
|
1341
1349
|
}
|
|
1342
1350
|
}
|
|
1343
1351
|
|
|
1344
|
-
// Single-attempt executor shared by all
|
|
1352
|
+
// Single-attempt executor shared by all features. Never retries (retries are feature-owned,
|
|
1345
1353
|
// e.g. advisor's 3-attempt backoff in bro.ts); never spawns a pre-aborted request; on cancellation,
|
|
1346
1354
|
// host deadline, or a protocol failure, stops the whole POSIX process group (SIGTERM, then SIGKILL
|
|
1347
1355
|
// after a bounded grace period) before resolving. `options` is for offline tests only -- production
|
|
@@ -1356,10 +1364,15 @@ export async function execute(
|
|
|
1356
1364
|
if (
|
|
1357
1365
|
(request.access === "workspace-full" && !request.cwd?.trim()) ||
|
|
1358
1366
|
(request.feature === "advisor" && request.access !== "workspace-full") ||
|
|
1367
|
+
(request.feature === "review" && (request.access !== "restricted" || !request.cwd?.trim())) ||
|
|
1359
1368
|
((request.feature === "explain" || request.feature === "show") && request.access !== "restricted") ||
|
|
1360
1369
|
(request.feature !== "btw" && request.continuation)
|
|
1361
1370
|
) return { status: "failure", message: "Unsupported execution request: check feature access, workspace cwd and continuation." };
|
|
1362
1371
|
const backend = (selection as { backend?: unknown }).backend;
|
|
1372
|
+
if (request.feature === "review") {
|
|
1373
|
+
const error = reviewBackendError(String(backend ?? "agy"));
|
|
1374
|
+
if (error) return { status: "failure", message: error };
|
|
1375
|
+
}
|
|
1363
1376
|
// An explicit tag guard: a stale or corrupt runtime tag must fail, never fall through to Agy.
|
|
1364
1377
|
if (backend !== undefined && backend !== "agy" && backend !== "claude" && backend !== "grok" && backend !== "codex" && backend !== "muse") {
|
|
1365
1378
|
return { status: "failure", message: `Unknown backend ${JSON.stringify(backend)}: pick Agy, Claude, Grok, Codex or Muse in \`/bro config\`.` };
|
|
@@ -1367,7 +1380,7 @@ export async function execute(
|
|
|
1367
1380
|
const killEscalationMs = options?.killEscalationMs ?? DEFAULT_KILL_ESCALATION_MS;
|
|
1368
1381
|
if (selection.backend === "codex") {
|
|
1369
1382
|
// Codex btw always runs (and resumes) in the caller's workspace, even restricted.
|
|
1370
|
-
if (request.feature === "btw" && !request.cwd?.trim()) {
|
|
1383
|
+
if ((request.feature === "btw" || request.feature === "review") && !request.cwd?.trim()) {
|
|
1371
1384
|
return { status: "failure", message: "Unsupported execution request: check feature access, workspace cwd and continuation." };
|
|
1372
1385
|
}
|
|
1373
1386
|
if (typeof selection.model !== "string" || !selection.model.trim() || (selection.effort !== undefined && !CODEX_EFFORTS.includes(selection.effort))) {
|
|
@@ -1377,7 +1390,7 @@ export async function execute(
|
|
|
1377
1390
|
}
|
|
1378
1391
|
if (selection.backend === "muse") {
|
|
1379
1392
|
// Muse btw always runs (and resumes) in the caller's workspace, even restricted.
|
|
1380
|
-
if (request.feature === "btw" && !request.cwd?.trim()) {
|
|
1393
|
+
if ((request.feature === "btw" || request.feature === "review") && !request.cwd?.trim()) {
|
|
1381
1394
|
return { status: "failure", message: "Unsupported execution request: check feature access, workspace cwd and continuation." };
|
|
1382
1395
|
}
|
|
1383
1396
|
if (typeof selection.model !== "string" || !selection.model.trim() || (selection.effort !== undefined && !MUSE_EFFORTS.includes(selection.effort))) {
|
|
@@ -1387,7 +1400,7 @@ export async function execute(
|
|
|
1387
1400
|
}
|
|
1388
1401
|
if (selection.backend === "grok") {
|
|
1389
1402
|
// Grok btw always runs (and resumes) in the caller's workspace, even restricted.
|
|
1390
|
-
if (request.feature === "btw" && !request.cwd?.trim()) {
|
|
1403
|
+
if ((request.feature === "btw" || request.feature === "review") && !request.cwd?.trim()) {
|
|
1391
1404
|
return { status: "failure", message: "Unsupported execution request: check feature access, workspace cwd and continuation." };
|
|
1392
1405
|
}
|
|
1393
1406
|
if (typeof selection.model !== "string" || !selection.model.trim() || (selection.effort !== undefined && !GROK_EFFORTS.includes(selection.effort))) {
|
|
@@ -1397,7 +1410,7 @@ export async function execute(
|
|
|
1397
1410
|
}
|
|
1398
1411
|
if (selection.backend === "claude") {
|
|
1399
1412
|
// Claude btw always runs (and resumes) in the caller's workspace, even restricted.
|
|
1400
|
-
if (request.feature === "btw" && !request.cwd?.trim()) {
|
|
1413
|
+
if ((request.feature === "btw" || request.feature === "review") && !request.cwd?.trim()) {
|
|
1401
1414
|
return { status: "failure", message: "Unsupported execution request: check feature access, workspace cwd and continuation." };
|
|
1402
1415
|
}
|
|
1403
1416
|
if (!selection.model.trim() || (selection.effort !== undefined && !CLAUDE_EFFORTS.includes(selection.effort))) {
|