@code-yeongyu/senpi 2026.10.26-2 → 2026.10.27-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 (61) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/bundle/chunks/{chunk-PPECSSWX.js → chunk-6CJC6JY4.js} +1 -1
  3. package/dist/bundle/chunks/chunk-NXQFBAUI.js +2 -0
  4. package/dist/bundle/chunks/{chunk-ESNSRUW4.js → chunk-P5E4EVQF.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-I2WFLKZ2.js → chunk-Q2A2V6NT.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-JPGT64RV.js → chunk-TV3UI63P.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-TQ67PB7U.js → chunk-TWHF52G6.js} +3 -3
  8. package/dist/bundle/chunks/chunk-ZAS4NBVK.js +410 -0
  9. package/dist/bundle/chunks/{chunk-RPW3XQW3.js → chunk-ZXIAVLJV.js} +75 -75
  10. package/dist/bundle/chunks/{cli-main-Q2GA53XM.js → cli-main-36FRZ7ZB.js} +1 -1
  11. package/dist/bundle/chunks/client-CBDPMWZJ.js +2 -0
  12. package/dist/bundle/chunks/{dispatch-5R3TYAYB.js → dispatch-W5PENPZM.js} +1 -1
  13. package/dist/bundle/chunks/{interactive-mode-5DEKY3YW.js → interactive-mode-5S7NNV2Z.js} +1 -1
  14. package/dist/bundle/chunks/{multi-session-host-OPR5VBFO.js → multi-session-host-E3OPE6HJ.js} +1 -1
  15. package/dist/bundle/chunks/{package-manager-cli-W55E3LHP.js → package-manager-cli-3XO456VE.js} +1 -1
  16. package/dist/bundle/chunks/protocol-GLNWYI5G.js +2 -0
  17. package/dist/bundle/chunks/{rpc-mode-IDUJTSYS.js → rpc-mode-GLWKLTMA.js} +1 -1
  18. package/dist/bundle/chunks/{session-control-endpoint-LMZH5JLH.js → session-control-endpoint-KLE2X4RR.js} +1 -1
  19. package/dist/bundle/chunks/session-worker.js +102 -102
  20. package/dist/bundle/cli.js +2 -2
  21. package/dist/bundle/index.js +1 -1
  22. package/dist/bundle/rpc-entry.js +1 -1
  23. package/dist/bundle/runtime-manifest.json +1 -1
  24. package/dist/core/extensions/builtin/anthropic-subscription/account-context.d.ts +15 -0
  25. package/dist/core/extensions/builtin/anthropic-subscription/account-context.js +53 -0
  26. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.d.ts +3 -0
  27. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +26 -9
  28. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.d.ts +16 -0
  29. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.js +27 -0
  30. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts +2 -13
  31. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -26
  32. package/dist/core/extensions/builtin/anthropic-subscription/errors.js +3 -2
  33. package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +6 -1
  34. package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +1 -1
  35. package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +13 -2
  36. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +1 -0
  37. package/dist/core/extensions/builtin/mcp/modern/request.js +3 -2
  38. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +2 -0
  39. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +2 -0
  40. package/dist/core/extensions/builtin/mcp/transport-sdk.js +12 -1
  41. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  42. package/dist/modes/interactive/interactive-mode.js +2 -1
  43. package/dist/modes/rpc/compact-commit-dependencies.d.ts +13 -0
  44. package/dist/modes/rpc/compact-commit-dependencies.js +48 -0
  45. package/dist/modes/rpc/compact-content-index.d.ts +1 -2
  46. package/dist/modes/rpc/compact-content-index.js +12 -23
  47. package/dist/modes/rpc/compact-content-store.d.ts +6 -2
  48. package/dist/modes/rpc/compact-content-store.js +40 -7
  49. package/dist/modes/rpc/compact-read-lease.d.ts +15 -0
  50. package/dist/modes/rpc/compact-read-lease.js +40 -0
  51. package/dist/modes/rpc/compact-session-state.d.ts +7 -28
  52. package/dist/modes/rpc/compact-session-state.js +58 -31
  53. package/dist/modes/rpc/compact-spool-storage.d.ts +5 -0
  54. package/dist/modes/rpc/compact-spool-storage.js +33 -0
  55. package/dist/modes/rpc/compact-text-roots.d.ts +22 -0
  56. package/dist/modes/rpc/compact-text-roots.js +138 -0
  57. package/dist/modes/rpc/compact-text-view.d.ts +22 -0
  58. package/dist/modes/rpc/compact-text-view.js +16 -0
  59. package/package.json +6 -6
  60. package/dist/bundle/chunks/chunk-2XF6W6F6.js +0 -410
  61. package/dist/bundle/chunks/client-LOS6GSEW.js +0 -2
