billion-context-pi 0.1.28 → 0.1.30

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;
@@ -2639,6 +2730,50 @@ function isPiHost(sm) {
2639
2730
  const source = sm;
2640
2731
  return typeof source.buildContextEntries === "function";
2641
2732
  }
2733
+ function mergeLiveEntries(entries, live) {
2734
+ const persisted = entries.filter((e) => e.type === "message");
2735
+ const out = [];
2736
+ let p = 0;
2737
+ let unmatched = 0;
2738
+ for (let i = 0; i < live.length; i++) {
2739
+ const msg = live[i];
2740
+ let matched;
2741
+ let j = p;
2742
+ while (j < persisted.length && persisted[j].message.role !== msg.role) j++;
2743
+ if (j < persisted.length && sameMessage(persisted[j].message, msg)) {
2744
+ matched = persisted[j];
2745
+ p = j + 1;
2746
+ }
2747
+ if (matched) {
2748
+ out.push(matched);
2749
+ } else {
2750
+ unmatched++;
2751
+ out.push({
2752
+ type: "message",
2753
+ id: `live-${i}`,
2754
+ parentId: null,
2755
+ timestamp: String(msg.timestamp ?? Date.now()),
2756
+ message: msg
2757
+ });
2758
+ }
2759
+ }
2760
+ if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
2761
+ return out;
2762
+ }
2763
+ function sameMessage(a, b) {
2764
+ const ra = a.role;
2765
+ const rb = b.role;
2766
+ if (ra !== rb) return false;
2767
+ const ca = a.content;
2768
+ const cb = b.content;
2769
+ if (ca === void 0 || cb === void 0) return false;
2770
+ try {
2771
+ return JSON.stringify(ca) === JSON.stringify(cb);
2772
+ } catch (e) {
2773
+ logWarn("runtime", { event: "message-compare-failed", error: e instanceof Error ? e.message : String(e) });
2774
+ return a === b;
2775
+ }
2776
+ }
2642
2777
  function createRuntime(adapter) {
2643
2778
  const core = createCore({ countTokens: defaultCountTokens });
2644
2779
  const store = new SessionStateStore();
@@ -2667,11 +2802,12 @@ function createRuntime(adapter) {
2667
2802
  function configFor(ctx) {
2668
2803
  return resolveConfig(adapterRef, liveContextLimit(ctx));
2669
2804
  }
2670
- async function stateFor(ctx) {
2805
+ async function stateFor(ctx, liveMessages) {
2671
2806
  const sm = ctx.sessionManager;
2672
2807
  const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
2673
2808
  const entries = readContextEntries(sm);
2674
- return { state, coreMessages: entriesToCoreMessages(entries), entries };
2809
+ const merged = isPiHost(sm) || !liveMessages || liveMessages.length === 0 ? entries : mergeLiveEntries(entries, liveMessages);
2810
+ return { state, coreMessages: entriesToCoreMessages(merged), entries: merged };
2675
2811
  }
2676
2812
  async function save(state, ctx) {
2677
2813
  const sm = ctx.sessionManager;
@@ -7082,64 +7218,6 @@ __export(typebox_exports, {
7082
7218
  With: () => With2
7083
7219
  });
7084
7220
 
7085
- // src/log.ts
7086
- import { promises as fs2 } from "fs";
7087
- import * as path2 from "path";
7088
- import { homedir } from "os";
7089
- var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
7090
- var LOG_FILE = process.env.ACP_LOG_FILE ?? path2.join(homedir(), ".pi", "acp-debug.log");
7091
- var runtimeDebug = null;
7092
- var initialized = false;
7093
- function setDebugEnabled(enabled) {
7094
- runtimeDebug = enabled;
7095
- }
7096
- function debugOn() {
7097
- return runtimeDebug ?? ENV_DEBUG;
7098
- }
7099
- async function write(line) {
7100
- if (!debugOn()) return;
7101
- if (!initialized) {
7102
- initialized = true;
7103
- await fs2.mkdir(path2.dirname(LOG_FILE), { recursive: true }).catch(() => {
7104
- });
7105
- }
7106
- await fs2.appendFile(LOG_FILE, line, "utf8").catch(() => {
7107
- });
7108
- }
7109
- var debug = {
7110
- get enabled() {
7111
- return debugOn();
7112
- },
7113
- get logFile() {
7114
- return LOG_FILE;
7115
- },
7116
- event(scope, fields) {
7117
- if (!debugOn()) return;
7118
- const ts = (/* @__PURE__ */ new Date()).toISOString();
7119
- const body = Object.entries(fields).map(([k, v]) => `${k}=${fmt(v)}`).join(" ");
7120
- void write(`${ts} [${scope}] ${body}
7121
- `);
7122
- }
7123
- };
7124
- function fmt(v) {
7125
- if (typeof v === "string") return v;
7126
- if (Array.isArray(v)) {
7127
- try {
7128
- return JSON.stringify(v);
7129
- } catch {
7130
- return `[${v.length}]`;
7131
- }
7132
- }
7133
- if (v && typeof v === "object") {
7134
- try {
7135
- return JSON.stringify(v);
7136
- } catch {
7137
- return String(v);
7138
- }
7139
- }
7140
- return String(v);
7141
- }
7142
-
7143
7221
  // src/tokens.ts
7144
7222
  function collectCoveredMessageIds(state) {
7145
7223
  const ids = /* @__PURE__ */ new Set();
@@ -7195,7 +7273,13 @@ function makeCompressTool(runtime) {
7195
7273
  ],
7196
7274
  parameters: CompressParams,
7197
7275
  async execute(toolCallId, params, _signal, _onUpdate, ctx) {
7198
- const result = await handleCompress(params, runtime, ctx, toolCallId);
7276
+ let result;
7277
+ try {
7278
+ result = await handleCompress(params, runtime, ctx, toolCallId);
7279
+ } catch (e) {
7280
+ logThrow("compress", e, { sid: ctx.sessionManager.getSessionId(), ranges: params.content?.length ?? 0 });
7281
+ throw e;
7282
+ }
7199
7283
  return { details: void 0, content: [{ type: "text", text: result }] };
7200
7284
  }
7201
7285
  };
@@ -7240,6 +7324,24 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
7240
7324
  activeAfter: applied.state.blocks.filter((b) => b.active).length,
7241
7325
  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 }))
7242
7326
  });
