billion-context-dsh 0.2.16 → 0.2.18

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.en.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [English](./README.en.md) | [中文](./README.md)
4
4
 
5
5
  > **⚠️ Beta notice — not for production use**
6
- > This project (**v0.2.16**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
6
+ > This project (**v0.2.18**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
7
7
 
8
8
  <p align="center">
9
9
  <strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
@@ -68,15 +68,24 @@ The command installs the package and automatically layers this package's bundle
68
68
 
69
69
  Restart `dsh` afterwards (bundle layers are composed at startup), open a new session, and verify: ask the model to call `acp_status`, or run `/acp status`. Shipped presets (standard / code / cordis) keep their realm-local `compaction-basic` fallback (automatic pressure compression still runs there; the ACP tools and nudge coexist); minimal and presets without a compaction realm use this engine directly.
70
70
 
71
- > **DSH version compatibility.** The package declares the peer range
72
- > `^0.1.0-rc.6 || ^0.1.1-rc.1` for `@deepseek-ai/dsh-compaction`, covering both the
73
- > `0.1.0-rc.x` and `0.1.1-rc.x` release lines (including the current DSH release;
74
- > the seam's `src/` is unchanged from `0.1.0-rc.6` to `0.1.1-rc.2`, so the public
75
- > API is identical). The range is two `||` clauses **on purpose**: npm
71
+ > **DSH version compatibility.** The package declares all four runtime seam
72
+ > packages (`dsh-compaction` / `dsh-session` / `dsh-llm` / `dsh-tools`) as peer
73
+ > dependencies, sharing the range
74
+ > `^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.4`,
75
+ > covering the `0.1.0-rc.x` and `0.1.1-rc.x` release lines (including the current
76
+ > DSH release; the seam's `src/` is unchanged from `0.1.0-rc.6` to `0.1.1-rc.2`, so the public
77
+ > API is identical) and the `0.1.2-alpha.x` line (which removed the
78
+ > `Session.events` getter in favour of `snapshotEvents()` / `eventAt()`; this
79
+ > engine feature-detects both shapes, so one build runs on either seam). The
80
+ > range is multiple `||` clauses **on purpose**: npm
76
81
  > (node-semver) only lets a prerelease version satisfy a range that carries a
77
82
  > comparator on the SAME `[major, minor, patch]` tuple as the candidate, so a lone
78
- > `^0.1.0-rc.6` can never match `0.1.1-rc.x` (issue #68) older releases fail to
79
- > install on DSH 0.1.1-rc.x; upgrade to a release containing this fix.
83
+ > `^0.1.0-rc.6` can never match `0.1.1-rc.x` (issue #68) or `0.1.2-alpha.x`
84
+ > older releases fail to install on DSH 0.1.1-rc.x / 0.1.2-alpha.x; upgrade to a
85
+ > release containing this fix. Declaring all four seam packages as peers (not
86
+ > just `dsh-compaction`) ensures that, even under pnpm's
87
+ > hoisted/linked layout, installations resolve them to the **host's own** copy
88
+ > rather than a stale nested copy inconsistent with the host.
80
89
 
81
90
  **Path B: plain `npm install` (package only — a composition row is required).**
82
91
 
@@ -86,6 +95,14 @@ npm install billion-context-dsh
86
95
 
87
96
  This only installs the package into your project/global store; it does **not** touch any profile — add a composition row as shown below or the engine never mounts.
88
97
 
98
+ **Install from the git source (`github:` spec — the form the plugin store shows).** The prebuilt `dist/` artifacts are committed to this repository, so a git-source install also works out of the box — **no build step needed**, and pnpm 11's default build-script blocking (`allowBuilds`) never applies to this package:
99
+
100
+ ```bash
101
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.18
102
+ ```
103
+
104
+ Prefer a `#<tag>` ref to get artifacts identical to that npm release; without a ref you get the latest default-branch build. Only building the repo yourself (`npm run build`) requires approving build scripts. Background and trade-offs: [docs/git-source-install-design.md](docs/git-source-install-design.md) (issue #92).
105
+
89
106
  ## Scope & customization
90
107
 
91
108
  Two audiences: ① Path B (plain npm install) users, who must write a composition row; ② Path A users who want custom `config` (the bundle already ships sane defaults — override them with a SAME-ID row).
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [中文](./README.md) | [English](./README.en.md)
4
4
 
5
5
  > **⚠️ 测试版声明——请勿用于生产环境**
6
- > 本项目(**v0.2.16**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
6
+ > 本项目(**v0.2.18**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
7
7
 
8
8
  <p align="center">
9
9
  <strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
@@ -71,14 +71,20 @@ dsh plugin --profile web add billion-context-dsh
71
71
 
72
72
  装完**重启 `dsh`**(bundle 层在启动时组合),新开会话即可用——让模型调用 `acp_status` 或执行 `/acp status` 自证。shipped 预设(standard / code / cordis)内部的 realm 级 `compaction-basic` 自动压缩兜底仍然保留(这些模式里"自动摘要"照旧,ACP 工具与 nudge 并存);minimal 等不带 compaction realm 的预设直接使用本引擎。
73
73
 
74
- > **与 DSH 版本的兼容性。** 包声明 peer 依赖 `@deepseek-ai/dsh-compaction`
75
- > `^0.1.0-rc.6 || ^0.1.1-rc.1`,同时覆盖 `0.1.0-rc.x` `0.1.1-rc.x` 两条 rc 线
76
- > (含当前最新 DSH release;从 `0.1.0-rc.6` `0.1.1-rc.2`,seam `src/` 源码
77
- > 零改动,公开 API 完全一致)。范围写成两个并集子句是**有意为之**:npm
78
- > (node-semver)的预发布匹配规则要求 range 里存在与候选版本**相同
74
+ > **与 DSH 版本的兼容性。** 包把四个运行期 seam 包(`dsh-compaction` /
75
+ > `dsh-session` / `dsh-llm` / `dsh-tools`)都声明为 peer 依赖,共享同一个
76
+ > 范围 `^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.4`,同时覆盖 `0.1.0-rc.x`、
77
+ > `0.1.1-rc.x` 两条 rc 线与 `0.1.2-alpha.x` 线(从 `0.1.0-rc.6` 到 `0.1.1-rc.2`,
78
+ > seam 的 `src/` 源码零改动,公开 API 完全一致;`0.1.2-alpha` 移除了
79
+ > `Session.events` getter,改为 `snapshotEvents()` / `eventAt()`,本引擎对两种
80
+ > 形态做了特性探测,单一构建两端通用)。范围写成多个并集子句是**有意为之**:
81
+ > npm (node-semver)的预发布匹配规则要求 range 里存在与候选版本**相同
79
82
  > `[major, minor, patch]` 元组**的比较器,单一 `^0.1.0-rc.6` 永远匹配不了
80
- > `0.1.1-rc.x`(issue #68)——因此旧发布的包在 DSH 0.1.1-rc.x 上装不上,
81
- > 升级到含本次修复的新版本即可。
83
+ > `0.1.1-rc.x`(issue #68),也匹配不了 `0.1.2-alpha.x`——因此旧发布的包在
84
+ > DSH 0.1.1-rc.x / 0.1.2-alpha.x 上装不上,升级到含本次修复的新版本即可。
85
+ > 把这四个 seam 包一并声明为 peer(而不只是 `dsh-compaction`),是为了让
86
+ > 安装在 pnpm 的集成/封存布局下仍能把它们解析到**宿主自己的副本**,而不是
87
+ > 某个与宿主不一致的陈旧嵌套副本。
82
88
 
83
89
  **方式二:纯 `npm install`(只装包,需要手写组合行)。**
84
90
 
@@ -88,6 +94,14 @@ npm install billion-context-dsh
88
94
 
89
95
  这只把包装进你的项目/全局,**不会**触碰任何 profile——请按下方「两种生效范围与自定义」手写组合行,引擎才会挂载。
90
96
 
97
+ **git 源安装(`github:` 规格,插件商店展示的形态)。** 预构建产物 `dist/` 已提交到仓库,从 git 源安装同样开箱即用——**无需任何构建步骤**,pnpm 11 默认拦截构建脚本(`allowBuilds`)的机制对这个包不构成障碍:
98
+
99
+ ```bash
100
+ dsh plugin --profile web add github:Tyan66666/billion-context-dsh#v0.2.18
101
+ ```
102
+
103
+ 建议带 `#<tag>` 安装,拿到与对应 npm 版本完全一致的产物;不带 ref 则装默认分支的最新构建。只有 clone 仓库自行从源码构建(`npm run build`)才需要放行构建。背景与方案取舍见 [docs/git-source-install-design.md](docs/git-source-install-design.md)(issue #92)。
104
+
91
105
  ## 两种生效范围与自定义
92
106
 
93
107
  本节服务于两类人:① 方式二(纯 npm 安装,必须手写组合行);② 方式一用户想自定义 `config`(bundle 已有默认行为,只需用**同 id** 行覆盖)。
package/dist/index.js CHANGED
@@ -2645,6 +2645,18 @@ import {
2645
2645
  } from "@deepseek-ai/dsh-compaction";
2646
2646
  import { createAssistantMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
2647
2647
 
2648
+ // src/session-events.ts
2649
+ function sessionEventsOf(session) {
2650
+ const snapshot = session.snapshotEvents?.();
2651
+ if (snapshot !== void 0) return snapshot;
2652
+ return session.events;
2653
+ }
2654
+ function eventAtOf(session, seq) {
2655
+ const eventAt = session.eventAt;
2656
+ if (typeof eventAt === "function") return eventAt.call(session, seq);
2657
+ return session.events[seq];
2658
+ }
2659
+
2648
2660
  // src/messages.ts
2649
2661
  function extractText(content) {
2650
2662
  if (typeof content === "string") return content;
@@ -2757,10 +2769,10 @@ function eventsToCoreMessages(events, toolNames) {
2757
2769
  return out;
2758
2770
  }
2759
2771
  function surfaceEventsOf(session) {
2760
- return session.surface.nodes.map((seq) => session.events[seq]).filter((event) => event !== void 0);
2772
+ return session.surface.nodes.map((seq) => eventAtOf(session, seq)).filter((event) => event !== void 0);
2761
2773
  }
2762
2774
  function allLogMessages(session) {
2763
- return eventsToCoreMessages(session.events);
2775
+ return eventsToCoreMessages(sessionEventsOf(session));
2764
2776
  }
2765
2777
  function extractEventText(event) {
2766
2778
  switch (event.type) {
@@ -2826,7 +2838,7 @@ function hostPriceEvent(event) {
2826
2838
  function shadowedHostTokens(session, seqs) {
2827
2839
  let total = 0;
2828
2840
  for (const seq of seqs) {
2829
- const event = session.events[seq];
2841
+ const event = eventAtOf(session, seq);
2830
2842
  if (event !== void 0) total += hostPriceEvent(event);
2831
2843
  }
2832
2844
  return total;
@@ -2873,7 +2885,7 @@ function assertNoActiveCompaction(events) {
2873
2885
  }
2874
2886
  }
2875
2887
  function hasPlainRef(session, seq) {
2876
- const event = session.events[seq];
2888
+ const event = eventAtOf(session, seq);
2877
2889
  if (event === void 0) return false;
2878
2890
  switch (event.type) {
2879
2891
  case "user/message":
@@ -2906,14 +2918,14 @@ var AlreadyCompressedRangeError = class extends Error {
2906
2918
  coveringBlockIds;
2907
2919
  };
2908
2920
  function recoverStaleRange(session, start, end) {
2909
- if (session.events[start] === void 0 || session.events[end] === void 0) {
2910
- const failedEdge = session.events[start] === void 0 ? start : end;
2921
+ if (eventAtOf(session, start) === void 0 || eventAtOf(session, end) === void 0) {
2922
+ const failedEdge = eventAtOf(session, start) === void 0 ? start : end;
2911
2923
  return { kind: "unresolvable", failedEdge };
2912
2924
  }
2913
2925
  const liveInside = session.surface.nodes.filter((seq) => seq >= start && seq <= end).sort((a, b) => a - b);
2914
- const plain = liveInside.filter((seq) => !isCheckpointNode(session.events[seq]));
2926
+ const plain = liveInside.filter((seq) => !isCheckpointNode(eventAtOf(session, seq)));
2915
2927
  if (plain.length === 0) {
2916
- const coveringBlockIds = rebuildBlockLedger(session.events).filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end)).map((entry) => entry.blockId);
2928
+ const coveringBlockIds = rebuildBlockLedger(sessionEventsOf(session)).filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end)).map((entry) => entry.blockId);
2917
2929
  return { kind: "already-compressed", coveringBlockIds };
2918
2930
  }
2919
2931
  return { kind: "ok", start: plain[0], end: plain[plain.length - 1] };
@@ -2996,8 +3008,8 @@ function readCompactionSummary(event) {
2996
3008
  return event.data;
2997
3009
  }
2998
3010
  function runCompactionTransaction(session, input) {
2999
- assertNoActiveCompaction(session.events);
3000
- const turn = findOpenTurn(session.events);
3011
+ assertNoActiveCompaction(sessionEventsOf(session));
3012
+ const turn = findOpenTurn(sessionEventsOf(session));
3001
3013
  const compactionId = CompactionId(randomUUID());
3002
3014
  const seqs = [];
3003
3015
  seqs.push(session.append("compaction/start", { compactionId, turn }).seq);
@@ -3111,7 +3123,7 @@ function hideSurfaceSeqs(session, seqs, provider, model, text, priceEvent = host
3111
3123
  const end = seqs[seqs.length - 1];
3112
3124
  let shadowedTokenCount = 0;
3113
3125
  for (const seq of seqs) {
3114
- const event = session.events[seq];
3126
+ const event = eventAtOf(session, seq);
3115
3127
  if (event !== void 0) shadowedTokenCount += priceEvent(event);
3116
3128
  }
3117
3129
  session.append("compaction/prune", {
@@ -3130,7 +3142,7 @@ function hideSurfaceSeqs(session, seqs, provider, model, text, priceEvent = host
3130
3142
  return;
3131
3143
  }
3132
3144
  session.append("assistant/message", {
3133
- turn: findOpenTurn(session.events) ?? 0,
3145
+ turn: findOpenTurn(sessionEventsOf(session)) ?? 0,
3134
3146
  step: 0,
3135
3147
  message: createAssistantMessage({ content: [], source: { provider, model } })
3136
3148
  }, {
@@ -3140,7 +3152,8 @@ function hideSurfaceSeqs(session, seqs, provider, model, text, priceEvent = host
3140
3152
  }
3141
3153
  function hideCompressToolPair(session, callId, resultSeq) {
3142
3154
  let callSeq = null;
3143
- for (const event of session.events) {
3155
+ const events = sessionEventsOf(session);
3156
+ for (const event of events) {
3144
3157
  if (event.type !== "assistant/message") continue;
3145
3158
  if (toolCallIdsOfEvent(event).includes(callId)) {
3146
3159
  callSeq = event.seq;
@@ -3148,11 +3161,11 @@ function hideCompressToolPair(session, callId, resultSeq) {
3148
3161
  }
3149
3162
  }
3150
3163
  if (callSeq === null) return false;
3151
- const callNodeIds = toolCallIdsOfEvent(session.events[callSeq]);
3164
+ const callNodeIds = toolCallIdsOfEvent(events[callSeq]);
3152
3165
  if (callNodeIds.length !== 1 || callNodeIds[0] !== callId) return false;
3153
3166
  let resolvedResultSeq = resultSeq ?? null;
3154
3167
  if (resolvedResultSeq === null) {
3155
- for (const event of session.events) {
3168
+ for (const event of events) {
3156
3169
  if (event.type === "tool/result" && toolCallIdOfResultEvent(event) === callId) {
3157
3170
  resolvedResultSeq = event.seq;
3158
3171
  break;
@@ -3164,8 +3177,8 @@ function hideCompressToolPair(session, callId, resultSeq) {
3164
3177
  const startIdx = nodes.indexOf(callSeq);
3165
3178
  const endIdx = nodes.indexOf(resolvedResultSeq);
3166
3179
  if (startIdx < 0 || endIdx < 0 || endIdx - startIdx !== 1) return false;
3167
- const { provider, model } = assistantProviderModel(session.events[callSeq]);
3168
- const resultEvent = session.events[resolvedResultSeq];
3180
+ const { provider, model } = assistantProviderModel(events[callSeq]);
3181
+ const resultEvent = events[resolvedResultSeq];
3169
3182
  const resultText = resultEvent === void 0 ? "" : extractEventText(resultEvent);
3170
3183
  hideSurfaceSeqs(session, [callSeq, resolvedResultSeq], provider, model, resultText.trim().length > 0 ? resultText : void 0);
3171
3184
  return true;
@@ -3178,7 +3191,7 @@ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE_
3178
3191
  const brokenResults = /* @__PURE__ */ new Map();
3179
3192
  for (let index = 0; index < nodes.length; index += 1) {
3180
3193
  const seq = nodes[index];
3181
- const event = session.events[seq];
3194
+ const event = eventAtOf(session, seq);
3182
3195
  if (event === void 0) continue;
3183
3196
  if (event.type === "assistant/message") {
3184
3197
  const ids = toolCallIdsOfEvent(event);
@@ -3200,7 +3213,7 @@ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE_
3200
3213
  if (callNodeIds !== void 0) {
3201
3214
  adjacent = true;
3202
3215
  for (let mid = call.index + 1; mid < index; mid += 1) {
3203
- const midEvent = session.events[nodes[mid]];
3216
+ const midEvent = eventAtOf(session, nodes[mid]);
3204
3217
  if (midEvent === void 0 || midEvent.type !== "tool/result") {
3205
3218
  adjacent = false;
3206
3219
  break;
@@ -3218,7 +3231,7 @@ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE_
3218
3231
  }
3219
3232
  const brokenIdsByCallSeq = /* @__PURE__ */ new Map();
3220
3233
  for (const [resultSeq, callSeq] of brokenResults) {
3221
- const id = toolCallIdOfResultEvent(session.events[resultSeq]);
3234
+ const id = toolCallIdOfResultEvent(eventAtOf(session, resultSeq));
3222
3235
  if (id !== null) {
3223
3236
  const list = brokenIdsByCallSeq.get(callSeq) ?? [];
3224
3237
  list.push(id);
@@ -3235,7 +3248,7 @@ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE_
3235
3248
  const hidden = [...hiddenSet].sort((a, b) => a - b);
3236
3249
  let count = 0;
3237
3250
  for (const seq of hidden) {
3238
- const event = session.events[seq];
3251
+ const event = eventAtOf(session, seq);
3239
3252
  if (event === void 0) continue;
3240
3253
  const { provider, model } = assistantProviderModel(event);
3241
3254
  hideSurfaceSeqs(session, [seq], provider, model);
@@ -3246,7 +3259,7 @@ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE_
3246
3259
  function openToolCallIds(session) {
3247
3260
  const open = /* @__PURE__ */ new Set();
3248
3261
  for (const seq of session.surface.nodes) {
3249
- const event = session.events[seq];
3262
+ const event = eventAtOf(session, seq);
3250
3263
  if (event === void 0) continue;
3251
3264
  if (event.type === "assistant/message") {
3252
3265
  for (const id of toolCallIdsOfEvent(event)) open.add(id);
@@ -3275,7 +3288,7 @@ function buildCompressibleSeqRanges(session, opts = {}) {
3275
3288
  for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq);
3276
3289
  }
3277
3290
  for (let index = nodes.length - 1; index >= 0; index -= 1) {
3278
- const event = session.events[nodes[index]];
3291
+ const event = eventAtOf(session, nodes[index]);
3279
3292
  if (event?.type === "user/message" && !isCheckpointNode(event)) {
3280
3293
  protectedSeqs.add(nodes[index]);
3281
3294
  break;
@@ -3288,7 +3301,7 @@ function buildCompressibleSeqRanges(session, opts = {}) {
3288
3301
  cur = null;
3289
3302
  };
3290
3303
  for (const seq of nodes) {
3291
- const event = session.events[seq];
3304
+ const event = eventAtOf(session, seq);
3292
3305
  if (event === void 0 || protectedSeqs.has(seq) || isCheckpointNode(event)) {
3293
3306
  flush();
3294
3307
  continue;
@@ -3335,7 +3348,7 @@ function surfaceSummary(session) {
3335
3348
  return `${nodes.length} nodes, seqs ${first}..${last}`;
3336
3349
  }
3337
3350
  function blockRegistry(session) {
3338
- const ledger = rebuildBlockLedger(session.events);
3351
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
3339
3352
  const kernelIdOf = /* @__PURE__ */ new Map();
3340
3353
  const raw = [];
3341
3354
  let next = 1;
@@ -3369,7 +3382,7 @@ function blockRegistry(session) {
3369
3382
  }));
3370
3383
  }
3371
3384
  function blockRefForSummarySeq(session, seq) {
3372
- const event = session.events[seq];
3385
+ const event = eventAtOf(session, seq);
3373
3386
  if (event?.type !== "user/message") return null;
3374
3387
  const source = event.data.source;
3375
3388
  if (source?.plugin !== "compact" || source.compactionId === void 0) return null;
@@ -3399,7 +3412,7 @@ function checkpointBlockIdOf(events, seq) {
3399
3412
  return source.compactionId;
3400
3413
  }
3401
3414
  function expandShadowedSeqs(session, blockId) {
3402
- const ledger = rebuildBlockLedger(session.events);
3415
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
3403
3416
  const byId = new Map(ledger.map((entry) => [entry.blockId, entry]));
3404
3417
  const root = byId.get(blockId);
3405
3418
  if (root === void 0) return [];
@@ -3409,7 +3422,7 @@ function expandShadowedSeqs(session, blockId) {
3409
3422
  if (seen.has(entry.blockId)) return;
3410
3423
  seen.add(entry.blockId);
3411
3424
  for (const seq of entry.shadowedSeqs) {
3412
- const childId = checkpointBlockIdOf(session.events, seq);
3425
+ const childId = checkpointBlockIdOf(sessionEventsOf(session), seq);
3413
3426
  const child = childId === null ? void 0 : byId.get(childId);
3414
3427
  if (child !== void 0) visit(child);
3415
3428
  else out.push(seq);
@@ -3486,9 +3499,10 @@ var AcpStateStore = class {
3486
3499
  const existing = this.states.get(id);
3487
3500
  if (existing !== void 0) return existing;
3488
3501
  const state = createInitialState();
3489
- if (session.events.some((event) => event.type === "compaction/summary")) {
3490
- state.blocks = rebuildKernelBlocks(session.events);
3491
- state.nextBlockId = nextBlockIdAfter(session.events);
3502
+ const events = sessionEventsOf(session);
3503
+ if (events.some((event) => event.type === "compaction/summary")) {
3504
+ state.blocks = rebuildKernelBlocks(events);
3505
+ state.nextBlockId = nextBlockIdAfter(events);
3492
3506
  }
3493
3507
  this.states.set(id, state);
3494
3508
  return state;
@@ -3707,7 +3721,7 @@ function buildNudge(agent, env, lastNudgeTurn) {
3707
3721
  const nudge = turn.nudge;
3708
3722
  if (nudge === void 0 || !nudge.shouldInject) return null;
3709
3723
  const emergency = nudge.breakdown?.emergencyOverride === 1;
3710
- const turnNumber = findOpenTurn(session.events) ?? 0;
3724
+ const turnNumber = findOpenTurn(sessionEventsOf(session)) ?? 0;
3711
3725
  const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber;
3712
3726
  if (alreadyShown) return null;
3713
3727
  lastNudgeTurn.set(session.id, turnNumber);
@@ -4126,7 +4140,7 @@ var decompressParameters = {
4126
4140
  function resolveBlockId(session, arg) {
4127
4141
  const byKernelRef = blockIdOfKernelRef(session, arg);
4128
4142
  if (byKernelRef !== null) return byKernelRef;
4129
- const ledger = rebuildBlockLedger(session.events);
4143
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
4130
4144
  const byPrefix = ledger.find((entry) => entry.blockId.startsWith(arg));
4131
4145
  return byPrefix?.blockId ?? null;
4132
4146
  }
@@ -4137,14 +4151,14 @@ function handleDecompress(_env, rawArgs, exec) {
4137
4151
  if (blockId === null) {
4138
4152
  return { text: `decompress: block "${args.blockId}" not found (see acp_status for the block list)` };
4139
4153
  }
4140
- const ledger = rebuildBlockLedger(session.events);
4154
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
4141
4155
  const block = ledger.find((entry) => entry.blockId === blockId);
4142
4156
  if (block === void 0) {
4143
4157
  return { text: `decompress: block "${args.blockId}" not found (see acp_status for the block list)` };
4144
4158
  }
4145
4159
  const parts = [];
4146
4160
  for (const seq of expandShadowedSeqs(session, block.blockId)) {
4147
- const event = session.events[seq];
4161
+ const event = eventAtOf(session, seq);
4148
4162
  const text = event === void 0 ? "" : extractEventText(event);
4149
4163
  if (text.length > 0) parts.push(`[seq ${seq}] ${text}`);
4150
4164
  }
@@ -4172,7 +4186,7 @@ function roleOfEvent(event) {
4172
4186
  }
4173
4187
  }
4174
4188
  function buildSearchDocs(session) {
4175
- const ledger = rebuildBlockLedger(session.events);
4189
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
4176
4190
  const docs = [];
4177
4191
  const claimed = /* @__PURE__ */ new Set();
4178
4192
  for (const block of ledger) {
@@ -4188,7 +4202,7 @@ function buildSearchDocs(session) {
4188
4202
  for (const seq of expandShadowedSeqs(session, block.blockId)) {
4189
4203
  if (claimed.has(seq)) continue;
4190
4204
  claimed.add(seq);
4191
- const event = session.events[seq];
4205
+ const event = eventAtOf(session, seq);
4192
4206
  if (event === void 0) continue;
4193
4207
  const role = roleOfEvent(event);
4194
4208
  const text = extractEventText(event);
@@ -4367,7 +4381,7 @@ async function detectContextWindow(agent, provider, model) {
4367
4381
  // src/commands.ts
4368
4382
  async function statusText(env, agent) {
4369
4383
  const session = agent.session;
4370
- const ledger = rebuildBlockLedger(session.events);
4384
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
4371
4385
  const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
4372
4386
  const coreMessages = allLogMessages(session);
4373
4387
  const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
@@ -4435,10 +4449,10 @@ function decompressText(_env, agent, args) {
4435
4449
  if (args.length < 1) return "/acp decompress <blockId>";
4436
4450
  const session = agent.session;
4437
4451
  const blockId = blockIdOfKernelRef(session, args[0]);
4438
- const ledger = rebuildBlockLedger(session.events);
4452
+ const ledger = rebuildBlockLedger(sessionEventsOf(session));
4439
4453
  const block = blockId === null ? ledger.find((entry) => entry.blockId.startsWith(args[0])) : ledger.find((entry) => entry.blockId === blockId);
4440
4454
  if (block === void 0) return `block "${args[0]}" not found (see /acp status)`;
4441
- const parts = expandShadowedSeqs(session, block.blockId).map((seq) => extractEventText(session.events[seq])).filter((text) => text.length > 0);
4455
+ const parts = expandShadowedSeqs(session, block.blockId).map((seq) => extractEventText(eventAtOf(session, seq))).filter((text) => text.length > 0);
4442
4456
  return `Block ${block.blockId} \u2014 ${block.summary}
4443
4457
 
4444
4458
  ${parts.join("\n\n") || "(no recoverable content)"}`;