@deepseek-ai/dsh-session-persistence-jsonl 0.0.1-rc.1 → 0.0.1-rc.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 +2 -2
- package/README.zh.md +1 -1
- package/lib/index.js +98 -26
- package/lib/types/index.d.ts +25 -1
- package/package.json +8 -8
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: 4cff3215cdb083d2fdb7c4a8f1b60e8c4028ba84
|
|
6
|
+
README.zh.md: 7e3ba5be4f2707ff6408d296ece1f43550d76286
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ A root belongs to one encoding. Startup discovery and targeted lookup reject the
|
|
|
42
42
|
- **Bound storage identity.** Lookup requires one matching session directory across the readable project directories, then verifies that the header id equals the requested id and that the header's id/cwd derive the selected transcript path. Listing applies the same path check and rejects duplicate ids. Identity failures occur before repair or append.
|
|
43
43
|
- **Lazy materialization.** `create(meta)` writes nothing; on the first `append`, the backend writes and `fsync`s the encoded header and first batch in a temporary file. POSIX publishes it without overwrite via a hard link and `fsync`s the parent directory. Windows publishes it without overwrite via `MoveFileExW(..., MOVEFILE_WRITE_THROUGH)` and creates missing directories through the same write-through pattern. A created-but-never-appended session leaves nothing on disk and is absent from `list`.
|
|
44
44
|
- **Append-only.** Flushed events are never rewritten. Subsequent raw batches append lines; compressed batches append one frame. Both paths `fsync`, and a caught write or sync failure rolls the file back to its prior byte length.
|
|
45
|
-
- **Crash recovery — preserve valid tail work.** `load` validates every complete compressed frame and scans their decompressed JSONL. If the last frame is structurally incomplete, the reader keeps its complete decoded records, truncates from that frame's start, and re-encodes those records with the synthetic tool, step, and turn closers required by the shared [persistence contract](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). Raw mode truncates from its first incomplete line.
|
|
45
|
+
- **Crash recovery — preserve valid tail work.** `load` validates every complete compressed frame and scans their decompressed JSONL. If the last frame is structurally incomplete, the reader keeps its complete decoded records, truncates from that frame's start, and re-encodes those records with the synthetic tool, step, and turn closers required by the shared [persistence contract](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). Raw mode truncates from its first incomplete line. An existing compressed artifact with no complete header frame, a checksum/decompression failure in a complete frame, or a defect at or before the last committed `turn/end` is corruption and rejects.
|
|
46
46
|
- **Non-mutating inspection.** `inspect()` returns an immutable balanced logical view and may synthesize recovery closers in memory, without truncating an incomplete tail or changing the lightweight revision.
|
|
47
47
|
- **Contiguous-seq.** `append` rejects a batch whose first `seq` does not continue the stored log, and rejects non-JSON-serializable `event.data` naming the offending event type.
|
|
48
48
|
- **Lightweight revisions.** `listSnapshots(signal?)` identifies a log by its device, inode, size, and nanosecond timestamps, avoiding a full-log parse while changing after append, repair, replacement, or store changes. A full-prefix read requires the same identity before and after reading the bytes, and `readStoredRevision()` uses that identity to validate retained preparations without loading the log. Snapshot listing forwards the exact signal through artifact discovery and checks cancellation around every `stat`; because filesystem `stat` is not interruptible, cancellation waits for the active call to settle, then rejects without starting another.
|
|
@@ -72,6 +72,6 @@ JSONL storage does not mutate live request prefixes. A resumed loop can reuse pr
|
|
|
72
72
|
- **Only the configured encoding and current `SESSION_FORMAT_VERSION` (v0) load** — changing compression requires a separate/fresh root or selecting the legacy raw mode; the pre-release format has no migration.
|
|
73
73
|
- **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.
|
|
74
74
|
- **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.
|
|
75
|
-
- **Nothing deletes session files** — logs accumulate under `root` until removed externally (the seam has no deletion
|
|
75
|
+
- **Nothing deletes session files** — logs accumulate under `root` until removed externally (the seam has no deletion API).
|
|
76
76
|
- **One live writer per session** — append and repair are coordinated only inside the owning backend instance. Another backend instance or process must not write the same session until that owner reaches quiescent disposal; initial same-id publication remains collision-safe through the POSIX no-overwrite hard link or Windows write-through rename without replacement.
|
|
77
77
|
- **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.
|
package/README.zh.md
CHANGED
|
@@ -42,7 +42,7 @@ JSONL 持久会话存储后端:`SessionPersistence` 的一个具体实现(`d
|
|
|
42
42
|
- **绑定存储身份。** 查找要求可读项目目录中只有一个匹配会话目录,然后验证 header id 等于请求 id,且 header id/cwd 派生所选 transcript 路径。列表应用同一路径检查,并拒绝重复 id。身份失败发生在修复或 append 前。
|
|
43
43
|
- **延迟实体化。**`create(meta)` 不写入;第一次 `append` 将编码 header 和第一批写入临时文件并执行 `fsync`。POSIX 通过硬链接无覆盖发布,并对父目录 `fsync`。Windows 通过 `MoveFileExW(..., MOVEFILE_WRITE_THROUGH)` 无覆盖发布,并通过同一 write-through pattern 创建缺失目录。已创建但从未 append 的会话不留下磁盘内容,不在 `list` 中。
|
|
44
44
|
- **仅追加。** 已 flush 事件绝不重写。后续原始批次 append 行;压缩批次 append 一个 frame。两条路径都执行 `fsync`,并在捕获到写入或同步失败时回滚到之前字节长度。
|
|
45
|
-
- **崩溃恢复:保留有效尾部工作。**`load` 验证每个完整压缩 frame,并扫描解压 JSONL。最后 frame 结构不完整时,读取器保留其完整解码记录,从 frame 开头截断,并使用共享[持久化约定](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) 需要的合成工具、步骤和轮次 closer 重新编码这些记录。原始 mode
|
|
45
|
+
- **崩溃恢复:保留有效尾部工作。**`load` 验证每个完整压缩 frame,并扫描解压 JSONL。最后 frame 结构不完整时,读取器保留其完整解码记录,从 frame 开头截断,并使用共享[持久化约定](../../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) 需要的合成工具、步骤和轮次 closer 重新编码这些记录。原始 mode 从第一个不完整行截断。已经存在却没有完整 header frame 的压缩工件、完整 frame 中的 checksum/解压失败,或位于最后已提交的 `turn/end` 处或之前的缺陷都属于损坏,会被拒绝。
|
|
46
46
|
- **非变更检查。**`inspect()` 返回不可变、平衡的逻辑视图,并可在内存中合成恢复 closer,但不会截断不完整尾部或更改轻量修订。
|
|
47
47
|
- **连续 seq。**`append` 拒绝第一个 `seq` 不继续已存储日志的批次,并拒绝非 JSON 可序列化 `event.data`,同时命名违规事件类型。
|
|
48
48
|
- **轻量修订。**`listSnapshots(signal?)` 使用 device、inode、size 和纳秒时间戳标识日志,避免解析完整日志;该标识会在 append、修复、替换或存储变更后改变。完整前缀读取要求读取字节前后的身份一致,`readStoredRevision()` 使用同一身份校验保留的 preparation,而不加载日志。快照列表通过产物发现转发精确信号,并在每个 `stat` 前后检查取消;由于文件系统 `stat` 不可中断,取消会等待活动调用完成,然后在不启动另一次调用的情况下拒绝。
|
package/lib/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { dirname, join, parse, resolve, toNamespacedPath } from "node:path";
|
|
|
5
5
|
import { performance } from "node:perf_hooks";
|
|
6
6
|
import { scheduler } from "node:timers/promises";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
|
-
import { DEFAULT_PREPARED_SESSION_CACHE_SIZE, DEFAULT_WRITE_BATCH_MAX_DELAY_MS, MAX_WRITE_BATCH_DELAY_MS, PersistenceCoordinator, SessionPersistence, SessionPersistenceRevision } from "@deepseek-ai/dsh-session-persistence";
|
|
9
|
-
import { decodeStorageRecord, packChunkRuns } from "@deepseek-ai/dsh-session";
|
|
8
|
+
import { DEFAULT_PREPARED_SESSION_CACHE_SIZE, DEFAULT_WRITE_BATCH_MAX_DELAY_MS, MAX_WRITE_BATCH_DELAY_MS, PersistenceCoordinator, SessionFormatUnsupportedError, SessionPersistence, SessionPersistenceRevision, sessionFormatVersionRefusal } from "@deepseek-ai/dsh-session-persistence";
|
|
9
|
+
import { SESSION_FORMAT_VERSION, decodeStorageRecord, packChunkRuns } from "@deepseek-ai/dsh-session";
|
|
10
10
|
import { constants, createZstdDecompress, zstdCompress, zstdDecompress, zstdDecompressSync } from "node:zlib";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
12
|
import { constants as constants$1 } from "node:buffer";
|
|
@@ -171,6 +171,19 @@ function eventLines(events, packChunks) {
|
|
|
171
171
|
return (packChunks ? packChunkRuns(events) : events).map((record) => JSON.stringify(record)).join("\n");
|
|
172
172
|
}
|
|
173
173
|
/** Parse one complete header record supplied independently from event rows. */
|
|
174
|
+
/**
|
|
175
|
+
* Refuse a header carrying a format version this build does not read BEFORE
|
|
176
|
+
* validating the current header shape or decoding any event row: a future
|
|
177
|
+
* format need not satisfy today's structural checks at all, and its user must
|
|
178
|
+
* see "upgrade the harness", never "corrupt session log".
|
|
179
|
+
* @param parsed - the JSON-parsed first line of a session artifact.
|
|
180
|
+
*/
|
|
181
|
+
function refuseForeignFormatVersion(parsed) {
|
|
182
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
183
|
+
const { version, id } = parsed;
|
|
184
|
+
if (typeof version !== "number" || version === SESSION_FORMAT_VERSION) return;
|
|
185
|
+
throw new SessionFormatUnsupportedError(sessionFormatVersionRefusal(typeof id === "string" ? id : String(id), version));
|
|
186
|
+
}
|
|
174
187
|
function parseHeaderRecord(record) {
|
|
175
188
|
if (record.length === 0 || record.at(-1) !== 10 || record.indexOf(10) !== record.length - 1) throw new Error("empty or header-less session log");
|
|
176
189
|
let parsed;
|
|
@@ -179,6 +192,7 @@ function parseHeaderRecord(record) {
|
|
|
179
192
|
} catch {
|
|
180
193
|
throw new Error("corrupt session log: header line is not valid JSON");
|
|
181
194
|
}
|
|
195
|
+
refuseForeignFormatVersion(parsed);
|
|
182
196
|
if (!isHeaderLine(parsed)) throw new Error("corrupt session log: first line is not a session header");
|
|
183
197
|
return fromHeaderLine(parsed);
|
|
184
198
|
}
|
|
@@ -607,7 +621,7 @@ const ERROR_FILE_EXISTS = 80;
|
|
|
607
621
|
const ERROR_INVALID_NAME = 123;
|
|
608
622
|
const ERROR_ALREADY_EXISTS = 183;
|
|
609
623
|
let bindings;
|
|
610
|
-
/** Load the small Win32
|
|
624
|
+
/** Load the small Win32 API lazily so non-Windows processes never load Koffi. */
|
|
611
625
|
async function win32() {
|
|
612
626
|
if (bindings !== void 0) return bindings;
|
|
613
627
|
const kernel32 = (await import("koffi")).default.load("kernel32.dll");
|
|
@@ -751,6 +765,7 @@ function isENOENT(error) {
|
|
|
751
765
|
*/
|
|
752
766
|
var SessionPersistenceJsonl = class extends SessionPersistence {
|
|
753
767
|
config;
|
|
768
|
+
supportsRawArtifacts = true;
|
|
754
769
|
static inject = ["sessions"];
|
|
755
770
|
static Config = z.object({
|
|
756
771
|
root: z.string().required(),
|
|
@@ -839,37 +854,94 @@ var SessionPersistenceJsonl = class extends SessionPersistence {
|
|
|
839
854
|
}
|
|
840
855
|
}
|
|
841
856
|
/**
|
|
842
|
-
* Read a stored
|
|
843
|
-
*
|
|
857
|
+
* Read a session's stored artifact text verbatim: the durable file bytes
|
|
858
|
+
* decoded from this backend's physical encoding (complete zstd frames
|
|
859
|
+
* concatenated, or UTF-8 plaintext). The content is the exact JSONL text the
|
|
860
|
+
* backend wrote — never a reconstruction from parsed events — so packed-
|
|
861
|
+
* chunk rows, key order, and line breaks survive byte-for-byte. A torn
|
|
862
|
+
* final frame is omitted, matching the committed-prefix semantics of every
|
|
863
|
+
* other read.
|
|
864
|
+
* @param id - the persisted session to read.
|
|
865
|
+
* @param signal - optional cancellation for the stat/read/decode work.
|
|
866
|
+
* @returns the raw artifact text plus the header parsed from its own first
|
|
867
|
+
* line, or `undefined` when the session has no stored artifact.
|
|
844
868
|
*/
|
|
845
|
-
async
|
|
846
|
-
|
|
847
|
-
|
|
869
|
+
async readRaw(id, signal) {
|
|
870
|
+
signal?.throwIfAborted();
|
|
871
|
+
await this.ensureRootEncoding();
|
|
872
|
+
signal?.throwIfAborted();
|
|
873
|
+
const path = await this.findLog(id, signal);
|
|
874
|
+
if (path === void 0) return void 0;
|
|
875
|
+
const { buffer } = await this.readStableFile(path, signal);
|
|
876
|
+
let content;
|
|
877
|
+
if (this.compression === "zstd") {
|
|
878
|
+
const { frames } = scanZstdFrames(buffer);
|
|
879
|
+
if (frames.length === 0) throw new Error("empty or header-less Zstandard session log");
|
|
880
|
+
const decoder = createZstdFrameDecoder();
|
|
881
|
+
const plaintexts = [];
|
|
882
|
+
for (const plaintext of decoder.decode(buffer, frames)) {
|
|
883
|
+
signal?.throwIfAborted();
|
|
884
|
+
plaintexts.push(Buffer.from(plaintext));
|
|
885
|
+
}
|
|
886
|
+
content = Buffer.concat(plaintexts).toString("utf8");
|
|
887
|
+
} else content = buffer.toString("utf8");
|
|
888
|
+
const meta = parseHeaderMeta(content.split("\n", 1)[0]);
|
|
889
|
+
if (meta === void 0 || meta.id !== id) throw new Error(`corrupt session log: invalid header line in "${path}"`);
|
|
890
|
+
return {
|
|
891
|
+
meta,
|
|
892
|
+
filename: "session.jsonl",
|
|
893
|
+
content
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Read a file's bytes under a revision-stable loop: a writer appending
|
|
898
|
+
* between stat and readFile would yield a torn physical file, so retry
|
|
899
|
+
* while the stat revision changes.
|
|
900
|
+
* @param path - the artifact file to read.
|
|
901
|
+
* @param signal - optional cancellation for the stat/read work.
|
|
902
|
+
* @returns the stable bytes and the revision that matched both stats.
|
|
903
|
+
*/
|
|
904
|
+
async readStableFile(path, signal) {
|
|
848
905
|
for (;;) {
|
|
849
906
|
signal?.throwIfAborted();
|
|
850
907
|
const before = fileRevision(await stat(path, { bigint: true }));
|
|
851
|
-
buffer = await readFile(path, { signal });
|
|
908
|
+
const buffer = await readFile(path, { signal });
|
|
852
909
|
signal?.throwIfAborted();
|
|
853
910
|
const after = fileRevision(await stat(path, { bigint: true }));
|
|
854
|
-
if (before === after) {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
911
|
+
if (before === after) return {
|
|
912
|
+
buffer,
|
|
913
|
+
revision: after
|
|
914
|
+
};
|
|
858
915
|
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Read a stored prefix and convert torn-tail state to the opaque marker the
|
|
919
|
+
* coordinator can round-trip without knowing the physical encoding.
|
|
920
|
+
*/
|
|
921
|
+
async readPrefix(path, expectedId, signal) {
|
|
922
|
+
const { buffer, revision } = await this.readStableFile(path, signal);
|
|
859
923
|
let prefix;
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
924
|
+
try {
|
|
925
|
+
if (this.compression === "zstd") prefix = await this.readZstdPrefix(buffer, signal);
|
|
926
|
+
else {
|
|
927
|
+
signal?.throwIfAborted();
|
|
928
|
+
const { meta, events, committedBytes } = scanLog(buffer);
|
|
929
|
+
signal?.throwIfAborted();
|
|
930
|
+
prefix = {
|
|
931
|
+
meta,
|
|
932
|
+
events,
|
|
933
|
+
...committedBytes < buffer.byteLength ? { tornMarker: {
|
|
934
|
+
truncateTo: committedBytes,
|
|
935
|
+
recoveredEvents: []
|
|
936
|
+
} } : {}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
} catch (error) {
|
|
940
|
+
if (error instanceof SessionFormatUnsupportedError && error.location === void 0) throw new SessionFormatUnsupportedError(`${error.message} (raw log: ${path})`, {
|
|
941
|
+
kind: "jsonl",
|
|
942
|
+
path
|
|
943
|
+
});
|
|
944
|
+
throw error;
|
|
873
945
|
}
|
|
874
946
|
signal?.throwIfAborted();
|
|
875
947
|
await this.assertStoredIdentity(path, prefix.meta, expectedId, signal);
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Context } from '@deepseek-ai/cordis';
|
|
9
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';
|
|
10
|
+
import { SessionPersistence, type PersistenceBackend, type SessionLocation, type SessionPersistenceSnapshot, type SessionInspection, type SessionPersistenceRevision as PersistenceRevision, type SessionRawArtifact, type StoredPrefix } from '@deepseek-ai/dsh-session-persistence';
|
|
11
11
|
import type { SessionEvent, SessionId, SessionHeader, SessionPreparation } from '@deepseek-ai/dsh-session';
|
|
12
12
|
import { type JsonlCompression } from './format.ts';
|
|
13
13
|
export type { JsonlCompression } from './format.ts';
|
|
@@ -51,6 +51,7 @@ interface JsonlTornMarker {
|
|
|
51
51
|
*/
|
|
52
52
|
export declare class SessionPersistenceJsonl extends SessionPersistence implements PersistenceBackend<JsonlTornMarker> {
|
|
53
53
|
config: Config;
|
|
54
|
+
readonly supportsRawArtifacts = true;
|
|
54
55
|
static inject: string[];
|
|
55
56
|
static Config: z<Config>;
|
|
56
57
|
/**
|
|
@@ -83,6 +84,29 @@ export declare class SessionPersistenceJsonl extends SessionPersistence implemen
|
|
|
83
84
|
* Resolving an id with unknown cwd still scans the project directories.
|
|
84
85
|
*/
|
|
85
86
|
readStoredRevision(id: SessionId, signal?: AbortSignal): Promise<PersistenceRevision | undefined>;
|
|
87
|
+
/**
|
|
88
|
+
* Read a session's stored artifact text verbatim: the durable file bytes
|
|
89
|
+
* decoded from this backend's physical encoding (complete zstd frames
|
|
90
|
+
* concatenated, or UTF-8 plaintext). The content is the exact JSONL text the
|
|
91
|
+
* backend wrote — never a reconstruction from parsed events — so packed-
|
|
92
|
+
* chunk rows, key order, and line breaks survive byte-for-byte. A torn
|
|
93
|
+
* final frame is omitted, matching the committed-prefix semantics of every
|
|
94
|
+
* other read.
|
|
95
|
+
* @param id - the persisted session to read.
|
|
96
|
+
* @param signal - optional cancellation for the stat/read/decode work.
|
|
97
|
+
* @returns the raw artifact text plus the header parsed from its own first
|
|
98
|
+
* line, or `undefined` when the session has no stored artifact.
|
|
99
|
+
*/
|
|
100
|
+
readRaw(id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>;
|
|
101
|
+
/**
|
|
102
|
+
* Read a file's bytes under a revision-stable loop: a writer appending
|
|
103
|
+
* between stat and readFile would yield a torn physical file, so retry
|
|
104
|
+
* while the stat revision changes.
|
|
105
|
+
* @param path - the artifact file to read.
|
|
106
|
+
* @param signal - optional cancellation for the stat/read work.
|
|
107
|
+
* @returns the stable bytes and the revision that matched both stats.
|
|
108
|
+
*/
|
|
109
|
+
private readStableFile;
|
|
86
110
|
/**
|
|
87
111
|
* Read a stored prefix and convert torn-tail state to the opaque marker the
|
|
88
112
|
* coordinator can round-trip without knowing the physical encoding.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-session-persistence-jsonl",
|
|
3
3
|
"description": "JSONL durable session persistence backend for the DeepSeek Harness",
|
|
4
|
-
"version": "0.0.1-rc.
|
|
4
|
+
"version": "0.0.1-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "restricted"
|
|
7
7
|
},
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
],
|
|
33
33
|
"license": "BSD-3-Clause",
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@deepseek-ai/dsh-
|
|
36
|
-
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.
|
|
37
|
-
"@deepseek-ai/
|
|
38
|
-
"@deepseek-ai/
|
|
35
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.2",
|
|
36
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.2",
|
|
37
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.2"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"koffi": "^3.1.0",
|
|
42
42
|
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.
|
|
46
|
-
"@deepseek-ai/dsh-session": "^0.0.1-rc.
|
|
47
|
-
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.
|
|
45
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-session-persistence": "^0.0.1-rc.2",
|
|
48
48
|
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
49
49
|
}
|
|
50
50
|
}
|