7327
+ logInfo("compress", {
7328
+ sid: ctx.sessionManager.getSessionId(),
7329
+ event: "applied",
7330
+ ranges: ranges.length,
7331
+ blocksCreated,
7332
+ tokensCompressed,
7333
+ beforeTokens,
7334
+ afterTokens,
7335
+ warnings: warnings.length,
7336
+ errors: errors.length,
7337
+ newBlockIds: newBlocks.map((b) => b.blockId)
7338
+ });
7339
+ if (errors.length > 0) {
7340
+ logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "errors", count: errors.length, errors: errors.slice(0, 5) });
7341
+ }
7342
+ if (warnings.length > 0) {
7343
+ logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "warnings", count: warnings.length, warnings: warnings.slice(0, 5) });
7344
+ }
7243
7345
  const lines = [`\u25A3 ACP | ${formatK2(beforeTokens)} \u2192 ${formatK2(afterTokens)} tokens (~${formatK2(tokensCompressed)} reclaimed, ${blocksCreated} block${blocksCreated > 1 ? "s" : ""})`];
7244
7346
  if (warnings.length > 0) lines.push("\u26A0\uFE0F " + warnings.join("; "));
7245
7347
  if (errors.length > 0) lines.push("Errors: " + errors.join("; "));
@@ -7273,7 +7375,13 @@ function makeDecompressTool(runtime) {
7273
7375
  ],
7274
7376
  parameters: DecompressParams,
