billion-context-pi 0.1.27 → 0.1.29

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
@@ -2411,9 +2411,9 @@ function projectMessage(message, id) {
2411
2411
  if (calls.length === 1) {
2412
2412
  const call = calls[0];
2413
2413
  const argStr = stringifyArgs(call.arguments);
2414
- const text = argStr && textParts ? `${textParts}
2414
+ const text2 = argStr && textParts ? `${textParts}
2415
2415
  ${argStr}` : argStr || textParts;
2416
- return [{ id, role: "assistant", contentType: "tool-call", toolName: call.name, toolCallId: call.id, text }];
2416
+ return [{ id, role: "assistant", contentType: "tool-call", toolName: call.name, toolCallId: call.id, text: text2 }];
2417
2417
  }
2418
2418
  return calls.map((call) => {
2419
2419
  const argStr = stringifyArgs(call.arguments);
@@ -2427,7 +2427,9 @@ ${argStr}` : argStr || textParts;
2427
2427
  };
2428
2428
  });
2429
2429
  }
2430
- return [{ id, role: "assistant", contentType: "text", text: extractText(msg.content) }];
2430
+ const text = extractText(msg.content);
2431
+ if (!text.trim()) return [];
2432
+ return [{ id, role: "assistant", contentType: "text", text }];
2431
2433
  }
2432
2434
  const customText = extractText(msg.content) || fallbackText(msg);
2433
2435
  return customText.length > 0 ? [{ id, role: "user", contentType: "text", text: customText }] : [];
