pi-pier 0.3.0 → 0.4.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.
Files changed (103) hide show
  1. package/README.md +16 -12
  2. package/package.json +3 -3
  3. package/schemas/role-manifest.schema.json +7 -1
  4. package/skills/pi-herdr-workflow/SKILL.md +4 -5
  5. package/src/ansi-text.ts +62 -11
  6. package/src/ask-dialog.ts +308 -0
  7. package/src/ask-user.ts +113 -112
  8. package/src/bootstrap.ts +10 -13
  9. package/src/compact-coordinator.ts +279 -261
  10. package/src/compact-economics-core.ts +77 -145
  11. package/src/config-catalog-core.ts +77 -197
  12. package/src/config-command.ts +271 -64
  13. package/src/cordis-services.ts +64 -0
  14. package/src/dashboard-command.ts +77 -33
  15. package/src/delegation-core.ts +115 -0
  16. package/src/efficiency-config-core.ts +286 -486
  17. package/src/efficiency-store.ts +16 -29
  18. package/src/gc-core.ts +10 -36
  19. package/src/guards.ts +6 -0
  20. package/src/herdr-client.ts +202 -233
  21. package/src/history-store.ts +38 -23
  22. package/src/index-gates.ts +118 -0
  23. package/src/index-locks.ts +2 -7
  24. package/src/index-master.ts +31 -29
  25. package/src/index-notices.ts +28 -15
  26. package/src/index-pipe.ts +31 -12
  27. package/src/index-roles.ts +306 -0
  28. package/src/index-runtime.ts +16 -6
  29. package/src/index-worker.ts +8 -18
  30. package/src/index.ts +561 -570
  31. package/src/jev-client.ts +12 -17
  32. package/src/jev-core.ts +138 -90
  33. package/src/ledger.ts +13 -21
  34. package/src/lock-core.ts +28 -56
  35. package/src/manifest-compose.ts +35 -20
  36. package/src/observation-core.ts +52 -100
  37. package/src/pane-title.ts +19 -38
  38. package/src/panel-open.ts +47 -0
  39. package/src/pi-surface.ts +34 -22
  40. package/src/pier-options.ts +53 -50
  41. package/src/pipe-channel.ts +44 -59
  42. package/src/platform-paths.ts +19 -52
  43. package/src/{core → plugins}/grid-shape.ts +8 -10
  44. package/src/{core → plugins}/heat-plan.ts +20 -22
  45. package/src/plugins/observation.ts +475 -0
  46. package/src/plugins/subagent.ts +766 -0
  47. package/src/{core → plugins}/terminal.ts +198 -139
  48. package/src/{core → plugins}/todo.ts +73 -134
  49. package/src/progress-core.ts +16 -53
  50. package/src/reconcile-core.ts +24 -33
  51. package/src/reducer-core.ts +26 -64
  52. package/src/reducer-invoker.ts +266 -239
  53. package/src/renderers.ts +108 -84
  54. package/src/role-loader.ts +33 -28
  55. package/src/role-manifest.ts +25 -31
  56. package/src/role-state.ts +119 -0
  57. package/src/roles/worker-default.json +3 -3
  58. package/src/routing-telemetry.ts +136 -0
  59. package/src/runtime-policy.ts +25 -40
  60. package/src/session-tail.ts +65 -41
  61. package/src/settle-wake-core.ts +61 -22
  62. package/src/slim-frame.ts +25 -122
  63. package/src/stale-core.ts +17 -24
  64. package/src/storage-layout.ts +14 -43
  65. package/src/subagent-core.ts +423 -120
  66. package/src/subagent-gc.ts +109 -67
  67. package/src/subagent-poller.ts +383 -24
  68. package/src/subagent-session.ts +516 -0
  69. package/src/subagent-spawn.ts +577 -52
  70. package/src/swallow.ts +7 -17
  71. package/src/terminal-core.ts +187 -134
  72. package/src/todo-core.ts +57 -109
  73. package/src/todo-read-hook.ts +46 -39
  74. package/src/todo-reminder-core.ts +26 -51
  75. package/src/todo-window.ts +11 -12
  76. package/src/todos-service.ts +15 -35
  77. package/src/tool-error.ts +6 -13
  78. package/src/tool-gate.ts +38 -21
  79. package/src/tree-view.ts +162 -0
  80. package/src/vocab.ts +52 -25
  81. package/src/worker-marker.ts +76 -0
  82. package/src/ask-multi.ts +0 -260
  83. package/src/config-guide.ts +0 -361
  84. package/src/core/observation.ts +0 -555
  85. package/src/core/subagent.ts +0 -633
  86. package/src/efficiency-config-io.ts +0 -53
  87. package/src/focus-poller.ts +0 -244
  88. package/src/git-adapter.ts +0 -113
  89. package/src/notice-buffer.ts +0 -23
  90. package/src/subagent-execute.ts +0 -328
  91. package/src/subagent-git-io.ts +0 -80
  92. package/src/subagent-launch.ts +0 -114
  93. package/src/subagent-list-action.ts +0 -69
  94. package/src/subagent-output-action.ts +0 -104
  95. package/src/subagent-output-adapter.ts +0 -27
  96. package/src/subagent-output-core.ts +0 -263
  97. package/src/subagent-poll-loop.ts +0 -305
  98. package/src/subagent-port.ts +0 -27
  99. package/src/subagent-ready.ts +0 -89
  100. package/src/subagent-registry.ts +0 -70
  101. package/src/subagent-resolution.ts +0 -41
  102. package/src/subagent-scope.ts +0 -41
  103. package/src/subagent-session-io.ts +0 -230
