billion-context-omp 0.1.8 → 0.2.0

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
@@ -3158,8 +3158,13 @@ function rangeFingerprints(ranges, coreMessages, byRef, blocks) {
3158
3158
  }
3159
3159
 
3160
3160
  // src/runtime.ts
3161
- function freshSlot() {
3162
- return { identities: [], foldedLen: 0, preview: false, state: createInitialState(), coreMessages: [], appliedCallIds: /* @__PURE__ */ new Set() };
3161
+ function freshSlot(preserveFrom) {
3162
+ const slot = { identities: [], foldedLen: 0, preview: false, state: createInitialState(), coreMessages: [], appliedCallIds: /* @__PURE__ */ new Set(), rejectStreak: 0 };
3163
+ if (preserveFrom) {
3164
+ slot.state = { ...slot.state, nudge: preserveFrom.state.nudge };
3165
+ slot.rejectStreak = preserveFrom.rejectStreak;
3166
+ }
3167
+ return slot;
3163
3168
  }
3164
3169
  function stateHasCompressCall(state, callId) {
3165
3170
  return state.blocks.some((b) => b.compressCallId === callId);
@@ -3205,7 +3210,7 @@ function createRuntime(adapter) {
3205
3210
  let slot = slotFor(sid);
3206
3211
  if (slot.preview) {
3207
3212
  debug.event("fold-refold", { sid, foldedLen: slot.foldedLen, lcp: 0, streamLen: stream.length, reason: "preview" });
3208
- slot = freshSlot();
3213
+ slot = freshSlot(slot);
3209
3214
  slots.set(sid, slot);
3210
3215
  }
3211
3216
  const ids = stream.map(messageIdentity);
@@ -3213,7 +3218,7 @@ function createRuntime(adapter) {
3213
3218
  while (lcp < Math.min(ids.length, slot.identities.length) && ids[lcp] === slot.identities[lcp]) lcp++;
3214
3219
  if (lcp < slot.foldedLen) {
3215
3220
  debug.event("fold-refold", { sid, foldedLen: slot.foldedLen, lcp, streamLen: ids.length });
3216
- slot = freshSlot();
3221
+ slot = freshSlot(slot);
3217
3222
  slots.set(sid, slot);
3218
3223
  lcp = 0;
3219
3224
  }
@@ -3295,6 +3300,11 @@ function createRuntime(adapter) {
3295
3300
  slot.state = state;
3296
3301
  if (toolCallId) slot.appliedCallIds.add(toolCallId);
3297
3302
  }
3303
+ function noteCompressOutcome(ctx, ok) {
3304
+ const slot = slotFor(sidOf(ctx));
3305
+ slot.rejectStreak = ok ? 0 : slot.rejectStreak + 1;
3306
+ return slot.rejectStreak;
3307
+ }
3298
3308
  return {
3299
3309
  core,
3300
3310
  get adapter() {
@@ -3314,6 +3324,7 @@ function createRuntime(adapter) {
3314
3324
  foldStream,
3315
3325
  stateFor,
3316
3326
  commitFoldState,
3327
+ noteCompressOutcome,
3317
3328
  forgetSession,
3318
3329
  primeFold,
3319
3330
  acquireLock
@@ -3414,6 +3425,11 @@ function makeCompressTool(runtime) {
3414
3425
  async function handleCompress(args, runtime, ctx, toolCallId) {
3415
3426
  const ranges = args.content ?? [];
3416
3427
  if (ranges.length === 0) return "No ranges provided.";
3428
+ const invalid = ranges.filter((r) => !r || typeof r.summary !== "string" || !r.summary.trim() || !r.startId || !r.endId);
3429
+ if (invalid.length > 0) {
3430
+ logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "invalid-ranges", count: invalid.length });
3431
+ return `Every range needs startId, endId and a summary (min 50 chars) \u2014 got ${invalid.length} range(s) missing fields. Re-send the FULL ranges array with summaries included.`;
3432
+ }
3417
3433
  const releaseLock = await runtime.acquireLock(ctx.sessionManager.getSessionId());
3418
3434
  try {
3419
3435
  const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
@@ -3444,7 +3460,7 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
3444
3460
  const invalidRanges = rangeSpecs.filter((r) => !r.startRef || !r.endRef || typeof r.startRef !== "string" || typeof r.endRef !== "string");
3445
3461
  if (invalidRanges.length > 0) {
3446
3462
  logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "invalid-ranges", count: invalidRanges.length, ranges: invalidRanges.map((r) => `${r.startRef}..${r.endRef}`) });
3447
- return `Rejected: ${invalidRanges.length} range(s) have invalid startId or endId (missing or non-string). All ranges must have valid message refs (e.g. "m00005") or block IDs (e.g. "b3"). No changes applied \u2014 run acp_status for current refs.`;
3463
+ return rejectionMessage(ctx, runtime, `Rejected: ${invalidRanges.length} range(s) have invalid startId or endId (missing or non-string). All ranges must have valid message refs (e.g. "m00005") or block IDs (e.g. "b3"). No changes applied \u2014 run acp_status for current refs.`);
3448
3464
  }
3449
3465
  let applied;
3450
3466
  try {
@@ -3456,12 +3472,13 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
3456
3472
  });
3457
3473
  } catch (e) {
3458
3474
  logThrow("compress", e, { sid: ctx.sessionManager.getSessionId(), phase: "applyCompression", ranges: rangeSpecs.length });
3459
- return `Compression failed: ${e instanceof Error ? e.message : String(e)}. No changes applied \u2014 state is unchanged.`;
3475
+ return rejectionMessage(ctx, runtime, `Compression failed: ${e instanceof Error ? e.message : String(e)}. No changes applied \u2014 state is unchanged.`);
3460
3476
  }
3461
3477
  if (applied.result.errors.length > 0) {
3462
3478
  logError("compress", { sid: ctx.sessionManager.getSessionId(), event: "apply-errors", count: applied.result.errors.length, errors: applied.result.errors.slice(0, 5) });
3463
- return `Compression rejected: ${applied.result.errors.join("; ")}. No changes applied \u2014 run acp_status to verify current state.`;
3479
+ return rejectionMessage(ctx, runtime, `Compression rejected: ${applied.result.errors.join("; ")}. No changes applied \u2014 run acp_status to verify current state.`);
3464
3480
  }
3481
+ runtime.noteCompressOutcome(ctx, true);
3465
3482
  await runtime.commitFoldState(ctx, applied.state, toolCallId);
3466
3483
  const { blocksCreated, tokensCompressed, warnings } = applied.result;
3467
3484
  const afterTokens = Math.max(0, beforeTokens - tokensCompressed);
@@ -3502,6 +3519,22 @@ async function handleCompress(args, runtime, ctx, toolCallId) {
3502
3519
  releaseLock();
3503
3520
  }
3504
3521
  }
3522
+ var LOOP_GUARD_STOP = 3;
3523
+ var LOOP_GUARD_SUPPRESS = 4;
3524
+ function rejectionMessage(ctx, runtime, base) {
3525
+ const streak = runtime.noteCompressOutcome(ctx, false);
3526
+ if (streak >= LOOP_GUARD_SUPPRESS) {
3527
+ logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "loop-guard", streak, mode: "suppressed" });
3528
+ return `Compression rejected (again \u2014 ${streak} consecutive rejections). No changes applied. STOP calling compress; it is not converging. Continue the task. Compress stays available: a fresh attempt works when acp_status shows a range that can meet the minimum size.`;
3529
+ }
3530
+ if (streak >= LOOP_GUARD_STOP) {
3531
+ logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "loop-guard", streak, mode: "stop-directive" });
3532
+ return `${base}
3533
+
3534
+ STOP: ${streak} compress calls rejected in a row. Do NOT retry the same range. Run acp_status to see what is actually compressible now; if no range can meet the minimum size, nothing is left to compress \u2014 stop and continue the actual task.`;
3535
+ }
3536
+ return base;
3537
+ }
3505
3538
 
