@deepseek-ai/dsh-session-log-export 0.1.2-alpha.5 → 0.1.3-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 +3 -4
- package/README.zh.md +3 -4
- package/lib/client.js +2 -2
- package/lib/index.js +164 -66
- package/lib/types/archive.d.ts +55 -29
- package/lib/types/index.d.ts +1 -1
- package/package.json +21 -18
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-query/session-log-export/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: db26d9c405b3408cc24f27e5947c94ccaa2393e8
|
|
6
|
+
README.zh.md: 4e2eb00de94dd6520af8c1836155a00d187ef99f
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Use this package when the Web bundle should let users export a session log. It r
|
|
|
29
29
|
|
|
30
30
|
### When to choose it
|
|
31
31
|
|
|
32
|
-
Choose it for a Web deployment that needs user-facing session export with a visible download dialog. Avoid it when a programmatic or Host-side export is needed: this package produces a browser download, not a Host path write
|
|
32
|
+
Choose it for a Web deployment that needs user-facing session export with a visible download dialog. Avoid it when a programmatic or Host-side export is needed: this package produces a browser download, not a Host path write. The logs are serialized from persistence read handles, so any mounted backend is supported.
|
|
33
33
|
|
|
34
34
|
### Composition
|
|
35
35
|
|
|
@@ -55,7 +55,7 @@ The Web bundle mounts the package with Connection, `dsh-commands`, `dsh-client-u
|
|
|
55
55
|
|
|
56
56
|
### What to expect
|
|
57
57
|
|
|
58
|
-
The dialog reports three phases: preparing, download started, or failed. Closing the dialog does not cancel an in-flight download, and the dialog does not reopen when that operation later settles. One session admits one active download at a time; repeated gestures share that operation. The export includes the live session's newest events: the host endpoint flushes a live root session before reading, so a slash-triggered ZIP includes the `command/run` and `command/done` pair that started the download; cold persisted sessions need no flush.
|
|
58
|
+
The dialog reports three phases: preparing, download started, or failed. Closing the dialog does not cancel an in-flight download, and the dialog does not reopen when that operation later settles. One session admits one active download at a time; repeated gestures share that operation. The export includes the live session's newest events: the host endpoint flushes a live root session before reading, so a slash-triggered ZIP includes the `command/run` and `command/done` pair that started the download; cold persisted sessions need no flush. Each logical log uses the current generation's canonical filename inside the archive (`session.jsonl` for v0, otherwise `session.vN.jsonl`), including beneath each sub-session directory. Images use `media/<attachmentId>.<ext>`, and generic files use `files/<digest-prefix>/<digest>/<name>`. Generic-file bytes are read and compressed as bounded chunks, so exporting a large upload does not buffer it in full.
|
|
59
59
|
|
|
60
60
|
### Failures
|
|
61
61
|
|
|
@@ -79,7 +79,7 @@ The package has two halves. The Host half ([`src/index.ts`](src/index.ts)) regis
|
|
|
79
79
|
|
|
80
80
|
Both entry paths issue a `HEAD` preflight to `GET /api/session.export?...`, then hand the GET URL to the browser download manager without buffering the ZIP in JavaScript. One controller owns one in-flight download per session, collapses concurrent gestures into that operation, and cancels the preflight on plugin disposal. Modal state lives in a snapshot store keyed by session, so the button and the command share one dialog per session.
|
|
81
81
|
|
|
82
|
-
The Host route is a feature-owned exact Fetch contribution. Connection applies its Host/Origin and browser-session checks and bridges the streaming `Response`; this package owns query validation, live-session flushes,
|
|
82
|
+
The Host route is a feature-owned exact Fetch contribution. Connection applies its Host/Origin and browser-session checks and bridges the streaming `Response`; this package owns query validation, live-session flushes, handle-based log reads and attachment reads, ZIP generation, and HTTP status semantics.
|
|
83
83
|
|
|
84
84
|
</details>
|
|
85
85
|
|
|
@@ -121,7 +121,6 @@ None. The log-only command lifecycle and browser download do not change the deri
|
|
|
121
121
|
|
|
122
122
|
These limits define when this package is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
|
|
123
123
|
|
|
124
|
-
- **Requires a per-Session raw artifact** — the download endpoint reads the shipped JSONL provider's plaintext or zstd artifact; an out-of-tree provider without a raw artifact cannot serve this route.
|
|
125
124
|
- **Browser download, not a Host-path writer** — the browser chooses the local destination; no Host path or native folder action is returned.
|
|
126
125
|
- **Preflight reports only pre-stream failures** — a descendant or attachment failure after the browser accepts the GET is reported by the browser download manager, not by the dialog.
|
|
127
126
|
|
package/README.zh.md
CHANGED
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 何时选择
|
|
31
31
|
|
|
32
|
-
为需要带可见下载弹窗的用户级会话导出的 Web 部署选择它。需要程序化或 Host 侧导出时避免使用:本包产生的是浏览器下载,而非 Host
|
|
32
|
+
为需要带可见下载弹窗的用户级会话导出的 Web 部署选择它。需要程序化或 Host 侧导出时避免使用:本包产生的是浏览器下载,而非 Host 路径写入。日志从持久化读句柄序列化而来,因此任何已挂载后端都受支持。
|
|
33
33
|
|
|
34
34
|
### 组合
|
|
35
35
|
|
|
@@ -55,7 +55,7 @@ Web bundle 将本包与 Connection、`dsh-commands`、`dsh-client-ui-commands`
|
|
|
55
55
|
|
|
56
56
|
### 预期行为
|
|
57
57
|
|
|
58
|
-
弹窗报告三个阶段:准备中、开始下载或失败。关闭弹窗不会取消正在进行的下载,该操作随后完成时弹窗也不会重新打开。每个会话同时只允许一项下载,重复操作共用该任务。导出包含实时会话的最新事件:Host 端点在读取前会 flush 活动的根会话,因此斜杠命令触发的 ZIP 会包含启动下载的 `command/run` 与 `command/done` 事件对;冷持久化会话不需要 flush
|
|
58
|
+
弹窗报告三个阶段:准备中、开始下载或失败。关闭弹窗不会取消正在进行的下载,该操作随后完成时弹窗也不会重新打开。每个会话同时只允许一项下载,重复操作共用该任务。导出包含实时会话的最新事件:Host 端点在读取前会 flush 活动的根会话,因此斜杠命令触发的 ZIP 会包含启动下载的 `command/run` 与 `command/done` 事件对;冷持久化会话不需要 flush。每份逻辑日志在归档中使用当前 generation 的规范文件名(v0 为 `session.jsonl`,其他版本为 `session.vN.jsonl`),每个子会话目录下也遵循同一规则。图片使用 `media/<attachmentId>.<ext>`,通用文件使用 `files/<digest-prefix>/<digest>/<name>`。通用文件以有界分块读取并压缩,因此导出大文件时不会把它完整缓冲进内存。
|
|
59
59
|
|
|
60
60
|
### 失败
|
|
61
61
|
|
|
@@ -79,7 +79,7 @@ Web bundle 将本包与 Connection、`dsh-commands`、`dsh-client-ui-commands`
|
|
|
79
79
|
|
|
80
80
|
两条入口都会对 `GET /api/session.export?...` 发出 `HEAD` 预检,然后把 GET URL 交给浏览器下载管理器,JavaScript 不缓冲 ZIP。一个控制器按会话持有一项进行中的下载,把并发操作折叠进该任务,并在插件释放时取消预检。弹窗状态存放在按会话键控的快照存储中,因此按钮与命令按会话共享一个弹窗。
|
|
81
81
|
|
|
82
|
-
Host 路由是业务拥有的精确 Fetch contribution。Connection 应用 Host/Origin 与浏览器会话检查并桥接流式 `Response`;本包拥有查询校验、活动会话 flush
|
|
82
|
+
Host 路由是业务拥有的精确 Fetch contribution。Connection 应用 Host/Origin 与浏览器会话检查并桥接流式 `Response`;本包拥有查询校验、活动会话 flush、基于句柄的日志读取与附件读取、ZIP 生成和 HTTP 状态语义。
|
|
83
83
|
|
|
84
84
|
</details>
|
|
85
85
|
|
|
@@ -121,7 +121,6 @@ Host 路由是业务拥有的精确 Fetch contribution。Connection 应用 Host/
|
|
|
121
121
|
|
|
122
122
|
这些限制说明本包何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
123
123
|
|
|
124
|
-
- **要求逐 Session 原始产物**——下载端点读取随产品交付的 JSONL provider 所提供的明文或 zstd 产物;没有原始产物的仓库外 provider 无法服务该 route。
|
|
125
124
|
- **浏览器下载,而非 Host 路径写入**——目标位置由浏览器选择;不会返回 Host 路径或原生文件夹操作。
|
|
126
125
|
- **预检只报告流式传输前的失败**——浏览器接受 GET 后发生的子会话或附件读取失败由浏览器下载管理器报告,不通过弹窗报告。
|
|
127
126
|
|
package/lib/client.js
CHANGED
|
@@ -169,7 +169,7 @@ window.__ModuleLoader__.load({
|
|
|
169
169
|
}
|
|
170
170
|
//#endregion
|
|
171
171
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/session-query/session-log-export/src/client/HeaderAction.module.css.mjs
|
|
172
|
-
const css = ".nL4_yW_sessionLogButton{border:.5px solid var(--dsw-alias-border-l4);
|
|
172
|
+
const css = ".nL4_yW_sessionLogButton{border:.5px solid var(--dsw-alias-border-l4);height:26px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);cursor:pointer;background:0 0;border-radius:13px;justify-content:center;align-items:center;gap:5px;padding:5px 10px;font-size:11px;font-weight:400;line-height:16px;display:inline-flex}.nL4_yW_sessionLogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.nL4_yW_sessionLogButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:wait}.nL4_yW_sessionLogButton span,.nL4_yW_sessionLogButton svg{flex:none}.nL4_yW_sessionLogButton svg{color:var(--dsw-alias-label-secondary)}.nL4_yW_sessionLogButton span{white-space:nowrap}";
|
|
173
173
|
const tagId = "@deepseek-ai/dsh-session-log-export/HeaderAction.module.css";
|
|
174
174
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
175
175
|
const tag = document.createElement("style");
|
|
@@ -197,7 +197,7 @@ window.__ModuleLoader__.load({
|
|
|
197
197
|
onClick: () => {
|
|
198
198
|
request(sessionId);
|
|
199
199
|
},
|
|
200
|
-
children: [(0, react_jsx_runtime.jsx)("span", { children: t("header.action") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size:
|
|
200
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: t("header.action") }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { size: 10 })]
|
|
201
201
|
}), (0, react_jsx_runtime.jsx)(SessionLogDownloadDialog, { ...props })] });
|
|
202
202
|
}
|
|
203
203
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import Schema from "@deepseek-ai/schemastery";
|
|
2
2
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
3
3
|
import { Zip, ZipDeflate } from "fflate";
|
|
4
|
+
import { SESSION_FORMAT_VERSION } from "@deepseek-ai/dsh-session";
|
|
5
|
+
import { sessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
|
|
6
|
+
import { SessionPersistenceNotFoundError } from "@deepseek-ai/dsh-session-persistence";
|
|
4
7
|
//#region lib/types/archive.js
|
|
5
8
|
/**
|
|
6
9
|
* Host-side session-log download: streams one ZIP archive whose files are the
|
|
7
|
-
* sessions'
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the
|
|
10
|
+
* sessions' logical session logs plus every referenced attachment. Each log
|
|
11
|
+
* is read through a persistence read handle and serialized here as canonical
|
|
12
|
+
* JSONL — one header line, then one line per validated event — so every
|
|
13
|
+
* backend (JSONL, SQLite, future) exports identically. The root log uses the
|
|
14
|
+
* current generation's canonical `session[.vN].jsonl` name; each subagent
|
|
15
|
+
* descendant uses `subagents/<id>/session[.vN].jsonl`; each image referenced by any included log
|
|
16
|
+
* under `media/<attachmentId>.<ext>` (content-addressed, so one archive never
|
|
17
|
+
* duplicates a shared image); each generic file sits under
|
|
18
|
+
* `files/<prefix>/<digest>/<name>` and streams from the attachment store. No
|
|
19
|
+
* manifest is written. Before each live session's log read, the SessionStore
|
|
20
|
+
* flush barrier makes the current in-memory log durable; cold sessions need no
|
|
21
|
+
* barrier. Request abort and response-consumer cancellation share one producer
|
|
22
|
+
* signal and terminate the active compressor.
|
|
18
23
|
* Compression runs on the host with fflate's streaming Zip API, so the archive
|
|
19
24
|
* bytes are produced incrementally and the host never holds the whole archive
|
|
20
25
|
* in one buffer; production waits for consumer pull whenever the response queue
|
|
@@ -39,7 +44,7 @@ function sessionLogExportDeps(ctx) {
|
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
46
|
* Flush one currently live session through the store's authoritative durability
|
|
42
|
-
* barrier immediately before its
|
|
47
|
+
* barrier immediately before its logical log is read. A cold or absent id has
|
|
43
48
|
* no in-memory work to flush.
|
|
44
49
|
* @param deps - export services, including the optional live-session store.
|
|
45
50
|
* @param id - the session whose artifact is about to be read.
|
|
@@ -54,6 +59,57 @@ async function flushLiveSessionLog(deps, id, signal) {
|
|
|
54
59
|
await sessions.flush(session);
|
|
55
60
|
signal?.throwIfAborted();
|
|
56
61
|
}
|
|
62
|
+
/** The current generation's canonical base filename for every exported session log. */
|
|
63
|
+
const SESSION_LOG_FILENAME = sessionFormatLogFilename(SESSION_FORMAT_VERSION);
|
|
64
|
+
/**
|
|
65
|
+
* Serialize one session's logical log as canonical JSONL text: the header
|
|
66
|
+
* line, then one line per event, with a trailing newline.
|
|
67
|
+
* @param header - the session's immutable header.
|
|
68
|
+
* @param events - the validated committed events in seq order.
|
|
69
|
+
* @returns the JSONL text.
|
|
70
|
+
*/
|
|
71
|
+
function serializeSessionLog(header, events) {
|
|
72
|
+
const lines = [JSON.stringify({
|
|
73
|
+
type: "session",
|
|
74
|
+
version: header.version,
|
|
75
|
+
id: header.id,
|
|
76
|
+
createdAt: header.createdAt,
|
|
77
|
+
...header.cwd !== void 0 ? { cwd: header.cwd } : {},
|
|
78
|
+
...header.parentSession !== void 0 ? { parentSession: header.parentSession } : {},
|
|
79
|
+
isSeeded: header.isSeeded,
|
|
80
|
+
...header.origin !== void 0 ? { origin: header.origin } : {},
|
|
81
|
+
delegationDepth: header.delegationDepth ?? 0,
|
|
82
|
+
...header.agentPreset !== void 0 ? { agentPreset: header.agentPreset } : {}
|
|
83
|
+
})];
|
|
84
|
+
for (const event of events) lines.push(JSON.stringify(event));
|
|
85
|
+
return `${lines.join("\n")}\n`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read one session's complete logical log through a read handle and serialize
|
|
89
|
+
* it. The read observes the committed log only — persistence never returns a
|
|
90
|
+
* torn tail — and a handle read after a resolved flush observes at least the
|
|
91
|
+
* flushed prefix.
|
|
92
|
+
* @param persistence - the mounted persistence backend.
|
|
93
|
+
* @param id - the session to read.
|
|
94
|
+
* @param signal - optional cancellation forwarded to the open and read.
|
|
95
|
+
* @returns the serialized JSONL text, or `undefined` when the session does not exist.
|
|
96
|
+
*/
|
|
97
|
+
async function readSessionLogText(persistence, id, signal) {
|
|
98
|
+
const options = signal === void 0 ? {} : { signal };
|
|
99
|
+
let handle;
|
|
100
|
+
try {
|
|
101
|
+
handle = await persistence.open(id, "read", options);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof SessionPersistenceNotFoundError) return void 0;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const { events } = await handle.read(0, void 0, options);
|
|
108
|
+
return serializeSessionLog(handle.header, events);
|
|
109
|
+
} finally {
|
|
110
|
+
await handle.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
57
113
|
/** Zip extension for each accepted raster media type. */
|
|
58
114
|
const MEDIA_TYPE_EXTENSIONS = {
|
|
59
115
|
"image/png": "png",
|
|
@@ -71,13 +127,21 @@ const MEDIA_TYPE_EXTENSIONS = {
|
|
|
71
127
|
function mediaEntryPath(ref) {
|
|
72
128
|
return `media/${String(ref.attachmentId)}.${MEDIA_TYPE_EXTENSIONS[ref.mediaType]}`;
|
|
73
129
|
}
|
|
130
|
+
/** Archive path that preserves one stored file reference's digest and name. */
|
|
131
|
+
function fileEntryPath(ref) {
|
|
132
|
+
const digest = String(ref.attachmentId).replace(/^sha256:/u, "");
|
|
133
|
+
const name = ref.name.replace(/[\\/\u0000-\u001f\u007f]/gu, "_");
|
|
134
|
+
const safeName = name === "." || name === ".." || name === "" ? "file" : name;
|
|
135
|
+
return `files/${digest.slice(0, 2)}/${digest}/${safeName}`;
|
|
136
|
+
}
|
|
74
137
|
/**
|
|
75
|
-
* Collect every
|
|
138
|
+
* Collect every attachment reference inside one content array, descending into
|
|
76
139
|
* nested tool results the way the live attachment route does.
|
|
77
140
|
* @param content - an event content array (or nested tool-result content).
|
|
78
|
-
* @param
|
|
141
|
+
* @param images - image dedupe map keyed by attachment id.
|
|
142
|
+
* @param files - file dedupe map keyed by attachment id and stored name.
|
|
79
143
|
*/
|
|
80
|
-
function
|
|
144
|
+
function collectAttachmentRefs(content, images, files) {
|
|
81
145
|
if (!Array.isArray(content)) return;
|
|
82
146
|
const pending = [];
|
|
83
147
|
for (const item of content) pending.push(item);
|
|
@@ -87,36 +151,44 @@ function collectImageRefs(content, refs) {
|
|
|
87
151
|
const block = value;
|
|
88
152
|
if (block.type === "image" && typeof block.attachment === "object" && block.attachment !== null) {
|
|
89
153
|
const ref = block.attachment;
|
|
90
|
-
|
|
154
|
+
images.set(String(ref.attachmentId), ref);
|
|
155
|
+
}
|
|
156
|
+
if (block.type === "file" && typeof block.attachment === "object" && block.attachment !== null) {
|
|
157
|
+
const ref = block.attachment;
|
|
158
|
+
files.set(`${String(ref.attachmentId)}\u0000${ref.name}`, ref);
|
|
91
159
|
}
|
|
92
160
|
if (Array.isArray(block.content)) for (const item of block.content) pending.push(item);
|
|
93
161
|
}
|
|
94
162
|
}
|
|
95
163
|
/**
|
|
96
|
-
* Collect every
|
|
164
|
+
* Collect every attachment reference one session event carries, across the same
|
|
97
165
|
* carriers the live attachment route scans (direct content, message content,
|
|
98
|
-
* inserted messages, and completed
|
|
166
|
+
* inserted messages, and completed blocks in embedded Assistant streams).
|
|
99
167
|
* @param event - one parsed JSONL event object.
|
|
100
|
-
* @param
|
|
168
|
+
* @param images - image dedupe map keyed by attachment id.
|
|
169
|
+
* @param files - file dedupe map keyed by attachment id and stored name.
|
|
101
170
|
*/
|
|
102
|
-
function
|
|
171
|
+
function collectEventAttachmentRefs(event, images, files) {
|
|
103
172
|
const data = event.data;
|
|
104
173
|
if (typeof data !== "object" || data === null) return;
|
|
105
174
|
const carrier = data;
|
|
106
|
-
|
|
107
|
-
if (carrier.message !== void 0)
|
|
108
|
-
if (carrier.inserted !== void 0) for (const message of carrier.inserted)
|
|
109
|
-
if (carrier.
|
|
175
|
+
collectAttachmentRefs(carrier.content, images, files);
|
|
176
|
+
if (carrier.message !== void 0) collectAttachmentRefs(carrier.message.content, images, files);
|
|
177
|
+
if (carrier.inserted !== void 0) for (const message of carrier.inserted) collectAttachmentRefs(message.content, images, files);
|
|
178
|
+
if (carrier.stream !== void 0) {
|
|
179
|
+
for (const record of carrier.stream) if (record.type === "chunk" && record.chunk?.type === "block-end") collectAttachmentRefs([record.chunk.block], images, files);
|
|
180
|
+
}
|
|
110
181
|
}
|
|
111
182
|
/**
|
|
112
|
-
* Collect the distinct
|
|
113
|
-
* Lines that fail to parse cannot reference
|
|
183
|
+
* Collect the distinct attachment references one stored artifact text names.
|
|
184
|
+
* Lines that fail to parse cannot reference attachments and are skipped (the
|
|
114
185
|
* artifact text itself is exported verbatim regardless).
|
|
115
186
|
* @param content - the stored artifact text.
|
|
116
|
-
* @returns
|
|
187
|
+
* @returns image and file dedupe maps.
|
|
117
188
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
189
|
+
function attachmentRefsInArtifact(content) {
|
|
190
|
+
const images = /* @__PURE__ */ new Map();
|
|
191
|
+
const files = /* @__PURE__ */ new Map();
|
|
120
192
|
for (const line of content.split("\n")) {
|
|
121
193
|
if (line === "") continue;
|
|
122
194
|
let event;
|
|
@@ -125,9 +197,12 @@ function imageRefsInArtifact(content) {
|
|
|
125
197
|
} catch {
|
|
126
198
|
continue;
|
|
127
199
|
}
|
|
128
|
-
|
|
200
|
+
collectEventAttachmentRefs(event, images, files);
|
|
129
201
|
}
|
|
130
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
images,
|
|
204
|
+
files
|
|
205
|
+
};
|
|
131
206
|
}
|
|
132
207
|
/**
|
|
133
208
|
* One safe zip path segment from an untrusted session id. Session ids are
|
|
@@ -150,30 +225,33 @@ function sessionLogZipFilename(sessionId) {
|
|
|
150
225
|
return `dsh-session-${safeSessionIdSegment(sessionId)}.zip`;
|
|
151
226
|
}
|
|
152
227
|
/**
|
|
153
|
-
* Yield the export entries in zip order: the preloaded root
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* after the consumer moves on), then every distinct
|
|
157
|
-
* the included logs
|
|
158
|
-
*
|
|
159
|
-
*
|
|
228
|
+
* Yield the export entries in zip order: the preloaded root log first, then
|
|
229
|
+
* every subagent descendant in lineage order (each flushed when live, read
|
|
230
|
+
* through a persistence read handle right before it is yielded, and dropped
|
|
231
|
+
* after the consumer moves on), then every distinct attachment referenced by
|
|
232
|
+
* the included logs. Images are read and verified as bounded stored objects;
|
|
233
|
+
* generic files remain streamed through the ZIP writer. The host holds at most
|
|
234
|
+
* one descendant log, one image, and one file chunk beyond the root.
|
|
160
235
|
* @param deps - the mounted export services (the caller answered 500 before this runs).
|
|
161
|
-
* @param
|
|
162
|
-
* missing-session path can answer cleanly before streaming starts).
|
|
236
|
+
* @param rootContent - the already-serialized root log (read by the caller so
|
|
237
|
+
* the missing-session path can answer cleanly before streaming starts).
|
|
163
238
|
* @param sessionId - the root session id.
|
|
164
239
|
* @param includeDescendants - whether to include every subagent descendant.
|
|
165
240
|
* @param signal - optional cancellation forwarded to lineage, persistence, and attachment reads.
|
|
166
241
|
* @returns the export entries in zip order.
|
|
167
242
|
*/
|
|
168
|
-
async function* sessionLogZipEntries(deps,
|
|
243
|
+
async function* sessionLogZipEntries(deps, rootContent, sessionId, includeDescendants, signal) {
|
|
169
244
|
const media = /* @__PURE__ */ new Map();
|
|
170
|
-
const
|
|
171
|
-
|
|
245
|
+
const files = /* @__PURE__ */ new Map();
|
|
246
|
+
const rememberAttachments = (content) => {
|
|
247
|
+
const refs = attachmentRefsInArtifact(content);
|
|
248
|
+
for (const [id, ref] of refs.images) media.set(id, ref);
|
|
249
|
+
for (const [id, ref] of refs.files) files.set(id, ref);
|
|
172
250
|
};
|
|
173
|
-
|
|
251
|
+
rememberAttachments(rootContent);
|
|
174
252
|
yield {
|
|
175
|
-
path:
|
|
176
|
-
content:
|
|
253
|
+
path: SESSION_LOG_FILENAME,
|
|
254
|
+
content: rootContent
|
|
177
255
|
};
|
|
178
256
|
if (includeDescendants) {
|
|
179
257
|
const seen = new Set([sessionId]);
|
|
@@ -184,13 +262,13 @@ async function* sessionLogZipEntries(deps, root, sessionId, includeDescendants,
|
|
|
184
262
|
if (seen.has(id)) continue;
|
|
185
263
|
seen.add(id);
|
|
186
264
|
await flushLiveSessionLog(deps, id, signal);
|
|
187
|
-
const
|
|
265
|
+
const content = await readSessionLogText(deps.sessionPersistence, id, signal);
|
|
188
266
|
signal?.throwIfAborted();
|
|
189
|
-
if (
|
|
190
|
-
|
|
267
|
+
if (content === void 0) throw new Error(`subagent "${id}" has no stored log`);
|
|
268
|
+
rememberAttachments(content);
|
|
191
269
|
yield {
|
|
192
|
-
path: `subagents/${safeSessionIdSegment(id)}/${
|
|
193
|
-
content
|
|
270
|
+
path: `subagents/${safeSessionIdSegment(id)}/${SESSION_LOG_FILENAME}`,
|
|
271
|
+
content
|
|
194
272
|
};
|
|
195
273
|
yield* collect(node.descendants);
|
|
196
274
|
}
|
|
@@ -208,8 +286,15 @@ async function* sessionLogZipEntries(deps, root, sessionId, includeDescendants,
|
|
|
208
286
|
data: stored.data
|
|
209
287
|
};
|
|
210
288
|
}
|
|
289
|
+
for (const ref of files.values()) {
|
|
290
|
+
signal?.throwIfAborted();
|
|
291
|
+
yield {
|
|
292
|
+
path: fileEntryPath(ref),
|
|
293
|
+
chunks: deps.attachments.readFileStream(ref, signal)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
211
296
|
}
|
|
212
|
-
/** How many code units of
|
|
297
|
+
/** How many code units of Session-log text one zip push carries (bounded encode memory). */
|
|
213
298
|
const PUSH_CHUNK_CODE_UNITS = 65536;
|
|
214
299
|
/** How many bytes of media one zip push carries (bounded memory; images are already size-capped). */
|
|
215
300
|
const PUSH_CHUNK_BYTES = 65536;
|
|
@@ -262,12 +347,24 @@ async function pushBinaryChunks(deflate, data, controller, capacity, signal) {
|
|
|
262
347
|
await capacity.wait(controller, signal);
|
|
263
348
|
} while (offset < data.byteLength);
|
|
264
349
|
}
|
|
350
|
+
/** Push one streamed file entry without retaining its complete byte sequence. */
|
|
351
|
+
async function pushStreamChunks(deflate, chunks, controller, capacity, signal) {
|
|
352
|
+
for await (const chunk of chunks) {
|
|
353
|
+
signal.throwIfAborted();
|
|
354
|
+
if (chunk.byteLength === 0) continue;
|
|
355
|
+
deflate.push(chunk, false);
|
|
356
|
+
await capacity.wait(controller, signal);
|
|
357
|
+
}
|
|
358
|
+
signal.throwIfAborted();
|
|
359
|
+
deflate.push(new Uint8Array(), true);
|
|
360
|
+
await capacity.wait(controller, signal);
|
|
361
|
+
}
|
|
265
362
|
/**
|
|
266
363
|
* Push one artifact's text into a deflate stream in bounded chunks, never
|
|
267
364
|
* splitting a surrogate pair across a chunk boundary (a lone high surrogate
|
|
268
365
|
* re-encodes as U+FFFD and would silently corrupt the exported artifact).
|
|
269
366
|
* @param deflate - the zip entry's deflate stream.
|
|
270
|
-
* @param content - the
|
|
367
|
+
* @param content - the canonical Session-log text.
|
|
271
368
|
* @param controller - response queue controller.
|
|
272
369
|
* @param capacity - pull-driven response-capacity gate.
|
|
273
370
|
* @param signal - cancellation; throws when aborted.
|
|
@@ -290,21 +387,21 @@ async function pushArtifactChunks(deflate, content, controller, capacity, signal
|
|
|
290
387
|
} while (!finalChunk);
|
|
291
388
|
}
|
|
292
389
|
/**
|
|
293
|
-
* Stream one session-log ZIP as a WHATWG ReadableStream. The root
|
|
294
|
-
*
|
|
390
|
+
* Stream one session-log ZIP as a WHATWG ReadableStream. The root log is read
|
|
391
|
+
* and serialized by the caller before this is called (a missing root or
|
|
295
392
|
* missing services answer cleanly before any byte is produced); each entry is
|
|
296
393
|
* then encoded and deflated in bounded chunks as it is produced, so the
|
|
297
394
|
* archive bytes arrive incrementally. A descendant that fails to read errors
|
|
298
395
|
* the stream (fail-loud, never silent under-export).
|
|
299
396
|
* @param deps - the mounted export services (the caller answered 500 before this runs).
|
|
300
|
-
* @param
|
|
397
|
+
* @param rootContent - the already-serialized root log (first zip entry).
|
|
301
398
|
* @param sessionId - the root session id.
|
|
302
399
|
* @param includeDescendants - whether to include every subagent descendant.
|
|
303
400
|
* @param compressionLevel - validated fflate DEFLATE level for every ZIP entry.
|
|
304
401
|
* @param signal - request cancellation combined with response-consumer cancellation.
|
|
305
402
|
* @returns the zip byte stream.
|
|
306
403
|
*/
|
|
307
|
-
function streamSessionLogZip(deps,
|
|
404
|
+
function streamSessionLogZip(deps, rootContent, sessionId, includeDescendants, compressionLevel, signal) {
|
|
308
405
|
const consumerAbort = new AbortController();
|
|
309
406
|
const producerSignal = AbortSignal.any([signal, consumerAbort.signal]);
|
|
310
407
|
let zip;
|
|
@@ -330,11 +427,12 @@ function streamSessionLogZip(deps, root, sessionId, includeDescendants, compress
|
|
|
330
427
|
zip = archive;
|
|
331
428
|
(async () => {
|
|
332
429
|
try {
|
|
333
|
-
for await (const entry of sessionLogZipEntries(deps,
|
|
430
|
+
for await (const entry of sessionLogZipEntries(deps, rootContent, sessionId, includeDescendants, producerSignal)) {
|
|
334
431
|
const deflate = new ZipDeflate(entry.path, { level: compressionLevel });
|
|
335
432
|
archive.add(deflate);
|
|
336
433
|
if ("content" in entry) await pushArtifactChunks(deflate, entry.content, controller, capacity, producerSignal);
|
|
337
|
-
else await pushBinaryChunks(deflate, entry.data, controller, capacity, producerSignal);
|
|
434
|
+
else if ("data" in entry) await pushBinaryChunks(deflate, entry.data, controller, capacity, producerSignal);
|
|
435
|
+
else await pushStreamChunks(deflate, entry.chunks, controller, capacity, producerSignal);
|
|
338
436
|
}
|
|
339
437
|
archive.end();
|
|
340
438
|
} catch (error) {
|
|
@@ -386,6 +484,7 @@ function apply(ctx, config = {}) {
|
|
|
386
484
|
connectionOf(ctx).fetch.register({
|
|
387
485
|
path: SESSION_LOG_EXPORT_PATH,
|
|
388
486
|
methods: ["GET", "HEAD"],
|
|
487
|
+
requestBody: "buffered",
|
|
389
488
|
fetch: async (request) => {
|
|
390
489
|
const response = await sessionLogExportResponse(ctx, request, config.compressionLevel ?? 6);
|
|
391
490
|
if (request.method === "GET") return response;
|
|
@@ -409,27 +508,26 @@ async function sessionLogExportResponse(ctx, request, compressionLevel) {
|
|
|
409
508
|
const sessionId = brandString(sessionIdValue);
|
|
410
509
|
const deps = sessionLogExportDeps(ctx);
|
|
411
510
|
if (deps.sessionQuery === void 0 || deps.sessionPersistence === void 0 || deps.attachments === void 0) return new Response("session log export is unavailable: missing session-query, session-persistence, or attachments service", { status: 500 });
|
|
412
|
-
if (!deps.sessionPersistence.supportsRawArtifacts) return new Response("session log export is unavailable: the persistence backend does not expose per-session raw artifacts", { status: 501 });
|
|
413
511
|
const ready = {
|
|
414
512
|
sessionQuery: deps.sessionQuery,
|
|
415
513
|
sessionPersistence: deps.sessionPersistence,
|
|
416
514
|
attachments: deps.attachments,
|
|
417
515
|
sessions: deps.sessions
|
|
418
516
|
};
|
|
419
|
-
let
|
|
517
|
+
let rootContent;
|
|
420
518
|
try {
|
|
421
519
|
await flushLiveSessionLog(deps, sessionId, request.signal);
|
|
422
|
-
|
|
520
|
+
rootContent = await readSessionLogText(deps.sessionPersistence, sessionId, request.signal);
|
|
423
521
|
request.signal.throwIfAborted();
|
|
424
522
|
} catch {
|
|
425
523
|
request.signal.throwIfAborted();
|
|
426
|
-
return new Response("session log export failed to
|
|
524
|
+
return new Response("session log export failed to read the stored log", { status: 500 });
|
|
427
525
|
}
|
|
428
|
-
if (
|
|
429
|
-
return new Response(streamSessionLogZip(ready,
|
|
526
|
+
if (rootContent === void 0) return new Response("session not found", { status: 404 });
|
|
527
|
+
return new Response(streamSessionLogZip(ready, rootContent, sessionId, descendantsValue === "true", compressionLevel, request.signal), { headers: {
|
|
430
528
|
"content-type": "application/zip",
|
|
431
529
|
"content-disposition": `attachment; filename="${sessionLogZipFilename(sessionId)}"`
|
|
432
530
|
} });
|
|
433
531
|
}
|
|
434
532
|
//#endregion
|
|
435
|
-
export { Config, DEFAULT_SESSION_LOG_COMPRESSION_LEVEL, SESSION_LOG_EXPORT_PATH, apply, flushLiveSessionLog, inject, name, sessionLogExportDeps, sessionLogZipEntries, sessionLogZipFilename, streamSessionLogZip };
|
|
533
|
+
export { Config, DEFAULT_SESSION_LOG_COMPRESSION_LEVEL, SESSION_LOG_EXPORT_PATH, SESSION_LOG_FILENAME, apply, flushLiveSessionLog, inject, name, readSessionLogText, serializeSessionLog, sessionLogExportDeps, sessionLogZipEntries, sessionLogZipFilename, streamSessionLogZip };
|
package/lib/types/archive.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host-side session-log download: streams one ZIP archive whose files are the
|
|
3
|
-
* sessions'
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* the
|
|
3
|
+
* sessions' logical session logs plus every referenced attachment. Each log
|
|
4
|
+
* is read through a persistence read handle and serialized here as canonical
|
|
5
|
+
* JSONL — one header line, then one line per validated event — so every
|
|
6
|
+
* backend (JSONL, SQLite, future) exports identically. The root log uses the
|
|
7
|
+
* current generation's canonical `session[.vN].jsonl` name; each subagent
|
|
8
|
+
* descendant uses `subagents/<id>/session[.vN].jsonl`; each image referenced by any included log
|
|
9
|
+
* under `media/<attachmentId>.<ext>` (content-addressed, so one archive never
|
|
10
|
+
* duplicates a shared image); each generic file sits under
|
|
11
|
+
* `files/<prefix>/<digest>/<name>` and streams from the attachment store. No
|
|
12
|
+
* manifest is written. Before each live session's log read, the SessionStore
|
|
13
|
+
* flush barrier makes the current in-memory log durable; cold sessions need no
|
|
14
|
+
* barrier. Request abort and response-consumer cancellation share one producer
|
|
15
|
+
* signal and terminate the active compressor.
|
|
14
16
|
* Compression runs on the host with fflate's streaming Zip API, so the archive
|
|
15
17
|
* bytes are produced incrementally and the host never holds the whole archive
|
|
16
18
|
* in one buffer; production waits for consumer pull whenever the response queue
|
|
@@ -21,8 +23,8 @@
|
|
|
21
23
|
import type { Context } from '@deepseek-ai/cordis';
|
|
22
24
|
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
23
25
|
import type { SessionQueryEngine } from '@deepseek-ai/dsh-session-query';
|
|
24
|
-
import type { SessionId, SessionStore } from '@deepseek-ai/dsh-session';
|
|
25
|
-
import type { SessionPersistence
|
|
26
|
+
import type { SessionEvent, SessionHeader, SessionId, SessionStore } from '@deepseek-ai/dsh-session';
|
|
27
|
+
import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence';
|
|
26
28
|
/** Valid fflate DEFLATE levels accepted by session-log export. */
|
|
27
29
|
export type SessionLogCompressionLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
28
30
|
/** Balanced default used when Session export configuration omits a compression level. */
|
|
@@ -49,21 +51,45 @@ export interface SessionLogExportReady {
|
|
|
49
51
|
export declare function sessionLogExportDeps(ctx: Context): SessionLogExportDeps;
|
|
50
52
|
/**
|
|
51
53
|
* Flush one currently live session through the store's authoritative durability
|
|
52
|
-
* barrier immediately before its
|
|
54
|
+
* barrier immediately before its logical log is read. A cold or absent id has
|
|
53
55
|
* no in-memory work to flush.
|
|
54
56
|
* @param deps - export services, including the optional live-session store.
|
|
55
57
|
* @param id - the session whose artifact is about to be read.
|
|
56
58
|
* @param signal - optional cancellation observed around the flush barrier.
|
|
57
59
|
*/
|
|
58
60
|
export declare function flushLiveSessionLog(deps: Pick<SessionLogExportDeps, 'sessions'>, id: SessionId, signal?: AbortSignal): Promise<void>;
|
|
59
|
-
/** One exported file: a
|
|
61
|
+
/** One exported file: a serialized session log or one referenced attachment object. */
|
|
60
62
|
export type SessionLogZipEntry = {
|
|
61
63
|
readonly path: string;
|
|
62
64
|
readonly content: string;
|
|
63
65
|
} | {
|
|
64
66
|
readonly path: string;
|
|
65
67
|
readonly data: Uint8Array;
|
|
68
|
+
} | {
|
|
69
|
+
readonly path: string;
|
|
70
|
+
readonly chunks: AsyncIterable<Uint8Array>;
|
|
66
71
|
};
|
|
72
|
+
/** The current generation's canonical base filename for every exported session log. */
|
|
73
|
+
export declare const SESSION_LOG_FILENAME: string;
|
|
74
|
+
/**
|
|
75
|
+
* Serialize one session's logical log as canonical JSONL text: the header
|
|
76
|
+
* line, then one line per event, with a trailing newline.
|
|
77
|
+
* @param header - the session's immutable header.
|
|
78
|
+
* @param events - the validated committed events in seq order.
|
|
79
|
+
* @returns the JSONL text.
|
|
80
|
+
*/
|
|
81
|
+
export declare function serializeSessionLog(header: SessionHeader, events: readonly SessionEvent[]): string;
|
|
82
|
+
/**
|
|
83
|
+
* Read one session's complete logical log through a read handle and serialize
|
|
84
|
+
* it. The read observes the committed log only — persistence never returns a
|
|
85
|
+
* torn tail — and a handle read after a resolved flush observes at least the
|
|
86
|
+
* flushed prefix.
|
|
87
|
+
* @param persistence - the mounted persistence backend.
|
|
88
|
+
* @param id - the session to read.
|
|
89
|
+
* @param signal - optional cancellation forwarded to the open and read.
|
|
90
|
+
* @returns the serialized JSONL text, or `undefined` when the session does not exist.
|
|
91
|
+
*/
|
|
92
|
+
export declare function readSessionLogText(persistence: SessionPersistence, id: SessionId, signal?: AbortSignal): Promise<string | undefined>;
|
|
67
93
|
/**
|
|
68
94
|
* The export archive filename for one root session.
|
|
69
95
|
* @param sessionId - the root session id (sanitized to one safe path segment).
|
|
@@ -71,36 +97,36 @@ export type SessionLogZipEntry = {
|
|
|
71
97
|
*/
|
|
72
98
|
export declare function sessionLogZipFilename(sessionId: string): string;
|
|
73
99
|
/**
|
|
74
|
-
* Yield the export entries in zip order: the preloaded root
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* after the consumer moves on), then every distinct
|
|
78
|
-
* the included logs
|
|
79
|
-
*
|
|
80
|
-
*
|
|
100
|
+
* Yield the export entries in zip order: the preloaded root log first, then
|
|
101
|
+
* every subagent descendant in lineage order (each flushed when live, read
|
|
102
|
+
* through a persistence read handle right before it is yielded, and dropped
|
|
103
|
+
* after the consumer moves on), then every distinct attachment referenced by
|
|
104
|
+
* the included logs. Images are read and verified as bounded stored objects;
|
|
105
|
+
* generic files remain streamed through the ZIP writer. The host holds at most
|
|
106
|
+
* one descendant log, one image, and one file chunk beyond the root.
|
|
81
107
|
* @param deps - the mounted export services (the caller answered 500 before this runs).
|
|
82
|
-
* @param
|
|
83
|
-
* missing-session path can answer cleanly before streaming starts).
|
|
108
|
+
* @param rootContent - the already-serialized root log (read by the caller so
|
|
109
|
+
* the missing-session path can answer cleanly before streaming starts).
|
|
84
110
|
* @param sessionId - the root session id.
|
|
85
111
|
* @param includeDescendants - whether to include every subagent descendant.
|
|
86
112
|
* @param signal - optional cancellation forwarded to lineage, persistence, and attachment reads.
|
|
87
113
|
* @returns the export entries in zip order.
|
|
88
114
|
*/
|
|
89
|
-
export declare function sessionLogZipEntries(deps: SessionLogExportReady,
|
|
115
|
+
export declare function sessionLogZipEntries(deps: SessionLogExportReady, rootContent: string, sessionId: SessionId, includeDescendants: boolean, signal?: AbortSignal): AsyncGenerator<SessionLogZipEntry>;
|
|
90
116
|
/**
|
|
91
|
-
* Stream one session-log ZIP as a WHATWG ReadableStream. The root
|
|
92
|
-
*
|
|
117
|
+
* Stream one session-log ZIP as a WHATWG ReadableStream. The root log is read
|
|
118
|
+
* and serialized by the caller before this is called (a missing root or
|
|
93
119
|
* missing services answer cleanly before any byte is produced); each entry is
|
|
94
120
|
* then encoded and deflated in bounded chunks as it is produced, so the
|
|
95
121
|
* archive bytes arrive incrementally. A descendant that fails to read errors
|
|
96
122
|
* the stream (fail-loud, never silent under-export).
|
|
97
123
|
* @param deps - the mounted export services (the caller answered 500 before this runs).
|
|
98
|
-
* @param
|
|
124
|
+
* @param rootContent - the already-serialized root log (first zip entry).
|
|
99
125
|
* @param sessionId - the root session id.
|
|
100
126
|
* @param includeDescendants - whether to include every subagent descendant.
|
|
101
127
|
* @param compressionLevel - validated fflate DEFLATE level for every ZIP entry.
|
|
102
128
|
* @param signal - request cancellation combined with response-consumer cancellation.
|
|
103
129
|
* @returns the zip byte stream.
|
|
104
130
|
*/
|
|
105
|
-
export declare function streamSessionLogZip(deps: SessionLogExportReady,
|
|
131
|
+
export declare function streamSessionLogZip(deps: SessionLogExportReady, rootContent: string, sessionId: SessionId, includeDescendants: boolean, compressionLevel: SessionLogCompressionLevel, signal: AbortSignal): ReadableStream<Uint8Array>;
|
|
106
132
|
//# sourceMappingURL=archive.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import Schema from '@deepseek-ai/schemastery';
|
|
4
4
|
import { type SessionLogCompressionLevel } from './archive.ts';
|
|
5
|
-
export { DEFAULT_SESSION_LOG_COMPRESSION_LEVEL, flushLiveSessionLog, sessionLogExportDeps, sessionLogZipEntries, sessionLogZipFilename, streamSessionLogZip, } from './archive.ts';
|
|
5
|
+
export { DEFAULT_SESSION_LOG_COMPRESSION_LEVEL, flushLiveSessionLog, readSessionLogText, serializeSessionLog, SESSION_LOG_FILENAME, sessionLogExportDeps, sessionLogZipEntries, sessionLogZipFilename, streamSessionLogZip, } from './archive.ts';
|
|
6
6
|
export type { SessionLogCompressionLevel, SessionLogExportDeps, SessionLogExportReady, SessionLogZipEntry, } from './archive.ts';
|
|
7
7
|
export declare const name = "session-log-download";
|
|
8
8
|
export declare const inject: string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-session-log-export",
|
|
3
3
|
"description": "Web Session-log export command and shared download dialog",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,32 +33,35 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"fflate": "^0.8.2",
|
|
36
|
+
"@deepseek-ai/dsh-session-format": "^0.1.3-alpha.2",
|
|
36
37
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
37
|
-
"@deepseek-ai/dsh-brand": "^0.1.
|
|
38
|
+
"@deepseek-ai/dsh-brand": "^0.1.3-alpha.2"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
|
-
"@deepseek-ai/cordis": "^4.0.2"
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
42
|
+
"@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.3-alpha.2"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@types/react": "~18.3.1",
|
|
44
47
|
"react": "^18.2.0",
|
|
45
48
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
49
|
+
"@deepseek-ai/dsh-agent": "^0.1.3-alpha.2",
|
|
46
50
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.3",
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-client-
|
|
52
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-session
|
|
59
|
-
"@deepseek-ai/dsh-session-
|
|
60
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.5"
|
|
51
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.3-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-attachment": "^0.1.3-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-client-store": "^0.1.3-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.3-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.3-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.3-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.3-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.3-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.3-alpha.2",
|
|
60
|
+
"@deepseek-ai/dsh-commands": "^0.1.3-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
|
|
62
|
+
"@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
|
|
63
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.3-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-session-query": "^0.1.3-alpha.2"
|
|
62
65
|
},
|
|
63
66
|
"dsh": {
|
|
64
67
|
"client": {
|