billion-context-omp 0.3.2 → 0.3.3-pr.145.10

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/README.md CHANGED
@@ -1,7 +1,22 @@
1
- # billion-context-omp
1
+ # billion-context-omp — DEPRECATED
2
+
3
+ > [!WARNING]
4
+ > **This package is deprecated.** For omp (oh-my-pi) context compression, use the
5
+ > [billion-context](https://github.com/ranxianglei/billion-context) proxy, which now
6
+ > supports omp directly:
7
+ >
8
+ > ```bash
9
+ > npm i -g billion-context && bili setup omp
10
+ > ```
11
+ >
12
+ > The proxy runs the same compression engine ([acp-kernel](https://github.com/ranxianglei/acp-kernel))
13
+ > at the HTTP layer, works with every omp model/provider (no extension API dependency),
14
+ > and is where all active development happens. This plugin repository is maintenance-only.
2
15
 
3
16
  [English](./README.md) | [中文](./README.zh-CN.md)
4
17
 
18
+ ---
19
+
5
20
  <p align="center">
6
21
  <strong>Billion-Context</strong> for <a href="https://github.com/can1357/oh-my-pi">omp (oh-my-pi)</a>
7
22
  <br />
@@ -10,12 +25,6 @@ The model decides <em>when</em> and <em>what</em> to compress — not a hard lim
10
25
 
11
26
  ---
12
27
 
13
- <p align="center">
14
- <a href="https://www.npmjs.com/package/billion-context-omp"><img src="https://img.shields.io/npm/v/billion-context-omp.svg?style=flat-square" alt="npm"></a>
15
- <a href="https://github.com/ranxianglei/billion-context-omp/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/billion-context-omp.svg?style=flat-square" alt="license"></a>
16
- <a href="https://github.com/ranxianglei/billion-context-omp"><img src="https://img.shields.io/badge/GitHub-ranxianglei%2Fbillion--context--omp-181717?style=flat-square&logo=github" alt="GitHub"></a>
17
- </p>
18
-
19
28
  <p align="center">
20
29
  <code>omp install billion-context-omp</code>
21
30
  </p>
package/README.zh-CN.md CHANGED
@@ -1,12 +1,17 @@
1
- [English](./README.md) | [中文](./README.zh-CN.md)
2
-
3
- # billion-context-omp
1
+ # billion-context-omp — 已废弃
2
+
3
+ > [!WARNING]
4
+ > **本包已废弃。** omp (oh-my-pi) 的上下文压缩请改用
5
+ > [billion-context](https://github.com/ranxianglei/billion-context) 代理版本,现已直接支持 omp:
6
+ >
7
+ > ```bash
8
+ > npm i -g billion-context && bili setup omp
9
+ > ```
10
+ >
11
+ > 代理在 HTTP 层运行同一压缩引擎([acp-kernel](https://github.com/ranxianglei/acp-kernel)),
12
+ > 适配 omp 的全部模型/供应商(不依赖插件扩展 API),后续开发都在那边进行。本插件仓库仅保留维护。
4
13
 
5
- <p align="center">
6
- 面向 <a href="https://github.com/can1357/oh-my-pi">omp (oh-my-pi)</a> 的 <strong>Billion-Context</strong>
7
- <br />
8
- 由模型决定<em>何时</em>压缩、<em>压缩什么</em> —— 而不是硬截断。
9
- </p>
14
+ [English](./README.md) | [中文](./README.zh-CN.md)
10
15
 
11
16
  ---
12
17
 
package/dist/index.js CHANGED
@@ -2348,6 +2348,144 @@ function countOccurrences(haystack, needle) {
2348
2348
  }
2349
2349
  return count;
2350
2350
  }
2351
+ var COMPRESS_TOOL_NAME = "compress";
2352
+ var DECOMPRESS_TOOL_NAME = "decompress";
2353
+ var SEARCH_CONTEXT_TOOL_NAME = "search_context";
2354
+ var ACP_STATUS_TOOL_NAME = "acp_status";
2355
+ var COMPRESS_TOOL = {
2356
+ name: COMPRESS_TOOL_NAME,
2357
+ description: "Replace a contiguous range of older conversation with a detailed summary you write. Use when content is genuinely consumed. Batch form: content=[{startId,endId,summary,topic?}]. REQUIRED \u2014 compress without content is invalid.",
2358
+ input_schema: {
2359
+ type: "object",
2360
+ properties: {
2361
+ topic: { type: "string", description: "Optional short title for the compressed range" },
2362
+ content: {
2363
+ type: "array",
2364
+ description: "One or more ranges to compress into separate summary blocks",
2365
+ items: {
2366
+ type: "object",
2367
+ properties: {
2368
+ topic: { type: "string" },
2369
+ startId: { type: "string", description: "mNNNNN ref at the start of the range" },
2370
+ endId: { type: "string", description: "mNNNNN ref at the end of the range" },
2371
+ summary: { type: "string", description: "Self-contained summary replacing the range" }
2372
+ },
2373
+ required: ["startId", "endId", "summary"]
2374
+ }
2375
+ }
2376
+ },
2377
+ required: ["content"]
2378
+ }
2379
+ };
2380
+ var COMPRESS_TOOL_OPENAI = {
2381
+ type: "function",
2382
+ function: {
2383
+ name: COMPRESS_TOOL_NAME,
2384
+ description: COMPRESS_TOOL.description,
2385
+ parameters: {
2386
+ type: "object",
2387
+ properties: {
2388
+ topic: { type: "string", description: "Optional short title for the compressed range" },
2389
+ content: {
2390
+ type: "array",
2391
+ description: "One or more ranges to compress into separate summary blocks. REQUIRED \u2014 compress without content is invalid.",
2392
+ items: {
2393
+ type: "object",
2394
+ properties: {
2395
+ topic: { type: "string" },
2396
+ startId: { type: "string", description: "mNNNNN ref at the start of the range" },
2397
+ endId: { type: "string", description: "mNNNNN ref at the end of the range" },
2398
+ summary: { type: "string", description: "Self-contained summary replacing the range" }
2399
+ },
2400
+ required: ["startId", "endId", "summary"]
2401
+ }
2402
+ }
2403
+ },
2404
+ required: ["content"]
2405
+ }
2406
+ }
2407
+ };
2408
+ var DECOMPRESS_TOOL_OPENAI = {
2409
+ type: "function",
2410
+ function: {
2411
+ name: DECOMPRESS_TOOL_NAME,
2412
+ description: "Restores previously compressed content. Use when you need exact details lost in compression. By default restores one tier up. Use full:true for all the way to original messages. Use toFile to write to file instead of inflating context.",
2413
+ parameters: {
2414
+ type: "object",
2415
+ properties: {
2416
+ blockId: { type: "string", description: "Block ID to decompress (e.g. b5)" },
2417
+ toFile: { type: "string", description: "Optional: write content to file instead of context" },
2418
+ full: { type: "boolean", description: "Restore all the way to original messages" }
2419
+ },
2420
+ required: ["blockId"]
2421
+ }
2422
+ }
2423
+ };
2424
+ var SEARCH_CONTEXT_TOOL_OPENAI = {
2425
+ type: "function",
2426
+ function: {
2427
+ name: SEARCH_CONTEXT_TOOL_NAME,
2428
+ description: "Search through compressed block summaries by keyword. Use BEFORE decompressing to find the right block.",
2429
+ parameters: {
2430
+ type: "object",
2431
+ properties: {
2432
+ query: { type: "string", description: "Search query" },
2433
+ limit: { type: "number", description: "Max results (default 5)" }
2434
+ },
2435
+ required: ["query"]
2436
+ }
2437
+ }
2438
+ };
2439
+ var ACP_STATUS_TOOL_OPENAI = {
2440
+ type: "function",
2441
+ function: {
2442
+ name: ACP_STATUS_TOOL_NAME,
2443
+ description: "Show context usage and compressible ranges. No args = overview. Use to find what to compress next.",
2444
+ parameters: {
2445
+ type: "object",
2446
+ properties: {}
2447
+ }
2448
+ }
2449
+ };
2450
+ var DECOMPRESS_TOOL = {
2451
+ name: DECOMPRESS_TOOL_NAME,
2452
+ description: DECOMPRESS_TOOL_OPENAI.function.description,
2453
+ input_schema: DECOMPRESS_TOOL_OPENAI.function.parameters
2454
+ };
2455
+ var SEARCH_CONTEXT_TOOL = {
2456
+ name: SEARCH_CONTEXT_TOOL_NAME,
2457
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
2458
+ input_schema: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
2459
+ };
2460
+ var ACP_STATUS_TOOL = {
2461
+ name: ACP_STATUS_TOOL_NAME,
2462
+ description: ACP_STATUS_TOOL_OPENAI.function.description,
2463
+ input_schema: ACP_STATUS_TOOL_OPENAI.function.parameters
2464
+ };
2465
+ var COMPRESS_TOOL_RESPONSES = {
2466
+ type: "function",
2467
+ name: COMPRESS_TOOL_NAME,
2468
+ description: COMPRESS_TOOL.description,
2469
+ parameters: COMPRESS_TOOL_OPENAI.function.parameters
2470
+ };
2471
+ var DECOMPRESS_TOOL_RESPONSES = {
2472
+ type: "function",
2473
+ name: DECOMPRESS_TOOL_OPENAI.function.name,
2474
+ description: DECOMPRESS_TOOL_OPENAI.function.description,
2475
+ parameters: DECOMPRESS_TOOL_OPENAI.function.parameters
2476
+ };
2477
+ var SEARCH_CONTEXT_TOOL_RESPONSES = {
2478
+ type: "function",
2479
+ name: SEARCH_CONTEXT_TOOL_OPENAI.function.name,
2480
+ description: SEARCH_CONTEXT_TOOL_OPENAI.function.description,
2481
+ parameters: SEARCH_CONTEXT_TOOL_OPENAI.function.parameters
2482
+ };
2483
+ var ACP_STATUS_TOOL_RESPONSES = {
2484
+ type: "function",
2485
+ name: ACP_STATUS_TOOL_OPENAI.function.name,
2486
+ description: ACP_STATUS_TOOL_OPENAI.function.description,
2487
+ parameters: ACP_STATUS_TOOL_OPENAI.function.parameters
2488
+ };
2351
2489
  function parseBlockIdArg(arg) {
2352
2490
  const normalized = arg.trim().toLowerCase();
2353
2491
  const refMatch = /^b0*(\d+)$/.exec(normalized);
@@ -3529,6 +3667,7 @@ When context usage passes a threshold, the system appends a breakdown showing wh
3529
3667
 
3530
3668
  // node_modules/acp-kernel/dist/wire/index.js
3531
3669
  import { createHash as createHash2 } from "crypto";
3670
+ import { createHash as createHash22 } from "crypto";
3532
3671
  function hashId(s) {
3533
3672
  return createHash2("sha256").update(s, "utf8").digest("hex").slice(0, 16);
3534
3673
  }
@@ -4359,12 +4498,6 @@ function mirrorResponsesToCore(view, systemText) {
4359
4498
  });
4360
4499
  return msgs;
4361
4500
  }
4362
-
4363
- // src/messages.ts
4364
- import { createHash as createHash3 } from "crypto";
4365
- var REF_TAG_SOURCE = "(?:<acp\\s[^>]*>m\\d+</acp>|\\[m\\d+\\])";
4366
- var REF_TAG = new RegExp(`^${REF_TAG_SOURCE}\\s?\\n?`);
4367
- var TRAILING_REF_TAG = new RegExp(`\\n*${REF_TAG_SOURCE}\\s*$`);
4368
4501
  function compressToolArgs(call) {
4369
4502
  let args = call.arguments;
4370
4503
  if (typeof args === "string") {
@@ -4395,12 +4528,159 @@ function compressToolArgs(call) {
4395
4528
  const ia = inner;
4396
4529
  return Array.isArray(ia.content) ? { content: ia.content, topic: ia.topic, summaryMaxChars: ia.summaryMaxChars } : { content: [ia] };
4397
4530
  }
4531
+ function toolCallNames(msgs) {
4532
+ const names = /* @__PURE__ */ new Map();
4533
+ for (const m of msgs) {
4534
+ if (m.contentType === "tool-call" && m.toolCallId && m.toolName) names.set(m.toolCallId, m.toolName);
4535
+ }
4536
+ return names;
4537
+ }
4538
+ function toolResultTextsCore(msgs) {
4539
+ const results = /* @__PURE__ */ new Map();
4540
+ for (const m of msgs) {
4541
+ if (m.contentType !== "tool-result" || !m.toolCallId) continue;
4542
+ results.set(m.toolCallId, m.text ?? "");
4543
+ }
4544
+ return results;
4545
+ }
4546
+ function findCompressCallsCore(msg) {
4547
+ if (msg.contentType !== "tool-call" || !msg.toolName) return [];
4548
+ const args = compressToolArgs({ name: msg.toolName, arguments: msg.text });
4549
+ if (!args) return [];
4550
+ const content = args.content;
4551
+ if (!Array.isArray(content)) return [];
4552
+ const ranges = [];
4553
+ const callTopic = typeof args.topic === "string" ? args.topic : void 0;
4554
+ for (const item of content) {
4555
+ const r = item;
4556
+ if (typeof r.startId !== "string" || typeof r.endId !== "string" || typeof r.summary !== "string" || r.summary.length === 0) continue;
4557
+ ranges.push({
4558
+ startRef: r.startId,
4559
+ endRef: r.endId,
4560
+ summary: r.summary,
4561
+ topic: typeof r.topic === "string" ? r.topic : callTopic,
4562
+ summaryMaxChars: typeof args.summaryMaxChars === "number" ? args.summaryMaxChars : void 0,
4563
+ compressCallId: msg.toolCallId ?? ""
4564
+ });
4565
+ }
4566
+ return ranges.length > 0 ? [{ id: msg.toolCallId ?? "", ranges }] : [];
4567
+ }
4568
+ function corePieceKey(cm) {
4569
+ return `${cm.role}|${cm.contentType}|${cm.toolName ?? ""}|${(cm.text ?? "").slice(0, 4096)}`;
4570
+ }
4571
+ function spanFingerprintCore(coreMessages, startId, endId) {
4572
+ const find = (id) => coreMessages.find((cm) => cm.id === id);
4573
+ const first = find(startId);
4574
+ const last = find(endId);
4575
+ if (!first || !last) return "";
4576
+ return createHash22("sha1").update(`${corePieceKey(first)}\0${corePieceKey(last)}`).digest("hex").slice(0, 8);
4577
+ }
4578
+ function spanFingerprintCoreIdx(coreMessages, startIdx, endIdx) {
4579
+ const first = coreMessages[startIdx];
4580
+ const last = coreMessages[endIdx];
4581
+ if (!first || !last) return "";
4582
+ return createHash22("sha1").update(`${corePieceKey(first)}\0${corePieceKey(last)}`).digest("hex").slice(0, 8);
4583
+ }
4584
+ function boundaryRawCore(ref, byRef, blocks, coreMessages, pick) {
4585
+ const raw = byRef[ref];
4586
+ if (raw) return raw;
4587
+ const m = /^b(\d+)$/i.exec(ref.trim());
4588
+ if (!m) return "";
4589
+ const block = blocks.find((b) => b.blockId.toLowerCase() === `b${m[1]}`);
4590
+ if (!block) return "";
4591
+ const idx = (id) => coreMessages.findIndex((cm) => cm.id === (byRef[id] ?? id));
4592
+ let best = -1;
4593
+ for (const id of block.effectiveMessageIds) {
4594
+ const i = idx(id);
4595
+ if (i < 0) continue;
4596
+ if (best < 0 || (pick === "min" ? i < best : i > best)) best = i;
4597
+ }
4598
+ return best < 0 ? "" : coreMessages[best]?.id ?? "";
4599
+ }
4600
+ function boundaryIndexCore(ref, byRef, blocks, coreMessages, pick, fallbackIdx = -1) {
4601
+ const id = boundaryRawCore(ref, byRef, blocks, coreMessages, pick);
4602
+ if (id) {
4603
+ const i = coreMessages.findIndex((cm) => cm.id === id);
4604
+ if (i >= 0) return i;
4605
+ }
4606
+ return fallbackIdx >= 0 && fallbackIdx < coreMessages.length ? fallbackIdx : -1;
4607
+ }
4608
+ function refOfPieceCore(coreMessages, idx, byRef) {
4609
+ const id = coreMessages[idx]?.id;
4610
+ if (!id) return "";
4611
+ for (const [ref, mapped] of Object.entries(byRef)) if (mapped === id) return ref;
4612
+ return "";
4613
+ }
4614
+ function staleRangeCore(r, rangeIndex, resultText, coreMessages, callIndex, byRef, blocks) {
4615
+ const pm = resultText.match(/\[pos=([0-9,-]+)\]/);
4616
+ const pair = pm ? pm[1].split(",")[rangeIndex] ?? "-" : "-";
4617
+ const hinted = pair !== "-";
4618
+ const [ps, pe] = pair === "-" ? ["", ""] : pair.split("-");
4619
+ const fbStart = ps && ps !== "" ? Number.parseInt(ps, 10) : -1;
4620
+ const fbEnd = pe && pe !== "" ? Number.parseInt(pe, 10) : -1;
4621
+ const startRaw = boundaryRawCore(r.startRef, byRef, blocks, coreMessages, "min");
4622
+ const endRaw = boundaryRawCore(r.endRef, byRef, blocks, coreMessages, "max");
4623
+ const rawStartIdx = startRaw ? coreMessages.findIndex((cm) => cm.id === startRaw) : -1;
4624
+ const rawEndIdx = endRaw ? coreMessages.findIndex((cm) => cm.id === endRaw) : -1;
4625
+ const startIdx = rawStartIdx >= 0 ? rawStartIdx : fbStart >= 0 && fbStart < coreMessages.length ? fbStart : -1;
4626
+ const endIdx = rawEndIdx >= 0 ? rawEndIdx : fbEnd >= 0 && fbEnd < coreMessages.length ? fbEnd : -1;
4627
+ if (startIdx < 0 || endIdx < 0) {
4628
+ if (!/^b\d+$/i.test(r.startRef.trim()) && !/^b\d+$/i.test(r.endRef.trim()))
4629
+ return { reject: `unresolved ${r.startRef}..${r.endRef} -> ${startIdx}..${endIdx}`, ...hinted ? { hint: true } : {} };
4630
+ return {};
4631
+ }
4632
+ if (endIdx > callIndex) return { reject: `end idx ${endIdx} > callIndex ${callIndex}`, ...hinted ? { hint: true } : {} };
4633
+ const m = resultText.match(/\[fp=([0-9a-f,-]+)\]/);
4634
+ if (m) {
4635
+ const want = m[1].split(",")[rangeIndex];
4636
+ if (want !== void 0 && want !== "-") {
4637
+ const got = spanFingerprintCoreIdx(coreMessages, startIdx, endIdx);
4638
+ if (want !== got) return { reject: `fp ${r.startRef}..${r.endRef} want ${want} got ${got} @${startIdx}..${endIdx}`, ...hinted ? { hint: true } : {} };
4639
+ }
4640
+ }
4641
+ const remap = {};
4642
+ if (/^m\d+$/i.test(r.startRef.trim()) && rawStartIdx < 0) {
4643
+ const ref = refOfPieceCore(coreMessages, startIdx, byRef);
4644
+ if (!ref) return { reject: `recovered ${r.startRef} @${startIdx} has no ref (protected piece)`, ...hinted ? { hint: true } : {} };
4645
+ remap.startRef = ref;
4646
+ }
4647
+ if (/^m\d+$/i.test(r.endRef.trim()) && rawEndIdx < 0) {
4648
+ const ref = refOfPieceCore(coreMessages, endIdx, byRef);
4649
+ if (!ref) return { reject: `recovered ${r.endRef} @${endIdx} has no ref (protected piece)`, ...hinted ? { hint: true } : {} };
4650
+ remap.endRef = ref;
4651
+ }
4652
+ if (!remap.startRef && !remap.endRef) return {};
4653
+ return { remap, recovered: { pos: pair, startIdx, endIdx } };
4654
+ }
4655
+ function rangeFingerprintsCore(ranges, coreMessages, byRef, blocks) {
4656
+ return ranges.map((r) => {
4657
+ const start = boundaryRawCore(r.startRef, byRef, blocks, coreMessages, "min");
4658
+ const end = start ? boundaryRawCore(r.endRef, byRef, blocks, coreMessages, "max") : "";
4659
+ if (start && end) {
4660
+ const fp = spanFingerprintCore(coreMessages, start, end);
4661
+ if (fp.length > 0) return fp;
4662
+ }
4663
+ return "-";
4664
+ });
4665
+ }
4666
+ function rangePositionsCore(ranges, coreMessages, byRef, blocks) {
4667
+ return ranges.map((r) => {
4668
+ const s = boundaryIndexCore(r.startRef, byRef, blocks, coreMessages, "min");
4669
+ const e = s >= 0 ? boundaryIndexCore(r.endRef, byRef, blocks, coreMessages, "max") : -1;
4670
+ return s >= 0 && e >= 0 ? `${s}-${e}` : "-";
4671
+ });
4672
+ }
4673
+
4674
+ // src/messages.ts
4675
+ import { createHash as createHash3 } from "crypto";
4676
+ var REF_TAG_SOURCE = "(?:<acp\\s[^>]*>m\\d+</acp>|\\[m\\d+\\])";
4677
+ var REF_TAG = new RegExp(`^${REF_TAG_SOURCE}\\s?\\n?`);
4678
+ var TRAILING_REF_TAG = new RegExp(`\\n*${REF_TAG_SOURCE}\\s*$`);
4398
4679
  function stripRefTag(text) {
4399
4680
  return text.replace(REF_TAG, "").replace(TRAILING_REF_TAG, "");
4400
4681
  }
4401
4682
 
4402
4683
  // src/wire-fold.ts
4403
- import { createHash as createHash4 } from "crypto";
4404
4684
  function detectProviderWireFormat(payload) {
4405
4685
  const fmt2 = detectWireFormat(payload);
4406
4686
  if (fmt2 === "anthropic" || fmt2 === "openai" || fmt2 === "responses") return fmt2;
@@ -4565,148 +4845,6 @@ function coreIdentity(msg) {
4565
4845
  text: stripRefTag(msg.text ?? "")
4566
4846
  });
4567
4847
  }
4568
- function toolCallNames(msgs) {
4569
- const names = /* @__PURE__ */ new Map();
4570
- for (const m of msgs) {
4571
- if (m.contentType === "tool-call" && m.toolCallId && m.toolName) names.set(m.toolCallId, m.toolName);
4572
- }
4573
- return names;
4574
- }
4575
- function toolResultTextsCore(msgs) {
4576
- const results = /* @__PURE__ */ new Map();
4577
- for (const m of msgs) {
4578
- if (m.contentType !== "tool-result" || !m.toolCallId) continue;
4579
- results.set(m.toolCallId, m.text ?? "");
4580
- }
4581
- return results;
4582
- }
4583
- function findCompressCallsCore(msg) {
4584
- if (msg.contentType !== "tool-call" || !msg.toolName) return [];
4585
- const args = compressToolArgs({ name: msg.toolName, arguments: msg.text });
4586
- if (!args) return [];
4587
- const content = args.content;
4588
- if (!Array.isArray(content)) return [];
4589
- const ranges = [];
4590
- const callTopic = typeof args.topic === "string" ? args.topic : void 0;
4591
- for (const item of content) {
4592
- const r = item;
4593
- if (typeof r.startId !== "string" || typeof r.endId !== "string" || typeof r.summary !== "string" || r.summary.length === 0) continue;
4594
- ranges.push({
4595
- startRef: r.startId,
4596
- endRef: r.endId,
4597
- summary: r.summary,
4598
- topic: typeof r.topic === "string" ? r.topic : callTopic,
4599
- summaryMaxChars: typeof args.summaryMaxChars === "number" ? args.summaryMaxChars : void 0,
4600
- compressCallId: msg.toolCallId ?? ""
4601
- });
4602
- }
4603
- return ranges.length > 0 ? [{ id: msg.toolCallId ?? "", ranges }] : [];
4604
- }
4605
- function corePieceKey(cm) {
4606
- return `${cm.role}|${cm.contentType}|${cm.toolName ?? ""}|${(cm.text ?? "").slice(0, 4096)}`;
4607
- }
4608
- function spanFingerprintCore(coreMessages, startId, endId) {
4609
- const find = (id) => coreMessages.find((cm) => cm.id === id);
4610
- const first = find(startId);
4611
- const last = find(endId);
4612
- if (!first || !last) return "";
4613
- return createHash4("sha1").update(`${corePieceKey(first)}\0${corePieceKey(last)}`).digest("hex").slice(0, 8);
4614
- }
4615
- function spanFingerprintCoreIdx(coreMessages, startIdx, endIdx) {
4616
- const first = coreMessages[startIdx];
4617
- const last = coreMessages[endIdx];
4618
- if (!first || !last) return "";
4619
- return createHash4("sha1").update(`${corePieceKey(first)}\0${corePieceKey(last)}`).digest("hex").slice(0, 8);
4620
- }
4621
- function boundaryRawCore(ref, byRef, blocks, coreMessages, pick) {
4622
- const raw = byRef[ref];
4623
- if (raw) return raw;
4624
- const m = /^b(\d+)$/i.exec(ref.trim());
4625
- if (!m) return "";
4626
- const block = blocks.find((b) => b.blockId.toLowerCase() === `b${m[1]}`);
4627
- if (!block) return "";
4628
- const idx = (id) => coreMessages.findIndex((cm) => cm.id === (byRef[id] ?? id));
4629
- let best = -1;
4630
- for (const id of block.effectiveMessageIds) {
4631
- const i = idx(id);
4632
- if (i < 0) continue;
4633
- if (best < 0 || (pick === "min" ? i < best : i > best)) best = i;
4634
- }
4635
- return best < 0 ? "" : coreMessages[best]?.id ?? "";
4636
- }
4637
- function boundaryIndexCore(ref, byRef, blocks, coreMessages, pick, fallbackIdx = -1) {
4638
- const id = boundaryRawCore(ref, byRef, blocks, coreMessages, pick);
4639
- if (id) {
4640
- const i = coreMessages.findIndex((cm) => cm.id === id);
4641
- if (i >= 0) return i;
4642
- }
4643
- return fallbackIdx >= 0 && fallbackIdx < coreMessages.length ? fallbackIdx : -1;
4644
- }
4645
- function refOfPieceCore(coreMessages, idx, byRef) {
4646
- const id = coreMessages[idx]?.id;
4647
- if (!id) return "";
4648
- for (const [ref, mapped] of Object.entries(byRef)) if (mapped === id) return ref;
4649
- return "";
4650
- }
4651
- function staleRangeCore(r, rangeIndex, resultText, coreMessages, callIndex, byRef, blocks) {
4652
- const pm = resultText.match(/\[pos=([0-9,-]+)\]/);
4653
- const pair = pm ? pm[1].split(",")[rangeIndex] ?? "-" : "-";
4654
- const hinted = pair !== "-";
4655
- const [ps, pe] = pair === "-" ? ["", ""] : pair.split("-");
4656
- const fbStart = ps && ps !== "" ? Number.parseInt(ps, 10) : -1;
4657
- const fbEnd = pe && pe !== "" ? Number.parseInt(pe, 10) : -1;
4658
- const startRaw = boundaryRawCore(r.startRef, byRef, blocks, coreMessages, "min");
4659
- const endRaw = boundaryRawCore(r.endRef, byRef, blocks, coreMessages, "max");
4660
- const rawStartIdx = startRaw ? coreMessages.findIndex((cm) => cm.id === startRaw) : -1;
4661
- const rawEndIdx = endRaw ? coreMessages.findIndex((cm) => cm.id === endRaw) : -1;
4662
- const startIdx = rawStartIdx >= 0 ? rawStartIdx : fbStart >= 0 && fbStart < coreMessages.length ? fbStart : -1;
4663
- const endIdx = rawEndIdx >= 0 ? rawEndIdx : fbEnd >= 0 && fbEnd < coreMessages.length ? fbEnd : -1;
4664
- if (startIdx < 0 || endIdx < 0) {
4665
- if (!/^b\d+$/i.test(r.startRef.trim()) && !/^b\d+$/i.test(r.endRef.trim()))
4666
- return { reject: `unresolved ${r.startRef}..${r.endRef} -> ${startIdx}..${endIdx}`, ...hinted ? { hint: true } : {} };
4667
- return {};
4668
- }
4669
- if (endIdx > callIndex) return { reject: `end idx ${endIdx} > callIndex ${callIndex}`, ...hinted ? { hint: true } : {} };
4670
- const m = resultText.match(/\[fp=([0-9a-f,-]+)\]/);
4671
- if (m) {
4672
- const want = m[1].split(",")[rangeIndex];
4673
- if (want !== void 0 && want !== "-") {
4674
- const got = spanFingerprintCoreIdx(coreMessages, startIdx, endIdx);
4675
- if (want !== got) return { reject: `fp ${r.startRef}..${r.endRef} want ${want} got ${got} @${startIdx}..${endIdx}`, ...hinted ? { hint: true } : {} };
4676
- }
4677
- }
4678
- const remap = {};
4679
- if (/^m\d+$/i.test(r.startRef.trim()) && rawStartIdx < 0) {
4680
- const ref = refOfPieceCore(coreMessages, startIdx, byRef);
4681
- if (!ref) return { reject: `recovered ${r.startRef} @${startIdx} has no ref (protected piece)`, ...hinted ? { hint: true } : {} };
4682
- remap.startRef = ref;
4683
- }
4684
- if (/^m\d+$/i.test(r.endRef.trim()) && rawEndIdx < 0) {
4685
- const ref = refOfPieceCore(coreMessages, endIdx, byRef);
4686
- if (!ref) return { reject: `recovered ${r.endRef} @${endIdx} has no ref (protected piece)`, ...hinted ? { hint: true } : {} };
4687
- remap.endRef = ref;
4688
- }
4689
- if (!remap.startRef && !remap.endRef) return {};
4690
- return { remap, recovered: { pos: pair, startIdx, endIdx } };
4691
- }
4692
- function rangeFingerprintsCore(ranges, coreMessages, byRef, blocks) {
4693
- return ranges.map((r) => {
4694
- const start = boundaryRawCore(r.startRef, byRef, blocks, coreMessages, "min");
4695
- const end = start ? boundaryRawCore(r.endRef, byRef, blocks, coreMessages, "max") : "";
4696
- if (start && end) {
4697
- const fp = spanFingerprintCore(coreMessages, start, end);
4698
- if (fp.length > 0) return fp;
4699
- }
4700
- return "-";
4701
- });
4702
- }
4703
- function rangePositionsCore(ranges, coreMessages, byRef, blocks) {
4704
- return ranges.map((r) => {
4705
- const s = boundaryIndexCore(r.startRef, byRef, blocks, coreMessages, "min");
4706
- const e = s >= 0 ? boundaryIndexCore(r.endRef, byRef, blocks, coreMessages, "max") : -1;
4707
- return s >= 0 && e >= 0 ? `${s}-${e}` : "-";
4708
- });
4709
- }
4710
4848
  function toMirrorView(view) {
4711
4849
  const out = [];
4712
4850
  for (const message of view) {
@@ -4769,7 +4907,7 @@ function isViewFlip(foldedLen, lcp) {
4769
4907
  }
4770
4908
  function preserveCompressedSlot(prev) {
4771
4909
  const slot = freshSlot(prev);
4772
- slot.state = { ...slot.state, blocks: prev.state.blocks, messageRefs: prev.state.messageRefs, stats: prev.state.stats };
4910
+ slot.state = { ...slot.state, blocks: prev.state.blocks, messageRefs: prev.state.messageRefs, stats: prev.state.stats, nextBlockId: prev.state.nextBlockId, nextRunId: prev.state.nextRunId };
4773
4911
  slot.appliedCallIds = new Set(prev.appliedCallIds);
4774
4912
  return slot;
4775
4913
  }
@@ -4832,6 +4970,14 @@ function createRuntime(adapter) {
4832
4970
  coreSlots.set(sid, slot);
4833
4971
  lcp = 0;
4834
4972
  }
4973
+ if (lcp === 0 && slot.state.blocks.length > 0) {
4974
+ const presentIds = new Set(stream.map((m) => m.id));
4975
+ const surviving = slot.state.blocks.filter((b) => b.active === false || b.effectiveMessageIds.some((id) => presentIds.has(id)));
4976
+ if (surviving.length < slot.state.blocks.length) {
4977
+ debug.event("fold-purge-stale-blocks", { sid, before: slot.state.blocks.length, after: surviving.length });
4978
+ slot.state = { ...slot.state, blocks: surviving };
4979
+ }
4980
+ }
4835
4981
  const coreMessages = stream;
4836
4982
  const config = configFor(ctx);
4837
4983
  const names = toolCallNames(stream);
@@ -4967,17 +5113,31 @@ ${acp}`);
4967
5113
  logInfo("fold", { sid, event: "fold-restore-empty" });
4968
5114
  return false;
4969
5115
  }
5116
+ const byId = /* @__PURE__ */ new Map();
5117
+ for (const b of r.state.blocks) {
5118
+ const prev = byId.get(b.blockId);
5119
+ if (!prev || b.createdAt > prev.createdAt) byId.set(b.blockId, b);
5120
+ }
5121
+ const blocks = [...byId.values()];
5122
+ if (blocks.length < r.state.blocks.length) {
5123
+ logInfo("fold", { sid, event: "fold-restore-dedup", before: r.state.blocks.length, after: blocks.length });
5124
+ }
5125
+ const maxId = blocks.reduce((m, b) => {
5126
+ const n = /^b(\d+)$/i.exec(b.blockId);
5127
+ return n ? Math.max(m, Number.parseInt(n[1], 10)) : m;
5128
+ }, 0);
5129
+ const state = { ...r.state, blocks, nextBlockId: Math.max(r.state.nextBlockId ?? 1, maxId + 1) };
4970
5130
  coreSlots.set(sid, {
4971
5131
  identities: r.identities,
4972
5132
  foldedLen: r.foldedLen,
4973
5133
  preview: false,
4974
5134
  live: true,
4975
- state: r.state,
5135
+ state,
4976
5136
  coreMessages: r.coreMessages,
4977
5137
  appliedCallIds: r.appliedCallIds,
4978
5138
  rejectStreak: r.rejectStreak
4979
5139
  });
4980
- logInfo("fold", { sid, event: "fold-restore", blocks: r.state.blocks.length, foldedLen: r.foldedLen });
5140
+ logInfo("fold", { sid, event: "fold-restore", blocks: state.blocks.length, foldedLen: r.foldedLen, nextBlockId: state.nextBlockId });
4981
5141
  return true;
4982
5142
  }
4983
5143
  function snapshotOf(slot) {
@@ -5780,7 +5940,7 @@ async function statusReport(runtime, ctx) {
5780
5940
  const coveredIds = collectCoveredMessageIds(state);
5781
5941
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5782
5942
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
5783
- const versionStr = "0.3.2" ? `billion-context-omp@${"0.3.2"}` : void 0;
5943
+ const versionStr = "0.3.3" ? `billion-context-omp@${"0.3.3"}` : void 0;
5784
5944
  return buildStatusPanel({
5785
5945
  version: versionStr,
5786
5946
  tokenCount: sessionTokens,
@@ -6131,7 +6291,7 @@ async function autoInstallLatest(latest, npmDirOverride) {
6131
6291
  logWarn("update", { event: "install-abort", reason: "npmroot-not-found", extDir });
6132
6292
  return "failed";
6133
6293
  }
6134
- const prevVersion = (await readPackageJson(join4(npmDir, "node_modules", PACKAGE_NAME, "package.json")))?.version ?? "0.3.2";
6294
+ const prevVersion = (await readPackageJson(join4(npmDir, "node_modules", PACKAGE_NAME, "package.json")))?.version ?? "0.3.3";
6135
6295
  try {
6136
6296
  const keepAlive = setInterval(() => {
6137
6297
  }, 500);
@@ -6143,7 +6303,7 @@ async function autoInstallLatest(latest, npmDirOverride) {
6143
6303
  }
6144
6304
  const verify = await verifyInstall(npmDir, latest);
6145
6305
  if (!verify.ok) {
6146
- const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.3.2";
6306
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.3.3";
6147
6307
  logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
6148
6308
  const rb = await runNpm(installArgs(rollbackTo), npmDir);
6149
6309
  logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
@@ -6183,7 +6343,7 @@ async function checkForUpdate(autoUpdate, notify) {
6183
6343
  const data = await res.json();
6184
6344
  const latest = data.version;
6185
6345
  if (!latest) return;
6186
- const current = runtimeVersion ?? "0.3.2";
6346
+ const current = runtimeVersion ?? "0.3.3";
6187
6347
  const hasUpdate = isNewer(latest, current);
6188
6348
  debug.event("update-check", {
6189
6349
  current,
@@ -6438,7 +6598,7 @@ function wireSessionLifecycle(pi, runtime) {
6438
6598
  pi.on("session_start", async (_event, ctx) => {
6439
6599
  const sid = ctx.sessionManager.getSessionId();
6440
6600
  const modelInfo = ctx.model;
6441
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.3.2" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
6601
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.3.3" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
6442
6602
  if (!hostMeetsMinimum()) {
6443
6603
  logWarn("session", { event: "host-too-old", version: VERSION2, min: MIN_HOST_VERSION.join(".") });
6444
6604
  try {
@@ -6449,7 +6609,7 @@ function wireSessionLifecycle(pi, runtime) {
6449
6609
  }
6450
6610
  }
6451
6611
  const selfPath = import.meta.url;
6452
- const conflict = stampAndDetect(selfPath, true ? "0.3.2" : null);
6612
+ const conflict = stampAndDetect(selfPath, true ? "0.3.3" : null);
6453
6613
  if (conflict) {
6454
6614
  logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
6455
6615
  try {