billion-context-pi 0.1.26 → 0.1.28

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/dist/index.js CHANGED
@@ -2429,9 +2429,17 @@ ${argStr}` : argStr || textParts;
2429
2429
  }
2430
2430
  return [{ id, role: "assistant", contentType: "text", text: extractText(msg.content) }];
2431
2431
  }
2432
- const customText = extractText(msg.content);
2432
+ const customText = extractText(msg.content) || fallbackText(msg);
2433
2433
  return customText.length > 0 ? [{ id, role: "user", contentType: "text", text: customText }] : [];
2434
2434
  }
2435
+ function fallbackText(msg) {
2436
+ const parts = [];
2437
+ if (msg.command) parts.push(`$ ${msg.command}`);
2438
+ const out = extractText(msg.output);
2439
+ if (out) parts.push(out);
2440
+ if (msg.summary) parts.push(msg.summary);
2441
+ return parts.join("\n").trim();
2442
+ }
2435
2443
  function stringifyArgs(args) {
2436
2444
  if (!args) return "";
2437
2445
  if (typeof args === "string") return args;
@@ -2621,6 +2629,16 @@ function mergeInitialState(parsed) {
2621
2629
  }
2622
2630
 
2623
2631
  // src/runtime.ts
2632
+ function readContextEntries(sm) {
2633
+ const source = sm;
2634
+ if (typeof source.buildContextEntries === "function") return source.buildContextEntries();
2635
+ if (typeof source.getBranch === "function") return source.getBranch();
2636
+ return [];
2637
+ }
2638
+ function isPiHost(sm) {
2639
+ const source = sm;
2640
+ return typeof source.buildContextEntries === "function";
2641
+ }
2624
2642
  function createRuntime(adapter) {
2625
2643
  const core = createCore({ countTokens: defaultCountTokens });
2626
2644
  const store = new SessionStateStore();
@@ -2652,7 +2670,7 @@ function createRuntime(adapter) {
2652
2670
  async function stateFor(ctx) {
2653
2671
  const sm = ctx.sessionManager;
2654
2672
  const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
2655
- const entries = sm.buildContextEntries();
2673
+ const entries = readContextEntries(sm);
2656
2674
  return { state, coreMessages: entriesToCoreMessages(entries), entries };
2657
2675
  }
2658
2676
  async function save(state, ctx) {
@@ -7552,7 +7570,8 @@ ${extra.join("\n")}` : base;
7552
7570
  import {
7553
7571
  spawn
7554
7572
  } from "child_process";
7555
- import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm } from "fs/promises";
7573
+ import { createWriteStream } from "fs";
7574
+ import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
7556
7575
  import { tmpdir as tmpdir2 } from "os";
7557
7576
  import { join as join4 } from "path";
7558
7577
 
@@ -7644,9 +7663,203 @@ var delegateStatusWidget = {
7644
7663
  }
7645
7664
  };
7646
7665
 
