billion-context-pi 0.1.57-pr.284.92 → 0.1.57

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/README.md CHANGED
@@ -133,7 +133,7 @@ Worker runs on Pi's full default toolset - no `--tools` allowlist is applied, so
133
133
 
134
134
  The full delegate result is saved to a file (`/tmp/acp-delegate/<runId>.out`); the tool result and injected notification carry only the **task title + file path** (no preview) - use `read` for the details. This keeps the parent context lean.
135
135
 
136
- - **Interactive (TUI) & RPC modes**: `async:true` (default) runs the child in the background; a short completion notification is injected into the chat when it finishes.
136
+ - **Interactive (TUI) & RPC modes**: `async:true` (default) runs the child in the background; a short completion notification is injected into the chat when it finishes — **unless the model already read the result file after the run finished** (detected via the `read` tool or a bash command referencing the file), in which case the notification is skipped: the model already has the result, so re-injecting it would only waste context. Set `delegate: { notifyIfRead: "always" }` in `acp.json` to restore the always-inject behavior.
137
137
  - **Print / JSON modes** (`pi -p`, SDK): `async:true` auto-downgrades to **synchronous** — the result returns as the tool result in the same turn (the parent exits after one turn, so background injection would be lost).
138
138
  - **Failures are loud, never silent.** A run that fails (nonzero exit, spawn error, watchdog timeout) injects a `FAILED ⚠️` notification carrying a short error excerpt, so a failed delegate cannot hide among sibling completions. If a notification cannot be delivered at all, a recovery notice is attached to the next delegate notification or the next `acp_delegate` / `acp_delegate_wait` / `acp_delegate_cancel` tool result — a dispatched run's failure always reaches the model before it wraps up.
139
139
 
package/dist/config.d.ts CHANGED
@@ -1,5 +1,21 @@
1
1
  import { type Config, type Prompts } from "acp-kernel";
2
2
  import type { ThrottleRetryConfig } from "./throttle-retry.js";
3
+ /** Per-role delegate defaults. Lets long-lived automation pin a cheaper or
4
+ * more capable model and a thinking level per delegate role, so the main
5
+ * agent doesn't have to fill them in on every `acp_delegate()` call. */
6
+ export interface DelegateRoleConfig {
7
+ /** Default model for this role, as `"provider/id"`. Resolution priority:
8
+ * per-call `model` > this role default > parent agent's current model. A
9
+ * value that isn't a valid `"provider/id"` is ignored (treated as unset).
10
+ * If the configured model doesn't exist in the live registry, the child
11
+ * falls back to the parent model and a warning is logged — it never fails. */
12
+ model?: string;
13
+ /** Default thinking level for this role: one of off|minimal|low|medium|
14
+ * high|xhigh|max. Resolution priority: per-call `thinkingLevel` > this role
15
+ * default > global `delegate.thinkingLevel` > Pi's own default. An invalid
16
+ * value is ignored with a warning (never fails). */
17
+ thinkingLevel?: string;
18
+ }
3
19
  /** Delegate sub-agent configuration. */
4
20
  export interface DelegateConfig {
5
21
  /** Enable acp_delegate tools (delegate/wait/cancel) and their system-prompt
@@ -37,6 +53,21 @@ export interface DelegateConfig {
37
53
  * back to unlimited with a warning. Env `PI_ACP_DELEGATE_MAX_CONCURRENT`
38
54
  * overrides this. See #294. */
39
55
  maxConcurrent?: number;
56
+ /** Global default thinking level applied to every delegate when neither the
57
+ * per-call `thinkingLevel` nor the role's own `thinkingLevel` is set. One of
58
+ * off|minimal|low|medium|high|xhigh|max. When unset at all levels, no
59
+ * `--thinking` flag is passed and each child uses Pi's own default. */
60
+ thinkingLevel?: string;
61
+ /** Per-role defaults keyed by role name (reviewer/researcher/worker/planner/
62
+ * oracle, or any custom role). See DelegateRoleConfig. Only affects roles
63
+ * that are named here; other roles inherit the parent model + Pi defaults. */
64
+ agents?: Record<string, DelegateRoleConfig>;
65
+ /** What happens to the completion notification when the model has already
66
+ * read the delegate's result file after the run finished.
67
+ * "skip" (default) — the notification is not injected; the model already
68
+ * saw the result, so re-injecting it would only waste context.
69
+ * "always" — always inject the notification (previous behavior). */
70
+ notifyIfRead?: "skip" | "always";
40
71
  }
41
72
  /** Resolved delegate policy: what actually takes effect after merging acp.json,
42
73
  * env overrides and defaults. Timeout fields are milliseconds; null means the
@@ -50,6 +81,13 @@ export interface DelegatePolicy {
50
81
  asyncTimeoutMs: number | null;
51
82
  /** Resolved cap on concurrent background delegates; Infinity = unlimited. */
52
83
  maxConcurrent: number;