@@ -1,19 +1,12 @@
1
1
  /**
2
- * D100 Online Context Compact Economics & Feasibility Core.
3
- *
4
- * Implements pure economics decision-making, request horizon estimation,
5
- * cache debt tracking, and native compaction feasibility preflights.
6
- *
7
- * Zero-dependency / pure algorithm core (except Pi's public cut point helper).
2
+ * D100 Online Context Compact economics & feasibility core: pure decision-making, request horizon
3
+ * estimation, cache debt tracking and native compaction feasibility preflights — no dependencies
4
+ * except pi's public cut-point helper.
8
5
  */
9
6
 
10
- import {
11
- findCutPoint,
12
- sessionEntryToContextMessages,
13
- type SessionEntry,
14
- } from '@earendil-works/pi-coding-agent';
7
+ import { findCutPoint, sessionEntryToContextMessages, type SessionEntry } from '@earendil-works/pi-coding-agent';
15
8
 
16
- export interface CompactionEconomics {
9
+ interface CompactionEconomics {
17
10
  readonly remainingRequestScale: number;
18
11
  readonly remainingRequestStddevK: number;
19
12
  readonly windowReserveTokens: number;
@@ -30,11 +23,10 @@ export const DEFAULT_COMPACTION_ECONOMICS: CompactionEconomics = Object.freeze({
30
23
  });
31
24
 
32
25
  /**
33
- * Token-account cache ratio: a compaction request re-reads the whole current
34
- * context once (writeTokens), so breakeven = writeTokens / savingTokens ⇔ ratio 2.
35
- * `auto` fallback when no usable cache pricing exists (2026-09-17 review: every
36
- * model actually in use reports zero cacheWrite; the old `null` disabled OCC
37
- * for all of them — 61/61 decisions `cache_ratio_unavailable`, zero compactions).
26
+ * Token-account cache ratio: a compaction request re-reads the whole context once (writeTokens), so
27
+ * breakeven = writeTokens / savingTokens ⇔ ratio 2. This is the `auto` fallback when no usable cache
28
+ * pricing exists, and a floor: ratio ≤ 1 would zero the incremental cost and compact even on the last
29
+ * boundary with nothing left to amortize.
38
30
  */
39
31
  export const TOKEN_ACCOUNT_CACHE_RATIO = 2.0;
40
32
 
@@ -45,7 +37,7 @@ const PROVIDER_FAMILY_CACHE_RATIOS: Readonly<Record<string, number>> = Object.fr
45
37
  deepseek: 10,
46
38
  });
47
39
 
48
- export type CompactionReason =
40
+ type CompactionReason =
49
41
  | 'economic'
50
42
  | 'window_protection'
51
43
  | 'deferred_economic'
@@ -54,14 +46,13 @@ export type CompactionReason =
54
46
  | 'horizon_unavailable'
55
47
  | 'cache_ratio_unavailable'
56
48
  | 'native_not_compactable'
49
+ | 'failure_backoff'
57
50
  | 'non_positive_saving';