@@ -2574,7 +2576,87 @@ function peelRefTagBlocks(blocks) {
2574
2576
 
2575
2577
  // src/state.ts
2576
2578
  import { promises as fs } from "fs";
2579
+ import * as path2 from "path";
2580
+
2581
+ // src/log.ts
2582
+ import { appendFileSync, mkdirSync, statSync, renameSync, existsSync } from "fs";
2577
2583
  import * as path from "path";
2584
+ import { homedir } from "os";
2585
+ var MAX_BYTES = 10 * 1024 * 1024;
2586
+ var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
2587
+ function resolveLogFile() {
2588
+ return process.env.ACP_LOG_FILE ?? path.join(homedir(), ".pi", "acp.log");
2589
+ }
2590
+ var runtimeDebug = null;
2591
+ function setDebugEnabled(enabled) {
2592
+ runtimeDebug = enabled;
2593
+ }
2594
+ function debugOn() {
2595
+ return runtimeDebug ?? ENV_DEBUG;
2596
+ }
2597
+ function fmt(v) {
2598
+ if (typeof v === "string") return v;
2599
+ if (v instanceof Error) return v.stack || String(v);
2600
+ try {
2601
+ return JSON.stringify(v);
2602
+ } catch {
2603
+ return String(v);
2604
+ }
2605
+ }
2606
+ function ts() {
2607
+ return (/* @__PURE__ */ new Date()).toISOString();
2608
+ }
2609
+ function writeLine(level, scope, fields) {
2610
+ const file = resolveLogFile();
2611
+ try {
2612
+ if (existsSync(file) && statSync(file).size >= MAX_BYTES) {
2613
+ renameSync(file, file + ".old");
2614
+ }
2615
+ } catch {
2616
+ }
2617
+ const body = Object.keys(fields).map((k) => `${k}=${fmt(fields[k])}`).join(" ");
2618
+ const line = `${ts()} [${level}] [${scope}] ${body}
2619
+ `;
2620
+ try {
2621
+ mkdirSync(path.dirname(file), { recursive: true });
2622
+ appendFileSync(file, line);
2623
+ } catch {
2624
+ }
2625
+ }
2626
+ function closeLogStream() {
2627
+ }
2628
+ function logError(scope, fields) {
2629
+ writeLine("error", scope, fields);
2630
+ }
2631
+ function logWarn(scope, fields) {
2632
+ writeLine("warn", scope, fields);
2633
+ }
2634
+ function logInfo(scope, fields) {
2635
+ writeLine("info", scope, fields);
2636
+ }
2637
+ function logThrow(scope, err, extra = {}) {
2638
+ const fields = { ...extra };
2639
+ if (err instanceof Error) {
2640
+ fields.error = err.message;
2641
+ fields.stack = err.stack ?? "";
2642
+ } else {
2643
+ fields.error = String(err);
2644
+ }
2645
+ writeLine("error", scope, fields);
2646
+ }
2647
+ var debug = {
2648
+ get enabled() {
2649
+ return debugOn();
2650
+ },
2651
+ get logFile() {
2652
+ return resolveLogFile();
2653
+ },
2654
+ event(scope, fields) {
2655
+ if (debugOn()) writeLine("debug", scope, fields);
2656
+ }
2657
+ };
2658
+
2659
+ // src/state.ts
2578
2660
  var STATE_SUFFIX = ".acp.json";
2579
2661
  function stateFileFor(sessionFile) {
2580
2662
  if (sessionFile) return sessionFile + STATE_SUFFIX;
@@ -2592,7 +2674,11 @@ var SessionStateStore = class {
2592
2674
  const raw = await fs.readFile(file, "utf8");
2593
2675
  const parsed = JSON.parse(raw);
2594
2676
  if (parsed && Array.isArray(parsed.blocks)) state = mergeInitialState(parsed);
2595
- } catch {
2677
+ } catch (e) {
2678
+ const code = e.code;
2679
+ if (code !== "ENOENT") {
2680
+ logWarn("state", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
2681
+ }
2596
2682
  }
2597
2683
  }
2598
2684
  this.cache = state;
@@ -2604,12 +2690,17 @@ var SessionStateStore = class {
2604
2690
  if (!file) return;
2605
2691
  this.cache = state;
2606
2692
  this.loadedKey = file;
2607
- const dir = path.dirname(file);
2608
- await fs.mkdir(dir, { recursive: true }).catch(() => {
2693
+ const dir = path2.dirname(file);
2694
+ await fs.mkdir(dir, { recursive: true }).catch((e) => {
2695
+ logError("state", { event: "save-mkdir-failed", dir, error: e instanceof Error ? e.message : String(e) });
2609
2696
  });
2610
- const tmp = path.join(dir, `.acp-tmp-${path.basename(file)}`);
2611
- await fs.writeFile(tmp, JSON.stringify(state), "utf8");
2612
- await fs.rename(tmp, file);
2697
+ const tmp = path2.join(dir, `.acp-tmp-${path2.basename(file)}`);
2698
+ try {
2699
+ await fs.writeFile(tmp, JSON.stringify(state), "utf8");
2700
+ await fs.rename(tmp, file);
2701
+ } catch (e) {
2702
+ logError("state", { event: "save-failed", file, error: e instanceof Error ? e.message : String(e) });
2703
+ }
2613
2704
  }
2614
2705
  invalidate() {
2615
2706
  this.cache = null;
@@ -2635,6 +2726,10 @@ function readContextEntries(sm) {
2635
2726
  if (typeof source.getBranch === "function") return source.getBranch();
2636
2727
  return [];
2637
2728
  }
2729
+ function isPiHost(sm) {
2730
+ const source = sm;
2731
+ return typeof source.buildContextEntries === "function";
2732
+ }
2638
2733
  function createRuntime(adapter) {
2639
2734
  const core = createCore({ countTokens: defaultCountTokens });
2640
2735
  const store = new SessionStateStore();
@@ -7078,64 +7173,6 @@ __export(typebox_exports, {
7078
7173
  With: () => With2
7079
7174
  });
7080
7175
 
7081
- // src/log.ts
7082
- import { promises as fs2 } from "fs";
7083
- import * as path2 from "path";
7084
- import { homedir } from "os";
7085
- var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
7086
- var LOG_FILE = process.env.ACP_LOG_FILE ?? path2.join(homedir(), ".pi", "acp-debug.log");
7087
- var runtimeDebug = null;
7088
- var initialized = false;
7089
- function setDebugEnabled(enabled) {
7090
- runtimeDebug = enabled;
7091
- }
7092
- function debugOn() {
7093
- return runtimeDebug ?? ENV_DEBUG;
7094
- }
7095
- async function write(line) {
7096
- if (!debugOn()) return;
7097
- if (!initialized) {
7098
- initialized = true;
7099
- await fs2.mkdir(path2.dirname(LOG_FILE), { recursive: true }).catch(() => {
7100
- });
7101
- }
7102
- await fs2.appendFile(LOG_FILE, line, "utf8").catch(() => {
7103
- });
7104
- }
7105
- var debug = {
7106
- get enabled() {
7107
- return debugOn();
7108
- },
7109
- get logFile() {
7110
- return LOG_FILE;
7111
- },
7112
- event(scope, fields) {
7113
- if (!debugOn()) return;
7114
- const ts = (/* @__PURE__ */ new Date()).toISOString();
7115
- const body = Object.entries(fields).map(([k, v]) => `${k}=${fmt(v)}`).join(" ");
7116
- void write(`${ts} [${scope}] ${body}
7117
- `);
7118
- }
7119
- };
7120
- function fmt(v) {
7121
- if (typeof v === "string") return v;
7122
- if (Array.isArray(v)) {
7123
- try {
7124
- return JSON.stringify(v);
7125
- } catch {
7126
- return `[${v.length}]`;
7127
- }
7128
- }
7129
- if (v && typeof v === "object") {
7130
- try {
7131
- return JSON.stringify(v);
7132
- } catch {
7133
- return String(v);
7134
- }
7135
- }
7136
- return String(v);
7137
- }
7138
-
7139
7176
  // src/tokens.ts
7140
7177
  function collectCoveredMessageIds(state) {
7141
7178
  const ids = /* @__PURE__ */ new Set();
@@ -7191,7 +7228,13 @@ function makeCompressTool(runtime) {
7191
7228
  ],
7192
7229
  parameters: CompressParams,
7193
7230
  async execute(toolCallId, params, _signal, _onUpdate, ctx) {
7194
- const result = await handleCompress(params, runtime, ctx, toolCallId);
7231
+ let result;
7232
+ try {
7233
+ result = await handleCompress(params, runtime, ctx, toolCallId);
7234
+ } catch (e) {
7235
+ logThrow("compress", e, { sid: ctx.sessionManager.getSessionId(), ranges: params.content?.length ?? 0 });
7236
+ throw e;
7237
+ }
7195
7238
  return { details: void 0, content: [{ type: "text", text: result }] };
7196
7239
  }
7197
7240
  };
@@ -7236,6 +7279,24 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
7236
7279
  activeAfter: applied.state.blocks.filter((b) => b.active).length,
7237
7280
  newBlocks: newBlocks.map((b) => ({ blockId: b.blockId, tier: b.tier, summaryLen: b.summary.length, directMsgCount: b.directMessageIds.length, effectiveMsgCount: b.effectiveMessageIds.length, summary: b.summary }))
7238
7281
  });
7282
+ logInfo("compress", {
7283
+ sid: ctx.sessionManager.getSessionId(),
7284
+ event: "applied",
7285
+ ranges: ranges.length,
7286
+ blocksCreated,
7287
+ tokensCompressed,
7288
+ beforeTokens,
7289
+ afterTokens,
7290
+ warnings: warnings.length,
7291
+ errors: errors.length,
7292
+ newBlockIds: newBlocks.map((b) => b.blockId)
7293
+ });
7294
+ if (errors.length > 0) {
7295
+ logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "errors", count: errors.length, errors: errors.slice(0, 5) });
7296
+ }
7297
+ if (warnings.length > 0) {
7298
+ logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "warnings", count: warnings.length, warnings: warnings.slice(0, 5) });
7299
+ }
7239
7300
  const lines = [`\u25A3 ACP | ${formatK2(beforeTokens)} \u2192 ${formatK2(afterTokens)} tokens (~${formatK2(tokensCompressed)} reclaimed, ${blocksCreated} block${blocksCreated > 1 ? "s" : ""})`];
7240
7301
  if (warnings.length > 0) lines.push("\u26A0\uFE0F " + warnings.join("; "));
7241
7302
  if (errors.length > 0) lines.push("Errors: " + errors.join("; "));
@@ -7269,7 +7330,13 @@ function makeDecompressTool(runtime) {
7269
7330
  ],
7270
7331
  parameters: DecompressParams,
7271
7332
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7272
- const result = await handleDecompress(params, runtime, ctx);
7333
+ let result;
7334
+ try {
7335
+ result = await handleDecompress(params, runtime, ctx);
7336
+ } catch (e) {
7337
+ logThrow("decompress", e, { sid: ctx.sessionManager.getSessionId(), blockId: params.blockId });
7338
+ throw e;
7339
+ }
7273
7340
  return { details: void 0, content: [{ type: "text", text: result }] };