7275
7377
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7276
- const result = await handleDecompress(params, runtime, ctx);
7378
+ let result;
7379
+ try {
7380
+ result = await handleDecompress(params, runtime, ctx);
7381
+ } catch (e) {
7382
+ logThrow("decompress", e, { sid: ctx.sessionManager.getSessionId(), blockId: params.blockId });
7383
+ throw e;
7384
+ }
7277
7385
  return { details: void 0, content: [{ type: "text", text: result }] };
7278
7386
  }
7279
7387
  };
@@ -7322,16 +7430,21 @@ async function handleMessageRef(ref, ownerBlockId, args, ctx) {
7322
7430
  const wantFile = args.toFile !== void 0 || args.inline === false || text.length >= MESSAGE_INLINE_THRESHOLD;
7323
7431
  if (!wantFile) {
7324
7432
  debug.event("decompress-message", { ref, ownerBlockId, mode: "inline", chars: text.length });
7433
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "inline", ref, ownerBlockId, chars: text.length });
7325
7434
  return `Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) restored inline:
7326
7435
 
7327
7436
  ${text}`;
7328
7437
  }
7329
7438
  const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(`msg-${ref}`);
7330
- if (typeof targetPath === "object" && "error" in targetPath) return targetPath.error;
7439
+ if (typeof targetPath === "object" && "error" in targetPath) {
7440
+ logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message-path-rejected", ref, toFile: args.toFile });
7441
+ return targetPath.error;
7442
+ }
7331
7443
  await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
7332
7444
  });
7333
7445
  await writeFile(targetPath, text, "utf8");
7334
7446
  debug.event("decompress-message", { ref, ownerBlockId, mode: "file", path: targetPath, chars: text.length });
7447
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "file", ref, ownerBlockId, path: targetPath, chars: text.length });
7335
7448
  return [
7336
7449
  `Message ${ref} (${role}, block ${ownerBlockId}, ${text.length} chars) written to ${targetPath}.`,
7337
7450
  "Block stays compressed \u2014 context unchanged. Use the read tool to access the content.",
@@ -7359,16 +7472,21 @@ async function handleDecompress(args, runtime, ctx) {
7359
7472
  if (count === 0) return `Block ${blockId} has no restorable message content.`;
7360
7473
  if (args.inline === true && !args.toFile) {
7361
7474
  debug.event("decompress", { blockId, full, count, mode: "inline" });
7475
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "inline", blockId, full, count });
7362
7476
  return `Restored block ${blockId} (${count} item${count === 1 ? "" : "s"}) inline:
7363
7477
 
7364
7478
  ${text}`;
7365
7479
  }
7366
7480
  const targetPath = args.toFile ? resolveToFilePath(args.toFile) : autoFilePath(blockId);
7367
- if (typeof targetPath === "object" && "error" in targetPath) return targetPath.error;
7481
+ if (typeof targetPath === "object" && "error" in targetPath) {
7482
+ logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block-path-rejected", blockId, toFile: args.toFile });
7483
+ return targetPath.error;
7484
+ }
7368
7485
  await mkdir(AUTO_DIR, { recursive: true }).catch(() => {
7369
7486
  });
7370
7487
  await writeFile(targetPath, text, "utf8");
7371
7488
  debug.event("decompress", { blockId, full, count, mode: "file", path: targetPath, chars: text.length });
7489
+ logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "file", blockId, full, count, path: targetPath, chars: text.length });
7372
7490
  const itemWord = count === 1 ? "item" : "items";
