billion-context-pi 0.1.29 → 0.1.31

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.
@@ -0,0 +1,23 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ /**
3
+ * Host compatibility layer for pi vs omp (oh-my-pi) API differences.
4
+ *
5
+ * pi: systemPrompt is string, getSystemPrompt() returns string
6
+ * omp: systemPrompt is string[], getSystemPrompt() returns string[]
7
+ *
8
+ * These helpers normalize the differences so the rest of the codebase
9
+ * can work with a consistent string interface.
10
+ */
11
+ /** Normalize systemPrompt to a single string (join with newlines if array). */
12
+ export declare function normalizeSystemPrompt(input: string | string[] | undefined): string;
13
+ /**
14
+ * Format systemPrompt for before_agent_start event handler.
15
+ * Always returns string to satisfy pi's type definition, but handles
16
+ * both string (pi) and string[] (omp) input types at runtime.
17
+ */
18
+ export declare function formatSystemPromptForEvent(base: string | string[], append: string): string;
19
+ /**
20
+ * Get the system prompt as a single string, regardless of host type.
21
+ * Handles both pi (string) and omp (string[]) return types.
22
+ */
23
+ export declare function getSystemPromptText(ctx: ExtensionContext): string;
package/dist/index.js CHANGED
@@ -2537,6 +2537,15 @@ function patchRefTag(original, core) {
2537
2537
  if (!tag) return original;
2538
2538
  const base = original;
2539
2539
  if (base.role === "assistant") return original;
2540
+ const tagCore = tag.replace(/\s+$/, "");
2541
+ let bodyStart = tagCore.length;
2542
+ if (core.text && core.text.charAt(bodyStart) === "\n") bodyStart += 1;
2543
+ const coreBody = core.text ? core.text.slice(bodyStart) : "";
2544
+ const originalBody = extractText(base.content);
2545
+ const trimEnd = (s) => s.replace(/\s+$/, "");
2546
+ if (coreBody && trimEnd(coreBody) !== trimEnd(originalBody)) {
2547
+ return rebuildBodyFromCore(original, coreBody, tag);
2548
+ }
2540
2549
  const rawBlocks = Array.isArray(base.content) ? base.content : typeof base.content === "string" ? [{ type: "text", text: base.content }] : [];
2541
2550
  const peeled = peelRefTagBlocks(rawBlocks);
2542
2551
  const newBlocks = [...peeled];
@@ -2560,6 +2569,23 @@ ${tag}` : tag };
2560
2569
  content: [...peeled, { type: "text", text: tag }]
2561
2570
  };
2562
2571
  }
2572
+ function rebuildBodyFromCore(original, coreBody, tag) {
2573
+ const base = original;
2574
+ const text = `${coreBody.replace(/\s+$/, "")}
2575
+
2576
+ ${tag}`;
2577
+ if (typeof base.content === "string") {
2578
+ return { ...original, content: text };
2579
+ }
2580
+ if (Array.isArray(base.content)) {
2581
+ const nonText = base.content.filter((b) => b.type !== "text");
2582
+ return {
2583
+ ...original,
2584
+ content: [...nonText, { type: "text", text }]
2585
+ };
2586
+ }
2587
+ return { ...original, content: [{ type: "text", text }] };
2588
+ }
2563
2589
  function peelRefTagBlocks(blocks) {
2564
2590
  const out = [];
2565
2591
  for (const block of blocks) {
@@ -2730,6 +2756,50 @@ function isPiHost(sm) {
2730
2756
  const source = sm;
2731
2757
  return typeof source.buildContextEntries === "function";
2732
2758
  }
2759
+ function mergeLiveEntries(entries, live) {
2760
+ const persisted = entries.filter((e) => e.type === "message");
2761
+ const out = [];
2762
+ let p = 0;
2763
+ let unmatched = 0;
2764
+ for (let i = 0; i < live.length; i++) {
2765
+ const msg = live[i];
2766
+ let matched;
2767
+ let j = p;
2768
+ while (j < persisted.length && persisted[j].message.role !== msg.role) j++;
2769
+ if (j < persisted.length && sameMessage(persisted[j].message, msg)) {
2770
+ matched = persisted[j];
2771
+ p = j + 1;
2772
+ }
2773
+ if (matched) {
2774
+ out.push(matched);
2775
+ } else {
2776
+ unmatched++;
2777
+ out.push({
2778
+ type: "message",
2779
+ id: `live-${i}`,
2780
+ parentId: null,
2781
+ timestamp: String(msg.timestamp ?? Date.now()),
2782
+ message: msg
2783
+ });
2784
+ }
2785
+ }
2786
+ if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
2787
+ return out;
2788
+ }
2789
+ function sameMessage(a, b) {
2790
+ const ra = a.role;
2791
+ const rb = b.role;
2792
+ if (ra !== rb) return false;
2793
+ const ca = a.content;
2794
+ const cb = b.content;
2795
+ if (ca === void 0 || cb === void 0) return false;
2796
+ try {
2797
+ return JSON.stringify(ca) === JSON.stringify(cb);
2798
+ } catch (e) {
2799
+ logWarn("runtime", { event: "message-compare-failed", error: e instanceof Error ? e.message : String(e) });
2800
+ return a === b;
2801
+ }
2802
+ }
2733
2803
  function createRuntime(adapter) {
2734
2804
  const core = createCore({ countTokens: defaultCountTokens });
2735
2805
  const store = new SessionStateStore();
@@ -2758,11 +2828,12 @@ function createRuntime(adapter) {
2758
2828
  function configFor(ctx) {
2759
2829
  return resolveConfig(adapterRef, liveContextLimit(ctx));
2760
2830
  }
2761
- async function stateFor(ctx) {
2831
+ async function stateFor(ctx, liveMessages) {
2762
2832
  const sm = ctx.sessionManager;
2763
2833
  const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
2764
2834
  const entries = readContextEntries(sm);
2765
- return { state, coreMessages: entriesToCoreMessages(entries), entries };
2835
+ const merged = isPiHost(sm) || !liveMessages || liveMessages.length === 0 ? entries : mergeLiveEntries(entries, liveMessages);
2836
+ return { state, coreMessages: entriesToCoreMessages(merged), entries: merged };
2766
2837
  }
2767
2838
  async function save(state, ctx) {
2768
2839
  const sm = ctx.sessionManager;
@@ -7469,7 +7540,7 @@ function buildMessageOwnerMap(state) {
7469
7540
  return m;
7470
7541
  }
7471
7542
  function estimateTokens2(text) {
7472
- if (!text) return 0;
7543
+ if (typeof text !== "string" || !text) return 0;
7473
7544
  const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
7474
7545
  const cjkCount = cjk?.length ?? 0;
7475
7546
  return cjkCount + Math.ceil((text.length - cjkCount) / 4);
@@ -8561,6 +8632,23 @@ function truncate2(s, n) {
8561
8632
  return s.slice(0, n - 1) + "\u2026";
8562
8633
  }
8563
8634
 
8635
+ // src/compat.ts
8636
+ function normalizeSystemPrompt(input) {
8637
+ if (input === void 0) return "";
8638
+ if (Array.isArray(input)) return input.join("\n");
8639
+ return input;
8640
+ }
8641
+ function formatSystemPromptForEvent(base, append) {
8642
+ const normalized = normalizeSystemPrompt(base);
8643
+ return `${normalized}
8644
+
8645
+ ${append}`;
8646
+ }
8647
+ function getSystemPromptText(ctx) {
8648
+ const result = ctx.getSystemPrompt?.();
8649
+ return normalizeSystemPrompt(result);
8650
+ }
8651
+
8564
8652
  // src/commands.ts
8565
8653
  function makeCommands(runtime) {
8566
8654
  return [
@@ -8648,7 +8736,7 @@ async function statusReport(runtime, ctx) {
8648
8736
  const bd = nudge?.contextBreakdown;
8649
8737
  const limit = config.modelContextLimit;
8650
8738
  const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
8651
- const systemPromptText = ctx.getSystemPrompt?.() ?? "";
8739
+ const systemPromptText = getSystemPromptText(ctx);
8652
8740
  const systemPromptTokens = systemPromptText ? defaultCountTokens(systemPromptText) : 0;
8653
8741
  const framework = bd ? Math.max(0, tokenCount - classified - systemPromptTokens) : 0;
8654
8742
  const displayTotal = tokenCount;
@@ -8656,7 +8744,7 @@ async function statusReport(runtime, ctx) {
8656
8744
  const activeBlocksList = state.blocks.filter((b) => b.active);
8657
8745
  const totalBlocksList = state.blocks;
8658
8746
  const lines = [];
8659
- const versionStr = "0.1.29" ? `billion-context-pi@${"0.1.29"}` : "";
8747
+ const versionStr = "0.1.31" ? `billion-context-pi@${"0.1.31"}` : "";
8660
8748
  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");
8661
8749
  lines.push("\u2502 ACP Context Analysis \u2502");
8662
8750
  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");
@@ -8961,11 +9049,12 @@ async function readPackageJson(path4) {
8961
9049
  }
8962
9050
  function findNpmRoot(extDir) {
8963
9051
  let dir = dirname3(extDir);
8964
- while (dir !== "/" && dir !== ".") {
9052
+ for (; ; ) {
8965
9053
  if (dir.endsWith("node_modules")) return dirname3(dir);
8966
- dir = dirname3(dir);
9054
+ const parent = dirname3(dir);
9055
+ if (parent === dir) return void 0;
9056
+ dir = parent;
8967
9057
  }
8968
- return void 0;
8969
9058
  }
8970
9059
  async function findExtensionDir() {
8971
9060
  let dir = dirname3(fileURLToPath(import.meta.url));
@@ -9021,7 +9110,7 @@ async function checkForUpdate(autoUpdate, notify) {
9021
9110
  const data = await res.json();
9022
9111
  const latest = data.version;
9023
9112
  if (!latest) return;
9024
- const current = runtimeVersion ?? "0.1.29";
9113
+ const current = runtimeVersion ?? "0.1.31";
9025
9114
  const hasUpdate = isNewer(latest, current);
9026
9115
  debug.event("update-check", {
9027
9116
  current,
@@ -9260,7 +9349,7 @@ function wireSessionLifecycle(pi, runtime) {
9260
9349
  runtime.store.invalidate();
9261
9350
  runtime.clearNudgeTracking();
9262
9351
  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 });
9352
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.31" : null });
9264
9353
  try {
9265
9354
  const user = await loadUserConfig(ctx.cwd);
9266
9355
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
@@ -9289,7 +9378,7 @@ function wireContextTransform(pi, runtime) {
9289
9378
  const sid = ctx.sessionManager.getSessionId();
9290
9379
  const release = await runtime.acquireLock(sid);
9291
9380
  try {
9292
- const { state, coreMessages, entries } = await runtime.stateFor(ctx);
9381
+ const { state, coreMessages, entries } = await runtime.stateFor(ctx, event.messages);
9293
9382
  const config = runtime.configFor(ctx);
9294
9383
  const coveredIds = collectCoveredMessageIds(state);
9295
9384
  const realUsage = ctx.getContextUsage?.();
@@ -9382,15 +9471,13 @@ function wireSystemPrompt(pi, runtime) {
9382
9471
  const delegate = runtime.adapter.delegate !== false;
9383
9472
  const prompt = delegate ? `${ACP_SYSTEM_PROMPT}
9384
9473
  ${ACP_DELEGATE_PROMPT}` : ACP_SYSTEM_PROMPT;
9385
- return { systemPrompt: `${event.systemPrompt}
9386
-
9387
- ${prompt}` };
9474
+ return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, prompt) };
9388
9475
  });
9389
9476
  }
9390
9477
  function collectOriginals(entries) {
9391
9478
  const map = /* @__PURE__ */ new Map();
9392
9479
  for (const entry of entries) {
9393
- if (entry.type === "message") {
9480
+ if (entry.type === "message" && entry.message) {
9394
9481
  map.set(entry.id, entry.message);
9395
9482
  } else if (entry.type === "custom_message") {
9396
9483
  const content = typeof entry.content === "string" ? [{ type: "text", text: entry.content }] : entry.content;