pi-bro 0.25.0 → 0.25.1
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 +1 -1
- package/docs/features/advisor.md +17 -22
- package/package.json +1 -1
- package/src/advisor.ts +72 -144
- package/src/backend.ts +4 -5
- package/src/prompt.ts +3 -3
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.25.1] - 2026-10-19
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- `bro_advisor` answers faster: it relies on the session snapshot (including tool calls and outputs) first, and only reads files or runs commands when needed.
|
|
10
|
+
- `bro_advisor` no longer automatically restarts failed consultations up to three times with 5s and 10s waits; it runs once and reports any failure immediately.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `buildAdvisorPrompt` and the `bro_advisor` tool description now instruct the advisor to use the captured session snapshot first and inspect the workspace only when something needed is missing or a key claim is in doubt, instead of requiring a fresh workspace investigation before advising.
|
|
15
|
+
- `runAdvisor` (formerly `runAdvisorWithRetries`) runs a single consultation attempt and surfaces any backend failure, timeout, or cancellation immediately without 5-second and 10-second full-restart retries.
|
|
16
|
+
|
|
5
17
|
## [0.25.0] - 2026-10-19
|
|
6
18
|
|
|
7
19
|
### Highlights
|
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ File and webpage examples need your own real path or URL; see [how to explain th
|
|
|
62
62
|
| `/bro elevate` or **Ctrl+Shift+E** | **[Elevate](docs/elevate.md).** Discover missing expert considerations, select useful improvements, and rewrite your task without submitting it. Shares BTW backend settings. |
|
|
63
63
|
| `/bro btw` | **[Side questions](docs/features/btw.md#bro-btw-side-conversation).** A separate conversation seeded with recent context, so you can ask without interrupting the agent. ⚠️ [Full-permission mode](#what-leaves-your-machine) can edit files. |
|
|
64
64
|
| `/bro guided-review <PR>` | **[Guided Review](docs/guided-review.md).** An explanation and quality assessment of a GitHub PR, with ranked findings, source evidence and private questions. Claude or Muse is recommended for untrusted PRs. |
|
|
65
|
-
| `bro_advisor` tool | **[Second opinion](docs/features/advisor.md#bro-advisor).** A tool your agent can call mid-task; a fresh backend process
|
|
65
|
+
| `bro_advisor` tool | **[Second opinion](docs/features/advisor.md#bro-advisor).** A tool your agent can call mid-task; a fresh backend process reviews the session snapshot, can inspect the workspace when needed, and returns advice to the agent. ⚠️ [Runs with permissions bypassed](#what-leaves-your-machine). |
|
|
66
66
|
| `/bro config`, `/bro preferences` | **[Settings](docs/reference/settings.md#settings) and [preferences](docs/user-guide.md#preferences).** Pick the backend, model and effort per feature, and tell Bro how you like answers. |
|
|
67
67
|
|
|
68
68
|
For help by task, start at the **[docs home](docs/README.md)**; see the [command reference](docs/reference/commands.md) for syntax.
|
package/docs/features/advisor.md
CHANGED
|
@@ -24,14 +24,15 @@ presence, and backend compatibility (for Agy, a minimum CLI version with an
|
|
|
24
24
|
instructions, its active tools, and the conversation so far including tool
|
|
25
25
|
calls and results — and sends it, along with an optional `question` the
|
|
26
26
|
executor may pass, to a **fresh, standalone process of the selected
|
|
27
|
-
backend** for every consultation. Nothing is resumed or reused across calls
|
|
28
|
-
|
|
29
|
-
- **Instructed to investigate, not implement**: the advisor process has real
|
|
27
|
+
backend** for every consultation. Nothing is resumed or reused across calls.
|
|
28
|
+
- **Snapshot first, workspace tools when needed**: the advisor process has real
|
|
30
29
|
tool access in the workspace with permissions auto-approved — there is no
|
|
31
|
-
enforced read-only isolation. It is instructed to
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
enforced read-only isolation. It is instructed to rely on the captured
|
|
31
|
+
snapshot (including its tool calls and results) first, inspect the workspace
|
|
32
|
+
only when something is missing or a key claim is in doubt, and return advice
|
|
33
|
+
while leaving edits to the executor. That boundary is a behavioral prompt
|
|
34
|
+
instruction rather than an enforced sandbox constraint, so treat its findings
|
|
35
|
+
as advice to verify, not a guaranteed hands-off review.
|
|
35
36
|
- **Steering**: The advisor reads standing priorities from `~/.pi/agent/bro-advisor.md`
|
|
36
37
|
and session-specific priorities from `/bro advisor-steer`.
|
|
37
38
|
`/bro advisor-steer` opens an editor for one session steering brief — e.g. "quick
|
|
@@ -47,24 +48,18 @@ presence, and backend compatibility (for Agy, a minimum CLI version with an
|
|
|
47
48
|
session file (`bro-advisor-steering`) and is restored on resume or reload. Forking a
|
|
48
49
|
session inherits session steering; edits made after the fork are independent of the
|
|
49
50
|
original branch. Clearing session steering leaves durable steering intact.
|
|
50
|
-
- **
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
The advisor CLI is asked to stop at 10 minutes; Bro's host deadline is 610
|
|
57
|
-
seconds plus bounded process cleanup. A CLI-reported error before that host
|
|
58
|
-
deadline remains an invocation failure and can be retried.
|
|
59
|
-
Cancelling the tool call skips any pending retry wait.
|
|
60
|
-
- **Progress and provenance**: while an attempt is running, Bro shows the last
|
|
51
|
+
- **Single attempt, no automatic retries**: each consultation runs once (the
|
|
52
|
+
advisor CLI is asked to stop at 10 minutes; Bro's host deadline is 610
|
|
53
|
+
seconds plus bounded process cleanup). Any backend failure, timeout, or
|
|
54
|
+
cancellation is returned immediately — including a context-length error,
|
|
55
|
+
verbatim — without waiting or restarting the consultation from scratch.
|
|
56
|
+
- **Progress and provenance**: while a consultation is running, Bro shows the last
|
|
61
57
|
thing the advisor actually reported — a tool name or a user-facing response
|
|
62
58
|
line, never hidden reasoning — and how long ago it arrived, e.g. `last
|
|
63
59
|
reported: Read src/app.ts (2s ago)`, or "awaiting first activity" before
|
|
64
60
|
anything arrives. Expanding a running consultation shows up to the last 4
|
|
65
|
-
reported activity lines
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
notes; the advisor's complete answer follows unchanged.
|
|
61
|
+
reported activity lines. The returned answer starts with backend, model,
|
|
62
|
+
effort, duration, workspace, steering presence, snapshot size, and known
|
|
63
|
+
omission/compaction notes; the advisor's complete answer follows unchanged.
|
|
69
64
|
- **Model/effort**: resolved the same way as explain/show/btw, through
|
|
70
65
|
`/bro config` (shared default or per-capability override).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-bro",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "A sidekick for you and your Pi agent: plain-language explanations, side questions, diagrams, PR review and second opinions, without cluttering the agent's context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/advisor.ts
CHANGED
|
@@ -228,8 +228,7 @@ export type AdvisorActivityCallback = (label: string, timestamp: number) => void
|
|
|
228
228
|
// Thin wrapper around the shared backend: advisor's stdin/stream-json transport, activity parsing,
|
|
229
229
|
// and process lifecycle now live in backend.ts (see docs/plans/2026-09-19-bro-advisor-design.md,
|
|
230
230
|
// "Transport" for why stdin rather than --print). This function keeps its exact existing
|
|
231
|
-
// signature/throw contract -- it is called directly by tests and by
|
|
232
|
-
// which owns the 3-attempt retry/backoff policy the backend itself never performs.
|
|
231
|
+
// signature/throw contract -- it is called directly by tests and by runAdvisor below.
|
|
233
232
|
export class TerminalConsultError extends Error {
|
|
234
233
|
readonly status: "failure" | "cancelled" | "timeout";
|
|
235
234
|
constructor(status: "failure" | "cancelled" | "timeout", message: string) {
|
|
@@ -262,32 +261,6 @@ export async function runAdvisorConsultation(
|
|
|
262
261
|
throw new TerminalConsultError(outcome.status, outcome.message);
|
|
263
262
|
}
|
|
264
263
|
|
|
265
|
-
function advisorDelay(ms: number, signal: AbortSignal, onTick?: (remainingMs: number) => void): Promise<void> {
|
|
266
|
-
return new Promise((resolve, reject) => {
|
|
267
|
-
if (signal.aborted) {
|
|
268
|
-
reject(new Error("Canceled."));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
const startedAt = Date.now();
|
|
272
|
-
const interval = onTick ? setInterval(() => onTick(Math.max(0, ms - (Date.now() - startedAt))), 1_000) : undefined;
|
|
273
|
-
interval?.unref();
|
|
274
|
-
const cleanup = () => {
|
|
275
|
-
clearTimeout(timer);
|
|
276
|
-
if (interval) clearInterval(interval);
|
|
277
|
-
signal.removeEventListener("abort", onAbort);
|
|
278
|
-
};
|
|
279
|
-
const onAbort = () => {
|
|
280
|
-
cleanup();
|
|
281
|
-
reject(new Error("Canceled."));
|
|
282
|
-
};
|
|
283
|
-
const timer = setTimeout(() => {
|
|
284
|
-
cleanup();
|
|
285
|
-
resolve();
|
|
286
|
-
}, ms);
|
|
287
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
264
|
export type AdvisorConsult = (
|
|
292
265
|
prompt: string,
|
|
293
266
|
selection: BackendSelection,
|
|
@@ -296,14 +269,9 @@ export type AdvisorConsult = (
|
|
|
296
269
|
killEscalationMs?: number,
|
|
297
270
|
onActivity?: AdvisorActivityCallback,
|
|
298
271
|
) => Promise<string>;
|
|
299
|
-
export type AdvisorDelayFn = (ms: number, signal: AbortSignal, onTick?: (remainingMs: number) => void) => Promise<void>;
|
|
300
272
|
export type AdvisorToolDetails = {
|
|
301
|
-
status: "investigating" | "
|
|
302
|
-
attempt: number;
|
|
303
|
-
of: number;
|
|
273
|
+
status: "investigating" | "done";
|
|
304
274
|
elapsedMs: number;
|
|
305
|
-
error?: string;
|
|
306
|
-
retryInMs?: number;
|
|
307
275
|
backend?: BackendName;
|
|
308
276
|
model?: string;
|
|
309
277
|
effort?: string;
|
|
@@ -314,136 +282,98 @@ export type AdvisorToolDetails = {
|
|
|
314
282
|
snapshotChars?: number;
|
|
315
283
|
broTruncated?: false;
|
|
316
284
|
omissions?: string;
|
|
317
|
-
// Real backend-reported activity for
|
|
318
|
-
// attempt (including a retry) starts, never carried over from a prior failed attempt.
|
|
285
|
+
// Real backend-reported activity for this consultation, bounded to the last 4 labels.
|
|
319
286
|
activity?: string[];
|
|
320
287
|
lastActivityAt?: number;
|
|
321
288
|
// Total count of accepted activity events (tool calls AND user-facing text deltas -- "activity"
|
|
322
|
-
// is the honest label, not "tool calls") observed across the
|
|
323
|
-
// retry attempt. Unlike `activity` above, this never resets on a retry; the final "done" details
|
|
324
|
-
// carry the same running total.
|
|
289
|
+
// is the honest label, not "tool calls") observed across the consultation.
|
|
325
290
|
activityCount?: number;
|
|
326
291
|
};
|
|
327
292
|
export type AdvisorProgressCallback = (details: AdvisorToolDetails) => void;
|
|
328
|
-
export type AdvisorRunResult = { advice: string;
|
|
293
|
+
export type AdvisorRunResult = { advice: string; durationMs: number; activityCount: number };
|
|
329
294
|
|
|
330
295
|
// Bursts of chunked NDJSON activity events are coalesced to this cadence so a fast stream of tool
|
|
331
296
|
// calls/text deltas doesn't flood onProgress; the 1s elapsed-time heartbeat below is unaffected and
|
|
332
297
|
// keeps ticking independently.
|
|
333
298
|
const ADVISOR_ACTIVITY_THROTTLE_MS = 250;
|
|
334
299
|
|
|
335
|
-
// `consult
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
export async function runAdvisorWithRetries(
|
|
300
|
+
// `consult` is injectable so tests can swap in a fake process spawn instead of spawning real
|
|
301
|
+
// processes. Every consultation runs once against a fresh, standalone backend process — never
|
|
302
|
+
// resumed via --conversation and never automatically retried.
|
|
303
|
+
export async function runAdvisor(
|
|
340
304
|
prompt: string,
|
|
341
305
|
selection: BackendSelection,
|
|
342
306
|
cwd: string,
|
|
343
307
|
signal: AbortSignal,
|
|
344
308
|
consult: AdvisorConsult = runAdvisorConsultation,
|
|
345
|
-
delayFn: AdvisorDelayFn = advisorDelay,
|
|
346
309
|
onProgress?: AdvisorProgressCallback,
|
|
347
310
|
progressIntervalMs = 1_000,
|
|
348
311
|
activityThrottleMs = ADVISOR_ACTIVITY_THROTTLE_MS,
|
|
349
312
|
): Promise<AdvisorRunResult> {
|
|
350
|
-
|
|
351
|
-
const totalAttempts = delays.length + 1;
|
|
313
|
+
if (signal.aborted) throw new Error("Canceled.");
|
|
352
314
|
const startedAt = Date.now();
|
|
353
|
-
|
|
354
|
-
// Cumulative across every attempt in this consultation, including retries -- unlike `activity`
|
|
355
|
-
// below, a retry must never reset this back to zero.
|
|
315
|
+
const activity: string[] = [];
|
|
356
316
|
let totalActivityCount = 0;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const activity: string[] = [];
|
|
361
|
-
let lastActivityAt: number | undefined;
|
|
362
|
-
let lastActivityEmitAt = 0;
|
|
363
|
-
let pendingActivityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
317
|
+
let lastActivityAt: number | undefined;
|
|
318
|
+
let lastActivityEmitAt = 0;
|
|
319
|
+
let pendingActivityTimer: ReturnType<typeof setTimeout> | undefined;
|
|
364
320
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const progressTimer = onProgress ? setInterval(emitRunning, progressIntervalMs) : undefined;
|
|
377
|
-
progressTimer?.unref();
|
|
321
|
+
const running = (): AdvisorToolDetails => ({
|
|
322
|
+
status: "investigating",
|
|
323
|
+
elapsedMs: Date.now() - startedAt,
|
|
324
|
+
activity: [...activity],
|
|
325
|
+
lastActivityAt,
|
|
326
|
+
activityCount: totalActivityCount,
|
|
327
|
+
});
|
|
328
|
+
const emitRunning = () => onProgress?.(running());
|
|
329
|
+
emitRunning();
|
|
330
|
+
const progressTimer = onProgress ? setInterval(emitRunning, progressIntervalMs) : undefined;
|
|
331
|
+
progressTimer?.unref();
|
|
378
332
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
333
|
+
const stopActivityThrottle = () => {
|
|
334
|
+
if (pendingActivityTimer) {
|
|
335
|
+
clearTimeout(pendingActivityTimer);
|
|
336
|
+
pendingActivityTimer = undefined;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const onActivity: AdvisorActivityCallback = (label, timestamp) => {
|
|
340
|
+
const normalized = label.replace(/\s+/g, " ").trim();
|
|
341
|
+
if (!normalized) return;
|
|
342
|
+
activity.push(normalized);
|
|
343
|
+
if (activity.length > MAX_ADVISOR_ACTIVITY_LINES) activity.splice(0, activity.length - MAX_ADVISOR_ACTIVITY_LINES);
|
|
344
|
+
totalActivityCount += 1;
|
|
345
|
+
lastActivityAt = timestamp;
|
|
346
|
+
if (!onProgress) return;
|
|
347
|
+
const elapsed = Date.now() - lastActivityEmitAt;
|
|
348
|
+
if (elapsed >= activityThrottleMs) {
|
|
349
|
+
lastActivityEmitAt = Date.now();
|
|
350
|
+
emitRunning();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!pendingActivityTimer) {
|
|
354
|
+
pendingActivityTimer = setTimeout(() => {
|
|
382
355
|
pendingActivityTimer = undefined;
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
const onActivity: AdvisorActivityCallback = (label, timestamp) => {
|
|
386
|
-
const normalized = label.replace(/\s+/g, " ").trim();
|
|
387
|
-
if (!normalized) return;
|
|
388
|
-
activity.push(normalized);
|
|
389
|
-
if (activity.length > MAX_ADVISOR_ACTIVITY_LINES) activity.splice(0, activity.length - MAX_ADVISOR_ACTIVITY_LINES);
|
|
390
|
-
totalActivityCount += 1;
|
|
391
|
-
lastActivityAt = timestamp;
|
|
392
|
-
if (!onProgress) return;
|
|
393
|
-
const elapsed = Date.now() - lastActivityEmitAt;
|
|
394
|
-
if (elapsed >= activityThrottleMs) {
|
|
395
356
|
lastActivityEmitAt = Date.now();
|
|
396
357
|
emitRunning();
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (!pendingActivityTimer) {
|
|
400
|
-
pendingActivityTimer = setTimeout(() => {
|
|
401
|
-
pendingActivityTimer = undefined;
|
|
402
|
-
lastActivityEmitAt = Date.now();
|
|
403
|
-
emitRunning();
|
|
404
|
-
}, activityThrottleMs - elapsed);
|
|
405
|
-
pendingActivityTimer.unref?.();
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
const advice = await consult(prompt, selection, cwd, signal, undefined, onActivity);
|
|
411
|
-
// `totalActivityCount` is read directly here rather than from the last onProgress snapshot,
|
|
412
|
-
// which may lag behind by up to `activityThrottleMs` -- the final count must be exact.
|
|
413
|
-
return { advice, attempts: attempt + 1, durationMs: Date.now() - startedAt, activityCount: totalActivityCount };
|
|
414
|
-
} catch (error) {
|
|
415
|
-
if (progressTimer) clearInterval(progressTimer);
|
|
416
|
-
stopActivityThrottle();
|
|
417
|
-
if (signal.aborted || errorMessage(error) === "Canceled." ||
|
|
418
|
-
(error instanceof TerminalConsultError && error.status !== "failure")) throw error;
|
|
419
|
-
lastError = error;
|
|
420
|
-
if (attempt === delays.length) break;
|
|
421
|
-
const delay = delays[attempt]!;
|
|
422
|
-
const retrying = (remainingMs: number): AdvisorToolDetails => ({
|
|
423
|
-
status: "retrying",
|
|
424
|
-
attempt: attempt + 2,
|
|
425
|
-
of: totalAttempts,
|
|
426
|
-
elapsedMs: Date.now() - startedAt,
|
|
427
|
-
error: errorMessage(error),
|
|
428
|
-
retryInMs: remainingMs,
|
|
429
|
-
activityCount: totalActivityCount,
|
|
430
|
-
});
|
|
431
|
-
onProgress?.(retrying(delay));
|
|
432
|
-
await delayFn(delay, signal, (remainingMs) => onProgress?.(retrying(remainingMs)));
|
|
433
|
-
} finally {
|
|
434
|
-
if (progressTimer) clearInterval(progressTimer);
|
|
435
|
-
stopActivityThrottle();
|
|
358
|
+
}, activityThrottleMs - elapsed);
|
|
359
|
+
pendingActivityTimer.unref?.();
|
|
436
360
|
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const advice = await consult(prompt, selection, cwd, signal, undefined, onActivity);
|
|
365
|
+
// `totalActivityCount` is read directly here rather than from the last onProgress snapshot,
|
|
366
|
+
// which may lag behind by up to `activityThrottleMs` -- the final count must be exact.
|
|
367
|
+
return { advice, durationMs: Date.now() - startedAt, activityCount: totalActivityCount };
|
|
368
|
+
} finally {
|
|
369
|
+
if (progressTimer) clearInterval(progressTimer);
|
|
370
|
+
stopActivityThrottle();
|
|
437
371
|
}
|
|
438
|
-
throw lastError;
|
|
439
372
|
}
|
|
440
373
|
|
|
441
374
|
export function advisorAttemptLabel(details: AdvisorToolDetails): string {
|
|
442
|
-
if (details.status === "retrying") {
|
|
443
|
-
return `Bro advisor · retrying in ${Math.ceil((details.retryInMs ?? 0) / 1_000)}s · attempt ${details.attempt}/${details.of}${details.error ? ` — ${details.error}` : ""}`;
|
|
444
|
-
}
|
|
445
375
|
if (details.status === "done") {
|
|
446
|
-
return `Bro advisor · ${details.model ?? "unknown model"} · ${
|
|
376
|
+
return `Bro advisor · ${details.model ?? "unknown model"} · ${Math.ceil((details.durationMs ?? details.elapsedMs) / 1_000)}s`;
|
|
447
377
|
}
|
|
448
378
|
const latest = details.activity?.at(-1);
|
|
449
379
|
const backendLabel = details.backend === "claude" ? "Claude" : details.backend === "grok" ? "Grok" : details.backend === "codex" ? "Codex" : details.backend === "muse" ? "Muse" : "Agy";
|
|
@@ -452,7 +382,7 @@ export function advisorAttemptLabel(details: AdvisorToolDetails): string {
|
|
|
452
382
|
const activityLabel = latest
|
|
453
383
|
? `last reported: ${advisorActivityPreview(latest)} (${Math.max(0, Math.floor((Date.now() - (details.lastActivityAt ?? Date.now())) / 1_000))}s ago)`
|
|
454
384
|
: `awaiting first activity from ${backendLabel}`;
|
|
455
|
-
return `Bro advisor · running · ${Math.floor(details.elapsedMs / 1_000)}s ·
|
|
385
|
+
return `Bro advisor · running · ${Math.floor(details.elapsedMs / 1_000)}s · ${activityLabel}`;
|
|
456
386
|
}
|
|
457
387
|
|
|
458
388
|
export function registerAdvisorTool(pi: ExtensionAPI): void {
|
|
@@ -460,8 +390,8 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
|
|
|
460
390
|
name: ADVISOR_TOOL_NAME,
|
|
461
391
|
label: "Bro advisor",
|
|
462
392
|
description:
|
|
463
|
-
"Consult a fresh, independent process of the configured advisor backend for a second opinion mid-task. It has real, unsandboxed tool access in the current workspace (read files, search, run commands) with permissions auto-approved, and is instructed to
|
|
464
|
-
promptSnippet: "Consult a fresh configured-backend process for a second opinion mid-task; it
|
|
393
|
+
"Consult a fresh, independent process of the configured advisor backend for a second opinion mid-task. It has real, unsandboxed tool access in the current workspace (read files, search, run commands) with permissions auto-approved, and is instructed to use the captured snapshot first, inspect the workspace only when needed, and leave edits to you — that is a behavioral instruction to the advisor, not an enforced restriction, so treat its findings as advice rather than a delegated implementation. No need to prepare a summary or evidence first: Bro automatically captures your system instructions, active tools, and the conversation so far, plus any human-set steering priorities, and sends them to the advisor.",
|
|
394
|
+
promptSnippet: "Consult a fresh configured-backend process for a second opinion mid-task; it reviews the captured session snapshot, can inspect the workspace when needed, and returns advice",
|
|
465
395
|
promptGuidelines: [
|
|
466
396
|
"Use bro_advisor when the user asks for an independent opinion, or when one would likely settle real uncertainty or catch a costly mistake. Routine decisions do not require consultation.",
|
|
467
397
|
"bro_advisor's question parameter is optional — Bro captures context automatically, so you do not need to prepare a summary or evidence before calling.",
|
|
@@ -483,21 +413,20 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
|
|
|
483
413
|
const prompt = buildAdvisorPrompt({ durable: durableSteering, session: state.steering }, snapshot.text, params?.question);
|
|
484
414
|
const hasDurable = Boolean(durableSteering.trim());
|
|
485
415
|
const hasSession = Boolean(state.steering.trim());
|
|
486
|
-
let
|
|
416
|
+
let lastProgress: AdvisorToolDetails = { status: "investigating", elapsedMs: 0, backend };
|
|
487
417
|
try {
|
|
488
|
-
const run = await
|
|
418
|
+
const run = await runAdvisor(
|
|
489
419
|
prompt,
|
|
490
420
|
selection,
|
|
491
421
|
ctx.cwd,
|
|
492
422
|
signal ?? new AbortController().signal,
|
|
493
423
|
undefined,
|
|
494
|
-
undefined,
|
|
495
424
|
(details) => {
|
|
496
|
-
|
|
497
|
-
const label = advisorAttemptLabel(
|
|
425
|
+
lastProgress = { ...details, backend };
|
|
426
|
+
const label = advisorAttemptLabel(lastProgress);
|
|
498
427
|
onUpdate?.({
|
|
499
428
|
content: [{ type: "text", text: label }],
|
|
500
|
-
details:
|
|
429
|
+
details: lastProgress,
|
|
501
430
|
});
|
|
502
431
|
// A host's tool-card renderer may replace onUpdate's partial renderResult with its
|
|
503
432
|
// own generic placeholder for the whole run (observed with pi-cc-extensions' default
|
|
@@ -511,9 +440,8 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
|
|
|
511
440
|
const effort = selection.effort ?? "model default";
|
|
512
441
|
const omissions = `image/reasoning bodies omitted when present; ${snapshot.hadCompaction ? "Pi compaction summaries replace earlier turns" : "no Pi compaction summary present"}`;
|
|
513
442
|
const details: AdvisorToolDetails = {
|
|
514
|
-
...
|
|
443
|
+
...lastProgress,
|
|
515
444
|
status: "done",
|
|
516
|
-
attempt: run.attempts,
|
|
517
445
|
backend,
|
|
518
446
|
model: selection.model,
|
|
519
447
|
effort,
|
|
@@ -524,12 +452,12 @@ export function registerAdvisorTool(pi: ExtensionAPI): void {
|
|
|
524
452
|
snapshotChars: snapshot.text.length,
|
|
525
453
|
broTruncated: false,
|
|
526
454
|
omissions,
|
|
527
|
-
// run.activityCount is the exact final total;
|
|
455
|
+
// run.activityCount is the exact final total; lastProgress's may lag behind by up to
|
|
528
456
|
// the activity throttle window.
|
|
529
457
|
activityCount: run.activityCount,
|
|
530
458
|
};
|
|
531
|
-
// The backend qualifier appears only off Agy, keeping the Agy header
|
|
532
|
-
const header = `Bro advisor · ${backend === "agy" ? "" : `backend: ${backend} · `}model: ${selection.model} · effort: ${effort} · ${
|
|
459
|
+
// The backend qualifier appears only off Agy, keeping the Agy header concise.
|
|
460
|
+
const header = `Bro advisor · ${backend === "agy" ? "" : `backend: ${backend} · `}model: ${selection.model} · effort: ${effort} · ${Math.ceil(run.durationMs / 1_000)}s`;
|
|
533
461
|
const steeringSummary = hasDurable && hasSession
|
|
534
462
|
? "included (durable+session)"
|
|
535
463
|
: hasDurable
|
package/src/backend.ts
CHANGED
|
@@ -1376,11 +1376,10 @@ async function executeMuse(
|
|
|
1376
1376
|
}
|
|
1377
1377
|
}
|
|
1378
1378
|
|
|
1379
|
-
// Single-attempt executor shared by all features. Never retries
|
|
1380
|
-
//
|
|
1381
|
-
//
|
|
1382
|
-
//
|
|
1383
|
-
// callers never override the deadline or kill-escalation delay.
|
|
1379
|
+
// Single-attempt executor shared by all features. Never retries; never spawns a pre-aborted
|
|
1380
|
+
// request; on cancellation, host deadline, or a protocol failure, stops the whole POSIX process
|
|
1381
|
+
// group (SIGTERM, then SIGKILL after a bounded grace period) before resolving. `options` is for
|
|
1382
|
+
// offline tests only -- production callers never override the deadline or kill-escalation delay.
|
|
1384
1383
|
export async function execute(
|
|
1385
1384
|
request: BackendRequest,
|
|
1386
1385
|
selection: BackendSelection,
|
package/src/prompt.ts
CHANGED
|
@@ -159,7 +159,7 @@ export function buildAdvisorPrompt(steering: AdvisorSteeringInput, snapshot: str
|
|
|
159
159
|
const questionSection = question?.trim()
|
|
160
160
|
? `## Executor's question\n\n${question.trim()}`
|
|
161
161
|
: "## Executor's question\n\nNone was given. Use your own judgment about what advice would help most, given the snapshot below.";
|
|
162
|
-
return `You are the Bro advisor: a separate process an executor coding agent voluntarily consulted mid-task, in the pi-bro Pi extension. You have real tool access in this workspace and are running with permissions auto-approved -- you can freely read files, search, and run read-oriented commands
|
|
162
|
+
return `You are the Bro advisor: a separate process an executor coding agent voluntarily consulted mid-task, in the pi-bro Pi extension. You have real tool access in this workspace and are running with permissions auto-approved -- you can freely read files, search, and run read-oriented commands when needed. Use the snapshot below first; only read files or run commands when something you need is missing from the snapshot, may be stale after later edits, or a key claim is in doubt.
|
|
163
163
|
|
|
164
164
|
Your job is strictly advisory. Return findings and recommendations in your reply; do not edit files or otherwise implement the change yourself -- the executor remains responsible for implementation.
|
|
165
165
|
|
|
@@ -167,11 +167,11 @@ ${steeringSection}
|
|
|
167
167
|
|
|
168
168
|
## Context snapshot from the executor's session
|
|
169
169
|
|
|
170
|
-
This is background/evidence captured from the executor's own conversation. It is the executor's own account of what happened, not independently verified by you --
|
|
170
|
+
This is background/evidence captured from the executor's own conversation, including its tool calls and results. It is the executor's own account of what happened, not independently verified by you -- rely on the tool outputs already shown here before running new checks:
|
|
171
171
|
|
|
172
172
|
${snapshot}
|
|
173
173
|
|
|
174
174
|
${questionSection}
|
|
175
175
|
|
|
176
|
-
Begin with a one-line answer or verdict. Then give concise findings, evidence, and recommended next actions grounded in
|
|
176
|
+
Begin with a one-line answer or verdict. Then give concise findings, evidence, and recommended next actions grounded in the snapshot and any workspace checks you needed. Omit investigation narration, waiting updates, and progress reports.`;
|
|
177
177
|
}
|