7666
+ // src/delegate-watchdog.ts
7667
+ function attachWatchdogs(child, hooks, opts) {
7668
+ let idleTimer;
7669
+ let eofTimer;
7670
+ let killGraceTimer;
7671
+ let timeoutTimer;
7672
+ const clearTimers = () => {
7673
+ if (idleTimer) clearTimeout(idleTimer);
7674
+ if (eofTimer) clearTimeout(eofTimer);
7675
+ if (killGraceTimer) clearTimeout(killGraceTimer);
7676
+ if (timeoutTimer) clearTimeout(timeoutTimer);
7677
+ };
7678
+ const killByWatchdog = (reason) => {
7679
+ if (hooks.isSettled()) return;
7680
+ hooks.onKill(reason);
7681
+ try {
7682
+ child.kill("SIGTERM");
7683
+ } catch {
7684
+ }
7685
+ killGraceTimer = setTimeout(() => {
7686
+ if (hooks.isSettled()) return;
7687
+ try {
7688
+ child.kill("SIGKILL");
7689
+ } catch {
7690
+ }
7691
+ }, opts.killGraceMs);
7692
+ killGraceTimer.unref?.();
7693
+ };
7694
+ const poke = () => {
7695
+ if (idleTimer) clearTimeout(idleTimer);
7696
+ idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
7697
+ idleTimer.unref?.();
7698
+ };
7699
+ poke();
7700
+ timeoutTimer = setTimeout(() => killByWatchdog(`${opts.timeoutMs / 6e4}m limit`), opts.timeoutMs);
7701
+ timeoutTimer.unref?.();
7702
+ const onStdoutEnd = () => {
7703
+ if (hooks.isSettled()) return;
7704
+ eofTimer = setTimeout(() => {
7705
+ if (hooks.isSettled()) return;
7706
+ hooks.onEofGrace();
7707
+ try {
7708
+ child.kill("SIGTERM");
7709
+ } catch {
7710
+ }
7711
+ }, opts.eofGraceMs);
7712
+ eofTimer.unref?.();
7713
+ };
7714
+ child.stdout?.once("end", onStdoutEnd);
7715
+ return {
7716
+ poke,
7717
+ dispose: () => {
7718
+ clearTimers();
7719
+ child.stdout?.removeListener("end", onStdoutEnd);
7720
+ }
7721
+ };
7722
+ }
7723
+
7724
+ // src/delegate-events.ts
7725
+ var ThinkingCollector = class {
7726
+ constructor(showThinking) {
7727
+ this.showThinking = showThinking;
7728
+ }
7729
+ showThinking;
7730
+ buf = "";
7731
+ push(delta) {
7732
+ this.buf += delta;
7733
+ }
7734
+ /** Return the segment line to write ("" when empty or disabled), resetting. */
7735
+ flush() {
7736
+ const text = this.buf.trim();
7737
+ this.buf = "";
7738
+ if (!this.showThinking || !text) return "";
7739
+ return `[thinking] ${text}
7740
+ `;
7741
+ }
7742
+ };
7743
+ function parseEventLine(line) {
7744
+ let ev;
7745
+ try {
7746
+ ev = JSON.parse(line);
7747
+ } catch {
7748
+ return null;
7749
+ }
7750
+ if (typeof ev !== "object" || ev === null) return null;
7751
+ const e = ev;
7752
+ if (e.type === "message_update") {
7753
+ const am = e.assistantMessageEvent;
7754
+ if (typeof am !== "object" || am === null) return null;
7755
+ const msg = am;
7756
+ switch (msg.type) {
7757
+ case "text_delta":
7758
+ return { kind: "reply-delta", delta: String(msg.delta ?? "") };
7759
+ case "text_end":
7760
+ return { kind: "reply-complete", content: String(msg.content ?? "") };
7761
+ case "thinking_delta":
7762
+ return { kind: "thinking-delta", delta: String(msg.delta ?? "") };
7763
+ case "thinking_end":
7764
+ return { kind: "thinking-end" };
7765
+ default:
7766
+ return null;
7767
+ }
7768
+ }
7769
+ if (e.type === "tool_execution_start") {
7770
+ return {
7771
+ kind: "tool-start",
7772
+ toolName: String(e.toolName ?? ""),
7773
+ argsText: formatArgs(e.args)
7774
+ };
7775
+ }
7776
+ if (e.type === "tool_execution_update") {
7777
+ return {
7778
+ kind: "tool-update",
7779
+ toolCallId: String(e.toolCallId ?? ""),
7780
+ text: extractContentText(e.partialResult)
7781
+ };
7782
+ }
7783
+ if (e.type === "tool_execution_end") {
7784
+ return {
7785
+ kind: "tool-end",
7786
+ toolName: String(e.toolName ?? ""),
7787
+ isError: Boolean(e.isError)
7788
+ };
7789
+ }
7790
+ if (e.type === "auto_retry_start") {
7791
+ return {
7792
+ kind: "retry-start",
7793
+ attempt: Number(e.attempt ?? 0),
7794
+ maxAttempts: Number(e.maxAttempts ?? 0),
7795
+ delayMs: Number(e.delayMs ?? 0),
7796
+ errorMessage: String(e.errorMessage ?? "")
7797
+ };
7798
+ }
7799
+ if (e.type === "auto_retry_end") {
7800
+ return {
7801
+ kind: "retry-end",
7802
+ success: Boolean(e.success),
7803
+ attempt: Number(e.attempt ?? 0)
7804
+ };
7805
+ }
7806
+ return null;
7807
+ }
7808
+ function formatArgs(args) {
7809
+ if (args && typeof args === "object") {
7810
+ const a = args;
7811
+ if (typeof a.command === "string") return a.command;
7812
+ }
7813
+ try {
7814
+ return JSON.stringify(args);
7815
+ } catch {
7816
+ return String(args);
7817
+ }
7818
+ }
7819
+ function extractContentText(payload) {
7820
+ if (!payload || typeof payload !== "object") return "";
7821
+ const content = payload.content;
7822
+ if (!Array.isArray(content)) return "";
7823
+ return content.map((c) => c && typeof c === "object" ? String(c.text ?? "") : "").join("");
7824
+ }
7825
+ function activityLines(ev, opts) {
7826
+ switch (ev.kind) {
7827
+ case "tool-start":
7828
+ return [`[tool] ${ev.toolName}${ev.argsText ? ` ${ev.argsText}` : ""}
7829
+ `];
7830
+ case "tool-update": {
7831
+ if (!ev.text) return [];
7832
+ return [ev.text.endsWith("\n") ? ev.text : `${ev.text}
7833
+ `];
7834
+ }
7835
+ case "tool-end":
7836
+ return [`[done] ${ev.toolName}${ev.isError ? " (error)" : ""}
7837
+ `];
7838
+ case "thinking-delta":
7839
+ return opts.showThinking ? [`[thinking] ${ev.delta}
7840
+ `] : [];
7841
+ case "retry-start":
7842
+ return [`[retry] attempt ${ev.attempt}/${ev.maxAttempts}, backoff ${ev.delayMs}ms${ev.errorMessage ? ` \u2014 ${ev.errorMessage}` : ""}
7843
+ `];
7844
+ case "retry-end":
7845
+ return [`[retry] attempt ${ev.attempt} ${ev.success ? "succeeded" : "failed"}
7846
+ `];
7847
+ default:
7848
+ return [];
7849
+ }
7850
+ }
7851
+ function newPortion(text, prev) {
7852
+ if (text.startsWith(prev)) return text.slice(prev.length);
7853
+ return text;
7854
+ }
7855
+
7647
7856
  // src/delegate-tool.ts
