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