@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,195 @@
1
+ import { join } from "node:path";
2
+ import { CompactReadError } from "./compact-read-lease.js";
3
+ import { createSpoolDir, removeSpoolDir } from "./compact-spool-dir.js";
4
+ import { CompactStorageReadError, describeCause, } from "./compact-storage-io.js";
5
+ /**
6
+ * The spool file path of compact current-state storage (senpi#4272, R3.C6.4): one serial
7
+ * asynchronous job chain, the private epoch directory, spool file writes, reads and reclamation,
8
+ * and the failure ledger. `CompactContentStore` adds leaf ownership and spilling on top.
9
+ */
10
+ export const RECLAIM_WORK_ALLOWANCE_BYTES = 1024 * 1024;
11
+ const RECLAIM_RETRY_DELAYS_MS = [100, 400, 1_600, 6_400, 25_600];
12
+ const FAILURE_HISTORY = 32;
13
+ export class CompactSpoolStorage {
14
+ assertReadable() {
15
+ if (this.retired)
16
+ throw new CompactReadError("retired");
17
+ }
18
+ beginRead() {
19
+ this.assertReadable();
20
+ this.reads++;
21
+ this.reading ??= Promise.withResolvers();
22
+ let done = false;
23
+ return () => {
24
+ if (done)
25
+ return;
26
+ done = true;
27
+ if (--this.reads === 0) {
28
+ this.reading?.resolve();
29
+ this.reading = undefined;
30
+ }
31
+ };
32
+ }
33
+ constructor(dir, io) {
34
+ this.failures = [];
35
+ this.nextFile = 1;
36
+ this.chain = Promise.resolve();
37
+ this.queued = 0;
38
+ this.spillQueued = false;
39
+ this.halted = false;
40
+ this.retired = false;
41
+ this.pendingIoCopyBytes = 0;
42
+ this.spillBytesWritten = 0;
43
+ this.reclaimDebtBytes = 0;
44
+ this.reads = 0;
45
+ this.dir = dir;
46
+ this.io = io;
47
+ }
48
+ /** Resolves once every queued storage job, including the ones they queued, has completed. */
49
+ async idle() {
50
+ while (this.queued > 0)
51
+ await this.chain;
52
+ }
53
+ /**
54
+ * Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
55
+ * created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
56
+ */
57
+ async retire() {
58
+ this.retired = true;
59
+ await this.reading?.promise;
60
+ await this.idle();
61
+ const token = this.spoolToken;
62
+ this.spoolToken = undefined;
63
+ if (token !== undefined)
64
+ await removeSpoolDir(this.dir, token);
65
+ }
66
+ /** Reads a committed file and rejects a missing, unreadable or short one. */
67
+ async readFile(path, bytes, operation) {
68
+ const done = this.beginRead();
69
+ try {
70
+ const result = await this.readCommittedFile(path, bytes, operation);
71
+ this.assertReadable();
72
+ return result;
73
+ }
74
+ finally {
75
+ done();
76
+ }
77
+ }
78
+ async readCommittedFile(path, bytes, operation) {
79
+ let data;
80
+ try {
81
+ data = await this.io.readFile(path);
82
+ }
83
+ catch (cause) {
84
+ throw this.readFailure(operation, path, cause);
85
+ }
86
+ if (data.byteLength !== bytes) {
87
+ throw this.readFailure(operation, path, new Error(`expected ${bytes} bytes, found ${data.byteLength}`));
88
+ }
89
+ return data;
90
+ }
91
+ /** Writes one new spool file; undefined means the write failed and was reported (a spill write also halts spilling). */
92
+ async writeFile(data, operation, retainedInMemoryBytes) {
93
+ const path = await this.ensureDir(retainedInMemoryBytes);
94
+ if (path === undefined)
95
+ return undefined;
96
+ const file = join(path, `${operation === "spill_write" ? "leaf" : "page"}-${this.nextFile++}.bin`);
97
+ this.pendingIoCopyBytes += data.byteLength;
98
+ try {
99
+ await this.io.writeFile(file, data);
100
+ this.spillBytesWritten += data.byteLength;
101
+ return file;
102
+ }
103
+ catch (cause) {
104
+ try {
105
+ this.fail(operation, file, cause, retainedInMemoryBytes);
106
+ }
107
+ finally {
108
+ this.discard(file, data.byteLength);
109
+ }
110
+ return undefined;
111
+ }
112
+ finally {
113
+ this.pendingIoCopyBytes -= data.byteLength;
114
+ }
115
+ }
116
+ /**
117
+ * An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
118
+ * finds it gone. Files dropped after retirement started belong to the directory retirement removes.
119
+ */
120
+ discard(path, bytes) {
121
+ if (this.retired)
122
+ return;
123
+ this.reclaimDebtBytes += bytes;
124
+ this.enqueue("reclaim", () => this.reclaim(path, bytes, 0));
125
+ }
126
+ /** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
127
+ async reclaim(path, bytes, attempt) {
128
+ try {
129
+ await this.io.unlink(path);
130
+ }
131
+ catch (cause) {
132
+ if (cause.code !== "ENOENT") {
133
+ this.fail("reclaim", path, cause, 0);
134
+ const delay = RECLAIM_RETRY_DELAYS_MS[attempt];
135
+ if (delay !== undefined) {
136
+ setTimeout(() => {
137
+ if (this.retired)
138
+ this.reclaimDebtBytes -= bytes;
139
+ else
140
+ this.enqueue("reclaim", () => this.reclaim(path, bytes, attempt + 1));
141
+ }, delay).unref?.();
142
+ }
143
+ return;
144
+ }
145
+ }
146
+ this.reclaimDebtBytes -= bytes;
147
+ this.scheduleSpill();
148
+ }
149
+ get spillAllowed() {
150
+ return !this.halted && !this.retired;
151
+ }
152
+ /** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
153
+ get spillStopped() {
154
+ return this.halted || this.reclaimDebtBytes > RECLAIM_WORK_ALLOWANCE_BYTES;
155
+ }
156
+ enqueue(operation, job) {
157
+ this.queued++;
158
+ this.chain = this.chain
159
+ .then(job)
160
+ .catch((cause) => {
161
+ if (operation === "spill_write")
162
+ this.spillQueued = false;
163
+ this.fail(operation, this.dir, cause, 0);
164
+ })
165
+ .finally(() => {
166
+ this.queued--;
167
+ });
168
+ }
169
+ /** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
170
+ ensureDir(retained) {
171
+ this.dirReady ??= createSpoolDir(this.dir).then((token) => {
172
+ this.spoolToken = token;
173
+ return this.dir;
174
+ }, (cause) => {
175
+ this.fail("spool_create", this.dir, cause, retained);
176
+ return undefined;
177
+ });
178
+ return this.dirReady;
179
+ }
180
+ readFailure(operation, path, cause) {
181
+ this.fail(operation, path, cause, 0);
182
+ return new CompactStorageReadError(operation, path, cause);
183
+ }
184
+ fail(operation, path, cause, retained) {
185
+ if (operation === "spill_write" || operation === "spool_create")
186
+ this.halted = true;
187
+ this.record({ operation, path, cause: describeCause(cause), retainedInMemoryBytes: retained });
188
+ }
189
+ record(failure) {
190
+ this.failures.push(failure);
191
+ if (this.failures.length > FAILURE_HISTORY)
192
+ this.failures.shift();
193
+ }
194
+ }
195
+ //# sourceMappingURL=compact-spool-storage.js.map
@@ -2,7 +2,7 @@
2
2
  * The asynchronous file seam of compact current-state storage (senpi#4272) and the failures it
3
3
  * reports. Every spool access goes through `CompactStorageIo`; nothing here blocks the host loop.
4
4
  */
5
- export type CompactStorageOperation = "spool_create" | "spill_write" | "leaf_read" | "reclaim";
5
+ export type CompactStorageOperation = "spool_create" | "spill_write" | "page_write" | "leaf_read" | "page_read" | "reclaim";
6
6
  export interface CompactStorageFailure {
7
7
  readonly operation: CompactStorageOperation;
8
8
  readonly path: string;
@@ -17,6 +17,7 @@ export interface CompactStorageIo {
17
17
  unlink(path: string): Promise<void>;
18
18
  }
19
19
  export declare const nodeCompactStorageIo: CompactStorageIo;
20
+ /** Never throws: a host-supplied cause that cannot be described must not abort the failure report. */
20
21
  export declare function describeCause(cause: unknown): string;
21
22
  /** Committed storage that can no longer be read: a real error, never empty content. */
22
23
  export declare class CompactStorageReadError extends Error {
@@ -4,8 +4,14 @@ export const nodeCompactStorageIo = {
4
4
  readFile: (path) => readFile(path),
5
5
  unlink: (path) => unlink(path),
6
6
  };
7
+ /** Never throws: a host-supplied cause that cannot be described must not abort the failure report. */
7
8
  export function describeCause(cause) {
8
- return cause instanceof Error ? cause.message : String(cause);
9
+ try {
10
+ return cause instanceof Error ? cause.message : String(cause);
11
+ }
12
+ catch {
13
+ return "unprintable failure cause";
14
+ }
9
15
  }
10
16
  /** Committed storage that can no longer be read: a real error, never empty content. */
11
17
  export class CompactStorageReadError extends Error {
@@ -0,0 +1,22 @@
1
+ import { type CompactContentStore, type LeafId } from "./compact-content-store.ts";
2
+ /** Persistent append tree: positive refs are leaves, negative refs are nodes, zero is empty. */
3
+ export declare class CompactTextRoots {
4
+ private readonly nodes;
5
+ private readonly pending;
6
+ private readonly store;
7
+ private next;
8
+ constructor(store: CompactContentStore);
9
+ get residentNodes(): number;
10
+ units(id: LeafId): number;
11
+ isCommitted(id: LeafId): boolean;
12
+ /** Transfers one live root and one newly owned bounded leaf into the new root. */
13
+ append(root: LeafId, text: string): LeafId;
14
+ adjust(id: LeafId, live: number, pins: number): void;
15
+ /** Bounded UTF-16 result and bounded leaf/tail count, checked before any I/O. */
16
+ read(root: LeafId, tail: string, offset: number, units: number, maxLeaves?: number, valid?: () => void): Promise<string>;
17
+ private node;
18
+ private appendLeaf;
19
+ private height;
20
+ private join;
21
+ }
22
+ //# sourceMappingURL=compact-text-roots.d.ts.map
@@ -0,0 +1,138 @@
1
+ import { CompactCommitDependencies } from "./compact-commit-dependencies.js";
2
+ import { COMPACT_LEAF_CODE_UNITS } from "./compact-content-store.js";
3
+ import { CompactReadError } from "./compact-read-lease.js";
4
+ /** Persistent append tree: positive refs are leaves, negative refs are nodes, zero is empty. */
5
+ export class CompactTextRoots {
6
+ constructor(store) {
7
+ this.nodes = new Map();
8
+ this.next = -1;
9
+ this.store = store;
10
+ this.pending = new CompactCommitDependencies(store, (id) => store.committed(id));
11
+ }
12
+ get residentNodes() {
13
+ return this.nodes.size;
14
+ }
15
+ units(id) {
16
+ return id === 0 ? 0 : id > 0 ? this.store.leafUnits(id) : this.node(id).units;
17
+ }
18
+ isCommitted(id) {
19
+ return this.nodes.has(id) && !this.pending.has(id);
20
+ }
21
+ /** Transfers one live root and one newly owned bounded leaf into the new root. */
22
+ append(root, text) {
23
+ this.units(root);
24
+ const leaf = this.store.createLeaf(text);
25
+ if (root === 0)
26
+ return leaf;
27
+ const result = this.appendLeaf(root, leaf);
28
+ this.store.adjust(root, -1, 0);
29
+ this.store.adjust(leaf, -1, 0);
30
+ return result;
31
+ }
32
+ adjust(id, live, pins) {
33
+ const node = this.node(id);
34
+ const beforeLive = Number(node.live > 0);
35
+ const beforePin = Number(node.live === 0 && node.pins > 0);
36
+ if (node.live + live < 0 || node.pins + pins < 0)
37
+ throw new CompactReadError("ownership");
38
+ node.live += live;
39
+ node.pins += pins;
40
+ const liveChange = Number(node.live > 0) - beforeLive;
41
+ const pinChange = Number(node.live === 0 && node.pins > 0) - beforePin;
42
+ // Extra cuts on a live subtree do not visit children. Only category transitions propagate.
43
+ if (liveChange || pinChange) {
44
+ this.store.adjust(node.left, liveChange, pinChange);
45
+ this.store.adjust(node.right, liveChange, pinChange);
46
+ }
47
+ if (node.live === 0 && node.pins === 0) {
48
+ this.pending.delete(id);
49
+ this.nodes.delete(id);
50
+ }
51
+ }
52
+ /** Bounded UTF-16 result and bounded leaf/tail count, checked before any I/O. */
53
+ async read(root, tail, offset, units, maxLeaves = 2, valid = () => this.store.assertReadable()) {
54
+ this.store.assertReadable();
55
+ valid();
56
+ const length = this.units(root);
57
+ if (!Number.isSafeInteger(offset) ||
58
+ !Number.isSafeInteger(units) ||
59
+ offset < 0 ||
60
+ units < 0 ||
61
+ offset + units > length + tail.length)
62
+ throw new CompactReadError("range");
63
+ if (units > COMPACT_LEAF_CODE_UNITS)
64
+ throw new CompactReadError("size_limit");
65
+ if (!Number.isSafeInteger(maxLeaves) || maxLeaves < 1 || maxLeaves > 2)
66
+ throw new CompactReadError("count_limit");
67
+ const selected = [];
68
+ const visit = (id, start, end) => {
69
+ if (start >= end || id === 0)
70
+ return;
71
+ if (id > 0) {
72
+ if (selected.length === maxLeaves)
73
+ throw new CompactReadError("count_limit");
74
+ selected.push({ id, offset: start, units: end - start });
75
+ }
76
+ else {
77
+ const node = this.node(id);
78
+ const leftUnits = this.units(node.left);
79
+ visit(node.left, start, Math.min(end, leftUnits));
80
+ visit(node.right, Math.max(0, start - leftUnits), end - leftUnits);
81
+ }
82
+ };
83
+ visit(root, offset, Math.min(length, offset + units));
84
+ const tailUnits = units === 0 ? 0 : Math.max(0, offset + units - Math.max(offset, length));
85
+ if (selected.length + Number(tailUnits > 0) > maxLeaves)
86
+ throw new CompactReadError("count_limit");
87
+ this.store.adjust(root, 0, 1);
88
+ const done = this.store.beginRead();
89
+ try {
90
+ let result = "";
91
+ for (const part of selected) {
92
+ const text = await this.store.readLeaf(part.id);
93
+ valid();
94
+ result += text.slice(part.offset, part.offset + part.units);
95
+ }
96
+ valid();
97
+ return result + tail.slice(Math.max(0, offset - length), Math.max(0, offset - length) + tailUnits);
98
+ }
99
+ finally {
100
+ this.store.adjust(root, 0, -1);
101
+ done();
102
+ }
103
+ }
104
+ node(id) {
105
+ const node = this.nodes.get(id);
106
+ if (!node)
107
+ throw new CompactReadError("ownership");
108
+ return node;
109
+ }
110
+ appendLeaf(root, leaf) {
111
+ const node = this.nodes.get(root);
112
+ if (!node || this.height(node.left) === this.height(node.right))
113
+ return this.join(root, leaf);
114
+ const right = this.appendLeaf(node.right, leaf);
115
+ const result = this.join(node.left, right);
116
+ this.store.adjust(right, -1, 0);
117
+ return result;
118
+ }
119
+ height(id) {
120
+ return id > 0 ? 0 : this.node(id).height;
121
+ }
122
+ join(left, right) {
123
+ const id = this.next--;
124
+ this.nodes.set(id, {
125
+ left,
126
+ right,
127
+ units: this.units(left) + this.units(right),
128
+ height: 1 + Math.max(this.height(left), this.height(right)),
129
+ live: 1,
130
+ pins: 0,
131
+ });
132
+ this.store.adjust(left, 1, 0);
133
+ this.store.adjust(right, 1, 0);
134
+ this.pending.set(id, [left, right]);
135
+ return id;
136
+ }
137
+ }
138
+ //# sourceMappingURL=compact-text-roots.js.map
@@ -0,0 +1,22 @@
1
+ import { type LeafId } from "./compact-content-store.ts";
2
+ import type { CompactTextRoots } from "./compact-text-roots.ts";
3
+ import type { CompactBlockKind, CompactPersistence } from "./compact-transition.ts";
4
+ export interface FrozenBlock {
5
+ readonly contentIndex: number;
6
+ readonly kind: CompactBlockKind;
7
+ readonly root: LeafId;
8
+ /** A cut shares the immutable tail (under one leaf), not a new leaf for each freeze. */
9
+ readonly tail: string;
10
+ }
11
+ export interface CompactMessageView {
12
+ readonly messageId: string;
13
+ readonly persistence: CompactPersistence | undefined;
14
+ readonly blocks: readonly {
15
+ readonly contentIndex: number;
16
+ readonly kind: CompactBlockKind;
17
+ readonly text: string;
18
+ }[];
19
+ }
20
+ /** Compatibility assembly only; each underlying range is bounded, the resulting legacy view is not. */
21
+ export declare function readCompactBlocks(texts: CompactTextRoots, blocks: readonly FrozenBlock[], valid: () => void): Promise<CompactMessageView["blocks"]>;
22
+ //# sourceMappingURL=compact-text-view.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { COMPACT_LEAF_CODE_UNITS } from "./compact-content-store.js";
2
+ /** Compatibility assembly only; each underlying range is bounded, the resulting legacy view is not. */
3
+ export async function readCompactBlocks(texts, blocks, valid) {
4
+ const result = [];
5
+ for (const { contentIndex, kind, root, tail } of blocks) {
6
+ valid();
7
+ const length = texts.units(root) + tail.length;
8
+ let text = "";
9
+ for (let offset = 0; offset < length; offset += COMPACT_LEAF_CODE_UNITS) {
10
+ text += await texts.read(root, tail, offset, Math.min(COMPACT_LEAF_CODE_UNITS, length - offset), 2, valid);
11
+ }
12
+ result.push({ contentIndex, kind, text });
13
+ }
14
+ return result;
15
+ }
16
+ //# sourceMappingURL=compact-text-view.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The typed transitions compact current state accepts (senpi#4272, R3.C6.4) and the typed
3
+ * rejection `CompactSessionState.accept` throws when one fails validation.
4
+ */
5
+ export type CompactBlockKind = "text" | "thinking" | "tool_arguments";
6
+ export type CompactPersistence = "pending" | "appended" | "failed";
7
+ export interface BlockAddress {
8
+ readonly messageId: string;
9
+ readonly contentIndex: number;
10
+ }
11
+ export type CompactTransition = (BlockAddress & {
12
+ readonly type: "block_start";
13
+ readonly kind: CompactBlockKind;
14
+ readonly seed: string;
15
+ /**
16
+ * The whole seed when later deltas replay it from offset 0, or 0 when they continue after it. Partial
17
+ * coverage is refused: the uncovered seed tail would make every later delta overlap or leave a gap.
18
+ */
19
+ readonly coveredCodeUnits: number;
20
+ }) | (BlockAddress & {
21
+ readonly type: "block_delta";
22
+ readonly offset: number;
23
+ readonly text: string;
24
+ }) | (BlockAddress & {
25
+ readonly type: "block_replace";
26
+ readonly text: string;
27
+ }) | {
28
+ readonly type: "message_status";
29
+ readonly messageId: string;
30
+ readonly persistence: CompactPersistence;
31
+ } | {
32
+ readonly type: "message_remove";
33
+ readonly messageId: string;
34
+ };
35
+ type CompactRejection = "unknown_block" | "duplicate_block" | "invalid_coverage" | "non_contiguous" | "conflicting_overlap" | "unknown_message";
36
+ export declare class CompactTransitionError extends Error {
37
+ readonly reason: CompactRejection;
38
+ constructor(reason: CompactRejection, detail: string);
39
+ }
40
+ export {};
41
+ //# sourceMappingURL=compact-transition.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The typed transitions compact current state accepts (senpi#4272, R3.C6.4) and the typed
3
+ * rejection `CompactSessionState.accept` throws when one fails validation.
4
+ */
5
+ export class CompactTransitionError extends Error {
6
+ constructor(reason, detail) {
7
+ super(`compact transition rejected (${reason}): ${detail}`);
8
+ this.name = "CompactTransitionError";
9
+ this.reason = reason;
10
+ }
11
+ }
12
+ //# sourceMappingURL=compact-transition.js.map
@@ -50,6 +50,8 @@ export interface RpcConnectionSink {
50
50
  writeRaw(chunk: string): void;
51
51
  /** Shared bindings accept correlated records synchronously, before JSONL buffering. */
52
52
  writeRecord?(record: object): void;
53
+ /** The internal structured lane (senpi#4272): seeded records and block controls, never a legacy destination. */
54
+ writeStructured?(record: object): void;
53
55
  waitForBackpressure(): Promise<void>;
54
56
  /** Tears the transport down once its event queue can no longer deliver (overflow, write failure). */
55
57
  close?(): void;
@@ -47,7 +47,7 @@ import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetModel, runSet
47
47
  import { EffectLedger } from "./effect-ledger.js";
48
48
  import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
49
49
  import { createRpcEventOutputBuffer } from "./event-output-buffer.js";
50
- import { settleExtensionUiResponse } from "./extension-ui-response.js";
50
+ import { answeredUiRequestId, settleExtensionUiResponse } from "./extension-ui-response.js";
51
51
  import { answerGetEntries } from "./get-entries-command.js";
52
52
  import { runHoldInput } from "./held-input-commands.js";
53
53
  import { HostSessionControl } from "./host-session-control.js";
@@ -64,6 +64,7 @@ import { runPauseForRun, runResumeForRun } from "./run-hold-commands.js";
64
64
  import { availableModelsData, interruptRunningTurn } from "./session-control-actions-data.js";
65
65
  import { SessionExtensionUiRequests, sessionDialogRequests } from "./session-extension-ui-requests.js";
66
66
  import { subscribeSessionRecords } from "./session-record-stream.js";
67
+ import { subscribeStructuredSessionRecords } from "./structured-session-records.js";
67
68
  import { RpcSessionRelocation } from "./session-relocation.js";
68
69
  import { answerHostResponse, isHostResponse, relocationChannel, } from "./session-relocation-channel.js";
69
70
  export { buildRpcSessionState } from "./rpc-session-state.js";
@@ -190,6 +191,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
190
191
  // Set by the first command that asks this session for a turn; `set_retry_fallback` is refused after it.
191
192
  let turnRequested = false;
192
193
  let unsubscribe;
194
+ let unsubscribeStructured;
193
195
  let unsubscribeBackpressure;
194
196
  let unsubscribeLoadedSurfaces;
195
197
  let unsubscribeExtensionEvents;
@@ -762,6 +764,10 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
762
764
  unsubscribe = subscribeSessionRecords(session, outputEvent, (abortSource) => {
763
765
  lastAbortSource = abortSource;
764
766
  });
767
+ unsubscribeStructured?.();
768
+ unsubscribeStructured = sink.writeStructured
769
+ ? subscribeStructuredSessionRecords(session, (record) => sink.writeStructured?.(tagSessionRecord(record)))
770
+ : undefined;
765
771
  unsubscribeBackpressure = session.agent.subscribe(async () => {
766
772
  await waitForRpcBackpressure();
767
773
  });
@@ -1704,15 +1710,13 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1704
1710
  parsed !== null &&
1705
1711
  "type" in parsed &&
1706
1712
  parsed.type === "extension_ui_response") {
1707
- if (!options.workerSecurityBridge &&
1708
- "id" in parsed &&
1709
- typeof parsed.id === "string" &&
1710
- grantRequests.has(parsed.id)) {
1713
+ const requestId = answeredUiRequestId(parsed);
1714
+ if (!options.workerSecurityBridge && requestId !== undefined && grantRequests.has(requestId)) {
1711
1715
  const sender = options.clientInfo?.connectionId();
1712
1716
  const capable = sender === undefined
1713
1717
  ? clientCapabilities?.includes("grant_select") === true
1714
1718
  : options.clientInfo?.hasCapability?.(sender, "grant_select") === true;
1715
- if (grantRequests.get(parsed.id) !== sender || !capable)
1719
+ if (grantRequests.get(requestId) !== sender || !capable)
1716
1720
  return;
1717
1721
  }
1718
1722
  const reply = settleExtensionUiResponse(parsed, {
@@ -1767,10 +1771,12 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
1767
1771
  pendingExtensionRequests.close();
1768
1772
  unsubscribeProviderAccountEvents();
1769
1773
  unsubscribe?.();
1774
+ unsubscribeStructured?.();
1770
1775
  unsubscribeBackpressure?.();
1771
1776
  unsubscribeLoadedSurfaces?.();
1772
1777
  unsubscribeExtensionEvents?.();
1773
1778
  unsubscribe = undefined;
1779
+ unsubscribeStructured = undefined;
1774
1780
  unsubscribeBackpressure = undefined;
1775
1781
  unsubscribeLoadedSurfaces = undefined;
1776
1782
  unsubscribeExtensionEvents = undefined;
@@ -16,7 +16,7 @@ import { keepOwnerPipe, OWNER_WATCH_FD } from "./host-lifecycle-launch.js";
16
16
  import { pollProtocolInfo } from "./host-readiness.js";
17
17
  import { initialHostEnvironment } from "./host-spawn-environment.js";
18
18
  import { hostLaunchProfile } from "./protocol-identity.js";
19
- import { createSocketSecret, socketSecretPath } from "./socket-transport.js";
19
+ import { ensureSocketSecret, socketSecretPath } from "./socket-transport.js";
20
20
  import { relayPresenterBootstrap } from "./mcp-presenter-bootstrap.js";
21
21
  export const DEFAULT_READINESS_TIMEOUT_MS = 10_000;
22
22
  export { DEFAULT_STOP_TIMEOUT_MS };
@@ -38,9 +38,11 @@ export async function startHost(paths, socket, options, generation = 0) {
38
38
  reason,
39
39
  });
40
40
  // The settings file must exist before the supervisor reads it at boot, so it
41
- // records the policy before the spawn instead of beside the pidfile.
41
+ // records the policy before the spawn instead of beside the pidfile. A valid public
42
+ // secret belongs to the endpoint, not to this start: clients that already read it
43
+ // derive the pipe name from it, so a start reuses it and never rotates it.
42
44
  if (process.platform === "win32")
43
- await createSocketSecret(socketSecretPath(socket));
45
+ await ensureSocketSecret(socketSecretPath(socket));
44
46
  await writeHostSettings(paths, {
45
47
  socket,
46
48
  capabilities: PINNED_HOST_CLIENT_CAPABILITIES,
@@ -109,7 +109,8 @@ export class RpcSecurityPresentations {
109
109
  if (pending.connection !== connection ||
110
110
  (connection !== undefined && !this.fanout.connectionHas(connection, "grant_select")))
111
111
  return false;
112
- state?.grants.delete(requestId);
112
+ // The entry stays until the worker reports the grant settled (settleGrant): a frame the worker
113
+ // refuses leaves the grant pending, and it must stay answerable only by its recipient.
113
114
  return true;
114
115
  }
115
116
  settleGrant(sessionId, requestId) {
@@ -1,6 +1,7 @@
1
1
  import { bindToProviderScope, runWithProviderScope } from "@earendil-works/pi-ai/node/provider-scope";
2
2
  import { createRpcConnectionHandler, } from "./connection-handler.js";
3
3
  import { createToolAttributionSpans } from "./session-attribution.js";
4
+ import { structuredLaneEnabled } from "./structured-session-records.js";
4
5
  /**
5
6
  * Creates the extension UI bridge and subscriptions within the entry's provider
6
7
  * scope. The classic handler remains the single source of command semantics.
@@ -54,8 +55,17 @@ export async function createRpcSessionBinding(sessionId, entry, writer, requestC
54
55
  return dropAfterClose();
55
56
  writeInScope(record);
56
57
  };
58
+ const writeStructuredInScope = bindToProviderScope((record) => writer.structuredLanes.accept(sessionId, record));
57
59
  const taggedSink = {
58
60
  writeRecord,
61
+ ...(structuredLaneEnabled()
62
+ ? {
63
+ writeStructured: (record) => {
64
+ if (entry.scope.state !== "closed")
65
+ writeStructuredInScope(record);
66
+ },
67
+ }
68
+ : {}),
59
69
  // A late raw reply after the scope closed is dropped like any record (senpi#2871); while the scope is
60
70
  // open, records arrive only through writeRecord, so a raw write is a wiring bug.
61
71
  writeRaw: () => {
@@ -2,6 +2,7 @@ import type { ExtensionUIContext, SessionKind } from "../../core/extensions/type
2
2
  import { type McpFormBinding } from "../../core/mcp-form-types.ts";
3
3
  import { McpPresenterAuthority, type McpPresenterConnection } from "./mcp-presenter-authority.ts";
4
4
  import type { SecurityNoticeSnapshot, SecurityNoticeState } from "../../core/security-notices.ts";
5
+ import { SessionStructuredLanes } from "./session-structured-lanes.ts";
5
6
  import { type MediaPersister } from "./media-placeholders.ts";
6
7
  import type { RpcHostLifecycleEvent, RpcOpenQueuedEvent, RpcSessionClosedEvent, RpcSessionClosedReason, RpcSessionParkedEvent } from "./rpc-types.ts";
7
8
  import { type SessionEventWriterConnection } from "./session-event-fanout.ts";
@@ -23,6 +24,7 @@ export declare const MAX_SHARED_STDIO_QUEUE_RECORDS = 4096;
23
24
  * scheduling boundary and violate D9.
24
25
  */
25
26
  export declare class SessionEventWriter {
27
+ readonly structuredLanes: SessionStructuredLanes;
26
28
  private presenterAuthority?;
27
29
  private readonly presenterConnections;
28
30
  private readonly presenterBindings;
@@ -3,6 +3,7 @@ import { bindPrivateMcpForms } from "../../core/mcp-form-types.js";
3
3
  import { McpPresenterAuthority } from "./mcp-presenter-authority.js";
4
4
  import { MEDIA_PLACEHOLDERS_CAPABILITY } from "./custom-capability.js";
5
5
  import { normalizeJsonValue } from "./json-normal-value.js";
6
+ import { SessionStructuredLanes } from "./session-structured-lanes.js";
6
7
  import { serializeJsonLine } from "./jsonl.js";
7
8
  import { omitNormalInlineMedia } from "./media-placeholders.js";
8
9
  import { RpcSecurityPresentations } from "./security-presentations.js";
@@ -141,6 +142,7 @@ export class SessionEventWriter {
141
142
  return this.reservedCloseBytes;
142
143
  }
143
144
  constructor(writeRaw, waitOrSchedule, scheduleFlush) {
145
+ this.structuredLanes = new SessionStructuredLanes();
144
146
  this.presenterConnections = new Map();
145
147
  this.presenterBindings = new Map();
146
148
  this.records = new SessionRecordAccounting();