@deepseek-ai/dsh-session-log-export 0.1.6-alpha.1 → 0.1.7-alpha.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/README.i18n.yaml +2 -2
- package/README.md +6 -2
- package/README.zh.md +6 -2
- package/lib/client.js +43 -20
- package/lib/index.js +48 -17
- package/lib/types/client/HeaderAction.d.ts +19 -2
- package/lib/types/client/controller.d.ts +4 -2
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/index.d.ts +1 -2
- package/lib/types/routes.d.ts +10 -0
- package/package.json +25 -24
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: fa77afe55a12803b4c637b66768ab8eec671edee
|
|
6
|
+
README.zh.md: a266d3a30426b4efe188711f8322aafca6fb654c
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ English | [中文](README.zh.md)
|
|
|
27
27
|
|
|
28
28
|
Use this package when the Web bundle should let users export a session log. It requires Connection, the command registry, Session query and persistence, and attachments. Mount the plugin, then choose `Download session log` from the Session Header's more-actions menu or type `/export`; the browser downloads `dsh-session-<id>.zip`.
|
|
29
29
|
|
|
30
|
+
When `ui-message-feedback` is mounted, the same menu also offers `Feedback`, which opens its existing Session feedback dialog. Opening or dismissing that form does not export the Session or submit feedback. The feedback row follows the feedback plugin's availability; export remains available independently.
|
|
31
|
+
|
|
30
32
|
### When to choose it
|
|
31
33
|
|
|
32
34
|
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.
|
|
@@ -50,13 +52,15 @@ The Web bundle mounts the package with Connection, `dsh-commands`, `dsh-client-u
|
|
|
50
52
|
|
|
51
53
|
| Input | Result |
|
|
52
54
|
|---|---|
|
|
53
|
-
| `/export` | Records a human-command lifecycle; the submitting browser downloads `
|
|
55
|
+
| `/export` | Records a human-command lifecycle; the submitting browser downloads the document-relative `api/session.export?sessionId=<id>&includeDescendants=true` (Host route `/api/session.export`) |
|
|
54
56
|
| `/export <path>` | An error; browser downloads choose their destination through the browser's ordinary download behavior |
|
|
55
57
|
|
|
56
58
|
### What to expect
|
|
57
59
|
|
|
58
60
|
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
61
|
|
|
62
|
+
Attachment collection reads declared content fields of built-in Session events and completed assistant stream blocks, including flat V4 tool-role messages. Unknown event payloads and unrelated fields remain unchanged in the exported log but do not cause attachment reads.
|
|
63
|
+
|
|
60
64
|
### Failures
|
|
61
65
|
|
|
62
66
|
The dialog shows a preparation error when the preflight fails before ZIP streaming starts — for example an unreachable or misconfigured host endpoint. A descendant or attachment read failure after the browser accepts the GET is reported by the browser download manager, not by the dialog.
|
|
@@ -77,7 +81,7 @@ The package has two halves. The Host half ([`src/index.ts`](src/index.ts)) regis
|
|
|
77
81
|
|
|
78
82
|
### Download flow
|
|
79
83
|
|
|
80
|
-
Both entry paths issue a `HEAD` preflight to
|
|
84
|
+
Both entry paths issue a `HEAD` preflight to the document-relative `api/session.export?...`, then hand the GET route 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
85
|
|
|
82
86
|
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
87
|
|
package/README.zh.md
CHANGED
|
@@ -27,6 +27,8 @@ kind: "package-reference"
|
|
|
27
27
|
|
|
28
28
|
当 Web bundle 需要让用户导出会话日志时使用本包。它需要 Connection、命令注册表、Session 查询与持久化以及附件服务。挂载插件,然后在 Session Header 的更多操作菜单中选择 `下载 Session 日志` 或输入 `/export`;浏览器会下载 `dsh-session-<id>.zip`。
|
|
29
29
|
|
|
30
|
+
挂载 `ui-message-feedback` 时,同一菜单还提供“反馈”,打开已有的 Session 反馈弹窗。打开或关闭该弹窗不会导出 Session 或提交反馈。反馈入口随反馈插件的可用状态显示;导出功能保持独立可用。
|
|
31
|
+
|
|
30
32
|
### 何时选择
|
|
31
33
|
|
|
32
34
|
为需要带可见下载弹窗的面向用户的会话导出的 Web 部署选择它。需要程序化或 Host 侧导出时避免使用:本包产生的是浏览器下载,而非 Host 路径写入。日志从持久化读句柄序列化而来,因此任何已挂载后端都受支持。
|
|
@@ -50,13 +52,15 @@ Web bundle 将本包与 Connection、`dsh-commands`、`dsh-client-ui-commands`
|
|
|
50
52
|
|
|
51
53
|
| 输入 | 结果 |
|
|
52
54
|
|---|---|
|
|
53
|
-
| `/export` |
|
|
55
|
+
| `/export` | 记录用户命令的生命周期;提交命令的浏览器下载文档相对的 `api/session.export?sessionId=<id>&includeDescendants=true`(Host 路由 `/api/session.export`) |
|
|
54
56
|
| `/export <path>` | 错误;浏览器下载通过浏览器的普通下载行为选择目标位置 |
|
|
55
57
|
|
|
56
58
|
### 预期行为
|
|
57
59
|
|
|
58
60
|
弹窗报告三个阶段:准备中、开始下载或失败。关闭弹窗不会取消正在进行的下载,该操作随后结束时弹窗也不会重新打开。每个会话同时只允许一项下载,重复操作共用该任务。导出包含实时会话的最新事件:Host 端点在读取前会 flush 活动的根会话,因此斜杠命令触发的 ZIP 会包含启动下载的 `command/run` 与 `command/done` 事件对;非活动的持久化会话不需要 flush。每份逻辑日志在归档中使用当前 generation 的规范文件名(v0 为 `session.jsonl`,其他版本为 `session.vN.jsonl`),每个子会话目录下也遵循同一规则。图片使用 `media/<attachmentId>.<ext>`,通用文件使用 `files/<digest-prefix>/<digest>/<name>`。通用文件以有界分块读取并压缩,因此导出大型上传文件时不会把它完整缓冲进内存。
|
|
59
61
|
|
|
62
|
+
附件收集读取内置 Session 事件声明的内容字段与已完成的 assistant 流块,包括扁平的 V4 tool 角色消息。未知事件载荷与无关字段在导出日志中保持不变,但不会触发附件读取。
|
|
63
|
+
|
|
60
64
|
### 失败
|
|
61
65
|
|
|
62
66
|
当 ZIP 流式传输开始前的预检失败时——例如 Host 端点不可达或配置错误——弹窗显示准备阶段错误。浏览器接受 GET 后发生的子会话或附件读取失败由浏览器下载管理器报告,不通过弹窗报告。
|
|
@@ -77,7 +81,7 @@ Web bundle 将本包与 Connection、`dsh-commands`、`dsh-client-ui-commands`
|
|
|
77
81
|
|
|
78
82
|
### 下载流程
|
|
79
83
|
|
|
80
|
-
|
|
84
|
+
两条入口都会先向文档相对的 `api/session.export?...` 发出 `HEAD` 预检请求,然后把 GET 路由交给浏览器下载管理器,JavaScript 不缓冲 ZIP。一个控制器按会话持有一项进行中的下载,把并发操作折叠进该任务,并在插件释放时取消预检。弹窗状态存放在按会话键控的快照存储中,因此按钮与命令按会话共享一个弹窗。
|
|
81
85
|
|
|
82
86
|
Host 路由是由该功能拥有的精确 Fetch 路由贡献。Connection 应用 Host/Origin 与浏览器会话检查并桥接流式 `Response`;本包拥有查询校验、活动会话 flush、基于句柄的日志读取与附件读取、ZIP 生成和 HTTP 状态语义。
|
|
83
87
|
|
package/lib/client.js
CHANGED
|
@@ -8,6 +8,9 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
let react = require("react");
|
|
10
10
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
11
|
+
/** Browser-relative form of {@link SESSION_LOG_EXPORT_PATH}. */
|
|
12
|
+
const SESSION_LOG_EXPORT_ROUTE = "/api/session.export".slice(1);
|
|
13
|
+
//#endregion
|
|
11
14
|
//#region lib/types/client/controller.js
|
|
12
15
|
/** Browser download state shared by the Session Header button and `/export`. */
|
|
13
16
|
const INITIAL = { bySession: {} };
|
|
@@ -20,8 +23,9 @@ window.__ModuleLoader__.load({
|
|
|
20
23
|
return `dsh-session-${String(sessionId).replace(/[^A-Za-z0-9_-]/g, "_")}.zip`;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
23
|
-
* Hand a Host download
|
|
24
|
-
*
|
|
26
|
+
* Hand a Host download route to the browser download manager, which resolves it
|
|
27
|
+
* against the document's own base.
|
|
28
|
+
* @param url - document-relative Host download route.
|
|
25
29
|
* @param filename - browser download filename.
|
|
26
30
|
*/
|
|
27
31
|
function downloadUrl(url, filename) {
|
|
@@ -30,11 +34,6 @@ window.__ModuleLoader__.load({
|
|
|
30
34
|
anchor.download = filename;
|
|
31
35
|
anchor.click();
|
|
32
36
|
}
|
|
33
|
-
/** Resolve the browser's Host base with the connection carrier's null-origin fallback. */
|
|
34
|
-
function hostBase() {
|
|
35
|
-
const origin = globalThis.location?.origin;
|
|
36
|
-
return origin !== void 0 && origin !== "null" ? origin : "http://dsh.internal";
|
|
37
|
-
}
|
|
38
37
|
function messageOf(error) {
|
|
39
38
|
return error instanceof Error ? error.message : String(error);
|
|
40
39
|
}
|
|
@@ -102,10 +101,11 @@ window.__ModuleLoader__.load({
|
|
|
102
101
|
error: null
|
|
103
102
|
});
|
|
104
103
|
try {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const route = `${SESSION_LOG_EXPORT_ROUTE}?${new URLSearchParams({
|
|
105
|
+
sessionId,
|
|
106
|
+
includeDescendants: "true"
|
|
107
|
+
}).toString()}`;
|
|
108
|
+
const response = await this.fetcher(route, {
|
|
109
109
|
method: "HEAD",
|
|
110
110
|
signal
|
|
111
111
|
});
|
|
@@ -113,7 +113,7 @@ window.__ModuleLoader__.load({
|
|
|
113
113
|
const detail = await response.text().catch(() => "");
|
|
114
114
|
throw new Error(`Export failed: HTTP ${response.status}${detail === "" ? "" : ` ${detail}`}`);
|
|
115
115
|
}
|
|
116
|
-
this.save(
|
|
116
|
+
this.save(route, sessionLogZipFilename(sessionId));
|
|
117
117
|
const open = this.store.getSnapshot().bySession[String(sessionId)]?.open ?? true;
|
|
118
118
|
this.publish(sessionId, {
|
|
119
119
|
open,
|
|
@@ -183,12 +183,13 @@ window.__ModuleLoader__.load({
|
|
|
183
183
|
//#endregion
|
|
184
184
|
//#region lib/types/client/HeaderAction.js
|
|
185
185
|
/**
|
|
186
|
-
* Render the Session Header
|
|
186
|
+
* Render the Session Header menu with download and optional feedback actions.
|
|
187
187
|
* @param props - Session runtime, download controller, and localized copy.
|
|
188
188
|
* @returns the persistent Header action and Session-scoped dialog.
|
|
189
189
|
*/
|
|
190
190
|
function SessionLogDownloadHeaderAction(props) {
|
|
191
|
-
const { sessionId, useSessionLogDownload, request, t } = props;
|
|
191
|
+
const { sessionId, useSessionLogDownload, useFeedbackAvailable, request, openFeedback, t } = props;
|
|
192
|
+
const feedbackAvailable = useFeedbackAvailable((value) => value);
|
|
192
193
|
const busy = useSessionLogDownload((state) => state.bySession[String(sessionId)])?.status === "downloading";
|
|
193
194
|
const [open, setOpen] = (0, react.useState)(false);
|
|
194
195
|
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
@@ -201,12 +202,17 @@ window.__ModuleLoader__.load({
|
|
|
201
202
|
items: [{
|
|
202
203
|
id: "download",
|
|
203
204
|
label: t("menu.download"),
|
|
204
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
205
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, {}),
|
|
205
206
|
disabled: busy
|
|
206
|
-
}
|
|
207
|
-
|
|
207
|
+
}, ...feedbackAvailable ? [{
|
|
208
|
+
id: "feedback",
|
|
209
|
+
label: t("menu.feedback"),
|
|
210
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPaperPlaneOutlineRegular, {})
|
|
211
|
+
}] : []],
|
|
212
|
+
onSelect: (id) => {
|
|
208
213
|
setOpen(false);
|
|
209
|
-
|
|
214
|
+
if (id === "feedback") openFeedback(sessionId);
|
|
215
|
+
else request(sessionId);
|
|
210
216
|
},
|
|
211
217
|
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
212
218
|
type: "button",
|
|
@@ -218,7 +224,7 @@ window.__ModuleLoader__.load({
|
|
|
218
224
|
onClick: () => {
|
|
219
225
|
setOpen((value) => !value);
|
|
220
226
|
},
|
|
221
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
227
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
222
228
|
})
|
|
223
229
|
}), (0, react_jsx_runtime.jsx)(SessionLogDownloadDialog, { ...props })] });
|
|
224
230
|
}
|
|
@@ -230,6 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
230
236
|
const zh = {
|
|
231
237
|
"header.more": "更多操作",
|
|
232
238
|
"menu.download": "下载 Session 日志",
|
|
239
|
+
"menu.feedback": "反馈",
|
|
233
240
|
"dialog.preparingTitle": "正在导出 Session",
|
|
234
241
|
"dialog.preparingDescription": "正在准备包含当前 Session、子 Session 和附件的 ZIP 文件。",
|
|
235
242
|
"dialog.successTitle": "Session 导出已开始下载",
|
|
@@ -242,6 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
242
249
|
const en = {
|
|
243
250
|
"header.more": "More actions",
|
|
244
251
|
"menu.download": "Download session log",
|
|
252
|
+
"menu.feedback": "Feedback",
|
|
245
253
|
"dialog.preparingTitle": "Exporting Session",
|
|
246
254
|
"dialog.preparingDescription": "Preparing a ZIP containing this Session, its sub-Sessions, and attachments.",
|
|
247
255
|
"dialog.successTitle": "Session download started",
|
|
@@ -268,6 +276,15 @@ window.__ModuleLoader__.load({
|
|
|
268
276
|
zh,
|
|
269
277
|
en
|
|
270
278
|
}), "session-log-download: browser dictionaries");
|
|
279
|
+
const feedbackAvailable = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(false);
|
|
280
|
+
ctx.inject(["feedbackUi"], (scope) => {
|
|
281
|
+
scope.effect(() => {
|
|
282
|
+
feedbackAvailable.set(true);
|
|
283
|
+
return () => {
|
|
284
|
+
feedbackAvailable.set(false);
|
|
285
|
+
};
|
|
286
|
+
}, "session-log-download: feedback availability");
|
|
287
|
+
});
|
|
271
288
|
ctx.on("command/executed", (sessionId, commandName, result) => {
|
|
272
289
|
if (commandName === "export" && result.kind === "success") controller.download(sessionId);
|
|
273
290
|
});
|
|
@@ -276,10 +293,16 @@ window.__ModuleLoader__.load({
|
|
|
276
293
|
id: "session-log-download",
|
|
277
294
|
locale: NS,
|
|
278
295
|
inject: () => ({
|
|
279
|
-
hooks: {
|
|
296
|
+
hooks: {
|
|
297
|
+
sessionLogDownload: controller.store,
|
|
298
|
+
feedbackAvailable
|
|
299
|
+
},
|
|
280
300
|
request: (sessionId) => controller.download(sessionId),
|
|
281
301
|
dismiss: (sessionId) => {
|
|
282
302
|
controller.dismiss(sessionId);
|
|
303
|
+
},
|
|
304
|
+
openFeedback: (sessionId) => {
|
|
305
|
+
ctx.get("feedbackUi")?.openSession(sessionId);
|
|
283
306
|
}
|
|
284
307
|
})
|
|
285
308
|
}, SessionLogDownloadHeaderAction));
|
package/lib/index.js
CHANGED
|
@@ -135,18 +135,14 @@ function fileEntryPath(ref) {
|
|
|
135
135
|
return `files/${digest.slice(0, 2)}/${digest}/${safeName}`;
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
|
-
* Collect
|
|
139
|
-
*
|
|
140
|
-
* @param content - an event content array (or nested tool-result content).
|
|
138
|
+
* Collect direct attachment blocks from one declared V4 content array.
|
|
139
|
+
* @param content - an event or message content array.
|
|
141
140
|
* @param images - image dedupe map keyed by attachment id.
|
|
142
141
|
* @param files - file dedupe map keyed by attachment id and stored name.
|
|
143
142
|
*/
|
|
144
143
|
function collectAttachmentRefs(content, images, files) {
|
|
145
144
|
if (!Array.isArray(content)) return;
|
|
146
|
-
const
|
|
147
|
-
for (const item of content) pending.push(item);
|
|
148
|
-
while (pending.length > 0) {
|
|
149
|
-
const value = pending.pop();
|
|
145
|
+
for (const value of content) {
|
|
150
146
|
if (typeof value !== "object" || value === null || Array.isArray(value)) continue;
|
|
151
147
|
const block = value;
|
|
152
148
|
if (block.type === "image" && typeof block.attachment === "object" && block.attachment !== null) {
|
|
@@ -157,24 +153,51 @@ function collectAttachmentRefs(content, images, files) {
|
|
|
157
153
|
const ref = block.attachment;
|
|
158
154
|
files.set(`${String(ref.attachmentId)}\u0000${ref.name}`, ref);
|
|
159
155
|
}
|
|
160
|
-
if (Array.isArray(block.content)) for (const item of block.content) pending.push(item);
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
/**
|
|
164
|
-
* Collect
|
|
165
|
-
*
|
|
166
|
-
* inserted messages, and completed blocks in embedded Assistant streams).
|
|
159
|
+
* Collect references only from declared first-party content fields and completed
|
|
160
|
+
* Assistant blocks. Unknown events and unrelated payload fields remain opaque.
|
|
167
161
|
* @param event - one parsed JSONL event object.
|
|
168
162
|
* @param images - image dedupe map keyed by attachment id.
|
|
169
163
|
* @param files - file dedupe map keyed by attachment id and stored name.
|
|
170
164
|
*/
|
|
171
165
|
function collectEventAttachmentRefs(event, images, files) {
|
|
172
|
-
|
|
166
|
+
if (typeof event !== "object" || event === null || Array.isArray(event)) return;
|
|
167
|
+
const row = event;
|
|
168
|
+
const data = row.data;
|
|
173
169
|
if (typeof data !== "object" || data === null) return;
|
|
174
170
|
const carrier = data;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
switch (row.type) {
|
|
172
|
+
case "user/message":
|
|
173
|
+
case "tool/ptc-dispatch":
|
|
174
|
+
collectAttachmentRefs(carrier.content, images, files);
|
|
175
|
+
return;
|
|
176
|
+
case "system/message":
|
|
177
|
+
case "developer/message":
|
|
178
|
+
case "tool/result":
|
|
179
|
+
case "team/message/queued":
|
|
180
|
+
collectAttachmentRefs(carrier.message?.content, images, files);
|
|
181
|
+
return;
|
|
182
|
+
case "agent/inbox/spliced": {
|
|
183
|
+
const messages = carrier.inserted;
|
|
184
|
+
if (!Array.isArray(messages)) return;
|
|
185
|
+
for (const message of messages) {
|
|
186
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) continue;
|
|
187
|
+
collectAttachmentRefs(message.content, images, files);
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
case "compaction/summary":
|
|
192
|
+
collectAttachmentRefs(carrier.summary, images, files);
|
|
193
|
+
collectAttachmentRefs(carrier.rawOutput, images, files);
|
|
194
|
+
return;
|
|
195
|
+
case "assistant/message":
|
|
196
|
+
collectAttachmentRefs(carrier.message?.content, images, files);
|
|
197
|
+
break;
|
|
198
|
+
case "assistant/attempt": break;
|
|
199
|
+
default: return;
|
|
200
|
+
}
|
|
178
201
|
if (carrier.stream !== void 0) {
|
|
179
202
|
for (const record of carrier.stream) if (record.type === "chunk" && record.chunk?.type === "block-end") collectAttachmentRefs([record.chunk.block], images, files);
|
|
180
203
|
}
|
|
@@ -455,12 +478,20 @@ function streamSessionLogZip(deps, rootContent, sessionId, includeDescendants, c
|
|
|
455
478
|
});
|
|
456
479
|
}
|
|
457
480
|
//#endregion
|
|
481
|
+
//#region lib/types/routes.js
|
|
482
|
+
/**
|
|
483
|
+
* The absolute pathname the Host registers the export under and the
|
|
484
|
+
* document-relative form the browser addresses; see
|
|
485
|
+
* .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md.
|
|
486
|
+
*/
|
|
487
|
+
/** Absolute registration path for the ZIP download route. */
|
|
488
|
+
const SESSION_LOG_EXPORT_PATH = "/api/session.export";
|
|
489
|
+
SESSION_LOG_EXPORT_PATH.slice(1);
|
|
490
|
+
//#endregion
|
|
458
491
|
//#region lib/types/index.js
|
|
459
492
|
/** Session-log download command and Host-owned streaming route. */
|
|
460
493
|
const name = "session-log-download";
|
|
461
494
|
const inject = ["commands", "connection"];
|
|
462
|
-
/** Stable browser download path retained across the transport migration. */
|
|
463
|
-
const SESSION_LOG_EXPORT_PATH = "/api/session.export";
|
|
464
495
|
/** Validate Session-log archive configuration. */
|
|
465
496
|
const Config = Schema.object({ compressionLevel: Schema.number().step(1).min(0).max(9).default(6) });
|
|
466
497
|
const REQUESTED = {
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
2
5
|
import { type SessionLogDownloadDialogProps } from './Dialog.tsx';
|
|
6
|
+
import type { SessionLogDownloadDialogInjected } from './Dialog.tsx';
|
|
7
|
+
/** Session download controls with observable feedback availability and a Session feedback action. */
|
|
8
|
+
export interface SessionLogDownloadHeaderInjected extends SessionLogDownloadDialogInjected {
|
|
9
|
+
hooks: SessionLogDownloadDialogInjected['hooks'] & {
|
|
10
|
+
feedbackAvailable: ObservableSnapshot<boolean>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Open the existing Session feedback draft without recording feedback; no-op after the feedback plugin unloads.
|
|
14
|
+
* @param sessionId - Session whose feedback form to open.
|
|
15
|
+
*/
|
|
16
|
+
openFeedback: (sessionId: SessionId) => void;
|
|
17
|
+
}
|
|
18
|
+
/** Session download props plus the optional feedback action. */
|
|
19
|
+
export type SessionLogDownloadHeaderProps = SessionLogDownloadDialogProps & InjectFace<SessionLogDownloadHeaderInjected>;
|
|
3
20
|
/**
|
|
4
|
-
* Render the Session Header
|
|
21
|
+
* Render the Session Header menu with download and optional feedback actions.
|
|
5
22
|
* @param props - Session runtime, download controller, and localized copy.
|
|
6
23
|
* @returns the persistent Header action and Session-scoped dialog.
|
|
7
24
|
*/
|
|
8
|
-
export declare function SessionLogDownloadHeaderAction(props:
|
|
25
|
+
export declare function SessionLogDownloadHeaderAction(props: SessionLogDownloadHeaderProps): ReactNode;
|
|
9
26
|
//# sourceMappingURL=HeaderAction.d.ts.map
|
|
@@ -13,6 +13,7 @@ export interface SessionLogDownloadEntry {
|
|
|
13
13
|
export interface SessionLogDownloadState {
|
|
14
14
|
bySession: Record<string, SessionLogDownloadEntry | undefined>;
|
|
15
15
|
}
|
|
16
|
+
/** HTTP carrier for the export route. */
|
|
16
17
|
type Fetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
17
18
|
type Save = (url: string, filename: string) => void;
|
|
18
19
|
/**
|
|
@@ -22,8 +23,9 @@ type Save = (url: string, filename: string) => void;
|
|
|
22
23
|
*/
|
|
23
24
|
export declare function sessionLogZipFilename(sessionId: SessionId): string;
|
|
24
25
|
/**
|
|
25
|
-
* Hand a Host download
|
|
26
|
-
*
|
|
26
|
+
* Hand a Host download route to the browser download manager, which resolves it
|
|
27
|
+
* against the document's own base.
|
|
28
|
+
* @param url - document-relative Host download route.
|
|
27
29
|
* @param filename - browser download filename.
|
|
28
30
|
*/
|
|
29
31
|
export declare function downloadUrl(url: string, filename: string): void;
|
|
@@ -4,6 +4,7 @@ export declare const NS = "session-log-download";
|
|
|
4
4
|
export declare const zh: {
|
|
5
5
|
readonly 'header.more': "更多操作";
|
|
6
6
|
readonly 'menu.download': "下载 Session 日志";
|
|
7
|
+
readonly 'menu.feedback': "反馈";
|
|
7
8
|
readonly 'dialog.preparingTitle': "正在导出 Session";
|
|
8
9
|
readonly 'dialog.preparingDescription': "正在准备包含当前 Session、子 Session 和附件的 ZIP 文件。";
|
|
9
10
|
readonly 'dialog.successTitle': "Session 导出已开始下载";
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,8 +6,7 @@ export { DEFAULT_SESSION_LOG_COMPRESSION_LEVEL, flushLiveSessionLog, readSession
|
|
|
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[];
|
|
9
|
-
|
|
10
|
-
export declare const SESSION_LOG_EXPORT_PATH = "/api/session.export";
|
|
9
|
+
export { SESSION_LOG_EXPORT_PATH } from './routes.ts';
|
|
11
10
|
/** Session-log archive policy. */
|
|
12
11
|
export interface Config {
|
|
13
12
|
/** DEFLATE level for each ZIP entry. @default 6 */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The absolute pathname the Host registers the export under and the
|
|
3
|
+
* document-relative form the browser addresses; see
|
|
4
|
+
* .agents/notes/implemented/architecture/2026-09-14-web-document-relative-app-routes.md.
|
|
5
|
+
*/
|
|
6
|
+
/** Absolute registration path for the ZIP download route. */
|
|
7
|
+
export declare const SESSION_LOG_EXPORT_PATH = "/api/session.export";
|
|
8
|
+
/** Browser-relative form of {@link SESSION_LOG_EXPORT_PATH}. */
|
|
9
|
+
export declare const SESSION_LOG_EXPORT_ROUTE: string;
|
|
10
|
+
//# sourceMappingURL=routes.d.ts.map
|
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.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,35 +33,36 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"fflate": "^0.8.2",
|
|
36
|
-
"@deepseek-ai/
|
|
37
|
-
"@deepseek-ai/
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
36
|
+
"@deepseek-ai/dsh-session-format": "^0.1.7-alpha.1",
|
|
37
|
+
"@deepseek-ai/schemastery": "^3.18.3",
|
|
38
|
+
"@deepseek-ai/dsh-brand": "^0.1.7-alpha.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/
|
|
42
|
-
"@deepseek-ai/
|
|
43
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
42
|
+
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
|
43
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.7-alpha.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/react": "~18.3.1",
|
|
47
47
|
"react": "^18.2.0",
|
|
48
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
49
|
-
"@deepseek-ai/cordis-plugin-loader": "^1.0.
|
|
50
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-client-
|
|
54
|
-
"@deepseek-ai/dsh-client-
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-session
|
|
62
|
-
"@deepseek-ai/dsh-session-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
49
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.4",
|
|
50
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.7-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-alpha.1",
|
|
53
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.7-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-message-feedback": "^0.1.7-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.7-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-commands": "^0.1.7-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.7-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-session-query": "^0.1.7-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-attachment": "^0.1.7-alpha.1"
|
|
65
66
|
},
|
|
66
67
|
"dsh": {
|
|
67
68
|
"client": {
|