dsh-cool-session 0.0.0-stage → 0.1.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -2
  3. package/README.zh.md +28 -0
  4. package/cordis.patch.yml +7 -0
  5. package/lib/client.js +2152 -0
  6. package/lib/index.js +690 -0
  7. package/lib/types/client/api.d.ts +30 -0
  8. package/lib/types/client/api.d.ts.map +1 -0
  9. package/lib/types/client/archive.d.ts +43 -0
  10. package/lib/types/client/archive.d.ts.map +1 -0
  11. package/lib/types/client/icons.d.ts +32 -0
  12. package/lib/types/client/icons.d.ts.map +1 -0
  13. package/lib/types/client/index.d.ts +28 -0
  14. package/lib/types/client/index.d.ts.map +1 -0
  15. package/lib/types/client/locales/de.d.ts +85 -0
  16. package/lib/types/client/locales/de.d.ts.map +1 -0
  17. package/lib/types/client/locales/en.d.ts +87 -0
  18. package/lib/types/client/locales/en.d.ts.map +1 -0
  19. package/lib/types/client/locales/es.d.ts +85 -0
  20. package/lib/types/client/locales/es.d.ts.map +1 -0
  21. package/lib/types/client/locales/fr.d.ts +87 -0
  22. package/lib/types/client/locales/fr.d.ts.map +1 -0
  23. package/lib/types/client/locales/ja.d.ts +87 -0
  24. package/lib/types/client/locales/ja.d.ts.map +1 -0
  25. package/lib/types/client/locales/pt.d.ts +86 -0
  26. package/lib/types/client/locales/pt.d.ts.map +1 -0
  27. package/lib/types/client/locales/ru.d.ts +89 -0
  28. package/lib/types/client/locales/ru.d.ts.map +1 -0
  29. package/lib/types/client/locales/zh.d.ts +87 -0
  30. package/lib/types/client/locales/zh.d.ts.map +1 -0
  31. package/lib/types/client/locales.d.ts +729 -0
  32. package/lib/types/client/locales.d.ts.map +1 -0
  33. package/lib/types/client/nav-icon.d.ts +30 -0
  34. package/lib/types/client/nav-icon.d.ts.map +1 -0
  35. package/lib/types/client/reason.d.ts +41 -0
  36. package/lib/types/client/reason.d.ts.map +1 -0
  37. package/lib/types/client/session-menu.d.ts +65 -0
  38. package/lib/types/client/session-menu.d.ts.map +1 -0
  39. package/lib/types/client/settings-section.d.ts +43 -0
  40. package/lib/types/client/settings-section.d.ts.map +1 -0
  41. package/lib/types/client/styles.d.ts +26 -0
  42. package/lib/types/client/styles.d.ts.map +1 -0
  43. package/lib/types/host/deletion.d.ts +26 -0
  44. package/lib/types/host/deletion.d.ts.map +1 -0
  45. package/lib/types/host/index.d.ts +20 -0
  46. package/lib/types/host/index.d.ts.map +1 -0
  47. package/lib/types/host/refusal.d.ts +25 -0
  48. package/lib/types/host/refusal.d.ts.map +1 -0
  49. package/lib/types/host/routes.d.ts +28 -0
  50. package/lib/types/host/routes.d.ts.map +1 -0
  51. package/lib/types/shared/contract.d.ts +61 -0
  52. package/lib/types/shared/contract.d.ts.map +1 -0
  53. package/package.json +69 -3