7274
7341
  }
7275
7342
  };
@@ -7318,16 +7385,21 @@ async function handleMessageRef(ref, ownerBlockId, args, ctx) {
7318
7385
  const wantFile = args.toFile !== void 0 || args.inline === false || text.length >= MESSAGE_INLINE_THRESHOLD;
7319
7386
  if (!wantFile) {
7320
7387
  debug.event("decompress-message", { ref, ownerBlockId, mode: "inline", chars: text.length });
7388
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "inline", ref, ownerBlockId, chars: text.length });
7321
7389
  return `Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) restored inline:
7322
7390
 
7323
7391
  ${text}`;
7324
7392
  }
7325
7393
  const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(`msg-${ref}`);
7326
- if (typeof targetPath === "object" && "error" in targetPath) return targetPath.error;
7394
+ if (typeof targetPath === "object" && "error" in targetPath) {
7395
+ logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message-path-rejected", ref, toFile: args.toFile });
7396
+ return targetPath.error;
7397
+ }
7327
7398
  await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
7328
7399
  });
7329
7400
  await writeFile(targetPath, text, "utf8");
7330
7401
  debug.event("decompress-message", { ref, ownerBlockId, mode: "file", path: targetPath, chars: text.length });
7402
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "file", ref, ownerBlockId, path: targetPath, chars: text.length });
7331
7403
  return [
7332
7404
  `Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) written to ${targetPath}.`,
7333
7405
  "Block stays compressed \u2014 context unchanged. Use the read tool to access the content.",
@@ -7355,16 +7427,21 @@ async function handleDecompress(args, runtime, ctx) {
7355
7427
  if (count === 0) return `Block ${blockId} has no restorable message content.`;
7356
7428
  if (args.inline === true && !args.toFile) {
7357
7429
  debug.event("decompress", { blockId, full, count, mode: "inline" });
7430
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "inline", blockId, full, count });
7358
7431
  return `Restored block ${blockId} (${count} item${count === 1 ? "" : "s"}) inline:
7359
7432
 
7360
7433
  ${text}`;
7361
7434
  }
7362
7435
  const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(blockId);
7363
- if (typeof targetPath === "object" && "error" in targetPath) return targetPath.error;
7436
+ if (typeof targetPath === "object" && "error" in targetPath) {
7437
+ logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block-path-rejected", blockId, toFile: args.toFile });
7438
+ return targetPath.error;
7439
+ }
7364
7440
  await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
7365
7441
  });
7366
7442
  await writeFile(targetPath, text, "utf8");
7367
7443
  debug.event("decompress", { blockId, full, count, mode: "file", path: targetPath, chars: text.length });
7444
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "file", blockId, full, count, path: targetPath, chars: text.length });
7368
7445
  const itemWord = count === 1 ? "item" : "items";
7369
7446
  const lines = [
7370
7447
  `Block ${blockId} (${count} ${itemWord}, ${text.length} chars) written to ${targetPath}.`,
@@ -7454,7 +7531,13 @@ function makeSearchTool(runtime) {
7454
7531
  ],
7455
7532
  parameters: SearchParams,
7456
7533
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7457
- const result = await handleSearch(params, runtime, ctx);
7534
+ let result;
7535
+ try {
7536
+ result = await handleSearch(params, runtime, ctx);
7537
+ } catch (e) {
7538
+ logThrow("search", e, { sid: ctx.sessionManager.getSessionId(), query: params.query });
7539
+ throw e;
7540
+ }
7458
7541
  return { details: void 0, content: [{ type: "text", text: result }] };
7459
7542
  }
7460
7543
  };
@@ -7519,7 +7602,13 @@ function makeStatusTool(runtime) {
7519
7602
  ],
7520
7603
  parameters: StatusParams,
7521
7604
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7522
- const result = await handleStatus(params, runtime, ctx);
7605
+ let result;
7606
+ try {
7607
+ result = await handleStatus(params, runtime, ctx);
7608
+ } catch (e) {
7609
+ logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
7610
+ throw e;
7611
+ }
7523
7612
  return { details: void 0, content: [{ type: "text", text: result }] };
7524
7613
  }
7525
7614
  };
