@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 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: 8697b0c2c105c928f5325f953b7fb663ae68e18e
6
- README.zh.md: fe60c6ab8cc8d2d5e7515416bb72624584c11b9d
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 `GET /api/session.export?sessionId=<id>&includeDescendants=true` |
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 `/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.
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` | 记录用户命令的生命周期;提交命令的浏览器下载 `GET /api/session.export?sessionId=<id>&includeDescendants=true` |
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
- 两条入口都会先向 `/api/session.export?...` 发出 `HEAD` 预检请求,然后把 GET URL 交给浏览器下载管理器,JavaScript 不缓冲 ZIP。一个控制器按会话持有一项进行中的下载,把并发操作折叠进该任务,并在插件释放时取消预检。弹窗状态存放在按会话键控的快照存储中,因此按钮与命令按会话共享一个弹窗。
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 URL to the browser download manager.
24
- * @param url - same-origin Host download URL.
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 url = new URL("/api/session.export", hostBase());
106
- url.searchParams.set("sessionId", sessionId);
107
- url.searchParams.set("includeDescendants", "true");
108
- const response = await this.fetcher(url, {
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(url.toString(), sessionLogZipFilename(sessionId));
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 more-actions icon button, its download menu, and the shared result dialog.
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.IconDownloadOutline16, {}),
205
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, {}),
205
206
  disabled: busy
206
- }],
207
- onSelect: () => {
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
- request(sessionId);
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.IconEllipsisOutline16, {})
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: { sessionLogDownload: controller.store },
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 every attachment reference inside one content array, descending into
139
- * nested tool results the way the live attachment route does.
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 pending = [];
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 every attachment reference one session event carries, across the same
165
- * carriers the live attachment route scans (direct content, message content,
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
- const data = event.data;
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
- 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);
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 more-actions icon button, its download menu, and the shared result dialog.
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: SessionLogDownloadDialogProps): ReactNode;
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 URL to the browser download manager.
26
- * @param url - same-origin Host download URL.
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 导出已开始下载";
@@ -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
- /** Stable browser download path retained across the transport migration. */
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.6-alpha.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/schemastery": "^3.18.2",
37
- "@deepseek-ai/dsh-brand": "^0.1.6-alpha.1",
38
- "@deepseek-ai/dsh-session-format": "^0.1.6-alpha.1"
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/dsh-session": "^0.1.6-alpha.1",
42
- "@deepseek-ai/cordis": "^4.0.2",
43
- "@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.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.2",
49
- "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
50
- "@deepseek-ai/dsh-agent": "^0.1.6-alpha.1",
51
- "@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
52
- "@deepseek-ai/dsh-attachment": "^0.1.6-alpha.1",
53
- "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
54
- "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
55
- "@deepseek-ai/dsh-client-ui-commands": "^0.1.6-alpha.1",
56
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.1",
57
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
58
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
59
- "@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.1",
60
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
61
- "@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.1",
62
- "@deepseek-ai/dsh-session-query": "^0.1.6-alpha.1",
63
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
64
- "@deepseek-ai/dsh-commands": "^0.1.6-alpha.1"
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": {