3506
3539
  // src/decompress-tool.ts
3507
3540
  import { type as type2 } from "@oh-my-pi/omptype";
@@ -4464,7 +4497,7 @@ async function statusReport(runtime, ctx) {
4464
4497
  const coveredIds = collectCoveredMessageIds(state);
4465
4498
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
4466
4499
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
4467
- const versionStr = "0.1.8" ? `billion-context-omp@${"0.1.8"}` : void 0;
4500
+ const versionStr = "0.2.0" ? `billion-context-omp@${"0.2.0"}` : void 0;
4468
4501
  return buildStatusPanel({
4469
4502
  version: versionStr,
4470
4503
  tokenCount: sessionTokens,
@@ -4476,6 +4509,237 @@ async function statusReport(runtime, ctx) {
4476
4509
  });
4477
4510
  }
4478
4511
 
4512
+ // src/wire-transform.ts
4513
+ var AI = /* @__PURE__ */ Symbol("acp.streamIndex");
4514
+ function detectWireFormat(payload) {
4515
+ if (payload === null || typeof payload !== "object") return "unknown";
4516
+ const p = payload;
4517
+ const messages = p.messages;
4518
+ if (!Array.isArray(messages)) return "unknown";
4519
+ if ("system" in p || "anthropic_version" in p) return "anthropic";
4520
+ for (const m of messages) {
4521
+ if (m === null || typeof m !== "object") continue;
4522
+ const c = m.content;
4523
+ if (Array.isArray(c)) {
4524
+ for (const b of c) {
4525
+ if (b && typeof b === "object" && typeof b.type === "string") {
4526
+ if (b.type === "tool_use" || b.type === "tool_result" || b.type === "thinking") return "anthropic";
4527
+ if (b.type === "text" && "cache_control" in b) return "anthropic";
4528
+ }
4529
+ }
4530
+ }
4531
+ if (Array.isArray(m.tool_calls)) return "openai";
4532
+ if (m.role === "tool" && typeof m.tool_call_id === "string") return "openai";
4533
+ if (m.role === "system" || m.role === "developer") return "openai";
4534
+ }
4535
+ return "openai";
4536
+ }
4537
+ function anthropicBlocks(m) {
4538
+ return typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content ?? [];
4539
+ }
4540
+ var assistantBase = () => ({
4541
+ api: "anthropic",
4542
+ provider: "anthropic",
4543
+ model: "wire",
4544
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
4545
+ stopReason: "stop",
4546
+ timestamp: Date.now()
4547
+ });
4548
+ function synthesizeStream(payload, format) {
4549
+ const messages = payload.messages ?? [];
4550
+ const stream = [];
4551
+ const back = [];
4552
+ const push = (msg, wi, kind) => {
4553
+ msg[AI] = stream.length;
4554
+ stream.push(msg);
4555
+ back.push({ wi, kind });
4556
+ };
4557
+ if (format === "anthropic") {
4558
+ const toolNames = /* @__PURE__ */ new Map();
4559
+ for (const raw of messages) {
4560
+ if (raw === null || typeof raw !== "object") continue;
4561
+ const m = raw;
4562
+ if (m.role !== "assistant") continue;
4563
+ for (const b of anthropicBlocks(m)) if (b.type === "tool_use" && b.id) toolNames.set(b.id, b.name ?? "");
4564
+ }
4565
+ messages.forEach((raw, wi) => {
4566
+ if (raw === null || typeof raw !== "object") return;
4567
+ const m = raw;
4568
+ const blocks = anthropicBlocks(m);
4569
+ if (m.role === "user") {
4570
+ let texts = [];
4571
+ for (const b of blocks) {
4572
+ if (b.type === "text" && typeof b.text === "string") texts.push(b.text);
4573
+ else if (b.type === "tool_result") {
4574
+ if (texts.length > 0) {
4575
+ push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4576
+ texts = [];
4577
+ }
4578
+ const trText = typeof b.content === "string" ? b.content : Array.isArray(b.content) ? b.content.map((c) => c.text ?? "").join("\n") : "";
4579
+ push({
4580
+ role: "toolResult",
4581
+ content: [{ type: "text", text: trText }],
4582
+ toolName: toolNames.get(b.tool_use_id ?? "") ?? "",
4583
+ toolCallId: b.tool_use_id ?? "",
4584
+ isError: b.is_error === true,
4585
+ timestamp: Date.now()
4586
+ }, wi, "toolResult");
4587
+ }
4588
+ }
4589
+ if (texts.length > 0) push({ role: "user", content: texts.map((t2) => ({ type: "text", text: t2 })), timestamp: Date.now() }, wi, "text");
4590
+ return;
4591
+ }
4592
+ if (m.role === "assistant") {
4593
+ const content = [];
4594
+ for (const b of blocks) {
4595
+ if (b.type === "text" && typeof b.text === "string" && b.text.length > 0) content.push({ type: "text", text: b.text });
4596
+ else if (b.type === "tool_use") content.push({ type: "toolCall", id: b.id, name: b.name, arguments: b.input ?? {} });
4597
+ }
4598
+ if (content.length > 0) push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4599
+ return;
4600
+ }
4601
+ const t = blocks.map((b) => typeof b.text === "string" ? b.text : "").join("\n");
4602
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4603
+ });
4604
+ return { stream, back, format };
4605
+ }
4606
+ messages.forEach((raw, wi) => {
4607
+ if (raw === null || typeof raw !== "object") return;
4608
+ const m = raw;
4609
+ const textOf = () => {
4610
+ const c = m.content;
4611
+ if (typeof c === "string") return c;
4612
+ if (Array.isArray(c)) return c.map((p) => p.type === "text" ? p.text ?? "" : "").join("\n");
4613
+ return "";
4614
+ };
4615
+ if (m.role === "system" || m.role === "developer") {
4616
+ const t2 = textOf();
4617
+ if (t2) push({ role: "user", content: [{ type: "text", text: t2 }], timestamp: Date.now() }, wi, "text");
4618
+ return;
4619
+ }
4620
+ if (m.role === "tool") {
4621
+ push({
4622
+ role: "toolResult",
4623
+ content: [{ type: "text", text: textOf() }],
4624
+ toolName: "",
4625
+ toolCallId: m.tool_call_id ?? "",
4626
+ isError: false,
4627
+ timestamp: Date.now()
4628
+ }, wi, "toolResult");
4629
+ return;
4630
+ }
4631
+ if (m.role === "assistant") {
4632
+ const calls = m.tool_calls ?? [];
4633
+ if (calls.length > 0) {
4634
+ const content = [];
4635
+ const t3 = textOf();
4636
+ if (t3) content.push({ type: "text", text: t3 });
4637
+ for (const c of calls) {
4638
+ let args = {};
4639
+ try {
4640
+ args = c.function?.arguments ? JSON.parse(c.function.arguments) : {};
4641
+ } catch {
4642
+ args = { raw: c.function?.arguments ?? "" };
4643
+ }
4644
+ content.push({ type: "toolCall", id: c.id, name: c.function?.name ?? "", arguments: args });
4645
+ }
4646
+ push({ role: "assistant", ...assistantBase(), content }, wi, "toolCall");
4647
+ return;
4648
+ }
4649
+ const t2 = textOf();
4650
+ if (t2) push({ role: "assistant", ...assistantBase(), content: [{ type: "text", text: t2 }] }, wi, "text");
4651
+ return;
4652
+ }
4653
+ const t = textOf();
4654
+ if (t) push({ role: "user", content: [{ type: "text", text: t }], timestamp: Date.now() }, wi, "text");
4655
+ });
4656
+ return { stream, back, format };
4657
+ }
4658
+ function rebuildWirePayload(rebuilt, payload, synth) {
4659
+ const messages = (payload.messages ?? []).slice();
4660
+ const out = [];
4661
+ const agentIndexOf = (m) => m[AI];
4662
+ for (const agent of rebuilt) {
4663
+ const ai = agentIndexOf(agent);
4664
+ if (ai === void 0 || ai >= synth.back.length) {
4665
+ const text2 = extractText(agent.content);
4666
+ const role = agent.role === "assistant" ? "assistant" : "user";
4667
+ if (synth.format === "anthropic") {
4668
+ out.push({ role, content: [{ type: "text", text: text2 }] });
4669
+ } else {
4670
+ out.push({ role, content: text2 });
4671
+ }
4672
+ continue;
4673
+ }
4674
+ const { wi, kind } = synth.back[ai];
4675
+ const src = messages[wi];
4676
+ if (!src) {
4677
+ out.push(agent);
4678
+ continue;
4679
+ }
4680
+ if (synth.format === "anthropic") {
4681
+ const srcMsg2 = src;
4682
+ const blocks = anthropicBlocks(srcMsg2);
4683
+ if (kind === "toolResult") {
4684
+ const block2 = blocks.find((b) => b.type === "tool_result" && b.tool_use_id === agent.toolCallId);
4685
+ const text3 = extractText(agent.content);
4686
+ if (block2) {
4687
+ out.push({ role: "user", content: [{ ...block2, content: [{ type: "text", text: text3 }] }] });
4688
+ continue;
4689
+ }
4690
+ }
4691
+ if (kind === "toolCall") {
4692
+ const callIds = new Set(
4693
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4694
+ );
4695
+ const agentText = extractText(agent.content);
4696
+ const outBlocks = [];
4697
+ let textSeen = false;
4698
+ for (const b of blocks) {
4699
+ if (b.type === "tool_use") {
4700
+ if (callIds.has(b.id ?? "")) outBlocks.push(b);
4701
+ continue;
4702
+ }
4703
+ if (b.type === "text") {
4704
+ if (!textSeen && typeof b.text === "string") {
4705
+ outBlocks.push(b);
4706
+ textSeen = true;
4707
+ }
4708
+ continue;
4709
+ }
4710
+ outBlocks.push(b);
4711
+ }
4712
+ if (agentText && !textSeen) outBlocks.unshift({ type: "text", text: agentText });
4713
+ out.push({ role: "assistant", content: outBlocks });
4714
+ continue;
4715
+ }
4716
+ const text2 = extractText(agent.content);
4717
+ const block = blocks.find((b) => b.type === "text");
4718
+ if (block) out.push({ role: srcMsg2.role, content: [{ ...block, text: text2 }] });
4719
+ else out.push({ role: srcMsg2.role, content: [{ type: "text", text: text2 }] });
4720
+ continue;
4721
+ }
4722
+ const srcMsg = src;
4723
+ const text = extractText(agent.content);
4724
+ if (kind === "toolResult") {
4725
+ out.push({ role: "tool", tool_call_id: agent.toolCallId ?? srcMsg.tool_call_id, content: text });
4726
+ continue;
4727
+ }
4728
+ if (kind === "toolCall") {
4729
+ const callIds = new Set(
4730
+ (agent.content ?? []).filter((b) => b.type === "toolCall" && b.id).map((b) => b.id)
4731
+ );
4732
+ const surviving = (srcMsg.tool_calls ?? []).filter((c) => callIds.has(c.id));
4733
+ const entry = { role: "assistant", content: text };
4734
+ if (surviving.length > 0) entry.tool_calls = surviving;
4735
+ out.push(entry);
4736
+ continue;
4737
+ }
4738
+ out.push({ role: srcMsg.role === "assistant" ? "assistant" : srcMsg.role, content: text });
4739
+ }
4740
+ return { ...payload, messages: out };
4741
+ }
4742
+
4479
4743
  // src/auto-compress.ts
4480
4744
  import { readFileSync } from "fs";
4481
4745
  import { join as join3 } from "path";
@@ -4570,7 +4834,7 @@ async function summarizeMessages(ctx, messages, prompts, configuredModel, opts)
4570
4834
  User instructions for this compaction: ${custom}`;
4571
4835
  const userText = `ENTIRE conversation to compress (${slice.length} messages, ~${tokens} tokens). Compress it:
4572
4836
 
4573
- ` + formatSlice(slice, createInitialState());
4837
+ ` + formatSlice(slice, opts?.messageRefs ? { ...createInitialState(), messageRefs: opts.messageRefs } : createInitialState());
4574
4838
  const response = await run(
4575
4839
  model,
4576
4840
  { systemPrompt: [instructions], messages: [{ role: "user", content: [{ type: "text", text: userText }], timestamp: Date.now() }] },
@@ -4638,6 +4902,8 @@ WHEN NOT TO COMPRESS
4638
4902
  - Content the current task step is actively reading or reasoning about.
4639
4903
  - Important user messages \u2014 preserve their exact intent, constraints, and acceptance criteria. If a message in the range must stay verbatim, exclude it from the compress range instead of compressing it.
4640
4904
  - Protected tool outputs \u2014 hard-excluded from compression ranges, survive intact in visible context.
4905
+ - Nothing left worth compressing \u2014 if no compressible range can meet the minimum size, do NOT call compress at all. Compression is maintenance, never the task goal; when there is nothing to compress, do the task.
4906
+ - A rejected compress call \u2014 never retry a rejected range unchanged. Re-check acp_status first; after 3 rejections, stop compressing and continue the task.
4641
4907
 
4642
4908
  ${prompts.howToCompressRules}
4643
4909
 
@@ -4784,7 +5050,7 @@ var PACKAGE_NAME = "billion-context-omp";
4784
5050
  var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
4785
5051
  var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
4786
5052
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
4787
- var THROTTLE_FILE = join4(homeDir(), CONFIG_DIR_NAME3, ".billion-context-omp-update-check");
5053
+ var throttleFile = () => join4(homeDir(), CONFIG_DIR_NAME3, ".billion-context-omp-update-check");
4788
5054
  var updateInFlight = false;
4789
5055
  function parseVersion(v) {
4790
5056
  return v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
@@ -4800,7 +5066,7 @@ function isNewer(latest, current) {
4800
5066
  }
4801
5067
  async function readLastCheck() {
4802
5068
  try {
4803
- const data = await readFile(THROTTLE_FILE, "utf-8");
5069
+ const data = await readFile(throttleFile(), "utf-8");
4804
5070
  return parseInt(data.trim(), 10) || 0;
4805
5071
  } catch {
4806
5072
  return 0;
@@ -4808,8 +5074,8 @@ async function readLastCheck() {
4808
5074
  }
4809
5075
  async function writeLastCheck(timestamp) {
4810
5076
  try {
4811
- await mkdir2(dirname3(THROTTLE_FILE), { recursive: true });
4812
- await writeFile2(THROTTLE_FILE, String(timestamp), "utf-8");
5077
+ await mkdir2(dirname3(throttleFile()), { recursive: true });
5078
+ await writeFile2(throttleFile(), String(timestamp), "utf-8");
4813
5079
  } catch {
4814
5080
  }
4815
5081
  }
@@ -4891,12 +5157,12 @@ async function checkForUpdate(autoUpdate, notify) {
4891
5157
  const now = Date.now();
4892
5158
  const lastCheck = await readLastCheck();
4893
5159
  if (now - lastCheck < CHECK_INTERVAL_MS) return;
4894
- await writeLastCheck(now);
4895
5160
  const runtimeVersion = await getRuntimeVersion();
4896
5161
  const res = await fetch(REGISTRY_URL, {
4897
5162
  signal: AbortSignal.timeout(5e3),
4898
5163
  headers: { Accept: "application/json" }
4899
5164
  });
5165
+ await writeLastCheck(now);
4900
5166
  if (!res.ok) {
4901
5167
  logWarn("update", { event: "check-http", status: res.status });
4902
5168
  return;
@@ -4904,7 +5170,7 @@ async function checkForUpdate(autoUpdate, notify) {
4904
5170
  const data = await res.json();
4905
5171
  const latest = data.version;
4906
5172
  if (!latest) return;
4907
- const current = runtimeVersion ?? "0.1.8";
5173
+ const current = runtimeVersion ?? "0.2.0";
4908
5174
  const hasUpdate = isNewer(latest, current);
4909
5175
  debug.event("update-check", {
4910
5176
  current,
@@ -5119,6 +5385,7 @@ var KNOWN = /* @__PURE__ */ new Set([
5119
5385
  "debug",
5120
5386
  "autoUpdate",
5121
5387
  "modelContextLimit",
5388
+ "transformMode",
5122
5389
  "toolBashDefaultTimeout",
5123
5390
  "toolOutputMaxBytes",
5124
5391
  "delegate",
@@ -5162,6 +5429,7 @@ function createAcpExtension(adapter = {}) {
5162
5429
  wireSessionLifecycle(pi, runtime);
5163
5430
  wireContextTransform(pi, runtime);
5164
5431
  wireSystemPrompt(pi, runtime);
5432
+ wireProviderTransform(pi, runtime);
5165
5433
  wireProviderDebug(pi);
5166
5434
  wireToolGuardrails(pi, runtime);
5167
5435
  pi.registerTool(makeCompressTool(runtime));
@@ -5181,11 +5449,13 @@ function wireCompactionDisable(pi, runtime) {
5181
5449
  const prep = event.preparation;
5182
5450
  const toSummarize = [...prep.messagesToSummarize ?? [], ...prep.turnPrefixMessages ?? []];
5183
5451
  if (toSummarize.length === 0) return void 0;
5452
+ const slot = await runtime.stateFor(ctx);
5184
5453
  ctx.ui?.notify?.(`ACP: compacting ${toSummarize.length} messages\u2026`, "info");
5185
5454
  const result = await summarizeMessages(ctx, toSummarize, runtime.prompts, runtime.adapter.compress?.compressModel, {
5186
5455
  previousSummary: prep.previousSummary,
5187
5456
  customInstructions: event.customInstructions,
5188
- signal: event.signal
5457
+ signal: event.signal,
5458
+ messageRefs: slot.state.messageRefs
5189
5459
  });
5190
5460
  if (!result) {
5191
5461
  ctx.ui?.notify?.("ACP: compression fell back to Pi native compaction", "warning");
@@ -5216,7 +5486,7 @@ function wireCompactionDisable(pi, runtime) {
5216
5486
  function wireSessionLifecycle(pi, runtime) {
5217
5487
  pi.on("session_start", async (_event, ctx) => {
5218
5488
  const sid = ctx.sessionManager.getSessionId();
5219
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.8" : null });
5489
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.0" : null });
5220
5490
  try {
5221
5491
  const user = await loadUserConfig(ctx.cwd);
5222
5492
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
@@ -5243,107 +5513,168 @@ function wireSessionLifecycle(pi, runtime) {
5243
5513
  closeLogStream();
5244
5514
  });
5245
5515
  }
5246
- function wireContextTransform(pi, runtime) {
5247
- pi.on("context", async (event, ctx) => {
5248
- const sid = ctx.sessionManager.getSessionId();
5249
- const release = await runtime.acquireLock(sid);
5250
- try {
5251
- const input = event.messages ?? [];
5252
- if (input.length === 0) {
5253
- debug.event("empty-stream-bypass", { sid });
5254
- return void 0;
5255
- }
5256
- debug.event("context-in-raw", { sid, msgs: input.length });
5257
- const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
5258
- const config = runtime.configFor(ctx);
5259
- const coveredIds = collectCoveredMessageIds(state);
5260
- const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
5261
- const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5262
- const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
5263
- const tokenCount = sentTokens;
5264
- debug.event("context-in", {
5265
- sid,
5266
- eventMsgs: event.messages?.length ?? 0,
5267
- streamLen,
5268
- coreMsgs: coreMessages.length,
5269
- tokenCount,
5270
- sessionTokens,
5271
- limit: config.modelContextLimit,
5272
- blocksBefore: state.blocks.length,
5273
- activeBefore: state.blocks.filter((b) => b.active).length
5274
- });
5275
- const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
5276
- runtime.commitFoldState(ctx, turn.state);
5277
- logInfo("turn", {
5278
- sid,
5279
- inMsgs: coreMessages.length,
5280
- outMsgs: turn.messages.length,
5281
- tokens: tokenCount,
5282
- sessionTokens,
5283
- pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
5284
- limit: config.modelContextLimit,
5285
- nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
5286
- nudgeReason: turn.nudge?.reason ?? null,
5287
- blocks: turn.state.blocks.length,
5288
- activeBlocks: turn.state.blocks.filter((b) => b.active).length
5289
- });
5290
- debug.event("processTurn", {
5291
- outMsgs: turn.messages.length,
5292
- summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5293
- prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5294
- nudgeShouldInject: turn.nudge?.shouldInject ?? false,
5295
- nudgeReason: turn.nudge?.reason ?? null,
5296
- nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
5297
- nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
5298
- nudgeTier: turn.nudge?.tier ?? null,
5299
- nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
5300
- nudgeProtectedCount: turn.nudge?.protectedRanges?.length ?? 0,
5301
- nothingToCompress: turn.nudge?.reason?.includes("nothing to compress") ?? false,
5302
- blocksAfter: turn.state.blocks.length,
5303
- activeAfter: turn.state.blocks.filter((b) => b.active).length
5304
- });
5305
- const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
5306
- debug.event("core-out", {
5307
- sid,
5308
- coreOutMsgs: turn.messages.length,
5309
- originalByIdSize: originalById.size,
5310
- rebuiltMsgs: rebuilt.length
5311
- });
5312
- const debugOn2 = debug.enabled;
5313
- if (turn.nudge?.shouldInject) {
5516
+ async function transformStream(ctx, runtime, input, mode) {
5517
+ const sid = ctx.sessionManager.getSessionId();
5518
+ const release = await runtime.acquireLock(sid);
5519
+ try {
5520
+ if (input.length === 0) {
5521
+ debug.event("empty-stream-bypass", { sid });
5522
+ return void 0;
5523
+ }
5524
+ debug.event("context-in-raw", { sid, msgs: input.length, mode });
5525
+ const { state, coreMessages, originalById, streamLen } = runtime.foldStream(ctx, input);
5526
+ const preTurnNudgeBaseline = state.nudge.lastPerMessageNudgeTokens;
5527
+ const preTurnNudgeShownTokens = state.nudge.lastNudgeShownTokens;
5528
+ const preTurnNudgeShownByTier = state.nudge.lastShownByTier;
5529
+ const config = runtime.configFor(ctx);
5530
+ const coveredIds = collectCoveredMessageIds(state);
5531
+ const systemPromptTokens = estimateTextTokens2(getSystemPromptText(ctx) ?? "");
5532
+ const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5533
+ const sessionTokens = ctx.getContextUsage?.()?.tokens ?? null;
5534
+ const tokenCount = sentTokens;
5535
+ debug.event("context-in", {
5536
+ sid,
5537
+ mode,
5538
+ streamLen,
5539
+ coreMsgs: coreMessages.length,
5540
+ tokenCount,
5541
+ sessionTokens,
5542
+ limit: config.modelContextLimit,
5543
+ blocksBefore: state.blocks.length,
5544
+ activeBefore: state.blocks.filter((b) => b.active).length
5545
+ });
5546
+ const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount });
5547
+ runtime.commitFoldState(ctx, turn.state);
5548
+ logInfo("turn", {
5549
+ sid,
5550
+ inMsgs: coreMessages.length,
5551
+ outMsgs: turn.messages.length,
5552
+ tokens: tokenCount,
5553
+ sessionTokens,
5554
+ pct: config.modelContextLimit > 0 ? Math.round(tokenCount / config.modelContextLimit * 100) : null,
5555
+ limit: config.modelContextLimit,
5556
+ nudge: turn.nudge?.shouldInject ? turn.nudge.breakdown?.emergencyOverride === 1 ? "emergency" : "active" : "idle",
5557
+ nudgeReason: turn.nudge?.reason ?? null,
5558
+ blocks: turn.state.blocks.length,
5559
+ activeBlocks: turn.state.blocks.filter((b) => b.active).length
5560
+ });
5561
+ debug.event("processTurn", {
5562
+ outMsgs: turn.messages.length,
5563
+ summaryMsgs: turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5564
+ prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
5565
+ nudgeShouldInject: turn.nudge?.shouldInject ?? false,
5566
+ nudgeReason: turn.nudge?.reason ?? null,
5567
+ nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
5568
+ nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
5569
+ nudgeTier: turn.nudge?.tier ?? null,
5570
+ nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
5571
+ nudgeProtectedCount: turn.nudge?.protectedRanges?.length ?? 0,
5572
+ nothingToCompress: turn.nudge?.reason?.includes("nothing to compress") ?? false,
5573
+ blocksAfter: turn.state.blocks.length,
5574
+ activeAfter: turn.state.blocks.filter((b) => b.active).length
5575
+ });
5576
+ const rebuilt = coreOutToAgentMessages(turn.messages, originalById);
5577
+ debug.event("core-out", {
5578
+ sid,
5579
+ coreOutMsgs: turn.messages.length,
5580
+ originalByIdSize: originalById.size,
5581
+ rebuiltMsgs: rebuilt.length
5582
+ });
5583
+ const debugOn2 = debug.enabled;
5584
+ let nudgeInjected = false;
5585
+ if (turn.nudge?.shouldInject) {
5586
+ const lastUser = [...input].reverse().find((m) => m.role === "user");
5587
+ const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
5588
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
5589
+ if (isFeedbackView) {
5590
+ debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
5591
+ } else {
5314
5592
  const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
5315
- {
5316
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
5317
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
5318
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
5319
- const example = top ? `
5593
+ const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
5594
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
5595
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
5596
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
5597
+ if (suppressed) {
5598
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
5599
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
5600
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5601
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
5602
+ } else {
5603
+ nudgeInjected = true;
5604
+ {
5605
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
5606
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
5607
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
5608
+ const example = top ? `
5320
5609
 
5321
5610
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
5322
- rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
5323
- if (emergency) {
5324
- logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
5325
- }
5326
- if (debugOn2 && ctx.hasUI) {
5327
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
5611
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
5612
+ if (emergency) {
5613
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
5614
+ }
5615
+ if (debugOn2 && ctx.hasUI) {
5616
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
5328
5617
  ${rendered.text}${example}`);
5618
+ }
5619
+ debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
5329
5620
  }
5330
- debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
5331
5621
  }
5332
5622
  }
5333
- dumpContextMessages(rebuilt, {
5334
- sid,
5335
- injected: turn.nudge?.shouldInject ?? false,
5336
- emergency: turn.nudge?.breakdown?.emergencyOverride === 1
5337
- });
5338
- await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
5339
- if (ctx.hasUI) ctx.ui.notify(msg);
5340
- });
5341
- return { messages: rebuilt };
5623
+ }
5624
+ dumpContextMessages(rebuilt, {
5625
+ sid,
5626
+ injected: nudgeInjected,
5627
+ emergency: turn.nudge?.breakdown?.emergencyOverride === 1
5628
+ });
5629
+ await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
5630
+ if (ctx.hasUI) ctx.ui.notify(msg);
5631
+ });
5632
+ return { rebuilt, nudgeInjected };
5633
+ } catch (e) {
5634
+ logThrow("context", e, { sid, phase: "transform", mode });
5635
+ throw e;
5636
+ } finally {
5637
+ release();
5638
+ }
5639
+ }
5640
+ function wireContextTransform(pi, runtime) {
5641
+ pi.on("context", async (event, ctx) => {
5642
+ if ((runtime.adapter.transformMode ?? "context") === "provider") {
5643
+ debug.event("context-observer-skip", { sid: ctx.sessionManager.getSessionId(), msgs: event.messages?.length ?? 0 });
5644
+ return void 0;
5645
+ }
5646
+ const result = await transformStream(ctx, runtime, event.messages ?? [], "context");
5647
+ if (!result) return void 0;
5648
+ return { messages: result.rebuilt };
5649
+ });
5650
+ }
5651
+ function wireProviderTransform(pi, runtime) {
5652
+ pi.on("before_provider_request", async (event, ctx) => {
5653
+ if ((runtime.adapter.transformMode ?? "context") !== "provider") return void 0;
5654
+ const payload = event.payload;
5655
+ if (payload === null || typeof payload !== "object" || !Array.isArray(payload.messages)) return void 0;
5656
+ const sid = ctx.sessionManager?.getSessionId?.() ?? "";
5657
+ const fmt2 = detectWireFormat(payload);
5658
+ if (fmt2 === "unknown") {
5659
+ debug.event("provider-transform-unknown-format", { sid });
5660
+ return void 0;
5661
+ }
5662
+ try {
5663
+ const synth = synthesizeStream(payload, fmt2);
5664
+ if (synth.stream.length === 0) return void 0;
5665
+ const result = await transformStream(ctx, runtime, synth.stream, "provider");
5666
+ if (!result) return void 0;
5667
+ const wireOut = rebuildWirePayload(result.rebuilt, payload, synth);
5668
+ const outMsgs = wireOut.messages?.length ?? 0;
5669
+ const inMsgs = payload.messages?.length ?? 0;
5670
+ if (outMsgs !== inMsgs || wireOut !== payload) {
5671
+ logInfo("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudge: result.nudgeInjected ? "injected" : "idle" });
5672
+ }
5673
+ debug.event("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudgeInjected: result.nudgeInjected });
5674
+ return wireOut === payload ? void 0 : wireOut;
5342
5675
  } catch (e) {
5343
- logThrow("context", e, { sid, phase: "transform" });
5344
- throw e;
5345
- } finally {
5346
- release();
5676
+ logThrow("provider-transform", e, { sid, fmt: fmt2 });
5677
+ return void 0;
5347
5678
  }
5348
5679
  });
5349
5680
  }