@code-yeongyu/senpi 2026.10.26-1 → 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 (179) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/bundle/chunks/{anthropic-messages-M235FJQV.js → anthropic-messages-ODSFJCXR.js} +1 -1
  3. package/dist/bundle/chunks/{args-FBNJOJSR.js → args-SWKJAYQD.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-YQRTQ7H6.js → azure-openai-responses-REGXS4XO.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-DMFAVE5N.js → chunk-4QVORFQ2.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-ZXHLH4MW.js → chunk-5W3DTM2J.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-QYFOHFPS.js → chunk-6CJC6JY4.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-QREKAXKX.js → chunk-6GAMRAVM.js} +5 -5
  10. package/dist/bundle/chunks/{chunk-MSHRIZGT.js → chunk-7IZH2KQX.js} +3 -3
  11. package/dist/bundle/chunks/{chunk-PH3JDRB2.js → chunk-ARSENVDY.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-5KZH6EYQ.js → chunk-CNDPCYNT.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-DYWIFF2Q.js → chunk-CYOHFREV.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-3LPWILUZ.js → chunk-DNDZPKIQ.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-ZLIVZQDY.js → chunk-EQNUL2WB.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-LO5MYLGF.js → chunk-FQDARFHT.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-7KOUQSBX.js → chunk-GPPOPK76.js} +3 -3
  18. package/dist/bundle/chunks/{chunk-I7QWTWVZ.js → chunk-IHKHCWWG.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-MY4ZD3EW.js → chunk-IY4G44MN.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-OQ3WDMB3.js → chunk-JH7MVRNJ.js} +1 -1
  21. package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
  22. package/dist/bundle/chunks/{chunk-UW6M3EXY.js → chunk-KRO63T4Q.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-J5FCH7TI.js → chunk-KTDIIF2F.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-3WP5RFQR.js → chunk-NDNIMC5W.js} +1 -1
  25. package/dist/bundle/chunks/chunk-NXQFBAUI.js +2 -0
  26. package/dist/bundle/chunks/{chunk-CDQPA4FP.js → chunk-ODRGPC5S.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-5FQMNHXV.js → chunk-P5E4EVQF.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-3A3XCLNH.js → chunk-PDG4XWO2.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-5LWUUXMH.js → chunk-PY4ZQHTN.js} +1 -1
  30. package/dist/bundle/chunks/chunk-Q2A2V6NT.js +5 -0
  31. package/dist/bundle/chunks/{chunk-SOXQ42VS.js → chunk-RQ5JTXUC.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-JPGT64RV.js → chunk-TV3UI63P.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-JFQUWEN7.js → chunk-TWHF52G6.js} +4 -4
  34. package/dist/bundle/chunks/{chunk-6U3TBFBA.js → chunk-UJPG4ROT.js} +1 -1
  35. package/dist/bundle/chunks/{chunk-UDM7VO6O.js → chunk-XM36IFP3.js} +1 -1
  36. package/dist/bundle/chunks/{chunk-XQ45JMGN.js → chunk-Z6BREXV3.js} +1 -1
  37. package/dist/bundle/chunks/chunk-ZAS4NBVK.js +410 -0
  38. package/dist/bundle/chunks/{chunk-ADQAPDPV.js → chunk-ZP6JJDYV.js} +1 -1
  39. package/dist/bundle/chunks/{chunk-EZP6AYW2.js → chunk-ZXIAVLJV.js} +88 -88
  40. package/dist/bundle/chunks/{cli-main-55EQA5PS.js → cli-main-36FRZ7ZB.js} +1 -1
  41. package/dist/bundle/chunks/client-CBDPMWZJ.js +2 -0
  42. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-ZJQGIIRC.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
  43. package/dist/bundle/chunks/cursor-agent.js +1 -1
  44. package/dist/bundle/chunks/devin-agent.js +1 -1
  45. package/dist/bundle/chunks/{dispatch-C2IBP5VV.js → dispatch-W5PENPZM.js} +3 -3
  46. package/dist/bundle/chunks/{google-generative-ai-5G4RIQA6.js → google-generative-ai-FHYFXKLA.js} +1 -1
  47. package/dist/bundle/chunks/{google-vertex-ALD4P27O.js → google-vertex-5DWE5U5K.js} +1 -1
  48. package/dist/bundle/chunks/{help-fast-path-PEIKGCKZ.js → help-fast-path-FETLIF3W.js} +1 -1
  49. package/dist/bundle/chunks/{host-command-657VCQ34.js → host-command-XEB7SHT3.js} +1 -1
  50. package/dist/bundle/chunks/{host-session-command-OV5XDOJK.js → host-session-command-FFN52LXJ.js} +1 -1
  51. package/dist/bundle/chunks/{interactive-mode-MLXSYK2H.js → interactive-mode-5S7NNV2Z.js} +1 -1
  52. package/dist/bundle/chunks/{mistral-conversations-RAE4K2XY.js → mistral-conversations-I2PI7DDF.js} +1 -1
  53. package/dist/bundle/chunks/{multi-session-host-774MACJU.js → multi-session-host-E3OPE6HJ.js} +3 -3
  54. package/dist/bundle/chunks/{openai-codex-responses-3CW4R7TG.js → openai-codex-responses-WSEX6SH7.js} +1 -1
  55. package/dist/bundle/chunks/{openai-completions-5UIJXQUF.js → openai-completions-YCBHTBSS.js} +1 -1
  56. package/dist/bundle/chunks/{openai-responses-2BO44MRW.js → openai-responses-GXNSQF4A.js} +1 -1
  57. package/dist/bundle/chunks/{package-manager-cli-MZHVCX2S.js → package-manager-cli-3XO456VE.js} +1 -1
  58. package/dist/bundle/chunks/{pi-messages-RF6GER4R.js → pi-messages-O6GISIRW.js} +1 -1
  59. package/dist/bundle/chunks/protocol-GLNWYI5G.js +2 -0
  60. package/dist/bundle/chunks/{role-ETSFSDTS.js → role-SHIZQKK4.js} +1 -1
  61. package/dist/bundle/chunks/{rotation-stream-ZMQB4GPO.js → rotation-stream-2PCZWNKF.js} +1 -1
  62. package/dist/bundle/chunks/{rpc-mode-UJXOIZCW.js → rpc-mode-GLWKLTMA.js} +1 -1
  63. package/dist/bundle/chunks/{schedule-command-KA7LFFNK.js → schedule-command-B7RRSANU.js} +1 -1
  64. package/dist/bundle/chunks/{session-control-endpoint-MFWXUR7Z.js → session-control-endpoint-KLE2X4RR.js} +1 -1
  65. package/dist/bundle/chunks/{session-picker-BP33NRSL.js → session-picker-DTGBOXDR.js} +1 -1
  66. package/dist/bundle/chunks/session-worker.js +131 -131
  67. package/dist/bundle/chunks/{typesafe-system-one-GNNZ5O5U.js → typesafe-system-one-XGYJJVSE.js} +1 -1
  68. package/dist/bundle/cli.js +2 -2
  69. package/dist/bundle/index.js +1 -1
  70. package/dist/bundle/rpc-entry.js +1 -1
  71. package/dist/bundle/runtime-manifest.json +1 -1
  72. package/dist/core/agent-session.d.ts +11 -1
  73. package/dist/core/agent-session.js +19 -2
  74. package/dist/core/extensions/builtin/anthropic-subscription/account-context.d.ts +15 -0
  75. package/dist/core/extensions/builtin/anthropic-subscription/account-context.js +53 -0
  76. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.d.ts +3 -0
  77. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +26 -9
  78. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.d.ts +16 -0
  79. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane-boundary.js +27 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.d.ts +2 -13
  81. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -26
  82. package/dist/core/extensions/builtin/anthropic-subscription/errors.js +3 -2
  83. package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +6 -1
  84. package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +1 -1
  85. package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +13 -2
  86. package/dist/core/extensions/builtin/compaction/index.js +7 -1
  87. package/dist/core/extensions/builtin/herdr/index.js +9 -5
  88. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
  89. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
  90. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
  91. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
  92. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
  93. package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
  94. package/dist/core/extensions/builtin/mcp/modern/consent-queue.js +1 -0
  95. package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
  96. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
  97. package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
  98. package/dist/core/extensions/builtin/mcp/modern/request.js +6 -2
  99. package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
  100. package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
  101. package/dist/core/extensions/builtin/mcp/sdk.lazy.d.ts +2 -0
  102. package/dist/core/extensions/builtin/mcp/sdk.lazy.js +2 -0
  103. package/dist/core/extensions/builtin/mcp/transport-sdk.js +12 -1
  104. package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
  105. package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
  106. package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
  107. package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
  108. package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
  109. package/dist/core/extensions/types.d.ts +2 -0
  110. package/dist/core/lifecycle-children.d.ts +3 -1
  111. package/dist/core/lifecycle-children.js +79 -15
  112. package/dist/core/lifecycle-record.js +1 -24
  113. package/dist/core/lifecycle-session-binding.js +2 -1
  114. package/dist/core/lifecycle-wait-items.d.ts +17 -0
  115. package/dist/core/lifecycle-wait-items.js +72 -0
  116. package/dist/core/model-runtime.js +6 -1
  117. package/dist/core/provider-api-key-auth.d.ts +6 -1
  118. package/dist/core/provider-api-key-auth.js +11 -4
  119. package/dist/core/provider-composer.d.ts +8 -1
  120. package/dist/core/provider-composer.js +7 -3
  121. package/dist/core/sdk.js +4 -1
  122. package/dist/core/session-activity.js +2 -0
  123. package/dist/core/session-lifecycle-types.d.ts +20 -4
  124. package/dist/core/session-lifecycle.js +35 -4
  125. package/dist/core/structured-session-lane.d.ts +33 -0
  126. package/dist/core/structured-session-lane.js +62 -0
  127. package/dist/core/work-custody-record.d.ts +2 -1
  128. package/dist/core/work-custody-record.js +2 -0
  129. package/dist/core/work-obligations.d.ts +1 -1
  130. package/dist/core/work-obligations.js +8 -2
  131. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  132. package/dist/modes/interactive/interactive-mode.js +2 -1
  133. package/dist/modes/json-event.d.ts +79 -1
  134. package/dist/modes/json-event.js +79 -1
  135. package/dist/modes/rpc/compact-commit-dependencies.d.ts +13 -0
  136. package/dist/modes/rpc/compact-commit-dependencies.js +48 -0
  137. package/dist/modes/rpc/compact-content-index.d.ts +84 -0
  138. package/dist/modes/rpc/compact-content-index.js +243 -0
  139. package/dist/modes/rpc/compact-content-store.d.ts +10 -43
  140. package/dist/modes/rpc/compact-content-store.js +61 -163
  141. package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
  142. package/dist/modes/rpc/compact-index-pages.js +31 -0
  143. package/dist/modes/rpc/compact-read-lease.d.ts +15 -0
  144. package/dist/modes/rpc/compact-read-lease.js +40 -0
  145. package/dist/modes/rpc/compact-session-state.d.ts +14 -63
  146. package/dist/modes/rpc/compact-session-state.js +69 -51
  147. package/dist/modes/rpc/compact-spool-dir.d.ts +4 -1
  148. package/dist/modes/rpc/compact-spool-dir.js +41 -8
  149. package/dist/modes/rpc/compact-spool-storage.d.ts +58 -0
  150. package/dist/modes/rpc/compact-spool-storage.js +195 -0
  151. package/dist/modes/rpc/compact-storage-io.d.ts +2 -1
  152. package/dist/modes/rpc/compact-storage-io.js +7 -1
  153. package/dist/modes/rpc/compact-text-roots.d.ts +22 -0
  154. package/dist/modes/rpc/compact-text-roots.js +138 -0
  155. package/dist/modes/rpc/compact-text-view.d.ts +22 -0
  156. package/dist/modes/rpc/compact-text-view.js +16 -0
  157. package/dist/modes/rpc/compact-transition.d.ts +41 -0
  158. package/dist/modes/rpc/compact-transition.js +12 -0
  159. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  160. package/dist/modes/rpc/connection-handler.js +12 -6
  161. package/dist/modes/rpc/host-ensure-start.js +5 -3
  162. package/dist/modes/rpc/security-presentations.js +2 -1
  163. package/dist/modes/rpc/session-binding.js +10 -0
  164. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  165. package/dist/modes/rpc/session-event-writer.js +2 -0
  166. package/dist/modes/rpc/session-record-stream.d.ts +3 -3
  167. package/dist/modes/rpc/session-record-stream.js +3 -3
  168. package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
  169. package/dist/modes/rpc/session-structured-lanes.js +24 -0
  170. package/dist/modes/rpc/session-worker-client.js +6 -5
  171. package/dist/modes/rpc/structured-session-records.d.ts +12 -0
  172. package/dist/modes/rpc/structured-session-records.js +88 -0
  173. package/docs/extensions.md +7 -1
  174. package/docs/rpc.md +17 -2
  175. package/package.json +6 -6
  176. package/dist/bundle/chunks/chunk-GQJYNFMO.js +0 -2
  177. package/dist/bundle/chunks/chunk-VICDRG3J.js +0 -410
  178. package/dist/bundle/chunks/chunk-VL7O5PO4.js +0 -5
  179. package/dist/bundle/chunks/client-LOS6GSEW.js +0 -2