package/lib/client.js ADDED
@@ -0,0 +1,2152 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-cool-session",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
+ let react = require("react");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ // src/client/api.ts
11
+ const BASE = "/dsh-cool-session/api";
12
+ /**
13
+ * POST one JSON body and read a JSON answer.
14
+ *
15
+ * @param path - route path below {@link BASE}.
16
+ * @param body - the request body.
17
+ * @returns the parsed payload, or an `{ ok: false }` result when the transport failed.
18
+ */
19
+ async function postJson(path, body) {
20
+ try {
21
+ const response = await fetch(`${BASE}${path}`, {
22
+ method: "POST",
23
+ headers: { "content-type": "application/json" },
24
+ body: JSON.stringify(body),
25
+ credentials: "same-origin"
26
+ });
27
+ const payload = await response.json().catch(() => null);
28
+ if (payload === null || typeof payload !== "object") return {
29
+ ok: false,
30
+ error: `HTTP ${response.status}`
31
+ };
32
+ return payload;
33
+ } catch (error) {
34
+ return {
35
+ ok: false,
36
+ error: error instanceof Error ? error.message : String(error)
37
+ };
38
+ }
39
+ }
40
+ /**
41
+ * Ask the Host to delete one Session and every subagent Session beneath it.
42
+ *
43
+ * The Host stops running work first, then removes the artifacts.
44
+ *
45
+ * @param sessionId - the root Session to delete.
46
+ * @returns the deletion summary, or a coded refusal.
47
+ */
48
+ function deleteSession(sessionId) {
49
+ return postJson("/delete", { sessionId });
50
+ }
51
+ /**
52
+ * Whether an answer means the Host half predates the batch route.
53
+ *
54
+ * The browser bundle reloads with the page while the Host half lives until the
55
+ * process restarts, so a freshly updated plugin can be newer than the routes it
56
+ * is talking to. A missing route is not a deletion failure: the single route is
57
+ * still there and can finish the same selection.
58
+ *
59
+ * @param payload - the answer to inspect.
60
+ * @returns true when `deleteSessions` should fall back to single requests.
61
+ */
62
+ function batchRouteMissing(payload) {
63
+ return payload.error === "HTTP 404" || payload.error === "HTTP 405";
64
+ }
65
+ /**
66
+ * Sessions per batch request.
67
+ *
68
+ * Smaller than the Host's own ceiling so a very large selection is a sequence
69
+ * of bounded requests; each batch still stops and removes its own Sessions in
70
+ * order, so the persistence writer never sees two deletions at once.
71
+ */
72
+ const DELETE_BATCH_SIZE = 100;
73
+ /**
74
+ * Ask the Host to delete several Sessions.
75
+ *
76
+ * The Host runs the same stop-then-remove sequence as {@link deleteSession} for
77
+ * each id, sequentially, and answers one result per id. When this Host half does
78
+ * not serve the batch route yet, the same work is done through the single route
79
+ * so the panel keeps working across a live plugin upgrade and a later restart.
80
+ *
81
+ * @param sessionIds - the root Sessions to delete, in request order.
82
+ * @returns the per-session summaries, or a request-level refusal.
83
+ */
84
+ async function deleteSessions(sessionIds) {
85
+ const results = [];
86
+ for (let start = 0; start < sessionIds.length; start += DELETE_BATCH_SIZE) {
87
+ const chunk = sessionIds.slice(start, start + DELETE_BATCH_SIZE);
88
+ const payload = await postJson("/delete-many", { sessionIds: chunk });
89
+ if (batchRouteMissing(payload)) {
90
+ for (const sessionId of chunk) results.push(await deleteSession(sessionId));
91
+ continue;
92
+ }
93
+ if (!Array.isArray(payload.results)) return {
94
+ ok: false,
95
+ error: typeof payload.error === "string" && payload.error !== "" ? payload.error : "the delete request failed",
96
+ results
97
+ };
98
+ results.push(...payload.results);
99
+ }
100
+ return {
101
+ ok: results.every((result) => result.ok),
102
+ results
103
+ };
104
+ }
105
+ // src/client/reason.ts
106
+ /**
107
+ * The activity families a dictionary names, keyed by the Host's kind.
108
+ *
109
+ * The Host sends `turn`, `subagent`, `job` and `schedule` because those are the
110
+ * provider identifiers it works with, not prose. Only this half knows the
111
+ * language to read them in, so the translation happens here; a kind a future
112
+ * build adds stays as its identifier rather than disappearing.
113
+ */
114
+ const ACTIVITY_KEYS = {
115
+ turn: "activity.turn",
116
+ subagent: "activity.subagent",
117
+ job: "activity.job",
118
+ schedule: "activity.schedule"
119
+ };
120
+ /**
121
+ * Word the work a refusal names, in the active language.
122
+ *
123
+ * @param t - the page's translate seat.
124
+ * @param raw - the Host's comma-separated kind list, when it named any.
125
+ * @returns the phrase to put in the sentence's `{activity}` position.
126
+ */
127
+ function activityPhrase(t, raw) {
128
+ const kinds = typeof raw === "string" ? raw.split(",").map((kind) => kind.trim()).filter((kind) => kind !== "") : [];
129
+ if (kinds.length === 0) return t("error.activityFallback");
130
+ return kinds.map((kind) => {
131
+ const key = ACTIVITY_KEYS[kind];
132
+ return key === void 0 ? kind : t(key);
133
+ }).join(t("activity.separator"));
134
+ }
135
+ /**
136
+ * Word one coded refusal as a sentence.
137
+ *
138
+ * @param t - the page's translate seat.
139
+ * @param failure - the refusal code and the values its sentence interpolates.
140
+ * @returns the sentence to show.
141
+ */
142
+ function failureText(t, failure) {
143
+ if (failure.code === "delete-active") return t("error.delete-active", {
144
+ ...failure.params,
145
+ activity: activityPhrase(t, failure.params?.activity)
146
+ });
147
+ return t(`error.${failure.code}`, failure.params);
148
+ }
149
+ /**
150
+ * Word one refused answer.
151
+ *
152
+ * @param t - the page's translate seat.
153
+ * @param payload - the Host answer.
154
+ * @param fallback - the entry to state when the answer carried no reason.
155
+ * @returns the reason to show.
156
+ */
157
+ function reasonOf(t, payload, fallback) {
158
+ if (payload.failure !== void 0) {
159
+ const sentence = failureText(t, payload.failure);
160
+ if (payload.failure.code === "delete-partial" && typeof payload.error === "string" && payload.error !== "") return `${sentence} ${payload.error}`;
161
+ return sentence;
162
+ }
163
+ if (payload.error === "HTTP 405") return t("error.host-stale");
164
+ return typeof payload.error === "string" && payload.error !== "" ? payload.error : t(fallback);
165
+ }
166
+ // src/client/session-menu.tsx
167
+ /**
168
+ * The native Session menu's delete row and its confirmation overlay.
169
+ *
170
+ * The row is contributed to `sidebar.workspaces.session.menu.item` by builds
171
+ * that have no `contextMenus` service; the client entry registers a plain-data
172
+ * action instead wherever that service exists, because the shipped Archive row
173
+ * is a footer action and only a footer action can follow it. Menus unmount as
174
+ * soon as they close, so either row only records a request; the frame-wide
175
+ * `shell.overlay` entry owns the dialog's state and the Host call. That split is
176
+ * also what lets the dialog survive the menu's portal disappearing on click.
177
+ *
178
+ * Deletion is permanent, so the confirmation is the only path: it asks before
179
+ * the Host stops running work and removes the artifacts, and disables itself
180
+ * while that happens.
181
+ */
182
+ let pendingDelete = null;
183
+ const deleteListeners = /* @__PURE__ */ new Set();
184
+ function emitDelete() {
185
+ for (const listener of [...deleteListeners]) listener();
186
+ }
187
+ function subscribeDelete(listener) {
188
+ deleteListeners.add(listener);
189
+ return () => {
190
+ deleteListeners.delete(listener);
191
+ };
192
+ }
193
+ function deleteSnapshot() {
194
+ return pendingDelete;
195
+ }
196
+ /**
197
+ * Open the delete confirmation for one Session.
198
+ *
199
+ * @param sessionId - the Session the menu row belongs to.
200
+ */
201
+ function requestDeleteSession(sessionId) {
202
+ pendingDelete = { sessionId };
203
+ emitDelete();
204
+ }
205
+ /** Dismiss the confirmation without deleting. */
206
+ function clearDeleteRequest() {
207
+ if (pendingDelete === null) return;
208
+ pendingDelete = null;
209
+ emitDelete();
210
+ }
211
+ /** Whether the menu service has taken over the Delete row. */
212
+ let serviceOwnsRow = false;
213
+ /**
214
+ * Stop drawing the legacy Slot row.
215
+ *
216
+ * The client entry installs that row when `apply` runs before `contextMenus` is
217
+ * mounted; the service then contributes Delete as a footer action so it can
218
+ * follow Archive. This flag keeps the two paths from drawing the row twice.
219
+ */
220
+ function retireLegacyDeleteRow() {
221
+ serviceOwnsRow = true;
222
+ }
223
+ /**
224
+ * The legacy delete row for builds without `contextMenus`, placed after Copy
225
+ * Session ID and separated from the shipped group. Selecting it closes the menu
226
+ * and raises the confirmation overlay.
227
+ *
228
+ * @param props - the row identity, menu hook, and locale seat.
229
+ * @returns the menu row, or null once the menu service owns it.
230
+ */
231
+ function SessionDeleteMenuItem({ sessionId, useMenuOpenState, t }) {
232
+ const [, setMenuOpen] = useMenuOpenState();
233
+ if (serviceOwnsRow) return null;
234
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
235
+ separatorBefore: true,
236
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
237
+ className: "dsh-cs-menu-delete-icon",
238
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, {})
239
+ }),
240
+ onSelect: () => {
241
+ setMenuOpen(false);
242
+ requestDeleteSession(sessionId);
243
+ },
244
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
245
+ className: "dsh-cs-menu-delete-label",
246
+ children: t("action.delete")
247
+ })
248
+ });
249
+ }
250
+ /**
251
+ * The frame-wide confirmation entry, drawn with DSH's own `Modal` primitive so
252
+ * its chrome, blurred mask, focus trap, and Escape handling match the rest of
253
+ * the product instead of being re-implemented here.
254
+ *
255
+ * @param props - the locale seat and the post-delete callback.
256
+ * @returns the dialog, or null while no delete is pending.
257
+ */
258
+ function SessionDeleteDialog({ t, onDeleted }) {
259
+ const request = (0, react.useSyncExternalStore)(subscribeDelete, deleteSnapshot, deleteSnapshot);
260
+ const [busy, setBusy] = (0, react.useState)(false);
261
+ const [error, setError] = (0, react.useState)(void 0);
262
+ (0, react.useEffect)(() => {
263
+ if (request === null) return;
264
+ setBusy(false);
265
+ setError(void 0);
266
+ }, [request]);
267
+ const close = () => {
268
+ if (!busy) clearDeleteRequest();
269
+ };
270
+ const confirm = async () => {
271
+ if (busy || request === null) return;
272
+ setBusy(true);
273
+ setError(void 0);
274
+ const payload = await deleteSession(request.sessionId);
275
+ if (payload.ok !== true) {
276
+ setError(reasonOf(t, payload, "error.delete-failed"));
277
+ setBusy(false);
278
+ if ((payload.deletedSessions?.length ?? 0) > 0) onDeleted?.(request.sessionId);
279
+ return;
280
+ }
281
+ if (payload.warnings !== void 0 && payload.warnings.length > 0) console.warn("[dsh-cool-session] delete cleanup warnings:", payload.warnings);
282
+ clearDeleteRequest();
283
+ onDeleted?.(request.sessionId);
284
+ };
285
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
286
+ open: request !== null,
287
+ onClose: close,
288
+ title: t("delete.title"),
289
+ description: t("delete.description"),
290
+ closeLabel: t("action.close"),
291
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
292
+ variant: "outline",
293
+ "data-modal-autofocus": true,
294
+ disabled: busy,
295
+ onClick: close,
296
+ children: t("action.cancel")
297
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
298
+ variant: "outline",
299
+ className: "dsh-cs-delete-action",
300
+ disabled: busy,
301
+ onClick: () => {
302
+ confirm();
303
+ },
304
+ children: busy ? t("delete.pending") : t("delete.confirm")
305
+ })] }),
306
+ children: error === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
307
+ className: "dsh-cs-delete-error",
308
+ role: "alert",
309
+ children: error
310
+ })
311
+ });
312
+ }
313
+ // src/client/icons.ts
314
+ /**
315
+ * Parse one asset's raw SVG into the artwork the nav patch inserts.
316
+ *
317
+ * The root element is dropped rather than copied: its `width`/`height` belong to
318
+ * the asset, not to the shell's 16px icon seat, and the root's `xmlns` would be
319
+ * redundant once the markup is part of the page.
320
+ *
321
+ * @param name - the asset filename stem, e.g. `archive` for `assets/archive.svg`.
322
+ * @returns the viewBox and inner markup to render.
323
+ */
324
+ function iconArtwork(name) {
325
+ const raw = {
326
+ "archive": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\">\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\n <g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\n <rect width=\"20\" height=\"5\" x=\"2\" y=\"3\" rx=\"1\" />\n <path d=\"M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8m-10 4h4\" />\n </g>\n</svg>\n",
327
+ "arrow-down": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\">\n <path d=\"M0 0h24v24H0z\" fill=\"none\" />\n <path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 9l6 6l6-6\" />\n</svg>\n"
328
+ }[name];
329
+ if (raw === void 0) throw new Error(`dsh-cool-session: missing icon asset '${name}.svg'`);
330
+ return {
331
+ viewBox: /viewBox="([^"]+)"/.exec(raw)?.[1] ?? "0 0 24 24",
332
+ inner: /<svg[^>]*>([\s\S]*)<\/svg>/.exec(raw)?.[1] ?? ""
333
+ };
334
+ }
335
+ /** Complete SVG markup for one asset at a fixed square size. */
336
+ function iconMarkup(name, size) {
337
+ const artwork = iconArtwork(name);
338
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="${artwork.viewBox}" aria-hidden="true" focusable="false">${artwork.inner}</svg>`;
339
+ }
340
+ /** The Settings navigation's icon seat: the shell draws its own glyphs at 16px. */
341
+ const NAV_ICON_SIZE = 16;
342
+ /** The filter triggers' caret, sized to sit inside a 32px control. */
343
+ const CARET_SIZE = 12;
344
+ /**
345
+ * The Sessions glyph as complete markup for the shell's navigation seat.
346
+ *
347
+ * It carries its own 16px size because the asset's `1em` would resolve against
348
+ * the nav cell's 14px text, and the stroke is `currentColor`, so the glyph takes
349
+ * the label's color in both appearances.
350
+ */
351
+ const ARCHIVE_NAV_MARKUP = iconMarkup("archive", NAV_ICON_SIZE);
352
+ /**
353
+ * The caret beside the panel's two filter triggers.
354
+ *
355
+ * `assets/arrow-down.svg` strokes with `currentColor`, so the trigger's label
356
+ * color reaches it; the fixed 12px keeps it clear of the control's 13px text.
357
+ */
358
+ const ARROW_DOWN_MARKUP = iconMarkup("arrow-down", CARET_SIZE);
359
+ // src/client/locales/zh.ts
360
+ const zh = {
361
+ "action.delete": "删除会话",
362
+ "action.cancel": "取消",
363
+ "action.close": "关闭",
364
+ "settings.nav": "会话管理",
365
+ "settings.title": "会话管理",
366
+ "settings.description": "搜索、筛选、归档或删除会话。",
367
+ "sessions.search": "搜索会话标题",
368
+ "sessions.searchPlaceholder": "搜索...",
369
+ "sessions.allWorkspaces": "全部工作区",
370
+ "sessions.noWorkspace": "未关联工作区",
371
+ "sessions.allSessions": "全部会话",
372
+ "sessions.unarchived": "未归档",
373
+ "sessions.archived": "已归档",
374
+ "sessions.running": "运行中",
375
+ "sessions.selectAll": "全选",
376
+ "sessions.selectOne": "选择 {title}",
377
+ "sessions.archiveSelected": "归档已选",
378
+ "sessions.deleteSelected": "删除已选",
379
+ "sessions.selectedCount": "已选择 {count} 个",
380
+ "sessions.columnTitle": "会话标题",
381
+ "sessions.columnStatus": "状态",
382
+ "sessions.columnActions": "操作",
383
+ "sessions.archive": "归档",
384
+ "sessions.unarchive": "取消归档",
385
+ "sessions.delete": "删除",
386
+ "sessions.loading": "正在载入会话…",
387
+ "sessions.empty": "暂无会话",
388
+ "sessions.noMatches": "没有匹配的会话",
389
+ "sessions.loadFailed": "无法读取会话列表",
390
+ "sessions.retry": "重试",
391
+ "sessions.unavailable": "会话服务尚未就绪,无法归档",
392
+ "sessions.actionFailed": "操作失败",
393
+ "sessions.bulkPartial.one": "已删除 1 个会话,{failed} 个删除失败。",
394
+ "sessions.bulkPartial.other": "已删除 {deleted} 个会话,{failed} 个删除失败。",
395
+ "sessions.bulkStopped.one": "已删除 1 个会话,其余未能继续处理。",
396
+ "sessions.bulkStopped.other": "已删除 {deleted} 个会话,其余未能继续处理。",
397
+ "sessions.bulkArchivePartial.one": "已归档 1 个会话,{skipped} 个运行中的会话已跳过。",
398
+ "sessions.bulkArchivePartial.other": "已归档 {archived} 个会话,{skipped} 个运行中的会话已跳过。",
399
+ "sessions.bulkArchiveFailed.one": "已归档 1 个会话,{failed} 个归档失败。",
400
+ "sessions.bulkArchiveFailed.other": "已归档 {archived} 个会话,{failed} 个归档失败。",
401
+ "sessions.deleteSelectedTitle": "删除选中的会话",
402
+ "sessions.deleteBatchDescription": "从磁盘移除选中的 {count} 个会话及其所有子会话的日志,正在运行的会话将被终止。此操作不可撤销!",
403
+ "sessions.archiveStopTitle": "停止并归档此会话?",
404
+ "sessions.archiveStopDescription": "“{title}”仍有正在进行的工作。归档会先停止这些工作,停止的工作不会自动继续。",
405
+ "sessions.archiveActivity": "将被停止的工作",
406
+ "sessions.activityTurn": "进行中的回合",
407
+ "sessions.activitySubagents.one": "1 个运行中的子智能体",
408
+ "sessions.activitySubagents.other": "{count} 个运行中的子智能体",
409
+ "sessions.activityJobs.one": "1 个后台任务",
410
+ "sessions.activityJobs.other": "{count} 个后台任务",
411
+ "sessions.activitySchedules.one": "1 条定时提醒",
412
+ "sessions.activitySchedules.other": "{count} 条定时提醒",
413
+ "sessions.activityOther.one": "1 项其他工作({kind})",
414
+ "sessions.activityOther.other": "{count} 项其他工作({kind})",
415
+ "sessions.archiveStopAction": "停止并归档",
416
+ "sessions.archiveStopPending": "正在停止并归档…",
417
+ "delete.title": "删除会话",
418
+ "delete.description": "从磁盘移除此会话及其所有子会话的日志,正在运行的会话将被终止。此操作不可撤销!",
419
+ "delete.pending": "正在停止并删除…",
420
+ "delete.confirm": "删除",
421
+ "error.host-stale": "Host 端仍在运行旧版本,请重启 DSH 后重试。",
422
+ "error.missing-session": "未指定会话",
423
+ "error.unknown-session": "该会话已不存在",
424
+ "error.subagent-delete": "子代理会话不能单独删除,请删除其父会话",
425
+ "error.delete-failed": "无法删除该会话",
426
+ "error.delete-unsupported": "当前 Harness 未暴露删除会话所需的磁盘目录信息",
427
+ "error.delete-active": "会话仍在运行({activity}),无法停止,因此未删除",
428
+ "error.session-live": "会话仍处于活动状态,无法释放",
429
+ "error.delete-partial": "已删除 {deleted} 个会话,但还有 {remaining} 个未能删除;请重试剩余项",
430
+ "error.session-running": "会话仍在运行,无法归档",
431
+ "error.activityFallback": "进行中的工作",
432
+ "activity.turn": "进行中的回合",
433
+ "activity.subagent": "运行中的子智能体",
434
+ "activity.job": "后台任务",
435
+ "activity.schedule": "定时提醒",
436
+ "activity.separator": "、"
437
+ };
438
+ // src/client/locales/en.ts
439
+ const en = {
440
+ "action.delete": "Delete session",
441
+ "action.cancel": "Cancel",
442
+ "action.close": "Close",
443
+ "settings.nav": "Sessions",
444
+ "settings.title": "Sessions",
445
+ "settings.description": "Search, filter, archive, or delete sessions.",
446
+ "sessions.search": "Search session titles",
447
+ "sessions.searchPlaceholder": "Search...",
448
+ "sessions.allWorkspaces": "All workspaces",
449
+ "sessions.noWorkspace": "No workspace",
450
+ "sessions.allSessions": "All sessions",
451
+ "sessions.unarchived": "Unarchived",
452
+ "sessions.archived": "Archived",
453
+ "sessions.running": "Running",
454
+ "sessions.selectAll": "Select all",
455
+ "sessions.selectOne": "Select {title}",
456
+ "sessions.archiveSelected": "Archive selected",
457
+ "sessions.deleteSelected": "Delete selected",
458
+ "sessions.selectedCount": "{count} selected",
459
+ "sessions.columnTitle": "Session",
460
+ "sessions.columnStatus": "Status",
461
+ "sessions.columnActions": "Actions",
462
+ "sessions.archive": "Archive",
463
+ "sessions.unarchive": "Unarchive",
464
+ "sessions.delete": "Delete",
465
+ "sessions.loading": "Loading sessions…",
466
+ "sessions.empty": "No sessions yet",
467
+ "sessions.noMatches": "No matching sessions",
468
+ "sessions.loadFailed": "The session list could not be loaded",
469
+ "sessions.retry": "Retry",
470
+ "sessions.unavailable": "Session services are not ready, so the archive action is unavailable",
471
+ "sessions.actionFailed": "The action failed",
472
+ "sessions.bulkPartial.one": "Deleted 1 session; {failed} could not be deleted.",
473
+ "sessions.bulkPartial.other": "Deleted {deleted} sessions; {failed} could not be deleted.",
474
+ "sessions.bulkStopped.one": "Deleted 1 session; the rest could not be processed.",
475
+ "sessions.bulkStopped.other": "Deleted {deleted} sessions; the rest could not be processed.",
476
+ "sessions.bulkArchivePartial.one": "Archived 1 session; {skipped} skipped because work was still running.",
477
+ "sessions.bulkArchivePartial.other": "Archived {archived} sessions; {skipped} skipped because work was still running.",
478
+ "sessions.bulkArchiveFailed.one": "Archived 1 session; {failed} could not be archived.",
479
+ "sessions.bulkArchiveFailed.other": "Archived {archived} sessions; {failed} could not be archived.",
480
+ "sessions.deleteSelectedTitle": "Delete selected sessions",
481
+ "sessions.deleteBatchDescription": "This removes the {count} selected sessions and all of their subagent sessions from disk, including their logs. Running sessions will be terminated. This cannot be undone!",
482
+ "sessions.archiveStopTitle": "Stop and archive this session?",
483
+ "sessions.archiveStopDescription": "“{title}” still has work in progress. Archiving stops it first; the stopped work will not resume on its own.",
484
+ "sessions.archiveActivity": "Work that will be stopped",
485
+ "sessions.activityTurn": "The turn in progress",
486
+ "sessions.activitySubagents.one": "1 running subagent",
487
+ "sessions.activitySubagents.other": "{count} running subagents",
488
+ "sessions.activityJobs.one": "1 background job",
489
+ "sessions.activityJobs.other": "{count} background jobs",
490
+ "sessions.activitySchedules.one": "1 scheduled reminder",
491
+ "sessions.activitySchedules.other": "{count} scheduled reminders",
492
+ "sessions.activityOther.one": "1 other item of work ({kind})",
493
+ "sessions.activityOther.other": "{count} other items of work ({kind})",
494
+ "sessions.archiveStopAction": "Stop and archive",
495
+ "sessions.archiveStopPending": "Stopping and archiving…",
496
+ "delete.title": "Delete session",
497
+ "delete.description": "This removes this session and all of its subagent sessions from disk, including their logs. Running sessions will be terminated. This cannot be undone!",
498
+ "delete.pending": "Stopping and deleting…",
499
+ "delete.confirm": "Delete",
500
+ "error.host-stale": "The Host half is still running an older build; restart DSH and try again.",
501
+ "error.missing-session": "No session was named",
502
+ "error.unknown-session": "That session no longer exists",
503
+ "error.subagent-delete": "A subagent session cannot be deleted on its own; delete its parent conversation",
504
+ "error.delete-failed": "The session could not be deleted",
505
+ "error.delete-unsupported": "This Harness does not expose the on-disk session layout needed for deletion",
506
+ "error.delete-active": "The session is still running ({activity}) and could not be stopped, so it was not deleted",
507
+ "error.session-live": "The session is still live and could not be released",
508
+ "error.delete-partial": "Some sessions could not be deleted: {deleted} removed, {remaining} still present. Retry the remaining ones",
509
+ "error.session-running": "The session is still running and could not be archived",
510
+ "error.activityFallback": "work in progress",
511
+ "activity.turn": "the turn in progress",
512
+ "activity.subagent": "running subagents",
513
+ "activity.job": "background jobs",
514
+ "activity.schedule": "scheduled reminders",
515
+ "activity.separator": ", "
516
+ };
517
+ // src/client/locales/ja.ts
518
+ const ja = {
519
+ "action.delete": "セッションを削除",
520
+ "action.cancel": "キャンセル",
521
+ "action.close": "閉じる",
522
+ "settings.nav": "セッション",
523
+ "settings.title": "セッション",
524
+ "settings.description": "セッションの検索・絞り込み・アーカイブ・削除を行います。",
525
+ "sessions.search": "セッション名を検索",
526
+ "sessions.searchPlaceholder": "検索...",
527
+ "sessions.allWorkspaces": "すべてのワークスペース",
528
+ "sessions.noWorkspace": "ワークスペースなし",
529
+ "sessions.allSessions": "すべてのセッション",
530
+ "sessions.unarchived": "未アーカイブ",
531
+ "sessions.archived": "アーカイブ済み",
532
+ "sessions.running": "実行中",
533
+ "sessions.selectAll": "すべて選択",
534
+ "sessions.selectOne": "{title} を選択",
535
+ "sessions.archiveSelected": "選択項目をアーカイブ",
536
+ "sessions.deleteSelected": "選択項目を削除",
537
+ "sessions.selectedCount": "{count} 件選択中",
538
+ "sessions.columnTitle": "セッション名",
539
+ "sessions.columnStatus": "状態",
540
+ "sessions.columnActions": "操作",
541
+ "sessions.archive": "アーカイブ",
542
+ "sessions.unarchive": "アーカイブを解除",
543
+ "sessions.delete": "削除",
544
+ "sessions.loading": "セッションを読み込み中…",
545
+ "sessions.empty": "セッションはまだありません",
546
+ "sessions.noMatches": "一致するセッションはありません",
547
+ "sessions.loadFailed": "セッション一覧を読み込めませんでした",
548
+ "sessions.retry": "再試行",
549
+ "sessions.unavailable": "セッションサービスが準備できていないため、アーカイブできません",
550
+ "sessions.actionFailed": "操作に失敗しました",
551
+ "sessions.bulkPartial.one": "セッションを 1 件削除しました。{failed} 件は削除できませんでした。",
552
+ "sessions.bulkPartial.other": "セッションを {deleted} 件削除しました。{failed} 件は削除できませんでした。",
553
+ "sessions.bulkStopped.one": "セッションを 1 件削除しました。残りは処理できませんでした。",
554
+ "sessions.bulkStopped.other": "セッションを {deleted} 件削除しました。残りは処理できませんでした。",
555
+ "sessions.bulkArchivePartial.one": "セッションを 1 件アーカイブしました。{skipped} 件は作業中のためスキップしました。",
556
+ "sessions.bulkArchivePartial.other": "セッションを {archived} 件アーカイブしました。{skipped} 件は作業中のためスキップしました。",
557
+ "sessions.bulkArchiveFailed.one": "セッションを 1 件アーカイブしました。{failed} 件はアーカイブできませんでした。",
558
+ "sessions.bulkArchiveFailed.other": "セッションを {archived} 件アーカイブしました。{failed} 件はアーカイブできませんでした。",
559
+ "sessions.deleteSelectedTitle": "選択したセッションを削除",
560
+ "sessions.deleteBatchDescription": "選択した {count} 件のセッションとそのすべてのサブエージェントセッションを、ログを含めてディスクから削除します。実行中のセッションは終了されます。この操作は元に戻せません!",
561
+ "sessions.archiveStopTitle": "このセッションを停止してアーカイブしますか?",
562
+ "sessions.archiveStopDescription": "「{title}」には進行中の作業があります。アーカイブするとまず停止され、停止された作業は自動的に再開されません。",
563
+ "sessions.archiveActivity": "停止される作業",
564
+ "sessions.activityTurn": "進行中のターン",
565
+ "sessions.activitySubagents.one": "実行中のサブエージェント 1 件",
566
+ "sessions.activitySubagents.other": "実行中のサブエージェント {count} 件",
567
+ "sessions.activityJobs.one": "バックグラウンドジョブ 1 件",
568
+ "sessions.activityJobs.other": "バックグラウンドジョブ {count} 件",
569
+ "sessions.activitySchedules.one": "スケジュールされたリマインダー 1 件",
570
+ "sessions.activitySchedules.other": "スケジュールされたリマインダー {count} 件",
571
+ "sessions.activityOther.one": "その他の作業 1 件({kind})",
572
+ "sessions.activityOther.other": "その他の作業 {count} 件({kind})",
573
+ "sessions.archiveStopAction": "停止してアーカイブ",
574
+ "sessions.archiveStopPending": "停止してアーカイブ中…",
575
+ "delete.title": "セッションを削除",
576
+ "delete.description": "このセッションとそのすべてのサブエージェントセッションを、ログを含めてディスクから削除します。実行中のセッションは終了されます。この操作は元に戻せません!",
577
+ "delete.pending": "停止して削除中…",
578
+ "delete.confirm": "削除",
579
+ "error.host-stale": "Host 側が古いビルドのままです。DSH を再起動してから再試行してください。",
580
+ "error.missing-session": "セッションが指定されていません",
581
+ "error.unknown-session": "そのセッションは既に存在しません",
582
+ "error.subagent-delete": "サブエージェントのセッションは単独では削除できません。親の会話を削除してください",
583
+ "error.delete-failed": "セッションを削除できませんでした",
584
+ "error.delete-unsupported": "この Harness は削除に必要なディスク上のセッション配置を公開していません",
585
+ "error.delete-active": "セッションはまだ実行中({activity})で停止できなかったため、削除しませんでした",
586
+ "error.session-live": "セッションがまだ活動中で、解放できませんでした",
587
+ "error.delete-partial": "一部のセッションを削除できませんでした:{deleted} 件を削除、{remaining} 件が残っています。残りを再試行してください",
588
+ "error.session-running": "セッションはまだ実行中で、アーカイブできませんでした",
589
+ "error.activityFallback": "進行中の作業",
590
+ "activity.turn": "進行中のターン",
591
+ "activity.subagent": "実行中のサブエージェント",
592
+ "activity.job": "バックグラウンドジョブ",
593
+ "activity.schedule": "スケジュールされたリマインダー",
594
+ "activity.separator": "、"
595
+ };
596
+ // src/client/locales/es.ts
597
+ const es = {
598
+ "action.delete": "Eliminar sesión",
599
+ "action.cancel": "Cancelar",
600
+ "action.close": "Cerrar",
601
+ "settings.nav": "Sesiones",
602
+ "settings.title": "Sesiones",
603
+ "settings.description": "Busca, filtra, archiva o elimina sesiones.",
604
+ "sessions.search": "Buscar títulos de sesión",
605
+ "sessions.searchPlaceholder": "Buscar...",
606
+ "sessions.allWorkspaces": "Todos los espacios de trabajo",
607
+ "sessions.noWorkspace": "Sin espacio de trabajo",
608
+ "sessions.allSessions": "Todas las sesiones",
609
+ "sessions.unarchived": "Sin archivar",
610
+ "sessions.archived": "Archivadas",
611
+ "sessions.running": "En ejecución",
612
+ "sessions.selectAll": "Seleccionar todo",
613
+ "sessions.selectOne": "Seleccionar {title}",
614
+ "sessions.archiveSelected": "Archivar seleccionadas",
615
+ "sessions.deleteSelected": "Eliminar seleccionadas",
616
+ "sessions.selectedCount": "Seleccionadas: {count}",
617
+ "sessions.columnTitle": "Sesión",
618
+ "sessions.columnStatus": "Estado",
619
+ "sessions.columnActions": "Acciones",
620
+ "sessions.archive": "Archivar",
621
+ "sessions.unarchive": "Desarchivar",
622
+ "sessions.delete": "Eliminar",
623
+ "sessions.loading": "Cargando sesiones…",
624
+ "sessions.empty": "Aún no hay sesiones",
625
+ "sessions.noMatches": "No hay sesiones coincidentes",
626
+ "sessions.loadFailed": "No se pudo cargar la lista de sesiones",
627
+ "sessions.retry": "Reintentar",
628
+ "sessions.unavailable": "Los servicios de sesiones no están listos, por lo que no se puede archivar",
629
+ "sessions.actionFailed": "La acción falló",
630
+ "sessions.bulkPartial.one": "Se eliminó 1 sesión; {failed} no se pudo eliminar.",
631
+ "sessions.bulkPartial.other": "Se eliminaron {deleted} sesiones; {failed} no se pudieron eliminar.",
632
+ "sessions.bulkStopped.one": "Se eliminó 1 sesión; el resto no se pudo procesar.",
633
+ "sessions.bulkStopped.other": "Se eliminaron {deleted} sesiones; el resto no se pudo procesar.",
634
+ "sessions.bulkArchivePartial.one": "Se archivó 1 sesión; {skipped} se omitió porque seguía trabajando.",
635
+ "sessions.bulkArchivePartial.other": "Se archivaron {archived} sesiones; {skipped} se omitieron porque seguían trabajando.",
636
+ "sessions.bulkArchiveFailed.one": "Se archivó 1 sesión; {failed} no se pudo archivar.",
637
+ "sessions.bulkArchiveFailed.other": "Se archivaron {archived} sesiones; {failed} no se pudieron archivar.",
638
+ "sessions.deleteSelectedTitle": "Eliminar las sesiones seleccionadas",
639
+ "sessions.deleteBatchDescription": "Esto elimina del disco las {count} sesiones seleccionadas y todas sus sesiones de subagente, incluidos sus registros. Las sesiones en ejecución se terminarán. ¡Esta acción no se puede deshacer!",
640
+ "sessions.archiveStopTitle": "¿Detener y archivar esta sesión?",
641
+ "sessions.archiveStopDescription": "«{title}» todavía tiene trabajo en progreso. Al archivarla se detiene primero; el trabajo detenido no se reanudará por sí solo.",
642
+ "sessions.archiveActivity": "Trabajo que se detendrá",
643
+ "sessions.activityTurn": "El turno en progreso",
644
+ "sessions.activitySubagents.one": "1 subagente en ejecución",
645
+ "sessions.activitySubagents.other": "{count} subagentes en ejecución",
646
+ "sessions.activityJobs.one": "1 tarea en segundo plano",
647
+ "sessions.activityJobs.other": "{count} tareas en segundo plano",
648
+ "sessions.activitySchedules.one": "1 recordatorio programado",
649
+ "sessions.activitySchedules.other": "{count} recordatorios programados",
650
+ "sessions.activityOther.one": "1 elemento de trabajo más ({kind})",
651
+ "sessions.activityOther.other": "{count} elementos de trabajo más ({kind})",
652
+ "sessions.archiveStopAction": "Detener y archivar",
653
+ "sessions.archiveStopPending": "Deteniendo y archivando…",
654
+ "delete.title": "Eliminar sesión",
655
+ "delete.description": "Esto elimina del disco esta sesión y todas sus sesiones de subagente, incluidos sus registros. Las sesiones en ejecución se terminarán. ¡Esta acción no se puede deshacer!",
656
+ "delete.pending": "Deteniendo y eliminando…",
657
+ "delete.confirm": "Eliminar",
658
+ "error.host-stale": "La mitad Host sigue ejecutando una compilación anterior; reinicia DSH y vuelve a intentarlo.",
659
+ "error.missing-session": "No se indicó ninguna sesión",
660
+ "error.unknown-session": "Esa sesión ya no existe",
661
+ "error.subagent-delete": "Una sesión de subagente no se puede eliminar por sí sola; elimina su conversación principal",
662
+ "error.delete-failed": "No se pudo eliminar la sesión",
663
+ "error.delete-unsupported": "Este Harness no expone la disposición de sesiones en disco necesaria para eliminarlas",
664
+ "error.delete-active": "La sesión sigue en ejecución ({activity}) y no se pudo detener, por lo que no se eliminó",
665
+ "error.session-live": "La sesión sigue activa y no se pudo liberar",
666
+ "error.delete-partial": "No se pudieron eliminar algunas sesiones: {deleted} eliminadas, {remaining} aún presentes. Reintenta las restantes",
667
+ "error.session-running": "La sesión sigue en ejecución y no se pudo archivar",
668
+ "error.activityFallback": "trabajo en progreso",
669
+ "activity.turn": "el turno en progreso",
670
+ "activity.subagent": "subagentes en ejecución",
671
+ "activity.job": "tareas en segundo plano",
672
+ "activity.schedule": "recordatorios programados",
673
+ "activity.separator": ", "
674
+ };
675
+ // src/client/locales/fr.ts
676
+ const fr = {
677
+ "action.delete": "Supprimer la séance",
678
+ "action.cancel": "Annuler",
679
+ "action.close": "Fermer",
680
+ "settings.nav": "Séances",
681
+ "settings.title": "Séances",
682
+ "settings.description": "Recherchez, filtrez, archivez ou supprimez des séances.",
683
+ "sessions.search": "Rechercher les titres de séance",
684
+ "sessions.searchPlaceholder": "Rechercher...",
685
+ "sessions.allWorkspaces": "Tous les espaces de travail",
686
+ "sessions.noWorkspace": "Aucun espace de travail",
687
+ "sessions.allSessions": "Toutes les séances",
688
+ "sessions.unarchived": "Non archivées",
689
+ "sessions.archived": "Archivées",
690
+ "sessions.running": "En cours",
691
+ "sessions.selectAll": "Tout sélectionner",
692
+ "sessions.selectOne": "Sélectionner {title}",
693
+ "sessions.archiveSelected": "Archiver la sélection",
694
+ "sessions.deleteSelected": "Supprimer la sélection",
695
+ "sessions.selectedCount": "Sélection : {count}",
696
+ "sessions.columnTitle": "Séance",
697
+ "sessions.columnStatus": "État",
698
+ "sessions.columnActions": "Actions",
699
+ "sessions.archive": "Archiver",
700
+ "sessions.unarchive": "Désarchiver",
701
+ "sessions.delete": "Supprimer",
702
+ "sessions.loading": "Chargement des séances…",
703
+ "sessions.empty": "Aucune séance pour l’instant",
704
+ "sessions.noMatches": "Aucune séance correspondante",
705
+ "sessions.loadFailed": "Impossible de charger la liste des séances",
706
+ "sessions.retry": "Réessayer",
707
+ "sessions.unavailable": "Les services de séance ne sont pas prêts, l’archivage est donc indisponible",
708
+ "sessions.actionFailed": "L’action a échoué",
709
+ "sessions.bulkPartial.one": "1 séance supprimée ; {failed} n’a pas pu l’être.",
710
+ "sessions.bulkPartial.other": "{deleted} séances supprimées ; {failed} n’ont pas pu l’être.",
711
+ "sessions.bulkStopped.one": "1 séance supprimée ; le reste n’a pas pu être traité.",
712
+ "sessions.bulkStopped.other": "{deleted} séances supprimées ; le reste n’a pas pu être traité.",
713
+ "sessions.bulkArchivePartial.one": "1 séance archivée ; {skipped} ignorée car elle travaillait encore.",
714
+ "sessions.bulkArchivePartial.other": "{archived} séances archivées ; {skipped} ignorées car elles travaillaient encore.",
715
+ "sessions.bulkArchiveFailed.one": "1 séance archivée ; {failed} n’a pas pu l’être.",
716
+ "sessions.bulkArchiveFailed.other": "{archived} séances archivées ; {failed} n’ont pas pu l’être.",
717
+ "sessions.deleteSelectedTitle": "Supprimer les séances sélectionnées",
718
+ "sessions.deleteBatchDescription": "Cela supprime du disque les {count} séances sélectionnées et toutes leurs séances de sous-agent, journaux compris. Les séances en cours seront interrompues. Cette action est irréversible !",
719
+ "sessions.archiveStopTitle": "Arrêter et archiver cette séance ?",
720
+ "sessions.archiveStopDescription": "« {title} » a encore du travail en cours. L’archivage l’arrête d’abord ; le travail arrêté ne reprendra pas de lui-même.",
721
+ "sessions.archiveActivity": "Travail qui sera arrêté",
722
+ "sessions.activityTurn": "Le tour en cours",
723
+ "sessions.activitySubagents.one": "1 sous-agent en cours d’exécution",
724
+ "sessions.activitySubagents.other": "{count} sous-agents en cours d’exécution",
725
+ "sessions.activityJobs.one": "1 tâche en arrière-plan",
726
+ "sessions.activityJobs.other": "{count} tâches en arrière-plan",
727
+ "sessions.activitySchedules.one": "1 rappel programmé",
728
+ "sessions.activitySchedules.other": "{count} rappels programmés",
729
+ "sessions.activityOther.one": "1 autre élément de travail ({kind})",
730
+ "sessions.activityOther.other": "{count} autres éléments de travail ({kind})",
731
+ "sessions.archiveStopAction": "Arrêter et archiver",
732
+ "sessions.archiveStopPending": "Arrêt et archivage…",
733
+ "delete.title": "Supprimer la séance",
734
+ "delete.description": "Cela supprime du disque cette séance et toutes ses séances de sous-agent, journaux compris. Les séances en cours seront interrompues. Cette action est irréversible !",
735
+ "delete.pending": "Arrêt et suppression…",
736
+ "delete.confirm": "Supprimer",
737
+ "error.host-stale": "La moitié Host exécute encore une compilation plus ancienne ; redémarrez DSH et réessayez.",
738
+ "error.missing-session": "Aucune séance n’a été indiquée",
739
+ "error.unknown-session": "Cette séance n’existe plus",
740
+ "error.subagent-delete": "Une séance de sous-agent ne peut pas être supprimée seule ; supprimez sa conversation parente",
741
+ "error.delete-failed": "La séance n’a pas pu être supprimée",
742
+ "error.delete-unsupported": "Ce Harness n’expose pas la disposition des séances sur disque nécessaire à la suppression",
743
+ "error.delete-active": "La séance est encore en cours ({activity}) et n’a pas pu être arrêtée ; elle n’a donc pas été supprimée",
744
+ "error.session-live": "La séance est encore active et n’a pas pu être libérée",
745
+ "error.delete-partial": "Certaines séances n’ont pas pu être supprimées : {deleted} supprimées, {remaining} encore présentes. Réessayez les restantes",
746
+ "error.session-running": "La séance est encore en cours et n’a pas pu être archivée",
747
+ "error.activityFallback": "travail en cours",
748
+ "activity.turn": "le tour en cours",
749
+ "activity.subagent": "sous-agents en cours d’exécution",
750
+ "activity.job": "tâches en arrière-plan",
751
+ "activity.schedule": "rappels programmés",
752
+ "activity.separator": ", "
753
+ };
754
+ // src/client/locales/de.ts
755
+ const de = {
756
+ "action.delete": "Sitzung löschen",
757
+ "action.cancel": "Abbrechen",
758
+ "action.close": "Schließen",
759
+ "settings.nav": "Sitzungen",
760
+ "settings.title": "Sitzungen",
761
+ "settings.description": "Sitzungen suchen, filtern, archivieren oder löschen.",
762
+ "sessions.search": "Sitzungstitel suchen",
763
+ "sessions.searchPlaceholder": "Suchen...",
764
+ "sessions.allWorkspaces": "Alle Arbeitsbereiche",
765
+ "sessions.noWorkspace": "Kein Arbeitsbereich",
766
+ "sessions.allSessions": "Alle Sitzungen",
767
+ "sessions.unarchived": "Nicht archiviert",
768
+ "sessions.archived": "Archiviert",
769
+ "sessions.running": "Läuft",
770
+ "sessions.selectAll": "Alle auswählen",
771
+ "sessions.selectOne": "{title} auswählen",
772
+ "sessions.archiveSelected": "Ausgewählte archivieren",
773
+ "sessions.deleteSelected": "Ausgewählte löschen",
774
+ "sessions.selectedCount": "{count} ausgewählt",
775
+ "sessions.columnTitle": "Sitzung",
776
+ "sessions.columnStatus": "Status",
777
+ "sessions.columnActions": "Aktionen",
778
+ "sessions.archive": "Archivieren",
779
+ "sessions.unarchive": "Dearchivieren",
780
+ "sessions.delete": "Löschen",
781
+ "sessions.loading": "Sitzungen werden geladen…",
782
+ "sessions.empty": "Noch keine Sitzungen",
783
+ "sessions.noMatches": "Keine passenden Sitzungen",
784
+ "sessions.loadFailed": "Die Sitzungsliste konnte nicht geladen werden",
785
+ "sessions.retry": "Erneut versuchen",
786
+ "sessions.unavailable": "Die Sitzungsdienste sind noch nicht bereit, daher ist das Archivieren nicht möglich",
787
+ "sessions.actionFailed": "Die Aktion ist fehlgeschlagen",
788
+ "sessions.bulkPartial.one": "1 Sitzung gelöscht; {failed} konnte nicht gelöscht werden.",
789
+ "sessions.bulkPartial.other": "{deleted} Sitzungen gelöscht; {failed} konnten nicht gelöscht werden.",
790
+ "sessions.bulkStopped.one": "1 Sitzung gelöscht; der Rest konnte nicht verarbeitet werden.",
791
+ "sessions.bulkStopped.other": "{deleted} Sitzungen gelöscht; der Rest konnte nicht verarbeitet werden.",
792
+ "sessions.bulkArchivePartial.one": "1 Sitzung archiviert; {skipped} übersprungen, weil noch Arbeit lief.",
793
+ "sessions.bulkArchivePartial.other": "{archived} Sitzungen archiviert; {skipped} übersprungen, weil noch Arbeit lief.",
794
+ "sessions.bulkArchiveFailed.one": "1 Sitzung archiviert; {failed} konnte nicht archiviert werden.",
795
+ "sessions.bulkArchiveFailed.other": "{archived} Sitzungen archiviert; {failed} konnten nicht archiviert werden.",
796
+ "sessions.deleteSelectedTitle": "Ausgewählte Sitzungen löschen",
797
+ "sessions.deleteBatchDescription": "Dadurch werden die {count} ausgewählten Sitzungen und alle ihre Subagenten-Sitzungen samt Protokollen von der Festplatte entfernt. Laufende Sitzungen werden beendet. Dies kann nicht rückgängig gemacht werden!",
798
+ "sessions.archiveStopTitle": "Diese Sitzung stoppen und archivieren?",
799
+ "sessions.archiveStopDescription": "„{title}“ hat noch laufende Arbeit. Das Archivieren stoppt sie zuerst; die gestoppte Arbeit wird nicht von selbst fortgesetzt.",
800
+ "sessions.archiveActivity": "Arbeit, die gestoppt wird",
801
+ "sessions.activityTurn": "Die laufende Runde",
802
+ "sessions.activitySubagents.one": "1 laufender Subagent",
803
+ "sessions.activitySubagents.other": "{count} laufende Subagenten",
804
+ "sessions.activityJobs.one": "1 Hintergrundjob",
805
+ "sessions.activityJobs.other": "{count} Hintergrundjobs",
806
+ "sessions.activitySchedules.one": "1 geplante Erinnerung",
807
+ "sessions.activitySchedules.other": "{count} geplante Erinnerungen",
808
+ "sessions.activityOther.one": "1 weiterer Arbeitsschritt ({kind})",
809
+ "sessions.activityOther.other": "{count} weitere Arbeitsschritte ({kind})",
810
+ "sessions.archiveStopAction": "Stoppen und archivieren",
811
+ "sessions.archiveStopPending": "Wird gestoppt und archiviert…",
812
+ "delete.title": "Sitzung löschen",
813
+ "delete.description": "Dadurch werden diese Sitzung und alle ihre Subagenten-Sitzungen samt Protokollen von der Festplatte entfernt. Laufende Sitzungen werden beendet. Dies kann nicht rückgängig gemacht werden!",
814
+ "delete.pending": "Wird gestoppt und gelöscht…",
815
+ "delete.confirm": "Löschen",
816
+ "error.host-stale": "Die Host-Hälfte läuft noch mit einem älteren Build; starte DSH neu und versuche es erneut.",
817
+ "error.missing-session": "Es wurde keine Sitzung benannt",
818
+ "error.unknown-session": "Diese Sitzung existiert nicht mehr",
819
+ "error.subagent-delete": "Eine Subagenten-Sitzung kann nicht allein gelöscht werden; lösche ihre übergeordnete Unterhaltung",
820
+ "error.delete-failed": "Die Sitzung konnte nicht gelöscht werden",
821
+ "error.delete-unsupported": "Dieses Harness legt die für das Löschen nötige Sitzungsablage auf der Festplatte nicht offen",
822
+ "error.delete-active": "Die Sitzung läuft noch ({activity}) und konnte nicht gestoppt werden, daher wurde sie nicht gelöscht",
823
+ "error.session-live": "Die Sitzung ist noch aktiv und konnte nicht freigegeben werden",
824
+ "error.delete-partial": "Einige Sitzungen konnten nicht gelöscht werden: {deleted} entfernt, {remaining} weiterhin vorhanden. Versuche es bei den restlichen erneut",
825
+ "error.session-running": "Die Sitzung läuft noch und konnte nicht archiviert werden",
826
+ "error.activityFallback": "laufende Arbeit",
827
+ "activity.turn": "die laufende Runde",
828
+ "activity.subagent": "laufende Subagenten",
829
+ "activity.job": "Hintergrundjobs",
830
+ "activity.schedule": "geplante Erinnerungen",
831
+ "activity.separator": ", "
832
+ };
833
+ // src/client/locales/ru.ts
834
+ const ru = {
835
+ "action.delete": "Удалить сеанс",
836
+ "action.cancel": "Отмена",
837
+ "action.close": "Закрыть",
838
+ "settings.nav": "Сеансы",
839
+ "settings.title": "Сеансы",
840
+ "settings.description": "Поиск, фильтрация, архивирование и удаление сеансов.",
841
+ "sessions.search": "Поиск по названиям сеансов",
842
+ "sessions.searchPlaceholder": "Поиск...",
843
+ "sessions.allWorkspaces": "Все рабочие области",
844
+ "sessions.noWorkspace": "Без рабочей области",
845
+ "sessions.allSessions": "Все сеансы",
846
+ "sessions.unarchived": "Неархивированные",
847
+ "sessions.archived": "Архивированные",
848
+ "sessions.running": "Выполняется",
849
+ "sessions.selectAll": "Выбрать все",
850
+ "sessions.selectOne": "Выбрать {title}",
851
+ "sessions.archiveSelected": "Архивировать выбранные",
852
+ "sessions.deleteSelected": "Удалить выбранные",
853
+ "sessions.selectedCount": "Выбрано: {count}",
854
+ "sessions.columnTitle": "Сеанс",
855
+ "sessions.columnStatus": "Состояние",
856
+ "sessions.columnActions": "Действия",
857
+ "sessions.archive": "Архивировать",
858
+ "sessions.unarchive": "Разархивировать",
859
+ "sessions.delete": "Удалить",
860
+ "sessions.loading": "Загрузка сеансов…",
861
+ "sessions.empty": "Сеансов пока нет",
862
+ "sessions.noMatches": "Подходящих сеансов нет",
863
+ "sessions.loadFailed": "Не удалось загрузить список сеансов",
864
+ "sessions.retry": "Повторить",
865
+ "sessions.unavailable": "Службы сеансов не готовы, поэтому архивирование недоступно",
866
+ "sessions.actionFailed": "Действие не выполнено",
867
+ "sessions.bulkPartial.one": "Удалён 1 сеанс; {failed} удалить не удалось.",
868
+ "sessions.bulkPartial.other": "Удалено сеансов: {deleted}; удалить не удалось: {failed}.",
869
+ "sessions.bulkStopped.one": "Удалён 1 сеанс; остальные обработать не удалось.",
870
+ "sessions.bulkStopped.other": "Удалено сеансов: {deleted}; остальные обработать не удалось.",
871
+ "sessions.bulkArchivePartial.one": "Архивирован 1 сеанс; {skipped} пропущен, так как в нём ещё шла работа.",
872
+ "sessions.bulkArchivePartial.other": "Архивировано сеансов: {archived}; пропущено из-за незавершённой работы: {skipped}.",
873
+ "sessions.bulkArchiveFailed.one": "Архивирован 1 сеанс; {failed} архивировать не удалось.",
874
+ "sessions.bulkArchiveFailed.other": "Архивировано сеансов: {archived}; архивировать не удалось: {failed}.",
875
+ "sessions.deleteSelectedTitle": "Удалить выбранные сеансы",
876
+ "sessions.deleteBatchDescription": "Это удалит с диска {count} выбранных сеансов и все их сеансы субагентов вместе с журналами. Выполняющиеся сеансы будут прерваны. Отменить это невозможно!",
877
+ "sessions.archiveStopTitle": "Остановить и заархивировать этот сеанс?",
878
+ "sessions.archiveStopDescription": "В «{title}» ещё идёт работа. Архивирование сначала остановит её; остановленная работа не возобновится сама по себе.",
879
+ "sessions.archiveActivity": "Работа, которая будет остановлена",
880
+ "sessions.activityTurn": "Текущий ход",
881
+ "sessions.activitySubagents.one": "1 работающий субагент",
882
+ "sessions.activitySubagents.other": "Работающих субагентов: {count}",
883
+ "sessions.activityJobs.one": "1 фоновое задание",
884
+ "sessions.activityJobs.other": "Фоновых заданий: {count}",
885
+ "sessions.activitySchedules.one": "1 запланированное напоминание",
886
+ "sessions.activitySchedules.other": "Запланированных напоминаний: {count}",
887
+ "sessions.activityOther.one": "1 другой элемент работы ({kind})",
888
+ "sessions.activityOther.other": "Других элементов работы: {count} ({kind})",
889
+ "sessions.archiveStopAction": "Остановить и заархивировать",
890
+ "sessions.archiveStopPending": "Остановка и архивирование…",
891
+ "delete.title": "Удалить сеанс",
892
+ "delete.description": "Это удалит с диска этот сеанс и все его сеансы субагентов вместе с журналами. Выполняющиеся сеансы будут прерваны. Отменить это невозможно!",
893
+ "delete.pending": "Остановка и удаление…",
894
+ "delete.confirm": "Удалить",
895
+ "error.host-stale": "Host-часть всё ещё работает на старой сборке; перезапустите DSH и повторите попытку.",
896
+ "error.missing-session": "Сеанс не указан",
897
+ "error.unknown-session": "Такого сеанса больше нет",
898
+ "error.subagent-delete": "Сеанс субагента нельзя удалить отдельно; удалите его родительский диалог",
899
+ "error.delete-failed": "Не удалось удалить сеанс",
900
+ "error.delete-unsupported": "Этот Harness не предоставляет раскладку сеансов на диске, необходимую для удаления",
901
+ "error.delete-active": "Сеанс всё ещё выполняется ({activity}) и остановить его не удалось, поэтому он не удалён",
902
+ "error.session-live": "Сеанс всё ещё активен, и освободить его не удалось",
903
+ "error.delete-partial": "Часть сеансов удалить не удалось: удалено {deleted}, осталось {remaining}. Повторите попытку для оставшихся",
904
+ "error.session-running": "Сеанс всё ещё выполняется, и заархивировать его не удалось",
905
+ "error.activityFallback": "идущая работа",
906
+ "activity.turn": "текущий ход",
907
+ "activity.subagent": "работающие субагенты",
908
+ "activity.job": "фоновые задания",
909
+ "activity.schedule": "запланированные напоминания",
910
+ "activity.separator": ", "
911
+ };
912
+ // src/client/locales/pt.ts
913
+ const pt = {
914
+ "action.delete": "Excluir sessão",
915
+ "action.cancel": "Cancelar",
916
+ "action.close": "Fechar",
917
+ "settings.nav": "Sessões",
918
+ "settings.title": "Sessões",
919
+ "settings.description": "Pesquise, filtre, arquive ou exclua sessões.",
920
+ "sessions.search": "Pesquisar títulos de sessão",
921
+ "sessions.searchPlaceholder": "Pesquisar...",
922
+ "sessions.allWorkspaces": "Todos os espaços de trabalho",
923
+ "sessions.noWorkspace": "Sem espaço de trabalho",
924
+ "sessions.allSessions": "Todas as sessões",
925
+ "sessions.unarchived": "Não arquivadas",
926
+ "sessions.archived": "Arquivadas",
927
+ "sessions.running": "Em execução",
928
+ "sessions.selectAll": "Selecionar tudo",
929
+ "sessions.selectOne": "Selecionar {title}",
930
+ "sessions.archiveSelected": "Arquivar selecionadas",
931
+ "sessions.deleteSelected": "Excluir selecionadas",
932
+ "sessions.selectedCount": "Selecionadas: {count}",
933
+ "sessions.columnTitle": "Sessão",
934
+ "sessions.columnStatus": "Status",
935
+ "sessions.columnActions": "Ações",
936
+ "sessions.archive": "Arquivar",
937
+ "sessions.unarchive": "Desarquivar",
938
+ "sessions.delete": "Excluir",
939
+ "sessions.loading": "Carregando sessões…",
940
+ "sessions.empty": "Ainda não há sessões",
941
+ "sessions.noMatches": "Nenhuma sessão correspondente",
942
+ "sessions.loadFailed": "Não foi possível carregar a lista de sessões",
943
+ "sessions.retry": "Tentar novamente",
944
+ "sessions.unavailable": "Os serviços de sessão não estão prontos, então o arquivamento não está disponível",
945
+ "sessions.actionFailed": "A ação falhou",
946
+ "sessions.bulkPartial.one": "1 sessão excluída; {failed} não pôde ser excluída.",
947
+ "sessions.bulkPartial.other": "{deleted} sessões excluídas; {failed} não puderam ser excluídas.",
948
+ "sessions.bulkStopped.one": "1 sessão excluída; o restante não pôde ser processado.",
949
+ "sessions.bulkStopped.other": "{deleted} sessões excluídas; o restante não pôde ser processado.",
950
+ "sessions.bulkArchivePartial.one": "1 sessão arquivada; {skipped} ignorada porque ainda havia trabalho em andamento.",
951
+ "sessions.bulkArchivePartial.other": "{archived} sessões arquivadas; {skipped} ignoradas porque ainda havia trabalho em andamento.",
952
+ "sessions.bulkArchiveFailed.one": "1 sessão arquivada; {failed} não pôde ser arquivada.",
953
+ "sessions.bulkArchiveFailed.other": "{archived} sessões arquivadas; {failed} não puderam ser arquivadas.",
954
+ "sessions.deleteSelectedTitle": "Excluir as sessões selecionadas",
955
+ "sessions.deleteBatchDescription": "Isso remove do disco as {count} sessões selecionadas e todas as suas sessões de subagente, incluindo seus registros. As sessões em execução serão encerradas. Esta ação não pode ser desfeita!",
956
+ "sessions.archiveStopTitle": "Parar e arquivar esta sessão?",
957
+ "sessions.archiveStopDescription": "“{title}” ainda tem trabalho em andamento. Arquivar interrompe esse trabalho primeiro; o trabalho interrompido não será retomado sozinho.",
958
+ "sessions.archiveActivity": "Trabalho que será interrompido",
959
+ "sessions.activityTurn": "O turno em andamento",
960
+ "sessions.activitySubagents.one": "1 subagente em execução",
961
+ "sessions.activitySubagents.other": "{count} subagentes em execução",
962
+ "sessions.activityJobs.one": "1 tarefa em segundo plano",
963
+ "sessions.activityJobs.other": "{count} tarefas em segundo plano",
964
+ "sessions.activitySchedules.one": "1 lembrete agendado",
965
+ "sessions.activitySchedules.other": "{count} lembretes agendados",
966
+ "sessions.activityOther.one": "1 outro item de trabalho ({kind})",
967
+ "sessions.activityOther.other": "{count} outros itens de trabalho ({kind})",
968
+ "sessions.archiveStopAction": "Parar e arquivar",
969
+ "sessions.archiveStopPending": "Parando e arquivando…",
970
+ "delete.title": "Excluir sessão",
971
+ "delete.description": "Isso remove do disco esta sessão e todas as suas sessões de subagente, incluindo seus registros. As sessões em execução serão encerradas. Esta ação não pode ser desfeita!",
972
+ "delete.pending": "Parando e excluindo…",
973
+ "delete.confirm": "Excluir",
974
+ "error.host-stale": "A metade Host ainda está executando uma compilação mais antiga; reinicie o DSH e tente novamente.",
975
+ "error.missing-session": "Nenhuma sessão foi indicada",
976
+ "error.unknown-session": "Essa sessão não existe mais",
977
+ "error.subagent-delete": "Uma sessão de subagente não pode ser excluída sozinha; exclua a conversa principal dela",
978
+ "error.delete-failed": "Não foi possível excluir a sessão",
979
+ "error.delete-unsupported": "Este Harness não expõe o layout de sessões em disco necessário para excluir",
980
+ "error.delete-active": "A sessão ainda está em execução ({activity}) e não pôde ser interrompida, então não foi excluída",
981
+ "error.session-live": "A sessão ainda está ativa e não pôde ser liberada",
982
+ "error.delete-partial": "Algumas sessões não puderam ser excluídas: {deleted} removidas, {remaining} ainda presentes. Tente novamente as restantes",
983
+ "error.session-running": "A sessão ainda está em execução e não pôde ser arquivada",
984
+ "error.activityFallback": "trabalho em andamento",
985
+ "activity.turn": "o turno em andamento",
986
+ "activity.subagent": "subagentes em execução",
987
+ "activity.job": "tarefas em segundo plano",
988
+ "activity.schedule": "lembretes agendados",
989
+ "activity.separator": ", "
990
+ };
991
+ // src/client/locales.ts
992
+ /** The dictionary namespace this page owns. */
993
+ const NS = "coolSession";
994
+ /** Every dictionary, in the shape the locale service registers. */
995
+ const DICTIONARIES = {
996
+ zh,
997
+ en,
998
+ ja,
999
+ es,
1000
+ fr,
1001
+ de,
1002
+ ru,
1003
+ pt
1004
+ };
1005
+ /**
1006
+ * Pick the singular or plural spelling of a count-bearing message.
1007
+ *
1008
+ * @param base - the message family, whose `.one` and `.other` entries exist.
1009
+ * @param count - the number the sentence reports.
1010
+ * @returns the key to translate.
1011
+ */
1012
+ function countKey(base, count) {
1013
+ return `${base}.${count === 1 ? "one" : "other"}`;
1014
+ }
1015
+ /**
1016
+ * Every spelling of the Settings navigation label this package ships.
1017
+ *
1018
+ * The Settings navigation glyph is patched by matching the rendered label, and
1019
+ * the shell's CSS module class names rotate between releases, so the label is
1020
+ * the only stable handle on that cell. A language switch can land between
1021
+ * registration and that read, so the set covers all eight dictionaries rather
1022
+ * than only the active one.
1023
+ *
1024
+ * @returns the distinct navigation labels, in dictionary order.
1025
+ */
1026
+ function navLabels() {
1027
+ return [...new Set(Object.values(DICTIONARIES).map((dict) => dict["settings.nav"]))];
1028
+ }
1029
+ // src/client/settings-section.tsx
1030
+ /**
1031
+ * The Sessions page of the Settings panel: list, search, filter, selection,
1032
+ * archive, and deletion.
1033
+ *
1034
+ * The list itself is not fetched here. DSH already keeps the Host-authoritative
1035
+ * Session catalog and Workspace archive set in the two client stores the shell
1036
+ * exposes to every slot (`useSessions` / `useWorkspaces`), and every archive is
1037
+ * a Workspace-controller call whose reply updates those stores. Reading them
1038
+ * directly means this page shares the sidebar's rows and running states instead
1039
+ * of maintaining a second, drifting catalog; deletion is the one operation the
1040
+ * client face does not expose, so it still crosses this package's own Host
1041
+ * route.
1042
+ *
1043
+ * Search is deliberately title-only: the row's title is the display title the
1044
+ * shell computes (durable title, then project directory name, then id), so the
1045
+ * text a reader can see in the row is exactly the text the box matches.
1046
+ */
1047
+ /** Flatten the two stores into the rows this page renders. */
1048
+ function buildRows(state, workspaces) {
1049
+ const archived = new Set(workspaces.archivedSessionIds ?? []);
1050
+ const workspaceBySession = /* @__PURE__ */ new Map();
1051
+ for (const workspace of workspaces.items ?? []) for (const id of workspace.sessionIds ?? []) if (!workspaceBySession.has(id)) workspaceBySession.set(id, {
1052
+ id: workspace.workspaceId,
1053
+ title: workspace.title
1054
+ });
1055
+ const rows = [];
1056
+ for (const id of state.ids ?? []) {
1057
+ const summary = state.byId?.[id];
1058
+ if (summary === void 0) continue;
1059
+ if (summary.origin === "subagent") continue;
1060
+ const workspace = workspaceBySession.get(id);
1061
+ const durable = typeof summary.title === "string" && summary.title.trim() !== "" ? summary.title.trim() : void 0;
1062
+ const display = typeof summary.displayTitle === "string" && summary.displayTitle.trim() !== "" ? summary.displayTitle.trim() : void 0;
1063
+ rows.push({
1064
+ id,
1065
+ title: durable ?? display ?? id,
1066
+ ...summary.cwd === void 0 ? {} : { cwd: summary.cwd },
1067
+ ...workspace === void 0 ? {} : {
1068
+ workspaceId: workspace.id,
1069
+ workspaceTitle: workspace.title
1070
+ },
1071
+ archived: archived.has(id),
1072
+ running: summary.running === true,
1073
+ updatedAt: typeof summary.updatedAt === "number" && Number.isFinite(summary.updatedAt) ? summary.updatedAt : 0
1074
+ });
1075
+ }
1076
+ rows.sort((left, right) => right.updatedAt - left.updatedAt || left.title.localeCompare(right.title));
1077
+ return rows;
1078
+ }
1079
+ /** Read the document language the locale service keeps in sync. */
1080
+ function localeTag() {
1081
+ if (typeof document === "undefined") return void 0;
1082
+ const language = document.documentElement.lang;
1083
+ return language === "" ? void 0 : language;
1084
+ }
1085
+ /** Format one epoch instant, or an em dash when the row carries none. */
1086
+ function formatDay(ms) {
1087
+ if (!Number.isFinite(ms) || ms <= 0) return "—";
1088
+ try {
1089
+ return new Intl.DateTimeFormat(localeTag(), {
1090
+ year: "numeric",
1091
+ month: "2-digit",
1092
+ day: "2-digit"
1093
+ }).format(ms);
1094
+ } catch {
1095
+ return "—";
1096
+ }
1097
+ }
1098
+ /** Format the same instant with its time, for the row's hover text. */
1099
+ function formatFull(ms) {
1100
+ if (!Number.isFinite(ms) || ms <= 0) return "";
1101
+ try {
1102
+ return new Intl.DateTimeFormat(localeTag(), {
1103
+ dateStyle: "medium",
1104
+ timeStyle: "short"
1105
+ }).format(ms);
1106
+ } catch {
1107
+ return "";
1108
+ }
1109
+ }
1110
+ /** Word one failure, keeping the retryable summary ahead of its detail. */
1111
+ function problemText(t, problem) {
1112
+ const parts = [];
1113
+ if (problem.prefix !== void 0) parts.push(t(problem.prefix.key, problem.prefix.params));
1114
+ if (problem.failure !== void 0) parts.push(failureText(t, problem.failure));
1115
+ else if (typeof problem.error === "string" && problem.error !== "") parts.push(problem.error === "HTTP 405" ? t("error.host-stale") : problem.error);
1116
+ else if (problem.prefix === void 0) parts.push(t(problem.fallback ?? "sessions.actionFailed"));
1117
+ return parts.join(" ");
1118
+ }
1119
+ /** Word a request-level failure, translating the one code the page knows. */
1120
+ function rawReason(t, error, fallback) {
1121
+ if (error === "HTTP 405") return t("error.host-stale");
1122
+ return typeof error === "string" && error !== "" ? error : t(fallback);
1123
+ }
1124
+ /** One activity family, as the stop-and-archive dialog names it. */
1125
+ function activityLine(t, activity) {
1126
+ switch (activity.kind) {
1127
+ case "turn": return t("sessions.activityTurn");
1128
+ case "subagent": return t(countKey("sessions.activitySubagents", activity.count), { count: activity.count });
1129
+ case "job": return t(countKey("sessions.activityJobs", activity.count), { count: activity.count });
1130
+ case "schedule": return t(countKey("sessions.activitySchedules", activity.count), { count: activity.count });
1131
+ default: return t(countKey("sessions.activityOther", activity.count), {
1132
+ count: activity.count,
1133
+ kind: activity.kind
1134
+ });
1135
+ }
1136
+ }
1137
+ /**
1138
+ * One toolbar filter: the product's Menu anchored to a control shaped like the
1139
+ * search box, so both filters share its 32px height and its border.
1140
+ *
1141
+ * The list portals to the document body: the options column is a scroll
1142
+ * container, and an in-place card would be clipped at its edge (or add a
1143
+ * horizontal scrollbar) instead of floating over the panel.
1144
+ *
1145
+ * @param props - the current option, the rows to offer, and the change callback.
1146
+ * @returns the trigger and its dropdown.
1147
+ */
1148
+ function FilterMenu({ label, items, selectedId, onSelect, className, align = "start", listClassName }) {
1149
+ const [open, setOpen] = (0, react.useState)(false);
1150
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1151
+ open,
1152
+ portal: true,
1153
+ align,
1154
+ items,
1155
+ selectedId,
1156
+ listClassName,
1157
+ className: "dsh-cs-filter-wrap",
1158
+ onSelect: (id) => {
1159
+ setOpen(false);
1160
+ onSelect(id);
1161
+ },
1162
+ onClose: () => {
1163
+ setOpen(false);
1164
+ },
1165
+ anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1166
+ type: "button",
1167
+ className: className === void 0 ? "dsh-cs-filter" : `dsh-cs-filter ${className}`,
1168
+ "aria-haspopup": "menu",
1169
+ "aria-expanded": open,
1170
+ onClick: () => {
1171
+ setOpen((value) => !value);
1172
+ },
1173
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1174
+ className: "dsh-cs-filter-label",
1175
+ children: label
1176
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1177
+ className: "dsh-cs-caret",
1178
+ "aria-hidden": "true",
1179
+ dangerouslySetInnerHTML: { __html: ARROW_DOWN_MARKUP }
1180
+ })]
1181
+ })
1182
+ });
1183
+ }
1184
+ /**
1185
+ * Render the Sessions page.
1186
+ *
1187
+ * @param props - the locale seat, the two catalog hooks, and the injected actions.
1188
+ * @returns the management surface and its confirmations.
1189
+ */
1190
+ function SessionsSection({ t, useSessions, useWorkspaces, setArchived, refresh, onDeleted }) {
1191
+ const sessionState = useSessions((state) => state);
1192
+ const workspaceState = useWorkspaces((state) => state);
1193
+ const [removed, setRemoved] = (0, react.useState)(() => /* @__PURE__ */ new Set());
1194
+ const rows = (0, react.useMemo)(() => buildRows(sessionState, workspaceState).filter((row) => !removed.has(row.id)), [
1195
+ sessionState,
1196
+ workspaceState,
1197
+ removed
1198
+ ]);
1199
+ const [query, setQuery] = (0, react.useState)("");
1200
+ const [workspaceFilter, setWorkspaceFilter] = (0, react.useState)("all");
1201
+ const [archiveFilter, setArchiveFilter] = (0, react.useState)("all");
1202
+ const [selected, setSelected] = (0, react.useState)(() => /* @__PURE__ */ new Set());
1203
+ const [busyId, setBusyId] = (0, react.useState)(null);
1204
+ const [bulkArchiving, setBulkArchiving] = (0, react.useState)(false);
1205
+ const [problem, setProblem] = (0, react.useState)(null);
1206
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
1207
+ const [deleting, setDeleting] = (0, react.useState)(false);
1208
+ const [deleteError, setDeleteError] = (0, react.useState)(void 0);
1209
+ const [archiveStop, setArchiveStop] = (0, react.useState)(null);
1210
+ const [archiving, setArchiving] = (0, react.useState)(false);
1211
+ const [archiveError, setArchiveError] = (0, react.useState)(void 0);
1212
+ const selectAllRef = (0, react.useRef)(null);
1213
+ (0, react.useEffect)(() => {
1214
+ setSelected((previous) => {
1215
+ if (previous.size === 0) return previous;
1216
+ const existing = new Set(rows.map((row) => row.id));
1217
+ let changed = false;
1218
+ const next = /* @__PURE__ */ new Set();
1219
+ for (const id of previous) if (existing.has(id)) next.add(id);
1220
+ else changed = true;
1221
+ return changed ? next : previous;
1222
+ });
1223
+ }, [rows]);
1224
+ const workspaceOptions = (0, react.useMemo)(() => {
1225
+ const titles = /* @__PURE__ */ new Map();
1226
+ for (const row of rows) if (row.workspaceId !== void 0) titles.set(row.workspaceId, row.workspaceTitle ?? "");
1227
+ return [...titles].map(([id, title]) => ({
1228
+ id,
1229
+ title
1230
+ })).sort((left, right) => left.title.localeCompare(right.title));
1231
+ }, [rows]);
1232
+ const hasUnassigned = (0, react.useMemo)(() => rows.some((row) => row.workspaceId === void 0), [rows]);
1233
+ const workspaceItems = (0, react.useMemo)(() => [
1234
+ {
1235
+ id: "all",
1236
+ label: t("sessions.allWorkspaces")
1237
+ },
1238
+ ...workspaceOptions.map((option) => ({
1239
+ id: option.id,
1240
+ label: option.title
1241
+ })),
1242
+ ...hasUnassigned ? [{
1243
+ id: "none",
1244
+ label: t("sessions.noWorkspace")
1245
+ }] : []
1246
+ ], [
1247
+ t,
1248
+ workspaceOptions,
1249
+ hasUnassigned
1250
+ ]);
1251
+ const archiveItems = (0, react.useMemo)(() => [
1252
+ {
1253
+ id: "all",
1254
+ label: t("sessions.allSessions")
1255
+ },
1256
+ {
1257
+ id: "unarchived",
1258
+ label: t("sessions.unarchived")
1259
+ },
1260
+ {
1261
+ id: "archived",
1262
+ label: t("sessions.archived")
1263
+ }
1264
+ ], [t]);
1265
+ const workspaceLabel = workspaceFilter === "all" ? t("sessions.allWorkspaces") : workspaceFilter === "none" ? t("sessions.noWorkspace") : workspaceOptions.find((option) => option.id === workspaceFilter)?.title ?? t("sessions.allWorkspaces");
1266
+ const archiveLabel = archiveFilter === "all" ? t("sessions.allSessions") : archiveFilter === "unarchived" ? t("sessions.unarchived") : t("sessions.archived");
1267
+ (0, react.useEffect)(() => {
1268
+ if (workspaceFilter === "none") {
1269
+ if (!hasUnassigned) setWorkspaceFilter("all");
1270
+ return;
1271
+ }
1272
+ if (workspaceFilter !== "all" && !workspaceOptions.some((option) => option.id === workspaceFilter)) setWorkspaceFilter("all");
1273
+ }, [
1274
+ workspaceFilter,
1275
+ workspaceOptions,
1276
+ hasUnassigned
1277
+ ]);
1278
+ const filtered = (0, react.useMemo)(() => {
1279
+ const needle = query.trim().toLocaleLowerCase();
1280
+ return rows.filter((row) => {
1281
+ if (workspaceFilter !== "all") {
1282
+ if (workspaceFilter === "none") {
1283
+ if (row.workspaceId !== void 0) return false;
1284
+ } else if (row.workspaceId !== workspaceFilter) return false;
1285
+ }
1286
+ if (archiveFilter === "archived" && !row.archived) return false;
1287
+ if (archiveFilter === "unarchived" && row.archived) return false;
1288
+ if (needle !== "" && !row.title.toLocaleLowerCase().includes(needle)) return false;
1289
+ return true;
1290
+ });
1291
+ }, [
1292
+ rows,
1293
+ query,
1294
+ workspaceFilter,
1295
+ archiveFilter
1296
+ ]);
1297
+ const visibleIds = (0, react.useMemo)(() => filtered.map((row) => row.id), [filtered]);
1298
+ const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id));
1299
+ const someSelected = visibleIds.some((id) => selected.has(id));
1300
+ (0, react.useEffect)(() => {
1301
+ const node = selectAllRef.current;
1302
+ if (node !== null) node.indeterminate = someSelected && !allSelected;
1303
+ }, [someSelected, allSelected]);
1304
+ const clearSelection = () => {
1305
+ setSelected(/* @__PURE__ */ new Set());
1306
+ };
1307
+ const archivableSelected = (0, react.useMemo)(() => rows.filter((row) => selected.has(row.id) && !row.archived), [rows, selected]);
1308
+ const toggleAll = () => {
1309
+ setSelected((previous) => {
1310
+ const next = new Set(previous);
1311
+ if (allSelected) for (const id of visibleIds) next.delete(id);
1312
+ else for (const id of visibleIds) next.add(id);
1313
+ return next;
1314
+ });
1315
+ };
1316
+ const toggleRow = (id, checked) => {
1317
+ setSelected((previous) => {
1318
+ const next = new Set(previous);
1319
+ if (checked) next.add(id);
1320
+ else next.delete(id);
1321
+ return next;
1322
+ });
1323
+ };
1324
+ const openDelete = (ids) => {
1325
+ if (ids.length === 0) return;
1326
+ setDeleteError(void 0);
1327
+ setDeleteTarget({ ids: [...ids] });
1328
+ };
1329
+ const closeDelete = () => {
1330
+ if (deleting) return;
1331
+ setDeleteTarget(null);
1332
+ setDeleteError(void 0);
1333
+ };
1334
+ const confirmDelete = async () => {
1335
+ const target = deleteTarget;
1336
+ if (deleting || target === null) return;
1337
+ setDeleting(true);
1338
+ setDeleteError(void 0);
1339
+ const payload = await deleteSessions(target.ids);
1340
+ const results = payload.results ?? [];
1341
+ const requestError = typeof payload.error === "string" && payload.error !== "" ? payload.error : void 0;
1342
+ if (results.length === 0) {
1343
+ setDeleting(false);
1344
+ setDeleteError(rawReason(t, requestError, "sessions.actionFailed"));
1345
+ return;
1346
+ }
1347
+ const succeeded = results.filter((result) => result.ok === true && typeof result.sessionId === "string").map((result) => result.sessionId);
1348
+ const failed = results.filter((result) => result.ok !== true);
1349
+ for (const id of succeeded) onDeleted?.(id);
1350
+ if (succeeded.length > 0) {
1351
+ setRemoved((previous) => {
1352
+ const next = new Set(previous);
1353
+ for (const id of succeeded) next.add(id);
1354
+ return next;
1355
+ });
1356
+ setSelected((previous) => {
1357
+ if (previous.size === 0) return previous;
1358
+ const next = new Set(previous);
1359
+ for (const id of succeeded) next.delete(id);
1360
+ return next;
1361
+ });
1362
+ }
1363
+ setDeleting(false);
1364
+ if (succeeded.length === 0) {
1365
+ const first = failed[0];
1366
+ setDeleteError(first === void 0 ? rawReason(t, requestError, "sessions.actionFailed") : reasonOf(t, first, "error.delete-failed"));
1367
+ return;
1368
+ }
1369
+ setDeleteTarget(null);
1370
+ if (failed.length > 0) {
1371
+ const first = failed[0];
1372
+ const detail = requestError ?? first?.error;
1373
+ setProblem({
1374
+ prefix: {
1375
+ key: countKey("sessions.bulkPartial", succeeded.length),
1376
+ params: {
1377
+ deleted: succeeded.length,
1378
+ failed: failed.length
1379
+ }
1380
+ },
1381
+ ...first === void 0 || first.failure === void 0 ? {} : { failure: first.failure },
1382
+ ...detail === void 0 ? {} : { error: detail }
1383
+ });
1384
+ } else if (requestError !== void 0) setProblem({
1385
+ prefix: {
1386
+ key: countKey("sessions.bulkStopped", succeeded.length),
1387
+ params: { deleted: succeeded.length }
1388
+ },
1389
+ error: requestError
1390
+ });
1391
+ };
1392
+ const toggleArchive = async (row) => {
1393
+ if (setArchived === void 0) {
1394
+ setProblem({ error: t("sessions.unavailable") });
1395
+ return;
1396
+ }
1397
+ setBusyId(row.id);
1398
+ setProblem(null);
1399
+ const result = await setArchived(row.id, row.archived, false);
1400
+ setBusyId(null);
1401
+ if (result.ok) return;
1402
+ if (result.unavailable === true) {
1403
+ setProblem({ error: t("sessions.unavailable") });
1404
+ return;
1405
+ }
1406
+ if (result.active !== void 0) {
1407
+ setArchiveError(void 0);
1408
+ setArchiveStop({
1409
+ row,
1410
+ activity: result.active
1411
+ });
1412
+ return;
1413
+ }
1414
+ setProblem({
1415
+ ...result.failure === void 0 ? {} : { failure: result.failure },
1416
+ ...result.message === void 0 ? {} : { error: result.message }
1417
+ });
1418
+ };
1419
+ /**
1420
+ * Archive every selected row that is not archived yet.
1421
+ *
1422
+ * Running rows are left for the single-row stop-and-archive confirmation:
1423
+ * stopping work in bulk without naming the sessions would hide what is about
1424
+ * to be interrupted, so this pass skips them and reports the count instead.
1425
+ */
1426
+ const archiveSelected = async () => {
1427
+ if (setArchived === void 0) {
1428
+ setProblem({ error: t("sessions.unavailable") });
1429
+ return;
1430
+ }
1431
+ if (bulkArchiving) return;
1432
+ const targets = rows.filter((row) => selected.has(row.id) && !row.archived);
1433
+ if (targets.length === 0) return;
1434
+ setBulkArchiving(true);
1435
+ setProblem(null);
1436
+ const archivedIds = [];
1437
+ let skipped = 0;
1438
+ let firstFailure;
1439
+ for (const row of targets) {
1440
+ const result = await setArchived(row.id, false, false);
1441
+ if (result.ok) archivedIds.push(row.id);
1442
+ else if (result.active !== void 0) skipped += 1;
1443
+ else firstFailure ??= result;
1444
+ }
1445
+ setBulkArchiving(false);
1446
+ if (archivedIds.length > 0) setSelected((previous) => {
1447
+ const next = new Set(previous);
1448
+ for (const id of archivedIds) next.delete(id);
1449
+ return next;
1450
+ });
1451
+ const failed = targets.length - archivedIds.length - skipped;
1452
+ if (failed > 0) {
1453
+ setProblem({
1454
+ ...archivedIds.length === 0 ? {} : { prefix: {
1455
+ key: countKey("sessions.bulkArchiveFailed", archivedIds.length),
1456
+ params: {
1457
+ archived: archivedIds.length,
1458
+ failed
1459
+ }
1460
+ } },
1461
+ ...firstFailure?.failure === void 0 ? {} : { failure: firstFailure.failure },
1462
+ ...firstFailure?.message === void 0 ? {} : { error: firstFailure.message }
1463
+ });
1464
+ return;
1465
+ }
1466
+ if (skipped > 0) setProblem({ prefix: {
1467
+ key: countKey("sessions.bulkArchivePartial", archivedIds.length),
1468
+ params: {
1469
+ archived: archivedIds.length,
1470
+ skipped
1471
+ }
1472
+ } });
1473
+ };
1474
+ const closeArchiveStop = () => {
1475
+ if (archiving) return;
1476
+ setArchiveStop(null);
1477
+ setArchiveError(void 0);
1478
+ };
1479
+ const confirmStopArchive = async () => {
1480
+ const pending = archiveStop;
1481
+ if (archiving || pending === null || setArchived === void 0) return;
1482
+ setArchiving(true);
1483
+ setArchiveError(void 0);
1484
+ const result = await setArchived(pending.row.id, false, true);
1485
+ setArchiving(false);
1486
+ if (result.ok) {
1487
+ setArchiveStop(null);
1488
+ return;
1489
+ }
1490
+ if (result.failure !== void 0) {
1491
+ setArchiveError(failureText(t, result.failure));
1492
+ return;
1493
+ }
1494
+ setArchiveError(typeof result.message === "string" && result.message !== "" ? result.message : t("sessions.actionFailed"));
1495
+ };
1496
+ const loading = sessionState.phase === "pending" && rows.length === 0;
1497
+ const loadFailed = !loading && rows.length === 0 && sessionState.error !== void 0 && sessionState.error !== null;
1498
+ const deleteIsBatch = (deleteTarget?.ids.length ?? 0) > 1;
1499
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1500
+ className: "dsh-cs-settings",
1501
+ children: [
1502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1503
+ className: "dsh-cs-settings-heading",
1504
+ children: t("settings.title")
1505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1506
+ className: "dsh-cs-settings-description",
1507
+ children: t("settings.description")
1508
+ })] }),
1509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1510
+ className: "dsh-cs-toolbar",
1511
+ children: [
1512
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1513
+ className: "dsh-cs-search",
1514
+ type: "search",
1515
+ value: query,
1516
+ placeholder: t("sessions.searchPlaceholder"),
1517
+ "aria-label": t("sessions.search"),
1518
+ onChange: (event) => {
1519
+ setQuery(event.target.value);
1520
+ clearSelection();
1521
+ }
1522
+ }),
1523
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterMenu, {
1524
+ label: workspaceLabel,
1525
+ items: workspaceItems,
1526
+ selectedId: workspaceFilter,
1527
+ className: "dsh-cs-filter-workspace",
1528
+ listClassName: "dsh-cs-menu",
1529
+ onSelect: (id) => {
1530
+ if (id === workspaceFilter) return;
1531
+ setWorkspaceFilter(id);
1532
+ clearSelection();
1533
+ }
1534
+ }),
1535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FilterMenu, {
1536
+ label: archiveLabel,
1537
+ items: archiveItems,
1538
+ selectedId: archiveFilter,
1539
+ className: "dsh-cs-filter-archive",
1540
+ align: "end",
1541
+ onSelect: (id) => {
1542
+ if (id === archiveFilter) return;
1543
+ setArchiveFilter(id);
1544
+ clearSelection();
1545
+ }
1546
+ })
1547
+ ]
1548
+ }),
1549
+ problem !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1550
+ className: "dsh-cs-problem",
1551
+ role: "alert",
1552
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: problemText(t, problem) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1553
+ type: "button",
1554
+ className: "dsh-cs-problem-dismiss",
1555
+ "aria-label": t("action.close"),
1556
+ onClick: () => {
1557
+ setProblem(null);
1558
+ },
1559
+ children: "×"
1560
+ })]
1561
+ }),
1562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1563
+ className: "dsh-cs-selection-bar",
1564
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1565
+ className: "dsh-cs-selection-count",
1566
+ children: t("sessions.selectedCount", { count: selected.size })
1567
+ }), selected.size > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1568
+ className: "dsh-cs-bulk-actions",
1569
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1570
+ type: "button",
1571
+ className: "dsh-cs-link",
1572
+ disabled: archivableSelected.length === 0 || bulkArchiving,
1573
+ onClick: () => {
1574
+ archiveSelected();
1575
+ },
1576
+ children: t("sessions.archiveSelected")
1577
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1578
+ type: "button",
1579
+ className: "dsh-cs-link dsh-cs-danger",
1580
+ disabled: deleting || bulkArchiving,
1581
+ onClick: () => {
1582
+ openDelete([...selected]);
1583
+ },
1584
+ children: t("sessions.deleteSelected")
1585
+ })]
1586
+ })]
1587
+ }),
1588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1589
+ className: "dsh-cs-table",
1590
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1591
+ className: "dsh-cs-table-body",
1592
+ role: "table",
1593
+ "aria-label": t("settings.title"),
1594
+ children: [
1595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1596
+ className: "dsh-cs-table-head",
1597
+ role: "row",
1598
+ children: [
1599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1600
+ role: "columnheader",
1601
+ className: "dsh-cs-head-check",
1602
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1603
+ ref: selectAllRef,
1604
+ type: "checkbox",
1605
+ checked: allSelected,
1606
+ disabled: visibleIds.length === 0 || bulkArchiving,
1607
+ "aria-label": t("sessions.selectAll"),
1608
+ title: t("sessions.selectAll"),
1609
+ onChange: toggleAll
1610
+ })
1611
+ }),
1612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1613
+ role: "columnheader",
1614
+ children: t("sessions.columnTitle")
1615
+ }),
1616
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1617
+ role: "columnheader",
1618
+ children: t("sessions.columnStatus")
1619
+ }),
1620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1621
+ role: "columnheader",
1622
+ className: "dsh-cs-head-actions",
1623
+ children: t("sessions.columnActions")
1624
+ })
1625
+ ]
1626
+ }),
1627
+ loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1628
+ className: "dsh-cs-empty",
1629
+ children: t("sessions.loading")
1630
+ }),
1631
+ loadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1632
+ className: "dsh-cs-empty",
1633
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("sessions.loadFailed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1634
+ variant: "outline",
1635
+ size: "sm",
1636
+ onClick: () => {
1637
+ refresh?.();
1638
+ },
1639
+ children: t("sessions.retry")
1640
+ })]
1641
+ }),
1642
+ !loading && !loadFailed && filtered.map((row) => {
1643
+ const full = formatFull(row.updatedAt);
1644
+ const state = row.running ? "running" : row.archived ? "archived" : "unarchived";
1645
+ const stateLabel = row.running ? t("sessions.running") : row.archived ? t("sessions.archived") : t("sessions.unarchived");
1646
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1647
+ className: "dsh-cs-row",
1648
+ role: "row",
1649
+ "data-session-id": row.id,
1650
+ "data-archived": row.archived ? "1" : "0",
1651
+ "data-running": row.running ? "1" : "0",
1652
+ children: [
1653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1654
+ className: "dsh-cs-row-check",
1655
+ type: "checkbox",
1656
+ checked: selected.has(row.id),
1657
+ disabled: bulkArchiving,
1658
+ "aria-label": t("sessions.selectOne", { title: row.title }),
1659
+ onChange: (event) => {
1660
+ toggleRow(row.id, event.target.checked);
1661
+ }
1662
+ }),
1663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1664
+ className: "dsh-cs-main",
1665
+ role: "cell",
1666
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1667
+ className: "dsh-cs-title",
1668
+ title: row.title,
1669
+ children: row.title
1670
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1671
+ className: "dsh-cs-meta",
1672
+ children: [
1673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1674
+ className: "dsh-cs-workspace",
1675
+ title: row.cwd ?? row.workspaceTitle,
1676
+ children: row.workspaceTitle ?? t("sessions.noWorkspace")
1677
+ }),
1678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1679
+ className: "dsh-cs-sep",
1680
+ "aria-hidden": "true",
1681
+ children: "·"
1682
+ }),
1683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
1684
+ className: "dsh-cs-date",
1685
+ dateTime: row.updatedAt > 0 ? new Date(row.updatedAt).toISOString() : void 0,
1686
+ title: full,
1687
+ children: formatDay(row.updatedAt)
1688
+ })
1689
+ ]
1690
+ })]
1691
+ }),
1692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1693
+ className: `dsh-cs-status dsh-cs-status-${state}`,
1694
+ role: "cell",
1695
+ children: stateLabel
1696
+ }),
1697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1698
+ className: "dsh-cs-actions",
1699
+ role: "cell",
1700
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1701
+ type: "button",
1702
+ className: "dsh-cs-link",
1703
+ disabled: busyId === row.id || bulkArchiving,
1704
+ onClick: () => {
1705
+ toggleArchive(row);
1706
+ },
1707
+ children: row.archived ? t("sessions.unarchive") : t("sessions.archive")
1708
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1709
+ type: "button",
1710
+ className: "dsh-cs-link dsh-cs-danger",
1711
+ disabled: deleting || busyId === row.id || bulkArchiving,
1712
+ onClick: () => {
1713
+ openDelete([row.id]);
1714
+ },
1715
+ children: t("sessions.delete")
1716
+ })]
1717
+ })
1718
+ ]
1719
+ }, row.id);
1720
+ }),
1721
+ !loading && !loadFailed && filtered.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1722
+ className: "dsh-cs-empty",
1723
+ children: rows.length === 0 ? t("sessions.empty") : t("sessions.noMatches")
1724
+ })
1725
+ ]
1726
+ })
1727
+ }),
1728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1729
+ open: deleteTarget !== null,
1730
+ onClose: closeDelete,
1731
+ title: deleteIsBatch ? t("sessions.deleteSelectedTitle") : t("delete.title"),
1732
+ description: deleteIsBatch ? t("sessions.deleteBatchDescription", { count: deleteTarget?.ids.length ?? 0 }) : t("delete.description"),
1733
+ closeLabel: t("action.close"),
1734
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1735
+ variant: "outline",
1736
+ "data-modal-autofocus": true,
1737
+ disabled: deleting,
1738
+ onClick: closeDelete,
1739
+ children: t("action.cancel")
1740
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1741
+ variant: "outline",
1742
+ className: "dsh-cs-delete-action",
1743
+ disabled: deleting,
1744
+ onClick: () => {
1745
+ confirmDelete();
1746
+ },
1747
+ children: deleting ? t("delete.pending") : t("delete.confirm")
1748
+ })] }),
1749
+ children: deleteError === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1750
+ className: "dsh-cs-delete-error",
1751
+ role: "alert",
1752
+ children: deleteError
1753
+ })
1754
+ }),
1755
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1756
+ open: archiveStop !== null,
1757
+ onClose: closeArchiveStop,
1758
+ title: t("sessions.archiveStopTitle"),
1759
+ description: archiveStop === null ? void 0 : t("sessions.archiveStopDescription", { title: archiveStop.row.title }),
1760
+ closeLabel: t("action.close"),
1761
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1762
+ variant: "outline",
1763
+ "data-modal-autofocus": true,
1764
+ disabled: archiving,
1765
+ onClick: closeArchiveStop,
1766
+ children: t("action.cancel")
1767
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1768
+ variant: "outline",
1769
+ className: "dsh-cs-delete-action",
1770
+ disabled: archiving,
1771
+ onClick: () => {
1772
+ confirmStopArchive();
1773
+ },
1774
+ children: archiving ? t("sessions.archiveStopPending") : t("sessions.archiveStopAction")
1775
+ })] }),
1776
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1777
+ className: "dsh-cs-activity",
1778
+ "aria-label": t("sessions.archiveActivity"),
1779
+ children: (archiveStop?.activity ?? []).map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: activityLine(t, entry) }, `${entry.kind}-${String(index)}`))
1780
+ }), archiveError === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1781
+ className: "dsh-cs-delete-error",
1782
+ role: "alert",
1783
+ children: archiveError
1784
+ })]
1785
+ })
1786
+ ]
1787
+ });
1788
+ }
1789
+ // src/client/archive.ts
1790
+ /** Read the activity families out of a `workspace/session-active` refusal. */
1791
+ function activityOf(details) {
1792
+ const entries = details?.activity;
1793
+ if (!Array.isArray(entries)) return [];
1794
+ const activity = [];
1795
+ for (const entry of entries) {
1796
+ if (entry === null || typeof entry !== "object") continue;
1797
+ const record = entry;
1798
+ if (typeof record.kind !== "string" || record.kind === "") continue;
1799
+ const count = Array.isArray(record.items) && record.items.length > 0 ? record.items.length : 1;
1800
+ activity.push({
1801
+ kind: record.kind,
1802
+ count
1803
+ });
1804
+ }
1805
+ return activity;
1806
+ }
1807
+ /**
1808
+ * Archive or restore one Session.
1809
+ *
1810
+ * @param workspaces - the client Workspace service, when the composition mounts one.
1811
+ * @param sessionId - the Session to move between the two sets.
1812
+ * @param archived - which set it should end in.
1813
+ * @param stopActivity - ask the Host to stop running work instead of refusing.
1814
+ * @returns the outcome the panel renders.
1815
+ */
1816
+ async function setSessionArchived(workspaces, sessionId, archived, stopActivity) {
1817
+ if (workspaces === void 0 || workspaces === null || typeof workspaces.archiveSession !== "function" || typeof workspaces.unarchiveSession !== "function") return {
1818
+ ok: false,
1819
+ unavailable: true
1820
+ };
1821
+ try {
1822
+ if (archived) await workspaces.unarchiveSession(sessionId);
1823
+ else await workspaces.archiveSession(sessionId, stopActivity ? { stopActivity: true } : void 0);
1824
+ return { ok: true };
1825
+ } catch (error) {
1826
+ const failure = error;
1827
+ const code = failure?.rpcError?.code;
1828
+ if (code === "workspace/session-active") {
1829
+ const activity = activityOf(failure.rpcError?.details);
1830
+ if (activity.length > 0) return {
1831
+ ok: false,
1832
+ active: activity
1833
+ };
1834
+ return {
1835
+ ok: false,
1836
+ failure: { code: "session-running" }
1837
+ };
1838
+ }
1839
+ if (code === "session/not-found") return {
1840
+ ok: false,
1841
+ failure: { code: "unknown-session" }
1842
+ };
1843
+ return {
1844
+ ok: false,
1845
+ message: error instanceof Error ? error.message : String(error)
1846
+ };
1847
+ }
1848
+ }
1849
+ // src/client/nav-icon.ts
1850
+ /**
1851
+ * The archive glyph on the Settings panel's Sessions entry.
1852
+ *
1853
+ * A section registration carries a label, not an icon: the shipped settings
1854
+ * shell picks its navigation glyph from the section id (`navIcon(row.id)` in
1855
+ * ui-settings-general), and every id it does not know — a plugin's id included —
1856
+ * gets the generic gear. The entry therefore owns its glyph the only way a
1857
+ * plugin can: after the cell renders, hide the shell's gear and put the parsed
1858
+ * `assets/archive.svg` artwork in the seat beside the label. dsh-cool-theme
1859
+ * patches its palette into the same seat for the same reason.
1860
+ *
1861
+ * Matching is by the rendered label rather than by class hash, because the
1862
+ * shell's CSS module hashes rotate between releases. The observer only watches
1863
+ * child-list changes: opening and closing Settings replaces the cell's children,
1864
+ * while typing elsewhere in the page usually edits text nodes and must not
1865
+ * trigger a scan.
1866
+ */
1867
+ /** Marks the span this module inserts, so re-patching stays idempotent. */
1868
+ const ICON_ATTRIBUTE = "data-dsh-cs-nav-icon";
1869
+ /** Marks the gear this module hid, so disposal can restore it. */
1870
+ const HIDDEN_ATTRIBUTE = "data-dsh-cs-nav-hidden";
1871
+ /** The nav label in every translated language, plus the one this render is reading. */
1872
+ function settingsLabels(t) {
1873
+ const labels = new Set(navLabels());
1874
+ try {
1875
+ const current = t("settings.nav");
1876
+ if (current !== "") labels.add(current);
1877
+ } catch {}
1878
+ return [...labels];
1879
+ }
1880
+ /**
1881
+ * Find the settings navigation cells, tolerating a rotated class hash.
1882
+ *
1883
+ * The panel carries `data-shortcut-modal="settings"` in the shipped shell; using
1884
+ * it as the scope keeps the observer from scanning the rest of the page while
1885
+ * Settings is closed. Inside the panel the cell class may be rehashed, so the
1886
+ * fallback reads every button under the nav column.
1887
+ *
1888
+ * @returns the cells to inspect, or every button under a nav-like element when
1889
+ * the hashed name is not present.
1890
+ */
1891
+ function navCells() {
1892
+ const panel = document.querySelector("[data-shortcut-modal=\"settings\"]");
1893
+ if (panel === null) return [];
1894
+ const cells = panel.querySelectorAll("[class*=\"navCell\"]");
1895
+ if (cells.length > 0) return [...cells];
1896
+ return [...panel.querySelectorAll("[class*=\"nav\"] button")];
1897
+ }
1898
+ /**
1899
+ * Replace the gear on the Sessions cell with the plugin's archive glyph.
1900
+ *
1901
+ * @param labels - the label spellings that identify this section's cell.
1902
+ */
1903
+ function patch(labels) {
1904
+ for (const cell of navCells()) {
1905
+ const label = cell.querySelector("[class*=\"navLabel\"]")?.textContent ?? cell.textContent ?? "";
1906
+ if (!labels.has(label.trim())) continue;
1907
+ if (cell.querySelector(`[${ICON_ATTRIBUTE}]`) !== null) continue;
1908
+ const svg = cell.querySelector("svg");
1909
+ if (svg === null) continue;
1910
+ svg.style.display = "none";
1911
+ svg.setAttribute(HIDDEN_ATTRIBUTE, "1");
1912
+ const holder = document.createElement("span");
1913
+ holder.className = "dsh-cs-nav-icon";
1914
+ holder.setAttribute(ICON_ATTRIBUTE, "1");
1915
+ holder.setAttribute("aria-hidden", "true");
1916
+ holder.innerHTML = ARCHIVE_NAV_MARKUP;
1917
+ svg.after(holder);
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Keep the Sessions entry's glyph patched for the plugin's lifetime.
1922
+ *
1923
+ * Runs once for a panel that is already open and again whenever the shell mounts
1924
+ * or re-renders the navigation. The disposer removes the inserted spans and
1925
+ * restores the gears, so an HMR update does not leave two glyphs in the seat.
1926
+ *
1927
+ * @param t - the plugin's translate seat, for the current locale's label.
1928
+ * @returns the disposer that stops observation and restores the shell's DOM.
1929
+ */
1930
+ function installSessionNavIcon(t) {
1931
+ if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => {};
1932
+ const labels = new Set(settingsLabels(t).filter((label) => label.length > 0));
1933
+ const apply = () => {
1934
+ patch(labels);
1935
+ };
1936
+ apply();
1937
+ let frame;
1938
+ const schedule = () => {
1939
+ if (frame !== void 0) return;
1940
+ if (typeof requestAnimationFrame !== "function") {
1941
+ apply();
1942
+ return;
1943
+ }
1944
+ frame = requestAnimationFrame(() => {
1945
+ frame = void 0;
1946
+ apply();
1947
+ });
1948
+ };
1949
+ const observer = new MutationObserver(schedule);
1950
+ observer.observe(document.body, {
1951
+ childList: true,
1952
+ subtree: true
1953
+ });
1954
+ return () => {
1955
+ observer.disconnect();
1956
+ if (frame !== void 0) cancelAnimationFrame(frame);
1957
+ for (const holder of document.querySelectorAll(`[${ICON_ATTRIBUTE}]`)) holder.remove();
1958
+ for (const svg of document.querySelectorAll(`svg[${HIDDEN_ATTRIBUTE}]`)) {
1959
+ svg.style.display = "";
1960
+ svg.removeAttribute(HIDDEN_ATTRIBUTE);
1961
+ }
1962
+ };
1963
+ }
1964
+ // src/client/styles.ts
1965
+ /** CSS rules, one selector per line. */
1966
+ const PLUGIN_CSS = [
1967
+ ".dsh-cs-menu-delete-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-state-error-primary)}",
1968
+ ".dsh-cs-menu-delete-label{color:var(--dsw-alias-state-error-primary)}",
1969
+ ".dsh-cs-delete-action:not(:disabled){color:var(--dsw-alias-state-error-primary)}",
1970
+ ".dsh-cs-delete-error{padding:8px 9px;border:.5px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent);border-radius:var(--dsw-radius-sm,6px);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 9%,transparent);color:var(--dsw-alias-state-error-primary);font-size:11.5px;line-height:1.55;white-space:pre-wrap}",
1971
+ ".dsh-cs-settings{display:flex;flex-direction:column;box-sizing:border-box;height:100%;min-height:0;gap:12px;max-width:760px;color:var(--dsw-alias-label-primary)}",
1972
+ ".dsh-cs-settings-heading{margin:0;font-size:18px;font-weight:600;line-height:26px}",
1973
+ ".dsh-cs-settings-description{margin:2px 0 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}",
1974
+ ".dsh-cs-toolbar{display:flex;align-items:center;gap:8px;min-width:0}",
1975
+ ".dsh-cs-search{flex:1;min-width:120px;box-sizing:border-box}",
1976
+ ".dsh-cs-filter-wrap{flex:none;min-width:0}",
1977
+ ".dsh-cs-filter{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;height:32px;max-width:180px;padding:0 8px 0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}",
1978
+ ".dsh-cs-filter:hover{border-color:var(--dsw-alias-border-l3)}",
1979
+ ".dsh-cs-filter:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:1px}",
1980
+ ".dsh-cs-filter-label{flex:1;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}",
1981
+ ".dsh-cs-filter-workspace{width:150px}",
1982
+ ".dsh-cs-filter-archive{width:120px}",
1983
+ ".dsh-cs-caret{display:inline-flex;flex:none;align-items:center;justify-content:center;margin-left:auto;color:var(--dsw-alias-label-tertiary)}",
1984
+ ".dsh-cs-caret svg{display:block;width:12px;height:12px}",
1985
+ ".dsh-cs-menu.dsh-cs-menu{min-width:180px;max-width:280px}",
1986
+ ".dsh-cs-selection-bar{display:flex;align-items:center;gap:10px;min-height:22px}",
1987
+ ".dsh-cs-selection-count{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}",
1988
+ ".dsh-cs-bulk-actions{display:flex;align-items:center;gap:2px;margin-left:auto}",
1989
+ ".dsh-cs-head-check{display:flex;align-items:center;justify-content:center}",
1990
+ ".dsh-cs-row-check{justify-self:center}",
1991
+ ".dsh-cs-head-check input,.dsh-cs-row-check{margin:0;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer}",
1992
+ ".dsh-cs-head-check input:disabled,.dsh-cs-row-check:disabled{cursor:not-allowed;opacity:.4}",
1993
+ ".dsh-cs-problem{display:flex;align-items:flex-start;gap:8px;padding:7px 9px;border:.5px solid color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent);border-radius:var(--dsw-radius-sm,6px);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 9%,transparent);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
1994
+ ".dsh-cs-problem span{flex:1;min-width:0;white-space:pre-wrap}",
1995
+ ".dsh-cs-problem-dismiss{flex:none;border:0;background:transparent;padding:0 2px;color:inherit;font:inherit;font-size:14px;line-height:18px;cursor:pointer;opacity:.7}",
1996
+ ".dsh-cs-problem-dismiss:hover{opacity:1}",
1997
+ ".dsh-cs-table{display:flex;flex:1;flex-direction:column;min-height:0;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);overflow:hidden}",
1998
+ ".dsh-cs-table-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}",
1999
+ ".dsh-cs-table-head,.dsh-cs-row{display:grid;grid-template-columns:22px minmax(0,1fr) 66px 112px;align-items:center;column-gap:8px;padding:8px 10px}",
2000
+ ".dsh-cs-table-head{position:sticky;top:0;z-index:1;background:var(--dsw-alias-bg-layer-1);border-bottom:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);font-size:11.5px;line-height:18px}",
2001
+ ".dsh-cs-head-actions{padding-right:5px;text-align:right}",
2002
+ ".dsh-cs-row+.dsh-cs-row{border-top:.5px solid var(--dsw-alias-border-l2)}",
2003
+ ".dsh-cs-row:hover{background:var(--dsw-alias-interactive-bg-hover)}",
2004
+ ".dsh-cs-main{display:flex;flex-direction:column;gap:1px;min-width:0}",
2005
+ ".dsh-cs-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}",
2006
+ ".dsh-cs-meta{display:flex;align-items:center;gap:4px;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-tertiary)}",
2007
+ ".dsh-cs-workspace{overflow:hidden;text-overflow:ellipsis}",
2008
+ ".dsh-cs-sep{flex:none;opacity:.7}",
2009
+ ".dsh-cs-status{font-size:12px;line-height:18px;white-space:nowrap}",
2010
+ ".dsh-cs-status-running{color:var(--dsw-alias-state-success-primary)}",
2011
+ ".dsh-cs-status-unarchived{color:var(--dsw-alias-state-warn-primary)}",
2012
+ ".dsh-cs-status-archived{color:var(--dsw-alias-label-tertiary)}",
2013
+ ".dsh-cs-actions{display:flex;justify-content:flex-end;gap:2px;min-width:0}",
2014
+ ".dsh-cs-link{border:0;border-radius:var(--dsw-radius-sm);padding:2px 4px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}",
2015
+ ".dsh-cs-link:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
2016
+ ".dsh-cs-link:disabled{opacity:.45;cursor:not-allowed}",
2017
+ ".dsh-cs-danger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary)}",
2018
+ ".dsh-cs-date{flex:none;font-size:11.5px;line-height:17px;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}",
2019
+ ".dsh-cs-empty{padding:26px 12px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}",
2020
+ ".dsh-cs-empty p{margin:0 0 10px}",
2021
+ ".dsh-cs-activity{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;font-size:12.5px;line-height:19px;color:var(--dsw-alias-label-secondary)}",
2022
+ ".dsh-cs-nav-icon{display:inline-flex;flex:none;align-items:center;justify-content:center}",
2023
+ ".dsh-cs-nav-icon svg{width:16px;height:16px;display:block}"
2024
+ ].join("\n");
2025
+ /**
2026
+ * Insert the stylesheet once for the plugin lifetime.
2027
+ *
2028
+ * The tag carries the platform's style-ownership record — `data-plugin` with this
2029
+ * package's id, which is what every DSH bundle writes on the tags it injects. A
2030
+ * tag carrying no attribute is claimed by whichever module materializes next and
2031
+ * deleted with it, so naming the owner here keeps the sheet this plugin's own:
2032
+ * only a reload of `dsh-cool-session` retires it, and this effect's disposer
2033
+ * removes it then anyway.
2034
+ *
2035
+ * @returns disposer that removes exactly this element.
2036
+ */
2037
+ function insertStyles() {
2038
+ const element = document.createElement("style");
2039
+ element.setAttribute("data-plugin", "dsh-cool-session");
2040
+ element.setAttribute("data-dsh-cool-session", "styles");
2041
+ element.textContent = PLUGIN_CSS;
2042
+ document.head.appendChild(element);
2043
+ return () => {
2044
+ element.remove();
2045
+ };
2046
+ }
2047
+ // src/client/index.ts
2048
+ /**
2049
+ * Client entry for dsh-cool-session.
2050
+ *
2051
+ * Contributes one native Session-menu row, the frame-wide confirmation it
2052
+ * opens, and the Sessions section of the Settings panel. The row lives in
2053
+ * `sidebar.workspaces.session.menu.item`; the dialog lives in `shell.overlay`,
2054
+ * because the menu unmounts as soon as it closes and cannot own a dialog that
2055
+ * must outlive that click; the section lives in `settings.section`, which the
2056
+ * settings shell projects into its navigation.
2057
+ *
2058
+ * The section reads the shell's Session and Workspace stores directly and
2059
+ * archives through the Workspace controller, so only deletion — the operation
2060
+ * the client face does not expose — needs this package's own Host route.
2061
+ */
2062
+ const name = "dsh-cool-session/client";
2063
+ /**
2064
+ * The locale service is a required collaborator rather than an optional one.
2065
+ *
2066
+ * The confirmation is worded entirely from this package's dictionaries, so a
2067
+ * composition without the service could only draw the wrong language: declaring
2068
+ * the dependency is what makes the framework apply this plugin once the service
2069
+ * is there, and what makes `ctx.get('locale')` below answer. DSH mounts the
2070
+ * service with the web client tree itself (`@deepseek-ai/dsh-web-app`), so
2071
+ * nothing this menu row can run in is without it.
2072
+ */
2073
+ const inject = ["slots", "locale"];
2074
+ function apply(ctx) {
2075
+ const slots = ctx.get("slots");
2076
+ if (slots === void 0) return;
2077
+ const locale = ctx.get("locale");
2078
+ ctx.effect(() => insertStyles(), "dsh-cool-session: styles");
2079
+ ctx.effect(() => locale.register(NS, DICTIONARIES), "dsh-cool-session: dictionaries");
2080
+ const t = locale.bind(NS);
2081
+ const afterDelete = (sessionId) => {
2082
+ const uiWorkspace = ctx.get("uiWorkspace");
2083
+ try {
2084
+ if (uiWorkspace?.mainReference?.sessionId === sessionId) uiWorkspace.clearMain?.();
2085
+ } catch {}
2086
+ try {
2087
+ ctx.get("sessions")?.refresh?.();
2088
+ } catch {}
2089
+ try {
2090
+ ctx.get("workspaces")?.refresh?.();
2091
+ } catch {}
2092
+ };
2093
+ const setArchived = (sessionId, archived, stopActivity) => setSessionArchived(ctx.get("workspaces"), sessionId, archived, stopActivity);
2094
+ const refreshSessions = () => {
2095
+ try {
2096
+ ctx.get("sessions")?.refresh?.();
2097
+ } catch {}
2098
+ };
2099
+ slots.inject("settings.section", () => slots.register({
2100
+ name: "settings.section",
2101
+ id: "dsh-cool-session.sessions",
2102
+ order: 12,
2103
+ label: () => t("settings.nav"),
2104
+ locale: NS,
2105
+ inject: () => ({
2106
+ setArchived,
2107
+ refresh: refreshSessions,
2108
+ onDeleted: afterDelete
2109
+ })
2110
+ }, SessionsSection));
2111
+ ctx.effect(() => installSessionNavIcon(t), "dsh-cool-session: settings nav icon");
2112
+ slots.inject("sidebar.workspaces.session.menu.item", () => slots.register({
2113
+ name: "sidebar.workspaces.session.menu.item",
2114
+ id: "dsh-cool-session.delete",
2115
+ order: 500,
2116
+ locale: NS
2117
+ }, SessionDeleteMenuItem));
2118
+ ctx.effect(() => ctx.inject(["contextMenus"], (menuCtx) => {
2119
+ retireLegacyDeleteRow();
2120
+ menuCtx.slots.inject("contextMenu.icon", () => menuCtx.slots.register({
2121
+ name: "contextMenu.icon",
2122
+ key: "dsh-cool-session.delete"
2123
+ }, _deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular));
2124
+ menuCtx.effect(() => menuCtx.contextMenus.register({
2125
+ id: "dsh-cool-session.delete",
2126
+ target: "session",
2127
+ order: 700,
2128
+ observe: () => [locale],
2129
+ resolve: ({ sessionId }) => [{
2130
+ id: "delete",
2131
+ label: t("action.delete"),
2132
+ icon: { key: "dsh-cool-session.delete" },
2133
+ afterContent: true,
2134
+ separatorBefore: true,
2135
+ danger: true,
2136
+ run: () => requestDeleteSession(sessionId)
2137
+ }]
2138
+ }), "dsh-cool-session: session menu action");
2139
+ }).dispose, "dsh-cool-session: session menu service");
2140
+ slots.inject("shell.overlay", () => slots.register({
2141
+ name: "shell.overlay",
2142
+ id: "dsh-cool-session.delete-dialog",
2143
+ locale: NS,
2144
+ inject: () => ({ onDeleted: afterDelete })
2145
+ }, SessionDeleteDialog));
2146
+ }
2147
+ exports.apply = apply;
2148
+ exports.inject = inject;
2149
+ exports.name = name;
2150
+ return module.exports;
2151
+ }
2152
+ });