7373
7491
  const lines = [
7374
7492
  `Block ${blockId} (${count} ${itemWord}, ${text.length} chars) written to ${targetPath}.`,
@@ -7396,7 +7514,7 @@ function buildMessageOwnerMap(state) {
7396
7514
  return m;
7397
7515
  }
7398
7516
  function estimateTokens2(text) {
7399
- if (!text) return 0;
7517
+ if (typeof text !== "string" || !text) return 0;
7400
7518
  const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
7401
7519
  const cjkCount = cjk?.length ?? 0;
7402
7520
  return cjkCount + Math.ceil((text.length - cjkCount) / 4);
@@ -7458,7 +7576,13 @@ function makeSearchTool(runtime) {
7458
7576
  ],
7459
7577
  parameters: SearchParams,
7460
7578
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7461
- const result = await handleSearch(params, runtime, ctx);
7579
+ let result;
7580
+ try {
7581
+ result = await handleSearch(params, runtime, ctx);
7582
+ } catch (e) {
7583
+ logThrow("search", e, { sid: ctx.sessionManager.getSessionId(), query: params.query });
7584
+ throw e;
7585
+ }
7462
7586
  return { details: void 0, content: [{ type: "text", text: result }] };
7463
7587
  }
7464
7588
  };
@@ -7523,7 +7647,13 @@ function makeStatusTool(runtime) {
7523
7647
  ],
7524
7648
  parameters: StatusParams,
7525
7649
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
7526
- const result = await handleStatus(params, runtime, ctx);
7650
+ let result;
7651
+ try {
7652
+ result = await handleStatus(params, runtime, ctx);
7653
+ } catch (e) {
7654
+ logThrow("status", e, { sid: ctx.sessionManager.getSessionId(), scope: params.scope ?? null });
7655
+ throw e;
7656
+ }
7527
7657
  return { details: void 0, content: [{ type: "text", text: result }] };
7528
7658
  }
7529
7659
  };
@@ -8103,6 +8233,7 @@ function makeDelegateCancelTool(_pi) {
8103
8233
  run.child?.kill("SIGTERM");
8104
8234
  } catch (err) {
8105
8235
  debug.event("delegate-cancel-kill-error", { runId, error: String(err) });
8236
+ logError("delegate", { event: "cancel-kill-error", runId, error: String(err) });
8106
8237
  }
8107
8238
  delegateStatusWidget.poke();
8108
8239
  return {
@@ -8133,8 +8264,11 @@ async function runDelegate(pi, args, ctx, signal) {
8133
8264
  const requestedAsync = args.async !== false;
8134
8265
  if (requestedAsync && !isAsync) {
8135
8266
  debug.event("delegate-async-downgraded", { reason: `mode=${ctx.mode}` });
8267
+ logInfo("delegate", { event: "async-downgraded", reason: `mode=${ctx.mode}` });
8136
8268
  }
8137
- debug.event("delegate-spawn", { agent: args.agent, cwd, async: isAsync, useJsonStream, cliArgs });
8269
+ const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
8270
+ debug.event("delegate-spawn", { agent: args.agent, runId, cwd, async: isAsync, useJsonStream, cliArgs });
8271
+ logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
8138
8272
  const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
8139
8273
  cwd,
8140
8274
  env: childEnv,
@@ -8143,10 +8277,10 @@ async function runDelegate(pi, args, ctx, signal) {
8143
8277
  });
8144
8278
  child.stdin?.once("error", (e) => {
8145
8279
  debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
8280
+ logError("delegate", { event: "stdin-error", runId, error: String(e) });
8146
8281
  });
8147
8282
  child.stdin?.end(args.task);
8148
8283
  let stderrText = "";
8149
- const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
8150
8284
  const startedAt = Date.now();
8151
8285
  if (isAsync) {
8152
8286
  let settled = false;
@@ -8278,23 +8412,27 @@ async function runDelegate(pi, args, ctx, signal) {
8278
8412
  run.finishedAt = Date.now();
8279
8413
  if (run.waiter) {
8280
8414
  debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
8415
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
8281
8416
  run.waiter();
8282
8417
  delegateStatusWidget.poke();
8283
8418
  return;
8284
8419
  }
8285
8420
  if (run.consumed) {
8286
8421
  debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8422
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected: false, via: "consumed", outLen: output.length, file: file2 });
8287
8423
  delegateStatusWidget.poke();
8288
8424
  return;
8289
8425
  }
8290
8426
  const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut);
