billion-context-pi 0.1.32 → 0.1.33

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.
@@ -1,5 +1,7 @@
1
+ import { type SpawnOptions } from "node:child_process";
1
2
  import { Type, type Static } from "typebox";
2
3
  import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
+ export declare function delegateSpawnOptions(cwd: string, env: NodeJS.ProcessEnv): SpawnOptions;
3
5
  /** Snapshot of currently-running delegate runs, for the TUI status widget. */
4
6
  export declare function runningRunsSnapshot(): {
5
7
  runId: string;
@@ -7,6 +9,34 @@ export declare function runningRunsSnapshot(): {
7
9
  task: string;
8
10
  startedAt: number;
9
11
  }[];
12
+ /** Minimal writable surface accepted by makeEventApplier — real WriteStreams
13
+ * in production, in-memory collectors in tests. */
14
+ export interface EventApplierWriters {
15
+ reply: {
16
+ write(chunk: string): void;
17
+ };
18
+ activity: {
19
+ write(chunk: string): void;
20
+ } | null;
21
+ }
22
+ export interface EventApplier {
23
+ handleEventLine(line: string): void;
24
+ getReplyText(): string;
25
+ /** omp fallback: `-p` prints the plain reply as raw stdout; append it
26
+ * straight through (no event parsing). */
27
+ appendRaw(text: string): void;
28
+ }
29
+ /** Applies parsed delegate JSON-event lines to the live reply/activity files.
30
+ * Extracted from the spawn closure so the write logic is unit-testable.
31
+ *
32
+ * reply-delta (text_delta) is streamed to the reply file as it arrives;
33
+ * reply-complete (text_end) carries the authoritative full content of the
34
+ * text block — any portion not already written is appended (tracked via
35
+ * msgWritten) so a final answer that arrives without preceding deltas is
36
+ * never lost from the file. */
37
+ export declare function makeEventApplier(opts: {
38
+ showThinking: boolean;
39
+ }, writers: EventApplierWriters): EventApplier;
10
40
  declare const DelegateParams: Type.TObject<{
11
41
  agent: Type.TString;
12
42
  task: Type.TString;
package/dist/index.js CHANGED
@@ -2654,10 +2654,11 @@ import * as path2 from "path";
2654
2654
  import { appendFileSync, mkdirSync, statSync, renameSync, existsSync } from "fs";
2655
2655
  import * as path from "path";
2656
2656
  import { homedir } from "os";
2657
+ import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
2657
2658
  var MAX_BYTES = 10 * 1024 * 1024;
2658
2659
  var ENV_DEBUG = process.env.ACP_DEBUG === "1" || process.env.ACP_DEBUG === "true";
2659
2660
  function resolveLogFile() {
2660
- return process.env.ACP_LOG_FILE ?? path.join(homedir(), ".pi", "acp.log");
2661
+ return process.env.ACP_LOG_FILE ?? path.join(homedir(), CONFIG_DIR_NAME, "acp.log");
2661
2662
  }
2662
2663
  var runtimeDebug = null;
2663
2664
  function setDebugEnabled(enabled) {
@@ -8064,6 +8065,14 @@ var ASYNC_TIMEOUT_MS = 30 * 6e4;
8064
8065
  var KILL_GRACE_MS = 1e4;
8065
8066
  var RESULT_SUMMARY_CHARS = 500;
8066
8067
  var OUT_DIR = join4(tmpdir2(), "acp-delegate");
8068
+ function delegateSpawnOptions(cwd, env) {
8069
+ return {
8070
+ cwd,
8071
+ env,
8072
+ stdio: ["pipe", "pipe", "pipe"],
8073
+ shell: false
8074
+ };
8075
+ }
8067
8076
  var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
8068
8077
  var RESTRICTED_TOOLS = "read,bash,grep,find,ls";
8069
8078
  var AGENTS = {
@@ -8110,6 +8119,69 @@ function runningRunsSnapshot() {
8110
8119
  }
8111
8120
  return out;
8112
8121
  }
8122
+ function makeEventApplier(opts, writers) {
8123
+ let replyText = "";
8124
+ let msgWritten = 0;
8125
+ const lastToolText = /* @__PURE__ */ new Map();
8126
+ const thinking = new ThinkingCollector(opts.showThinking);
8127
+ const flushThinking = () => {
8128
+ const line = thinking.flush();
8129
+ if (line) writers.activity?.write(line);
8130
+ };
8131
+ const handleEventLine = (line) => {
8132
+ const ev = parseEventLine(line);
8133
+ if (!ev) return;
8134
+ if (ev.kind === "thinking-delta") {
8135
+ thinking.push(ev.delta);
8136
+ return;
8137
+ }
8138
+ if (ev.kind === "thinking-end") {
8139
+ flushThinking();
8140
+ return;
8141
+ }
8142
+ if (ev.kind === "reply-delta") {
8143
+ flushThinking();
8144
+ replyText += ev.delta;
8145
+ msgWritten += ev.delta.length;
8146
+ writers.reply.write(ev.delta);
8147
+ return;
8148
+ }
8149
+ if (ev.kind === "reply-complete") {
8150
+ flushThinking();
8151
+ const tail = ev.content.slice(msgWritten);
8152
+ if (tail) {
8153
+ writers.reply.write(tail);
8154
+ debug.event("reply-complete-tail", { tailLen: tail.length, contentLen: ev.content.length });
8155
+ }
8156
+ if (ev.content.length < msgWritten) {
8157
+ logWarn("delegate", { event: "reply-content-shorter-than-delta", contentLen: ev.content.length, written: msgWritten });
8158
+ }
8159
+ msgWritten = 0;
8160
+ replyText = ev.content;
8161
+ return;
8162
+ }
8163
+ if (ev.kind === "tool-update") {
8164
+ flushThinking();
8165
+ const prev = lastToolText.get(ev.toolCallId) ?? "";
8166
+ const add = newPortion(ev.text, prev);
8167
+ lastToolText.set(ev.toolCallId, ev.text);
8168
+ if (add) writers.activity?.write(add.endsWith("\n") ? add : `${add}
8169
+ `);
8170
+ return;
8171
+ }
8172
+ flushThinking();
8173
+ const lines = activityLines(ev, { showThinking: opts.showThinking });
8174
+ if (lines.length) writers.activity?.write(lines.join(""));
8175
+ };
8176
+ return {
8177
+ handleEventLine,
8178
+ getReplyText: () => replyText,
8179
+ appendRaw(text) {
8180
+ replyText += text;
8181
+ writers.reply.write(text);
8182
+ }
8183
+ };
8184
+ }
8113
8185
  var WAIT_TIMEOUT_MS_DEFAULT = 1e4;
8114
8186
  var WAIT_TIMEOUT_MS_MAX = 3e5;
8115
8187
  var DelegateParams = typebox_exports.Object({
@@ -8341,12 +8413,11 @@ async function runDelegate(pi, args, ctx, signal) {
8341
8413
  const runId = `del_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
8342
8414
  debug.event("delegate-spawn", { agent: args.agent, runId, cwd, async: isAsync, useJsonStream, cliArgs });
8343
8415
  logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
8344
- const child = spawn(process.execPath, [process.argv[1], ...cliArgs], {
8345
- cwd,
8346
- env: childEnv,
8347
- stdio: ["pipe", "pipe", "pipe"],
8348
- shell: process.platform === "win32"
8349
- });
8416
+ const child = spawn(
8417
+ process.execPath,
8418
+ [process.argv[1], ...cliArgs],
8419
+ delegateSpawnOptions(cwd, childEnv)
8420
+ );
8350
8421
  child.stdin?.once("error", (e) => {
8351
8422
  debug.event("delegate-stdin-error", { runId: "pre-spawn", error: String(e) });
8352
8423
  logError("delegate", { event: "stdin-error", runId, error: String(e) });
@@ -8380,49 +8451,11 @@ async function runDelegate(pi, args, ctx, signal) {
8380
8451
  if (!s || s.destroyed || s.closed) return resolve2();
8381
8452
  s.end(() => resolve2());
8382
8453
  });
8383
- let replyText = "";
8384
8454
  let stdoutBuf = "";
8385
- const lastToolText = /* @__PURE__ */ new Map();
8386
- const thinking = new ThinkingCollector(args.showThinking === true);
8387
- const flushThinking = () => {
8388
- const line = thinking.flush();
8389
- if (line) activityStream?.write(line);
8390
- };
8391
- const handleEventLine = (line) => {
8392
- const ev = parseEventLine(line);
8393
- if (!ev) return;
8394
- if (ev.kind === "thinking-delta") {
8395
- thinking.push(ev.delta);
8396
- return;
8397
- }
8398
- if (ev.kind === "thinking-end") {
8399
- flushThinking();
8400
- return;
8401
- }
8402
- if (ev.kind === "reply-delta") {
8403
- flushThinking();
8404
- replyText += ev.delta;
8405
- replyStream.write(ev.delta);
8406
- return;
8407
- }
8408
- if (ev.kind === "reply-complete") {
8409
- flushThinking();
8410
- replyText = ev.content;
8411
- return;
8412
- }
8413
- if (ev.kind === "tool-update") {
8414
- flushThinking();
8415
- const prev = lastToolText.get(ev.toolCallId) ?? "";
8416
- const add = newPortion(ev.text, prev);
8417
- lastToolText.set(ev.toolCallId, ev.text);
8418
- if (add) activityStream?.write(add.endsWith("\n") ? add : `${add}
8419
- `);
8420
- return;
8421
- }
8422
- flushThinking();
8423
- const lines = activityLines(ev, { showThinking: args.showThinking === true });
8424
- if (lines.length) activityStream?.write(lines.join(""));
8425
- };
8455
+ const applier = makeEventApplier(
8456
+ { showThinking: args.showThinking === true },
8457
+ { reply: replyStream, activity: activityStream }
8458
+ );
8426
8459
  child.stdout?.on("data", (c) => {
8427
8460
  watchdog.poke();
8428
8461
  if (useJsonStream) {
@@ -8431,12 +8464,11 @@ async function runDelegate(pi, args, ctx, signal) {
8431
8464
  while ((nl = stdoutBuf.indexOf("\n")) >= 0) {
8432
8465
  const line = stdoutBuf.slice(0, nl);
8433
8466
  stdoutBuf = stdoutBuf.slice(nl + 1);
8434
- handleEventLine(line);
8467
+ applier.handleEventLine(line);
8435
8468
  }
8436
8469
  } else {
8437
8470
  const text = c.toString("utf8");
8438
- replyText += text;
8439
- replyStream.write(text);
8471
+ applier.appendRaw(text);
8440
8472
  }
8441
8473
  });
8442
8474
  child.stderr?.on("data", (c) => {
@@ -8461,7 +8493,7 @@ async function runDelegate(pi, args, ctx, signal) {
8461
8493
  void cleanupTmp(tmpDir);
8462
8494
  await Promise.all([endStream(replyStream), endStream(activityStream)]);
8463
8495
  run.exitCode = code;
8464
- const output = replyText.trim();
8496
+ const output = applier.getReplyText().trim();
8465
8497
  const body2 = code === 0 ? output || "(no output)" : stderrText.trim() || output || "(no output)";
8466
8498
  if (run.status === "cancelled") {
8467
8499
  await Promise.all([rm(replyFile, { force: true }), rm(activityFile, { force: true })]);
@@ -8790,7 +8822,7 @@ async function statusReport(runtime, ctx) {
8790
8822
  const activeBlocksList = state.blocks.filter((b) => b.active);
8791
8823
  const totalBlocksList = state.blocks;
8792
8824
  const lines = [];
8793
- const versionStr = "0.1.32" ? `billion-context-pi@${"0.1.32"}` : "";
8825
+ const versionStr = "0.1.33" ? `billion-context-pi@${"0.1.33"}` : "";
8794
8826
  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");
8795
8827
  lines.push("\u2502 ACP Context Analysis \u2502");
8796
8828
  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");
@@ -9052,11 +9084,12 @@ import { join as join5, dirname as dirname3 } from "path";
9052
9084
  import { fileURLToPath } from "url";
9053
9085
  import { execFile } from "child_process";
9054
9086
  import { homedir as homedir3 } from "os";
9087
+ import { CONFIG_DIR_NAME as CONFIG_DIR_NAME2 } from "@earendil-works/pi-coding-agent";
9055
9088
  var PACKAGE_NAME = "billion-context-pi";
9056
9089
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
9057
9090
  var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
9058
9091
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
9059
- var THROTTLE_FILE = join5(homedir3(), ".pi", "agent", ".billion-context-pi-update-check");
9092
+ var THROTTLE_FILE = join5(homedir3(), CONFIG_DIR_NAME2, "agent", ".billion-context-pi-update-check");
9060
9093
  var updateInFlight = false;
9061
9094
  function parseVersion(v) {
9062
9095
  return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
@@ -9156,7 +9189,7 @@ async function checkForUpdate(autoUpdate, notify) {
9156
9189
  const data = await res.json();
9157
9190
  const latest = data.version;
9158
9191
  if (!latest) return;
9159
- const current = runtimeVersion ?? "0.1.32";
9192
+ const current = runtimeVersion ?? "0.1.33";
9160
9193
  const hasUpdate = isNewer(latest, current);
9161
9194
  debug.event("update-check", {
9162
9195
  current,
@@ -9195,6 +9228,7 @@ import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename
9195
9228
  import { existsSync as existsSync2 } from "fs";
9196
9229
  import { homedir as homedir4 } from "os";
9197
9230
  import { join as join6 } from "path";
9231
+ import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
9198
9232
  var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
9199
9233
  var BUILTIN_DEFAULT_TOOLS = {
9200
9234
  advisor: ["read", "grep", "find", "ls", "bash", "intercom"],
@@ -9211,7 +9245,7 @@ function resolveAgentDir() {
9211
9245
  const configured = process.env.PI_CODING_AGENT_DIR;
9212
9246
  if (configured === "~") return homedir4();
9213
9247
  if (configured?.startsWith("~/")) return join6(homedir4(), configured.slice(2));
9214
- return configured || join6(homedir4(), ".pi", "agent");
9248
+ return configured || join6(homedir4(), CONFIG_DIR_NAME3, "agent");
9215
9249
  }
9216
9250
  function desiredTools(existing, name) {
9217
9251
  const base = Array.isArray(existing?.tools) && existing.tools.length > 0 ? [...existing.tools] : [...BUILTIN_DEFAULT_TOOLS[name] ?? []];
@@ -9323,11 +9357,11 @@ async function runSetupAndNotify(notify) {
9323
9357
  import { promises as fs2 } from "fs";
9324
9358
  import * as path3 from "path";
9325
9359
  import { homedir as homedir5 } from "os";
9326
- import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
9360
+ import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@earendil-works/pi-coding-agent";
9327
9361
  async function loadUserConfig(cwd) {
9328
9362
  const home = homedir5();
9329
9363
  const merged = {};
9330
- for (const base of [join8(home, CONFIG_DIR_NAME), join8(cwd, CONFIG_DIR_NAME)]) {
9364
+ for (const base of [join8(home, CONFIG_DIR_NAME4), join8(cwd, CONFIG_DIR_NAME4)]) {
9331
9365
  const file = join8(base, "acp.json");
9332
9366
  try {
9333
9367
  const raw = await fs2.readFile(file, "utf8");
@@ -9395,7 +9429,7 @@ function wireSessionLifecycle(pi, runtime) {
9395
9429
  runtime.store.invalidate();
9396
9430
  runtime.clearNudgeTracking();
9397
9431
  const sid = ctx.sessionManager.getSessionId();
9398
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.32" : null });
9432
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.33" : null });
9399
9433
  try {
9400
9434
  const user = await loadUserConfig(ctx.cwd);
9401
9435
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));