@deepseek-ai/dsh-session-persistence-jsonl 0.1.3-alpha.2 → 0.1.5-alpha.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/README.i18n.yaml +2 -2
- package/README.md +7 -4
- package/README.zh.md +7 -4
- package/lib/index.js +14 -16
- package/lib/types/format.d.ts +3 -3
- package/lib/types/lease.d.ts +2 -2
- package/lib/worker.cjs +10436 -17
- package/package.json +13 -13
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 194ea835f87e4c34e0821083246bcf89c0e0a684
|
|
6
|
+
README.zh.md: 4512d67e3439cbb4cf8642ca67be56bf0c7ac5b1
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
`dsh-session-persistence-jsonl` stores each session in a current append-only JSONL log and retains immutable historical format generations — checksummed Zstandard frames by default, raw newline-delimited lines when compression is disabled. It serves the current logical `SessionEvent` stream through persistence handles, so format migration, compression, historical decoding, and crash recovery remain storage-internal details. Choose it when consumers need a per-session file on disk; the logs are readable as plain lines when `compression: 'none'` is selected. A root directory is the one required configuration; durability, lazy materialization,
|
|
12
|
+
`dsh-session-persistence-jsonl` stores each session in a current append-only JSONL log and retains immutable historical format generations — checksummed Zstandard frames by default, raw newline-delimited lines when compression is disabled. It serves the current logical `SessionEvent` stream through persistence handles, so format migration, compression, historical decoding, and crash recovery remain storage-internal details. Choose it when consumers need a per-session file on disk; the logs are readable as plain lines when `compression: 'none'` is selected. A root directory is the one required configuration; durability, lazy materialization, [supported historical-format migration](../session-format-catalog/README.md), and torn-tail crash recovery come with the backend.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -53,7 +53,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
|
|
53
53
|
|
|
54
54
|
### On-disk layout
|
|
55
55
|
|
|
56
|
-
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename.
|
|
56
|
+
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless provenance representation described below:
|
|
57
57
|
|
|
58
58
|
```text
|
|
59
59
|
<root>/
|
|
@@ -73,6 +73,8 @@ Session ids are injectively escaped to one safe path segment before use (no trav
|
|
|
73
73
|
|
|
74
74
|
A session is materialized lazily: `create(header)` writes nothing and returns the owned write handle, and the handle's first `append` writes and `fsync`s the encoded header and first batch through a no-overwrite publish — so a created-but-never-appended session leaves nothing on disk unless its owner calls `handle.flush()`, which publishes one header frame without an event. Each subsequent batch appends lines or one compressed frame and `fsync`s before the append resolves; a caught write or sync failure rolls the file back to its prior length. Committed events are never rewritten. After a crash, the stored log keeps its interrupted final turn — every record in the committed prefix survives, and the resuming reader appends synthetic closers through its write handle. An incomplete final raw line is discarded. A torn final Zstandard frame contributes only its complete decoded JSONL records; a write handle truncates the torn bytes and durably rewrites those recovered records before its first new batch. Checksum, decompression, or structural failure in a complete committed frame rejects as corruption.
|
|
75
75
|
|
|
76
|
+
The current-generation scanner applies the current codec owner’s structural admission checks before recoverable-tail handling. Retired required PTC tags and `request/header.header.system` refuse the file even after an earlier malformed row; recovery never truncates them as ordinary damaged tail data.
|
|
77
|
+
|
|
76
78
|
### Reading the logs
|
|
77
79
|
|
|
78
80
|
`open(id, 'read'|'write')` selects the highest canonical generation. Current input follows the ordinary fast path. For historical input, a read open decodes and migrates the source once, validates the current logical result, and returns it without publishing a successor. A write open reuses that revision-keyed preparation when available, or performs the same preparation, then encodes a same-directory temporary file in bounded chunks, verifies it in a Worker Thread, rechecks the source revision, and publishes the current successor without overwrite before returning. The source remains byte-identical. Source drift after preparation rejects that write open without replacing the logical history already returned to readers; a later write open prepares the new revision. The backend marks decoded event graphs `shared-frozen` when it freezes them before memoization; handle reads and slices preserve that state, including empty slices. Only an unmaterialized pending log reports `detached`. `stat(id)` and `list()` select and translate only the highest generation header without reading event rows or starting migration; snapshots carry `sizeBytes` and a best-effort stat-derived revision for the selected file. With `compression: 'none'`, the log is newline-delimited text an external reader can consume directly; the compressed default must be read through the backend.
|
|
@@ -93,7 +95,7 @@ The backend owns its complete storage runtime (`src/storage.ts`): `JsonlSessionH
|
|
|
93
95
|
|
|
94
96
|
### Physical encoding
|
|
95
97
|
|
|
96
|
-
The default artifact is a standard concatenation of independent [Zstandard frames](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md): one checksummed frame containing only the header line, then one checksummed frame per durable append batch, using Node's built-in Zstandard API at its default compression level (no level knob).
|
|
98
|
+
The default artifact is a standard concatenation of independent [Zstandard frames](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md): one checksummed frame containing only the header line, then one checksummed frame per durable append batch, using Node's built-in Zstandard API at its default compression level (no level knob). The current format writes one event per row; `sourceEventSeqs` uses a lossless storage representation in which consecutive runs of at least three sequence numbers become `[start, end]` pairs, any other list stays verbatim, and reading expands the exact in-memory array. Historical migration reuses one Zstandard decoder, passes parsed rows through stateful format stages, and streams current records through one compression context in about 1 MiB main-thread slices while retaining only final current events, bounded decoder state, and the required sequence-remap table. Listing reads and validates only the header frame. `compression: 'none'` keeps the same storage-form logical lines without frame compression. A root belongs to one encoding: startup discovery and targeted lookup reject generations with the other suffix; format migration preserves the configured encoding, while compression conversion, mixed-root fallback, and dual write remain unsupported. Frozen v0 and v1 codecs retain their packed-row decoders solely for historical generations.
|
|
97
99
|
|
|
98
100
|
### Source map
|
|
99
101
|
|
|
@@ -149,12 +151,13 @@ JSONL storage does not mutate live request prefixes. A resumed loop can reuse pr
|
|
|
149
151
|
|
|
150
152
|
These limits define when this backend is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
|
|
151
153
|
|
|
152
|
-
- **Format migration preserves the configured encoding and supports only the catalogued chain** — this build migrates
|
|
154
|
+
- **Format migration preserves the configured encoding and supports only the catalogued chain** — this build migrates supported historical generations to the current format; changing compression requires a separate root, and retained predecessors do not provide automatic fallback or downgrade support.
|
|
153
155
|
- **The flat-file storage layout does not load** — use a separate root or move pre-release artifacts into the project/session directory layout before loading.
|
|
154
156
|
- **Compressed files are not directly line-readable** — use the backend to load them, or select `compression: 'none'` before writing a fresh root when external line readers are required.
|
|
155
157
|
- **Nothing deletes session files** — logs accumulate under `root` until removed externally; the seam has no deletion API.
|
|
156
158
|
- **One live writer per session** — the write-handle claim excludes a second writer inside the owning backend instance, and a kernel lock (non-blocking `flock(2)` on `session.lock`; on Windows a named kernel semaphore derived from that path, with no filesystem footprint) excludes every other instance and process; the lock is taken at write-open of an existing artifact and, for a created session, only right before its first materializing write, so an unmaterialized session leaves no filesystem footprint. A crashed holder's lock dies with its process, so its session is writable again immediately, while a live-but-wedged holder blocks writers until its process exits (on POSIX, removing the lock file forfeits that exclusion; release itself never removes it). Advisory `flock` is unreliable on some network filesystems (NFSv3), and the Windows semaphore name is per login session.
|
|
157
159
|
- **POSIX materialization requires hard-link support** — first append uses `link()` so same-id races fail instead of overwriting a committed log; Windows uses write-through rename without replacement.
|
|
160
|
+
- **POSIX writes require the matching prebuilt system addon** — [`node-addon-system`](../../../native/system/README.md) supplies asynchronous flock without consumer-side compilation. A missing addon rejects write ownership; Windows retains its semaphore implementation.
|
|
158
161
|
|
|
159
162
|
<a id="dev-note"></a>
|
|
160
163
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
`dsh-session-persistence-jsonl` 把每个会话存为当前的仅追加 JSONL 日志,并保留不可变的历史格式 generation——默认以带校验和的 Zstandard 帧存储,禁用压缩时以换行分隔的原始文本行存储。它通过持久化句柄提供当前逻辑 `SessionEvent` 流,因此格式迁移、压缩、历史解码与崩溃恢复仍是存储内部细节。当消费方需要按会话的磁盘文件时选择它;选择 `compression: 'none'`
|
|
12
|
+
`dsh-session-persistence-jsonl` 把每个会话存为当前的仅追加 JSONL 日志,并保留不可变的历史格式 generation——默认以带校验和的 Zstandard 帧存储,禁用压缩时以换行分隔的原始文本行存储。它通过持久化句柄提供当前逻辑 `SessionEvent` 流,因此格式迁移、压缩、历史解码与崩溃恢复仍是存储内部细节。当消费方需要按会话的磁盘文件时选择它;选择 `compression: 'none'` 后日志可作为纯文本按行读取。根目录是唯一必填配置;持久性、延迟实体化、[受支持的历史格式迁移](../session-format-catalog/README.zh.md)与撕裂尾部崩溃恢复都随后端提供。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -53,7 +53,7 @@ kind: "package-reference"
|
|
|
53
53
|
|
|
54
54
|
### 磁盘布局
|
|
55
55
|
|
|
56
|
-
每个会话在可读项目目录下获得一个会话自有目录。每个规范 generation 都以版本与文件名一致的物理 header
|
|
56
|
+
每个会话在可读项目目录下获得一个会话自有目录。每个规范 generation 都以版本与文件名一致的物理 header 开始。当前格式为每个持久事件存储一行;冻结的 v0 与 v1 reader 也能理解其历史 packed Assistant delta 行。当前格式在 header 中存储 `isSeeded`,并从最后一个带标记的 `session/end-seed` 推导 inherited cut;历史 codec 则转换其数字 `seedLength`。格式 catalog 会在句柄暴露当前逻辑值之前完成该转换。当前存储记录使用下文所述的无损来源序列表示:
|
|
57
57
|
|
|
58
58
|
```text
|
|
59
59
|
<root>/
|
|
@@ -73,6 +73,8 @@ kind: "package-reference"
|
|
|
73
73
|
|
|
74
74
|
会话延迟实体化:`create(header)` 不写入任何内容并返回持有的写句柄,句柄的第一次 `append` 通过无覆盖发布写入并 `fsync` 编码后的 header 与第一批——因此已创建但从未 append 的会话不留下任何磁盘内容,除非其所有者调用 `handle.flush()`,以无事件的单个 header 帧发布它。后续每个批次追加行或一个压缩帧,并在 append 完成前 `fsync`;捕获到写入或同步失败时把文件回滚到之前的字节长度。已提交事件绝不重写。崩溃后,已存储日志保留被中断的最终轮次——已提交前缀中的每条记录都保留下来,由执行恢复的读方通过其写句柄追加合成 closer。不完整的最终原始行会被丢弃。撕裂的最终 Zstandard 帧只贡献其中完整解码出的 JSONL 记录;写句柄会截掉撕裂字节,并在第一次新批次之前持久重写这些恢复出的记录。完整已提交帧中的校验和、解压或结构失败以损坏拒绝。
|
|
75
75
|
|
|
76
|
+
当前代际扫描器在处理可恢复尾部之前,执行当前编解码器所有者的结构准入检查。已退役的必需 PTC 标签与 `request/header.header.system` 即使出现在较早的畸形行之后也会导致文件被拒绝;恢复绝不将它们作为普通损坏尾部数据截断。
|
|
77
|
+
|
|
76
78
|
### 读取日志
|
|
77
79
|
|
|
78
80
|
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation。Backend 在 memo 化前冻结已解码的 event graph,并在此时将其标记为 `shared-frozen`;句柄读取和 slice 即使为空也保留该状态。只有尚未实体化的 pending 空日志报告 `detached`。`stat(id)` 与 `list()` 只选择并转换最高 generation 的 header,不读取事件行,也不启动迁移;快照携带所选文件的 `sizeBytes` 与尽力而为的 stat 派生修订号。选择 `compression: 'none'` 后,日志是外部读取方可直接消费的换行分隔文本;压缩默认值必须经后端读取。
|
|
@@ -93,7 +95,7 @@ kind: "package-reference"
|
|
|
93
95
|
|
|
94
96
|
### 物理编码
|
|
95
97
|
|
|
96
|
-
默认产物是独立 [Zstandard 帧](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md) 的标准拼接:一个仅包含 header 行的带校验和帧,后跟每个持久 append 批次一个带校验和帧,使用 Node 内置 Zstandard API
|
|
98
|
+
默认产物是独立 [Zstandard 帧](../../../.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md) 的标准拼接:一个仅包含 header 行的带校验和帧,后跟每个持久 append 批次一个带校验和帧,使用 Node 内置 Zstandard API 的默认压缩级别(无级别开关)。当前格式为每个事件写一行;`sourceEventSeqs` 使用无损存储形式:至少包含三个序列号的连续段会变成 `[start, end]` 区间对,其他列表原样保留;读取时会展开回精确的内存数组。历史迁移会复用一个 Zstandard decoder,让已解析行流经有状态格式 Stage,并通过一个压缩 context 以约 1 MiB 主线程分片流式写入当前记录,同时只保留最终当前事件、有界 decoder 状态与必需的序号重映射表。列表只读取并验证 header 帧。`compression: 'none'` 保留相同的存储形式逻辑行,但不使用帧压缩。一个根只属于一种编码:启动发现与定向查找会拒绝使用另一后缀的 generation;格式迁移保留已配置编码,而压缩转换、混合根回退与双写仍不受支持。冻结的 v0 与 v1 codec 仅为历史 generation 保留 packed-row decoder。
|
|
97
99
|
|
|
98
100
|
### 源码地图
|
|
99
101
|
|
|
@@ -149,12 +151,13 @@ JSONL 存储不修改实时请求前缀。只有重建历史、当前 envelope
|
|
|
149
151
|
|
|
150
152
|
这些限制说明本后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
151
153
|
|
|
152
|
-
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build
|
|
154
|
+
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build 把受支持的历史代迁移到当前格式;更改压缩需要独立根,保留的前任不提供自动 fallback 或 downgrade 支持。
|
|
153
155
|
- **平铺文件存储布局不加载**——加载前使用独立根,或将预发布产物移入项目/会话目录布局。
|
|
154
156
|
- **压缩文件不能直接按行读取**——使用后端加载;或在写入新根前选择 `compression: 'none'`,供外部行读取方使用。
|
|
155
157
|
- **不删除会话文件**——日志在 `root` 下累积,直到外部移除;seam 无删除接口。
|
|
156
158
|
- **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows 上为由该路径派生的命名内核信号量,零文件系统足迹)排除其他所有实例与进程;锁在写打开既有工件时立即获取,新建会话则仅在首次物化写入之前获取,因此未物化的会话不留任何文件系统足迹。崩溃持有者的锁随其进程消亡,会话立即可再写入,而活着但卡死的持有者会阻塞写入方直到其进程退出(POSIX 上删除锁文件即放弃该排他;释放本身从不删除它)。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠,Windows 信号量名按登录会话隔离。
|
|
157
159
|
- **POSIX 实体化需要硬链接支持**——第一次 append 使用 `link()`,使同 id 竞态失败而不覆盖已提交日志;Windows 使用无替换 write-through rename。
|
|
160
|
+
- **POSIX 写入需要匹配的预编译系统 addon**——[`node-addon-system`](../../../native/system/README.zh.md) 提供异步 flock,无须在用户侧编译。addon 缺失时拒绝写入所有权;Windows 保留其信号量实现。
|
|
158
161
|
|
|
159
162
|
<a id="dev-note"></a>
|
|
160
163
|
### 开发备注
|
package/lib/index.js
CHANGED
|
@@ -8,9 +8,10 @@ import { scheduler } from "node:timers/promises";
|
|
|
8
8
|
import { createHash, randomBytes } from "node:crypto";
|
|
9
9
|
import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistence, SessionPersistenceCorruptionError, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertStoredId, materializeAppendBatch, materializeCreateHeader, sessionFormatVersionRefusal, validateStoredEvents } from "@deepseek-ai/dsh-session-persistence";
|
|
10
10
|
import { BlockAssembler, errorChain, expandAssistantStream } from "@deepseek-ai/dsh-llm";
|
|
11
|
-
import {
|
|
11
|
+
import { tryLockExclusive } from "@deepseek-ai/node-addon-system/flock";
|
|
12
12
|
import { SESSION_FORMAT_VERSION, Session, SessionId, SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
13
|
-
import { parseSessionFormatLogFilename, sessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
|
|
13
|
+
import { SessionFormatUnsupportedMigrationError as SessionFormatUnsupportedMigrationError$1, parseSessionFormatLogFilename, sessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
|
|
14
|
+
import { assertV3RowAdmission } from "@deepseek-ai/dsh-session-format-v2-to-v3";
|
|
14
15
|
import { constants, createZstdCompress, createZstdDecompress, zstdCompress, zstdDecompress, zstdDecompressSync } from "node:zlib";
|
|
15
16
|
import { isDeepStrictEqual, promisify } from "node:util";
|
|
16
17
|
import { constants as constants$1 } from "node:buffer";
|
|
@@ -611,7 +612,7 @@ async function createLeafDirectoryWin32(parent, target) {
|
|
|
611
612
|
/**
|
|
612
613
|
* Cross-process write-ownership lock for one session's artifact directory,
|
|
613
614
|
* held for the whole life of a write handle. The arbiter is the kernel:
|
|
614
|
-
* POSIX takes a non-blocking `flock(2)`
|
|
615
|
+
* POSIX takes a non-blocking `flock(2)` via native system support on `session.lock`
|
|
615
616
|
* beside the log, and Windows holds a named kernel semaphore derived from
|
|
616
617
|
* that path — never a file lock or handle, so readers, searches, and
|
|
617
618
|
* directory removal proceed freely while the lock is held. Contention maps
|
|
@@ -632,22 +633,13 @@ async function createLeafDirectoryWin32(parent, target) {
|
|
|
632
633
|
* unmaterialized session has no filesystem footprint. Release never removes
|
|
633
634
|
* the POSIX lock file: every acquired lock belongs to a materialized or
|
|
634
635
|
* materializing session, and the surviving file keeps the stable inode later
|
|
635
|
-
* lockers verify against. The browser worker
|
|
636
|
+
* lockers verify against. The browser worker stubs the native flock entry to
|
|
636
637
|
* immediate success: it is single-process, so the in-process write claim
|
|
637
638
|
* already excludes every writer.
|
|
638
639
|
* @module @deepseek-ai/dsh-session-persistence-jsonl/lease
|
|
639
640
|
*/
|
|
640
641
|
/** Base name of the kernel lock file inside a session's directory. */
|
|
641
642
|
const LEASE_FILENAME = "session.lock";
|
|
642
|
-
/** Promise face over fs-ext's callback flock, pinned to its string-flag overload. */
|
|
643
|
-
function flockAsync(fd, flags) {
|
|
644
|
-
return new Promise((resolve, reject) => {
|
|
645
|
-
flock(fd, flags, (error) => {
|
|
646
|
-
if (error) reject(error);
|
|
647
|
-
else resolve();
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
643
|
/** Whether a flock failure means another descriptor holds the lock. */
|
|
652
644
|
function isLockContention(error) {
|
|
653
645
|
const code = error?.code;
|
|
@@ -695,7 +687,7 @@ var SessionWriteLease = class SessionWriteLease {
|
|
|
695
687
|
const handle = await open(path, "w");
|
|
696
688
|
try {
|
|
697
689
|
try {
|
|
698
|
-
await
|
|
690
|
+
await tryLockExclusive(handle.fd);
|
|
699
691
|
} catch (error) {
|
|
700
692
|
if (isLockContention(error)) throw new SessionAlreadyOwnedError(id);
|
|
701
693
|
throw error;
|
|
@@ -945,7 +937,7 @@ function logPath(root, cwd, id, compression) {
|
|
|
945
937
|
return generationLogPath(root, cwd, id, SESSION_FORMAT_VERSION, compression);
|
|
946
938
|
}
|
|
947
939
|
/**
|
|
948
|
-
* Serialize a
|
|
940
|
+
* Serialize a current event batch as JSONL lines (no trailing newline). Compact
|
|
949
941
|
* Assistant streams are nested event data; every event occupies one row.
|
|
950
942
|
* @param events - the batch to serialize, in log order.
|
|
951
943
|
* @returns the batch's JSONL text; the writer adds the final newline.
|
|
@@ -954,7 +946,7 @@ function eventLines(events) {
|
|
|
954
946
|
return events.map(eventLine).join("\n");
|
|
955
947
|
}
|
|
956
948
|
/**
|
|
957
|
-
* Serialize one
|
|
949
|
+
* Serialize one current event as one JSONL record without its trailing newline.
|
|
958
950
|
* @param event - current event to encode.
|
|
959
951
|
* @returns one physical JSON record.
|
|
960
952
|
*/
|
|
@@ -1095,6 +1087,12 @@ var SessionLogScanner = class {
|
|
|
1095
1087
|
this.issue ??= issue;
|
|
1096
1088
|
return;
|
|
1097
1089
|
}
|
|
1090
|
+
try {
|
|
1091
|
+
assertV3RowAdmission(decoded);
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
if (error instanceof SessionFormatUnsupportedMigrationError$1) throw new SessionFormatUnsupportedError(error.message);
|
|
1094
|
+
throw error;
|
|
1095
|
+
}
|
|
1098
1096
|
if (this.issue !== void 0) {
|
|
1099
1097
|
if (typeof decoded === "object" && decoded !== null && decoded.type === "turn/end") throw this.issue;
|
|
1100
1098
|
return;
|
package/lib/types/format.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare function generationLogFilename(version: number, compression: Json
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function parseGenerationLogFilename(filename: string, compression: JsonlCompression): number | undefined;
|
|
38
38
|
/**
|
|
39
|
-
* The current
|
|
39
|
+
* The current physical header stored as the first JSONL record. The exact
|
|
40
40
|
* inherited cut lives on the last tagged `session/end-seed` event.
|
|
41
41
|
*/
|
|
42
42
|
interface HeaderLine {
|
|
@@ -124,14 +124,14 @@ export declare function generationLogPath(root: string, cwd: string | undefined,
|
|
|
124
124
|
*/
|
|
125
125
|
export declare function logPath(root: string, cwd: string | undefined, id: SessionId, compression: JsonlCompression): string;
|
|
126
126
|
/**
|
|
127
|
-
* Serialize a
|
|
127
|
+
* Serialize a current event batch as JSONL lines (no trailing newline). Compact
|
|
128
128
|
* Assistant streams are nested event data; every event occupies one row.
|
|
129
129
|
* @param events - the batch to serialize, in log order.
|
|
130
130
|
* @returns the batch's JSONL text; the writer adds the final newline.
|
|
131
131
|
*/
|
|
132
132
|
export declare function eventLines(events: readonly SessionEvent[]): string;
|
|
133
133
|
/**
|
|
134
|
-
* Serialize one
|
|
134
|
+
* Serialize one current event as one JSONL record without its trailing newline.
|
|
135
135
|
* @param event - current event to encode.
|
|
136
136
|
* @returns one physical JSON record.
|
|
137
137
|
*/
|
package/lib/types/lease.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-process write-ownership lock for one session's artifact directory,
|
|
3
3
|
* held for the whole life of a write handle. The arbiter is the kernel:
|
|
4
|
-
* POSIX takes a non-blocking `flock(2)`
|
|
4
|
+
* POSIX takes a non-blocking `flock(2)` via native system support on `session.lock`
|
|
5
5
|
* beside the log, and Windows holds a named kernel semaphore derived from
|
|
6
6
|
* that path — never a file lock or handle, so readers, searches, and
|
|
7
7
|
* directory removal proceed freely while the lock is held. Contention maps
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* unmaterialized session has no filesystem footprint. Release never removes
|
|
23
23
|
* the POSIX lock file: every acquired lock belongs to a materialized or
|
|
24
24
|
* materializing session, and the surviving file keeps the stable inode later
|
|
25
|
-
* lockers verify against. The browser worker
|
|
25
|
+
* lockers verify against. The browser worker stubs the native flock entry to
|
|
26
26
|
* immediate success: it is single-process, so the in-process write claim
|
|
27
27
|
* already excludes every writer.
|
|
28
28
|
* @module @deepseek-ai/dsh-session-persistence-jsonl/lease
|