58
51
 
59
- export interface RequestHorizonEstimate {
60
- readonly completedBoundaryRequestCounts: readonly number[];
52
+ interface RequestHorizonEstimate {
61
53
  readonly requestsPerBoundaryMean: number;
62
54
  readonly requestsPerBoundaryLowerBound: number;
63
55
  readonly unboundedExpectedRemainingRequests: number;
64
- readonly averageContextTokenIncrement: number | null;
65
56
  readonly windowRequestUpperBound: number | null;
66
57
  readonly expectedRemainingRequests: number;
67
58
  }
@@ -71,15 +62,8 @@ export interface CompactionDecision {
71
62
  readonly archiveTokens: number;
72
63
  readonly memoTokens: number;
73
64
  readonly contextTokens: number;
74
- readonly completedBoundaryRequestCounts: readonly number[] | null;
75
- readonly requestsPerBoundaryMean: number | null;
76
- readonly requestsPerBoundaryLowerBound: number | null;
77
- readonly unboundedExpectedRemainingRequests: number | null;
78
- readonly averageContextTokenIncrement: number | null;
79
- readonly windowRequestUpperBound: number | null;
80
65
  readonly expectedRemainingRequests: number | null;
81
66
  readonly breakevenRequests: number | null;
82
- readonly combinedBreakevenRequests: number | null;
83
67
  readonly effectiveHorizonRequests: number | null;
84
68
  readonly remainingBoundaries: number;
85
69
  readonly cacheWriteReadRatio: number | null;
@@ -106,9 +90,7 @@ export function estimateRemainingRequests(input: {
106
90
  readonly averageContextTokenIncrement: number | null;
107
91
  }): RequestHorizonEstimate {
108
92
  const counts = input.completedBoundaryRequestCounts;
109
- const mean =
110
- counts.reduce((total, count) => total + count, 0) /
111
- Math.max(1, counts.length);
93
+ const mean = counts.reduce((total, count) => total + count, 0) / Math.max(1, counts.length);
112
94
  let lowerBound = mean;
113
95
  if (input.standardDeviationK !== 0) {
114
96
  if (counts.length < MINIMUM_VARIANCE_SAMPLES) {
@@ -133,11 +115,9 @@ export function estimateRemainingRequests(input: {
133
115
  );
134
116
 
135
117
  return {
136
- completedBoundaryRequestCounts: [...counts],
137
118
  requestsPerBoundaryMean: mean,
138
119
  requestsPerBoundaryLowerBound: lowerBound,
139
120
  unboundedExpectedRemainingRequests,
140
- averageContextTokenIncrement: input.averageContextTokenIncrement,
141
121
  windowRequestUpperBound,
142
122
  expectedRemainingRequests:
143
123
  windowRequestUpperBound === null
@@ -162,97 +142,92 @@ export function decideCompaction(input: {
162
142
  readonly economics?: CompactionEconomics;
163
143
  }): CompactionDecision {
164
144
  const economics = input.economics ?? DEFAULT_COMPACTION_ECONOMICS;
165
- const horizon =
166
- input.completedBoundaryRequestCounts === null
167
- ? null
168
- : estimateRemainingRequests({
169
- completedBoundaryRequestCounts: input.completedBoundaryRequestCounts,
170
- remainingBoundaries: input.remainingBoundaries,
171
- scale: economics.remainingRequestScale,
172
- standardDeviationK: economics.remainingRequestStddevK,
173
- contextTokens: input.contextTokens,
174
- contextWindowTokens: input.contextWindowTokens,
175
- averageContextTokenIncrement: input.averageContextTokenIncrement,
176
- });
145
+ const horizon = input.completedBoundaryRequestCounts === null ? null : estimateRemainingRequests({
146
+ completedBoundaryRequestCounts: input.completedBoundaryRequestCounts,
147
+ remainingBoundaries: input.remainingBoundaries,
148
+ scale: economics.remainingRequestScale,
149
+ standardDeviationK: economics.remainingRequestStddevK,
150
+ contextTokens: input.contextTokens,
151
+ contextWindowTokens: input.contextWindowTokens,
152
+ averageContextTokenIncrement: input.averageContextTokenIncrement,
153
+ });
177
154
 
178
155
  const savingTokens = input.archiveTokens - input.memoTokens;
156
+ const compressible = savingTokens > 0;
179
157
  const incrementalCacheCostRatio =
180
158
  input.cacheWriteReadRatio === null ? null : Math.max(0, input.cacheWriteReadRatio - 1);
159
+ const incrementalWriteCost = incrementalCacheCostRatio === null
160
+ ? null
161
+ : input.writeTokens * incrementalCacheCostRatio;
181
162
 
182
- const breakevenRequests =
183
- savingTokens > 0 && incrementalCacheCostRatio !== null
184
- ? (input.writeTokens * incrementalCacheCostRatio) / savingTokens
185
- : null;
163
+ const breakevenRequests = compressible && incrementalWriteCost !== null
164
+ ? incrementalWriteCost / savingTokens
165
+ : null;
166
+ const combinedBreakevenRequests = compressible && incrementalWriteCost !== null
167
+ ? (input.carriedDebtTokens + incrementalWriteCost) / savingTokens
168
+ : null;
186
169
 
187
- const combinedBreakevenRequests =
188
- savingTokens > 0 && incrementalCacheCostRatio !== null
189
- ? (input.carriedDebtTokens + input.writeTokens * incrementalCacheCostRatio) / savingTokens
190
- : null;
191
170
  const firstCompaction = input.priorCompactionCount === 0;
192
- // The first-compaction relaxation survives cold-start sample noise when real work
193
- // remains. With zero remaining boundaries the horizon is 1 (nothing left to amortize
194
- // over); scaling it by firstCompactionRequestScale would "discover" a horizon of 2
195
- // and burn a full-context summarization for zero future benefit (2026-09-17 review).
171
+ const horizonRequests = horizon?.expectedRemainingRequests ?? null;
172
+ // The first-compaction relaxation absorbs cold-start sample noise, but only when work remains: with
173
+ // zero remaining boundaries the horizon is 1, and scaling it to 2 would burn a full-context summary.
196
174
  const relaxFirstCompaction = firstCompaction && input.remainingBoundaries > 0;
197
- const effectiveHorizonRequests =
198
- horizon === null
199
- ? null
200
- : relaxFirstCompaction
201
- ? Math.min(
202
- horizon.expectedRemainingRequests * economics.firstCompactionRequestScale,
203
- horizon.windowRequestUpperBound ?? Number.POSITIVE_INFINITY,
204
- )
205
- : horizon.expectedRemainingRequests;
175
+ const effectiveHorizonRequests = horizon === null
176
+ ? null
177
+ : relaxFirstCompaction
178
+ ? Math.min(
179
+ horizon.expectedRemainingRequests * economics.firstCompactionRequestScale,
180
+ horizon.windowRequestUpperBound ?? Number.POSITIVE_INFINITY,
181
+ )
182
+ : horizon.expectedRemainingRequests;
206
183
 
207
184
  const windowProtection =
208
185
  input.contextWindowTokens !== null &&
209
186
  input.contextTokens >= input.contextWindowTokens - economics.windowReserveTokens;
210
-
211
187
  const baseEconomic =
212
- horizon !== null &&
213
- horizon.expectedRemainingRequests > 0 &&
188
+ horizonRequests !== null &&
189
+ horizonRequests > 0 &&
214
190
  breakevenRequests !== null &&
215
- breakevenRequests <= horizon.expectedRemainingRequests;
216
-
191
+ breakevenRequests <= horizonRequests;
217
192
  const firstEconomic =
218
193
  firstCompaction &&
219
194
  effectiveHorizonRequests !== null &&
220
195
  effectiveHorizonRequests > 0 &&
221
196
  breakevenRequests !== null &&
222
197
  breakevenRequests <= effectiveHorizonRequests;
223
-
224
198
  const subsequentMarginOpen =
225
199
  !firstCompaction &&
226
- horizon !== null &&
200
+ horizonRequests !== null &&
227
201
  breakevenRequests !== null &&
228
- breakevenRequests * economics.subsequentCompactionMargin <= horizon.expectedRemainingRequests;
229
-
202
+ breakevenRequests * economics.subsequentCompactionMargin <= horizonRequests;
230
203
  const carriedDebtGateOpen =
231
204
  !firstCompaction &&
232
- horizon !== null &&
205
+ horizonRequests !== null &&
233
206
  combinedBreakevenRequests !== null &&
234
- combinedBreakevenRequests <= horizon.expectedRemainingRequests;
207
+ combinedBreakevenRequests <= horizonRequests;
235
208
 
236
209
  const economic = firstCompaction ? firstEconomic : baseEconomic && subsequentMarginOpen && carriedDebtGateOpen;
237
- const compressible = savingTokens > 0;
238
210
  const compact = compressible && (windowProtection || economic);
239
211
 
212
+ // Ordered guards: first hit wins, so the tail entry is the fallback.
213
+ const reason = ([
214
+ ['non_positive_saving', !compressible],
215
+ ['window_protection', windowProtection],
216
+ ['economic', economic],
217
+ ['horizon_unavailable', horizon === null],
218
+ ['cache_ratio_unavailable', breakevenRequests === null],
219
+ ['deferred_subsequent_margin', !firstCompaction && baseEconomic && !subsequentMarginOpen],
220
+ ['deferred_carried_debt', !firstCompaction && baseEconomic && !carriedDebtGateOpen],
221
+ ['deferred_economic', true],
222
+ ] as Array<[CompactionReason, boolean]>).find(([, hit]) => hit)![0];
223
+
240
224
  return {
241
225
  writeTokens: input.writeTokens,
242
226
  archiveTokens: input.archiveTokens,
243
227
  memoTokens: input.memoTokens,
244
228
  contextTokens: input.contextTokens,
245
- ...(horizon ?? {
246
- completedBoundaryRequestCounts: null,
247
- requestsPerBoundaryMean: null,
248
- requestsPerBoundaryLowerBound: null,
249
- unboundedExpectedRemainingRequests: null,
250
- averageContextTokenIncrement: input.averageContextTokenIncrement,
251
- windowRequestUpperBound: null,
252
- expectedRemainingRequests: null,
253
- }),
229
+ expectedRemainingRequests: horizonRequests,
254
230
  breakevenRequests,
255
- combinedBreakevenRequests,
256
231
  effectiveHorizonRequests,
257
232
  remainingBoundaries: input.remainingBoundaries,
258
233
  cacheWriteReadRatio: input.cacheWriteReadRatio,
@@ -261,56 +236,23 @@ export function decideCompaction(input: {
261
236
  carriedDebtTokens: input.carriedDebtTokens,
262
237
  cacheDebtRepaymentTokens: input.cacheDebtRepaymentTokens,
263
238
  compact,
264
- reason: !compressible
265
- ? 'non_positive_saving'
266
- : windowProtection
267
- ? 'window_protection'
268
- : economic
269
- ? 'economic'
270
- : horizon === null
271
- ? 'horizon_unavailable'
272
- : breakevenRequests === null
273
- ? 'cache_ratio_unavailable'
274
- : !firstCompaction && baseEconomic && !subsequentMarginOpen
275
- ? 'deferred_subsequent_margin'
276
- : !firstCompaction && baseEconomic && !carriedDebtGateOpen
277
- ? 'deferred_carried_debt'
278
- : 'deferred_economic',
239
+ reason,
279
240
  };
280
241
  }
281
242
 
282
243
  /**
283
- * Resolve the cache write/read cost ratio for economic compaction decisions.
284
- *
285
- * Priority:
286
- * 1. Explicit numeric config → use as-is (e.g. 12.5 for Anthropic-style pricing)
287
- * 2. Model cost metadata:
288
- * a. cacheWrite > 0 && cacheRead > 0 → write/read (explicit cache SKU)
289
- * b. cacheWrite == 0 && cacheRead > 0 && input > 0 → input/cacheRead
290
- * (implicit cache: rewriting the prefix is billed at the input price)
291
- * 3. Provider-family fallback (gemini≈4, grok/deepseek≈10 — implicit-cache
292
- * families without a local cacheWrite SKU)
293
- * 4. Token-account fallback 2.0: a compaction re-reads writeTokens once,
294
- * so breakeven = writeTokens/savingTokens ⇔ ratio 2
295
- *
296
- * Returns null only for an explicitly invalid numeric config.
297
- *
298
- * History (P0-1): `auto` used to return null when cacheWrite==0, reading that
299
- * as "no KV cache at all". The 2026-09-17 trial disproved it on this machine:
300
- * grok/deepseek/gemini sessions all show non-zero cacheRead usage while
301
- * cacheWrite stays 0 — there is no separate write SKU, not "no cache". null
302
- * disabled OCC for every model actually used (61/61 decisions
303
- * `cache_ratio_unavailable`, zero compactions). The floor matters as much as
304
- * the fix: never implicitly fall back below 2.0, because ratio <= 1 zeroes the
305
- * incremental cost and would compact even on the last boundary with nothing
306
- * left to amortize (the original P0-1 failure mode).
244
+ * Resolve the cache write/read cost ratio for compaction decisions, in order: explicit numeric config
245
+ * (as-is, e.g. 12.5 for Anthropic-style pricing), model cost metadata (write/read, or input/cacheRead
246
+ * when cache writes are billed at the input price), provider-family fallback (gemini ≈ 4, grok/deepseek
247
+ * ≈ 10), then TOKEN_ACCOUNT_CACHE_RATIO. Returns null only for an explicitly invalid numeric config:
248
+ * `auto` must never resolve to null, since a zero-price table means "no local cacheWrite SKU", not
249
+ * "no cache", and null would disable OCC for every model in actual use.
307
250
  */
308
251
  export function resolveCacheRatioFromCost(
309
252
  ratioConfig: number | 'auto',
310
253
  cost?: { input?: number; cacheRead?: number; cacheWrite?: number } | null,
311
254
  identity?: { provider?: string; modelId?: string } | null,
312
255
  ): number | null {
313
- // Explicit config takes absolute precedence
314
256
  if (typeof ratioConfig === 'number') {
315
257
  return Number.isFinite(ratioConfig) && ratioConfig >= 0 ? ratioConfig : null;
316
258
  }
@@ -319,7 +261,6 @@ export function resolveCacheRatioFromCost(
319
261
  const read = cost.cacheRead ?? 0;
320
262
  const write = cost.cacheWrite ?? 0;
321
263
  const input = cost.input ?? 0;
322
-
323
264
  if (read > 0 && write > 0) return write / read;
324
265
  if (read > 0 && write === 0 && input > 0) return input / read;
325
266
  }
@@ -335,37 +276,27 @@ function compactionMessageCount(entries: readonly SessionEntry[], startIndex: nu
335
276
  let count = 0;
336
277
  for (let index = startIndex; index < endIndex; index++) {
337
278
  const entry = entries[index];
338
- if (entry && entry.type !== 'compaction' && sessionEntryToContextMessages(entry).length > 0) {
339
- count++;
340
- }
279
+ if (entry && entry.type !== 'compaction' && sessionEntryToContextMessages(entry).length > 0) count++;
341
280
  }
342
281
  return count;
343
282
  }
344
283
 
284
+ /** Appends the abort marker pi's cut-point search expects after our intentional abort. */
345
285
  function branchAfterAbort(entries: readonly SessionEntry[]): SessionEntry[] {
346
- const last = entries.at(-1);
347
- const markerProvider = 'pi-herdr';
348
286
  return [
349
287
  ...entries,
350
288
  {
351
289
  type: 'message',
352
290
  id: 'pi-herdr-online-context-compact-abort-marker',
353
- parentId: last?.id ?? null,
291
+ parentId: entries.at(-1)?.id ?? null,
354
292
  timestamp: new Date(0).toISOString(),
355
293
  message: {
356
294
  role: 'assistant',
357
295
  content: [],
358
- api: markerProvider,
359
- provider: markerProvider,
296
+ api: 'pi-herdr',
297
+ provider: 'pi-herdr',
360
298
  model: 'aborted',
361
- usage: {
362
- input: 0,
363
- output: 0,
364
- cacheRead: 0,
365
- cacheWrite: 0,
366
- totalTokens: 0,
367
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
368
- },
299
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
369
300
  stopReason: 'aborted',
370
301
  timestamp: 0,
371
302
  },
@@ -373,6 +304,7 @@ function branchAfterAbort(entries: readonly SessionEntry[]): SessionEntry[] {
373
304
  ];
374
305
  }
375
306
 
307
+ /** Would pi's own compaction find history to summarize on this branch? OCC must not abort otherwise. */
376
308
  export function nativeCompactionFeasible(entries: readonly SessionEntry[], keepRecentTokens: number): boolean {
377
309
  if (!Array.isArray(entries) || entries.length === 0) return false;
378
310
  const path = branchAfterAbort(entries);