8291
8427
  run.injected = injected;
8292
8428
  debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
8429
+ logInfo("delegate", { event: "done", runId, agent: args.agent, code, status: run.status, injected, outLen: output.length, file: file2 });
8293
8430
  delegateStatusWidget.poke();
8294
8431
  } catch (err) {
8295
8432
  run.status = "failed";
8296
8433
  run.finishedAt = Date.now();
8297
8434
  debug.event("delegate-done-error", { runId, error: String(err) });
8435
+ logError("delegate", { event: "done-error", runId, agent: args.agent, error: String(err) });
8298
8436
  run.waiter?.();
8299
8437
  delegateStatusWidget.poke();
8300
8438
  }
@@ -8315,6 +8453,7 @@ async function runDelegate(pi, args, ctx, signal) {
8315
8453
  run.finishedAt = Date.now();
8316
8454
  run.result = { code: null, file: "", body: `spawn error: ${String(err)}` };
8317
8455
  debug.event("delegate-spawn-error", { runId, error: String(err) });
8456
+ logError("delegate", { event: "spawn-error", runId, agent: args.agent, error: String(err) });
8318
8457
  run.waiter?.();
8319
8458
  delegateStatusWidget.poke();
8320
8459
  }
@@ -8409,6 +8548,7 @@ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8409
8548
  const send = pi.sendUserMessage;
8410
8549
  if (typeof send !== "function") {
8411
8550
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
8551
+ logWarn("delegate", { event: "inject-skipped", runId, reason: "sendUserMessage unavailable" });
8412
8552
  return false;
8413
8553
  }
8414
8554
  const status = code === 0 ? "completed" : "failed";
@@ -8422,6 +8562,7 @@ function injectResult(pi, agent, runId, task, code, file, timedOut) {
8422
8562
  return true;
8423
8563
  } catch (err) {
8424
8564
  debug.event("delegate-inject-error", { runId, error: String(err) });
8565
+ logError("delegate", { event: "inject-error", runId, agent, error: String(err) });
8425
8566
  return false;
8426
8567
  }
8427
8568
  }
@@ -8449,6 +8590,7 @@ async function persistResult(runId, body) {
8449
8590
  return file;
8450
8591
  } catch (err) {
8451
8592
  debug.event("delegate-persist-error", { runId, file, error: String(err) });
8593
+ logError("delegate", { event: "persist-error", runId, file, error: String(err) });
8452
8594
  return "";
8453
8595
  }
8454
8596
  }
@@ -8464,6 +8606,23 @@ function truncate2(s, n) {
8464
8606
  return s.slice(0, n - 1) + "\u2026";
8465
8607
  }
8466
8608
 
8609
+ // src/compat.ts
8610
+ function normalizeSystemPrompt(input) {
8611
+ if (input === void 0) return "";
8612
+ if (Array.isArray(input)) return input.join("\n");
8613
+ return input;
8614
+ }
8615
+ function formatSystemPromptForEvent(base, append) {
8616
+ const normalized = normalizeSystemPrompt(base);
8617
+ return `${normalized}
8618
+
8619
+ ${append}`;
8620
+ }
8621
+ function getSystemPromptText(ctx) {
8622
+ const result = ctx.getSystemPrompt?.();
8623
+ return normalizeSystemPrompt(result);
8624
+ }
8625
+
8467
8626
  // src/commands.ts