@@ -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,52 +1,7 @@
1
- import { type CompactContentStoreOptions, type CompactStorageStatus, type LeafId } from "./compact-content-store.ts";
2
- /**
3
- * The in-memory current-state owner over compact storage (senpi#4272, R3.C6.4 first half).
4
- *
5
- * `accept` is synchronous: it validates a typed transition completely, then publishes the next
6
- * sequence, or throws and leaves the prior root untouched. Block text lives in owned bounded leaves
7
- * that spill asynchronously; settled message rows live in a resident map whose persistence status
8
- * changes only through `message_status`, never through storage success or failure. The map is
9
- * exact for every transition: a status change replaces the whole row, a removal deletes it, and a
10
- * transition addressing a removed or unknown message is refused rather than masked. `freeze` pins
11
- * an immutable cut synchronously, and `read` materializes it asynchronously from RAM or disk.
12
- */
13
- type CompactBlockKind = "text" | "thinking" | "tool_arguments";
14
- type CompactPersistence = "pending" | "appended" | "failed";
15
- interface BlockAddress {
16
- readonly messageId: string;
17
- readonly contentIndex: number;
18
- }
19
- export type CompactTransition = (BlockAddress & {
20
- readonly type: "block_start";
21
- readonly kind: CompactBlockKind;
22
- readonly seed: string;
23
- /**
24
- * The whole seed when later deltas replay it from offset 0, or 0 when they continue after it. Partial
25
- * coverage is refused: the uncovered seed tail would make every later delta overlap or leave a gap.
26
- */
27
- readonly coveredCodeUnits: number;
28
- }) | (BlockAddress & {
29
- readonly type: "block_delta";
30
- readonly offset: number;
31
- readonly text: string;
32
- }) | (BlockAddress & {
33
- readonly type: "block_replace";
34
- readonly text: string;
35
- }) | {
36
- readonly type: "message_status";
37
- readonly messageId: string;
38
- readonly persistence: CompactPersistence;
39
- } | {
40
- readonly type: "message_remove";
41
- readonly messageId: string;
42
- };
43
- interface FrozenBlock {
44
- readonly contentIndex: number;
45
- readonly kind: CompactBlockKind;
46
- readonly leaves: readonly LeafId[];
47
- /** A cut shares the immutable active tail string (under one leaf) instead of minting a leaf per freeze. */
48
- readonly tail: string;
49
- }
1
+ import { CompactContentIndex, type FrozenIndex } from "./compact-content-index.ts";
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";
50
5
  interface SettledRow {
51
6
  readonly persistence: CompactPersistence;
52
7
  readonly blocks: readonly FrozenBlock[];
@@ -54,16 +9,11 @@ interface SettledRow {
54
9
  interface FrozenCompactState {
55
10
  readonly sequence: number;
56
11
  readonly active: ReadonlyMap<string, readonly FrozenBlock[]>;
57
- readonly settled: ReadonlyMap<string, SettledRow>;
12
+ readonly settled: FrozenIndex<SettledRow>;
58
13
  }
59
- interface CompactMessageView {
60
- readonly messageId: string;
61
- readonly persistence: CompactPersistence | undefined;
62
- readonly blocks: readonly {
63
- readonly contentIndex: number;
64
- readonly kind: CompactBlockKind;
65
- readonly text: string;
66
- }[];
14
+ export interface CompactSessionStateOptions extends CompactContentStoreOptions {
15
+ /** Index compaction is considered once this many override slots exist (one page by default). */
16
+ readonly compactAboveRows?: number;
67
17
  }
68
18
  export declare class CompactSessionState {
69
19
  private readonly store;
@@ -71,7 +21,8 @@ export declare class CompactSessionState {
71
21
  private readonly active;
72
22
  private sequence;
73
23
  private frozenTailBytes;
74
- constructor(options: CompactContentStoreOptions);
24
+ private readonly cuts;
25
+ constructor(options: CompactSessionStateOptions);
75
26
  get acceptedSequence(): number;
76
27
  accept(transition: CompactTransition): number;
77
28
  freeze(): FrozenCompactState;
@@ -81,13 +32,14 @@ export declare class CompactSessionState {
81
32
  sequence: number;
82
33
  messages: CompactMessageView[];
83
34
  }>;
84
- storageStatus(): CompactStorageStatus & {
35
+ private cutLease;
36
+ storageStatus(): CompactStorageStatus & ReturnType<CompactContentIndex<SettledRow>["status"]> & {
85
37
  readonly residentTailBytes: number;
86
38
  /** Active tails held by unreleased cuts; may share storage with `residentTailBytes`. */
87
39
  readonly frozenTailBytes: number;
88
40
  readonly hotWitnessBytes: number;
89
- /** Settled rows kept resident: S03a-1 has no paged row index, so every row header stays in RAM. */
90
- readonly residentSettledRows: number;
41
+ readonly residentTextNodes: number;
42
+ readonly residentTextIndexBytes: number;
91
43
  };
92
44
  idle(): Promise<void>;
93
45
  retire(): Promise<void>;
@@ -105,7 +57,6 @@ export declare class CompactSessionState {
105
57
  /** Settling mints the final tail into a spillable leaf; a freeze of an active block shares the tail. */
106
58
  private seal;
107
59
  private pin;
108
- private texts;
109
60
  }
110
61
  export {};
111
62
  //# sourceMappingURL=compact-session-state.d.ts.map
@@ -1,24 +1,25 @@
1
+ import { CompactContentIndex } from "./compact-content-index.js";
2
+ import { CompactReadError, CompactReadLease } from "./compact-read-lease.js";
3
+ import { readCompactBlocks } from "./compact-text-view.js";
1
4
  import { COMPACT_LEAF_CODE_UNITS, CompactContentStore, } from "./compact-content-store.js";
2
- class CompactTransitionError extends Error {
3
- constructor(reason, detail) {
4
- super(`compact transition rejected (${reason}): ${detail}`);
5
- this.name = "CompactTransitionError";
6
- this.reason = reason;
7
- }
8
- }
5
+ import { CompactTransitionError, } from "./compact-transition.js";
9
6
  const byContentIndex = ([a], [b]) => a - b;
10
7
  export class CompactSessionState {
11
8
  constructor(options) {
12
- this.settled = new Map();
13
9
  this.active = new Map();
14
10
  this.sequence = 0;
15
11
  this.frozenTailBytes = 0;
12
+ this.cuts = new WeakMap();
16
13
  this.store = new CompactContentStore(options);
14
+ const leavesOf = (row) => row.blocks.map((block) => block.root);
15
+ this.settled = new CompactContentIndex(this.store, leavesOf, options.compactAboveRows ?? 128);
17
16
  }
18
17
  get acceptedSequence() {
19
18
  return this.sequence;
20
19
  }
21
20
  accept(transition) {
21
+ if (transition.type !== "message_remove")
22
+ this.store.assertReadable();
22
23
  switch (transition.type) {
23
24
  case "block_start":
24
25
  this.start(transition);
@@ -39,32 +40,56 @@ export class CompactSessionState {
39
40
  return ++this.sequence;
40
41
  }
41
42
  freeze() {
43
+ this.store.assertReadable();
42
44
  const active = new Map();
43
45
  for (const [messageId, blocks] of this.active)
44
46
  active.set(messageId, this.seal(blocks, false));
45
- const settled = new Map(this.settled);
46
47
  for (const blocks of active.values())
47
48
  this.pin(blocks, 1);
48
- for (const row of settled.values())
49
- this.pin(row.blocks, 1);
50
- return { sequence: this.sequence, active, settled };
49
+ const frozen = { sequence: this.sequence, active, settled: this.settled.freeze() };
50
+ this.cuts.set(frozen, new CompactReadLease(() => {
51
+ for (const blocks of frozen.active.values())
52
+ this.pin(blocks, -1);
53
+ this.settled.release(frozen.settled);
54
+ }));
55
+ return frozen;
51
56
  }
52
57
  release(frozen) {
53
- for (const blocks of frozen.active.values())
54
- this.pin(blocks, -1);
55
- for (const row of frozen.settled.values())
56
- this.pin(row.blocks, -1);
58
+ this.cutLease(frozen).release();
57
59
  }
58
60
  /** Rejects with the storage error when committed content is gone; never returns empty content for it. */
59
61
  async read(frozen) {
60
- const messages = [];
61
- for (const [messageId, row] of frozen.settled) {
62
- messages.push({ messageId, persistence: row.persistence, blocks: await this.texts(row.blocks) });
63
- }
64
- for (const [messageId, blocks] of frozen.active) {
65
- messages.push({ messageId, persistence: undefined, blocks: await this.texts(blocks) });
66
- }
67
- return { sequence: frozen.sequence, messages };
62
+ const lease = this.cutLease(frozen);
63
+ const valid = () => {
64
+ lease.assert();
65
+ this.store.assertReadable();
66
+ };
67
+ valid();
68
+ return lease.run(async () => {
69
+ const messages = [];
70
+ for (const [messageId, row] of await this.settled.entries(frozen.settled)) {
71
+ messages.push({
72
+ messageId,
73
+ persistence: row.persistence,
74
+ blocks: await readCompactBlocks(this.store.text, row.blocks, valid),
75
+ });
76
+ }
77
+ for (const [messageId, blocks] of frozen.active) {
78
+ messages.push({
79
+ messageId,
80
+ persistence: undefined,
81
+ blocks: await readCompactBlocks(this.store.text, blocks, valid),
82
+ });
83
+ }
84
+ valid();
85
+ return { sequence: frozen.sequence, messages };
86
+ });
87
+ }
88
+ cutLease(frozen) {
89
+ const lease = this.cuts.get(frozen);
90
+ if (!lease)
91
+ throw new CompactReadError("ownership");
92
+ return lease;
68
93
  }
69
94
  storageStatus() {
70
95
  let residentTailBytes = 0;
@@ -77,10 +102,13 @@ export class CompactSessionState {
77
102
  }
78
103
  return {
79
104
  ...this.store.status(),
105
+ ...this.settled.status(),
80
106
  residentTailBytes,
81
107
  frozenTailBytes: this.frozenTailBytes,
82
108
  hotWitnessBytes,
83
- residentSettledRows: this.settled.size,
109
+ residentTextNodes: this.store.text.residentNodes,
110
+ // Fixed accounting charge for node headers, two edges and pending dependency bookkeeping.
111
+ residentTextIndexBytes: this.store.text.residentNodes * 192,
84
112
  };
85
113
  }
86
114
  idle() {
@@ -98,7 +126,7 @@ export class CompactSessionState {
98
126
  throw new CompactTransitionError("invalid_coverage", `${covered} of ${t.seed.length} seed code units`);
99
127
  }
100
128
  const emitted = covered > 0 ? 0 : t.seed.length;
101
- const block = { kind: t.kind, leaves: [], tail: "", units: 0, emitted, covered, witness: "" };
129
+ const block = { kind: t.kind, root: 0, tail: "", units: 0, emitted, covered, witness: "" };
102
130
  block.witness = t.seed.slice(0, covered);
103
131
  this.append(block, t.seed);
104
132
  const blocks = this.active.get(t.messageId) ?? new Map();
@@ -124,8 +152,8 @@ export class CompactSessionState {
124
152
  block.witness = "";
125
153
  }
126
154
  replace(block, text) {
127
- for (const id of block.leaves.splice(0))
128
- this.store.adjust(id, -1, 0);
155
+ this.store.adjust(block.root, -1, 0);
156
+ block.root = 0;
129
157
  block.tail = "";
130
158
  block.units = 0;
131
159
  block.covered = 0;
@@ -137,12 +165,12 @@ export class CompactSessionState {
137
165
  const blocks = this.active.get(messageId);
138
166
  if (blocks) {
139
167
  this.settled.set(messageId, { persistence, blocks: this.seal(blocks, true) });
168
+ for (const block of blocks.values())
169
+ this.store.adjust(block.root, -1, 0);
140
170
  this.active.delete(messageId);
141
- return;
142
171
  }
143
- const row = this.settled.get(messageId);
144
- if (row) {
145
- this.settled.set(messageId, { ...row, persistence });
172
+ else if (this.settled.has(messageId)) {
173
+ this.settled.patch(messageId, { persistence });
146
174
  }
147
175
  else {
148
176
  throw new CompactTransitionError("unknown_message", messageId);
@@ -150,17 +178,14 @@ export class CompactSessionState {
150
178
  }
151
179
  remove(messageId) {
152
180
  const blocks = this.active.get(messageId);
153
- const row = this.settled.get(messageId);
154
- if (!blocks && !row)
181
+ const settled = this.settled.has(messageId);
182
+ if (!blocks && !settled)
155
183
  throw new CompactTransitionError("unknown_message", messageId);
156
184
  for (const block of blocks?.values() ?? [])
157
- for (const id of block.leaves)
158
- this.store.adjust(id, -1, 0);
159
- for (const block of row?.blocks ?? [])
160
- for (const id of block.leaves)
161
- this.store.adjust(id, -1, 0);
185
+ this.store.adjust(block.root, -1, 0);
162
186
  this.active.delete(messageId);
163
- this.settled.delete(messageId);
187
+ if (settled)
188
+ this.settled.delete(messageId);
164
189
  }
165
190
  block(address) {
166
191
  const block = this.active.get(address.messageId)?.get(address.contentIndex);
@@ -173,7 +198,7 @@ export class CompactSessionState {
173
198
  let rest = text;
174
199
  while (block.tail.length + rest.length >= COMPACT_LEAF_CODE_UNITS) {
175
200
  const take = COMPACT_LEAF_CODE_UNITS - block.tail.length;
176
- block.leaves.push(this.store.createLeaf(block.tail + rest.slice(0, take)));
201
+ block.root = this.store.text.append(block.root, block.tail + rest.slice(0, take));
177
202
  block.tail = "";
178
203
  rest = rest.slice(take);
179
204
  }
@@ -183,24 +208,17 @@ export class CompactSessionState {
183
208
  seal(blocks, settle) {
184
209
  return [...blocks].sort(byContentIndex).map(([contentIndex, block]) => {
185
210
  if (settle && block.tail.length > 0) {
186
- block.leaves.push(this.store.createLeaf(block.tail));
211
+ block.root = this.store.text.append(block.root, block.tail);
187
212
  block.tail = "";
188
213
  }
189
- return { contentIndex, kind: block.kind, leaves: [...block.leaves], tail: block.tail };
214
+ return { contentIndex, kind: block.kind, root: block.root, tail: block.tail };
190
215
  });
191
216
  }
192
217
  pin(blocks, delta) {
193
218
  for (const block of blocks) {
194
219
  this.frozenTailBytes += delta * block.tail.length * 2;
195
- for (const id of block.leaves)
196
- this.store.adjust(id, 0, delta);
220
+ this.store.adjust(block.root, 0, delta);
197
221
  }
198
222
  }
199
- async texts(blocks) {
200
- return Promise.all(blocks.map(async ({ contentIndex, kind, leaves, tail }) => {
201
- const parts = await Promise.all(leaves.map((id) => this.store.readLeaf(id)));
202
- return { contentIndex, kind, text: parts.join("") + tail };
203
- }));
204
- }
205
223
  }
206
224
  //# sourceMappingURL=compact-session-state.js.map
@@ -1,5 +1,8 @@
1
1
  /** Creates a fresh private epoch directory and returns the token its ownership marker holds. */
2
2
  export declare function createSpoolDir(dir: string): Promise<string>;
3
- /** Removes `dir` only while it is still the private, marked directory `createSpoolDir` made. */
3
+ /**
4
+ * Removes `dir` only while it is still the private, marked directory `createSpoolDir` made. A
5
+ * directory that is already gone leaves nothing to remove, so retirement settles as a no-op.
6
+ */
4
7
  export declare function removeSpoolDir(dir: string, token: string): Promise<void>;
5
8
  //# sourceMappingURL=compact-spool-dir.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { lstat, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { lstat, mkdir, readFile, rm, rmdir, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
+ import { describeCause } from "./compact-storage-io.js";
4
5
  /**
5
6
  * Ownership of the compact spool's epoch directory (senpi#4272). The store creates the directory
6
7
  * itself (0700, never adopting an existing one) and writes a private marker into it; removal
@@ -9,8 +10,10 @@ import { dirname, join } from "node:path";
9
10
  */
10
11
  const MARKER = ".compact-spool-owner";
11
12
  class CompactSpoolRefusedError extends Error {
12
- constructor(reason, path) {
13
- super(`compact spool directory refused (${reason}): ${path}`);
13
+ constructor(reason, path, cause, cleanupFailure) {
14
+ const detail = cause === undefined ? "" : `: ${describeCause(cause)}`;
15
+ const kept = cleanupFailure === undefined ? "" : `; removing the directory failed: ${describeCause(cleanupFailure)}`;
16
+ super(`compact spool directory refused (${reason}): ${path}${detail}${kept}`, { cause });
14
17
  this.name = "CompactSpoolRefusedError";
15
18
  this.reason = reason;
16
19
  this.path = path;
@@ -28,18 +31,48 @@ export async function createSpoolDir(dir) {
28
31
  throw cause;
29
32
  }
30
33
  const token = randomUUID();
31
- await writeFile(join(dir, MARKER), token, { flag: "wx", mode: 0o600 });
34
+ try {
35
+ await writeFile(join(dir, MARKER), token, { flag: "wx", mode: 0o600 });
36
+ }
37
+ catch (cause) {
38
+ throw await abandonSpoolDir(dir, cause);
39
+ }
32
40
  return token;
33
41
  }
34
- /** Removes `dir` only while it is still the private, marked directory `createSpoolDir` made. */
42
+ /**
43
+ * Removes a directory `createSpoolDir` just made but could not mark, so a later store creates it
44
+ * fresh instead of refusing it as pre-existing. Only a marker this call's exclusive create made (any
45
+ * failure but EEXIST) is unlinked, then the directory itself, which `rmdir` removes only while empty.
46
+ */
47
+ async function abandonSpoolDir(dir, cause) {
48
+ try {
49
+ if (cause.code !== "EEXIST")
50
+ await rm(join(dir, MARKER), { force: true });
51
+ await rmdir(dir);
52
+ return new CompactSpoolRefusedError("marker_write_failed", dir, cause);
53
+ }
54
+ catch (cleanupFailure) {
55
+ return new CompactSpoolRefusedError("marker_write_failed", dir, cause, cleanupFailure);
56
+ }
57
+ }
58
+ /**
59
+ * Removes `dir` only while it is still the private, marked directory `createSpoolDir` made. A
60
+ * directory that is already gone leaves nothing to remove, so retirement settles as a no-op.
61
+ */
35
62
  export async function removeSpoolDir(dir, token) {
36
- const refusal = await refusalFor(dir, token);
63
+ const owned = await lstat(dir).catch((cause) => {
64
+ if (cause.code === "ENOENT")
65
+ return undefined;
66
+ throw cause;
67
+ });
68
+ if (owned === undefined)
69
+ return;
70
+ const refusal = await refusalFor(dir, owned, token);
37
71
  if (refusal)
38
72
  throw new CompactSpoolRefusedError(refusal, dir);
39
73
  await rm(dir, { recursive: true, force: true });
40
74
  }
41
- async function refusalFor(dir, token) {
42
- const owned = await lstat(dir);
75
+ async function refusalFor(dir, owned, token) {
43
76
  if (owned.isSymbolicLink())
44
77
  return "symlink";
45
78
  if (!owned.isDirectory())
@@ -0,0 +1,58 @@
1
+ import { type CompactStorageFailure, type CompactStorageIo, type CompactStorageOperation } from "./compact-storage-io.ts";
2
+ /**
3
+ * The spool file path of compact current-state storage (senpi#4272, R3.C6.4): one serial
4
+ * asynchronous job chain, the private epoch directory, spool file writes, reads and reclamation,
5
+ * and the failure ledger. `CompactContentStore` adds leaf ownership and spilling on top.
6
+ */
7
+ export declare const RECLAIM_WORK_ALLOWANCE_BYTES: number;
8
+ export declare abstract class CompactSpoolStorage {
9
+ readonly dir: string;
10
+ private readonly io;
11
+ protected readonly failures: CompactStorageFailure[];
12
+ private nextFile;
13
+ private dirReady;
14
+ private spoolToken;
15
+ private chain;
16
+ private queued;
17
+ protected spillQueued: boolean;
18
+ private halted;
19
+ protected retired: boolean;
20
+ protected pendingIoCopyBytes: number;
21
+ protected spillBytesWritten: number;
22
+ protected reclaimDebtBytes: number;
23
+ private reads;
24
+ private reading;
25
+ assertReadable(): void;
26
+ beginRead(): () => void;
27
+ constructor(dir: string, io: CompactStorageIo);
28
+ /** Resolves once every queued storage job, including the ones they queued, has completed. */
29
+ idle(): Promise<void>;
30
+ /**
31
+ * Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
32
+ * created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
33
+ */
34
+ retire(): Promise<void>;
35
+ /** Reads a committed file and rejects a missing, unreadable or short one. */
36
+ readFile(path: string, bytes: number, operation: CompactStorageOperation): Promise<Buffer>;
37
+ private readCommittedFile;
38
+ /** Writes one new spool file; undefined means the write failed and was reported (a spill write also halts spilling). */
39
+ writeFile(data: Buffer, operation: CompactStorageOperation, retainedInMemoryBytes: number): Promise<string | undefined>;
40
+ /**
41
+ * An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
42
+ * finds it gone. Files dropped after retirement started belong to the directory retirement removes.
43
+ */
44
+ discard(path: string, bytes: number): void;
45
+ /** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
46
+ private reclaim;
47
+ get spillAllowed(): boolean;
48
+ /** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
49
+ protected get spillStopped(): boolean;
50
+ enqueue(operation: CompactStorageOperation, job: () => Promise<void>): void;
51
+ protected abstract scheduleSpill(): void;
52
+ /** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
53
+ private ensureDir;
54
+ private readFailure;
55
+ private fail;
56
+ private record;
57
+ }
58
+ //# sourceMappingURL=compact-spool-storage.d.ts.map