pi-bro 0.19.1 → 0.19.3
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 +13 -0
- package/backend.ts +15 -6
- package/bro.ts +58 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-bro are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.19.3] - 2026-09-30
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Send large Agy explain, Show, and BTW prompts over stdin instead of exceeding argument-size limits. Older Agy versions report an upgrade hint rather than silently falling back (#73).
|
|
10
|
+
- Retain newest turns and message tails when bounding Show and BTW context, preserving JSON message framing (#73).
|
|
11
|
+
|
|
12
|
+
## [0.19.2] - 2026-09-30
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Preserve Show's diagram controls, captured source, and steering when reopening and rerunning a result. Recreate missing temporary diagrams without a model call and retain the previous diagram after a failed rerun (#71).
|
|
17
|
+
|
|
5
18
|
## [0.19.1] - 2026-09-30
|
|
6
19
|
|
|
7
20
|
### Fixed
|
package/backend.ts
CHANGED
|
@@ -211,8 +211,8 @@ export function parseBtwAgyLine(line: string): { delta?: string; result?: string
|
|
|
211
211
|
return { conversationId };
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
//
|
|
215
|
-
//
|
|
214
|
+
// Explain/show and BTW use argv for small prompts and stdin NDJSON for large
|
|
215
|
+
// prompts; sandbox, cwd, timeout, and continuation semantics stay unchanged.
|
|
216
216
|
async function executeArgvPrint(
|
|
217
217
|
request: BackendRequest,
|
|
218
218
|
selection: AgySelection,
|
|
@@ -221,6 +221,9 @@ async function executeArgvPrint(
|
|
|
221
221
|
killEscalationMs: number,
|
|
222
222
|
deadlineMsOverride: number | undefined,
|
|
223
223
|
): Promise<BackendOutcome> {
|
|
224
|
+
// Keep legacy CLI compatibility for small prompts; large inputs use the advisor's
|
|
225
|
+
// stdin protocol rather than risking Linux's per-argument byte limit.
|
|
226
|
+
const stdinPrompt = Buffer.byteLength(request.prompt, "utf8") >= 120_000;
|
|
224
227
|
const isBtw = request.feature === "btw";
|
|
225
228
|
const full = isBtw && request.access === "workspace-full";
|
|
226
229
|
const deadlineMs = deadlineMsOverride ?? (isBtw ? (full ? 610_000 : 130_000) : 125_000);
|
|
@@ -251,12 +254,11 @@ async function executeArgvPrint(
|
|
|
251
254
|
"--print-timeout",
|
|
252
255
|
printTimeout,
|
|
253
256
|
...(request.continuation ? ["--conversation", request.continuation.id] : []),
|
|
254
|
-
"--print",
|
|
255
|
-
request.prompt,
|
|
257
|
+
...(stdinPrompt ? ["--input-format", "stream-json"] : ["--print", request.prompt]),
|
|
256
258
|
],
|
|
257
259
|
{
|
|
258
260
|
cwd: full ? request.cwd : runDirectory,
|
|
259
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
261
|
+
stdio: [stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"],
|
|
260
262
|
windowsHide: true,
|
|
261
263
|
detached: process.platform !== "win32",
|
|
262
264
|
},
|
|
@@ -278,6 +280,11 @@ async function executeArgvPrint(
|
|
|
278
280
|
processError = error;
|
|
279
281
|
});
|
|
280
282
|
|
|
283
|
+
if (stdinPrompt) {
|
|
284
|
+
child.stdin?.on("error", () => { /* Report early exits through the close/error path. */ });
|
|
285
|
+
child.stdin?.end(`${JSON.stringify({ event: "user", message: { content: request.prompt } })}\n`);
|
|
286
|
+
}
|
|
287
|
+
|
|
281
288
|
const lines = createInterface({ input: child.stdout!, crlfDelay: Infinity });
|
|
282
289
|
try {
|
|
283
290
|
for await (const line of lines) {
|
|
@@ -327,7 +334,9 @@ async function executeArgvPrint(
|
|
|
327
334
|
if (exitSignal || code === null) {
|
|
328
335
|
return { status: "failure", message: unexpectedSignalMessage(exitSignal), partialText: partial || undefined };
|
|
329
336
|
}
|
|
330
|
-
if (code !== 0) return { status: "failure", message:
|
|
337
|
+
if (code !== 0) return { status: "failure", message: stdinPrompt && advisorFlagErrorHint(stderr)
|
|
338
|
+
? withDoctor("Large prompts require Agy 1.1.15+ with stdin support. Run `agy update`. " + stderr.trim())
|
|
339
|
+
: agyFailureMessage(action, { code, killed: false, stderr }) };
|
|
331
340
|
|
|
332
341
|
const text = final.trim();
|
|
333
342
|
if (!text) return { status: "failure", message: withDoctor(stderr.trim() || emptyTextMessage) };
|
package/bro.ts
CHANGED
|
@@ -1475,7 +1475,8 @@ function serializeShowTurns(turns: readonly ShowTurn[]): string {
|
|
|
1475
1475
|
return turns.flatMap((turn) => turn.entries).join("\n\n");
|
|
1476
1476
|
}
|
|
1477
1477
|
|
|
1478
|
-
export function captureShowTranscript(ctx: ExtensionCommandContext, turnsRequested: number): BroSource | undefined {
|
|
1478
|
+
export function captureShowTranscript(ctx: ExtensionCommandContext, turnsRequested: number, maxLength = MAX_TEXT_LENGTH): BroSource | undefined {
|
|
1479
|
+
if (!Number.isSafeInteger(maxLength) || maxLength < 128) throw new Error("Transcript limit must be an integer of at least 128 characters.");
|
|
1479
1480
|
const turns: ShowTurn[] = [];
|
|
1480
1481
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
1481
1482
|
if (entry.type !== "message") continue;
|
|
@@ -1503,7 +1504,7 @@ export function captureShowTranscript(ctx: ExtensionCommandContext, turnsRequest
|
|
|
1503
1504
|
if (seen === 0) return undefined;
|
|
1504
1505
|
|
|
1505
1506
|
let text = serializeShowTurns(turns.slice(start));
|
|
1506
|
-
while (text.length >
|
|
1507
|
+
while (text.length > maxLength && start < turns.length - 1) {
|
|
1507
1508
|
let next = turns.length;
|
|
1508
1509
|
for (let index = start + 1; index < turns.length; index += 1) {
|
|
1509
1510
|
if (turns[index]!.startsTurn) {
|
|
@@ -1515,7 +1516,22 @@ export function captureShowTranscript(ctx: ExtensionCommandContext, turnsRequest
|
|
|
1515
1516
|
start = next;
|
|
1516
1517
|
text = serializeShowTurns(turns.slice(start));
|
|
1517
1518
|
}
|
|
1518
|
-
if (text.length >
|
|
1519
|
+
if (text.length > maxLength) {
|
|
1520
|
+
// Prefer the newest complete messages even within an oversized single turn.
|
|
1521
|
+
const entries = turns.slice(start).flatMap(turn => turn.entries);
|
|
1522
|
+
const notice = "[… earlier messages truncated …]\n";
|
|
1523
|
+
while (entries.length > 1 && entries.join("\n\n").length + notice.length > maxLength) entries.shift();
|
|
1524
|
+
text = entries.join("\n\n");
|
|
1525
|
+
if (text.length + notice.length > maxLength) {
|
|
1526
|
+
const newline = text.indexOf("\n");
|
|
1527
|
+
const header = text.slice(0, newline);
|
|
1528
|
+
const content = JSON.parse(text.slice(newline + 1)) as string;
|
|
1529
|
+
const marker = "[… earlier text truncated …] ";
|
|
1530
|
+
let tail = content.slice(-(maxLength - header.length - marker.length - 8));
|
|
1531
|
+
while (`${header}\n${JSON.stringify(marker + tail)}`.length > maxLength) tail = tail.slice(Math.max(1, Math.floor(tail.length / 10)));
|
|
1532
|
+
text = `${header}\n${JSON.stringify(marker + tail)}`;
|
|
1533
|
+
} else text = notice + text;
|
|
1534
|
+
}
|
|
1519
1535
|
const kept = turns.slice(start).filter((turn) => turn.startsTurn).length;
|
|
1520
1536
|
return { text: text.trim(), label: `last ${Math.max(1, kept)} turn${kept > 1 ? "s" : ""} · conversation only` };
|
|
1521
1537
|
}
|
|
@@ -2942,6 +2958,12 @@ async function showBroModal(ctx: ExtensionCommandContext, options: BroModalOptio
|
|
|
2942
2958
|
options.retryLabel ?? "simplify again",
|
|
2943
2959
|
);
|
|
2944
2960
|
|
|
2961
|
+
const present = (result: ModalResult, notice = "") => {
|
|
2962
|
+
const display = result.htmlPath ? stripShowHtmlFence(result.text) : result.text;
|
|
2963
|
+
modal.setResult(display, options.retryable ?? Boolean(options.run), notice, result.source?.label, result.text, result.model);
|
|
2964
|
+
modal.setHtmlPath(result.htmlPath ?? "");
|
|
2965
|
+
};
|
|
2966
|
+
|
|
2945
2967
|
execute = (source?: BroSource) => {
|
|
2946
2968
|
if (!options.run || controller || closed) return;
|
|
2947
2969
|
const previous = current;
|
|
@@ -2958,16 +2980,14 @@ async function showBroModal(ctx: ExtensionCommandContext, options: BroModalOptio
|
|
|
2958
2980
|
if (closed || nextController.signal.aborted) return;
|
|
2959
2981
|
current = result;
|
|
2960
2982
|
options.onResult?.(result);
|
|
2961
|
-
|
|
2962
|
-
modal.setResult(display, options.retryable ?? true, "", result.source?.label, result.text, result.model);
|
|
2963
|
-
if (result.htmlPath) modal.setHtmlPath(result.htmlPath);
|
|
2983
|
+
present(result);
|
|
2964
2984
|
})
|
|
2965
2985
|
.catch((error) => {
|
|
2966
2986
|
if (closed || nextController.signal.aborted) return;
|
|
2967
2987
|
const message = error instanceof Error ? error.message : String(error);
|
|
2968
2988
|
if (previous) {
|
|
2969
2989
|
current = previous;
|
|
2970
|
-
|
|
2990
|
+
present(previous, `Retry failed: ${message}`);
|
|
2971
2991
|
} else {
|
|
2972
2992
|
modal.setError(message);
|
|
2973
2993
|
}
|
|
@@ -2980,7 +3000,7 @@ async function showBroModal(ctx: ExtensionCommandContext, options: BroModalOptio
|
|
|
2980
3000
|
if (options.text !== undefined) {
|
|
2981
3001
|
modal.setStatic(options.kind ?? "help", options.text, options.copyable ?? false);
|
|
2982
3002
|
} else if (current) {
|
|
2983
|
-
|
|
3003
|
+
present(current);
|
|
2984
3004
|
} else {
|
|
2985
3005
|
execute();
|
|
2986
3006
|
}
|
|
@@ -3318,10 +3338,7 @@ async function openBtwModal(
|
|
|
3318
3338
|
}
|
|
3319
3339
|
if (thread.turns.length === 0) {
|
|
3320
3340
|
thread.conversationId = undefined;
|
|
3321
|
-
|
|
3322
|
-
if (context && context.length > BTW_CONTEXT_MAX) {
|
|
3323
|
-
context = `${context.slice(0, BTW_CONTEXT_MAX)}\n[… context truncated …]`;
|
|
3324
|
-
}
|
|
3341
|
+
const context = captureShowTranscript(ctx, BTW_CONTEXT_TURNS, BTW_CONTEXT_MAX)?.text;
|
|
3325
3342
|
thread.context = context;
|
|
3326
3343
|
}
|
|
3327
3344
|
// Resume natively when possible; otherwise seed the fresh native session with the main-session
|
|
@@ -3479,10 +3496,14 @@ async function openBtwModal(
|
|
|
3479
3496
|
}
|
|
3480
3497
|
|
|
3481
3498
|
export default async function bro(pi: ExtensionAPI) {
|
|
3482
|
-
let lastResult:
|
|
3499
|
+
let lastResult: ModalResult | undefined;
|
|
3500
|
+
let lastShowSteering: string | undefined;
|
|
3483
3501
|
let btwThread: BtwThread | undefined;
|
|
3484
|
-
const remember = (result: ModalResult) => {
|
|
3485
|
-
if (result.source)
|
|
3502
|
+
const remember = (result: ModalResult, showSteering?: string) => {
|
|
3503
|
+
if (result.source) {
|
|
3504
|
+
lastResult = result;
|
|
3505
|
+
lastShowSteering = showSteering;
|
|
3506
|
+
}
|
|
3486
3507
|
};
|
|
3487
3508
|
|
|
3488
3509
|
pi.on("session_start", async (_event, _ctx) => {
|
|
@@ -3659,7 +3680,7 @@ export default async function bro(pi: ExtensionAPI) {
|
|
|
3659
3680
|
loadingText: "Drawing what happened…",
|
|
3660
3681
|
retryLabel: "show again",
|
|
3661
3682
|
run: runShow,
|
|
3662
|
-
onResult: remember,
|
|
3683
|
+
onResult: (result) => remember(result, steering),
|
|
3663
3684
|
});
|
|
3664
3685
|
} catch (error) {
|
|
3665
3686
|
ctx.ui.notify(withDoctor(error), "error");
|
|
@@ -3860,10 +3881,29 @@ export default async function bro(pi: ExtensionAPI) {
|
|
|
3860
3881
|
return;
|
|
3861
3882
|
}
|
|
3862
3883
|
|
|
3884
|
+
const steering = lastShowSteering;
|
|
3885
|
+
if (steering !== undefined) {
|
|
3886
|
+
const html = extractShowHtml(lastResult.text);
|
|
3887
|
+
// Temp files may have been cleaned by the OS or another Bro session.
|
|
3888
|
+
if (html && ctx.mode === "tui") {
|
|
3889
|
+
try { lastResult.htmlPath = await writeShowHtml(html); }
|
|
3890
|
+
catch (error) {
|
|
3891
|
+
lastResult.htmlPath = undefined;
|
|
3892
|
+
ctx.ui.notify(`Diagram file unavailable: ${errorMessage(error)}`, "warning");
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3863
3896
|
await showBroModal(ctx, {
|
|
3864
3897
|
result: lastResult,
|
|
3865
|
-
|
|
3866
|
-
|
|
3898
|
+
loadingText: steering !== undefined ? "Drawing what happened…" : undefined,
|
|
3899
|
+
retryLabel: steering !== undefined ? "show again" : undefined,
|
|
3900
|
+
run: steering === undefined ? run : async (signal, source, onProgress) => {
|
|
3901
|
+
if (!source) throw new Error("No captured Show source.");
|
|
3902
|
+
const result = await runShowExplanation(source.text, steering, signal, await readSettings(), onProgress);
|
|
3903
|
+
const html = extractShowHtml(result.text);
|
|
3904
|
+
return { source, ...result, ...(html ? { htmlPath: await writeShowHtml(html) } : {}) };
|
|
3905
|
+
},
|
|
3906
|
+
onResult: (result) => remember(result, steering),
|
|
3867
3907
|
});
|
|
3868
3908
|
return;
|
|
3869
3909
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bro",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.3",
|
|
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, opens a sandboxed side conversation with /bro btw, and provides a second-opinion advisor tool for executor agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|