@code-yeongyu/senpi 2026.10.26-1 → 2026.10.26-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/bundle/chunks/{anthropic-messages-M235FJQV.js → anthropic-messages-ODSFJCXR.js} +1 -1
- package/dist/bundle/chunks/{args-FBNJOJSR.js → args-SWKJAYQD.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YQRTQ7H6.js → azure-openai-responses-REGXS4XO.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/chunk-2XF6W6F6.js +410 -0
- package/dist/bundle/chunks/{chunk-DMFAVE5N.js → chunk-4QVORFQ2.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZXHLH4MW.js → chunk-5W3DTM2J.js} +1 -1
- package/dist/bundle/chunks/{chunk-QREKAXKX.js → chunk-6GAMRAVM.js} +5 -5
- package/dist/bundle/chunks/{chunk-MSHRIZGT.js → chunk-7IZH2KQX.js} +3 -3
- package/dist/bundle/chunks/{chunk-PH3JDRB2.js → chunk-ARSENVDY.js} +1 -1
- package/dist/bundle/chunks/{chunk-5KZH6EYQ.js → chunk-CNDPCYNT.js} +1 -1
- package/dist/bundle/chunks/{chunk-DYWIFF2Q.js → chunk-CYOHFREV.js} +1 -1
- package/dist/bundle/chunks/{chunk-3LPWILUZ.js → chunk-DNDZPKIQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZLIVZQDY.js → chunk-EQNUL2WB.js} +1 -1
- package/dist/bundle/chunks/{chunk-5FQMNHXV.js → chunk-ESNSRUW4.js} +1 -1
- package/dist/bundle/chunks/{chunk-LO5MYLGF.js → chunk-FQDARFHT.js} +1 -1
- package/dist/bundle/chunks/{chunk-7KOUQSBX.js → chunk-GPPOPK76.js} +3 -3
- package/dist/bundle/chunks/chunk-I2WFLKZ2.js +5 -0
- package/dist/bundle/chunks/{chunk-I7QWTWVZ.js → chunk-IHKHCWWG.js} +1 -1
- package/dist/bundle/chunks/{chunk-MY4ZD3EW.js → chunk-IY4G44MN.js} +1 -1
- package/dist/bundle/chunks/{chunk-OQ3WDMB3.js → chunk-JH7MVRNJ.js} +1 -1
- package/dist/bundle/chunks/chunk-KBSFTXAT.js +2 -0
- package/dist/bundle/chunks/{chunk-UW6M3EXY.js → chunk-KRO63T4Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-J5FCH7TI.js → chunk-KTDIIF2F.js} +1 -1
- package/dist/bundle/chunks/{chunk-3WP5RFQR.js → chunk-NDNIMC5W.js} +1 -1
- package/dist/bundle/chunks/{chunk-CDQPA4FP.js → chunk-ODRGPC5S.js} +1 -1
- package/dist/bundle/chunks/{chunk-3A3XCLNH.js → chunk-PDG4XWO2.js} +1 -1
- package/dist/bundle/chunks/{chunk-QYFOHFPS.js → chunk-PPECSSWX.js} +1 -1
- package/dist/bundle/chunks/{chunk-5LWUUXMH.js → chunk-PY4ZQHTN.js} +1 -1
- package/dist/bundle/chunks/{chunk-EZP6AYW2.js → chunk-RPW3XQW3.js} +22 -22
- package/dist/bundle/chunks/{chunk-SOXQ42VS.js → chunk-RQ5JTXUC.js} +1 -1
- package/dist/bundle/chunks/{chunk-JFQUWEN7.js → chunk-TQ67PB7U.js} +2 -2
- package/dist/bundle/chunks/{chunk-6U3TBFBA.js → chunk-UJPG4ROT.js} +1 -1
- package/dist/bundle/chunks/{chunk-UDM7VO6O.js → chunk-XM36IFP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-XQ45JMGN.js → chunk-Z6BREXV3.js} +1 -1
- package/dist/bundle/chunks/{chunk-ADQAPDPV.js → chunk-ZP6JJDYV.js} +1 -1
- package/dist/bundle/chunks/{cli-main-55EQA5PS.js → cli-main-Q2GA53XM.js} +1 -1
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-ZJQGIIRC.js → cloudflare-workers-ai-system-one-WDHT7BBV.js} +1 -1
- package/dist/bundle/chunks/cursor-agent.js +1 -1
- package/dist/bundle/chunks/devin-agent.js +1 -1
- package/dist/bundle/chunks/{dispatch-C2IBP5VV.js → dispatch-5R3TYAYB.js} +3 -3
- package/dist/bundle/chunks/{google-generative-ai-5G4RIQA6.js → google-generative-ai-FHYFXKLA.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-ALD4P27O.js → google-vertex-5DWE5U5K.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-PEIKGCKZ.js → help-fast-path-FETLIF3W.js} +1 -1
- package/dist/bundle/chunks/{host-command-657VCQ34.js → host-command-XEB7SHT3.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-OV5XDOJK.js → host-session-command-FFN52LXJ.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-MLXSYK2H.js → interactive-mode-5DEKY3YW.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-RAE4K2XY.js → mistral-conversations-I2PI7DDF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-774MACJU.js → multi-session-host-OPR5VBFO.js} +3 -3
- package/dist/bundle/chunks/{openai-codex-responses-3CW4R7TG.js → openai-codex-responses-WSEX6SH7.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-5UIJXQUF.js → openai-completions-YCBHTBSS.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-2BO44MRW.js → openai-responses-GXNSQF4A.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-MZHVCX2S.js → package-manager-cli-W55E3LHP.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-RF6GER4R.js → pi-messages-O6GISIRW.js} +1 -1
- package/dist/bundle/chunks/{role-ETSFSDTS.js → role-SHIZQKK4.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-ZMQB4GPO.js → rotation-stream-2PCZWNKF.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-UJXOIZCW.js → rpc-mode-IDUJTSYS.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-KA7LFFNK.js → schedule-command-B7RRSANU.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-MFWXUR7Z.js → session-control-endpoint-LMZH5JLH.js} +1 -1
- package/dist/bundle/chunks/{session-picker-BP33NRSL.js → session-picker-DTGBOXDR.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +41 -41
- package/dist/bundle/chunks/{typesafe-system-one-GNNZ5O5U.js → typesafe-system-one-XGYJJVSE.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +11 -1
- package/dist/core/agent-session.js +19 -2
- package/dist/core/extensions/builtin/compaction/index.js +7 -1
- package/dist/core/extensions/builtin/herdr/index.js +9 -5
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +6 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +25 -0
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +3 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +50 -19
- package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +4 -3
- package/dist/core/extensions/builtin/mcp/auth/oauth.js +19 -12
- package/dist/core/extensions/builtin/mcp/modern/dispatch.js +1 -0
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.d.ts +4 -1
- package/dist/core/extensions/builtin/mcp/modern/parameter-headers.js +33 -35
- package/dist/core/extensions/builtin/mcp/modern/request.js +3 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/modern/transport.js +4 -2
- package/dist/core/extensions/builtin/terminal/extension-state.js +2 -1
- package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +9 -0
- package/dist/core/extensions/builtin/vertex-model-garden/auth.js +17 -1
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.d.ts +6 -0
- package/dist/core/extensions/builtin/websearch/websearch/providers/google.js +6 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/lifecycle-children.d.ts +3 -1
- package/dist/core/lifecycle-children.js +79 -15
- package/dist/core/lifecycle-record.js +1 -24
- package/dist/core/lifecycle-session-binding.js +2 -1
- package/dist/core/lifecycle-wait-items.d.ts +17 -0
- package/dist/core/lifecycle-wait-items.js +72 -0
- package/dist/core/model-runtime.js +6 -1
- package/dist/core/provider-api-key-auth.d.ts +6 -1
- package/dist/core/provider-api-key-auth.js +11 -4
- package/dist/core/provider-composer.d.ts +8 -1
- package/dist/core/provider-composer.js +7 -3
- package/dist/core/sdk.js +4 -1
- package/dist/core/session-activity.js +2 -0
- package/dist/core/session-lifecycle-types.d.ts +20 -4
- package/dist/core/session-lifecycle.js +35 -4
- package/dist/core/structured-session-lane.d.ts +33 -0
- package/dist/core/structured-session-lane.js +62 -0
- package/dist/core/work-custody-record.d.ts +2 -1
- package/dist/core/work-custody-record.js +2 -0
- package/dist/core/work-obligations.d.ts +1 -1
- package/dist/core/work-obligations.js +8 -2
- package/dist/modes/json-event.d.ts +79 -1
- package/dist/modes/json-event.js +79 -1
- package/dist/modes/rpc/compact-content-index.d.ts +85 -0
- package/dist/modes/rpc/compact-content-index.js +254 -0
- package/dist/modes/rpc/compact-content-store.d.ts +6 -43
- package/dist/modes/rpc/compact-content-store.js +22 -157
- package/dist/modes/rpc/compact-index-pages.d.ts +12 -0
- package/dist/modes/rpc/compact-index-pages.js +31 -0
- package/dist/modes/rpc/compact-session-state.d.ts +11 -39
- package/dist/modes/rpc/compact-session-state.js +17 -26
- package/dist/modes/rpc/compact-spool-dir.d.ts +4 -1
- package/dist/modes/rpc/compact-spool-dir.js +41 -8
- package/dist/modes/rpc/compact-spool-storage.d.ts +53 -0
- package/dist/modes/rpc/compact-spool-storage.js +162 -0
- package/dist/modes/rpc/compact-storage-io.d.ts +2 -1
- package/dist/modes/rpc/compact-storage-io.js +7 -1
- package/dist/modes/rpc/compact-transition.d.ts +41 -0
- package/dist/modes/rpc/compact-transition.js +12 -0
- package/dist/modes/rpc/connection-handler.d.ts +2 -0
- package/dist/modes/rpc/connection-handler.js +12 -6
- package/dist/modes/rpc/host-ensure-start.js +5 -3
- package/dist/modes/rpc/security-presentations.js +2 -1
- package/dist/modes/rpc/session-binding.js +10 -0
- package/dist/modes/rpc/session-event-writer.d.ts +2 -0
- package/dist/modes/rpc/session-event-writer.js +2 -0
- package/dist/modes/rpc/session-record-stream.d.ts +3 -3
- package/dist/modes/rpc/session-record-stream.js +3 -3
- package/dist/modes/rpc/session-structured-lanes.d.ts +12 -0
- package/dist/modes/rpc/session-structured-lanes.js +24 -0
- package/dist/modes/rpc/session-worker-client.js +6 -5
- package/dist/modes/rpc/structured-session-records.d.ts +12 -0
- package/dist/modes/rpc/structured-session-records.js +88 -0
- package/docs/extensions.md +7 -1
- package/docs/rpc.md +17 -2
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-GQJYNFMO.js +0 -2
- package/dist/bundle/chunks/chunk-VICDRG3J.js +0 -410
- package/dist/bundle/chunks/chunk-VL7O5PO4.js +0 -5
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { CompactStorageReadError, describeCause, nodeCompactStorageIo, } from "./compact-storage-io.js";
|
|
1
|
+
import { CompactSpoolStorage, RECLAIM_WORK_ALLOWANCE_BYTES } from "./compact-spool-storage.js";
|
|
2
|
+
import { nodeCompactStorageIo } from "./compact-storage-io.js";
|
|
4
3
|
/**
|
|
5
4
|
* Owned bounded leaves for compact current state (senpi#4272, R3.C6.4 / R3.C2.4).
|
|
6
5
|
*
|
|
@@ -11,30 +10,16 @@ import { CompactStorageReadError, describeCause, nodeCompactStorageIo, } from ".
|
|
|
11
10
|
*/
|
|
12
11
|
/** 32,768 UTF-16 code units (64 KiB as UTF-16LE), so a split surrogate survives a leaf boundary exactly. */
|
|
13
12
|
export const COMPACT_LEAF_CODE_UNITS = 32_768;
|
|
14
|
-
|
|
15
|
-
const RECLAIM_RETRY_DELAYS_MS = [100, 400, 1_600, 6_400, 25_600];
|
|
16
|
-
const FAILURE_HISTORY = 32;
|
|
17
|
-
export class CompactContentStore {
|
|
13
|
+
export class CompactContentStore extends CompactSpoolStorage {
|
|
18
14
|
constructor(options) {
|
|
15
|
+
super(options.dir, options.io ?? nodeCompactStorageIo);
|
|
19
16
|
this.leaves = new Map();
|
|
20
17
|
/** Resident leaves in creation order, so each spill takes the oldest without rescanning committed ones. */
|
|
21
18
|
this.ramLeaves = new Set();
|
|
22
|
-
this.failures = [];
|
|
23
19
|
this.nextLeafId = 1;
|
|
24
|
-
this.nextFile = 1;
|
|
25
|
-
this.chain = Promise.resolve();
|
|
26
|
-
this.queued = 0;
|
|
27
|
-
this.spillQueued = false;
|
|
28
|
-
this.halted = false;
|
|
29
|
-
this.retired = false;
|
|
30
20
|
this.residentPayloadBytes = 0;
|
|
31
|
-
this.pendingIoCopyBytes = 0;
|
|
32
21
|
this.pinnedOnlyBytes = 0;
|
|
33
22
|
this.spilledLiveBytes = 0;
|
|
34
|
-
this.spillBytesWritten = 0;
|
|
35
|
-
this.reclaimDebtBytes = 0;
|
|
36
|
-
this.dir = options.dir;
|
|
37
|
-
this.io = options.io ?? nodeCompactStorageIo;
|
|
38
23
|
this.spillAboveBytes = options.spillAboveBytes ?? 4 * 1024 * 1024;
|
|
39
24
|
this.spillTargetBytes = options.spillTargetBytes ?? 2 * 1024 * 1024;
|
|
40
25
|
}
|
|
@@ -68,6 +53,9 @@ export class CompactContentStore {
|
|
|
68
53
|
}
|
|
69
54
|
this.residentPayloadBytes -= leaf.units * 2;
|
|
70
55
|
}
|
|
56
|
+
isCommitted(id) {
|
|
57
|
+
return this.leaves.get(id)?.state === "committed";
|
|
58
|
+
}
|
|
71
59
|
/** Reads a leaf the caller still references, from RAM or its committed file. */
|
|
72
60
|
async readLeaf(id) {
|
|
73
61
|
const leaf = this.leaves.get(id);
|
|
@@ -77,23 +65,6 @@ export class CompactContentStore {
|
|
|
77
65
|
return leaf.text;
|
|
78
66
|
return (await this.readFile(leaf.path ?? "", leaf.units * 2, "leaf_read")).toString("utf16le");
|
|
79
67
|
}
|
|
80
|
-
/** Resolves once every queued storage job, including the ones they queued, has completed. */
|
|
81
|
-
async idle() {
|
|
82
|
-
while (this.queued > 0)
|
|
83
|
-
await this.chain;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
|
|
87
|
-
* created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
|
|
88
|
-
*/
|
|
89
|
-
async retire() {
|
|
90
|
-
this.retired = true;
|
|
91
|
-
await this.idle();
|
|
92
|
-
const token = this.spoolToken;
|
|
93
|
-
this.spoolToken = undefined;
|
|
94
|
-
if (token !== undefined)
|
|
95
|
-
await removeSpoolDir(this.dir, token);
|
|
96
|
-
}
|
|
97
68
|
status() {
|
|
98
69
|
return {
|
|
99
70
|
residentPayloadBytes: this.residentPayloadBytes,
|
|
@@ -109,94 +80,6 @@ export class CompactContentStore {
|
|
|
109
80
|
failures: [...this.failures],
|
|
110
81
|
};
|
|
111
82
|
}
|
|
112
|
-
/** Reads a committed file and rejects a missing, unreadable or short one. */
|
|
113
|
-
async readFile(path, bytes, operation) {
|
|
114
|
-
let data;
|
|
115
|
-
try {
|
|
116
|
-
data = await this.io.readFile(path);
|
|
117
|
-
}
|
|
118
|
-
catch (cause) {
|
|
119
|
-
throw this.readFailure(operation, path, cause);
|
|
120
|
-
}
|
|
121
|
-
if (data.byteLength !== bytes) {
|
|
122
|
-
throw this.readFailure(operation, path, new Error(`expected ${bytes} bytes, found ${data.byteLength}`));
|
|
123
|
-
}
|
|
124
|
-
return data;
|
|
125
|
-
}
|
|
126
|
-
/** Writes one new spool file; undefined means the write failed, was reported, and spilling halted. */
|
|
127
|
-
async writeFile(data, operation, retainedInMemoryBytes) {
|
|
128
|
-
const path = await this.ensureDir(retainedInMemoryBytes);
|
|
129
|
-
if (path === undefined)
|
|
130
|
-
return undefined;
|
|
131
|
-
const file = join(path, `leaf-${this.nextFile++}.bin`);
|
|
132
|
-
this.pendingIoCopyBytes += data.byteLength;
|
|
133
|
-
try {
|
|
134
|
-
await this.io.writeFile(file, data);
|
|
135
|
-
this.spillBytesWritten += data.byteLength;
|
|
136
|
-
return file;
|
|
137
|
-
}
|
|
138
|
-
catch (cause) {
|
|
139
|
-
this.fail(operation, file, cause, retainedInMemoryBytes);
|
|
140
|
-
this.discard(file, data.byteLength);
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
this.pendingIoCopyBytes -= data.byteLength;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
|
|
149
|
-
* finds it gone. Files dropped after retirement started belong to the directory retirement removes.
|
|
150
|
-
*/
|
|
151
|
-
discard(path, bytes) {
|
|
152
|
-
if (this.retired)
|
|
153
|
-
return;
|
|
154
|
-
this.reclaimDebtBytes += bytes;
|
|
155
|
-
this.enqueue("reclaim", () => this.reclaim(path, bytes, 0));
|
|
156
|
-
}
|
|
157
|
-
/** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
|
|
158
|
-
async reclaim(path, bytes, attempt) {
|
|
159
|
-
try {
|
|
160
|
-
await this.io.unlink(path);
|
|
161
|
-
}
|
|
162
|
-
catch (cause) {
|
|
163
|
-
if (cause.code !== "ENOENT") {
|
|
164
|
-
this.fail("reclaim", path, cause, 0);
|
|
165
|
-
const delay = RECLAIM_RETRY_DELAYS_MS[attempt];
|
|
166
|
-
if (delay !== undefined) {
|
|
167
|
-
setTimeout(() => {
|
|
168
|
-
if (this.retired)
|
|
169
|
-
this.reclaimDebtBytes -= bytes;
|
|
170
|
-
else
|
|
171
|
-
this.enqueue("reclaim", () => this.reclaim(path, bytes, attempt + 1));
|
|
172
|
-
}, delay).unref?.();
|
|
173
|
-
}
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
this.reclaimDebtBytes -= bytes;
|
|
178
|
-
this.scheduleSpill();
|
|
179
|
-
}
|
|
180
|
-
get spillAllowed() {
|
|
181
|
-
return !this.halted && !this.retired;
|
|
182
|
-
}
|
|
183
|
-
/** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
|
|
184
|
-
get spillStopped() {
|
|
185
|
-
return this.halted || this.reclaimDebtBytes > RECLAIM_WORK_ALLOWANCE_BYTES;
|
|
186
|
-
}
|
|
187
|
-
enqueue(operation, job) {
|
|
188
|
-
this.queued++;
|
|
189
|
-
this.chain = this.chain
|
|
190
|
-
.then(job)
|
|
191
|
-
.catch((cause) => {
|
|
192
|
-
if (operation === "spill_write")
|
|
193
|
-
this.spillQueued = false;
|
|
194
|
-
this.fail(operation, this.dir, cause, 0);
|
|
195
|
-
})
|
|
196
|
-
.finally(() => {
|
|
197
|
-
this.queued--;
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
83
|
scheduleSpill() {
|
|
201
84
|
if (this.spillQueued || !this.spillAllowed || this.residentPayloadBytes <= this.spillAboveBytes)
|
|
202
85
|
return;
|
|
@@ -216,22 +99,29 @@ export class CompactContentStore {
|
|
|
216
99
|
const bytes = leaf.units * 2;
|
|
217
100
|
leaf.state = "pending";
|
|
218
101
|
this.ramLeaves.delete(id);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
leaf.state = "ram";
|
|
223
|
-
if (owned)
|
|
224
|
-
this.ramLeaves.add(id);
|
|
102
|
+
let path;
|
|
103
|
+
try {
|
|
104
|
+
path = await this.writeFile(Buffer.from(leaf.text, "utf16le"), "spill_write", bytes);
|
|
225
105
|
}
|
|
226
|
-
|
|
106
|
+
finally {
|
|
107
|
+
// A failed write, or a failure report that threw, keeps the leaf resident and spillable.
|
|
108
|
+
if (path === undefined) {
|
|
109
|
+
leaf.state = "ram";
|
|
110
|
+
if (this.leaves.get(id) === leaf)
|
|
111
|
+
this.ramLeaves.add(id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const owned = this.leaves.get(id) === leaf;
|
|
115
|
+
if (path !== undefined && (!owned || this.retired)) {
|
|
227
116
|
this.discard(path, bytes);
|
|
228
117
|
}
|
|
229
|
-
else {
|
|
118
|
+
else if (path !== undefined) {
|
|
230
119
|
leaf.state = "committed";
|
|
231
120
|
leaf.path = path;
|
|
232
121
|
leaf.text = undefined;
|
|
233
122
|
this.residentPayloadBytes -= bytes;
|
|
234
123
|
this.spilledLiveBytes += bytes;
|
|
124
|
+
this.onCommitted?.(id);
|
|
235
125
|
}
|
|
236
126
|
this.spillQueued = false;
|
|
237
127
|
if (this.residentPayloadBytes > this.spillTargetBytes) {
|
|
@@ -240,30 +130,5 @@ export class CompactContentStore {
|
|
|
240
130
|
this.enqueue("spill_write", () => this.spillNext());
|
|
241
131
|
}
|
|
242
132
|
}
|
|
243
|
-
/** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
|
|
244
|
-
ensureDir(retained) {
|
|
245
|
-
this.dirReady ??= createSpoolDir(this.dir).then((token) => {
|
|
246
|
-
this.spoolToken = token;
|
|
247
|
-
return this.dir;
|
|
248
|
-
}, (cause) => {
|
|
249
|
-
this.fail("spool_create", this.dir, cause, retained);
|
|
250
|
-
return undefined;
|
|
251
|
-
});
|
|
252
|
-
return this.dirReady;
|
|
253
|
-
}
|
|
254
|
-
readFailure(operation, path, cause) {
|
|
255
|
-
this.fail(operation, path, cause, 0);
|
|
256
|
-
return new CompactStorageReadError(operation, path, cause);
|
|
257
|
-
}
|
|
258
|
-
fail(operation, path, cause, retained) {
|
|
259
|
-
if (operation === "spill_write" || operation === "spool_create")
|
|
260
|
-
this.halted = true;
|
|
261
|
-
this.record({ operation, path, cause: describeCause(cause), retainedInMemoryBytes: retained });
|
|
262
|
-
}
|
|
263
|
-
record(failure) {
|
|
264
|
-
this.failures.push(failure);
|
|
265
|
-
if (this.failures.length > FAILURE_HISTORY)
|
|
266
|
-
this.failures.shift();
|
|
267
|
-
}
|
|
268
133
|
}
|
|
269
134
|
//# sourceMappingURL=compact-content-store.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page layout of the compact settled-row index (senpi#4272): how folded rows are packed into
|
|
3
|
+
* copy-on-write page files, and which base pages are small enough to merge on the next compaction.
|
|
4
|
+
*/
|
|
5
|
+
/** A page under half full in both rows and bytes is rewritten by the next compaction, so pages merge. */
|
|
6
|
+
export declare function isSmallPage(page: {
|
|
7
|
+
readonly keys: readonly string[];
|
|
8
|
+
readonly bytes: number;
|
|
9
|
+
}): boolean;
|
|
10
|
+
/** Packs entries into pages of at most `PAGE_ROWS` rows and `PAGE_BYTES` UTF-8 bytes (one oversized row stands alone). */
|
|
11
|
+
export declare function packPages<Row>(rows: readonly [string, Row][]): [string, Row][][];
|
|
12
|
+
//# sourceMappingURL=compact-index-pages.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page layout of the compact settled-row index (senpi#4272): how folded rows are packed into
|
|
3
|
+
* copy-on-write page files, and which base pages are small enough to merge on the next compaction.
|
|
4
|
+
*/
|
|
5
|
+
const PAGE_ROWS = 128;
|
|
6
|
+
const PAGE_BYTES = 64 * 1024;
|
|
7
|
+
/** A page under half full in both rows and bytes is rewritten by the next compaction, so pages merge. */
|
|
8
|
+
export function isSmallPage(page) {
|
|
9
|
+
return page.keys.length < PAGE_ROWS / 2 && page.bytes < PAGE_BYTES / 2;
|
|
10
|
+
}
|
|
11
|
+
/** Packs entries into pages of at most `PAGE_ROWS` rows and `PAGE_BYTES` UTF-8 bytes (one oversized row stands alone). */
|
|
12
|
+
export function packPages(rows) {
|
|
13
|
+
const pages = [];
|
|
14
|
+
let page = [];
|
|
15
|
+
// UTF-8 bytes of the serialized page: the brackets, plus each entry and its separator.
|
|
16
|
+
let bytes = 1;
|
|
17
|
+
for (const entry of rows) {
|
|
18
|
+
const size = Buffer.byteLength(JSON.stringify(entry)) + 1;
|
|
19
|
+
if (page.length > 0 && (page.length === PAGE_ROWS || bytes + size > PAGE_BYTES)) {
|
|
20
|
+
pages.push(page);
|
|
21
|
+
page = [];
|
|
22
|
+
bytes = 1;
|
|
23
|
+
}
|
|
24
|
+
page.push(entry);
|
|
25
|
+
bytes += size;
|
|
26
|
+
}
|
|
27
|
+
if (page.length > 0)
|
|
28
|
+
pages.push(page);
|
|
29
|
+
return pages;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=compact-index-pages.js.map
|
|
@@ -1,45 +1,15 @@
|
|
|
1
|
+
import { CompactContentIndex, type FrozenIndex } from "./compact-content-index.ts";
|
|
1
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";
|
|
2
4
|
/**
|
|
3
5
|
* The in-memory current-state owner over compact storage (senpi#4272, R3.C6.4 first half).
|
|
4
6
|
*
|
|
5
7
|
* `accept` is synchronous: it validates a typed transition completely, then publishes the next
|
|
6
8
|
* sequence, or throws and leaves the prior root untouched. Block text lives in owned bounded leaves
|
|
7
|
-
* that spill asynchronously; settled
|
|
8
|
-
* changes only through `message_status`, never through storage success or failure.
|
|
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
|
|
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
11
|
* an immutable cut synchronously, and `read` materializes it asynchronously from RAM or disk.
|
|
12
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
13
|
interface FrozenBlock {
|
|
44
14
|
readonly contentIndex: number;
|
|
45
15
|
readonly kind: CompactBlockKind;
|
|
@@ -54,7 +24,7 @@ interface SettledRow {
|
|
|
54
24
|
interface FrozenCompactState {
|
|
55
25
|
readonly sequence: number;
|
|
56
26
|
readonly active: ReadonlyMap<string, readonly FrozenBlock[]>;
|
|
57
|
-
readonly settled:
|
|
27
|
+
readonly settled: FrozenIndex<SettledRow>;
|
|
58
28
|
}
|
|
59
29
|
interface CompactMessageView {
|
|
60
30
|
readonly messageId: string;
|
|
@@ -65,13 +35,17 @@ interface CompactMessageView {
|
|
|
65
35
|
readonly text: string;
|
|
66
36
|
}[];
|
|
67
37
|
}
|
|
38
|
+
export interface CompactSessionStateOptions extends CompactContentStoreOptions {
|
|
39
|
+
/** Index compaction is considered once this many override slots exist (one page by default). */
|
|
40
|
+
readonly compactAboveRows?: number;
|
|
41
|
+
}
|
|
68
42
|
export declare class CompactSessionState {
|
|
69
43
|
private readonly store;
|
|
70
44
|
private readonly settled;
|
|
71
45
|
private readonly active;
|
|
72
46
|
private sequence;
|
|
73
47
|
private frozenTailBytes;
|
|
74
|
-
constructor(options:
|
|
48
|
+
constructor(options: CompactSessionStateOptions);
|
|
75
49
|
get acceptedSequence(): number;
|
|
76
50
|
accept(transition: CompactTransition): number;
|
|
77
51
|
freeze(): FrozenCompactState;
|
|
@@ -81,13 +55,11 @@ export declare class CompactSessionState {
|
|
|
81
55
|
sequence: number;
|
|
82
56
|
messages: CompactMessageView[];
|
|
83
57
|
}>;
|
|
84
|
-
storageStatus(): CompactStorageStatus & {
|
|
58
|
+
storageStatus(): CompactStorageStatus & ReturnType<CompactContentIndex<SettledRow>["status"]> & {
|
|
85
59
|
readonly residentTailBytes: number;
|
|
86
60
|
/** Active tails held by unreleased cuts; may share storage with `residentTailBytes`. */
|
|
87
61
|
readonly frozenTailBytes: number;
|
|
88
62
|
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;
|
|
91
63
|
};
|
|
92
64
|
idle(): Promise<void>;
|
|
93
65
|
retire(): Promise<void>;
|
|
@@ -1,19 +1,15 @@
|
|
|
1
|
+
import { CompactContentIndex } from "./compact-content-index.js";
|
|
1
2
|
import { COMPACT_LEAF_CODE_UNITS, CompactContentStore, } from "./compact-content-store.js";
|
|
2
|
-
|
|
3
|
-
constructor(reason, detail) {
|
|
4
|
-
super(`compact transition rejected (${reason}): ${detail}`);
|
|
5
|
-
this.name = "CompactTransitionError";
|
|
6
|
-
this.reason = reason;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
3
|
+
import { CompactTransitionError, } from "./compact-transition.js";
|
|
9
4
|
const byContentIndex = ([a], [b]) => a - b;
|
|
10
5
|
export class CompactSessionState {
|
|
11
6
|
constructor(options) {
|
|
12
|
-
this.settled = new Map();
|
|
13
7
|
this.active = new Map();
|
|
14
8
|
this.sequence = 0;
|
|
15
9
|
this.frozenTailBytes = 0;
|
|
16
10
|
this.store = new CompactContentStore(options);
|
|
11
|
+
const leavesOf = (row) => row.blocks.flatMap((block) => block.leaves);
|
|
12
|
+
this.settled = new CompactContentIndex(this.store, leavesOf, options.compactAboveRows ?? 128);
|
|
17
13
|
}
|
|
18
14
|
get acceptedSequence() {
|
|
19
15
|
return this.sequence;
|
|
@@ -42,23 +38,19 @@ export class CompactSessionState {
|
|
|
42
38
|
const active = new Map();
|
|
43
39
|
for (const [messageId, blocks] of this.active)
|
|
44
40
|
active.set(messageId, this.seal(blocks, false));
|
|
45
|
-
const settled = new Map(this.settled);
|
|
46
41
|
for (const blocks of active.values())
|
|
47
42
|
this.pin(blocks, 1);
|
|
48
|
-
|
|
49
|
-
this.pin(row.blocks, 1);
|
|
50
|
-
return { sequence: this.sequence, active, settled };
|
|
43
|
+
return { sequence: this.sequence, active, settled: this.settled.freeze() };
|
|
51
44
|
}
|
|
52
45
|
release(frozen) {
|
|
53
46
|
for (const blocks of frozen.active.values())
|
|
54
47
|
this.pin(blocks, -1);
|
|
55
|
-
|
|
56
|
-
this.pin(row.blocks, -1);
|
|
48
|
+
this.settled.release(frozen.settled);
|
|
57
49
|
}
|
|
58
50
|
/** Rejects with the storage error when committed content is gone; never returns empty content for it. */
|
|
59
51
|
async read(frozen) {
|
|
60
52
|
const messages = [];
|
|
61
|
-
for (const [messageId, row] of frozen.settled) {
|
|
53
|
+
for (const [messageId, row] of await this.settled.entries(frozen.settled)) {
|
|
62
54
|
messages.push({ messageId, persistence: row.persistence, blocks: await this.texts(row.blocks) });
|
|
63
55
|
}
|
|
64
56
|
for (const [messageId, blocks] of frozen.active) {
|
|
@@ -77,10 +69,10 @@ export class CompactSessionState {
|
|
|
77
69
|
}
|
|
78
70
|
return {
|
|
79
71
|
...this.store.status(),
|
|
72
|
+
...this.settled.status(),
|
|
80
73
|
residentTailBytes,
|
|
81
74
|
frozenTailBytes: this.frozenTailBytes,
|
|
82
75
|
hotWitnessBytes,
|
|
83
|
-
residentSettledRows: this.settled.size,
|
|
84
76
|
};
|
|
85
77
|
}
|
|
86
78
|
idle() {
|
|
@@ -137,12 +129,13 @@ export class CompactSessionState {
|
|
|
137
129
|
const blocks = this.active.get(messageId);
|
|
138
130
|
if (blocks) {
|
|
139
131
|
this.settled.set(messageId, { persistence, blocks: this.seal(blocks, true) });
|
|
132
|
+
for (const block of blocks.values())
|
|
133
|
+
for (const id of block.leaves)
|
|
134
|
+
this.store.adjust(id, -1, 0);
|
|
140
135
|
this.active.delete(messageId);
|
|
141
|
-
return;
|
|
142
136
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.settled.set(messageId, { ...row, persistence });
|
|
137
|
+
else if (this.settled.has(messageId)) {
|
|
138
|
+
this.settled.patch(messageId, { persistence });
|
|
146
139
|
}
|
|
147
140
|
else {
|
|
148
141
|
throw new CompactTransitionError("unknown_message", messageId);
|
|
@@ -150,17 +143,15 @@ export class CompactSessionState {
|
|
|
150
143
|
}
|
|
151
144
|
remove(messageId) {
|
|
152
145
|
const blocks = this.active.get(messageId);
|
|
153
|
-
const
|
|
154
|
-
if (!blocks && !
|
|
146
|
+
const settled = this.settled.has(messageId);
|
|
147
|
+
if (!blocks && !settled)
|
|
155
148
|
throw new CompactTransitionError("unknown_message", messageId);
|
|
156
149
|
for (const block of blocks?.values() ?? [])
|
|
157
150
|
for (const id of block.leaves)
|
|
158
151
|
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);
|
|
162
152
|
this.active.delete(messageId);
|
|
163
|
-
|
|
153
|
+
if (settled)
|
|
154
|
+
this.settled.delete(messageId);
|
|
164
155
|
}
|
|
165
156
|
block(address) {
|
|
166
157
|
const block = this.active.get(address.messageId)?.get(address.contentIndex);
|
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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,53 @@
|
|
|
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
|
+
constructor(dir: string, io: CompactStorageIo);
|
|
24
|
+
/** Resolves once every queued storage job, including the ones they queued, has completed. */
|
|
25
|
+
idle(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Invalidates spilling, joins owned I/O, then removes the epoch directory only when this store
|
|
28
|
+
* created it and it still passes the ownership checks; otherwise rejects with `CompactSpoolRefusedError`.
|
|
29
|
+
*/
|
|
30
|
+
retire(): Promise<void>;
|
|
31
|
+
/** Reads a committed file and rejects a missing, unreadable or short one. */
|
|
32
|
+
readFile(path: string, bytes: number, operation: CompactStorageOperation): Promise<Buffer>;
|
|
33
|
+
/** Writes one new spool file; undefined means the write failed and was reported (a spill write also halts spilling). */
|
|
34
|
+
writeFile(data: Buffer, operation: CompactStorageOperation, retainedInMemoryBytes: number): Promise<string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* An unreferenced (or partially written) spool file: reclamation debt until its unlink completes or
|
|
37
|
+
* finds it gone. Files dropped after retirement started belong to the directory retirement removes.
|
|
38
|
+
*/
|
|
39
|
+
discard(path: string, bytes: number): void;
|
|
40
|
+
/** A failed unlink keeps its debt and is retried with bounded backoff outside the job chain. */
|
|
41
|
+
private reclaim;
|
|
42
|
+
get spillAllowed(): boolean;
|
|
43
|
+
/** True while a storage failure or unreclaimed debt above the work allowance holds spilling back. */
|
|
44
|
+
protected get spillStopped(): boolean;
|
|
45
|
+
enqueue(operation: CompactStorageOperation, job: () => Promise<void>): void;
|
|
46
|
+
protected abstract scheduleSpill(): void;
|
|
47
|
+
/** Creates the private epoch directory once; a refusal or failure halts spilling and is reported. */
|
|
48
|
+
private ensureDir;
|
|
49
|
+
private readFailure;
|
|
50
|
+
private fail;
|
|
51
|
+
private record;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=compact-spool-storage.d.ts.map
|