@monotykamary/dsh-session-log-export 0.1.0-rc.5
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +49 -0
- package/README.zh.md +49 -0
- package/lib/client.js +268 -0
- package/lib/index.js +24 -0
- package/lib/invariant.js +15 -0
- package/lib/types/client/Dialog.d.ts +20 -0
- package/lib/types/client/HeaderAction.d.ts +9 -0
- package/lib/types/client/controller.d.ts +62 -0
- package/lib/types/client/index.d.ts +23 -0
- package/lib/types/client/locales.d.ts +17 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/invariant.d.ts +11 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/session-query/session-log-export/README.md
|
|
5
|
+
README.md: 051dcb9d1bd77bd743980b533394f016489aa4fd
|
|
6
|
+
README.zh.md: 8e40eaf7e2fc0aaf54dffd4f44b5502f82defa35
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @monotykamary/dsh-session-log-export
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Web Session-log download control over the host-streamed ZIP endpoint owned by `dsh-host-apiproxy`. The Host half registers `/export`; the browser half owns a 111×32 `Session log` action in the Session Header, one download controller, and one modal shared by that button and the slash command. ZIP generation, raw JSONL/zstd reads, descendants, attachments, backpressure, and HTTP error semantics remain owned by the [ApiProxy download implementation](../../host/apiproxy/README.md).
|
|
6
|
+
|
|
7
|
+
## Command contract
|
|
8
|
+
|
|
9
|
+
| Input | Result |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `/export` | Record a human-command lifecycle; the submitting browser receives the local execution acknowledgment and downloads `GET /api/session.export?sessionId=<id>&includeDescendants=true`. |
|
|
12
|
+
| `/export <path>` | Return an error. Browser downloads choose their destination through the browser's ordinary download behavior. |
|
|
13
|
+
|
|
14
|
+
The command is mounted only by the Web bundle. The local `command/executed` acknowledgment triggers the slash download only after a successful `/export` result in the browser that submitted it; other tabs still render the durable command row without repeating the browser side effect. The Header button calls the same controller directly. Both entry paths issue a `HEAD` preflight, then hand the GET URL to the browser download manager without buffering the ZIP in JavaScript; they share in-flight collapsing, cancellation of the preflight on plugin disposal, preparation-error handling, browser save behavior, and the same Modal.
|
|
15
|
+
|
|
16
|
+
The Host download endpoint flushes a live root Session before `readRaw`, so a slash-triggered ZIP includes the `command/run` and `command/done` pair whose acknowledgment started the download. Cold persisted Sessions require no flush.
|
|
17
|
+
|
|
18
|
+
The modal reports preparation, download start, or failure. Closing it does not cancel an in-flight download and does not reopen it when that operation later settles. One Session admits one active download at a time; repeated gestures share that operation.
|
|
19
|
+
|
|
20
|
+
## Composition
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
- id: session-log-download
|
|
24
|
+
name: '@monotykamary/dsh-session-log-export'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Web bundle mounts the package beside `dsh-host-apiproxy`, `dsh-commands`, `dsh-client-ui-commands`, and `dsh-client-ui-conversation`. The package contributes its button and modal to the right-aligned `conversation.session.header.utilities` list, independently of the title-adjacent mode, Subagent, and Task entries in `conversation.session.header.actions`; Trajectory carries no export control.
|
|
28
|
+
|
|
29
|
+
## Model Experience
|
|
30
|
+
|
|
31
|
+
### Human `/export` control
|
|
32
|
+
|
|
33
|
+
#### What the model sees
|
|
34
|
+
|
|
35
|
+
Nothing. `/export` stays on the human-command plane, and the ZIP download does not enter model history.
|
|
36
|
+
|
|
37
|
+
#### Token effect
|
|
38
|
+
|
|
39
|
+
Zero. The command creates no model turn.
|
|
40
|
+
|
|
41
|
+
#### KV Cache effect
|
|
42
|
+
|
|
43
|
+
None. The log-only command lifecycle and browser download do not change the derived request prefix.
|
|
44
|
+
|
|
45
|
+
## Known Limitations and Deferred Work
|
|
46
|
+
|
|
47
|
+
- The download endpoint requires a persistence backend with a per-Session raw artifact. The shipped JSONL backend supports plaintext and zstd artifacts; SQLite export is not included in this change.
|
|
48
|
+
- This is a browser download, not a Host-path writer. The browser chooses the local destination; no Host path or native folder action is returned.
|
|
49
|
+
- The preflight reports failures found before ZIP streaming starts. A descendant or attachment failure after the browser accepts the GET is reported by the browser download manager, not by the modal.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @monotykamary/dsh-session-log-export
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
Web Session 日志下载控制,使用 `dsh-host-apiproxy` 拥有的 Host 流式 ZIP 端点。Host 半包注册 `/export`;浏览器半包在 Session Header 中提供 111×32 的 `Session log` 操作,以及一个供该按钮与斜杠命令共用的下载控制器和弹窗。ZIP 生成、原始 JSONL/zstd 读取、子 Session、附件、背压和 HTTP 错误语义仍由 [ApiProxy 下载实现](../../host/apiproxy/README.md)负责。
|
|
6
|
+
|
|
7
|
+
## 命令约定
|
|
8
|
+
|
|
9
|
+
| 输入 | 结果 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `/export` | 记录一组用户命令生命周期;提交命令的浏览器收到本地执行确认后,下载 `GET /api/session.export?sessionId=<id>&includeDescendants=true`。 |
|
|
12
|
+
| `/export <path>` | 返回错误。浏览器下载通过浏览器的普通下载行为选择目标位置。 |
|
|
13
|
+
|
|
14
|
+
该命令只由 Web bundle 挂载。只有 `/export` 返回成功时,本地 `command/executed` 确认才会在提交命令的浏览器中触发斜杠下载;其他标签页仍会渲染持久命令行,但不会重复执行浏览器副作用。Header 按钮直接调用同一个控制器。两种入口都会先发出 `HEAD` 预检,再把 GET URL 交给浏览器下载管理器,JavaScript 不会缓冲 ZIP;它们共用并发折叠、插件释放时取消预检、准备阶段错误处理、浏览器保存行为和同一个 Modal。
|
|
15
|
+
|
|
16
|
+
Host 下载端点会在 `readRaw` 前 flush 活动的根 Session,因此斜杠命令触发的 ZIP 会包含启动下载的 `command/run` 与 `command/done` 事件对。冷持久化 Session 不需要 flush。
|
|
17
|
+
|
|
18
|
+
弹窗报告准备中、开始下载或失败。关闭弹窗不会取消正在进行的下载;该操作随后完成时也不会重新打开弹窗。每个 Session 同时只允许一项下载,重复操作会共用该任务。
|
|
19
|
+
|
|
20
|
+
## 组合
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
- id: session-log-download
|
|
24
|
+
name: '@monotykamary/dsh-session-log-export'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Web bundle 将本包与 `dsh-host-apiproxy`、`dsh-commands`、`dsh-client-ui-commands` 和 `dsh-client-ui-conversation` 一起挂载。本包把按钮和弹窗贡献到最右侧的 `conversation.session.header.utilities` 列表,与标题旁 `conversation.session.header.actions` 中的模式、Subagent 和 Task 配置项相互独立;Trajectory 不包含导出入口。
|
|
28
|
+
|
|
29
|
+
## 模型体验
|
|
30
|
+
|
|
31
|
+
### 用户 `/export` 控制
|
|
32
|
+
|
|
33
|
+
#### 模型看到什么
|
|
34
|
+
|
|
35
|
+
无。`/export` 留在用户命令平面,ZIP 下载不会进入模型历史。
|
|
36
|
+
|
|
37
|
+
#### Token 影响
|
|
38
|
+
|
|
39
|
+
为零。该命令不创建模型轮次。
|
|
40
|
+
|
|
41
|
+
#### KV Cache 影响
|
|
42
|
+
|
|
43
|
+
无。仅日志命令生命周期和浏览器下载不会改变派生请求前缀。
|
|
44
|
+
|
|
45
|
+
## 已知限制与暂缓事项
|
|
46
|
+
|
|
47
|
+
- 下载端点要求持久化后端具有逐 Session 原始工件。随附 JSONL 后端支持明文和 zstd 工件;本次改动不包含 SQLite 导出。
|
|
48
|
+
- 这是浏览器下载,不是 Host 路径写入。目标位置由浏览器选择,不会返回 Host 路径或原生文件夹操作。
|
|
49
|
+
- 预检只报告 ZIP 开始流式传输前发现的失败。浏览器接受 GET 后发生的子 Session 或附件读取失败由浏览器下载管理器报告,不通过弹窗报告。
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@monotykamary/dsh-session-log-export",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _monotykamary_dsh_client_runtime_client = require("@monotykamary/dsh-client-runtime/client");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let _monotykamary_dsh_client_ui_primitives = require("@monotykamary/dsh-client-ui-primitives");
|
|
10
|
+
//#region lib/types/client/controller.js
|
|
11
|
+
/** Browser download state shared by the Session Header button and `/export`. */
|
|
12
|
+
const INITIAL = { bySession: {} };
|
|
13
|
+
/**
|
|
14
|
+
* Collapse an untrusted Session id into the filename convention owned by the host endpoint.
|
|
15
|
+
* @param sessionId - Session whose archive is downloaded.
|
|
16
|
+
* @returns one safe browser download filename.
|
|
17
|
+
*/
|
|
18
|
+
function sessionLogZipFilename(sessionId) {
|
|
19
|
+
return `dsh-session-${String(sessionId).replace(/[^A-Za-z0-9_-]/g, "_")}.zip`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Hand a Host download URL to the browser download manager.
|
|
23
|
+
* @param url - same-origin Host download URL.
|
|
24
|
+
* @param filename - browser download filename.
|
|
25
|
+
*/
|
|
26
|
+
function downloadUrl(url, filename) {
|
|
27
|
+
const anchor = document.createElement("a");
|
|
28
|
+
anchor.href = url;
|
|
29
|
+
anchor.download = filename;
|
|
30
|
+
anchor.click();
|
|
31
|
+
}
|
|
32
|
+
/** Resolve the browser's Host base with the connection carrier's null-origin fallback. */
|
|
33
|
+
function hostBase() {
|
|
34
|
+
const origin = globalThis.location?.origin;
|
|
35
|
+
return origin !== void 0 && origin !== "null" ? origin : "http://dsh.internal";
|
|
36
|
+
}
|
|
37
|
+
function messageOf(error) {
|
|
38
|
+
return error instanceof Error ? error.message : String(error);
|
|
39
|
+
}
|
|
40
|
+
/** Owns one in-flight browser download per Session and publishes modal state. */
|
|
41
|
+
var SessionLogDownloadController = class {
|
|
42
|
+
fetcher;
|
|
43
|
+
save;
|
|
44
|
+
/** uSES-safe state source shared by every Session-scoped modal contribution. */
|
|
45
|
+
store = (0, _monotykamary_dsh_client_runtime_client.createSnapshotStore)(INITIAL);
|
|
46
|
+
active = /* @__PURE__ */ new Map();
|
|
47
|
+
disposed = false;
|
|
48
|
+
/**
|
|
49
|
+
* @param fetcher - HTTP carrier used to read the host-streamed ZIP.
|
|
50
|
+
* @param save - browser save operation.
|
|
51
|
+
*/
|
|
52
|
+
constructor(fetcher = (input, init) => fetch(input, init), save = downloadUrl) {
|
|
53
|
+
this.fetcher = fetcher;
|
|
54
|
+
this.save = save;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Download one Session tree; concurrent gestures for the same Session share one operation.
|
|
58
|
+
* @param sessionId - root Session whose ZIP includes descendants and attachments.
|
|
59
|
+
* @returns after the browser save starts, an error state is published, or a late post-disposal request is ignored.
|
|
60
|
+
*/
|
|
61
|
+
download(sessionId) {
|
|
62
|
+
const existing = this.active.get(sessionId);
|
|
63
|
+
if (existing !== void 0) return existing.done;
|
|
64
|
+
if (this.disposed) return Promise.resolve();
|
|
65
|
+
const abort = new AbortController();
|
|
66
|
+
const done = this.run(sessionId, abort.signal).finally(() => {
|
|
67
|
+
this.active.delete(sessionId);
|
|
68
|
+
});
|
|
69
|
+
this.active.set(sessionId, {
|
|
70
|
+
abort,
|
|
71
|
+
done
|
|
72
|
+
});
|
|
73
|
+
return done;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Close one Session's dialog without cancelling an in-flight browser download.
|
|
77
|
+
* @param sessionId - Session whose modal closes.
|
|
78
|
+
*/
|
|
79
|
+
dismiss(sessionId) {
|
|
80
|
+
const current = this.store.getSnapshot().bySession[String(sessionId)];
|
|
81
|
+
if (current === void 0 || !current.open) return;
|
|
82
|
+
this.publish(sessionId, {
|
|
83
|
+
...current,
|
|
84
|
+
open: false
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Abort active fetches and reach quiescence.
|
|
89
|
+
* @returns after every active operation settles.
|
|
90
|
+
*/
|
|
91
|
+
async dispose() {
|
|
92
|
+
this.disposed = true;
|
|
93
|
+
const active = [...this.active.values()];
|
|
94
|
+
for (const operation of active) operation.abort.abort();
|
|
95
|
+
await Promise.allSettled(active.map((operation) => operation.done));
|
|
96
|
+
}
|
|
97
|
+
async run(sessionId, signal) {
|
|
98
|
+
this.publish(sessionId, {
|
|
99
|
+
open: true,
|
|
100
|
+
status: "downloading",
|
|
101
|
+
error: null
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
const url = new URL("/api/session.export", hostBase());
|
|
105
|
+
url.searchParams.set("sessionId", sessionId);
|
|
106
|
+
url.searchParams.set("includeDescendants", "true");
|
|
107
|
+
const response = await this.fetcher(url, {
|
|
108
|
+
method: "HEAD",
|
|
109
|
+
signal
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const detail = await response.text().catch(() => "");
|
|
113
|
+
throw new Error(`Export failed: HTTP ${response.status}${detail === "" ? "" : ` ${detail}`}`);
|
|
114
|
+
}
|
|
115
|
+
this.save(url.toString(), sessionLogZipFilename(sessionId));
|
|
116
|
+
const open = this.store.getSnapshot().bySession[String(sessionId)]?.open ?? true;
|
|
117
|
+
this.publish(sessionId, {
|
|
118
|
+
open,
|
|
119
|
+
status: "success",
|
|
120
|
+
error: null
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (signal.aborted) return;
|
|
124
|
+
const open = this.store.getSnapshot().bySession[String(sessionId)]?.open ?? true;
|
|
125
|
+
this.publish(sessionId, {
|
|
126
|
+
open,
|
|
127
|
+
status: "error",
|
|
128
|
+
error: messageOf(error)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
publish(sessionId, entry) {
|
|
133
|
+
this.store.update((state) => {
|
|
134
|
+
state.bySession = {
|
|
135
|
+
...state.bySession,
|
|
136
|
+
[String(sessionId)]: entry
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region lib/types/client/Dialog.js
|
|
143
|
+
/**
|
|
144
|
+
* Modal shared by the Session Header button and this browser's `/export` command.
|
|
145
|
+
* @param props - Session runtime, bound controller state, actions, and localized copy.
|
|
146
|
+
* @returns the modal portal contribution.
|
|
147
|
+
*/
|
|
148
|
+
function SessionLogDownloadDialog({ sessionId, useSessionLogDownload, dismiss, t }) {
|
|
149
|
+
const entry = useSessionLogDownload((state) => state.bySession[String(sessionId)]);
|
|
150
|
+
const status = entry?.status;
|
|
151
|
+
const open = entry?.open === true;
|
|
152
|
+
const error = status === "error" ? entry?.error || t("dialog.commandFailed") : null;
|
|
153
|
+
return (0, react_jsx_runtime.jsx)(_monotykamary_dsh_client_ui_primitives.Modal, {
|
|
154
|
+
open,
|
|
155
|
+
onClose: () => {
|
|
156
|
+
dismiss(sessionId);
|
|
157
|
+
},
|
|
158
|
+
title: status === "downloading" ? t("dialog.preparingTitle") : status === "success" ? t("dialog.successTitle") : t("dialog.errorTitle"),
|
|
159
|
+
description: status === "downloading" ? t("dialog.preparingDescription") : status === "success" ? t("dialog.successDescription") : error ?? t("dialog.commandFailed"),
|
|
160
|
+
closeLabel: t("dialog.close"),
|
|
161
|
+
footer: (0, react_jsx_runtime.jsx)(_monotykamary_dsh_client_ui_primitives.Button, {
|
|
162
|
+
variant: "primary",
|
|
163
|
+
onClick: () => {
|
|
164
|
+
dismiss(sessionId);
|
|
165
|
+
},
|
|
166
|
+
children: t("dialog.close")
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region \0dsh-css:/Users/monotykamary/VCS/working-remote/open-source/deepseek-harness/packages/session-query/session-log-export/src/client/HeaderAction.module.css.mjs
|
|
172
|
+
const css = ".TBrRZG_sessionLogButton{border:1px solid var(--dsw-alias-border-l2);min-width:111px;height:32px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);cursor:pointer;background:0 0;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:400;line-height:20px;display:inline-flex}.TBrRZG_sessionLogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.TBrRZG_sessionLogButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:wait}.TBrRZG_sessionLogButton span,.TBrRZG_sessionLogButton svg{flex:none}.TBrRZG_sessionLogButton span{white-space:nowrap}";
|
|
173
|
+
const tagId = "@monotykamary/dsh-session-log-export/HeaderAction.module.css";
|
|
174
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
175
|
+
const tag = document.createElement("style");
|
|
176
|
+
tag.dataset.plugin = "@monotykamary/dsh-session-log-export";
|
|
177
|
+
tag.dataset.pluginCss = tagId;
|
|
178
|
+
tag.textContent = css;
|
|
179
|
+
document.head.appendChild(tag);
|
|
180
|
+
}
|
|
181
|
+
var HeaderAction_module_css_default = { "sessionLogButton": "TBrRZG_sessionLogButton" };
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region lib/types/client/HeaderAction.js
|
|
184
|
+
/**
|
|
185
|
+
* Render the Session Header export capsule and its shared result dialog.
|
|
186
|
+
* @param props - Session runtime, download controller, and localized dialog copy.
|
|
187
|
+
* @returns the persistent Header action and Session-scoped dialog.
|
|
188
|
+
*/
|
|
189
|
+
function SessionLogDownloadHeaderAction(props) {
|
|
190
|
+
const { sessionId, useSessionLogDownload, request } = props;
|
|
191
|
+
const busy = useSessionLogDownload((state) => state.bySession[String(sessionId)])?.status === "downloading";
|
|
192
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
193
|
+
type: "button",
|
|
194
|
+
className: HeaderAction_module_css_default.sessionLogButton,
|
|
195
|
+
disabled: busy,
|
|
196
|
+
"aria-busy": busy,
|
|
197
|
+
onClick: () => {
|
|
198
|
+
request(sessionId);
|
|
199
|
+
},
|
|
200
|
+
children: [(0, react_jsx_runtime.jsx)("span", { children: "Session log" }), (0, react_jsx_runtime.jsx)(_monotykamary_dsh_client_ui_primitives.IconDownloadOutline16, { size: 12 })]
|
|
201
|
+
}), (0, react_jsx_runtime.jsx)(SessionLogDownloadDialog, { ...props })] });
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region lib/types/client/locales.js
|
|
205
|
+
/** Locale namespace owned by Session export browser feedback. */
|
|
206
|
+
const NS = "session-log-download";
|
|
207
|
+
/** Simplified-Chinese Session export strings. */
|
|
208
|
+
const zh = {
|
|
209
|
+
"dialog.preparingTitle": "正在导出 Session",
|
|
210
|
+
"dialog.preparingDescription": "正在准备包含当前 Session、子 Session 和附件的 ZIP 文件。",
|
|
211
|
+
"dialog.successTitle": "Session 导出已开始下载",
|
|
212
|
+
"dialog.successDescription": "浏览器正在下载 Session ZIP 文件。",
|
|
213
|
+
"dialog.errorTitle": "Session 导出失败",
|
|
214
|
+
"dialog.close": "关闭",
|
|
215
|
+
"dialog.commandFailed": "无法启动 Session 导出。"
|
|
216
|
+
};
|
|
217
|
+
/** English Session export strings. */
|
|
218
|
+
const en = {
|
|
219
|
+
"dialog.preparingTitle": "Exporting Session",
|
|
220
|
+
"dialog.preparingDescription": "Preparing a ZIP containing this Session, its sub-Sessions, and attachments.",
|
|
221
|
+
"dialog.successTitle": "Session download started",
|
|
222
|
+
"dialog.successDescription": "The browser is downloading the Session ZIP.",
|
|
223
|
+
"dialog.errorTitle": "Session export failed",
|
|
224
|
+
"dialog.close": "Close",
|
|
225
|
+
"dialog.commandFailed": "Could not start the Session export."
|
|
226
|
+
};
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region lib/types/client/index.js
|
|
229
|
+
/** Browser plugin owning Session export download state and its shared modal. */
|
|
230
|
+
const inject = ["slots", "locale"];
|
|
231
|
+
/**
|
|
232
|
+
* Provide the download controller and mount its modal into the Session Header.
|
|
233
|
+
* @param ctx - browser context carrying slots and locale services.
|
|
234
|
+
*/
|
|
235
|
+
function apply(ctx) {
|
|
236
|
+
const controller = new SessionLogDownloadController();
|
|
237
|
+
ctx.provide("sessionLogDownload", controller);
|
|
238
|
+
ctx.effect(() => async () => {
|
|
239
|
+
await controller.dispose();
|
|
240
|
+
}, "session-log-download: browser download lifecycle");
|
|
241
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
242
|
+
zh,
|
|
243
|
+
en
|
|
244
|
+
}), "session-log-download: browser dictionaries");
|
|
245
|
+
ctx.on("command/executed", (sessionId, commandName, result) => {
|
|
246
|
+
if (commandName === "export" && result.kind === "success") controller.download(sessionId);
|
|
247
|
+
});
|
|
248
|
+
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
249
|
+
name: "conversation.session.header.utilities",
|
|
250
|
+
id: "session-log-download",
|
|
251
|
+
locale: NS,
|
|
252
|
+
inject: () => ({
|
|
253
|
+
hooks: { sessionLogDownload: controller.store },
|
|
254
|
+
request: (sessionId) => controller.download(sessionId),
|
|
255
|
+
dismiss: (sessionId) => {
|
|
256
|
+
controller.dismiss(sessionId);
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}, SessionLogDownloadHeaderAction));
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
exports.apply = apply;
|
|
263
|
+
exports.inject = inject;
|
|
264
|
+
return module.exports;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region lib/types/index.js
|
|
2
|
+
/** Web Session-log download command over the host endpoint owned by ApiProxy. */
|
|
3
|
+
const name = "session-log-download";
|
|
4
|
+
const inject = ["commands"];
|
|
5
|
+
const REQUESTED = {
|
|
6
|
+
kind: "success",
|
|
7
|
+
text: "Session log download requested."
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Register the Web-only `/export` command that the browser download plugin observes.
|
|
11
|
+
* @param ctx - Host context carrying the human-command registry.
|
|
12
|
+
*/
|
|
13
|
+
function apply(ctx) {
|
|
14
|
+
ctx.effect(() => ctx.commands.register({
|
|
15
|
+
name: "export",
|
|
16
|
+
description: "Download this Session log as a ZIP archive",
|
|
17
|
+
handler: (invocation) => Promise.resolve(invocation.rawInput.trim() === "" ? REQUESTED : {
|
|
18
|
+
kind: "error",
|
|
19
|
+
text: "The Web /export command does not accept a path."
|
|
20
|
+
})
|
|
21
|
+
}), "session-log-download: command");
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/** Package invariant companion for `@monotykamary/dsh-session-log-export`. */
|
|
3
|
+
const PACKAGE_NAME = "@monotykamary/dsh-session-log-export";
|
|
4
|
+
const name = "session-export-invariant";
|
|
5
|
+
const inject = ["invariants"];
|
|
6
|
+
/** No runtime invariant: the command registry owns lifecycle pairing and ApiProxy owns ZIP integrity. */
|
|
7
|
+
const install = () => {};
|
|
8
|
+
/**
|
|
9
|
+
* Register this package's invariant companion.
|
|
10
|
+
* @param ctx - Host context carrying the invariant registry.
|
|
11
|
+
* @returns the registration disposer after setup succeeds.
|
|
12
|
+
*/
|
|
13
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
14
|
+
//#endregion
|
|
15
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ObservableSnapshot, SessionId } from '@monotykamary/dsh-client-runtime/client';
|
|
2
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@monotykamary/dsh-client-ui-slots';
|
|
3
|
+
import type { SessionLogDownloadState } from './controller.ts';
|
|
4
|
+
import { NS } from './locales.ts';
|
|
5
|
+
/** Browser operations and state injected into the Session Header contribution. */
|
|
6
|
+
export interface SessionLogDownloadDialogInjected {
|
|
7
|
+
hooks: {
|
|
8
|
+
sessionLogDownload: ObservableSnapshot<SessionLogDownloadState>;
|
|
9
|
+
};
|
|
10
|
+
request: (sessionId: SessionId) => Promise<void>;
|
|
11
|
+
dismiss: (sessionId: SessionId) => void;
|
|
12
|
+
}
|
|
13
|
+
export type SessionLogDownloadDialogProps = PropsRuntime<'conversation.session.header.utilities'> & PropsLocale<typeof NS> & InjectFace<SessionLogDownloadDialogInjected>;
|
|
14
|
+
/**
|
|
15
|
+
* Modal shared by the Session Header button and this browser's `/export` command.
|
|
16
|
+
* @param props - Session runtime, bound controller state, actions, and localized copy.
|
|
17
|
+
* @returns the modal portal contribution.
|
|
18
|
+
*/
|
|
19
|
+
export declare function SessionLogDownloadDialog({ sessionId, useSessionLogDownload, dismiss, t, }: SessionLogDownloadDialogProps): import("react").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=Dialog.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type SessionLogDownloadDialogProps } from './Dialog.tsx';
|
|
3
|
+
/**
|
|
4
|
+
* Render the Session Header export capsule and its shared result dialog.
|
|
5
|
+
* @param props - Session runtime, download controller, and localized dialog copy.
|
|
6
|
+
* @returns the persistent Header action and Session-scoped dialog.
|
|
7
|
+
*/
|
|
8
|
+
export declare function SessionLogDownloadHeaderAction(props: SessionLogDownloadDialogProps): ReactNode;
|
|
9
|
+
//# sourceMappingURL=HeaderAction.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Browser download state shared by the Session Header button and `/export`. */
|
|
2
|
+
import { type SessionId, type SnapshotStore } from '@monotykamary/dsh-client-runtime/client';
|
|
3
|
+
/** Download phases presented by the shared modal. */
|
|
4
|
+
export type SessionLogDownloadStatus = 'downloading' | 'success' | 'error';
|
|
5
|
+
/** One Session's current download-dialog state. */
|
|
6
|
+
export interface SessionLogDownloadEntry {
|
|
7
|
+
readonly open: boolean;
|
|
8
|
+
readonly status: SessionLogDownloadStatus;
|
|
9
|
+
readonly error: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Download states keyed by the Session whose Header owns the dialog. */
|
|
12
|
+
export interface SessionLogDownloadState {
|
|
13
|
+
bySession: Record<string, SessionLogDownloadEntry | undefined>;
|
|
14
|
+
}
|
|
15
|
+
type Fetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
16
|
+
type Save = (url: string, filename: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Collapse an untrusted Session id into the filename convention owned by the host endpoint.
|
|
19
|
+
* @param sessionId - Session whose archive is downloaded.
|
|
20
|
+
* @returns one safe browser download filename.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sessionLogZipFilename(sessionId: SessionId): string;
|
|
23
|
+
/**
|
|
24
|
+
* Hand a Host download URL to the browser download manager.
|
|
25
|
+
* @param url - same-origin Host download URL.
|
|
26
|
+
* @param filename - browser download filename.
|
|
27
|
+
*/
|
|
28
|
+
export declare function downloadUrl(url: string, filename: string): void;
|
|
29
|
+
/** Owns one in-flight browser download per Session and publishes modal state. */
|
|
30
|
+
export declare class SessionLogDownloadController {
|
|
31
|
+
private readonly fetcher;
|
|
32
|
+
private readonly save;
|
|
33
|
+
/** uSES-safe state source shared by every Session-scoped modal contribution. */
|
|
34
|
+
readonly store: SnapshotStore<SessionLogDownloadState>;
|
|
35
|
+
private readonly active;
|
|
36
|
+
private disposed;
|
|
37
|
+
/**
|
|
38
|
+
* @param fetcher - HTTP carrier used to read the host-streamed ZIP.
|
|
39
|
+
* @param save - browser save operation.
|
|
40
|
+
*/
|
|
41
|
+
constructor(fetcher?: Fetch, save?: Save);
|
|
42
|
+
/**
|
|
43
|
+
* Download one Session tree; concurrent gestures for the same Session share one operation.
|
|
44
|
+
* @param sessionId - root Session whose ZIP includes descendants and attachments.
|
|
45
|
+
* @returns after the browser save starts, an error state is published, or a late post-disposal request is ignored.
|
|
46
|
+
*/
|
|
47
|
+
download(sessionId: SessionId): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Close one Session's dialog without cancelling an in-flight browser download.
|
|
50
|
+
* @param sessionId - Session whose modal closes.
|
|
51
|
+
*/
|
|
52
|
+
dismiss(sessionId: SessionId): void;
|
|
53
|
+
/**
|
|
54
|
+
* Abort active fetches and reach quiescence.
|
|
55
|
+
* @returns after every active operation settles.
|
|
56
|
+
*/
|
|
57
|
+
dispose(): Promise<void>;
|
|
58
|
+
private run;
|
|
59
|
+
private publish;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Browser plugin owning Session export download state and its shared modal. */
|
|
2
|
+
import type { ClientContext } from '@monotykamary/dsh-client-runtime/client';
|
|
3
|
+
import { SessionLogDownloadController } from './controller.ts';
|
|
4
|
+
import { type SessionLogDownloadKey } from './locales.ts';
|
|
5
|
+
declare module '@monotykamary/cordis' {
|
|
6
|
+
interface Context {
|
|
7
|
+
sessionLogDownload: SessionLogDownloadController;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
declare module '@monotykamary/dsh-client-ui-slots' {
|
|
11
|
+
interface LocaleNamespaceMap {
|
|
12
|
+
'session-log-download': SessionLogDownloadKey;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export type { SessionLogDownloadEntry, SessionLogDownloadState } from './controller.ts';
|
|
16
|
+
export declare const inject: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Provide the download controller and mount its modal into the Session Header.
|
|
19
|
+
* @param ctx - browser context carrying slots and locale services.
|
|
20
|
+
*/
|
|
21
|
+
export declare function apply(ctx: ClientContext): void;
|
|
22
|
+
export type { SessionLogDownloadDialogInjected, SessionLogDownloadDialogProps } from './Dialog.tsx';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Locale namespace owned by Session export browser feedback. */
|
|
2
|
+
export declare const NS = "session-log-download";
|
|
3
|
+
/** Simplified-Chinese Session export strings. */
|
|
4
|
+
export declare const zh: {
|
|
5
|
+
readonly 'dialog.preparingTitle': "正在导出 Session";
|
|
6
|
+
readonly 'dialog.preparingDescription': "正在准备包含当前 Session、子 Session 和附件的 ZIP 文件。";
|
|
7
|
+
readonly 'dialog.successTitle': "Session 导出已开始下载";
|
|
8
|
+
readonly 'dialog.successDescription': "浏览器正在下载 Session ZIP 文件。";
|
|
9
|
+
readonly 'dialog.errorTitle': "Session 导出失败";
|
|
10
|
+
readonly 'dialog.close': "关闭";
|
|
11
|
+
readonly 'dialog.commandFailed': "无法启动 Session 导出。";
|
|
12
|
+
};
|
|
13
|
+
/** English Session export strings. */
|
|
14
|
+
export declare const en: Record<keyof typeof zh, string>;
|
|
15
|
+
/** Stable locale keys consumed by the shared modal. */
|
|
16
|
+
export type SessionLogDownloadKey = keyof typeof zh;
|
|
17
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Web Session-log download command over the host endpoint owned by ApiProxy. */
|
|
2
|
+
import type { Context } from '@monotykamary/cordis';
|
|
3
|
+
export declare const name = "session-log-download";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Register the Web-only `/export` command that the browser download plugin observes.
|
|
7
|
+
* @param ctx - Host context carrying the human-command registry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function apply(ctx: Context): void;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Package invariant companion for `@monotykamary/dsh-session-log-export`. */
|
|
2
|
+
import type { Context } from '@monotykamary/cordis';
|
|
3
|
+
export declare const name = "session-export-invariant";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Register this package's invariant companion.
|
|
7
|
+
* @param ctx - Host context carrying the invariant registry.
|
|
8
|
+
* @returns the registration disposer after setup succeeds.
|
|
9
|
+
*/
|
|
10
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
11
|
+
//# sourceMappingURL=invariant.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monotykamary/dsh-session-log-export",
|
|
3
|
+
"description": "Web Session-log export command and shared download dialog",
|
|
4
|
+
"version": "0.1.0-rc.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/session-query/session-log-export"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./lib/types/client/index.d.ts",
|
|
27
|
+
"default": "./lib/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib/index.js",
|
|
34
|
+
"lib/invariant.js",
|
|
35
|
+
"lib/client.js",
|
|
36
|
+
"lib/types/**/*.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^18.2.0",
|
|
41
|
+
"@monotykamary/dsh-client-locale": "^0.1.0-rc.5",
|
|
42
|
+
"@monotykamary/cordis": "^4.0.1",
|
|
43
|
+
"@monotykamary/dsh-client-ui-commands": "^0.1.0-rc.5",
|
|
44
|
+
"@monotykamary/dsh-client-ui-slots": "^0.1.0-rc.5",
|
|
45
|
+
"@monotykamary/dsh-commands": "^0.1.0-rc.5",
|
|
46
|
+
"@monotykamary/dsh-invariants": "^0.1.0-rc.5",
|
|
47
|
+
"@monotykamary/dsh-client-ui-conversation": "^0.1.0-rc.5",
|
|
48
|
+
"@monotykamary/dsh-client-ui-primitives": "^0.1.0-rc.5",
|
|
49
|
+
"@monotykamary/dsh-client-runtime": "^0.1.0-rc.5"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "~18.3.1",
|
|
53
|
+
"react": "^18.2.0",
|
|
54
|
+
"@monotykamary/cordis": "^4.0.1",
|
|
55
|
+
"@monotykamary/dsh-client-locale": "^0.1.0-rc.5",
|
|
56
|
+
"@monotykamary/dsh-agent": "^0.1.0-rc.5",
|
|
57
|
+
"@monotykamary/cordis-plugin-loader": "^1.0.2",
|
|
58
|
+
"@monotykamary/dsh-client-runtime": "^0.1.0-rc.5",
|
|
59
|
+
"@monotykamary/dsh-client-ui-conversation": "^0.1.0-rc.5",
|
|
60
|
+
"@monotykamary/dsh-client-ui-primitives": "^0.1.0-rc.5",
|
|
61
|
+
"@monotykamary/dsh-client-ui-slots": "^0.1.0-rc.5",
|
|
62
|
+
"@monotykamary/dsh-session": "^0.1.0-rc.5",
|
|
63
|
+
"@monotykamary/dsh-invariants": "^0.1.0-rc.5",
|
|
64
|
+
"@monotykamary/dsh-client-ui-commands": "^0.1.0-rc.5",
|
|
65
|
+
"@monotykamary/dsh-commands": "^0.1.0-rc.5"
|
|
66
|
+
},
|
|
67
|
+
"dsh": {
|
|
68
|
+
"client": {
|
|
69
|
+
"inject": [
|
|
70
|
+
"@monotykamary/dsh-client-locale",
|
|
71
|
+
"@monotykamary/dsh-client-runtime",
|
|
72
|
+
"@monotykamary/dsh-client-ui-commands",
|
|
73
|
+
"@monotykamary/dsh-client-ui-conversation"
|
|
74
|
+
],
|
|
75
|
+
"platform": "web"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"scripts": {
|
|
79
|
+
"bundle": "tsdown",
|
|
80
|
+
"watch": "tsdown --watch"
|
|
81
|
+
}
|
|
82
|
+
}
|