7648
7857
  var MAX_DEPTH = 2;
7649
7858
  var SYNC_TIMEOUT_MS = 5 * 6e4;
7859
+ var EOF_GRACE_MS = 1e4;
7860
+ var IDLE_GRACE_MS = 5 * 6e4;
7861
+ var ASYNC_TIMEOUT_MS = 30 * 6e4;
7862
+ var KILL_GRACE_MS = 1e4;
7650
7863
  var RESULT_SUMMARY_CHARS = 500;
7651
7864
  var OUT_DIR = join4(tmpdir2(), "acp-delegate");
7652
7865
  var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
@@ -7714,6 +7927,11 @@ var DelegateParams = typebox_exports.Object({
7714
7927
  typebox_exports.Boolean({
7715
7928
  description: "If true (default), return immediately with a runId. In long-lived sessions (interactive/rpc) a short notification is injected into chat when the delegate finishes; in one-shot sessions (print/json, e.g. `pi -p` / SDK) async auto-downgrades to sync and the result is returned here. If false, always block and return the output here."
7716
7929
  })
7930
+ ),
7931
+ showThinking: typebox_exports.Optional(
7932
+ typebox_exports.Boolean({
7933
+ description: "If true, the delegate's thinking deltas are also written to the live activity file (default: false \u2014 only tool activity is shown)."
7934
+ })
7717
7935
  )
7718
7936
  });
