pi-bro 0.13.0 → 0.13.2
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 +12 -0
- package/README.md +3 -3
- package/bro.ts +55 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-bro are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.13.2] - 2026-09-19
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- In the `/bro btw` modal, your questions now render as a quoted **You** block and answers carry an explicit **Bro** label, with a horizontal rule between turns. The old `## you` heading was indistinguishable from headings inside Bro's answers.
|
|
10
|
+
|
|
11
|
+
## [0.13.1] - 2026-09-17
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Renamed `/bro btw` composer commands from `/send` and `/send all` to `/copy` and `/copy-all` (also accepts `/copy all`), with `/copy!` and `/copy-all!` force variants to replace existing main-editor drafts. Legacy `/send` commands remain supported as aliases.
|
|
16
|
+
|
|
5
17
|
## [0.13.0] - 2026-09-15
|
|
6
18
|
|
|
7
19
|
### Added
|
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ your terminal mode; press **C** to copy the complete explanation reliably.
|
|
|
108
108
|
`/bro btw` opens a separate multi-turn conversation in a modal, so you can ask
|
|
109
109
|
a quick side question while the main agent keeps working. It runs through Agy,
|
|
110
110
|
the same backend as the rest of Bro, and never adds anything to Pi's
|
|
111
|
-
conversation unless you explicitly
|
|
111
|
+
conversation unless you explicitly copy it into the editor.
|
|
112
112
|
|
|
113
113
|
- **Sandboxed by default**: the side conversation is read-only (no project
|
|
114
114
|
access). Add `--full` to let it read and edit the workspace.
|
|
@@ -119,8 +119,8 @@ conversation unless you explicitly send it back.
|
|
|
119
119
|
conversation text (40,000 characters max, with a truncation notice); the
|
|
120
120
|
side agent can also read the repo itself when running in `--full` mode.
|
|
121
121
|
- **In the modal**: type a question and press Enter (empty Enter re-asks the
|
|
122
|
-
last question). `/
|
|
123
|
-
without submitting (use `/
|
|
122
|
+
last question). `/copy` copies the latest answer into the main editor
|
|
123
|
+
without submitting (use `/copy!` to replace an existing draft); `/copy-all`
|
|
124
124
|
copies the full thread; `/retry` re-asks
|
|
125
125
|
the last question; `/clear` resets the thread; Esc closes. A visible
|
|
126
126
|
`full · edits repo` badge shows whenever `--full` mode is active.
|
package/bro.ts
CHANGED
|
@@ -1098,7 +1098,7 @@ Press **R** to simplify the captured source again. Run a new \`/bro text\`, \`/b
|
|
|
1098
1098
|
|
|
1099
1099
|
## Side conversation
|
|
1100
1100
|
|
|
1101
|
-
- \`/bro btw [--fresh] [--full] [question]\` — open a side conversation. Sandboxed (read-only) by default; add \`--full\` to let it read and edit the workspace, and \`--fresh\` to start without main-session context. Inside the side thread, type questions and press Enter (empty Enter re-asks); \`/
|
|
1101
|
+
- \`/bro btw [--fresh] [--full] [question]\` — open a side conversation. Sandboxed (read-only) by default; add \`--full\` to let it read and edit the workspace, and \`--fresh\` to start without main-session context. Inside the side thread, type questions and press Enter (empty Enter re-asks); \`/copy\` copies the latest answer to the main editor without submitting (use \`/copy!\` to replace an existing draft), \`/copy-all\` the full thread, \`/retry\` re-asks the last question, and \`/clear\` resets the thread. Esc closes.
|
|
1102
1102
|
|
|
1103
1103
|
## Current settings
|
|
1104
1104
|
|
|
@@ -1475,6 +1475,18 @@ export function resolveBtwThread(existing: BtwThread | undefined, parsed: { fres
|
|
|
1475
1475
|
return !existing || startFresh ? { turns: [], full: targetFull } : existing;
|
|
1476
1476
|
}
|
|
1477
1477
|
|
|
1478
|
+
export function formatBtwTranscript(turns: readonly BtwTurn[]): string {
|
|
1479
|
+
return turns
|
|
1480
|
+
.map((turn) => {
|
|
1481
|
+
const question = turn.question.split(/\r?\n/).map((line) => (line ? `> ${line}` : ">")).join("\n");
|
|
1482
|
+
// A turn aborted mid-stream can end inside an unclosed code fence, which would swallow the
|
|
1483
|
+
// separator and every later turn; close it so each turn renders as its own block.
|
|
1484
|
+
const answer = (turn.answer.match(/^```/gm)?.length ?? 0) % 2 === 1 ? `${turn.answer}\n\`\`\`` : turn.answer;
|
|
1485
|
+
return `> **You**\n>\n${question}\n\n**Bro**\n\n${answer}`;
|
|
1486
|
+
})
|
|
1487
|
+
.join("\n\n---\n\n");
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1478
1490
|
export function parseBtwAgyLine(line: string): { delta?: string; result?: string; conversationId?: string; error?: string } {
|
|
1479
1491
|
let event: AgyEvent;
|
|
1480
1492
|
try {
|
|
@@ -1496,6 +1508,37 @@ export function parseBtwAgyLine(line: string): { delta?: string; result?: string
|
|
|
1496
1508
|
return { conversationId };
|
|
1497
1509
|
}
|
|
1498
1510
|
|
|
1511
|
+
export type BtwComposerAction =
|
|
1512
|
+
| { kind: "clear" }
|
|
1513
|
+
| { kind: "retry" }
|
|
1514
|
+
| { kind: "copy"; all: boolean; force: boolean }
|
|
1515
|
+
| { kind: "question"; text: string };
|
|
1516
|
+
|
|
1517
|
+
export function parseBtwComposerCommand(value: string): BtwComposerAction {
|
|
1518
|
+
const command = value.trim();
|
|
1519
|
+
if (command === "/clear") return { kind: "clear" };
|
|
1520
|
+
if (command === "/retry" || command === "") return { kind: "retry" };
|
|
1521
|
+
if (
|
|
1522
|
+
command === "/copy" ||
|
|
1523
|
+
command === "/copy!" ||
|
|
1524
|
+
command === "/copy-all" ||
|
|
1525
|
+
command === "/copy-all!" ||
|
|
1526
|
+
command === "/copy all" ||
|
|
1527
|
+
command === "/copy all!" ||
|
|
1528
|
+
command === "/send" ||
|
|
1529
|
+
command === "/send!" ||
|
|
1530
|
+
command === "/send all" ||
|
|
1531
|
+
command === "/send all!"
|
|
1532
|
+
) {
|
|
1533
|
+
const all =
|
|
1534
|
+
command.startsWith("/copy-all") ||
|
|
1535
|
+
command.startsWith("/copy all") ||
|
|
1536
|
+
command.startsWith("/send all");
|
|
1537
|
+
return { kind: "copy", all, force: command.endsWith("!") };
|
|
1538
|
+
}
|
|
1539
|
+
return { kind: "question", text: command };
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1499
1542
|
async function runBtwTurn(
|
|
1500
1543
|
prompt: string,
|
|
1501
1544
|
selection: ReturnType<typeof agySelection>,
|
|
@@ -1725,7 +1768,7 @@ class BtwModal implements Focusable {
|
|
|
1725
1768
|
|
|
1726
1769
|
const controls = this.running
|
|
1727
1770
|
? this.theme.fg("dim", "Thinking… · Esc cancel")
|
|
1728
|
-
: this.theme.fg("dim", "Enter ask · Esc close · /
|
|
1771
|
+
: this.theme.fg("dim", "Enter ask · Esc close · /copy · /copy-all · /clear · /retry");
|
|
1729
1772
|
|
|
1730
1773
|
const lines = [
|
|
1731
1774
|
this.borderLine(innerWidth, "top"),
|
|
@@ -1760,7 +1803,7 @@ async function openBtwModal(
|
|
|
1760
1803
|
let closed = false;
|
|
1761
1804
|
let controller: AbortController | undefined;
|
|
1762
1805
|
|
|
1763
|
-
const transcript = () => thread.turns
|
|
1806
|
+
const transcript = () => formatBtwTranscript(thread.turns);
|
|
1764
1807
|
|
|
1765
1808
|
const close = () => {
|
|
1766
1809
|
if (closed) return;
|
|
@@ -1845,35 +1888,35 @@ async function openBtwModal(
|
|
|
1845
1888
|
const handoff = (all: boolean, force: boolean) => {
|
|
1846
1889
|
const text = all ? transcript() : (thread.turns.at(-1)?.answer ?? "");
|
|
1847
1890
|
if (!text.trim()) {
|
|
1848
|
-
modal.setNotice("Nothing to
|
|
1891
|
+
modal.setNotice("Nothing to copy yet.");
|
|
1849
1892
|
return;
|
|
1850
1893
|
}
|
|
1851
1894
|
if (ctx.ui.getEditorText().trim() && !force) {
|
|
1852
|
-
modal.setNotice("Main editor has a draft. Use /
|
|
1895
|
+
modal.setNotice("Main editor has a draft. Use /copy! (or /copy-all!) to replace it.");
|
|
1853
1896
|
return;
|
|
1854
1897
|
}
|
|
1855
1898
|
ctx.ui.setEditorText(text);
|
|
1856
|
-
modal.setNotice(all ? "
|
|
1899
|
+
modal.setNotice(all ? "Copied the full thread to the editor." : "Copied the latest answer to the editor.");
|
|
1857
1900
|
};
|
|
1858
1901
|
|
|
1859
1902
|
function submit(value: string): void {
|
|
1860
|
-
const
|
|
1861
|
-
if (
|
|
1903
|
+
const action = parseBtwComposerCommand(value);
|
|
1904
|
+
if (action.kind === "clear") {
|
|
1862
1905
|
modal.clearComposer();
|
|
1863
1906
|
clear();
|
|
1864
1907
|
return;
|
|
1865
1908
|
}
|
|
1866
|
-
if (
|
|
1909
|
+
if (action.kind === "copy") {
|
|
1867
1910
|
modal.clearComposer();
|
|
1868
|
-
handoff(
|
|
1911
|
+
handoff(action.all, action.force);
|
|
1869
1912
|
return;
|
|
1870
1913
|
}
|
|
1871
|
-
if (
|
|
1914
|
+
if (action.kind === "retry") {
|
|
1872
1915
|
modal.clearComposer();
|
|
1873
1916
|
retry();
|
|
1874
1917
|
return;
|
|
1875
1918
|
}
|
|
1876
|
-
void runTurn(
|
|
1919
|
+
void runTurn(action.text);
|
|
1877
1920
|
}
|
|
1878
1921
|
|
|
1879
1922
|
modal.setFull(thread.full);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bro",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "An Earendil Pi extension that explains pasted text, assistant responses, local documents, public webpages, and recent session turns (as shapes) in a context-isolated window, and opens a sandboxed side conversation with /bro btw.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|