84
+ /** Global default thinking level (undefined when unset). */
85
+ thinkingLevel?: string;
86
+ /** Per-role defaults keyed by role name (undefined when unset). */
87
+ agents?: Record<string, DelegateRoleConfig>;
88
+ /** Whether to suppress the completion notification when the model already
89
+ * read the result file after the run finished. Always resolved ("skip" default). */
90
+ notifyIfRead: "skip" | "always";
53
91
  }
54
92
  export declare const DEFAULT_DELEGATE_POLICY: DelegatePolicy;
55
93
  /** Compression tuning fields, shared by all three levels (global, provider,
@@ -1,8 +1,9 @@
1
1
  import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
2
  import { Type, type Static } from "typebox";
3
3
  import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
- import { type DelegatePolicy } from "./config.js";
4
+ import { type DelegatePolicy, type DelegateRoleConfig } from "./config.js";
5
5
  import { type Usage } from "./delegate-events.js";
6
+ export declare const OUT_DIR: string;
6
7
  export declare function delegateStdinText(resumeFrom: boolean, task: string | undefined): string;
7
8
  export declare function delegateSpawnOptions(cwd: string, env: NodeJS.ProcessEnv): SpawnOptions;
8
9
  /** Child process env for a nested delegate: depth increments by one, and the
@@ -54,6 +55,15 @@ interface DelegateRun {
54
55
  /** True while the run sits in the coalescing notification queue (scheduled
55
56
  * for the next batched flush, not yet delivered). */
56
57
  notifyQueued?: boolean;
58
+ /** When the model successfully read this run's result file (read tool, or a
59
+ * bash command referencing it). A read at/after finishedAt means the model
60
+ * already saw the final result — the completion notification is then
61
+ * suppressed (notifyIfRead: "skip"). */
62
+ readAt?: number;
63
+ /** True when the completion notification was suppressed because the model
64
+ * had already read the result file. Treated as delivered (injected=true)
65
+ * so wait/recovery never re-surface the result. */
66
+ readSuppressed?: boolean;
57
67
  }
58
68
  export declare function addDelegateUsage(u: Usage): void;
59
69
  export declare function getDelegateUsage(): Usage | undefined;
@@ -77,6 +87,23 @@ export declare class ConcurrencyGate {
77
87
  * launches FIFO (skipping any cancelled while queued). */
78
88
  release(): void;
79
89
  }
90
+ export declare function setDelegateDefaults(d: {
91
+ thinkingLevel?: string;
92
+ agents?: Record<string, DelegateRoleConfig>;
93
+ } | undefined): void;
94
+ export declare function resetDelegateDefaults(): void;
95
+ declare const VALID_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
96
+ export declare function isValidThinkingLevel(v: unknown): v is (typeof VALID_THINKING_LEVELS)[number];
97
+ export declare function setDelegateNotifyIfRead(mode: "skip" | "always"): void;
98
+ /** Mark the run whose result file the model just read (via the `read` tool).
99
+ * Returns true when a run matched. A read of the final result file after the
100
+ * run finished means the model already saw the result — the completion
101
+ * notification is then suppressed (notifyIfRead: "skip"). */
102
+ export declare function markDelegateResultRead(filePath: string): boolean;
103
+ /** Mark runs referenced by a bash command (e.g. `cat <result file>`).
104
+ * Matches runIds appearing anywhere in the command string; only existing
105
+ * runs are affected, so task text that merely contains "del_..." is inert. */
106
+ export declare function markDelegateRunReadByCommand(command: string): boolean;
80
107
  /** Snapshot of currently-running delegate runs, for the TUI status widget. */
