billion-context-omp 0.2.9 → 0.3.1

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
@@ -574,7 +574,7 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
574
574
  );
575
575
  return { messages: updated, truncatedCount, savedTokens };
576
576
  }
577
- var KEEP_LAST_ORPHANED = 0;
577
+ var KEEP_LAST_ORPHANED = 2;
578
578
  function rangeKey(startRef, endRef) {
579
579
  return `${startRef}::${endRef}`;
580
580
  }
@@ -3005,6 +3005,27 @@ function resolveTransformMode(adapter, model, hostVersion = VERSION) {
3005
3005
  if (api === "openai-completions" && hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) return "provider";
3006
3006
  return "context";
3007
3007
  }
3008
+ function providerDeliveryWarning(adapter, model, hostVersion = VERSION) {
3009
+ if (adapter.transformMode !== "provider") return void 0;
3010
+ const api = model?.api;
3011
+ const dropWarning = (target) => ({
3012
+ key: `drop:${target}`,
3013
+ reason: `host < 17.3.8 drops the before_provider_request replacement on ${target} (fixed upstream pi-ai 17.3.8, can1357/oh-my-pi#8717)`,
3014
+ message: `\u26A0 billion-context-omp: transformMode "provider" is set, but this host (pi-ai < 17.3.8) discards the rewritten payload on ${target} \u2014 compression is NOT applied. Upgrade the host (omp update) or remove the transformMode override.`
3015
+ });
3016
+ if (api === "openai-completions" && !hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) {
3017
+ return dropWarning(api);
3018
+ }
3019
+ if (api === "amazon-bedrock" || api === "cursor") {
3020
+ if (!hostVersionAtLeast(OPENAI_COMPLETIONS_VIABLE_FROM, hostVersion)) return dropWarning(api);
3021
+ return {
3022
+ key: `nocodec:${api}`,
3023
+ reason: `${api} honors the replacement from 17.3.8 but its wire body has no codec path yet (issue #83)`,
3024
+ message: `\u26A0 billion-context-omp: transformMode "provider" is set, but the ${api} wire body has no codec path yet (#83) \u2014 compression is NOT applied. Remove the transformMode override to use context mode.`
3025
+ };
3026
+ }
3027
+ return void 0;
3028
+ }
3008
3029
 
3009
3030
  // node_modules/acp-kernel/dist/wire/index.js
3010
3031
  import { createHash } from "crypto";
@@ -3817,6 +3838,13 @@ function unrepresentableOpenaiMessage(message) {
3817
3838
  if (typeof role !== "string" || !OPENAI_CODEC_ROLES.has(role)) {
3818
3839
  return `openai role ${JSON.stringify(role) ?? "missing"}`;
3819
3840
  }
3841
+ for (const field of ["function_call", "audio", "annotations"]) {
3842
+ if (message[field] !== void 0) {
3843
+ return `openai ${field} field is dropped by the rebuild`;
3844
+ }
3845
+ }
3846
+ const refusal = message.refusal;
3847
+ if (refusal !== null && refusal !== void 0) return "openai refusal content is dropped by the rebuild";
3820
3848
  const content = message.content;
3821
3849
  if (content == null || typeof content === "string") return null;
3822
3850
  if (!Array.isArray(content)) return "content neither string nor part array";
@@ -3833,6 +3861,39 @@ function unrepresentableOpenaiMessage(message) {
3833
3861
  }
3834
3862
  return null;
3835
3863
  }
3864
+ function restoreOpenaiWireFidelity(originalMessages, rebuilt) {
3865
+ const detailsByCall = /* @__PURE__ */ new Map();
3866
+ for (const message of originalMessages) {
3867
+ if (message === null || typeof message !== "object") continue;
3868
+ const calls = message.tool_calls;
3869
+ const details = message.reasoning_details;
3870
+ if (!Array.isArray(calls) || !Array.isArray(details) || details.length === 0) continue;
3871
+ for (const call of calls) {
3872
+ const id = call?.id;
3873
+ if (typeof id === "string" && !detailsByCall.has(id)) detailsByCall.set(id, details);
3874
+ }
3875
+ }
3876
+ return rebuilt.map((message) => {
3877
+ if (message === null || typeof message !== "object") return message;
3878
+ const m = message;
3879
+ if (m.role !== "assistant") return message;
3880
+ const calls = Array.isArray(m.tool_calls) ? m.tool_calls : [];
3881
+ const attached = [];
3882
+ for (const call of calls) {
3883
+ const id = call?.id;
3884
+ if (typeof id !== "string") continue;
3885
+ const d = detailsByCall.get(id);
3886
+ if (d) attached.push(...d);
3887
+ }
3888
+ const hasReasoningField = m.reasoning_content !== void 0 || m.reasoning !== void 0 || m.reasoning_text !== void 0;
3889
+ const emptyContent = m.content === null && (calls.length > 0 || hasReasoningField);
3890
+ if (attached.length === 0 && !emptyContent) return message;
3891
+ const out = { ...m };
3892
+ if (emptyContent) out.content = "";
3893
+ if (attached.length > 0) out.reasoning_details = attached;
3894
+ return out;
3895
+ });
3896
+ }
3836
3897
  var renderRefsAll = createRenderRefsNode("all");
