@deepseek-ai/dsh-session-persistence-jsonl 0.0.1-rc.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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +77 -0
- package/README.zh.md +77 -0
- package/lib/index.js +1389 -0
- package/lib/invariant.js +23 -0
- package/lib/types/format.d.ts +175 -0
- package/lib/types/index.d.ts +163 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/win32.d.ts +30 -0
- package/lib/types/zstd-private-decoder.d.ts +33 -0
- package/lib/types/zstd-public-decoder.d.ts +15 -0
- package/lib/types/zstd.d.ts +68 -0
- package/package.json +50 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-persistence-jsonl`.
|
|
4
|
+
* @module @deepseek-ai/dsh-session-persistence-jsonl/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-session-persistence-jsonl";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "session-persistence-jsonl-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: persistence correctness requires backend round-trip and crash-tail tests;
|
|
13
|
+
* this package exposes no continuously observable in-process relation.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk format helpers for the JSONL session-persistence backend: path
|
|
3
|
+
* sanitization (a {@link SessionId} is an unvalidated branded string, so it
|
|
4
|
+
* MUST be encoded before use in a path — no traversal, no collision), the
|
|
5
|
+
* per-project/session directory layout, header-line (de)serialization, and the
|
|
6
|
+
* truncation-repair offset computation.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-session-persistence-jsonl/format
|
|
9
|
+
*/
|
|
10
|
+
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session';
|
|
11
|
+
/** Physical encoding selected for JSONL session artifacts. */
|
|
12
|
+
export type JsonlCompression = 'zstd' | 'none';
|
|
13
|
+
/**
|
|
14
|
+
* Return the artifact suffix for one physical encoding.
|
|
15
|
+
* @param compression - configured JSONL artifact encoding.
|
|
16
|
+
* @returns `.jsonl.zstd` for Zstandard or `.jsonl` for plaintext.
|
|
17
|
+
*/
|
|
18
|
+
export declare function logSuffix(compression: JsonlCompression): '.jsonl.zstd' | '.jsonl';
|
|
19
|
+
/**
|
|
20
|
+
* The first JSONL record of a session artifact: the immutable
|
|
21
|
+
* {@link SessionHeader} tagged as a `session` record so a reader can tell it
|
|
22
|
+
* apart from an event line.
|
|
23
|
+
*/
|
|
24
|
+
export interface HeaderLine {
|
|
25
|
+
type: 'session';
|
|
26
|
+
version: number;
|
|
27
|
+
id: SessionId;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
parentSession?: SessionId;
|
|
31
|
+
seedLength?: number;
|
|
32
|
+
origin?: 'subagent';
|
|
33
|
+
delegationDepth: number;
|
|
34
|
+
agentPreset?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the header line object from a {@link SessionHeader}.
|
|
38
|
+
* @param header - the immutable session metadata to serialize.
|
|
39
|
+
* @returns the `type: 'session'`-tagged line object, absent optional fields omitted (never null).
|
|
40
|
+
*/
|
|
41
|
+
export declare function toHeaderLine(header: SessionHeader): HeaderLine;
|
|
42
|
+
/**
|
|
43
|
+
* Parse a header line back into a {@link SessionHeader}.
|
|
44
|
+
* @param line - the shape-checked first line of a log (see the `isHeaderLine` guard).
|
|
45
|
+
* @returns the header, absent optional fields omitted.
|
|
46
|
+
*/
|
|
47
|
+
export declare function fromHeaderLine(line: HeaderLine): SessionHeader;
|
|
48
|
+
/**
|
|
49
|
+
* Encode an arbitrary string as a single safe path segment, injectively over ALL JS (UTF-16)
|
|
50
|
+
* strings — including lone surrogates. A {@link SessionId} is an unvalidated branded string,
|
|
51
|
+
* so this neutralizes `../`, absolute paths, NUL, and separators before any filesystem use.
|
|
52
|
+
* Safe code units remain literal; every other unit, including `~`, becomes
|
|
53
|
+
* `~XXXX`. Operating on code units preserves lone surrogates, while special-
|
|
54
|
+
* casing `.` and `..` prevents traversal by an otherwise safe whole segment.
|
|
55
|
+
*
|
|
56
|
+
* @param raw - the string to encode; must be non-empty (throws on `''`).
|
|
57
|
+
* @returns the escaped single path segment, decodable back to `raw`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function encodeSegment(raw: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Build the readable directory key for a project path.
|
|
62
|
+
* Filesystem separators and drive separators become `-`; unsafe code units use
|
|
63
|
+
* the same `~XXXX` escape as session ids. The key is bounded for filesystem
|
|
64
|
+
* component limits. Separator replacement and truncation are intentionally
|
|
65
|
+
* lossy, following the common human-navigable project-directory convention.
|
|
66
|
+
* @param cwd - the session's project directory.
|
|
67
|
+
* @returns a single filesystem-safe project directory name.
|
|
68
|
+
*/
|
|
69
|
+
export declare function projectKey(cwd: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* The configured root's human-navigable project directory. A configured root
|
|
72
|
+
* may be local or shared; this grouping does not prescribe its deployment.
|
|
73
|
+
* @param root - the backend's session root directory.
|
|
74
|
+
* @param cwd - the session's project directory; `undefined` selects `_no-cwd`.
|
|
75
|
+
* @returns the project directory path under `root`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function projectDir(root: string, cwd: string | undefined): string;
|
|
78
|
+
/**
|
|
79
|
+
* The directory owned by one session and available for future session-local
|
|
80
|
+
* artifacts.
|
|
81
|
+
* @param root - the backend's session root directory.
|
|
82
|
+
* @param cwd - the session's project directory.
|
|
83
|
+
* @param id - the session id, encoded to one safe path segment.
|
|
84
|
+
* @returns the session directory beneath its project directory.
|
|
85
|
+
*/
|
|
86
|
+
export declare function sessionDir(root: string, cwd: string | undefined, id: SessionId): string;
|
|
87
|
+
/**
|
|
88
|
+
* The append-only event-log file path for a session.
|
|
89
|
+
* @param root - the backend's session root directory.
|
|
90
|
+
* @param cwd - the session's project directory (`undefined` → `_no-cwd`).
|
|
91
|
+
* @param id - the session id, path-encoded via {@link encodeSegment} before filesystem use.
|
|
92
|
+
* @param compression - physical artifact encoding and filename suffix.
|
|
93
|
+
* @returns the session's configured JSONL artifact path.
|
|
94
|
+
*/
|
|
95
|
+
export declare function logPath(root: string, cwd: string | undefined, id: SessionId, compression: JsonlCompression): string;
|
|
96
|
+
/**
|
|
97
|
+
* Serialize an event batch as JSONL lines (no trailing newline). With
|
|
98
|
+
* `packChunks` on, delta-chunk runs pack into `text-chunks` /
|
|
99
|
+
* `reasoning-chunks` / `tool-call-chunks` storage rows; off writes one event
|
|
100
|
+
* per line, byte-identical to the pre-packing layout. Reading is layout-blind
|
|
101
|
+
* either way ({@link scanLog} always decodes rows), so the switch changes only
|
|
102
|
+
* newly written bytes.
|
|
103
|
+
* @param events - the batch to serialize, in log order.
|
|
104
|
+
* @param packChunks - whether to pack delta runs into storage rows.
|
|
105
|
+
* @returns the batch's JSONL text; the writer adds the final newline.
|
|
106
|
+
*/
|
|
107
|
+
export declare function eventLines(events: readonly SessionEvent[], packChunks: boolean): string;
|
|
108
|
+
interface SessionLogScan {
|
|
109
|
+
meta: SessionHeader;
|
|
110
|
+
events: SessionEvent[];
|
|
111
|
+
committedBytes: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Incrementally scan complete JSONL event records after an independently
|
|
115
|
+
* supplied header record. Newline search and byte offsets stay on raw buffers;
|
|
116
|
+
* only complete records are decoded to UTF-8. A fragment crossing writes is
|
|
117
|
+
* copied because a decoder may reuse its output buffer after `write()` returns.
|
|
118
|
+
*/
|
|
119
|
+
export declare class SessionLogScanner {
|
|
120
|
+
private readonly meta;
|
|
121
|
+
private readonly events;
|
|
122
|
+
private fragments;
|
|
123
|
+
private fragmentBytes;
|
|
124
|
+
private inputBytes;
|
|
125
|
+
private committedBytes;
|
|
126
|
+
private eventLine;
|
|
127
|
+
private issue;
|
|
128
|
+
private finished;
|
|
129
|
+
/**
|
|
130
|
+
* Create an event scanner from exactly one newline-terminated header record.
|
|
131
|
+
* @param headerRecord - the complete first JSONL record, including its newline.
|
|
132
|
+
*/
|
|
133
|
+
constructor(headerRecord: Buffer);
|
|
134
|
+
/**
|
|
135
|
+
* Consume the next raw plaintext chunk, retaining only an incomplete final record.
|
|
136
|
+
* @param chunk - bytes immediately following all previously supplied bytes.
|
|
137
|
+
*/
|
|
138
|
+
write(chunk: Buffer): void;
|
|
139
|
+
/**
|
|
140
|
+
* Snapshot progress before appending a recoverable torn-frame prefix.
|
|
141
|
+
* @returns byte, committed-prefix, and expanded-event cursors.
|
|
142
|
+
*/
|
|
143
|
+
checkpoint(): {
|
|
144
|
+
inputBytes: number;
|
|
145
|
+
committedBytes: number;
|
|
146
|
+
eventCount: number;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Finish scanning, ignoring a final record without a newline as a torn tail.
|
|
150
|
+
* @returns the header, contiguous event prefix, and safe truncation offset.
|
|
151
|
+
*/
|
|
152
|
+
finish(): SessionLogScan;
|
|
153
|
+
/** Decode one complete event row and update the contiguous prefix. */
|
|
154
|
+
private consumeEventLine;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Parse a complete or torn JSONL buffer into its preserved event prefix. This
|
|
158
|
+
* compatibility wrapper supplies the first record separately, then delegates
|
|
159
|
+
* event rows to {@link SessionLogScanner}.
|
|
160
|
+
*
|
|
161
|
+
* @param buffer - the raw bytes of the log file (header line first).
|
|
162
|
+
* @returns the header, preserved event prefix, and byte offset safe to append at.
|
|
163
|
+
*/
|
|
164
|
+
export declare function scanLog(buffer: Buffer): SessionLogScan;
|
|
165
|
+
/**
|
|
166
|
+
* Parse just the header line of a log into a {@link SessionHeader}, or
|
|
167
|
+
* `undefined` if it is missing/not a header. Used by `list()` to read session
|
|
168
|
+
* metadata WITHOUT parsing the whole log: a session picker scales with the
|
|
169
|
+
* number of sessions, not the total size of every conversation.
|
|
170
|
+
* @param firstLine - the first line of a log file (without its trailing newline).
|
|
171
|
+
* @returns the parsed header, or `undefined` when the line is not a well-formed session header.
|
|
172
|
+
*/
|
|
173
|
+
export declare function parseHeaderMeta(firstLine: string): SessionHeader | undefined;
|
|
174
|
+
export {};
|
|
175
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL durable session-persistence backend. It stores a header and contiguous
|
|
3
|
+
* events in one append-only file per session, and delegates orchestration to
|
|
4
|
+
* {@link PersistenceCoordinator}. Its side-effect-free locator returns the
|
|
5
|
+
* absolute per-session log target before materialization.
|
|
6
|
+
* @module @deepseek-ai/dsh-session-persistence-jsonl
|
|
7
|
+
*/
|
|
8
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import { SessionPersistence, type PersistenceBackend, type SessionLocation, type SessionPersistenceSnapshot, type SessionInspection, type SessionPersistenceRevision as PersistenceRevision, type StoredPrefix } from '@deepseek-ai/dsh-session-persistence';
|
|
11
|
+
import type { SessionEvent, SessionId, SessionHeader, SessionPreparation } from '@deepseek-ai/dsh-session';
|
|
12
|
+
import { type JsonlCompression } from './format.ts';
|
|
13
|
+
export type { JsonlCompression } from './format.ts';
|
|
14
|
+
/** Loader schema for the JSONL artifact's physical encoding. */
|
|
15
|
+
export declare const JsonlCompressionSchema: z<JsonlCompression>;
|
|
16
|
+
/** Plugin config: where the JSONL backend keeps its session logs, and the packed-row write switch. */
|
|
17
|
+
export interface Config {
|
|
18
|
+
/**
|
|
19
|
+
* Root directory for all session files. Required (no default): a default of
|
|
20
|
+
* `process.cwd()` would scatter session files as the process's cwd changes
|
|
21
|
+
* (bash calls, subprocesses). Sessions group under human-readable project
|
|
22
|
+
* directories, then per-session directories. An existing root must be a
|
|
23
|
+
* readable directory; an absent root is created on first materialization.
|
|
24
|
+
*/
|
|
25
|
+
root: string;
|
|
26
|
+
/**
|
|
27
|
+
* Write runs of consecutive `assistant/chunk` delta events as packed
|
|
28
|
+
* `text-chunks`/`reasoning-chunks`/`tool-call-chunks` rows (lossless,
|
|
29
|
+
* ~60% smaller logs measured on a real session). Defaults to true; false
|
|
30
|
+
* keeps one `SessionEvent` per line for diagnostics. Reading packed rows is
|
|
31
|
+
* unconditional: a log's layout never depends on this switch.
|
|
32
|
+
*/
|
|
33
|
+
packChunks?: boolean;
|
|
34
|
+
/** Physical encoding; defaults to checksummed Zstandard frames. */
|
|
35
|
+
compression?: JsonlCompression;
|
|
36
|
+
/** Maximum cold Session preparations retained for history-to-resume reuse. */
|
|
37
|
+
preparedSessionCacheSize?: number;
|
|
38
|
+
/** Fixed live-event coalescing window; not a backend completion deadline. */
|
|
39
|
+
writeBatchMaxDelayMs?: number;
|
|
40
|
+
}
|
|
41
|
+
/** Opaque coordinator token for replacing bytes recovered from a torn frame. */
|
|
42
|
+
interface JsonlTornMarker {
|
|
43
|
+
truncateTo: number;
|
|
44
|
+
recoveredEvents: SessionEvent[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The JSONL persistence backend. Load as a plugin; it registers as
|
|
48
|
+
* `ctx.sessionPersistence` and (via the coordinator) installs the write-path
|
|
49
|
+
* listeners. Its torn-tail marker carries the byte offset and any events
|
|
50
|
+
* recovered from an incomplete final Zstandard frame.
|
|
51
|
+
*/
|
|
52
|
+
export declare class SessionPersistenceJsonl extends SessionPersistence implements PersistenceBackend<JsonlTornMarker> {
|
|
53
|
+
config: Config;
|
|
54
|
+
static inject: string[];
|
|
55
|
+
static Config: z<Config>;
|
|
56
|
+
/**
|
|
57
|
+
* Backend label for coordinator diagnostics and effects. It shadows
|
|
58
|
+
* `Service.name` without changing the service key captured by the base
|
|
59
|
+
* constructor.
|
|
60
|
+
*/
|
|
61
|
+
readonly name = "session-persistence-jsonl";
|
|
62
|
+
private root;
|
|
63
|
+
private packChunks;
|
|
64
|
+
private compression;
|
|
65
|
+
private coordinator;
|
|
66
|
+
private rootEncodingCheck;
|
|
67
|
+
constructor(ctx: Context, config: Config);
|
|
68
|
+
/** Resolve the absolute target path without touching the filesystem. */
|
|
69
|
+
locate(meta: SessionHeader): SessionLocation;
|
|
70
|
+
create(meta: SessionHeader): Promise<void>;
|
|
71
|
+
append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;
|
|
72
|
+
prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>;
|
|
73
|
+
load(id: SessionId): Promise<SessionInspection>;
|
|
74
|
+
inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>;
|
|
75
|
+
readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{
|
|
76
|
+
meta: SessionHeader;
|
|
77
|
+
events: SessionEvent[];
|
|
78
|
+
}>;
|
|
79
|
+
/** Read a stored prefix by id across all project directories when cwd is unknown. */
|
|
80
|
+
loadStored(id: SessionId, signal?: AbortSignal): Promise<StoredPrefix<JsonlTornMarker> | undefined>;
|
|
81
|
+
/**
|
|
82
|
+
* Read one log's stat-derived revision without loading its event bytes.
|
|
83
|
+
* Resolving an id with unknown cwd still scans the project directories.
|
|
84
|
+
*/
|
|
85
|
+
readStoredRevision(id: SessionId, signal?: AbortSignal): Promise<PersistenceRevision | undefined>;
|
|
86
|
+
/**
|
|
87
|
+
* Read a stored prefix and convert torn-tail state to the opaque marker the
|
|
88
|
+
* coordinator can round-trip without knowing the physical encoding.
|
|
89
|
+
*/
|
|
90
|
+
private readPrefix;
|
|
91
|
+
/** Decode complete frames and retain complete JSONL records from a torn final frame. */
|
|
92
|
+
private readZstdPrefix;
|
|
93
|
+
/** Durably append a batch, lazily materializing the file when not yet present. */
|
|
94
|
+
appendBatch(meta: SessionHeader, events: readonly SessionEvent[], isMaterialized: boolean): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Make a crash repair durable: truncate a torn tail, restore complete events
|
|
97
|
+
* decoded from it, then append synthetic closers. Two fsync'd steps — the seam
|
|
98
|
+
* does not require this to be atomic.
|
|
99
|
+
*/
|
|
100
|
+
commitRepair(meta: SessionHeader, tornMarker: JsonlTornMarker | undefined, closers: readonly SessionEvent[]): Promise<void>;
|
|
101
|
+
/** List valid unique stored sessions' metadata (header line only — no full-log parse). */
|
|
102
|
+
list(signal?: AbortSignal): Promise<SessionHeader[]>;
|
|
103
|
+
/** List metadata plus a stat-derived identity for each append-only log. */
|
|
104
|
+
listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>;
|
|
105
|
+
private listArtifacts;
|
|
106
|
+
/** Atomically write the header line + first batch (temp-write, fsync, publish). */
|
|
107
|
+
private materialize;
|
|
108
|
+
private materializePosix;
|
|
109
|
+
private materializeWin32;
|
|
110
|
+
private rejectExistingLog;
|
|
111
|
+
private writeSyncedTempFile;
|
|
112
|
+
/** Encode the header and first batch without combining their frame boundaries. */
|
|
113
|
+
private encodeMaterialization;
|
|
114
|
+
/** Encode one durable append batch in the configured physical representation. */
|
|
115
|
+
private encodeEventBatch;
|
|
116
|
+
/** fsync a POSIX directory so a just-created/renamed entry is crash-durable. */
|
|
117
|
+
private syncDirPosix;
|
|
118
|
+
/**
|
|
119
|
+
* Append and fsync event lines. On a partial write or sync failure, restore the
|
|
120
|
+
* previous size before rethrowing because the unchanged cursor will retry the
|
|
121
|
+
* batch; leaving partial bytes would create duplicate sequence numbers.
|
|
122
|
+
*/
|
|
123
|
+
private appendLines;
|
|
124
|
+
private rollbackAppend;
|
|
125
|
+
/** Truncate the log file to `offset` bytes and fsync (discard the crash tail). */
|
|
126
|
+
private repair;
|
|
127
|
+
/**
|
|
128
|
+
* Read the first newline-terminated line of a file without loading the whole
|
|
129
|
+
* file. Returns undefined if the file is empty or has no complete first line.
|
|
130
|
+
* Reads in bounded chunks so a huge log costs only the header read.
|
|
131
|
+
*/
|
|
132
|
+
private readFirstLine;
|
|
133
|
+
/** Read and validate only the independently compressed header frame. */
|
|
134
|
+
private readFirstZstdLine;
|
|
135
|
+
/** Find the unique physical log for an id across every project directory. */
|
|
136
|
+
private findLog;
|
|
137
|
+
/** Require an existing configured root to be a readable directory. */
|
|
138
|
+
private assertUsableRoot;
|
|
139
|
+
/** Reject metadata that does not identify the selected physical log. */
|
|
140
|
+
private assertStoredIdentity;
|
|
141
|
+
/**
|
|
142
|
+
* Whether two path spellings resolve to the same physical file. This admits
|
|
143
|
+
* case aliases on case-insensitive filesystems without weakening identity
|
|
144
|
+
* checks on case-sensitive stores.
|
|
145
|
+
*/
|
|
146
|
+
private sameFile;
|
|
147
|
+
/** The human-readable project directories under the configured root. */
|
|
148
|
+
private listProjectDirs;
|
|
149
|
+
/** List session-owned directories and reject the obsolete flat-file layout. */
|
|
150
|
+
private listSessionDirs;
|
|
151
|
+
/** Reject a root that already belongs to the other physical encoding. */
|
|
152
|
+
private ensureRootEncoding;
|
|
153
|
+
private checkRootEncoding;
|
|
154
|
+
private rejectLegacyFlatArtifact;
|
|
155
|
+
private rejectOppositeArtifact;
|
|
156
|
+
private oppositeCompression;
|
|
157
|
+
private encodingMismatch;
|
|
158
|
+
private legacyLayout;
|
|
159
|
+
private exists;
|
|
160
|
+
private assertLogParentAllowsAbsence;
|
|
161
|
+
}
|
|
162
|
+
export default SessionPersistenceJsonl;
|
|
163
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-session-persistence-jsonl`.
|
|
3
|
+
* @module @deepseek-ai/dsh-session-persistence-jsonl/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "session-persistence-jsonl-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows durable namespace helpers for the JSONL backend.
|
|
3
|
+
*
|
|
4
|
+
* POSIX publishes a newly-created log by creating a directory entry and then
|
|
5
|
+
* fsyncing the parent directory. Windows does not expose that parent-directory
|
|
6
|
+
* fsync contract through Node, so the Windows path uses the native durable
|
|
7
|
+
* namespace primitive instead: create a staging object in the target directory
|
|
8
|
+
* and publish it with `MoveFileExW(..., MOVEFILE_WRITE_THROUGH)` without
|
|
9
|
+
* replacement or cross-volume copy fallback.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-session-persistence-jsonl/win32
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Publish `existing` at `replacement` with Windows write-through rename
|
|
15
|
+
* semantics. The destination must not already exist; the move must stay within
|
|
16
|
+
* the volume (no copy fallback flag is set).
|
|
17
|
+
* @param existing - the synced staging path to move.
|
|
18
|
+
* @param replacement - the final path, which must not already exist.
|
|
19
|
+
*/
|
|
20
|
+
export declare function publishNewFileWin32(existing: string, replacement: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Create `target` and its missing ancestors with durable Windows namespace
|
|
23
|
+
* publication. Each missing directory is first created as a random staging
|
|
24
|
+
* sibling, then moved to its final name with `MOVEFILE_WRITE_THROUGH`; races
|
|
25
|
+
* with another creator are accepted only after verifying the winner is a
|
|
26
|
+
* directory.
|
|
27
|
+
* @param target - the absolute directory path to create durably when absent.
|
|
28
|
+
*/
|
|
29
|
+
export declare function ensureDurableDirectoryWin32(target: string): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=win32.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-private synchronous Zstandard frame decoder optimization.
|
|
3
|
+
* @module dsh-session-persistence-jsonl/zstd-private-decoder
|
|
4
|
+
*/
|
|
5
|
+
import type { ZstdFrameDecoder, ZstdFrameRange } from './zstd.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Synchronous multi-frame decoder backed by one Node Zstd stream handle. Node
|
|
8
|
+
* exposes synchronous decoding only as a one-shot API, so this adapter uses
|
|
9
|
+
* the stream's private handle contract to reuse its native context and output
|
|
10
|
+
* chunks across frames.
|
|
11
|
+
*/
|
|
12
|
+
export declare class NodePrivateZstdFrameDecoder implements ZstdFrameDecoder {
|
|
13
|
+
private readonly stream;
|
|
14
|
+
private readonly errorKey;
|
|
15
|
+
private readonly output;
|
|
16
|
+
private decoderError?;
|
|
17
|
+
private started;
|
|
18
|
+
private closed;
|
|
19
|
+
private constructor();
|
|
20
|
+
/**
|
|
21
|
+
* Create the optimized decoder when this Node release exposes the expected
|
|
22
|
+
* private stream shape.
|
|
23
|
+
* @returns a shared decoder, or `undefined` when callers must use the public fallback.
|
|
24
|
+
*/
|
|
25
|
+
static create(): NodePrivateZstdFrameDecoder | undefined;
|
|
26
|
+
/** @inheritdoc */
|
|
27
|
+
decode(source: Buffer, frames: readonly ZstdFrameRange[]): Generator<Buffer, void, void>;
|
|
28
|
+
/** Decode one frame; its returned scratch view remains valid until the next call. */
|
|
29
|
+
private decodeFrame;
|
|
30
|
+
/** @inheritdoc */
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=zstd-private-decoder.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public-API synchronous Zstandard frame decoder fallback.
|
|
3
|
+
* @module dsh-session-persistence-jsonl/zstd-public-decoder
|
|
4
|
+
*/
|
|
5
|
+
import type { ZstdFrameDecoder, ZstdFrameRange } from './zstd.ts';
|
|
6
|
+
/** Multi-frame adapter built exclusively from Node's supported one-shot API. */
|
|
7
|
+
export declare class PublicZstdFrameDecoder implements ZstdFrameDecoder {
|
|
8
|
+
private started;
|
|
9
|
+
private closed;
|
|
10
|
+
/** @inheritdoc */
|
|
11
|
+
decode(source: Buffer, frames: readonly ZstdFrameRange[]): Generator<Buffer, void, void>;
|
|
12
|
+
/** @inheritdoc */
|
|
13
|
+
close(): void;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=zstd-public-decoder.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zstandard frame primitives for the JSONL persistence backend. The backend
|
|
3
|
+
* owns a concatenated-frame container so it can append and recover batches
|
|
4
|
+
* without exposing compression mechanics through the persistence seam.
|
|
5
|
+
* @module dsh-session-persistence-jsonl/zstd
|
|
6
|
+
*/
|
|
7
|
+
/** Byte range occupied by one structurally complete Zstandard frame. */
|
|
8
|
+
export interface ZstdFrameRange {
|
|
9
|
+
/** Inclusive frame start. */
|
|
10
|
+
start: number;
|
|
11
|
+
/** Exclusive frame end. */
|
|
12
|
+
end: number;
|
|
13
|
+
}
|
|
14
|
+
/** Structural scan result for a concatenated Zstandard stream. */
|
|
15
|
+
export interface ZstdFrameScan {
|
|
16
|
+
/** Complete frames in file order. */
|
|
17
|
+
frames: ZstdFrameRange[];
|
|
18
|
+
/** Start of an incomplete final frame, when EOF interrupts one. */
|
|
19
|
+
tornStart?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Locate complete frames without decompressing their blocks. Invalid complete
|
|
23
|
+
* structure rejects; EOF inside the final frame returns its start for repair.
|
|
24
|
+
* @param buffer - complete bytes currently present in the session artifact.
|
|
25
|
+
* @param maxFrames - optional complete-frame limit for metadata-only readers.
|
|
26
|
+
* @returns complete frame ranges and an optional incomplete-final-frame start.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scanZstdFrames(buffer: Buffer, maxFrames?: number): ZstdFrameScan;
|
|
29
|
+
/**
|
|
30
|
+
* Compress one independently decodable, checksummed Zstandard frame.
|
|
31
|
+
* @param input - JSONL bytes for a header or durable event batch.
|
|
32
|
+
* @returns the complete encoded frame.
|
|
33
|
+
*/
|
|
34
|
+
export declare function compressZstdFrame(input: Buffer | string): Promise<Buffer>;
|
|
35
|
+
/**
|
|
36
|
+
* Decompress one complete frame and validate its checksum.
|
|
37
|
+
* @param input - one structurally complete Zstandard frame.
|
|
38
|
+
* @returns the frame plaintext.
|
|
39
|
+
*/
|
|
40
|
+
export declare function decompressZstdFrame(input: Buffer): Promise<Buffer>;
|
|
41
|
+
/** Common lifecycle for interchangeable synchronous multi-frame decoders. */
|
|
42
|
+
export interface ZstdFrameDecoder {
|
|
43
|
+
/**
|
|
44
|
+
* Decode and checksum complete frames in source order. Each yielded buffer
|
|
45
|
+
* remains valid only until the iterator advances to the next frame.
|
|
46
|
+
* @param source - concatenated Zstandard frame bytes.
|
|
47
|
+
* @param frames - structurally complete ranges within `source`.
|
|
48
|
+
* @returns one plaintext buffer per frame.
|
|
49
|
+
*/
|
|
50
|
+
decode(source: Buffer, frames: readonly ZstdFrameRange[]): Generator<Buffer, void, void>;
|
|
51
|
+
/** Release decoder-owned resources; repeated calls are harmless. */
|
|
52
|
+
close(): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Select the shared private decoder when the running Node 22/24/26 shape is
|
|
56
|
+
* compatible, otherwise preserve correctness with the public one-shot API.
|
|
57
|
+
* @returns a synchronous decoder with an implementation-independent lifecycle.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createZstdFrameDecoder(): ZstdFrameDecoder;
|
|
60
|
+
/**
|
|
61
|
+
* Recover available plaintext from a structurally incomplete final frame.
|
|
62
|
+
* `ZSTD_e_flush` deliberately suppresses final-frame and checksum completion;
|
|
63
|
+
* callers must establish the torn frame boundary before using this helper.
|
|
64
|
+
* @param input - available bytes from a known incomplete Zstandard frame.
|
|
65
|
+
* @returns plaintext produced from the available input.
|
|
66
|
+
*/
|
|
67
|
+
export declare function decompressZstdPrefix(input: Buffer): Promise<Buffer>;
|
|
68
|
+
//# sourceMappingURL=zstd.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-session-persistence-jsonl",
|
|
3
|
+
"description": "JSONL durable session persistence backend for the DeepSeek Harness",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/session/session-persistence-jsonl"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"koffi": "^3.1.0",
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
49
|
+
}
|
|
50
|
+
}
|