7719
7937
  var CancelParams = typebox_exports.Object({
@@ -7771,7 +7989,8 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
7771
7989
  };
7772
7990
  }
7773
7991
  function formatRunResult(run) {
7774
- const header = run.status === "completed" ? `Delegate **${run.agent}** (runId \`${run.runId}\`) completed (exit ${run.exitCode ?? "?"})${remainingLineForWait(run.runId)}` : `Delegate **${run.agent}** (runId \`${run.runId}\`) ${run.status} (exit ${run.exitCode ?? "?"})${remainingLineForWait(run.runId)}`;
7992
+ const timeoutNote = run.timedOut ? ` (timed out: ${run.timedOut})` : "";
7993
+ const header = run.status === "completed" ? `Delegate **${run.agent}** (runId \`${run.runId}\`) completed (exit ${run.exitCode ?? "?"})${timeoutNote}${remainingLineForWait(run.runId)}` : `Delegate **${run.agent}** (runId \`${run.runId}\`) ${run.status} (exit ${run.exitCode ?? "?"})${timeoutNote}${remainingLineForWait(run.runId)}`;
7775
7994
  return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
7776
7995
  }
7777
7996
  function remainingLineForWait(selfRunId) {
@@ -7910,13 +8129,12 @@ async function runDelegate(pi, args, ctx, signal) {
7910
8129
  ...process.env,
7911
8130
  PI_ACP_DELEGATE_DEPTH: String(parentDepth + 1)
7912
8131
  };
7913
- const { cliArgs, tmpDir } = await buildChildArgs(args, agent.prompt, ctx);
8132
+ const { cliArgs, tmpDir, isAsync, useJsonStream } = await buildChildArgs(args, agent.prompt, ctx);
7914
8133
  const requestedAsync = args.async !== false;
7915
- const isAsync = requestedAsync && ctx.mode !== "print" && ctx.mode !== "json";
7916
8134
  if (requestedAsync && !isAsync) {
7917
8135
  debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
7918
8136
  }
7919
- debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, cliArgs });
8137
+ debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, useJsonStream, cliArgs });
7920
8138
  const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
7921
8139
  cwd,
7922
8140
  env: childEnv,
@@ -7927,12 +8145,94 @@ async function runDelegate(pi, args, ctx, signal) {
7927
8145
  debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
7928
8146
  });
7929
8147
  child.stdin?.end(args.task);
7930
- let stdoutChunks = [];
7931
8148
  let stderrText = "";
7932
8149
  const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
7933
8150
  const startedAt = Date.now();