8468
8627
  function makeCommands(runtime) {
8469
8628
  return [
@@ -8551,7 +8710,7 @@ async function statusReport(runtime, ctx) {
8551
8710
  const bd = nudge?.contextBreakdown;
8552
8711
  const limit = config.modelContextLimit;
8553
8712
  const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
8554
- const systemPromptText = ctx.getSystemPrompt?.() ?? "";
8713
+ const systemPromptText = getSystemPromptText(ctx);
8555
8714
  const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
8556
8715
  const framework = bd ? Math.max(0, tokenCount - classified - systemPromptTokens) : 0;
8557
8716
  const displayTotal = tokenCount;
@@ -8559,7 +8718,7 @@ async function statusReport(runtime, ctx) {
8559
8718
  const activeBlocksList = state.blocks.filter((b) => b.active);
8560
8719
  const totalBlocksList = state.blocks;
8561
8720
  const lines = [];
8562
- const versionStr = "0.1.28" ? `billion-context-pi@${"0.1.28"}` : "";
8721
+ const versionStr = "0.1.30" ? `billion-context-pi@${"0.1.30"}` : "";
8563
8722
  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");
8564
8723
  lines.push("\u2502 ACP Context Analysis \u2502");
8565
8724
  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");
@@ -8803,11 +8962,13 @@ function wireToolGuardrails(pi, runtime) {
8803
8962
  if (next) {
8804
8963
  modified = next;
8805
8964
  debug.event("guardrail-output-cap", { max, hadPath: !!fullPath });
8965
+ logWarn("guardrail", { event: "output-cap", max, hadPath: !!fullPath });
8806
8966
  }
8807
8967
  }
8808
8968
  if (timeoutSecs !== void 0) {
8809
8969
  modified = appendTimeoutNotice(modified ?? event.content, timeoutSecs);
8810
8970
  debug.event("guardrail-bash-timeout-notice", { secs: timeoutSecs });
8971
+ logInfo("guardrail", { event: "bash-timeout-notice", secs: timeoutSecs });
8811
8972
  }
8812
8973
  if (modified) return { content: modified };
8813
8974
  });
@@ -8862,11 +9023,12 @@ async function readPackageJson(path4) {
8862
9023
  }
8863
9024
  function findNpmRoot(extDir) {
8864
9025
  let dir = dirname3(extDir);
8865
- while (dir !== "/" && dir !== ".") {
9026
+ for (; ; ) {
8866
9027
  if (dir.endsWith("node_modules")) return dirname3(dir);
8867
- dir = dirname3(dir);
9028
+ const parent = dirname3(dir);
9029
+ if (parent === dir) return void 0;
9030
+ dir = parent;
8868
9031
  }
8869
- return void 0;
8870
9032
  }
8871
9033
  async function findExtensionDir() {
8872
9034
  let dir = dirname3(fileURLToPath(import.meta.url));
@@ -8915,29 +9077,36 @@ async function checkForUpdate(autoUpdate, notify) {
8915
9077
  signal: AbortSignal.timeout(5e3),
8916
9078
  headers: { Accept: "application/json" }
8917
9079
  });
8918
- if (!res.ok) return;
9080
+ if (!res.ok) {
9081
+ logWarn("update", { event: "check-http", status: res.status });
9082
+ return;
9083
+ }
8919
9084
  const data = await res.json();
8920
9085
  const latest = data.version;
8921
9086
  if (!latest) return;
8922
- const current = runtimeVersion ?? "0.1.28";
9087
+ const current = runtimeVersion ?? "0.1.30";
9088
+ const hasUpdate = isNewer(latest, current);
8923
9089
  debug.event("update-check", {
8924
9090
  current,
8925
9091
  latest,
8926
- hasUpdate: isNewer(latest, current)
9092
+ hasUpdate
8927
9093
  });
8928
- if (isNewer(latest, current)) {
9094
+ logInfo("update", { event: "check", current, latest, hasUpdate });
9095
+ if (hasUpdate) {
8929
9096
  const installed = await autoInstallLatest(latest);
8930
9097
  if (installed && notify) {
8931
9098
  notify(
8932
9099
  `\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart Pi to finish.\x1B[0m`
8933
9100
  );
9101
+ logInfo("update", { event: "auto-installed", from: current, to: latest });
8934
9102
  } else if (!installed && notify) {
8935
9103
  notify(
8936
9104
  `${PACKAGE_NAME} ${latest} available (you have ${current}). Run: pi update --extension npm:${PACKAGE_NAME}`
8937
9105
  );
8938
9106
  }
8939
9107
  }
8940
- } catch {
9108
+ } catch (e) {
9109
+ logWarn("update", { event: "check-error", error: e instanceof Error ? e.message : String(e) });
8941
9110
  } finally {
8942
9111
  updateInFlight = false;
8943
9112
  }
@@ -8951,7 +9120,7 @@ async function getRuntimeVersion() {
8951
9120
 
8952
9121
  // src/setup-subagent-tools.ts
8953
9122
  import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
8954
- import { existsSync } from "fs";
9123
+ import { existsSync as existsSync2 } from "fs";
8955
9124
  import { homedir as homedir4 } from "os";
8956
9125
  import { join as join6 } from "path";
8957
9126
  var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
@@ -9015,7 +9184,7 @@ async function ensureSubagentAcpTools(settingsPath) {
9015
9184
  return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
9016
9185
  }
9017
9186
  const backupPath = `${path4}.acp-bak`;
9018
- if (!existsSync(backupPath)) {
9187
+ if (!existsSync2(backupPath)) {
9019
9188
  try {
9020
9189
  await copyFile(path4, backupPath);
9021
9190
  } catch {
@@ -9067,15 +9236,19 @@ async function runSetupAndNotify(notify) {
9067
9236
  if (result.action === "updated" && notify) {
9068
9237
  notify(`ACP: enabled context tools (compress/decompress/search_context/acp_status) for subagents`);
9069
9238
  }
9239
+ if (result.action === "failed") {
9240
+ logWarn("setup", { event: "subagent-tools", action: result.action, reason: result.reason });
9241
+ }
9070
9242
  return result;
9071
9243
  } catch (e) {
9072
9244
  debug.event("setup-subagent-tools-error", { msg: String(e) });
9245
+ logError("setup", { event: "subagent-tools-error", error: e instanceof Error ? e.message : String(e), stack: e instanceof Error ? e.stack ?? "" : "" });
9073
9246
  return { path: "", action: "failed", reason: String(e) };
9074
9247
  }
9075
9248
  }
9076
9249
 
9077
9250
  // src/user-config.ts
9078
- import { promises as fs3 } from "fs";
9251
+ import { promises as fs2 } from "fs";
9079
9252
  import * as path3 from "path";
9080
9253
  import { homedir as homedir5 } from "os";
9081
9254
  import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
@@ -9085,13 +9258,17 @@ async function loadUserConfig(cwd) {
9085
9258
  for (const base of [join8(home, CONFIG_DIR_NAME), join8(cwd, CONFIG_DIR_NAME)]) {
9086
9259
  const file = join8(base, "acp.json");
9087
9260
  try {
9088
- const raw = await fs3.readFile(file, "utf8");
9261
+ const raw = await fs2.readFile(file, "utf8");
9089
9262
  const parsed = JSON.parse(raw);
9090
9263
  if (parsed && typeof parsed === "object") {
9091
9264
  Object.assign(merged, pickKnown(parsed));
9092
9265
  debug.event("config-loaded", { file });
9093
9266
  }
9094
- } catch {
9267
+ } catch (e) {
9268
+ const code = e.code;
9269
+ if (code !== "ENOENT") {
9270
+ logWarn("config", { event: "load-failed", file, error: e instanceof Error ? e.message : String(e) });
9271
+ }
9095
9272
  }
9096
9273
  }
9097
9274
  return merged;
@@ -9145,11 +9322,14 @@ function wireSessionLifecycle(pi, runtime) {
9145
9322
  pi.on("session_start", async (_event, ctx) => {
9146
9323
  runtime.store.invalidate();
9147
9324
  runtime.clearNudgeTracking();
9325
+ const sid = ctx.sessionManager.getSessionId();
9326
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.30" : null });
9148
9327
  try {
9149
9328
  const user = await loadUserConfig(ctx.cwd);
9150
9329
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
9151
9330
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
9152
- } catch {
9331
+ } catch (e) {
9332
+ logThrow("config", e, { sid, phase: "session_start" });
9153
9333
  }
9154
9334
  if (runtime.adapter.delegate !== false) {
9155
9335
  pi.registerTool(makeDelegateTool(pi));
@@ -9164,6 +9344,7 @@ function wireSessionLifecycle(pi, runtime) {
9164
9344
  });
9165
9345
  pi.on("session_shutdown", () => {
9166
9346
  delegateStatusWidget.dispose();
9347
+ closeLogStream();
9167
9348
  });
9168
9349
  }
9169
9350
  function wireContextTransform(pi, runtime) {
@@ -9171,7 +9352,7 @@ function wireContextTransform(pi, runtime) {
9171
9352
  const sid = ctx.sessionManager.getSessionId();
9172
9353
  const release = await runtime.acquireLock(sid);
9173
9354
  try {
9174
- const { state, coreMessages, entries } = await runtime.stateFor(ctx);
9355
+ const { state, coreMessages, entries } = await runtime.stateFor(ctx, event.messages);
9175
9356
  const config = runtime.configFor(ctx);
9176
9357
  const coveredIds = collectCoveredMessageIds(state);
9177
9358
  const realUsage = ctx.getContextUsage?.();
@@ -9192,6 +9373,18 @@ function wireContextTransform(pi, runtime) {
9192
9373
  });
9193
9374
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
9194
9375
  await runtime.save(turn.state, ctx);
9376
+ logInfo("turn", {
9377
+ sid,
9378
+ inMsgs: coreMessages.length,
9379
+ outMsgs: turn.messages.length,
9380
+ tokens: tokenCount,
9381
+ pct: realUsage?.percent ?? (config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null),
9382
+ limit: config.modelContextLimit,
9383
+ nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
9384
+ nudgeReason: turn.nudge?.reason ?? null,
9385
+ blocks: turn.state.blocks.length,
9386
+ activeBlocks: turn.state.blocks.filter((b) => b.active).length
9387
+ });
9195
9388
  debug.event("processTurn", {
9196
9389
  outMsgs: turn.messages.length,
9197
9390
  summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
@@ -9221,6 +9414,9 @@ function wireContextTransform(pi, runtime) {
9221
9414
  const example = top ? `
9222
9415
 
9223
9416
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
9417
+ if (emergency) {
9418
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
9419
+ }
9224
9420
  if (debugOn2 && ctx.hasUI) {
9225
9421
  ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
9226
9422
  ${rendered.text}${example}`);
@@ -9236,6 +9432,9 @@ ${rendered.text}${example}`);
9236
9432
  if (ctx.hasUI) ctx.ui.notify(msg);
9237
9433
  });
9238
9434
  return { messages: rebuilt };
9435
+ } catch (e) {
9436
+ logThrow("context", e, { sid, phase: "transform" });
9437
+ throw e;
9239
9438
  } finally {
9240
9439
  release();
9241
9440
  }
@@ -9246,15 +9445,13 @@ function wireSystemPrompt(pi, runtime) {
9246
9445
  const delegate = runtime.adapter.delegate !== false;
9247
9446
  const prompt = delegate ? `${ACP_SYSTEM_PROMPT}
9248
9447
  ${ACP_DELEGATE_PROMPT}` : ACP_SYSTEM_PROMPT;
9249
- return { systemPrompt: `${event.systemPrompt}
9250
-
9251
- ${prompt}` };
9448
+ return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, prompt) };
9252
9449
  });
9253
9450
  }
9254
9451
  function collectOriginals(entries) {
9255
9452
  const map = /* @__PURE__ */ new Map();
9256
9453
  for (const entry of entries) {
9257
- if (entry.type === "message") {
9454
+ if (entry.type === "message" && entry.message) {
9258
9455
  map.set(entry.id, entry.message);
9259
9456
  } else if (entry.type === "custom_message") {
9260
9457
  const content = typeof entry.content === "string" ? [{ type: "text", text: entry.content }] : entry.content;