@@ -1,6 +1,6 @@
1
1
  import { beginLegacyMcpOperation, physicalMcpClient, takeMcpRequestOrigin, } from "../request-origin.js";
2
2
  import { AuthError } from "../errors.js";
3
- import { loadMcpSdkTypes, loadMcpSdkZodCompat } from "../sdk.lazy.js";
3
+ import { loadMcpSdkProtocol, loadMcpSdkTypes, loadMcpSdkZodCompat } from "../sdk.lazy.js";
4
4
  import { mcpElapsedClock } from "../wrap.js";
5
5
  import { collectMrtrForm } from "./elicitation.js";
6
6
  import { isRecord, validateMrtrForm } from "./form-schema.js";
@@ -45,8 +45,9 @@ export function installModernRequestAdapter(client, protocol, clock = mcpElapsed
45
45
  if (own(request.params ?? {}, "requestState") || own(request.params ?? {}, "inputResponses") || options?.task)
46
46
  throw new McpMrtrError("MCP_MRTR_INVALID_INPUT");
47
47
  const params = structuredClone(request.params ?? {});
48
+ const { DEFAULT_REQUEST_TIMEOUT_MSEC } = await loadMcpSdkProtocol();
48
49
  const start = clock.now();
49
- const timeout = sdkOptions.timeout ?? 60_000;
50
+ const timeout = sdkOptions.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
50
51
  const duration = Math.min(sdkOptions.maxTotalTimeout ?? Infinity, 9 * timeout + 300_000);
51
52
  const deadline = start + duration;
52
53
  const operation = new AbortController();
@@ -21,6 +21,8 @@ export declare const loadMcpSdkZodCompat: () => Promise<typeof import("@modelcon
21
21
  export declare const loadMcpSdkStdioTransport: () => Promise<typeof import("@modelcontextprotocol/sdk/client/stdio.js")>;
22
22
  /** SDK-owned protocol schemas for elicitation and modern request results. */
23
23
  export declare const loadMcpSdkTypes: () => Promise<typeof import("@modelcontextprotocol/sdk/types.js")>;
24
+ /** SDK-owned request defaults, loaded only by negotiated modern operations. */
25
+ export declare const loadMcpSdkProtocol: () => Promise<typeof import("@modelcontextprotocol/sdk/shared/protocol.js")>;
24
26
  /** OAuth 2.1 discovery, authorization, token exchange and refresh. */
25
27
  export declare const loadMcpSdkAuth: () => Promise<typeof import("@modelcontextprotocol/sdk/client/auth.js")>;
26
28
  /**
@@ -42,6 +42,8 @@ export const loadMcpSdkZodCompat = memoizeLoader(() => import("@modelcontextprot
42
42
  export const loadMcpSdkStdioTransport = memoizeLoader(() => import("@modelcontextprotocol/sdk/client/stdio.js"));
43
43
  /** SDK-owned protocol schemas for elicitation and modern request results. */
44
44
  export const loadMcpSdkTypes = memoizeLoader(() => import("@modelcontextprotocol/sdk/types.js"));
45
+ /** SDK-owned request defaults, loaded only by negotiated modern operations. */
46
+ export const loadMcpSdkProtocol = memoizeLoader(() => import("@modelcontextprotocol/sdk/shared/protocol.js"));
45
47
  let authModule;
46
48
  /** OAuth 2.1 discovery, authorization, token exchange and refresh. */
47
49
  export const loadMcpSdkAuth = memoizeLoader(async () => {
@@ -89,6 +89,17 @@ function pipeStderr(transport, logger, sink, protocol) {
89
89
  const stderr = transport.stderr;
90
90
  if (stderr === undefined || stderr === null)
91
91
  return;
92
+ const start = transport.start.bind(transport);
93
+ transport.start = async () => {
94
+ const onclose = transport.onclose;
95
+ transport.onclose = () => {
96
+ stderrEnded = true;
97
+ if (protocol.version === undefined)
98
+ protocol.resolveStdioDiagnostics?.(false);
99
+ onclose?.();
100
+ };
101
+ await start();
102
+ };
92
103
  const logLine = (line) => {
93
104
  // Arbitrary server text may echo private messages, schemas, or answers. Keep diagnostic
94
105
  // events and allowlisted system error codes, never untrusted payload text under enforcement.
@@ -127,7 +138,7 @@ function pipeStderr(transport, logger, sink, protocol) {
127
138
  return;
128
139
  const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
129
140
  if (!protocol.legacy && !protocol.privateForms) {
130
- // Only legacy-positive negotiation may release startup diagnostics.
141
+ // Release only after legacy classification or an unclassified child's exit.
131
142
  undecided += text.slice(0, Math.max(0, 512 * 1024 - undecided.length));
132
143
  return;
133
144
  }
@@ -329,7 +329,8 @@ export declare class InteractiveMode {
329
329
  */
330
330
  private unsubscribeSecurityNotices?;
331
331
  private detachMcpGrantPresentation?;
332
- private mcpMrtrPresentationLifetime;
332
+ /** A presentation is optional until extension UI is created; shutdown may precede it. */
333
+ private mcpMrtrPresentationLifetime?;
333
334
  private mcpLocalPresenter?;
334
335
  private bindCurrentSessionExtensions;
335
336
  private applyFullscreenScrollbarSetting;
@@ -691,7 +691,6 @@ export class InteractiveMode {
691
691
  this.onRightClickPaste = () => {
692
692
  void this.handleRightClickPaste();
693
693
  };
694
- this.mcpMrtrPresentationLifetime = new AbortController();
695
694
  /**
696
695
  * Gracefully shutdown the agent.
697
696
  * Stops the TUI before emitting shutdown events so extension UI cleanup cannot
@@ -2859,6 +2858,8 @@ export class InteractiveMode {
2859
2858
  }
2860
2859
  createExtensionUIContext() {
2861
2860
  this.mcpMrtrPresentationLifetime ??= new AbortController();
2861
+ if (this.isShuttingDown)
2862
+ this.mcpMrtrPresentationLifetime.abort();
2862
2863
  const context = {
2863
2864
  select: (title, options, opts) => this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionSelector(title, options, opts), opts),
2864
2865
  confirm: async (title, message, opts) => (await this.withBlockedHostDialog(crypto.randomUUID(), title, () => this.showExtensionConfirm(title, message, opts), opts)) ?? false,
@@ -0,0 +1,13 @@
1
+ import type { CompactContentStore, LeafId } from "./compact-content-store.ts";
2
+ /** Per-owner readiness, independent of counted references: one leaf can unblock many rows/trees. */
3
+ export declare class CompactCommitDependencies<Key> {
4
+ private readonly pending;
5
+ private readonly dependants;
6
+ private readonly store;
7
+ constructor(store: CompactContentStore, ready: (key: Key) => void);
8
+ get size(): number;
9
+ has(key: Key): boolean;
10
+ set(key: Key, refs: readonly LeafId[]): void;
11
+ delete(key: Key): void;
12
+ }
13
+ //# sourceMappingURL=compact-commit-dependencies.d.ts.map
@@ -0,0 +1,48 @@
1
+ /** Per-owner readiness, independent of counted references: one leaf can unblock many rows/trees. */
2
+ export class CompactCommitDependencies {
3
+ constructor(store, ready) {
4
+ this.pending = new Map();
5
+ this.dependants = new Map();
6
+ this.store = store;
7
+ store.subscribeCommitted((id) => {
8
+ const keys = this.dependants.get(id);
9
+ this.dependants.delete(id);
10
+ for (const key of keys ?? []) {
11
+ const pending = this.pending.get(key);
12
+ pending?.delete(id);
13
+ if (pending?.size === 0) {
14
+ this.pending.delete(key);
15
+ ready(key);
16
+ }
17
+ }
18
+ });
19
+ }
20
+ get size() {
21
+ return this.pending.size;
22
+ }
23
+ has(key) {
24
+ return this.pending.has(key);
25
+ }
26
+ set(key, refs) {
27
+ this.delete(key);
28
+ const pending = new Set(refs.filter((id) => !this.store.isCommitted(id)));
29
+ if (pending.size === 0)
30
+ return;
31
+ this.pending.set(key, pending);
32
+ for (const id of pending) {
33
+ const keys = this.dependants.get(id) ?? new Set();
34
+ keys.add(key);
35
+ this.dependants.set(id, keys);
36
+ }
37
+ }
38
+ delete(key) {
39
+ for (const id of this.pending.get(key) ?? []) {
40
+ const keys = this.dependants.get(id);
41
+ keys?.delete(key);
42
+ if (keys?.size === 0)
43
+ this.dependants.delete(id);
44
+ }
45
+ this.pending.delete(key);
46
+ }
47
+ }
48
+ //# sourceMappingURL=compact-commit-dependencies.js.map
@@ -41,10 +41,10 @@ export declare class CompactContentIndex<Row extends object> {
41
41
  private readonly overrides;
42
42
  /** Uncommitted leaves per row override; only overrides absent here are eligible to fold. */
43
43
  private readonly unsettledLeaves;
44
- private readonly leafKey;
45
44
  private compacting;
46
45
  private tombstones;
47
46
  private tombstoneKeyUnits;
47
+ private readonly frozen;
48
48
  constructor(store: CompactContentStore, leavesOf: (row: Row) => readonly LeafId[], compactAboveRows: number);
49
49
  has(key: string): boolean;
50
50
  set(key: string, row: Row): void;
@@ -64,7 +64,6 @@ export declare class CompactContentIndex<Row extends object> {
64
64
  };
65
65
  /** The acceptance path checks counts only: an override is eligible once none of its leaves is uncommitted. */
66
66
  maybeCompact(): void;
67
- private committed;
68
67
  /** Folds eligible overrides and every small base page; false leaves the previous base untouched. */
69
68
  private compact;
70
69
  /**
@@ -1,18 +1,18 @@
1
+ import { CompactCommitDependencies } from "./compact-commit-dependencies.js";
1
2
  import { isSmallPage, packPages } from "./compact-index-pages.js";
3
+ import { CompactReadError } from "./compact-read-lease.js";
2
4
  export class CompactContentIndex {
3
5
  constructor(store, leavesOf, compactAboveRows) {
4
6
  this.base = { pages: [], pageOf: new Map() };
5
7
  this.overrides = new Map();
6
- /** Uncommitted leaves per row override; only overrides absent here are eligible to fold. */
7
- this.unsettledLeaves = new Map();
8
- this.leafKey = new Map();
9
8
  this.compacting = false;
10
9
  this.tombstones = 0;
11
10
  this.tombstoneKeyUnits = 0;
11
+ this.frozen = new WeakSet();
12
12
  this.store = store;
13
13
  this.leavesOf = leavesOf;
14
14
  this.compactAboveRows = compactAboveRows;
15
- store.onCommitted = (id) => this.committed(id);
15
+ this.unsettledLeaves = new CompactCommitDependencies(store, () => this.maybeCompact());
16
16
  }
17
17
  has(key) {
18
18
  const override = this.overrides.get(key);
@@ -42,9 +42,13 @@ export class CompactContentIndex {
42
42
  page.pins++;
43
43
  for (const override of this.overrides.values())
44
44
  this.pinRow(override, 1);
45
- return { base: this.base, overrides: new Map(this.overrides) };
45
+ const frozen = { base: this.base, overrides: new Map(this.overrides) };
46
+ this.frozen.add(frozen);
47
+ return frozen;
46
48
  }
47
49
  release(frozen) {
50
+ if (!this.frozen.delete(frozen))
51
+ return;
48
52
  for (const override of frozen.overrides.values())
49
53
  this.pinRow(override, -1);
50
54
  for (const page of frozen.base.pages) {
@@ -55,6 +59,8 @@ export class CompactContentIndex {
55
59
  }
56
60
  /** Every row visible at a frozen root, reading only that root's own pages. */
57
61
  async entries(frozen) {
62
+ if (!this.frozen.has(frozen))
63
+ throw new CompactReadError("released");
58
64
  const rows = new Map();
59
65
  for (const page of frozen.base.pages)
60
66
  for (const [key, row] of await this.readPage(page))
@@ -90,18 +96,6 @@ export class CompactContentIndex {
90
96
  this.maybeCompact();
91
97
  });
92
98
  }
93
- committed(id) {
94
- const key = this.leafKey.get(id);
95
- if (key === undefined)
96
- return;
97
- this.leafKey.delete(id);
98
- const left = (this.unsettledLeaves.get(key) ?? 0) - 1;
99
- if (left > 0)
100
- this.unsettledLeaves.set(key, left);
101
- else
102
- this.unsettledLeaves.delete(key);
103
- this.maybeCompact();
104
- }
105
99
  /** Folds eligible overrides and every small base page; false leaves the previous base untouched. */
106
100
  async compact() {
107
101
  const base = this.base;
@@ -204,11 +198,7 @@ export class CompactContentIndex {
204
198
  this.store.adjust(id, 1, 0);
205
199
  this.drop(key);
206
200
  this.overrides.set(key, override);
207
- const unsettled = leaves.filter((id) => !this.store.isCommitted(id));
208
- for (const id of unsettled)
209
- this.leafKey.set(id, key);
210
- if (unsettled.length > 0)
211
- this.unsettledLeaves.set(key, unsettled.length);
201
+ this.unsettledLeaves.set(key, leaves);
212
202
  if (override.kind === "tombstone")
213
203
  this.countTombstone(key, 1);
214
204
  this.maybeCompact();
@@ -221,7 +211,6 @@ export class CompactContentIndex {
221
211
  this.unsettledLeaves.delete(key);
222
212
  if (old.kind === "row") {
223
213
  for (const id of this.leavesOf(old.row)) {
224
- this.leafKey.delete(id);
225
214
  this.store.adjust(id, -1, 0);
226
215
  }
227
216
  }
@@ -1,5 +1,6 @@
1
1
  import { CompactSpoolStorage } from "./compact-spool-storage.ts";
2
2
  import { type CompactStorageFailure, type CompactStorageIo } from "./compact-storage-io.ts";
3
+ import { CompactTextRoots } from "./compact-text-roots.ts";
3
4
  /**
4
5
  * Owned bounded leaves for compact current state (senpi#4272, R3.C6.4 / R3.C2.4).
5
6
  *
@@ -41,8 +42,11 @@ export declare class CompactContentStore extends CompactSpoolStorage {
41
42
  private residentPayloadBytes;
42
43
  private pinnedOnlyBytes;
43
44
  private spilledLiveBytes;
44
- /** Called after a leaf commits, so index compaction can include rows that now reference only disk. */
45
- onCommitted: ((id: LeafId) => void) | undefined;
45
+ private readonly subscribers;
46
+ readonly text: CompactTextRoots;
47
+ subscribeCommitted(listener: (id: LeafId) => void): void;
48
+ committed(id: LeafId): void;
49
+ leafUnits(id: LeafId): number;
46
50
  constructor(options: CompactContentStoreOptions);
47
51
  /** Takes ownership of an immutable value with one live reference. */
48
52
  createLeaf(text: string): LeafId;
@@ -1,5 +1,7 @@
1
1
  import { CompactSpoolStorage, RECLAIM_WORK_ALLOWANCE_BYTES } from "./compact-spool-storage.js";
2
2
  import { nodeCompactStorageIo } from "./compact-storage-io.js";
3
+ import { CompactReadError } from "./compact-read-lease.js";
4
+ import { CompactTextRoots } from "./compact-text-roots.js";
3
5
  /**
4
6
  * Owned bounded leaves for compact current state (senpi#4272, R3.C6.4 / R3.C2.4).
5
7
  *
@@ -11,6 +13,19 @@ import { nodeCompactStorageIo } from "./compact-storage-io.js";
11
13
  /** 32,768 UTF-16 code units (64 KiB as UTF-16LE), so a split surrogate survives a leaf boundary exactly. */
12
14
  export const COMPACT_LEAF_CODE_UNITS = 32_768;
13
15
  export class CompactContentStore extends CompactSpoolStorage {
16
+ subscribeCommitted(listener) {
17
+ this.subscribers.add(listener);
18
+ }
19
+ committed(id) {
20
+ for (const listener of this.subscribers)
21
+ listener(id);
22
+ }
23
+ leafUnits(id) {
24
+ const leaf = this.leaves.get(id);
25
+ if (!leaf)
26
+ throw new CompactReadError("ownership");
27
+ return leaf.units;
28
+ }
14
29
  constructor(options) {
15
30
  super(options.dir, options.io ?? nodeCompactStorageIo);
16
31
  this.leaves = new Map();
@@ -20,11 +35,16 @@ export class CompactContentStore extends CompactSpoolStorage {
20
35
  this.residentPayloadBytes = 0;
21
36
  this.pinnedOnlyBytes = 0;
22
37
  this.spilledLiveBytes = 0;
38
+ this.subscribers = new Set();
39
+ this.text = new CompactTextRoots(this);
23
40
  this.spillAboveBytes = options.spillAboveBytes ?? 4 * 1024 * 1024;
24
41
  this.spillTargetBytes = options.spillTargetBytes ?? 2 * 1024 * 1024;
25
42
  }
26
43
  /** Takes ownership of an immutable value with one live reference. */
27
44
  createLeaf(text) {
45
+ this.assertReadable();
46
+ if (text.length > COMPACT_LEAF_CODE_UNITS)
47
+ throw new CompactReadError("size_limit");
28
48
  const id = this.nextLeafId++;
29
49
  this.leaves.set(id, { text, units: text.length, live: 1, pins: 0, state: "ram" });
30
50
  this.ramLeaves.add(id);
@@ -34,9 +54,13 @@ export class CompactContentStore extends CompactSpoolStorage {
34
54
  }
35
55
  /** Moves live and cut-pin references; the last reference drops RAM or reclaims the file. */
36
56
  adjust(id, live, pins) {
57
+ if (id === 0)
58
+ return;
59
+ if (id < 0)
60
+ return this.text.adjust(id, live, pins);
37
61
  const leaf = this.leaves.get(id);
38
- if (!leaf)
39
- throw new Error(`compact leaf ${id} is not owned`);
62
+ if (!leaf || leaf.live + live < 0 || leaf.pins + pins < 0)
63
+ throw new CompactReadError("ownership");
40
64
  const pinnedOnly = leaf.live === 0 && leaf.pins > 0;
41
65
  leaf.live += live;
42
66
  leaf.pins += pins;
@@ -54,16 +78,25 @@ export class CompactContentStore extends CompactSpoolStorage {
54
78
  this.residentPayloadBytes -= leaf.units * 2;
55
79
  }
56
80
  isCommitted(id) {
81
+ if (id <= 0)
82
+ return id === 0 || this.text.isCommitted(id);
57
83
  return this.leaves.get(id)?.state === "committed";
58
84
  }
59
85
  /** Reads a leaf the caller still references, from RAM or its committed file. */
60
86
  async readLeaf(id) {
87
+ this.assertReadable();
61
88
  const leaf = this.leaves.get(id);
62
89
  if (!leaf)
63
- throw new Error(`compact leaf ${id} is not owned`);
64
- if (leaf.text !== undefined)
65
- return leaf.text;
66
- return (await this.readFile(leaf.path ?? "", leaf.units * 2, "leaf_read")).toString("utf16le");
90
+ throw new CompactReadError("ownership");
91
+ this.adjust(id, 0, 1);
92
+ try {
93
+ if (leaf.text !== undefined)
94
+ return leaf.text;
95
+ return (await this.readFile(leaf.path ?? "", leaf.units * 2, "leaf_read")).toString("utf16le");
96
+ }
97
+ finally {
98
+ this.adjust(id, 0, -1);
99
+ }
67
100
  }
68
101
  status() {
69
102
  return {
@@ -121,7 +154,7 @@ export class CompactContentStore extends CompactSpoolStorage {
121
154
  leaf.text = undefined;
122
155
  this.residentPayloadBytes -= bytes;
123
156
  this.spilledLiveBytes += bytes;
124
- this.onCommitted?.(id);
157
+ this.committed(id);
125
158
  }
126
159
  this.spillQueued = false;
127
160
  if (this.residentPayloadBytes > this.spillTargetBytes) {
@@ -0,0 +1,15 @@
1
+ export declare class CompactReadError extends Error {
2
+ readonly reason: "released" | "retired" | "range" | "size_limit" | "count_limit" | "ownership";
3
+ constructor(reason: CompactReadError["reason"]);
4
+ }
5
+ /** Invalidates immediately, but holds the owner's references until its last operation unwinds. */
6
+ export declare class CompactReadLease {
7
+ private released;
8
+ private operations;
9
+ private readonly dispose;
10
+ constructor(dispose: () => void);
11
+ assert(): void;
12
+ run<T>(read: () => Promise<T>): Promise<T>;
13
+ release(): void;
14
+ }
15
+ //# sourceMappingURL=compact-read-lease.d.ts.map
@@ -0,0 +1,40 @@
1
+ export class CompactReadError extends Error {
2
+ constructor(reason) {
3
+ super(`compact read refused (${reason})`);
4
+ this.name = "CompactReadError";
5
+ this.reason = reason;
6
+ }
7
+ }
8
+ /** Invalidates immediately, but holds the owner's references until its last operation unwinds. */
9
+ export class CompactReadLease {
10
+ constructor(dispose) {
11
+ this.released = false;
12
+ this.operations = 0;
13
+ this.dispose = dispose;
14
+ }
15
+ assert() {
16
+ if (this.released)
17
+ throw new CompactReadError("released");
18
+ }
19
+ async run(read) {
20
+ this.assert();
21
+ this.operations++;
22
+ try {
23
+ const result = await read();
24
+ this.assert();
25
+ return result;
26
+ }
27
+ finally {
28
+ if (--this.operations === 0 && this.released)
29
+ this.dispose();
30
+ }
31
+ }
32
+ release() {
33
+ if (this.released)
34
+ return;
35
+ this.released = true;
36
+ if (this.operations === 0)
37
+ this.dispose();
38
+ }
39
+ }
40
+ //# sourceMappingURL=compact-read-lease.js.map
@@ -1,22 +1,7 @@
1
1
  import { CompactContentIndex, type FrozenIndex } from "./compact-content-index.ts";
2
- import { type CompactContentStoreOptions, type CompactStorageStatus, type LeafId } from "./compact-content-store.ts";
3
- import { type CompactBlockKind, type CompactPersistence, type CompactTransition } from "./compact-transition.ts";
4
- /**
5
- * The in-memory current-state owner over compact storage (senpi#4272, R3.C6.4 first half).
6
- *
7
- * `accept` is synchronous: it validates a typed transition completely, then publishes the next
8
- * sequence, or throws and leaves the prior root untouched. Block text lives in owned bounded leaves
9
- * that spill asynchronously; settled messages live in a spillable index whose persistence status
10
- * changes only through `message_status`, never through storage success or failure. `freeze` pins
11
- * an immutable cut synchronously, and `read` materializes it asynchronously from RAM or disk.
12
- */
13
- interface FrozenBlock {
14
- readonly contentIndex: number;
15
- readonly kind: CompactBlockKind;
16
- readonly leaves: readonly LeafId[];
17
- /** A cut shares the immutable active tail string (under one leaf) instead of minting a leaf per freeze. */
18
- readonly tail: string;
19
- }
2
+ import { type CompactMessageView, type FrozenBlock } from "./compact-text-view.ts";
3
+ import { type CompactContentStoreOptions, type CompactStorageStatus } from "./compact-content-store.ts";
4
+ import { type CompactPersistence, type CompactTransition } from "./compact-transition.ts";
20
5
  interface SettledRow {
21
6
  readonly persistence: CompactPersistence;
22
7
  readonly blocks: readonly FrozenBlock[];
@@ -26,15 +11,6 @@ interface FrozenCompactState {
26
11
  readonly active: ReadonlyMap<string, readonly FrozenBlock[]>;
27
12
  readonly settled: FrozenIndex<SettledRow>;
28
13
  }
29
- interface CompactMessageView {
30
- readonly messageId: string;
31
- readonly persistence: CompactPersistence | undefined;
32
- readonly blocks: readonly {
33
- readonly contentIndex: number;
34
- readonly kind: CompactBlockKind;
35
- readonly text: string;
36
- }[];
37
- }
38
14
  export interface CompactSessionStateOptions extends CompactContentStoreOptions {
39
15
  /** Index compaction is considered once this many override slots exist (one page by default). */
40
16
  readonly compactAboveRows?: number;
@@ -45,6 +21,7 @@ export declare class CompactSessionState {
45
21
  private readonly active;
46
22
  private sequence;
47
23
  private frozenTailBytes;
24
+ private readonly cuts;
48
25
  constructor(options: CompactSessionStateOptions);
49
26
  get acceptedSequence(): number;
50
27
  accept(transition: CompactTransition): number;
@@ -55,11 +32,14 @@ export declare class CompactSessionState {
55
32
  sequence: number;
56
33
  messages: CompactMessageView[];
57
34
  }>;
35
+ private cutLease;
58
36
  storageStatus(): CompactStorageStatus & ReturnType<CompactContentIndex<SettledRow>["status"]> & {
59
37
  readonly residentTailBytes: number;
60
38
  /** Active tails held by unreleased cuts; may share storage with `residentTailBytes`. */
61
39
  readonly frozenTailBytes: number;
62
40
  readonly hotWitnessBytes: number;
41
+ readonly residentTextNodes: number;
42
+ readonly residentTextIndexBytes: number;
63
43
  };
64
44
  idle(): Promise<void>;
65
45
  retire(): Promise<void>;
@@ -77,7 +57,6 @@ export declare class CompactSessionState {
77
57
  /** Settling mints the final tail into a spillable leaf; a freeze of an active block shares the tail. */
78
58
  private seal;
79
59
  private pin;
80
- private texts;
81
60
  }
82
61
  export {};
83
62
  //# sourceMappingURL=compact-session-state.d.ts.map