3837
3898
  function applyWireTagContract(msgs, state, scope) {
3838
3899
  const stripAssistantTags = (m) => m.contentType === "text" && m.role === "assistant" ? { ...m, text: stripRefTag(m.text ?? "") } : m;
@@ -3991,22 +4052,23 @@ function viewToCoreStream(view, systemText) {
3991
4052
  if (text) messages.push({ role: "user", content: text });
3992
4053
  } else if (m.role === "assistant") {
3993
4054
  const blocks = Array.isArray(m.content) ? m.content : [];
3994
- const calls = blocks.filter(
3995
- (b) => b !== null && typeof b === "object" && b.type === "toolCall"
3996
- );
4055
+ const typed = blocks;
4056
+ const calls = typed.filter((b) => b !== null && typeof b === "object" && b.type === "toolCall");
4057
+ const reasoning = typed.filter((b) => b !== null && typeof b === "object" && b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim().length > 0).map((b) => b.thinking).join("\n");
3997
4058
  const text = extractViewText(m.content);
3998
4059
  if (calls.length > 0) {
3999
4060
  messages.push({
4000
4061
  role: "assistant",
4001
4062
  content: text,
4063
+ ...reasoning ? { reasoning_content: reasoning } : {},
4002
4064
  tool_calls: calls.map((c) => ({
4003
4065
  id: c.id,
4004
4066
  type: "function",
4005
4067
  function: { name: c.name ?? "", arguments: JSON.stringify(c.arguments ?? {}) }
4006
4068
  }))
4007
4069
  });
4008
- } else if (text) {
4009
- messages.push({ role: "assistant", content: text });
4070
+ } else if (text || reasoning) {
4071
+ messages.push({ role: "assistant", content: text, ...reasoning ? { reasoning_content: reasoning } : {} });
4010
4072
  }
4011
4073
  } else if (m.role === "toolResult") {
4012
4074
  messages.push({ role: "tool", tool_call_id: m.toolCallId ?? "", content: extractViewText(m.content) });
@@ -4027,20 +4089,27 @@ function viewToAnthropicCore(view) {
4027
4089
  if (text) messages.push({ role: "user", content: [{ type: "text", text }] });
4028
4090
  } else if (m.role === "assistant") {
4029
4091
  const blocks = Array.isArray(m.content) ? m.content : [];
4030
- const calls = blocks.filter(
4031
- (b) => b !== null && typeof b === "object" && b.type === "toolCall"
4032
- );
4033
- const text = extractViewText(m.content);
4092
+ const typed = blocks;
4034
4093
  const content = [];
4035
- if (text) content.push({ type: "text", text });
4036
- for (const c of calls) {
4037
- let input = {};
4038
- try {
4039
- input = c.arguments && typeof c.arguments === "object" ? c.arguments : JSON.parse(JSON.stringify(c.arguments ?? {}));
4040
- } catch {
4041
- input = {};
4094
+ for (const b of typed) {
4095
+ if (b === null || typeof b !== "object") continue;
4096
+ if (b.type === "thinking" && typeof b.thinking === "string" && b.thinking.trim().length > 0) {
4097
+ content.push({
4098
+ type: "thinking",
4099
+ thinking: b.thinking,
4100
+ ...typeof b.thinkingSignature === "string" && b.thinkingSignature ? { signature: b.thinkingSignature } : {}
4101
+ });
4102
+ } else if (b.type === "text" && typeof b.text === "string" && stripRefTag(b.text).trim().length > 0) {
4103
+ content.push({ type: "text", text: stripRefTag(b.text) });
4104
+ } else if (b.type === "toolCall") {
4105
+ let input = {};
4106
+ try {
4107
+ input = b.arguments && typeof b.arguments === "object" ? b.arguments : JSON.parse(JSON.stringify(b.arguments ?? {}));
4108
+ } catch {
4109
+ input = {};
4110
+ }
4111
+ content.push({ type: "tool_use", id: b.id, name: b.name ?? "", input });
4042
4112
  }
4043
- content.push({ type: "tool_use", id: c.id, name: c.name ?? "", input });
4044
4113
  }
4045
4114
  if (content.length > 0) messages.push({ role: "assistant", content });
4046
4115
  } else if (m.role === "toolResult") {
@@ -4353,6 +4422,9 @@ ${acp}`);
4353
4422
  slot.rejectStreak = ok ? 0 : slot.rejectStreak + 1;
4354
4423
  return slot.rejectStreak;
4355
4424
  }
4425
+ function rejectStreakFor(ctx) {
4426
+ return slotForMode(ctx, sidOf(ctx)).rejectStreak;
4427
+ }
4356
4428
  return {
4357
4429
  core,
4358
4430
  get adapter() {
@@ -4375,6 +4447,7 @@ ${acp}`);
4375
4447
  commitFoldState,
4376
4448
  recordRebuiltOutput,
4377
4449
  noteCompressOutcome,
4450
+ rejectStreakFor,
4378
4451
  forgetSession,
4379
4452
  primeFold,
4380
4453
  acquireLock
@@ -5562,7 +5635,7 @@ async function statusReport(runtime, ctx) {
5562
5635
  const coveredIds = collectCoveredMessageIds(state);
5563
5636
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
5564
5637
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
5565
- const versionStr = "0.2.9" ? `billion-context-omp@${"0.2.9"}` : void 0;
5638
+ const versionStr = "0.3.1" ? `billion-context-omp@${"0.3.1"}` : void 0;
5566
5639
  return buildStatusPanel({
5567
5640
  version: versionStr,
5568
5641
  tokenCount: sessionTokens,
@@ -5727,7 +5800,7 @@ function stampAndDetect(selfPath, version, now = Date.now()) {
5727
5800
  }
5728
5801
 
5729
5802
  // src/update.ts
5730
- import { readFile, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
5803
+ import { readFile, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
5731
5804
  import { join as join4, dirname as dirname3 } from "path";
5732
5805
  import { fileURLToPath } from "url";
5733
5806
  import { execFile } from "child_process";
@@ -5793,43 +5866,112 @@ async function findExtensionDir() {
5793
5866
  dir = parent;
5794
5867
  }
5795
5868
  }
5796
- async function autoInstallLatest(latest) {
5869
+ var runNpmImpl = (args, cwd) => new Promise((resolve2) => {
5870
+ execFile(
5871
+ "npm",
5872
+ args,
5873
+ { cwd, timeout: 6e4, shell: process.platform === "win32", maxBuffer: 4 * 1024 * 1024 },
5874
+ (err, stdout, stderr) => {
5875
+ if (err) logWarn("update", { event: "install-exec-failed", error: err.message, stderr: String(stderr).slice(0, 300) });
5876
+ resolve2({ code: err ? 1 : 0, stdout: String(stdout), stderr: String(stderr) });
5877
+ }
5878
+ );
5879
+ });
5880
+ var runNpm = runNpmImpl;
5881
+ var runNodeImpl = (args) => new Promise((resolve2) => {
5882
+ execFile(
5883
+ process.execPath,
5884
+ args,
5885
+ { timeout: 15e3, maxBuffer: 4 * 1024 * 1024, shell: false },
5886
+ (err, stdout, stderr) => {
5887
+ resolve2({ code: err ? 1 : 0, stdout: String(stdout), stderr: String(stderr) });
5888
+ }
5889
+ );
5890
+ });
5891
+ var runNode = runNodeImpl;
5892
+ function declaredEntries(pkg) {
5893
+ const entries = /* @__PURE__ */ new Set();
5894
+ for (const ext of pkg.omp?.extensions ?? []) {
5895
+ if (typeof ext === "string") entries.add(ext);
5896
+ }
5897
+ const dot = pkg.exports?.["."];
5898
+ if (typeof dot === "string") entries.add(dot);
5899
+ else if (dot && typeof dot.import === "string") entries.add(dot.import);
5900
+ if (typeof pkg.main === "string") entries.add(pkg.main);
5901
+ return [...entries];
5902
+ }
5903
+ async function verifyInstall(npmDir, latest) {
5904
+ const extDir = join4(npmDir, "node_modules", PACKAGE_NAME);
5905
+ const pkg = await readPackageJson(join4(extDir, "package.json"));
5906
+ if (!pkg) return { ok: false, reason: "package-json-missing" };
5907
+ if (pkg.version !== latest) return { ok: false, reason: `version-mismatch:${pkg.version ?? "none"}` };
5908
+ const entries = declaredEntries(pkg);
5909
+ if (entries.length === 0) return { ok: false, reason: "no-entry-declared" };
5910
+ for (const rel of entries) {
5911
+ try {
5912
+ await access(join4(extDir, rel));
5913
+ } catch {
5914
+ return { ok: false, reason: `entry-missing:${rel}` };
5915
+ }
5916
+ }
5917
+ const smokeEntry = pkg.omp?.extensions?.[0] ?? entries[0];
5918
+ if (!smokeEntry) return { ok: false, reason: "no-entry-declared" };
5919
+ const smoke = `const{pathToFileURL}=require("node:url");import(pathToFileURL(process.argv[1]).href).then(()=>{},(e)=>{console.error(e&&e.stack||e);process.exit(1)});`;
5920
+ const r = await runNode(["-e", smoke, join4(extDir, smokeEntry)]);
5921
+ if (r.code !== 0) return { ok: false, reason: `entry-import-failed:${r.stderr.slice(0, 500)}` };
5922
+ return { ok: true };
5923
+ }
5924
+ var installArgs = (version) => [
5925
+ "install",
5926
+ `${PACKAGE_NAME}@${version}`,
5927
+ // --no-save: the auto-updater must never mutate the host's package.json
5928
+ // or lockfile.
5929
+ "--no-save",
5930
+ "--silent",
5931
+ "--no-audit",
5932
+ "--no-fund"
5933
+ ];
5934
+ async function autoInstallLatest(latest, npmDirOverride) {
5797
5935
  if (!SEMVER_RE.test(latest)) {
5798
5936
  logWarn("update", { event: "install-abort", reason: "semver", latest });
5799
- return false;
5937
+ return "failed";
5800
5938
  }
5801
5939
  const extDir = await findExtensionDir();
5802
5940
  if (!extDir) {
5803
5941
  logWarn("update", { event: "install-abort", reason: "extdir-not-found", moduleUrl: import.meta.url });
5804
- return false;
5942
+ return "failed";
5805
5943
  }
5806
- const npmDir = findNpmRoot(extDir);
5944
+ const npmDir = npmDirOverride ?? findNpmRoot(extDir);
5807
5945
  if (!npmDir) {
5808
5946
  logWarn("update", { event: "install-abort", reason: "npmroot-not-found", extDir });
5809
- return false;
5947
+ return "failed";
5810
5948
  }
5949
+ const prevVersion = (await readPackageJson(join4(npmDir, "node_modules", PACKAGE_NAME, "package.json")))?.version ?? "0.3.1";
5811
5950
  try {
5812
5951
  const keepAlive = setInterval(() => {
5813
5952
  }, 500);
5814
5953
  try {
5815
- const code = await new Promise((resolve2) => {
5816
- execFile(
5817
- "npm",
5818
- ["install", `${PACKAGE_NAME}@${latest}`, "--silent", "--no-audit", "--no-fund"],
5819
- { cwd: npmDir, timeout: 6e4, shell: process.platform === "win32" },
5820
- (err, _stdout, stderr) => {
5821
- if (err) logWarn("update", { event: "install-exec-failed", error: err.message, stderr: String(stderr).slice(0, 300) });
5822
- resolve2(err ? 1 : 0);
5823
- }
5824
- );
5825
- });
5826
- return code === 0;
5954
+ const res = await runNpm(installArgs(latest), npmDir);
5955
+ if (res.code !== 0) {
5956
+ logWarn("update", { event: "auto-install-failed", latest, stderr: res.stderr.slice(0, 2e3) });
5957
+ return "failed";
5958
+ }
5959
+ const verify = await verifyInstall(npmDir, latest);
5960
+ if (!verify.ok) {
5961
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.3.1";
5962
+ logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
5963
+ const rb = await runNpm(installArgs(rollbackTo), npmDir);
5964
+ logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
5965
+ return "rolled-back";
5966
+ }
5967
+ logInfo("update", { event: "auto-installed", from: prevVersion, to: latest });
5968
+ return "ok";
5827
5969
  } finally {
5828
5970
  clearInterval(keepAlive);
5829
5971
  }
5830
5972
  } catch (e) {
5831
5973
  logWarn("update", { event: "install-throw", error: e instanceof Error ? e.message : String(e) });
5832
- return false;
5974
+ return "failed";
5833
5975
  }
5834
5976
  }
5835
5977
  async function checkForUpdate(autoUpdate, notify) {
@@ -5856,7 +5998,7 @@ async function checkForUpdate(autoUpdate, notify) {
5856
5998
  const data = await res.json();
5857
5999
  const latest = data.version;
5858
6000
  if (!latest) return;
5859
- const current = runtimeVersion ?? "0.2.9";
6001
+ const current = runtimeVersion ?? "0.3.1";
5860
6002
  const hasUpdate = isNewer(latest, current);
5861
6003
  debug.event("update-check", {
5862
6004
  current,
@@ -5865,16 +6007,16 @@ async function checkForUpdate(autoUpdate, notify) {
5865
6007
  });
5866
6008
  logInfo("update", { event: "check", current, latest, hasUpdate });
5867
6009
  if (hasUpdate) {
5868
- const installed = await autoInstallLatest(latest);
5869
- if (installed && notify) {
6010
+ const outcome = await autoInstallLatest(latest);
6011
+ if (!notify) return;
6012
+ if (outcome === "ok") {
6013
+ notify(`\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart omp to finish.\x1B[0m`);
6014
+ } else if (outcome === "rolled-back") {
5870
6015
  notify(
5871
- `\x1B[32m\u2714 ACP auto-updated ${current} \u2192 ${latest}. Restart omp to finish.\x1B[0m`
5872
- );
5873
- logInfo("update", { event: "auto-installed", from: current, to: latest });
5874
- } else if (!installed && notify) {
5875
- notify(
5876
- `${PACKAGE_NAME} ${latest} available (you have ${current}). Run: omp install ${PACKAGE_NAME}@latest`
6016
+ `\x1B[33m\u26A0 ${PACKAGE_NAME} ${latest} failed verification and was rolled back. Keeping ${current}. A later release will auto-update.\x1B[0m`
5877
6017
  );
6018
+ } else {
6019
+ notify(`${PACKAGE_NAME} ${latest} available (you have ${current}). Run: omp install ${PACKAGE_NAME}@latest`);
5878
6020
  }
5879
6021
  }
5880
6022
  } catch (e) {
@@ -6112,10 +6254,11 @@ function applyUserConfig(adapter, user) {
6112
6254
  function createAcpExtension(adapter = {}) {
6113
6255
  return (pi) => {
6114
6256
  const runtime = createRuntime(adapter);
6257
+ const warnDelivery = makeDeliveryWarner();
6115
6258
  wireSessionLifecycle(pi, runtime);
6116
- wireContextTransform(pi, runtime);
6259
+ wireContextTransform(pi, runtime, warnDelivery);
6117
6260
  wireSystemPrompt(pi, runtime);
6118
- wireProviderTransform(pi, runtime);
6261
+ wireProviderTransform(pi, runtime, warnDelivery);
6119
6262
  wireProviderDebug(pi);
6120
6263
  wireToolGuardrails(pi, runtime);
6121
6264
  pi.registerTool(makeCompressTool(runtime));
@@ -6129,26 +6272,14 @@ function createAcpExtension(adapter = {}) {
6129
6272
  }
6130
6273
  var index_default = createAcpExtension();
6131
6274
  function wireSessionLifecycle(pi, runtime) {
6132
- pi.on("session_start", async (_event, ctx) => {
6275
+ const prepareAndPrime = async (ctx, phase) => {
6133
6276
  const sid = ctx.sessionManager.getSessionId();
6134
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.9" : null });
6135
- const selfPath = import.meta.url;
6136
- const conflict = stampAndDetect(selfPath, true ? "0.2.9" : null);
6137
- if (conflict) {
6138
- logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
6139
- try {
6140
- if (ctx.hasUI) {
6141
- ctx.ui.notify(`\u26A0 billion-context-omp loaded TWICE (also from ${conflict.path}). Two instances corrupt compression state \u2014 remove one (check 'omp plugin list' vs config.yml extensions).`);
6142
- }
6143
- } catch {
6144
- }
6145
- }
6146
6277
  try {
6147
6278
  const user = await loadUserConfig(ctx.cwd);
6148
6279
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
6149
6280
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
6150
6281
  } catch (e) {
6151
- logThrow("config", e, { sid, phase: "session_start" });
6282
+ logThrow("config", e, { sid, phase });
6152
6283
  }
6153
6284
  try {
6154
6285
  runtime.setPrompts(resolvePrompts(runtime.adapter.prompts, { acknowledgeRisk: runtime.adapter.acknowledgePromptsRisk === true }));
@@ -6157,10 +6288,35 @@ function wireSessionLifecycle(pi, runtime) {
6157
6288
  runtime.setPrompts(defaultPrompts);
6158
6289
  }
6159
6290
  runtime.primeFold(ctx);
6291
+ };
6292
+ pi.on("session_start", async (_event, ctx) => {
6293
+ const sid = ctx.sessionManager.getSessionId();
6294
+ const modelInfo = ctx.model;
6295
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.3.1" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
6296
+ const selfPath = import.meta.url;
6297
+ const conflict = stampAndDetect(selfPath, true ? "0.3.1" : null);
6298
+ if (conflict) {
6299
+ logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
6300
+ try {
6301
+ if (ctx.hasUI) {
6302
+ ctx.ui.notify(`\u26A0 billion-context-omp loaded TWICE (also from ${conflict.path}). Two instances corrupt compression state \u2014 remove one (check 'omp plugin list' vs config.yml extensions).`);
6303
+ }
6304
+ } catch {
6305
+ }
6306
+ }
6307
+ await prepareAndPrime(ctx, "session_start");
6160
6308
  void checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
6161
6309
  if (ctx.hasUI) ctx.ui.notify(msg);
6162
6310
  }).catch((e) => logThrow("update", e, { sid, phase: "session_start" }));
6163
6311
  });
6312
+ pi.on("session_switch", async (event, ctx) => {
6313
+ logInfo("session", { event: "switch", sid: ctx.sessionManager.getSessionId(), reason: event.reason, previous: event.previousSessionFile ?? null });
6314
+ await prepareAndPrime(ctx, "session_switch");
6315
+ });
6316
+ pi.on("session_branch", async (_event, ctx) => {
6317
+ logInfo("session", { event: "branch", sid: ctx.sessionManager.getSessionId() });
6318
+ await prepareAndPrime(ctx, "session_branch");
6319
+ });
6164
6320
  pi.on("session_shutdown", (_event, ctx) => {
6165
6321
  try {
6166
6322
  runtime.forgetSession(ctx.sessionManager.getSessionId());
@@ -6204,6 +6360,7 @@ async function transformStream(ctx, runtime, input, mode) {
6204
6360
  runtime.commitFoldState(ctx, turn.state);
6205
6361
  logInfo("turn", {
6206
6362
  sid,
6363
+ model: ctx.model?.id ?? null,
6207
6364
  inMsgs: coreMessages.length,
6208
6365
  outMsgs: turn.messages.length,
6209
6366
  tokens: tokenCount,
@@ -6242,38 +6399,48 @@ async function transformStream(ctx, runtime, input, mode) {
6242
6399
  if (turn.nudge?.shouldInject) {
6243
6400
  const lastUser = [...input].reverse().find((m) => m.role === "user");
6244
6401
  const tailText = lastUser ? JSON.stringify(lastUser.content ?? "") : "";
6245
- const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
6402
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached") || tailText.includes("compress calls were rejected in a row");
6246
6403
  if (isFeedbackView) {
6247
6404
  debug.event("nudge-feedback-skip", { sid: ctx.sessionManager.getSessionId(), msgs: input.length });
6248
6405
  } else {
6249
6406
  const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
6407
+ const rejectStreak = runtime.rejectStreakFor(ctx);
6250
6408
  const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
6251
- const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6252
- const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6253
- const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6254
- if (suppressed) {
6255
- turn.state.nudge.lastNudgeShownTokens = prevShown;
6409
+ if (rejectStreak >= LOOP_GUARD_STOP) {
6410
+ turn.state.nudge.lastNudgeShownTokens = epochReset ? 0 : preTurnNudgeShownTokens;
6256
6411
  turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6257
- logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6258
- debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6259
- } else {
6260
6412
  nudgeInjected = true;
6261
- {
6262
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6263
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6264
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6265
- const example = top ? `
6413
+ rebuilt.push(holdMessage(rejectStreak));
6414
+ logInfo("nudge", { sid, event: "hold-injected", streak: rejectStreak, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6415
+ debug.event("nudge-hold", { sid, streak: rejectStreak });
6416
+ } else {
6417
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6418
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6419
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6420
+ if (suppressed) {
6421
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
6422
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6423
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6424
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6425
+ } else {
6426
+ nudgeInjected = true;
6427
+ {
6428
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6429
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6430
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6431
+ const example = top ? `
6266
6432
 
6267
6433
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
6268
- rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
6269
- if (emergency) {
6270
- logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6271
- }
6272
- if (debugOn2 && ctx.hasUI) {
6273
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6434
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example));
6435
+ if (emergency) {
6436
+ logWarn("nudge", { sid: ctx.sessionManager.getSessionId(), event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6437
+ }
6438
+ if (debugOn2 && ctx.hasUI) {
6439
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6274
6440
  ${rendered.text}${example}`);
6441
+ }
6442
+ debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6275
6443
  }
6276
- debug.event("nudge-injected", { sid: ctx.sessionManager.getSessionId(), voice: rendered.voice, channels: ["context", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6277
6444
  }
6278
6445
  }
6279
6446
  }
@@ -6298,10 +6465,13 @@ ${rendered.text}${example}`);
6298
6465
  }).catch((e) => logThrow("update", e, { sid, phase: "context" }));
6299
6466
  return result;
6300
6467
  }
6301
- function wireContextTransform(pi, runtime) {
6468
+ function wireContextTransform(pi, runtime, warnDelivery) {
6302
6469
  pi.on("context", async (event, ctx) => {
6303
6470
  if (resolveTransformMode(runtime.adapter, ctx.model) === "provider") {
6304
- debug.event("context-observer-skip", { sid: ctx.sessionManager.getSessionId(), msgs: event.messages?.length ?? 0 });
6471
+ const sid = ctx.sessionManager.getSessionId();
6472
+ debug.event("context-observer-skip", { sid, msgs: event.messages?.length ?? 0 });
6473
+ const warning = providerDeliveryWarning(runtime.adapter, ctx.model);
6474
+ if (warning) warnDelivery(ctx, sid, warning);
6305
6475
  return void 0;
6306
6476
  }
6307
6477
  const result = await transformStream(ctx, runtime, event.messages ?? [], "context");
@@ -6309,7 +6479,20 @@ function wireContextTransform(pi, runtime) {
6309
6479
  return { messages: result.rebuilt };
6310
6480
  });
6311
6481
  }
6312
- function wireProviderTransform(pi, runtime) {
6482
+ function makeDeliveryWarner() {
6483
+ const warned = /* @__PURE__ */ new Set();
6484
+ return (ctx, sid, warning) => {
6485
+ const dedup = `${sid}:${warning.key}`;
6486
+ if (warned.has(dedup)) return;
6487
+ warned.add(dedup);
6488
+ logWarn("provider-transform", { sid, event: "undelivered", reason: warning.reason });
6489
+ try {
6490
+ if (ctx.hasUI) ctx.ui.notify(warning.message);
6491
+ } catch {
6492
+ }
6493
+ };
6494
+ }
6495
+ function wireProviderTransform(pi, runtime, warnDelivery) {
6313
6496
  pi.on("before_provider_request", async (event, ctx) => {
6314
6497
  if (resolveTransformMode(runtime.adapter, ctx.model) !== "provider") return void 0;
6315
6498
  const payload = event.payload;
@@ -6318,6 +6501,13 @@ function wireProviderTransform(pi, runtime) {
6318
6501
  const fmt2 = detectProviderWireFormat(payload);
6319
6502
  if (fmt2 === null) {
6320
6503
  debug.event("provider-transform-unknown-format", { sid });
6504
+ if (runtime.adapter.transformMode === "provider") {
6505
+ warnDelivery(ctx, sid, {
6506
+ key: "unknown-wire-format",
6507
+ reason: "explicit provider mode but the wire body has no codec path (unknown format) \u2014 payload passes through",
6508
+ message: '\u26A0 billion-context-omp: transformMode "provider" is set, but this wire body has no codec path \u2014 compression is NOT applied here. Remove the override to use context mode.'
6509
+ });
6510
+ }
6321
6511
  return void 0;
6322
6512
  }
6323
6513
  const representable = payloadRepresentable(payload, fmt2);
@@ -6331,13 +6521,15 @@ function wireProviderTransform(pi, runtime) {
6331
6521
  if (msgs.length === 0) return void 0;
6332
6522
  const result = await transformStreamCore(ctx, runtime, msgs, fmt2);
6333
6523
  if (!result) return void 0;
6334
- const outMsgs = coreToPayloadMessages(result.coreOut, fmt2, cacheControls).length;
6335
- const inMsgs = payload.messages?.length ?? 0;
6524
+ const inMessages = payload.messages ?? [];
6525
+ const rebuilt = fmt2 === "openai" ? restoreOpenaiWireFidelity(inMessages, coreToPayloadMessages(result.coreOut, fmt2, cacheControls)) : coreToPayloadMessages(result.coreOut, fmt2, cacheControls);
6526
+ const outMsgs = rebuilt.length;
6527
+ const inMsgs = inMessages.length;
6336
6528
  if (outMsgs !== inMsgs) {
6337
6529
  logInfo("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudge: result.nudgeInjected ? "injected" : "idle" });
6338
6530
  }
6339
6531
  debug.event("provider-transform", { sid, fmt: fmt2, inMsgs, outMsgs, nudgeInjected: result.nudgeInjected });
6340
- return { ...payload, messages: coreToPayloadMessages(result.coreOut, fmt2, cacheControls) };
6532
+ return { ...payload, messages: rebuilt };
6341
6533
  } catch (e) {
6342
6534
  logThrow("provider-transform", e, { sid, fmt: fmt2 });
6343
6535
  return void 0;
@@ -6415,38 +6607,48 @@ async function transformStreamCore(ctx, runtime, wireMsgs, fmt2) {
6415
6607
  if (turn.nudge?.shouldInject) {
6416
6608
  const lastUser = [...wireMsgs].reverse().find((m) => m.role === "user");
6417
6609
  const tailText = lastUser ? lastUser.text ?? "" : "";
6418
- const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached");
6610
+ const isFeedbackView = tailText.includes("efficiency nudge to compress early") || tailText.includes("Context limit reached") || tailText.includes("compress calls were rejected in a row");
6419
6611
  if (isFeedbackView) {
6420
6612
  debug.event("nudge-feedback-skip", { sid, msgs: wireMsgs.length });
6421
6613
  } else {
6422
6614
  const emergency = turn.nudge.breakdown?.emergencyOverride === 1;
6423
6615
  const epochReset = turn.state.nudge.lastPerMessageNudgeTokens !== preTurnNudgeBaseline;
6424
- const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6425
- const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6426
- const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6427
- if (suppressed) {
6428
- turn.state.nudge.lastNudgeShownTokens = prevShown;
6616
+ const rejectStreak = runtime.rejectStreakFor(ctx);
6617
+ if (rejectStreak >= LOOP_GUARD_STOP) {
6618
+ turn.state.nudge.lastNudgeShownTokens = epochReset ? 0 : preTurnNudgeShownTokens;
6429
6619
  turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6430
- logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6431
- debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6432
- } else {
6433
6620
  nudgeInjected = true;
6434
- turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6435
- const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6436
- const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6437
- const example = top ? `
6621
+ coreOut.push({ id: `acp_hold_${Date.now()}`, role: "user", contentType: "text", text: holdText(rejectStreak) });
6622
+ logInfo("nudge", { sid, event: "hold-injected", streak: rejectStreak, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6623
+ debug.event("nudge-hold", { sid, streak: rejectStreak });
6624
+ } else {
6625
+ const prevShown = epochReset ? 0 : preTurnNudgeShownTokens;
6626
+ const cadenceFloor = turn.nudge.breakdown?.growthFloor ?? 0;
6627
+ const suppressed = !emergency && prevShown > 0 && tokenCount - prevShown < cadenceFloor;
6628
+ if (suppressed) {
6629
+ turn.state.nudge.lastNudgeShownTokens = prevShown;
6630
+ turn.state.nudge.lastShownByTier = preTurnNudgeShownByTier;
6631
+ logInfo("nudge", { sid, event: "cadence-suppressed", growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6632
+ debug.event("nudge-suppressed", { sid, growth: tokenCount - prevShown, floor: cadenceFloor, pct: Math.round(turn.nudge.contextUsage * 100), reason: turn.nudge.reason });
6633
+ } else {
6634
+ nudgeInjected = true;
6635
+ turn.nudge.compressibleRanges = viableRanges(turn.nudge.compressibleRanges);
6636
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
6637
+ const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
6638
+ const example = top ? `
6438
6639
 
6439
6640
  Example: compress({ content: [{ startId: "${top.startRef}", endId: "${top.endRef}", summary: "..." }] })` : "";
6440
- if (emergency) {
6441
- logWarn("nudge", { sid, event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6442
- }
6443
- const debugOn2 = debug.enabled;
6444
- if (debugOn2 && ctx.hasUI) {
6445
- ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6641
+ if (emergency) {
6642
+ logWarn("nudge", { sid, event: "emergency-inject", pct: Math.round(turn.nudge.contextUsage * 100), voice: rendered.voice, compressible: turn.nudge.compressibleRanges.length });
6643
+ }
6644
+ const debugOn2 = debug.enabled;
6645
+ if (debugOn2 && ctx.hasUI) {
6646
+ ctx.ui.notify(`[ACP nudge \u2192 context]${emergency ? " [EMERGENCY]" : ""}
6446
6647
  ${rendered.text}${example}`);
6648
+ }
6649
+ debug.event("nudge-injected", { sid, voice: rendered.voice, channels: ["wire", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6650
+ coreOut.push({ id: `acp_nudge_${Date.now()}`, role: "user", contentType: "text", text: nudgeText(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example) });
6447
6651
  }
6448
- debug.event("nudge-injected", { sid, voice: rendered.voice, channels: ["wire", debugOn2 ? "terminal" : null].filter(Boolean), emergency, text: rendered.text + example });
6449
- coreOut.push({ id: `acp_nudge_${Date.now()}`, role: "user", contentType: "text", text: nudgeText(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts, example) });
6450
6652
  }
6451
6653
  }
6452
6654
  }
@@ -6522,6 +6724,16 @@ function nudgeMessage(nudge, blocks, prompts, example) {
6522
6724
  timestamp: Date.now()
6523
6725
  };
6524
6726
  }
6727
+ function holdText(streak) {
6728
+ return `[ACP hold] Your last ${streak} compress calls were rejected in a row. Do NOT call compress again now and do NOT retry the same range \u2014 the compress reminder is suspended until context actually changes. Continue the actual task. If context still needs relief, run acp_status first and target ONLY a range that meets the minimum size.`;
6729
+ }
6730
+ function holdMessage(streak) {
6731
+ return {
6732
+ role: "user",
6733
+ content: [{ type: "text", text: holdText(streak) }],
6734
+ timestamp: Date.now()
6735
+ };
6736
+ }
6525
6737
  export {
6526
6738
  createAcpExtension,
6527
6739
  index_default as default