@@ -7566,7 +7655,8 @@ ${extra.join("\n")}` : base;
7566
7655
  import {
7567
7656
  spawn
7568
7657
  } from "child_process";
7569
- import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm } from "fs/promises";
7658
+ import { createWriteStream } from "fs";
7659
+ import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
7570
7660
  import { tmpdir as tmpdir2 } from "os";
7571
7661
  import { join as join4 } from "path";
7572
7662
 
@@ -7658,9 +7748,203 @@ var delegateStatusWidget = {
7658
7748
  }
7659
7749
  };
7660
7750
 
7751
+ // src/delegate-watchdog.ts
7752
+ function attachWatchdogs(child, hooks, opts) {
7753
+ let idleTimer;
7754
+ let eofTimer;
7755
+ let killGraceTimer;
7756
+ let timeoutTimer;
7757
+ const clearTimers = () => {
7758
+ if (idleTimer) clearTimeout(idleTimer);
7759
+ if (eofTimer) clearTimeout(eofTimer);
7760
+ if (killGraceTimer) clearTimeout(killGraceTimer);
7761
+ if (timeoutTimer) clearTimeout(timeoutTimer);
7762
+ };
7763
+ const killByWatchdog = (reason) => {
7764
+ if (hooks.isSettled()) return;
7765
+ hooks.onKill(reason);
7766
+ try {
7767
+ child.kill("SIGTERM");
7768
+ } catch {
7769
+ }
7770
+ killGraceTimer = setTimeout(() => {
7771
+ if (hooks.isSettled()) return;
7772
+ try {
7773
+ child.kill("SIGKILL");
7774
+ } catch {
7775
+ }
7776
+ }, opts.killGraceMs);
7777
+ killGraceTimer.unref?.();
7778
+ };
7779
+ const poke = () => {
7780
+ if (idleTimer) clearTimeout(idleTimer);
7781
+ idleTimer = setTimeout(() => killByWatchdog(`no output for ${opts.idleMs / 6e4}m`), opts.idleMs);
7782
+ idleTimer.unref?.();
7783
+ };
7784
+ poke();
7785
+ timeoutTimer = setTimeout(() => killByWatchdog(`${opts.timeoutMs / 6e4}m limit`), opts.timeoutMs);
7786
+ timeoutTimer.unref?.();
7787
+ const onStdoutEnd = () => {
7788
+ if (hooks.isSettled()) return;
7789
+ eofTimer = setTimeout(() => {
7790
+ if (hooks.isSettled()) return;
7791
+ hooks.onEofGrace();
7792
+ try {
7793
+ child.kill("SIGTERM");
7794
+ } catch {
7795
+ }
7796
+ }, opts.eofGraceMs);
7797
+ eofTimer.unref?.();
7798
+ };
7799
+ child.stdout?.once("end", onStdoutEnd);
7800
+ return {
7801
+ poke,
7802
+ dispose: () => {
7803
+ clearTimers();
7804
+ child.stdout?.removeListener("end", onStdoutEnd);
7805
+ }
7806
+ };
7807
+ }
7808
+
7809
+ // src/delegate-events.ts
7810
+ var ThinkingCollector = class {
7811
+ constructor(showThinking) {
7812
+ this.showThinking = showThinking;
7813
+ }
7814
+ showThinking;
7815
+ buf = "";
7816
+ push(delta) {
7817
+ this.buf += delta;
7818
+ }
7819
+ /** Return the segment line to write ("" when empty or disabled), resetting. */
7820
+ flush() {
7821
+ const text = this.buf.trim();
7822
+ this.buf = "";
7823
+ if (!this.showThinking || !text) return "";
7824
+ return `[thinking] ${text}
7825
+ `;
7826
+ }
7827
+ };
7828
+ function parseEventLine(line) {
7829
+ let ev;
7830
+ try {
7831
+ ev = JSON.parse(line);
7832
+ } catch {
7833
+ return null;
7834
+ }
7835
+ if (typeof ev !== "object" || ev === null) return null;
7836
+ const e = ev;
7837
+ if (e.type === "message_update") {
7838
+ const am = e.assistantMessageEvent;
7839
+ if (typeof am !== "object" || am === null) return null;
7840
+ const msg = am;
7841
+ switch (msg.type) {
7842
+ case "text_delta":
7843
+ return { kind: "reply-delta", delta: String(msg.delta ?? "") };
7844
+ case "text_end":
7845
+ return { kind: "reply-complete", content: String(msg.content ?? "") };
7846
+ case "thinking_delta":
7847
+ return { kind: "thinking-delta", delta: String(msg.delta ?? "") };
7848
+ case "thinking_end":
7849
+ return { kind: "thinking-end" };
7850
+ default:
7851
+ return null;
7852
+ }
7853
+ }
7854
+ if (e.type === "tool_execution_start") {
7855
+ return {
7856
+ kind: "tool-start",
7857
+ toolName: String(e.toolName ?? ""),
7858
+ argsText: formatArgs(e.args)
7859
+ };
7860
+ }
7861
+ if (e.type === "tool_execution_update") {
7862
+ return {
7863
+ kind: "tool-update",
7864
+ toolCallId: String(e.toolCallId ?? ""),
7865
+ text: extractContentText(e.partialResult)
7866
+ };
7867
+ }
7868
+ if (e.type === "tool_execution_end") {
7869
+ return {
7870
+ kind: "tool-end",
7871
+ toolName: String(e.toolName ?? ""),
7872
+ isError: Boolean(e.isError)
7873
+ };
7874
+ }
7875
+ if (e.type === "auto_retry_start") {
7876
+ return {
7877
+ kind: "retry-start",
7878
+ attempt: Number(e.attempt ?? 0),
7879
+ maxAttempts: Number(e.maxAttempts ?? 0),
7880
+ delayMs: Number(e.delayMs ?? 0),
7881
+ errorMessage: String(e.errorMessage ?? "")
7882
+ };
7883
+ }
7884
+ if (e.type === "auto_retry_end") {
7885
+ return {
7886
+ kind: "retry-end",
7887
+ success: Boolean(e.success),
7888
+ attempt: Number(e.attempt ?? 0)
7889
+ };
7890
+ }
7891
+ return null;
7892
+ }
7893
+ function formatArgs(args) {
7894
+ if (args && typeof args === "object") {
7895
+ const a = args;
7896
+ if (typeof a.command === "string") return a.command;
7897
+ }
7898
+ try {
7899
+ return JSON.stringify(args);
7900
+ } catch {
7901
+ return String(args);
7902
+ }
7903
+ }
7904
+ function extractContentText(payload) {
7905
+ if (!payload || typeof payload !== "object") return "";
7906
+ const content = payload.content;
7907
+ if (!Array.isArray(content)) return "";
7908
+ return content.map((c) => c && typeof c === "object" ? String(c.text ?? "") : "").join("");
7909
+ }
7910
+ function activityLines(ev, opts) {
7911
+ switch (ev.kind) {
7912
+ case "tool-start":
7913
+ return [`[tool] ${ev.toolName}${ev.argsText ? ` ${ev.argsText}` : ""}
7914
+ `];
7915
+ case "tool-update": {
7916
+ if (!ev.text) return [];
7917
+ return [ev.text.endsWith("\n") ? ev.text : `${ev.text}
7918
+ `];
7919
+ }
7920
+ case "tool-end":
7921
+ return [`[done] ${ev.toolName}${ev.isError ? " (error)" : ""}
7922
+ `];
7923
+ case "thinking-delta":
7924
+ return opts.showThinking ? [`[thinking] ${ev.delta}
7925
+ `] : [];
7926
+ case "retry-start":
7927
+ return [`[retry] attempt ${ev.attempt}/${ev.maxAttempts}, backoff ${ev.delayMs}ms${ev.errorMessage ? ` \u2014 ${ev.errorMessage}` : ""}
7928
+ `];
7929
+ case "retry-end":
7930
+ return [`[retry] attempt ${ev.attempt} ${ev.success ? "succeeded" : "failed"}
7931
+ `];
7932
+ default:
7933
+ return [];
7934
+ }
7935
+ }
7936
+ function newPortion(text, prev) {
7937
+ if (text.startsWith(prev)) return text.slice(prev.length);
7938
+ return text;
7939
+ }
7940
+
7661
7941
  // src/delegate-tool.ts
7662
7942
  var MAX_DEPTH = 2;
7663
7943
  var SYNC_TIMEOUT_MS = 5 * 6e4;
7944
+ var EOF_GRACE_MS = 1e4;
7945
+ var IDLE_GRACE_MS = 5 * 6e4;
7946
+ var ASYNC_TIMEOUT_MS = 30 * 6e4;
7947
+ var KILL_GRACE_MS = 1e4;
7664
7948
  var RESULT_SUMMARY_CHARS = 500;
7665
7949
  var OUT_DIR = join4(tmpdir2(), "acp-delegate");
7666
7950
  var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
@@ -7728,6 +8012,11 @@ var DelegateParams = typebox_exports.Object({
7728
8012
  typebox_exports.Boolean({
7729
8013
  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."
7730
8014
  })
8015
+ ),
8016
+ showThinking: typebox_exports.Optional(
8017
+ typebox_exports.Boolean({
8018
+ description: "If true, the delegate's thinking deltas are also written to the live activity file (default: false \u2014 only tool activity is shown)."
8019
+ })
7731
8020
  )
7732
8021
  });
7733
8022
  var CancelParams = typebox_exports.Object({
@@ -7785,7 +8074,8 @@ The delegate runs in its own clean pi process \u2014 it does NOT see this conver
7785
8074
  };
7786
8075
  }
7787
8076
  function formatRunResult(run) {
7788
- 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)}`;
8077
+ const timeoutNote = run.timedOut ? ` (timed out: ${run.timedOut})` : "";
8078
+ 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)}`;
7789
8079
  return formatPayload(header, run.result?.file ?? "", run.task, run.result?.body);
7790
8080
  }
7791
8081
  function remainingLineForWait(selfRunId) {
@@ -7898,6 +8188,7 @@ function makeDelegateCancelTool(_pi) {
7898
8188
  run.child?.kill("SIGTERM");
7899
8189
  } catch (err) {
7900
8190
  debug.event("delegate-cancel-kill-error", { runId, error: String(err) });
8191
+ logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
7901
8192
  }
7902
8193
  delegateStatusWidget.poke();
7903
8194
  return {
@@ -7924,13 +8215,15 @@ async function runDelegate(pi, args, ctx, signal) {
7924
8215
  ...process.env,
7925
8216
  PI_ACP_DELEGATE_DEPTH: String(parentDepth + 1)
7926
8217
  };
7927
- const { cliArgs, tmpDir } = await buildChildArgs(args, agent.prompt, ctx);
8218
+ const { cliArgs, tmpDir, isAsync, useJsonStream } = await buildChildArgs(args, agent.prompt, ctx);
7928
8219
  const requestedAsync = args.async !== false;
7929
- const isAsync = requestedAsync && ctx.mode !== "print" && ctx.mode !== "json";
7930
8220
  if (requestedAsync && !isAsync) {
7931
8221
  debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
8222
+ logInfo("delegate", { event: "async-downgraded", reason: `mode=${ctx.mode}` });
7932
8223
  }
7933
- debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, cliArgs });
8224
+ const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
8225
+ debug.event("delegate-spawn", { agent: args.agent, runId, cwd, async: isAsync, useJsonStream, cliArgs });
8226
+ logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
7934
8227
  const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
7935
8228
  cwd,
7936
8229
  env: childEnv,
@@ -7939,14 +8232,96 @@ async function runDelegate(pi, args, ctx, signal) {
7939
8232
  });
7940
8233
  child.stdin?.once("error", (e) => {
7941
8234
  debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
8235
+ logError("delegate", { event: "stdin-error", runId, error: String(e) });
7942
8236
  });
7943
8237
  child.stdin?.end(args.task);
7944
- let stdoutChunks = [];
7945
8238
  let stderrText = "";
7946
- const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
7947
8239
  const startedAt = Date.now();
7948
8240
  if (isAsync) {
7949
- child.stdout?.on("data", (c) => stdoutChunks.push(c));
8241
+ let settled = false;
8242
+ const watchdog = attachWatchdogs(
8243
+ child,
8244
+ {
8245
+ isSettled: () => settled || run.status !== "running",
8246
+ onKill: (reason) => {
8247
+ run.timedOut = reason;
8248
+ debug.event("delegate-watchdog", { runId, reason });
8249
+ },
8250
+ onEofGrace: () => {
8251
+ run.timedOut = "output ended but process did not exit";
8252
+ debug.event("delegate-eof-grace", { runId, ms: EOF_GRACE_MS });
8253
+ }
8254
+ },
8255
+ { eofGraceMs: EOF_GRACE_MS, idleMs: IDLE_GRACE_MS, timeoutMs: ASYNC_TIMEOUT_MS, killGraceMs: KILL_GRACE_MS }
8256
+ );
8257
+ const replyFile = join4(OUT_DIR, `${runId}.out`);
8258
+ const activityFile = join4(OUT_DIR, `${runId}.activity`);
8259
+ await mkdir2(OUT_DIR, { recursive: true });
8260
+ const replyStream = createWriteStream(replyFile, { flags: "a" });
8261
+ const activityStream = useJsonStream ? createWriteStream(activityFile, { flags: "a" }) : null;
8262
+ const endStream = (s) => new Promise((resolve2) => {
8263
+ if (!s || s.destroyed || s.closed) return resolve2();
8264
+ s.end(() => resolve2());
8265
+ });
8266
+ let replyText = "";
8267
+ let stdoutBuf = "";
8268
+ const lastToolText = /* @__PURE__ */ new Map();
8269
+ const thinking = new ThinkingCollector(args.showThinking === true);
8270
+ const flushThinking = () => {
8271
+ const line = thinking.flush();
8272
+ if (line) activityStream?.write(line);
8273
+ };
8274
+ const handleEventLine = (line) => {
8275
+ const ev = parseEventLine(line);
8276
+ if (!ev) return;
8277
+ if (ev.kind === "thinking-delta") {
8278
+ thinking.push(ev.delta);
8279
+ return;
8280
+ }
8281
+ if (ev.kind === "thinking-end") {
8282
+ flushThinking();
8283
+ return;
8284
+ }
8285
+ if (ev.kind === "reply-delta") {
8286
+ flushThinking();
8287
+ replyText += ev.delta;
8288
+ replyStream.write(ev.delta);
8289
+ return;
8290
+ }
8291
+ if (ev.kind === "reply-complete") {
8292
+ flushThinking();
8293
+ replyText = ev.content;
8294
+ return;
8295
+ }
8296
+ if (ev.kind === "tool-update") {
8297
+ flushThinking();
8298
+ const prev = lastToolText.get(ev.toolCallId) ?? "";
8299
+ const add = newPortion(ev.text, prev);
8300
+ lastToolText.set(ev.toolCallId, ev.text);
8301
+ if (add) activityStream?.write(add.endsWith("\n") ? add : `${add}
8302
+ `);
8303
+ return;
8304
+ }
8305
+ flushThinking();
8306
+ const lines = activityLines(ev, { showThinking: args.showThinking === true });
8307
+ if (lines.length) activityStream?.write(lines.join(""));
8308
+ };
8309
+ child.stdout?.on("data", (c) => {
8310
+ watchdog.poke();
8311
+ if (useJsonStream) {
8312
+ stdoutBuf += c.toString("utf8");
8313
+ let nl;
8314
+ while ((nl = stdoutBuf.indexOf("\n")) >= 0) {
8315
+ const line = stdoutBuf.slice(0, nl);
8316
+ stdoutBuf = stdoutBuf.slice(nl + 1);
8317
+ handleEventLine(line);
8318
+ }
8319
+ } else {
8320
+ const text = c.toString("utf8");
8321
+ replyText += text;
8322
+ replyStream.write(text);
8323
+ }
8324
+ });
7950
8325
  child.stderr?.on("data", (c) => {
7951
8326
  stderrText += c.toString("utf8");
7952
8327
  });
@@ -7961,52 +8336,79 @@ async function runDelegate(pi, args, ctx, signal) {
7961
8336
  };
7962
8337
  runs.set(runId, run);
7963
8338
  delegateStatusWidget.poke();
7964
- child.on("close", (code) => {
7965
- void cleanupTmp(tmpDir);
7966
- const output = Buffer.concat(stdoutChunks).toString("utf8").trim();
7967
- run.exitCode = code;
7968
- const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
7969
- if (run.status === "cancelled") {
7970
- run.finishedAt = Date.now();
7971
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
7972
- run.waiter?.();
7973
- delegateStatusWidget.poke();
7974
- return;
7975
- }
7976
- void persistResult(runId, body2).then((file2) => {
7977
- run.result = { code, file: file2, body: body2 };
7978
- run.status = code === 0 ? "completed" : "failed";
7979
- run.finishedAt = Date.now();
7980
- if (run.waiter) {
7981
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
7982
- run.waiter();
8339
+ const finalize = (code) => {
8340
+ void (async () => {
8341
+ if (settled) return;
8342
+ settled = true;
8343
+ watchdog.dispose();
8344
+ void cleanupTmp(tmpDir);
8345
+ await Promise.all([endStream(replyStream), endStream(activityStream)]);
8346
+ run.exitCode = code;
8347
+ const output = replyText.trim();
8348
+ const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
8349
+ if (run.status === "cancelled") {
8350
+ await Promise.all([rm(replyFile, { force: true }), rm(activityFile, { force: true })]);
8351
+ run.finishedAt = Date.now();
8352
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, outLen: output.length });
8353
+ run.waiter?.();
7983
8354
  delegateStatusWidget.poke();
7984
8355
  return;
7985
8356
  }
7986
- if (run.consumed) {
7987
- debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8357
+ try {
8358
+ const file2 = replyFile;
8359
+ if (output === "") {
8360
+ const fallback = stderrText.trim();
8361
+ await appendFile(file2, fallback ? `${fallback}
8362
+ ` : "(no output)\n");
8363
+ }
8364
+ const effectiveCode = code ?? (output || stderrText ? 0 : null);
8365
+ run.result = { code, file: file2, body: body2 };
8366
+ run.status = effectiveCode === 0 ? "completed" : "failed";
8367
+ run.finishedAt = Date.now();
8368
+ if (run.waiter) {
8369
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
8370
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
8371
+ run.waiter();
8372
+ delegateStatusWidget.poke();
8373
+ return;
8374
+ }
8375
+ if (run.consumed) {
8376
+ debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8377
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8378
+ delegateStatusWidget.poke();
8379
+ return;
8380
+ }
8381
+ const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
8382
+ run.injected = injected;
8383
+ debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
8384
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected, outLen: output.length, file: file2 });
8385
+ delegateStatusWidget.poke();
8386
+ } catch (err) {
8387
+ run.status = "failed";
8388
+ run.finishedAt = Date.now();
8389
+ debug.event("delegate-done-error", { runId, error: String(err) });
8390
+ logError("delegate", { event: "done-error", runId, agent: args.agent, error: String(err) });
8391
+ run.waiter?.();
7988
8392
  delegateStatusWidget.poke();
7989
- return;
7990
8393
  }
7991
- const injected = injectResult(pi, args.agent, runId, args.task, code, file2);
7992
- run.injected = injected;
7993
- debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
7994
- delegateStatusWidget.poke();
7995
- }).catch((err) => {
7996
- run.status = "failed";
7997
- run.finishedAt = Date.now();
7998
- debug.event("delegate-done-error", { runId, error: String(err) });
7999
- run.waiter?.();
8000
- delegateStatusWidget.poke();
8001
- });
8002
- });
8394
+ })();
8395
+ };
8396
+ child.on("close", (code) => finalize(code));
8003
8397
  child.on("error", (err) => {
8398
+ if (settled) return;
8399
+ settled = true;
8400
+ watchdog.dispose();
8004
8401
  void cleanupTmp(tmpDir);
8402
+ void replyStream.destroy();
8403
+ void activityStream?.destroy();
8404
+ void rm(replyFile, { force: true });
8405
+ void rm(activityFile, { force: true });
8005
8406
  if (run.status === "running" || run.status === "cancelled") {
8006
8407
  run.status = run.status === "cancelled" ? "cancelled" : "failed";
8007
8408
  run.finishedAt = Date.now();
8008
8409
  run.result = { code: null, file: "", body: `spawn error: ${String(err)}` };
8009
8410
  debug.event("delegate-spawn-error", { runId, error: String(err) });
8411
+ logError("delegate", { event: "spawn-error", runId, agent: args.agent, error: String(err) });
8010
8412
  run.waiter?.();
8011
8413
  delegateStatusWidget.poke();
8012
8414
  }
@@ -8016,6 +8418,8 @@ async function runDelegate(pi, args, ctx, signal) {
8016
8418
  `Delegated to **${args.agent}** (runId \`${runId}\`).`,
8017
8419
  `Task: ${truncate2(args.task, 160)}`,
8018
8420
  `Running in the background at \`${cwd}\`.`,
8421
+ 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).`,
8422
+ `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.`,
8019
8423
  ``,
8020
8424
  `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.`
8021
8425
  ].join("\n");
@@ -8034,7 +8438,9 @@ async function buildChildArgs(args, rolePrompt, ctx) {
8034
8438
  ---
8035
8439
 
8036
8440
  Complete the task below.`, "utf8");