7934
8151
  if (isAsync) {
7935
- child.stdout?.on("data", (c) => stdoutChunks.push(c));
8152
+ let settled = false;
8153
+ const watchdog = attachWatchdogs(
8154
+ child,
8155
+ {
8156
+ isSettled: () => settled || run.status !== "running",
8157
+ onKill: (reason) => {
8158
+ run.timedOut = reason;
8159
+ debug.event("delegate-watchdog", { runId, reason });
8160
+ },
8161
+ onEofGrace: () => {
8162
+ run.timedOut = "output ended but process did not exit";
8163
+ debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
8164
+ }
8165
+ },
8166
+ { eofGraceMs: EOF_GRACE_MS, idleMs: IDLE_GRACE_MS, timeoutMs: ASYNC_TIMEOUT_MS, killGraceMs: KILL_GRACE_MS }
8167
+ );
8168
+ const replyFile = join4(OUT_DIR, `${runId}.out`);
8169
+ const activityFile = join4(OUT_DIR, `${runId}.activity`);
8170
+ await mkdir2(OUT_DIR, { recursive: true });
8171
+ const replyStream = createWriteStream(replyFile, { flags: "a" });
8172
+ const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
8173
+ const endStream = (s) => new Promise((resolve2) => {
8174
+ if (!s || s.destroyed || s.closed) return resolve2();
8175
+ s.end(() => resolve2());
8176
+ });
8177
+ let replyText = "";
8178
+ let stdoutBuf = "";
8179
+ const lastToolText = /* @__PURE__ */ new Map();
8180
+ const thinking = new ThinkingCollector(args.showThinking === true);
8181
+ const flushThinking = () => {
8182
+ const line = thinking.flush();
8183
+ if (line) activityStream?.write(line);
8184
+ };
8185
+ const handleEventLine = (line) => {
8186
+ const ev = parseEventLine(line);
8187
+ if (!ev) return;
8188
+ if (ev.kind === "thinking-delta") {
8189
+ thinking.push(ev.delta);
8190
+ return;
8191
+ }
8192
+ if (ev.kind === "thinking-end") {
8193
+ flushThinking();
8194
+ return;
8195
+ }
8196
+ if (ev.kind === "reply-delta") {
8197
+ flushThinking();
8198
+ replyText += ev.delta;
8199
+ replyStream.write(ev.delta);
8200
+ return;
8201
+ }
8202
+ if (ev.kind === "reply-complete") {
8203
+ flushThinking();
8204
+ replyText = ev.content;
8205
+ return;
8206
+ }
8207
+ if (ev.kind === "tool-update") {
8208
+ flushThinking();
8209
+ const prev = lastToolText.get(ev.toolCallId) ?? "";
8210
+ const add = newPortion(ev.text, prev);
8211
+ lastToolText.set(ev.toolCallId, ev.text);
8212
+ if (add) activityStream?.write(add.endsWith("\n") ? add : `${add}
8213
+ `);
8214
+ return;
8215
+ }
8216
+ flushThinking();
8217
+ const lines = activityLines(ev, { showThinking: args.showThinking === true });
8218
+ if (lines.length) activityStream?.write(lines.join(""));
8219
+ };
8220
+ child.stdout?.on("data", (c) => {
8221
+ watchdog.poke();
8222
+ if (useJsonStream) {
8223
+ stdoutBuf += c.toString("utf8");
8224
+ let nl;
8225
+ while ((nl = stdoutBuf.indexOf("\n")) >= 0) {
8226
+ const line = stdoutBuf.slice(0, nl);
8227
+ stdoutBuf = stdoutBuf.slice(nl + 1);
8228
+ handleEventLine(line);
8229
+ }
8230
+ } else {
8231
+ const text = c.toString("utf8");
8232
+ replyText += text;
8233
+ replyStream.write(text);
8234
+ }
8235
+ });
7936
8236
  child.stderr?.on("data", (c) => {
7937
8237
  stderrText += c.toString("utf8");
7938
8238
  });
@@ -7947,47 +8247,69 @@ async function runDelegate(pi, args, ctx, signal) {
7947
8247
  };
7948
8248
  runs.set(runId, run);
7949
8249
  delegateStatusWidget.poke();
7950
- child.on("close", (code) => {
7951
- void cleanupTmp(tmpDir);
7952
- const output = Buffer.concat(stdoutChunks).toString("utf8").trim();
7953
- run.exitCode = code;
7954
- const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
7955
- if (run.status === "cancelled") {
7956
- run.finishedAt = Date.now();
7957
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
7958
- run.waiter?.();
7959
- delegateStatusWidget.poke();
7960
- return;
7961
- }
7962
- void persistResult(runId, body2).then((file2) => {
7963
- run.result = { code, file: file2, body: body2 };
7964
- run.status = code === 0 ? "completed" : "failed";
7965
- run.finishedAt = Date.now();
7966
- if (run.waiter) {
7967
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
7968
- run.waiter();
8250
+ const finalize = (code) => {
8251
+ void (async () => {
8252
+ if (settled) return;
8253
+ settled = true;
8254
+ watchdog.dispose();
8255
+ void cleanupTmp(tmpDir);
8256
+ await Promise.all([endStream(replyStream), endStream(activityStream)]);
8257
+ run.exitCode = code;
8258
+ const output = replyText.trim();
8259
+ const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
8260
+ if (run.status === "cancelled") {
8261
+ await Promise.all([rm(replyFile, { force: true }), rm(activityFile, { force: true })]);
8262
+ run.finishedAt = Date.now();
8263
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
8264
+ run.waiter?.();
7969
8265
  delegateStatusWidget.poke();
7970
8266
  return;
7971
8267
  }
7972
- if (run.consumed) {
7973
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8268
+ try {
8269
+ const file2 = replyFile;
8270
+ if (output === "") {
8271
+ const fallback = stderrText.trim();
8272
+ await appendFile(file2, fallback ? `${fallback}
8273
+ ` : "(no output)\n");
8274
+ }
8275
+ const effectiveCode = code ?? (output || stderrText ? 0 : null);
8276
+ run.result = { code, file: file2, body: body2 };
8277
+ run.status = effectiveCode === 0 ? "completed" : "failed";
8278
+ run.finishedAt = Date.now();
8279
+ if (run.waiter) {
8280
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
8281
+ run.waiter();
8282
+ delegateStatusWidget.poke();
8283
+ return;
8284
+ }
8285
+ if (run.consumed) {
8286
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8287
+ delegateStatusWidget.poke();
8288
+ return;
8289
+ }
8290
+ const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
8291
+ run.injected = injected;
8292
+ debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
8293
+ delegateStatusWidget.poke();
8294
+ } catch (err) {
8295
+ run.status = "failed";
8296
+ run.finishedAt = Date.now();
8297
+ debug.event("delegate-done-error", { runId, error: String(err) });
8298
+ run.waiter?.();
7974
8299
  delegateStatusWidget.poke();
7975
- return;
7976
8300
  }
7977
- const injected = injectResult(pi, args.agent, runId, args.task, code, file2);
7978
- run.injected = injected;
7979
- debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
7980
- delegateStatusWidget.poke();
7981
- }).catch((err) => {
7982
- run.status = "failed";
7983
- run.finishedAt = Date.now();
7984
- debug.event("delegate-done-error", { runId, error: String(err) });
7985
- run.waiter?.();
7986
- delegateStatusWidget.poke();
7987
- });
7988
- });
8301
+ })();
8302
+ };
8303
+ child.on("close", (code) => finalize(code));
7989
8304
  child.on("error", (err) => {
8305
+ if (settled) return;
8306
+ settled = true;
8307
+ watchdog.dispose();
7990
8308
  void cleanupTmp(tmpDir);
8309
+ void replyStream.destroy();
8310
+ void activityStream?.destroy();
8311
+ void rm(replyFile, { force: true });
8312
+ void rm(activityFile, { force: true });
7991
8313
  if (run.status === "running" || run.status === "cancelled") {
7992
8314
  run.status = run.status === "cancelled" ? "cancelled" : "failed";
7993
8315
  run.finishedAt = Date.now();
@@ -8002,6 +8324,8 @@ async function runDelegate(pi, args, ctx, signal) {
8002
8324
  `Delegated to **${args.agent}** (runId \`${runId}\`).`,
8003
8325
  `Task: ${truncate2(args.task, 160)}`,
8004
8326
  `Running in the background at \`${cwd}\`.`,
8327
+ useJsonStream ? `Live activity is streaming to \`${activityFile}\` \u2014 read it anytime to watch the delegate work (tool calls and their output${args.showThinking ? ", plus thinking" : ""}).` : `The reply is streaming to \`${replyFile}\` \u2014 read it anytime to see partial output (this host has no json event mode, so tool activity is not visible).`,
8328
+ `A watchdog force-finishes a hung run: no output for ${IDLE_GRACE_MS / 6e4}m, 10s after output ends, or a ${ASYNC_TIMEOUT_MS / 6e4}m hard limit \u2014 the result reflects whatever was produced.`,
8005
8329
  ``,
8006
8330
  `Call acp_delegate_wait({ runId: "${runId}" }) to block for the result (default 10s timeout). If the wait times out, or you skip it, a completion notification (with the result file path) is still injected here automatically when the delegate finishes \u2014 so you may also just continue other work now and let the result find you.`
8007
8331
  ].join("\n");
@@ -8020,7 +8344,9 @@ async function buildChildArgs(args, rolePrompt, ctx) {
8020
8344
  ---
8021
8345
 
8022
8346
  Complete the task below.`, "utf8");
8023
- const cliArgs = ["-p", "--no-session", "--append-system-prompt", promptFile];
8347
+ const isAsync = args.async !== false && ctx.mode !== "print" && ctx.mode !== "json";
8348
+ const useJsonStream = isAsync && isPiHost(ctx.sessionManager);
8349
+ const cliArgs = useJsonStream ? ["--mode", "json", "--no-session", "--append-system-prompt", promptFile] : ["-p", "--no-session", "--append-system-prompt", promptFile];
8024
8350
  const agentDef = AGENTS[args.agent];
8025
8351
  if (agentDef?.restricted) {
8026
8352
  const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
@@ -8033,7 +8359,7 @@ Complete the task below.`, "utf8");
8033
8359
  } else if (ctx.model) {
8034
8360
  cliArgs.push("--provider", ctx.model.provider, "--model", ctx.model.id);
8035
8361
  }
8036
- return { cliArgs, tmpDir };
8362
+ return { cliArgs, tmpDir, isAsync, useJsonStream };
8037
8363
  }
8038
8364
  function waitForChild(child, signal) {
8039
8365
  return new Promise((resolve2) => {
@@ -8079,7 +8405,7 @@ function formatSyncResult(agent, runId, task, r, file) {
8079
8405
  const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
8080
8406
  return formatPayload(header, file, task, body);
8081
8407
  }
8082
- function injectResult(pi, agent, runId, task, code, file) {
8408
+ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8083
8409
  const send = pi.sendUserMessage;
8084
8410
  if (typeof send !== "function") {
8085
8411
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
@@ -8088,7 +8414,8 @@ function injectResult(pi, agent, runId, task, code, file) {
8088
8414
  const status = code === 0 ? "completed" : "failed";
8089
8415
  const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
8090
8416
  const remainingLine = remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running; keep doing other work and their notifications will arrive as they finish.` : " No delegates are currently running.";
8091
- const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${remainingLine} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
8417
+ const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
8418
+ const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine} This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.`;
8092
8419
  const text = formatPayload(header, file, task);
8093
8420
  try {
8094
8421
  send.call(pi, text, { deliverAs: "followUp" });
@@ -8232,7 +8559,7 @@ async function statusReport(runtime, ctx) {
8232
8559
  const activeBlocksList = state.blocks.filter((b) => b.active);
8233
8560
  const totalBlocksList = state.blocks;
8234
8561
  const lines = [];
8235
- const versionStr = "0.1.26" ? `billion-context-pi@${"0.1.26"}` : "";
8562
+ const versionStr = "0.1.28" ? `billion-context-pi@${"0.1.28"}` : "";
8236
8563
  lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
8237
8564
  lines.push("\u2502 ACP Context Analysis \u2502");
8238
8565
  lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
@@ -8376,9 +8703,11 @@ When a background delegate finishes, an automated completion notification is inj
8376
8703
 
8377
8704
  // src/tool-guardrails.ts
8378
8705
  import {
8379
- isBashToolResult,
8380
8706
  isToolCallEventType
8381
8707
  } from "@earendil-works/pi-coding-agent";
8708
+ function isBashToolResult(e) {
8709
+ return e.toolName === "bash";
8710
+ }
8382
8711
  function resolveBashTimeout(input, defaultTimeout) {
8383
8712
  if (input.timeout !== void 0) return void 0;
8384
8713
  const d = defaultTimeout ?? DEFAULT_TOOL_BASH_TIMEOUT;
@@ -8590,7 +8919,7 @@ async function checkForUpdate(autoUpdate, notify) {
8590
8919
  const data = await res.json();
8591
8920
  const latest = data.version;
8592
8921
  if (!latest) return;
8593
- const current = runtimeVersion ?? "0.1.26";
8922
+ const current = runtimeVersion ?? "0.1.28";
8594
8923
  debug.event("update-check", {
8595
8924
  current,
8596
8925
  latest,