81
108
  export declare function runningRunsSnapshot(): {
82
109
  runId: string;
@@ -84,6 +111,31 @@ export declare function runningRunsSnapshot(): {
84
111
  task: string;
85
112
  startedAt: number;
86
113
  }[];
114
+ export interface FleetRunView {
115
+ runId: string;
116
+ agent: string;
117
+ task: string;
118
+ cwd: string;
119
+ startedAt: number;
120
+ finishedAt?: number;
121
+ status: RunStatus;
122
+ exitLabel: string;
123
+ timedOut?: string;
124
+ resumedFrom?: string;
125
+ /** Streamed reply text (always retained, even for cancelled runs). */
126
+ replyFile: string;
127
+ /** Live tool-activity log (async json-stream runs only). */
128
+ activityFile?: string;
129
+ sessionFile?: string;
130
+ usage?: Usage;
131
+ }
132
+ /** Running first (oldest spawn on top), then recently finished (newest first,
133
+ * capped) — the order the fleet inspector list shows. */
134
+ export declare function orderRunsForFleet(all: DelegateRun[]): DelegateRun[];
135
+ /** Display-safe snapshot of every delegate run known this session, for the
136
+ * fleet inspector (/acp-fleet). In-memory only: runs from previous pi
137
+ * processes are not listed (their files remain under OUT_DIR). */
138
+ export declare function fleetRunsSnapshot(): FleetRunView[];
87
139
  /** Minimal writable surface accepted by makeEventApplier — real WriteStreams
88
140
  * in production, in-memory collectors in tests. */
89
141
  export interface EventApplierWriters {
@@ -125,6 +177,7 @@ declare const DelegateParams: Type.TObject<{
125
177
  resumeFrom: Type.TOptional<Type.TString>;
126
178
  cwd: Type.TOptional<Type.TString>;
127
179
  model: Type.TOptional<Type.TString>;
180
+ thinkingLevel: Type.TOptional<Type.TString>;
128
181
  async: Type.TOptional<Type.TBoolean>;
129
182
  showThinking: Type.TOptional<Type.TBoolean>;
130
183
  }>;
@@ -187,6 +240,7 @@ export declare function formatBatchRunSection(run: DelegateRun): string;
187
240
  * in which case the caller delivers the full payload via formatRunResult(). */
188
241
  export declare function injectedWaitMessage(run: {
189
242
  injected?: boolean;
243
+ readSuppressed?: boolean;
190
244
  result?: {
191
245
  file: string;
192
246
  };
@@ -227,6 +281,22 @@ export declare function asyncWatchdogDescription(): string;
227
281
  * never exited). If a result was delivered (non-empty reply or stderr), the
228
282
  * run counts as completed (0); otherwise it stays null = genuine failure. */
229
283
  export declare function effectiveExitCode(code: number | null, output: string, stderr: string): number | null;
284
+ /** Pure read-after-finish predicate: should the completion notification be
285
+ * suppressed because the model already read the final result file? Only a
286
+ * read at/after finishedAt counts — a read while the run was still in flight
287
+ * (readAt < finishedAt) saw partial output, so the notification still goes
288
+ * out. Callers additionally gate on run.status === "completed": a FAILED
289
+ * run's file holds only partial output with no failure marker, so the model
290
+ * cannot tell it failed from the file — failure notifications are never
291
+ * suppressed (failures are loud). */
292
+ export declare function shouldSuppressRead(run: {
293
+ readAt?: number;
294
+ finishedAt?: number;
295
+ }, mode: "skip" | "always"): boolean;
296
+ /** Suppress the completion notification for a run the model already read:
297
+ * mark it delivered (so wait/recovery/flush never re-surface the result),
298
+ * account its usage in separate mode, and log the skip. Idempotent. */
299
+ export declare function applyReadSuppression(run: DelegateRun, runId: string): void;
230
300
  /** status (set by finalize from the effective exit code) is the authority for
231
301
  * the FAILED/completed decision; the raw code is diagnostic display only
232
302
  * ("exit ?"), so the notification can never disagree with run.status. */
@@ -0,0 +1,42 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { type FleetRunView } from "./delegate-tool.js";
3
+ export interface InspectorTheme {
4
+ fg(color: string, text: string): string;
5
+ bold(text: string): string;
6
+ bg(color: string, text: string): string;
7
+ italic?(text: string): string;
8
+ underline?(text: string): string;
9
+ strikethrough?(text: string): string;
10
+ }
11
+ export declare function formatDuration(ms: number): string;
12
+ export declare function statusIcon(status: FleetRunView["status"]): string;
13
+ export declare function usageSummary(usage: FleetRunView["usage"]): string | undefined;
14
+ export interface ListRow {
15
+ run: FleetRunView;
16
+ label: string;
17
+ detail: string;
18
+ }
19
+ export declare function buildListRows(runs: FleetRunView[], now: number): ListRow[];
20
+ /** Last maxBytes of a file as utf8; a partial leading line is dropped so the
21
+ * tail always starts on a line boundary. Missing/unreadable → "". */
22
+ export declare function readTailSync(file: string | undefined, maxBytes: number): string;
23
+ /** First maxBytes of a file as utf8. Missing/unreadable → "". */
24
+ export declare function readHeadSync(file: string | undefined, maxBytes: number): string;
25
+ export interface TranscriptBlock {
26
+ kind: "user" | "thinking" | "text" | "toolCall" | "toolResult" | "meta";
27
+ name?: string;
28
+ text: string;
29
+ isError?: boolean;
30
+ args?: unknown;
31
+ }
32
+ /** Pi-style one-line summary of a tool call (mirrors pi's formatToolCall). */
33
+ export declare function formatToolLabel(name: string, args: unknown): string;
34
+ /** Parse a pi session .jsonl into display blocks (conversation + thinking + tools). */
35
+ export declare function parseSessionJsonl(raw: string): TranscriptBlock[];
36
+ export declare function renderListBody(rows: ListRow[], selIdx: number, theme: InspectorTheme, innerW: number): string[];
37
+ export declare function renderTranscriptBlocks(blocks: TranscriptBlock[], theme: InspectorTheme, innerW: number): string[];
38
+ /** Plain-text snapshot for non-TUI hosts (rpc/print/json). */
39
+ export declare function buildSnapshotText(runs: FleetRunView[], now: number): string;
40
+ /** Open the live fleet inspector: bordered TUI overlay in interactive mode,
41
+ * plain-text snapshot notification elsewhere. Resolves when the user closes it. */
42
+ export declare function openFleetInspector(ctx: ExtensionContext): Promise<void>;