8037
- const cliArgs = ["-p", "--no-session", "--append-system-prompt", promptFile];
8441
+ const isAsync = args.async !== false && ctx.mode !== "print" && ctx.mode !== "json";
8442
+ const useJsonStream = isAsync && isPiHost(ctx.sessionManager);
8443
+ const cliArgs = useJsonStream ? ["--mode", "json", "--no-session", "--append-system-prompt", promptFile] : ["-p", "--no-session", "--append-system-prompt", promptFile];
8038
8444
  const agentDef = AGENTS[args.agent];
8039
8445
  if (agentDef?.restricted) {
8040
8446
  const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
@@ -8047,7 +8453,7 @@ Complete the task below.`, "utf8");
8047
8453
  } else if (ctx.model) {
8048
8454
  cliArgs.push("--provider", ctx.model.provider, "--model", ctx.model.id);
8049
8455
  }
8050
- return { cliArgs, tmpDir };
8456
+ return { cliArgs, tmpDir, isAsync, useJsonStream };
8051
8457
  }
8052
8458
  function waitForChild(child, signal) {
8053
8459
  return new Promise((resolve2) => {
@@ -8093,22 +8499,25 @@ function formatSyncResult(agent, runId, task, r, file) {
8093
8499
  const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
8094
8500
  return formatPayload(header, file, task, body);
8095
8501
  }
8096
- function injectResult(pi, agent, runId, task, code, file) {
8502
+ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8097
8503
  const send = pi.sendUserMessage;
8098
8504
  if (typeof send !== "function") {
8099
8505
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
8506
+ logWarn("delegate", { event: "inject-skipped", runId, reason: "sendUserMessage unavailable" });
8100
8507
  return false;
8101
8508
  }
8102
8509
  const status = code === 0 ? "completed" : "failed";
8103
8510
  const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
8104
8511
  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.";
8105
- 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.`;
8512
+ const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
8513
+ 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.`;
8106
8514
  const text = formatPayload(header, file, task);
8107
8515
  try {
8108
8516
  send.call(pi, text, { deliverAs: "followUp" });
8109
8517
  return true;
8110
8518
  } catch (err) {
8111
8519
  debug.event("delegate-inject-error", { runId, error: String(err) });
8520
+ logError("delegate", { event: "inject-error", runId, agent, error: String(err) });
8112
8521
  return false;
8113
8522
  }
8114
8523
  }
@@ -8136,6 +8545,7 @@ async function persistResult(runId, body) {
8136
8545
  return file;
8137
8546
  } catch (err) {
8138
8547
  debug.event("delegate-persist-error", { runId, file, error: String(err) });
8548
+ logError("delegate", { event: "persist-error", runId, file, error: String(err) });
8139
8549
  return "";
8140
8550
  }
8141
8551
  }
@@ -8246,7 +8656,7 @@ async function statusReport(runtime, ctx) {
8246
8656
  const activeBlocksList = state.blocks.filter((b) => b.active);
8247
8657
  const totalBlocksList = state.blocks;
8248
8658
  const lines = [];
8249
- const versionStr = "0.1.27" ? `billion-context-pi@${"0.1.27"}` : "";
8659
+ const versionStr = "0.1.29" ? `billion-context-pi@${"0.1.29"}` : "";
8250
8660
  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");
8251
8661
  lines.push("\u2502 ACP Context Analysis \u2502");
8252
8662
  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");
@@ -8490,11 +8900,13 @@ function wireToolGuardrails(pi, runtime) {
8490
8900
  if (next) {
8491
8901
  modified = next;
8492
8902
  debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
8903
+ logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
8493
8904
  }
8494
8905
  }
8495
8906
  if (timeoutSecs !== void 0) {
8496
8907
  modified = appendTimeoutNotice(modified ?? event.content, timeoutSecs);
8497
8908
  debug.event("guardrail-bash-timeout-notice", { secs: timeoutSecs });
8909
+ logInfo("guardrail", { event: "bash-timeout-notice", secs: timeoutSecs });
8498
8910
  }
8499
8911
  if (modified) return { content: modified };
8500
8912
  });
@@ -8602,29 +9014,36 @@ async function checkForUpdate(autoUpdate, notify) {
8602
9014
  signal: AbortSignal.timeout(5e3),
8603
9015
  headers: { Accept: "application/json" }
8604
9016
  });
8605
- if (!res.ok) return;
9017
+ if (!res.ok) {
9018
+ logWarn("update", { event: "check-http", status: res.status });
9019
+ return;
9020
+ }
8606
9021
  const data = await res.json();
8607
9022
  const latest = data.version;
8608
9023
  if (!latest) return;
8609
- const current = runtimeVersion ?? "0.1.27";
9024
+ const current = runtimeVersion ?? "0.1.29";
9025
+ const hasUpdate = isNewer(latest, current);
8610
9026
  debug.event("update-check", {
8611
9027
  current,
8612
9028
  latest,
8613
- hasUpdate: isNewer(latest, current)
9029
+ hasUpdate
8614
9030
  });
8615
- if (isNewer(latest, current)) {
9031
+ logInfo("update", { event: "check", current, latest, hasUpdate });
9032
+ if (hasUpdate) {
8616
9033
  const installed = await autoInstallLatest(latest);
8617
9034
  if (installed && notify) {
8618
9035
  notify(
8619
9036
  `\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart Pi to finish.\x1B[0m`
8620
9037
  );
9038
+ logInfo("update", { event: "auto-installed", from: current, to: latest });
8621
9039
  } else if (!installed && notify) {
8622
9040
  notify(
8623
9041
  `${PACKAGE_NAME} ${latest} available (you have ${current}). Run: pi update --extension npm:${PACKAGE_NAME}`
8624
9042
  );
8625
9043
  }
8626
9044
  }
8627
- } catch {
9045
+ } catch (e) {
9046
+ logWarn("update", { event: "check-error", error: e instanceof Error ? e.message : String(e) });
8628
9047
  } finally {
8629
9048
  updateInFlight = false;
8630
9049
  }
@@ -8638,7 +9057,7 @@ async function getRuntimeVersion() {
8638
9057
 
8639
9058
  // src/setup-subagent-tools.ts
8640
9059
  import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
8641
- import { existsSync } from "fs";
9060
+ import { existsSync as existsSync2 } from "fs";
8642
9061
  import { homedir as homedir4 } from "os";
8643
9062
  import { join as join6 } from "path";
8644
9063
  var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
@@ -8702,7 +9121,7 @@ async function ensureSubagentAcpTools(settingsPath) {
8702
9121
  return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
8703
9122
  }
8704
9123
  const backupPath = `${path4}.acp-bak`;
8705
- if (!existsSync(backupPath)) {
9124
+ if (!existsSync2(backupPath)) {
8706
9125
  try {
8707
9126
  await copyFile(path4, backupPath);
8708
9127
  } catch {
@@ -8754,15 +9173,19 @@ async function runSetupAndNotify(notify) {
8754
9173
  if (result.action === "updated" && notify) {
8755
9174
  notify(`ACP: enabled context tools (compress/decompress/search_context/acp_status) for subagents`);
8756
9175
  }
9176
+ if (result.action === "failed") {
9177
+ logWarn("setup", { event: "subagent-tools", action: result.action, reason: result.reason });
9178
+ }
8757
9179
  return result;
8758
9180
  } catch (e) {
8759
9181
  debug.event("setup-subagent-tools-error", { msg: String(e) });
9182
+ logError("setup", { event: "subagent-tools-error", error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack ?? "" : "" });
8760
9183
  return { path: "", action: "failed", reason: String(e) };
8761
9184
  }
8762
9185
  }
8763
9186
 
8764
9187
  // src/user-config.ts
8765
- import { promises as fs3 } from "fs";
9188
+ import { promises as fs2 } from "fs";
8766
9189
  import * as path3 from "path";
8767
9190
  import { homedir as homedir5 } from "os";
8768
9191
  import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
@@ -8772,13 +9195,17 @@ async function loadUserConfig(cwd) {
8772
9195
  for (const base of [join8(home, CONFIG_DIR_NAME), join8(cwd, CONFIG_DIR_NAME)]) {
8773
9196
  const file = join8(base, "acp.json");
8774
9197
  try {
8775
- const raw = await fs3.readFile(file, "utf8");
9198
+ const raw = await fs2.readFile(file, "utf8");
8776
9199
  const parsed = JSON.parse(raw);
8777
9200
  if (parsed && typeof parsed === "object") {
8778
9201
  Object.assign(merged, pickKnown(parsed));
8779
9202
  debug.event("config-loaded", { file });
8780
9203
  }
8781
- } catch {
9204
+ } catch (e) {
9205
+ const code = e.code;
9206
+ if (code !== "ENOENT") {
9207
+ logWarn("config", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
9208
+ }
8782
9209
  }
8783
9210
  }
8784
9211
  return merged;
@@ -8832,11 +9259,14 @@ function wireSessionLifecycle(pi, runtime) {
8832
9259
  pi.on("session_start", async (_event, ctx) => {
8833
9260
  runtime.store.invalidate();
8834
9261
  runtime.clearNudgeTracking();
9262
+ const sid = ctx.sessionManager.getSessionId();
9263
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.29" : null });
8835
9264
  try {
8836
9265
  const user = await loadUserConfig(ctx.cwd);
8837
9266
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
8838
9267
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
8839
- } catch {
9268
+ } catch (e) {
9269
+ logThrow("config", e, { sid, phase: "session_start" });
8840
9270
  }
8841
9271
  if (runtime.adapter.delegate !== false) {
8842
9272
  pi.registerTool(makeDelegateTool(pi));
@@ -8851,6 +9281,7 @@ function wireSessionLifecycle(pi, runtime) {
8851
9281
  });
8852
9282
  pi.on("session_shutdown", () => {
8853
9283
  delegateStatusWidget.dispose();
9284
+ closeLogStream();
8854
9285
  });
8855
9286
  }
8856
9287
  function wireContextTransform(pi, runtime) {
@@ -8879,6 +9310,18 @@ function wireContextTransform(pi, runtime) {
8879
9310
  });
8880
9311
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
8881
9312
  await runtime.save(turn.state, ctx);
9313
+ logInfo("turn", {
9314
+ sid,
9315
+ inMsgs: coreMessages.length,
9316
+ outMsgs: turn.messages.length,
9317
+ tokens: tokenCount,
9318
+ pct: realUsage?.percent ?? (config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null),
9319
+ limit: config.modelContextLimit,
9320
+ nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
9321
+ nudgeReason: turn.nudge?.reason ?? null,
9322
+ blocks: turn.state.blocks.length,
9323
+ activeBlocks: turn.state.blocks.filter((b) => b.active).length
9324
+ });
8882
9325
  debug.event("processTurn", {
8883
9326
  outMsgs: turn.messages.length,
8884
9327
  summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
@@ -8908,6 +9351,9 @@ function wireContextTransform(pi, runtime) {
8908
9351
  const example = top ? `
8909
9352
 
8910
9353
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
9354
+ if (emergency) {
9355
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
9356
+ }
8911
9357
  if (debugOn2 && ctx.hasUI) {
8912
9358
  ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
8913
9359
  ${rendered.text}${example}`);
@@ -8923,6 +9369,9 @@ ${rendered.text}${example}`);
8923
9369
  if (ctx.hasUI) ctx.ui.notify(msg);
8924
9370
  });
8925
9371
  return { messages: rebuilt };
9372
+ } catch (e) {
9373
+ logThrow("context", e, { sid, phase: "transform" });
9374
+ throw e;
8926
9375
  } finally {
8927
9376
  release();
8928
9377
  }