@omdp/dsh-archived-sessions 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/client.js +583 -0
- package/lib/index.js +776 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,8 @@ pnpm add @omdp/dsh-archived-sessions -w
|
|
|
65
65
|
|
|
66
66
|
### 变更记录
|
|
67
67
|
|
|
68
|
+
- **0.3.2**(2026-09-10):修两处 fork 遗留——① client 半区的模块 id 仍是 `@muwinds/dsh-archived-sessions`(未随包名改),浏览器端按 `@omdp/...` 找不到模块、设置页不显示;② 会话根目录改为从 `DSH_HOME` 环境变量推导(`<DSH_HOME>/sessions`,缺省 `~/.dsh/sessions`),不再硬编码本机路径。
|
|
69
|
+
- **0.3.1**(2026-09-10):**修复 0.3.0 的发布事故**——0.3.0 的 tarball 里没有 `lib/`(仓库 `.gitignore` 的 `**/lib/` 规则把源码吞了,npm 只打包到 4 个文件),安装后插件加载失败会拖垮 DSH;0.3.1 补回 `lib/index.js` + `lib/client.js`(发布前已核对 tarball 内容)。
|
|
68
70
|
- **0.3.0**(2026-09-10):fork 自 0.2.0;适配 DSH 0.1.5-rc.1(list 快照形状 + 路径自解析);按树删除子会话;孤儿清理;删除路径安全校验。
|
|
69
71
|
|
|
70
72
|
---
|
|
@@ -126,6 +128,8 @@ pnpm add @omdp/dsh-archived-sessions -w
|
|
|
126
128
|
|
|
127
129
|
### Changelog
|
|
128
130
|
|
|
131
|
+
- **0.3.2** (2026-09-10): fixes two fork leftovers — ① the client half's module id was still `@muwinds/dsh-archived-sessions` (not renamed with the package), so the browser could not find the module and the settings page never rendered; ② the session root is now derived from the `DSH_HOME` environment variable (`<DSH_HOME>/sessions`, falling back to `~/.dsh/sessions`) instead of a hard-coded local path.
|
|
132
|
+
- **0.3.1** (2026-09-10): **fixes a 0.3.0 release accident** — the 0.3.0 tarball contained no `lib/` (the repo's `**/lib/` gitignore rule swallowed the source, so npm packed only 4 files); installing it broke the plugin load and could take DSH down. 0.3.1 restores `lib/index.js` + `lib/client.js` (tarball contents verified before publishing).
|
|
129
133
|
- **0.3.0** (2026-09-10): forked from 0.2.0; DSH 0.1.5-rc.1 support (snapshot list shape + self path resolution); tree delete; orphan sweep; deletion path safety check.
|
|
130
134
|
|
|
131
135
|
## License
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@omdp/dsh-archived-sessions",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
|
|
9
|
+
// ---------- styles ----------
|
|
10
|
+
var STYLE_ID = "dsw-arch-style";
|
|
11
|
+
var CSS =
|
|
12
|
+
".dsw-arch-page{font-size:13px;padding:2px 2px 24px;}" +
|
|
13
|
+
".dsw-arch-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px;}" +
|
|
14
|
+
".dsw-arch-page-title{font-weight:600;color:var(--dsw-alias-label-primary);}" +
|
|
15
|
+
".dsw-arch-meta{color:var(--dsw-alias-label-secondary);font-size:12px;}" +
|
|
16
|
+
".dsw-arch-btn{background:transparent;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);border-radius:6px;padding:3px 7px;font-size:12px;cursor:pointer;margin-right:4px;white-space:nowrap;}" +
|
|
17
|
+
".dsw-arch-btn:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-primary);}" +
|
|
18
|
+
".dsw-arch-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);}" +
|
|
19
|
+
".dsw-arch-btn:disabled{opacity:0.45;cursor:default;}" +
|
|
20
|
+
".dsw-arch-table{width:100%;border-collapse:collapse;table-layout:fixed;}" +
|
|
21
|
+
".dsw-arch-table th{text-align:left;font-weight:600;color:var(--dsw-alias-label-secondary);font-size:12px;padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l1);}" +
|
|
22
|
+
".dsw-arch-table td{padding:8px 8px;border-bottom:1px solid var(--dsw-alias-border-l1);vertical-align:top;}" +
|
|
23
|
+
".dsw-arch-table tr:last-child td{border-bottom:none;}" +
|
|
24
|
+
".dsw-arch-table th:nth-child(1),.dsw-arch-table td:nth-child(1){width:32px;text-align:center;padding-left:2px;padding-right:2px;}" +
|
|
25
|
+
".dsw-arch-table th:nth-child(2),.dsw-arch-table td:nth-child(2){width:auto;}" +
|
|
26
|
+
".dsw-arch-table th:nth-child(3),.dsw-arch-table td:nth-child(3){width:104px;}" +
|
|
27
|
+
".dsw-arch-table th:nth-child(4),.dsw-arch-table td:nth-child(4){width:92px;padding-left:2px;padding-right:2px;}" +
|
|
28
|
+
".dsw-arch-size{white-space:nowrap;}" +
|
|
29
|
+
".dsw-arch-actions{white-space:nowrap;}" +
|
|
30
|
+
".dsw-arch-title-click{cursor:pointer;color:var(--dsw-alias-label-primary);}" +
|
|
31
|
+
".dsw-arch-title-click:hover{text-decoration:underline;}" +
|
|
32
|
+
".dsw-arch-id,.dsw-arch-path{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;color:var(--dsw-alias-label-secondary);word-break:break-all;margin-top:2px;}" +
|
|
33
|
+
".dsw-arch-selectbar{display:flex;align-items:center;gap:8px;padding:6px 10px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;font-size:12px;flex-wrap:wrap;}" +
|
|
34
|
+
".dsw-arch-selectbar .dsw-arch-meta{flex:1;}" +
|
|
35
|
+
".dsw-arch-detail{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:10px 12px;font-size:12px;text-align:left;}" +
|
|
36
|
+
".dsw-arch-detail-head{margin-bottom:8px;}" +
|
|
37
|
+
".dsw-arch-detail-body{max-height:360px;overflow:auto;}" +
|
|
38
|
+
".dsw-arch-msg{margin-bottom:8px;white-space:pre-wrap;word-break:break-word;}" +
|
|
39
|
+
".dsw-arch-msg-role{display:inline-block;font-weight:600;margin-right:6px;}" +
|
|
40
|
+
".dsw-arch-msg-user .dsw-arch-msg-role{color:var(--dsw-alias-label-primary);}" +
|
|
41
|
+
".dsw-arch-msg-assistant .dsw-arch-msg-role{color:var(--dsw-alias-brand-primary);}" +
|
|
42
|
+
".dsw-arch-msg-tool .dsw-arch-msg-role{color:var(--dsw-alias-state-warn-primary);}" +
|
|
43
|
+
".dsw-arch-msg-time{color:var(--dsw-alias-label-secondary);font-size:11px;margin-left:6px;}" +
|
|
44
|
+
".dsw-arch-detail-note{color:var(--dsw-alias-label-secondary);font-size:11px;margin-bottom:8px;}" +
|
|
45
|
+
".dsw-arch-error{color:var(--dsw-alias-state-error-primary);font-size:12px;margin-bottom:8px;}" +
|
|
46
|
+
".dsw-arch-empty{color:var(--dsw-alias-label-secondary);padding:28px 0;text-align:center;}" +
|
|
47
|
+
".dsw-arch-badge{display:inline-block;border:1px solid var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);border-radius:4px;padding:1px 6px;font-size:11px;}";
|
|
48
|
+
|
|
49
|
+
function ensureStyle() {
|
|
50
|
+
try {
|
|
51
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
52
|
+
var el = document.createElement("style");
|
|
53
|
+
el.id = STYLE_ID;
|
|
54
|
+
el.textContent = CSS;
|
|
55
|
+
document.head.appendChild(el);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------- api ----------
|
|
62
|
+
function api(action, args) {
|
|
63
|
+
return fetch("/dsh-archived/" + action, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "content-type": "application/json" },
|
|
66
|
+
body: JSON.stringify(args || {})
|
|
67
|
+
}).then(function (res) {
|
|
68
|
+
return res.json().catch(function () {
|
|
69
|
+
return null;
|
|
70
|
+
});
|
|
71
|
+
}).then(function (payload) {
|
|
72
|
+
if (payload === null || typeof payload !== "object") {
|
|
73
|
+
var err = new Error("请求失败:服务返回了无效响应");
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
if (payload.error) {
|
|
77
|
+
var e2 = new Error(String(payload.error));
|
|
78
|
+
throw e2;
|
|
79
|
+
}
|
|
80
|
+
return payload;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------- page component ----------
|
|
85
|
+
function ArchivedSessionsPage() {
|
|
86
|
+
var _useState = react.useState(true);
|
|
87
|
+
var loading = _useState[0];
|
|
88
|
+
var setLoading = _useState[1];
|
|
89
|
+
var _useState2 = react.useState(null);
|
|
90
|
+
var error = _useState2[0];
|
|
91
|
+
var setError = _useState2[1];
|
|
92
|
+
var _useState3 = react.useState([]);
|
|
93
|
+
var items = _useState3[0];
|
|
94
|
+
var setItems = _useState3[1];
|
|
95
|
+
var _useState4 = react.useState(0);
|
|
96
|
+
var total = _useState4[0];
|
|
97
|
+
var setTotal = _useState4[1];
|
|
98
|
+
var _useState5 = react.useState(null);
|
|
99
|
+
var busy = _useState5[0];
|
|
100
|
+
var setBusy = _useState5[1];
|
|
101
|
+
var _useState6 = react.useState(null);
|
|
102
|
+
var confirmId = _useState6[0];
|
|
103
|
+
var setConfirmId = _useState6[1];
|
|
104
|
+
var _useState7 = react.useState([]);
|
|
105
|
+
var selected = _useState7[0];
|
|
106
|
+
var setSelected = _useState7[1];
|
|
107
|
+
var _useState8 = react.useState(null);
|
|
108
|
+
var batchBusy = _useState8[0];
|
|
109
|
+
var setBatchBusy = _useState8[1];
|
|
110
|
+
var _useState9 = react.useState(null);
|
|
111
|
+
var batchConfirm = _useState9[0];
|
|
112
|
+
var setBatchConfirm = _useState9[1];
|
|
113
|
+
var _useState10 = react.useState(null);
|
|
114
|
+
var expandedId = _useState10[0];
|
|
115
|
+
var setExpandedId = _useState10[1];
|
|
116
|
+
var _useState11 = react.useState(null);
|
|
117
|
+
var detail = _useState11[0];
|
|
118
|
+
var setDetail = _useState11[1];
|
|
119
|
+
var _useState12 = react.useState([]);
|
|
120
|
+
var orphans = _useState12[0];
|
|
121
|
+
var setOrphans = _useState12[1];
|
|
122
|
+
var _useState13 = react.useState(false);
|
|
123
|
+
var showOrphans = _useState13[0];
|
|
124
|
+
var setShowOrphans = _useState13[1];
|
|
125
|
+
var _useState14 = react.useState(null);
|
|
126
|
+
var orphanBusy = _useState14[0];
|
|
127
|
+
var setOrphanBusy = _useState14[1];
|
|
128
|
+
var _useState15 = react.useState(false);
|
|
129
|
+
var confirmSweep = _useState15[0];
|
|
130
|
+
var setConfirmSweep = _useState15[1];
|
|
131
|
+
|
|
132
|
+
function describe(err) {
|
|
133
|
+
if (err === null || err === undefined) return String(err);
|
|
134
|
+
if (typeof err === "object" && typeof err.message === "string") return err.message;
|
|
135
|
+
return String(err);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function applyList(res) {
|
|
139
|
+
var next = res && Array.isArray(res.items) ? res.items : [];
|
|
140
|
+
setItems(next);
|
|
141
|
+
setTotal(res && typeof res.totalBytes === "number" ? res.totalBytes : 0);
|
|
142
|
+
var ids = {};
|
|
143
|
+
for (var i = 0; i < next.length; i++) ids[next[i].id] = true;
|
|
144
|
+
setSelected(function (sel) {
|
|
145
|
+
return sel.filter(function (id) { return ids[id]; });
|
|
146
|
+
});
|
|
147
|
+
if (expandedId && !ids[expandedId]) {
|
|
148
|
+
setExpandedId(null);
|
|
149
|
+
setDetail(null);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function load() {
|
|
154
|
+
setLoading(true);
|
|
155
|
+
setError(null);
|
|
156
|
+
api("list", {}).then(function (res) {
|
|
157
|
+
applyList(res);
|
|
158
|
+
setLoading(false);
|
|
159
|
+
}).catch(function (err) {
|
|
160
|
+
setError(describe(err));
|
|
161
|
+
setLoading(false);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
react.useEffect(function () {
|
|
166
|
+
load();
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
function refreshViews() {
|
|
170
|
+
try {
|
|
171
|
+
if (ctx.workspaces && typeof ctx.workspaces.refresh === "function") {
|
|
172
|
+
var p = ctx.workspaces.refresh();
|
|
173
|
+
if (p && typeof p.catch === "function") p.catch(function () {});
|
|
174
|
+
}
|
|
175
|
+
} catch (e) {
|
|
176
|
+
// ignore
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
if (ctx.sessions && typeof ctx.sessions.refresh === "function") {
|
|
180
|
+
var p2 = ctx.sessions.refresh();
|
|
181
|
+
if (p2 && typeof p2.catch === "function") p2.catch(function () {});
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// ignore
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function reloadAfterAction() {
|
|
189
|
+
refreshViews();
|
|
190
|
+
return api("list", {}).then(applyList);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---- orphan management ----
|
|
194
|
+
function loadOrphans() {
|
|
195
|
+
setShowOrphans(true);
|
|
196
|
+
setOrphanBusy("load");
|
|
197
|
+
setError(null);
|
|
198
|
+
api("orphans", {}).then(function (res) {
|
|
199
|
+
setOrphans(res && Array.isArray(res.items) ? res.items : []);
|
|
200
|
+
setOrphanBusy(null);
|
|
201
|
+
}).catch(function (err) {
|
|
202
|
+
setError(describe(err));
|
|
203
|
+
setOrphanBusy(null);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function sweepOrphans() {
|
|
208
|
+
setOrphanBusy("sweep");
|
|
209
|
+
setError(null);
|
|
210
|
+
api("sweep", {}).then(function (res) {
|
|
211
|
+
setConfirmSweep(false);
|
|
212
|
+
setOrphans([]);
|
|
213
|
+
setOrphanBusy(null);
|
|
214
|
+
refreshViews();
|
|
215
|
+
return api("list", {}).then(applyList);
|
|
216
|
+
}).catch(function (err) {
|
|
217
|
+
setError(describe(err));
|
|
218
|
+
setOrphanBusy(null);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---- row operations ----
|
|
223
|
+
function unarchive(id) {
|
|
224
|
+
setBusy("unarchive:" + id);
|
|
225
|
+
setError(null);
|
|
226
|
+
api("unarchive", { sessionId: id })
|
|
227
|
+
.then(function () { return reloadAfterAction(); })
|
|
228
|
+
.catch(function (err) { setError(describe(err)); })
|
|
229
|
+
.finally(function () { setBusy(null); });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Single-button two-step delete: 删除 -> 确认 (auto-reverts after 5s).
|
|
233
|
+
function armDelete(id) {
|
|
234
|
+
setConfirmId(id);
|
|
235
|
+
try {
|
|
236
|
+
if (ctx.timer && typeof ctx.timer.timeout === "function") {
|
|
237
|
+
ctx.timer.timeout(function () {
|
|
238
|
+
setConfirmId(function (cur) { return cur === id ? null : cur; });
|
|
239
|
+
}, 5000);
|
|
240
|
+
}
|
|
241
|
+
} catch (e) {
|
|
242
|
+
// timer unavailable; stay armed until the user acts
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function doDelete(id) {
|
|
247
|
+
setBusy("delete:" + id);
|
|
248
|
+
setError(null);
|
|
249
|
+
api("delete", { sessionId: id })
|
|
250
|
+
.then(function () { return reloadAfterAction(); })
|
|
251
|
+
.catch(function (err) { setError(describe(err)); })
|
|
252
|
+
.finally(function () {
|
|
253
|
+
setBusy(null);
|
|
254
|
+
setConfirmId(null);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---- selection ----
|
|
259
|
+
function toggleSelect(id) {
|
|
260
|
+
setSelected(function (sel) {
|
|
261
|
+
return sel.includes(id) ? sel.filter(function (x) { return x !== id; }) : sel.concat([id]);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function toggleSelectAll() {
|
|
266
|
+
setSelected(function (sel) {
|
|
267
|
+
return sel.length === items.length ? [] : items.map(function (item) { return item.id; });
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---- batch operations ----
|
|
272
|
+
function runBatch(ids, kind) {
|
|
273
|
+
setBatchBusy(kind);
|
|
274
|
+
setError(null);
|
|
275
|
+
var results = [];
|
|
276
|
+
var cursor = Promise.resolve();
|
|
277
|
+
var method = kind === "delete" ? "delete" : "unarchive";
|
|
278
|
+
for (var i = 0; i < ids.length; i++) {
|
|
279
|
+
(function (id) {
|
|
280
|
+
cursor = cursor.then(function () {
|
|
281
|
+
return api(method, { sessionId: id })
|
|
282
|
+
.then(function () { results.push({ id: id, ok: true }); })
|
|
283
|
+
.catch(function (err) { results.push({ id: id, ok: false, error: describe(err) }); });
|
|
284
|
+
});
|
|
285
|
+
})(ids[i]);
|
|
286
|
+
}
|
|
287
|
+
cursor.then(function () {
|
|
288
|
+
var failed = results.filter(function (r) { return !r.ok; });
|
|
289
|
+
if (failed.length > 0) {
|
|
290
|
+
setError((kind === "delete" ? "删除" : "释放") + "失败 " + failed.length + " 个会话:" + failed.map(function (f) { return f.id; }).join(", "));
|
|
291
|
+
}
|
|
292
|
+
refreshViews();
|
|
293
|
+
return api("list", {});
|
|
294
|
+
}).then(applyList)
|
|
295
|
+
.catch(function (err) { setError(describe(err)); })
|
|
296
|
+
.finally(function () {
|
|
297
|
+
setBatchBusy(null);
|
|
298
|
+
setBatchConfirm(null);
|
|
299
|
+
setSelected([]);
|
|
300
|
+
setConfirmId(null);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function releaseAll() { runBatch(items.map(function (item) { return item.id; }), "unarchive"); }
|
|
305
|
+
function clearAll() { runBatch(items.map(function (item) { return item.id; }), "delete"); }
|
|
306
|
+
function releaseSelected() { runBatch(selected, "unarchive"); }
|
|
307
|
+
function deleteSelected() { runBatch(selected, "delete"); }
|
|
308
|
+
|
|
309
|
+
// ---- detail ----
|
|
310
|
+
function toggleDetail(id) {
|
|
311
|
+
if (expandedId === id) {
|
|
312
|
+
setExpandedId(null);
|
|
313
|
+
setDetail(null);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
setExpandedId(id);
|
|
317
|
+
setDetail({ id: id, loading: true, data: null, error: null });
|
|
318
|
+
api("detail", { sessionId: id }).then(function (data) {
|
|
319
|
+
setDetail({ id: id, loading: false, data: data, error: null });
|
|
320
|
+
}).catch(function (err) {
|
|
321
|
+
setDetail({ id: id, loading: false, data: null, error: describe(err) });
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function detailCell() {
|
|
326
|
+
if (!detail) return null;
|
|
327
|
+
if (detail.loading) {
|
|
328
|
+
return react.createElement("div", { className: "dsw-arch-detail" }, "加载会话内容中…");
|
|
329
|
+
}
|
|
330
|
+
if (detail.error) {
|
|
331
|
+
return react.createElement("div", { className: "dsw-arch-detail" }, String(detail.error));
|
|
332
|
+
}
|
|
333
|
+
var d = detail.data;
|
|
334
|
+
if (!d || !Array.isArray(d.messages)) {
|
|
335
|
+
return react.createElement("div", { className: "dsw-arch-detail" }, "没有可显示的内容");
|
|
336
|
+
}
|
|
337
|
+
var headParts = [];
|
|
338
|
+
if (d.createdAt) headParts.push("创建于 " + fmtTime(d.createdAt));
|
|
339
|
+
if (d.cwd) headParts.push(d.cwd);
|
|
340
|
+
var headEl = react.createElement("div", { className: "dsw-arch-detail-head" },
|
|
341
|
+
react.createElement("span", { className: "dsw-arch-page-title" }, "会话内容"),
|
|
342
|
+
react.createElement("span", { className: "dsw-arch-meta" }, " · " + headParts.join(" · "))
|
|
343
|
+
);
|
|
344
|
+
var noteEl = react.createElement("div", { className: "dsw-arch-detail-note" },
|
|
345
|
+
"共 " + String(d.totalEvents) + " 条事件,提取 " + String(d.messageCount) + " 条消息" + (d.truncated ? "(仅显示前 100 条)" : "")
|
|
346
|
+
);
|
|
347
|
+
var msgs = d.messages.map(function (m) {
|
|
348
|
+
var roleLabel = m.role === "user" ? "用户" : m.role === "assistant" ? "助手" : "工具";
|
|
349
|
+
return react.createElement("div", { className: "dsw-arch-msg dsw-arch-msg-" + m.role },
|
|
350
|
+
react.createElement("span", { className: "dsw-arch-msg-role" }, roleLabel),
|
|
351
|
+
react.createElement("span", { className: "dsw-arch-msg-time" }, fmtTime(m.time)),
|
|
352
|
+
react.createElement("div", null, m.text)
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
return react.createElement("div", { className: "dsw-arch-detail" }, headEl, noteEl,
|
|
356
|
+
react.createElement("div", { className: "dsw-arch-detail-body" }, msgs)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---- formatting ----
|
|
361
|
+
function fmtSize(bytes) {
|
|
362
|
+
var n = Number(bytes) || 0;
|
|
363
|
+
if (n <= 0) return "0 B";
|
|
364
|
+
var units = ["B", "KB", "MB", "GB", "TB"];
|
|
365
|
+
var v = n;
|
|
366
|
+
var i = 0;
|
|
367
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
368
|
+
v = v / 1024;
|
|
369
|
+
i = i + 1;
|
|
370
|
+
}
|
|
371
|
+
return (i === 0 ? String(Math.round(v)) : v.toFixed(1)) + " " + units[i];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function fmtTime(ms) {
|
|
375
|
+
if (!ms) return "—";
|
|
376
|
+
try {
|
|
377
|
+
return new Date(ms).toLocaleString();
|
|
378
|
+
} catch (e) {
|
|
379
|
+
return String(ms);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
var anyBusy = busy !== null || batchBusy !== null;
|
|
384
|
+
|
|
385
|
+
var header = react.createElement("div", { className: "dsw-arch-toolbar" },
|
|
386
|
+
react.createElement("span", { className: "dsw-arch-page-title" }, "归档会话"),
|
|
387
|
+
react.createElement("span", { className: "dsw-arch-meta" },
|
|
388
|
+
String(items.length) + " 个会话 · 共 " + fmtSize(total) + (loading ? " · 加载中…" : "") + (batchBusy ? " · 批量操作中…" : "")),
|
|
389
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { load(); } }, "刷新"),
|
|
390
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy || items.length === 0, onClick: function () { releaseAll(); } }, "一键释放"),
|
|
391
|
+
react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: anyBusy || items.length === 0, onClick: function () { setBatchConfirm("clear"); } }, "一键清空"),
|
|
392
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { loadOrphans(); } }, showOrphans ? "隐藏孤儿" : "孤儿清理")
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
var confirmBar = null;
|
|
396
|
+
if (batchConfirm === "clear") {
|
|
397
|
+
confirmBar = react.createElement("div", { className: "dsw-arch-selectbar" },
|
|
398
|
+
react.createElement("span", { className: "dsw-arch-meta", style: { color: "var(--dsw-alias-state-warn-primary)" } }, "确认清空全部 " + String(items.length) + " 个归档会话?此操作会从硬盘删除(含各自的子会话)且不可恢复。"),
|
|
399
|
+
react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: anyBusy, onClick: function () { clearAll(); } }, "确认清空"),
|
|
400
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { setBatchConfirm(null); } }, "取消")
|
|
401
|
+
);
|
|
402
|
+
} else if (batchConfirm === "delete") {
|
|
403
|
+
confirmBar = react.createElement("div", { className: "dsw-arch-selectbar" },
|
|
404
|
+
react.createElement("span", { className: "dsw-arch-meta", style: { color: "var(--dsw-alias-state-warn-primary)" } }, "确认删除选中的 " + String(selected.length) + " 个会话(含各自的子会话)?此操作不可恢复。"),
|
|
405
|
+
react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: anyBusy, onClick: function () { deleteSelected(); } }, "确认删除"),
|
|
406
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { setBatchConfirm(null); } }, "取消")
|
|
407
|
+
);
|
|
408
|
+
} else if (selected.length > 0) {
|
|
409
|
+
confirmBar = react.createElement("div", { className: "dsw-arch-selectbar" },
|
|
410
|
+
react.createElement("span", { className: "dsw-arch-meta" }, "已选择 " + String(selected.length) + " 个会话"),
|
|
411
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { releaseSelected(); } }, "释放选中"),
|
|
412
|
+
react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: anyBusy, onClick: function () { setBatchConfirm("delete"); } }, "删除选中"),
|
|
413
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: anyBusy, onClick: function () { setSelected([]); } }, "取消选择")
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
var body;
|
|
418
|
+
if (loading && items.length === 0) {
|
|
419
|
+
body = react.createElement("div", { className: "dsw-arch-empty" }, "加载中…");
|
|
420
|
+
} else if (items.length === 0) {
|
|
421
|
+
body = react.createElement("div", { className: "dsw-arch-empty" }, "没有已归档的会话");
|
|
422
|
+
} else {
|
|
423
|
+
var allSelected = selected.length === items.length;
|
|
424
|
+
var childCount = {};
|
|
425
|
+
for (var ci = 0; ci < items.length; ci++) {
|
|
426
|
+
var p = items[ci].parentSession;
|
|
427
|
+
if (p) childCount[p] = (childCount[p] || 0) + 1;
|
|
428
|
+
}
|
|
429
|
+
var rows = [];
|
|
430
|
+
for (var i = 0; i < items.length; i++) {
|
|
431
|
+
(function (item) {
|
|
432
|
+
var isBusy = busy === "unarchive:" + item.id || busy === "delete:" + item.id;
|
|
433
|
+
var confirming = confirmId === item.id;
|
|
434
|
+
var metaParts = [];
|
|
435
|
+
if (item.createdAt) metaParts.push("创建于 " + fmtTime(item.createdAt));
|
|
436
|
+
if (item.cwd) metaParts.push(item.cwd);
|
|
437
|
+
var kids = childCount[item.id] || 0;
|
|
438
|
+
if (kids > 0) metaParts.push("含 " + String(kids) + " 个子会话");
|
|
439
|
+
|
|
440
|
+
var checkEl = react.createElement("input", {
|
|
441
|
+
type: "checkbox",
|
|
442
|
+
checked: selected.includes(item.id),
|
|
443
|
+
disabled: anyBusy,
|
|
444
|
+
onChange: function () { toggleSelect(item.id); }
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
var chevron = expandedId === item.id ? "▾ " : "▸ ";
|
|
448
|
+
var titleEl = react.createElement("div", {
|
|
449
|
+
className: "dsw-arch-title-click",
|
|
450
|
+
title: "点击查看会话内容",
|
|
451
|
+
onClick: function () { toggleDetail(item.id); }
|
|
452
|
+
}, chevron + (item.title || "(无标题)"));
|
|
453
|
+
var idEl = react.createElement("div", { className: "dsw-arch-id" }, item.id);
|
|
454
|
+
var metaEl = react.createElement("div", { className: "dsw-arch-meta" }, metaParts.join(" · "));
|
|
455
|
+
var pathEl = item.path
|
|
456
|
+
? react.createElement("div", { className: "dsw-arch-path" }, item.path)
|
|
457
|
+
: null;
|
|
458
|
+
var sizeEl = item.missing
|
|
459
|
+
? react.createElement("span", { className: "dsw-arch-badge" }, "文件缺失")
|
|
460
|
+
: react.createElement("span", { className: "dsw-arch-size" }, fmtSize(item.sizeBytes));
|
|
461
|
+
|
|
462
|
+
var unarchiveBtn = react.createElement("button", {
|
|
463
|
+
className: "dsw-arch-btn",
|
|
464
|
+
disabled: anyBusy || item.missing,
|
|
465
|
+
onClick: function () { unarchive(item.id); }
|
|
466
|
+
}, "释放");
|
|
467
|
+
|
|
468
|
+
var deleteBtn = react.createElement("button", {
|
|
469
|
+
className: "dsw-arch-btn dsw-arch-btn-danger",
|
|
470
|
+
disabled: anyBusy,
|
|
471
|
+
title: confirming ? "再次点击确认删除(5 秒后自动取消)" : "删除(需再次点击确认)",
|
|
472
|
+
onClick: function () { confirming ? doDelete(item.id) : armDelete(item.id); }
|
|
473
|
+
}, confirming ? "确认" : "删除");
|
|
474
|
+
|
|
475
|
+
rows.push(react.createElement("tr", { key: item.id },
|
|
476
|
+
react.createElement("td", null, checkEl),
|
|
477
|
+
react.createElement("td", null, titleEl, idEl, metaEl, pathEl),
|
|
478
|
+
react.createElement("td", null, sizeEl),
|
|
479
|
+
react.createElement("td", { className: "dsw-arch-actions" }, unarchiveBtn, deleteBtn)
|
|
480
|
+
));
|
|
481
|
+
|
|
482
|
+
if (expandedId === item.id) {
|
|
483
|
+
rows.push(react.createElement("tr", { key: item.id + "-detail" },
|
|
484
|
+
react.createElement("td", { colSpan: 4, style: { padding: "4px 10px 10px" } }, detailCell())
|
|
485
|
+
));
|
|
486
|
+
}
|
|
487
|
+
})(items[i]);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
var headCheck = react.createElement("input", {
|
|
491
|
+
type: "checkbox",
|
|
492
|
+
checked: allSelected,
|
|
493
|
+
disabled: anyBusy,
|
|
494
|
+
onChange: function () { toggleSelectAll(); }
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
body = react.createElement("table", { className: "dsw-arch-table" },
|
|
498
|
+
react.createElement("thead", null,
|
|
499
|
+
react.createElement("tr", null,
|
|
500
|
+
react.createElement("th", null, headCheck),
|
|
501
|
+
react.createElement("th", null, "会话"),
|
|
502
|
+
react.createElement("th", null, "磁盘占用"),
|
|
503
|
+
react.createElement("th", null, "操作")
|
|
504
|
+
)
|
|
505
|
+
),
|
|
506
|
+
react.createElement("tbody", null, rows)
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
var errorEl = error
|
|
511
|
+
? react.createElement("div", { className: "dsw-arch-error" }, String(error))
|
|
512
|
+
: null;
|
|
513
|
+
|
|
514
|
+
var orphanPanel = null;
|
|
515
|
+
if (showOrphans) {
|
|
516
|
+
var orphanRows = orphans.map(function (o) {
|
|
517
|
+
return react.createElement("tr", { key: o.id },
|
|
518
|
+
react.createElement("td", null,
|
|
519
|
+
react.createElement("div", { className: "dsw-arch-title-click" }, o.id),
|
|
520
|
+
react.createElement("div", { className: "dsw-arch-id" }, "父会话: " + o.parentSession + (o.cwd ? " · " + o.cwd : ""))
|
|
521
|
+
),
|
|
522
|
+
react.createElement("td", null, o.missing
|
|
523
|
+
? react.createElement("span", { className: "dsw-arch-badge" }, "文件缺失")
|
|
524
|
+
: react.createElement("span", { className: "dsw-arch-size" }, fmtSize(o.sizeBytes)))
|
|
525
|
+
);
|
|
526
|
+
});
|
|
527
|
+
var orphanTotal = 0;
|
|
528
|
+
for (var oi = 0; oi < orphans.length; oi++) orphanTotal += orphans[oi].sizeBytes || 0;
|
|
529
|
+
var sweepBtn;
|
|
530
|
+
if (confirmSweep) {
|
|
531
|
+
sweepBtn = react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: orphanBusy !== null, onClick: function () { sweepOrphans(); } }, "确认清理 " + String(orphans.length) + " 个孤儿会话"),
|
|
532
|
+
react.createElement("button", { className: "dsw-arch-btn", disabled: orphanBusy !== null, onClick: function () { setConfirmSweep(false); } }, "取消");
|
|
533
|
+
} else {
|
|
534
|
+
sweepBtn = react.createElement("button", { className: "dsw-arch-btn dsw-arch-btn-danger", disabled: orphanBusy !== null || orphans.length === 0, onClick: function () { setConfirmSweep(true); } }, "清理孤儿会话");
|
|
535
|
+
}
|
|
536
|
+
orphanPanel = react.createElement("div", { className: "dsw-arch-selectbar", style: { marginTop: "10px", display: "block" } },
|
|
537
|
+
react.createElement("div", { className: "dsw-arch-meta", style: { marginBottom: "6px" } },
|
|
538
|
+
"孤儿会话 " + String(orphans.length) + " 个 · 共 " + fmtSize(orphanTotal) +
|
|
539
|
+
(orphanBusy === "load" ? " · 加载中…" : "") + (orphanBusy === "sweep" ? " · 清理中…" : "")),
|
|
540
|
+
react.createElement("div", null, sweepBtn),
|
|
541
|
+
orphanRows.length > 0
|
|
542
|
+
? react.createElement("table", { className: "dsw-arch-table", style: { marginTop: "8px" } },
|
|
543
|
+
react.createElement("thead", null,
|
|
544
|
+
react.createElement("tr", null,
|
|
545
|
+
react.createElement("th", null, "孤儿会话(父会话已删除)"),
|
|
546
|
+
react.createElement("th", null, "磁盘占用"))),
|
|
547
|
+
react.createElement("tbody", null, orphanRows))
|
|
548
|
+
: (orphanBusy === "load" ? null : react.createElement("div", { className: "dsw-arch-empty" }, "没有孤儿会话"))
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return react.createElement("div", { className: "dsw-arch-page" }, errorEl, header, confirmBar, body, orphanPanel);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ---------- plugin body ----------
|
|
556
|
+
var inject = ["slots", "workspaces", "sessions", "timer"];
|
|
557
|
+
|
|
558
|
+
function apply(ctx) {
|
|
559
|
+
ensureStyle();
|
|
560
|
+
ctx.effect(function () {
|
|
561
|
+
return function () {
|
|
562
|
+
try {
|
|
563
|
+
var el = document.getElementById(STYLE_ID);
|
|
564
|
+
if (el) el.remove();
|
|
565
|
+
} catch (e) {
|
|
566
|
+
// ignore
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}, "ui-archived-sessions: style cleanup");
|
|
570
|
+
|
|
571
|
+
ctx.slots.inject("settings.section", function () {
|
|
572
|
+
return ctx.slots.register(
|
|
573
|
+
{ name: "settings.section", id: "archived-sessions", order: 30, label: function () { return "归档会话"; } },
|
|
574
|
+
function () { return react.createElement(ArchivedSessionsPage); }
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
exports.apply = apply;
|
|
580
|
+
exports.inject = inject;
|
|
581
|
+
return module.exports;
|
|
582
|
+
}
|
|
583
|
+
});
|