@deepseek-ai/dsh-client-ui-workspace 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1894 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@deepseek-ai/dsh-client-ui-workspace",
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_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ //#region lib/types/client/stores.js
12
+ /**
13
+ * The workspace browser's viewing store: the session-list grouping mode,
14
+ * persisted across reloads. Module level exports the factory only (a
15
+ * module-level handle would pin the store identity across plugin reloads);
16
+ * register() receives the factory and the browser derives its PropsStore
17
+ * share from the return type.
18
+ */
19
+ /**
20
+ * Create the workspace browser viewing store handle.
21
+ * @returns the store handle (spec + type + identity + factory in one).
22
+ */
23
+ function createWorkspaceViewStore() {
24
+ return (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
25
+ init: () => ({ groupBy: "workspace" }),
26
+ persist: "dsh.workspace.view",
27
+ actions: { setGroupBy: (d, mode) => {
28
+ d.groupBy = mode;
29
+ } }
30
+ });
31
+ }
32
+ //#endregion
33
+ //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
34
+ function r(e) {
35
+ var t, f, n = "";
36
+ if ("string" == typeof e || "number" == typeof e) n += e;
37
+ else if ("object" == typeof e) if (Array.isArray(e)) {
38
+ var o = e.length;
39
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
40
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
41
+ return n;
42
+ }
43
+ function clsx() {
44
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
45
+ return n;
46
+ }
47
+ /** Display label for the ungrouped bucket row. */
48
+ const UNGROUPED_LABEL = "Ungrouped";
49
+ /**
50
+ * Directory display label: basename of the path (both separators accepted).
51
+ * Ungrouped-bucket fallback for surfaces without a workspace title.
52
+ * @param cwd - directory path, or undefined for the ungrouped bucket.
53
+ * @returns basename, the raw cwd when it has no basename, or the ungrouped label.
54
+ */
55
+ function projectLabel(cwd) {
56
+ if (cwd === void 0 || cwd === "") return UNGROUPED_LABEL;
57
+ const base = cwd.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
58
+ return base !== void 0 && base !== "" ? base : cwd;
59
+ }
60
+ /** Recency comparator: newest first, id as the deterministic tiebreak (ids are unique per group). */
61
+ function byRecency(a, b) {
62
+ if (b.updatedAt !== a.updatedAt) return b.updatedAt - a.updatedAt;
63
+ return a.id < b.id ? -1 : 1;
64
+ }
65
+ /**
66
+ * Ordinary sessions are visible; among blank sessions, only the current one
67
+ * is visible. Subagent children use their parent header catalog; archived
68
+ * sessions are visible nowhere, while their accounting slots remain so
69
+ * unarchiving restores position.
70
+ */
71
+ function sessionVisible(session, current, archived) {
72
+ return session.origin !== "subagent" && !archived.has(session.id) && (!session.blank || session.id === current);
73
+ }
74
+ /**
75
+ * A blank session is the selected Workspace's provisional New Session row;
76
+ * its canonical title never enters search (blank rows are query-excluded)
77
+ * and the renderer localizes its display label.
78
+ */
79
+ function sessionTitle(session) {
80
+ return session.blank ? "New Session" : session.displayTitle;
81
+ }
82
+ /** Build one group without projecting session lineage into presentation. */
83
+ function buildGroup(key, workspaceId, cwd, createdAt, label, members, order) {
84
+ const sessions = [...members];
85
+ if (order === "recency") sessions.sort(byRecency);
86
+ return {
87
+ key,
88
+ workspaceId,
89
+ cwd,
90
+ createdAt,
91
+ label,
92
+ sessions
93
+ };
94
+ }
95
+ /**
96
+ * Group Sessions by Host Workspace: one group per entity in stable Host
97
+ * order, with members resolved from sessionIds in their stored order. Sessions
98
+ * outside every Workspace trail in the recency-ordered Ungrouped bucket.
99
+ */
100
+ function groupByWorkspace(list, workspaces, archived) {
101
+ const groups = [];
102
+ const accounted = /* @__PURE__ */ new Set();
103
+ for (const workspace of workspaces) {
104
+ const members = [];
105
+ for (const id of workspace.sessionIds) {
106
+ const summary = list.byId[id];
107
+ if (summary === void 0) continue;
108
+ accounted.add(id);
109
+ if (!sessionVisible(summary, list.current, archived)) continue;
110
+ members.push(summary);
111
+ }
112
+ groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members, "account"));
113
+ }
114
+ const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, list.current, archived));
115
+ if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, UNGROUPED_LABEL, stray, "recency"));
116
+ return groups;
117
+ }
118
+ function sessionNode(s, descendants) {
119
+ return {
120
+ id: s.id,
121
+ title: sessionTitle(s),
122
+ blank: s.blank,
123
+ running: s.running,
124
+ runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
125
+ completed: s.completed === true,
126
+ updatedAt: s.updatedAt,
127
+ ...s.pendingInteraction === void 0 ? {} : { pendingInteraction: s.pendingInteraction }
128
+ };
129
+ }
130
+ /**
131
+ * Derive the workspace browser groups with every session as a top-level row.
132
+ *
133
+ * Every group shows; sessions populate under expanded groups, preserving
134
+ * Host account order. Blank sessions are excluded except for the selected
135
+ * provisional New Session row; archived sessions are excluded everywhere.
136
+ * Content search lives outside this derivation
137
+ * (see {@link deriveSearchResults}).
138
+ * @param list - sessions list snapshot (`current` feeds containsCurrent).
139
+ * @param workspaces - real workspaces in stable Host order.
140
+ * @param archivedSessionIds - registry-global archive set.
141
+ * @param view - local expansion arrays.
142
+ * @returns group sections in render order.
143
+ */
144
+ function deriveGroups(list, workspaces, archivedSessionIds, view) {
145
+ const archived = new Set(archivedSessionIds);
146
+ const expandedProjects = new Set(view.expandedProjects);
147
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
148
+ const currentGroup = list.current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(list.current))?.workspaceId ?? "";
149
+ const groups = [];
150
+ for (const g of groupByWorkspace(list, workspaces, archived)) {
151
+ const expanded = expandedProjects.has(g.key);
152
+ groups.push({
153
+ key: g.key,
154
+ workspaceId: g.workspaceId,
155
+ cwd: g.cwd,
156
+ createdAt: g.createdAt,
157
+ label: g.label,
158
+ sessionCount: g.sessions.length,
159
+ expanded,
160
+ containsCurrent: g.key === currentGroup,
161
+ sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants)) : []
162
+ });
163
+ }
164
+ return groups;
165
+ }
166
+ /**
167
+ * Derive the flat session list ("In one list" mode): every session — fork
168
+ * children included — as a top-level row, strictly newest-first. No grouping,
169
+ * no parent/child adjacency. Content search lives outside this derivation
170
+ * (see {@link deriveSearchResults}).
171
+ * @param list - sessions list snapshot.
172
+ * @param archivedSessionIds - registry-global archive set.
173
+ * @returns flat rows in render order.
174
+ */
175
+ function deriveFlat(list, archivedSessionIds) {
176
+ const archived = new Set(archivedSessionIds);
177
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
178
+ const rows = [];
179
+ for (const id of list.ids) {
180
+ const s = list.byId[id];
181
+ if (s === void 0 || !sessionVisible(s, list.current, archived)) continue;
182
+ rows.push(s);
183
+ }
184
+ rows.sort(byRecency);
185
+ return rows.map((session) => sessionNode(session, descendants));
186
+ }
187
+ /**
188
+ * Merge immediate title/Workspace substring matches with ranked Host content
189
+ * matches. Local rows lead newest-first, content-only rows retain backend
190
+ * order, and duplicate sessions receive the backend snippet in place.
191
+ * @param list - session metadata authority.
192
+ * @param workspaces - Workspace membership and display labels.
193
+ * @param query - caller text; surrounding whitespace is ignored.
194
+ * @param archivedSessionIds - registry-global archive set (members never match).
195
+ * @param content - ranked Host content-search page.
196
+ * @param limit - protocol-owned maximum merged row count.
197
+ * @returns bounded deduplicated flat rows and a refine-query hint bit.
198
+ */
199
+ function deriveSearchResults(list, workspaces, query, archivedSessionIds, content, limit) {
200
+ const q = query.trim().toLowerCase();
201
+ if (q === "") return {
202
+ items: [],
203
+ hasMore: false
204
+ };
205
+ const archived = new Set(archivedSessionIds);
206
+ const descendants = (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(list.byId);
207
+ const workspaceBySession = /* @__PURE__ */ new Map();
208
+ for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
209
+ const labelOf = (summary) => workspaceBySession.get(summary.id) ?? projectLabel(summary.cwd);
210
+ const contentBySession = /* @__PURE__ */ new Map();
211
+ for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
212
+ const local = [];
213
+ for (const id of list.ids) {
214
+ const summary = list.byId[id];
215
+ if (summary === void 0 || summary.blank || !sessionVisible(summary, list.current, archived)) continue;
216
+ if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
217
+ }
218
+ local.sort(byRecency);
219
+ const ordered = [];
220
+ const included = /* @__PURE__ */ new Set();
221
+ const include = (summary) => {
222
+ if (included.has(summary.id)) return;
223
+ included.add(summary.id);
224
+ ordered.push(summary);
225
+ };
226
+ for (const summary of local) include(summary);
227
+ for (const item of content.items) {
228
+ const summary = list.byId[item.sessionId];
229
+ if (summary !== void 0 && !summary.blank && sessionVisible(summary, list.current, archived)) include(summary);
230
+ }
231
+ return {
232
+ items: ordered.slice(0, limit).map((summary) => {
233
+ const match = contentBySession.get(summary.id);
234
+ return {
235
+ id: summary.id,
236
+ title: sessionTitle(summary),
237
+ workspace: labelOf(summary),
238
+ running: summary.running,
239
+ runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
240
+ ...summary.pendingInteraction === void 0 ? {} : { pendingInteraction: summary.pendingInteraction },
241
+ completed: summary.completed === true,
242
+ ...match === void 0 ? {} : { snippet: match.snippet }
243
+ };
244
+ }),
245
+ hasMore: content.hasMore || ordered.length > limit
246
+ };
247
+ }
248
+ /**
249
+ * Compact relative time for session rows, as a structured bucket the
250
+ * renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
251
+ * @param updatedAt - epoch ms of the session's last activity.
252
+ * @param now - current epoch ms (injected for pure rendering).
253
+ * @returns the row's trailing time bucket and magnitude.
254
+ */
255
+ function relativeTime(updatedAt, now) {
256
+ const MIN = 6e4;
257
+ const HOUR = 36e5;
258
+ const DAY = 864e5;
259
+ const diff = Math.max(0, now - updatedAt);
260
+ if (diff < MIN) return {
261
+ unit: "now",
262
+ n: 0
263
+ };
264
+ if (diff < HOUR) return {
265
+ unit: "minutes",
266
+ n: Math.floor(diff / MIN)
267
+ };
268
+ if (diff < DAY) return {
269
+ unit: "hours",
270
+ n: Math.floor(diff / HOUR)
271
+ };
272
+ if (diff < 30 * DAY) return {
273
+ unit: "days",
274
+ n: Math.floor(diff / DAY)
275
+ };
276
+ if (diff < 365 * DAY) return {
277
+ unit: "months",
278
+ n: Math.floor(diff / (30 * DAY))
279
+ };
280
+ return {
281
+ unit: "years",
282
+ n: Math.floor(diff / (365 * DAY))
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
287
+ const css$2 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding:0 8px;display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-active)}.YDXeBa_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:62px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:7px 8px;display:flex}.YDXeBa_searchResultRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-active)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;margin-left:20px;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{color:var(--dsw-alias-label-tertiary)}.YDXeBa_searchResultSnippet{color:var(--dsw-alias-label-secondary)}.YDXeBa_projectRow{box-sizing:border-box;align-items:flex-start;height:54px;padding-top:6px;padding-bottom:6px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{height:34px;animation:YDXeBa_row-in .15s var(--ds-ease-in-out);gap:0}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}@keyframes YDXeBa_row-in{0%{opacity:0}}.YDXeBa_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.YDXeBa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.YDXeBa_folderActive{color:var(--dsw-alias-state-business-primary)}.YDXeBa_projectRow .YDXeBa_chevron{display:none}.YDXeBa_projectRow:hover .YDXeBa_chevron{display:inline-flex}.YDXeBa_projectRow:hover .YDXeBa_folder{display:none}.YDXeBa_arrow{transition:transform .15s var(--ds-ease-in-out)}.YDXeBa_arrowOpen{transform:rotate(90deg)}.YDXeBa_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.YDXeBa_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_renameInput{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.YDXeBa_sessionRow .YDXeBa_title{flex:1}.YDXeBa_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.YDXeBa_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:12px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time{display:none}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore{box-shadow:0 -2px 0 0 var(--dsw-alias-state-business-primary)}.YDXeBa_sessionRow.YDXeBa_dropAfter{box-shadow:0 2px 0 0 var(--dsw-alias-state-business-primary)}.YDXeBa_hoverContent{flex-direction:column;gap:8px;display:flex}.YDXeBa_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.YDXeBa_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.YDXeBa_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.YDXeBa_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.YDXeBa_iconButton{cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.YDXeBa_iconButton:hover{color:var(--dsw-alias-label-primary)}.YDXeBa_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.YDXeBa_sessionRow,.YDXeBa_arrow{transition:none;animation:none}}";
288
+ const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
289
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
290
+ const tag = document.createElement("style");
291
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
292
+ tag.dataset.pluginCss = tagId$2;
293
+ tag.textContent = css$2;
294
+ document.head.appendChild(tag);
295
+ }
296
+ var Rows_module_css_default = {
297
+ "searchResultRow": "YDXeBa_searchResultRow",
298
+ "slot": "YDXeBa_slot",
299
+ "title": "YDXeBa_title",
300
+ "row-in": "YDXeBa_row-in",
301
+ "folderActive": "YDXeBa_folderActive",
302
+ "rowActions": "YDXeBa_rowActions",
303
+ "searchResultHeading": "YDXeBa_searchResultHeading",
304
+ "hoverStatus": "YDXeBa_hoverStatus",
305
+ "menuOpen": "YDXeBa_menuOpen",
306
+ "chevron": "YDXeBa_chevron",
307
+ "hoverPath": "YDXeBa_hoverPath",
308
+ "visuallyHidden": "YDXeBa_visuallyHidden",
309
+ "searchResultTitle": "YDXeBa_searchResultTitle",
310
+ "searchResultWorkspace": "YDXeBa_searchResultWorkspace",
311
+ "projectText": "YDXeBa_projectText",
312
+ "selected": "YDXeBa_selected",
313
+ "hoverContent": "YDXeBa_hoverContent",
314
+ "renameInput": "YDXeBa_renameInput",
315
+ "dropAfter": "YDXeBa_dropAfter",
316
+ "folder": "YDXeBa_folder",
317
+ "searchResultSnippet": "YDXeBa_searchResultSnippet",
318
+ "meta": "YDXeBa_meta",
319
+ "time": "YDXeBa_time",
320
+ "dropBefore": "YDXeBa_dropBefore",
321
+ "sessionRow": "YDXeBa_sessionRow",
322
+ "hoverTime": "YDXeBa_hoverTime",
323
+ "iconButton": "YDXeBa_iconButton",
324
+ "hoverTitle": "YDXeBa_hoverTitle",
325
+ "dot": "YDXeBa_dot",
326
+ "arrow": "YDXeBa_arrow",
327
+ "projectRow": "YDXeBa_projectRow",
328
+ "arrowOpen": "YDXeBa_arrowOpen"
329
+ };
330
+ //#endregion
331
+ //#region lib/types/client/rows/Rows.js
332
+ /**
333
+ * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
334
+ * all data and callbacks arrive via props. Hover swaps (folder->chevron,
335
+ * time->ellipsis, action buttons) are CSS-only. Row ... menus are visual-only
336
+ * except workspace Rename/Delete and session Rename/Fork/Archive; the session
337
+ * and workspace hover cards are suppressed while a menu is open.
338
+ */
339
+ /** Row display title: blank rows show the localized New Session label. */
340
+ function displayTitle(node, t) {
341
+ return node.blank ? t("session.new") : node.title;
342
+ }
343
+ /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
344
+ function timeLabel(updatedAt, now, t) {
345
+ const { unit, n } = relativeTime(updatedAt, now);
346
+ return unit === "now" ? t("time.now") : t(`time.${unit}`, { n });
347
+ }
348
+ /** Hover-card variant: distances wrap in the ago template; the now bucket stays bare (no "now ago"). */
349
+ function hoverTimeLabel(updatedAt, now, t) {
350
+ const { unit, n } = relativeTime(updatedAt, now);
351
+ return unit === "now" ? t("time.now") : t("time.ago", { t: t(`time.${unit}`, { n }) });
352
+ }
353
+ /**
354
+ * Absolute creation time through the dictionary's date template (the message
355
+ * clock pattern): `toLocaleString` would follow the browser language, not the
356
+ * app locale, and produce mixed-language text after a switch.
357
+ */
358
+ function createdLabel(createdAt, t) {
359
+ const d = new Date(createdAt);
360
+ const pad2 = (v) => String(v).padStart(2, "0");
361
+ return t("hover.created", { time: `${t("date.ymd", {
362
+ y: d.getFullYear(),
363
+ m: d.getMonth() + 1,
364
+ d: d.getDate()
365
+ })} ${pad2(d.getHours())}:${pad2(d.getMinutes())}` });
366
+ }
367
+ /** Hover-card body: workspace title, full directory path, absolute creation time. */
368
+ function WorkspaceHoverContent({ label, cwd, createdAt, t }) {
369
+ return (0, react_jsx_runtime.jsxs)("div", {
370
+ className: Rows_module_css_default.hoverContent,
371
+ children: [
372
+ (0, react_jsx_runtime.jsx)("div", {
373
+ className: Rows_module_css_default.hoverTitle,
374
+ children: label
375
+ }),
376
+ (0, react_jsx_runtime.jsx)("div", {
377
+ className: Rows_module_css_default.hoverPath,
378
+ children: cwd
379
+ }),
380
+ (0, react_jsx_runtime.jsx)("div", {
381
+ className: Rows_module_css_default.hoverTime,
382
+ children: createdLabel(createdAt, t)
383
+ })
384
+ ]
385
+ });
386
+ }
387
+ /**
388
+ * Project (workspace) header row: 54px, folder + title + session count;
389
+ * hover reveals the chevron and create button, and dwelling on a real
390
+ * Workspace shows its hover card (the ungrouped bucket has none).
391
+ * `containsCurrent` arrives on the node (derivation fact, no renderer scan).
392
+ * @param props.group - derived group node.
393
+ * @param props.onToggle - expand/collapse the group.
394
+ * @param props.onCreate - start a frontend Session inside this Workspace.
395
+ * @param props.t - the browser root's locale seat.
396
+ * @returns the row element.
397
+ */
398
+ function ProjectRowItem({ group, onToggle, onCreate, actions, t }) {
399
+ const row = group;
400
+ const label = row.workspaceId === void 0 ? t("group.ungrouped") : row.label;
401
+ const active = group.expanded && group.containsCurrent;
402
+ const count = t(row.sessionCount === 1 ? "sessions.count.one" : "sessions.count.other", { n: row.sessionCount });
403
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
404
+ const workspaceMenuItems = [{
405
+ id: "rename",
406
+ label: t("rename"),
407
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
408
+ }, {
409
+ id: "delete",
410
+ label: t("delete.workspace"),
411
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
412
+ danger: true
413
+ }];
414
+ const ownRow = (0, react_jsx_runtime.jsxs)("div", {
415
+ className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
416
+ role: "treeitem",
417
+ "aria-expanded": row.expanded,
418
+ onClick: onToggle,
419
+ children: [
420
+ (0, react_jsx_runtime.jsx)("span", {
421
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
422
+ children: row.expanded ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {})
423
+ }),
424
+ (0, react_jsx_runtime.jsx)("span", {
425
+ className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
426
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFill14, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
427
+ }),
428
+ (0, react_jsx_runtime.jsxs)("span", {
429
+ className: Rows_module_css_default.projectText,
430
+ children: [(0, react_jsx_runtime.jsx)("span", {
431
+ className: Rows_module_css_default.title,
432
+ children: label
433
+ }), (0, react_jsx_runtime.jsx)("span", {
434
+ className: Rows_module_css_default.meta,
435
+ children: count
436
+ })]
437
+ }),
438
+ (0, react_jsx_runtime.jsxs)("span", {
439
+ className: Rows_module_css_default.rowActions,
440
+ children: [actions !== void 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
441
+ open: menuOpen,
442
+ onClose: () => {
443
+ setMenuOpen(false);
444
+ },
445
+ items: workspaceMenuItems,
446
+ onSelect: (id) => {
447
+ setMenuOpen(false);
448
+ /* v8 ignore next -- workspaceMenuItems carries exactly these two rows today. */
449
+ if (id !== "rename" && id !== "delete") return;
450
+ if (id === "rename") actions.rename();
451
+ else actions.delete();
452
+ },
453
+ portal: true,
454
+ closeOnPointerLeave: true,
455
+ anchor: (0, react_jsx_runtime.jsx)("button", {
456
+ type: "button",
457
+ className: Rows_module_css_default.iconButton,
458
+ "aria-label": t("actions.workspace.aria", { name: label }),
459
+ onClick: (e) => {
460
+ e.stopPropagation();
461
+ setMenuOpen((v) => !v);
462
+ },
463
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
464
+ })
465
+ }), (0, react_jsx_runtime.jsx)("button", {
466
+ type: "button",
467
+ className: Rows_module_css_default.iconButton,
468
+ "aria-label": t("actions.newSession.aria", { name: label }),
469
+ onClick: (e) => {
470
+ e.stopPropagation();
471
+ onCreate();
472
+ },
473
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
474
+ })]
475
+ })
476
+ ]
477
+ });
478
+ if (row.createdAt === void 0) return ownRow;
479
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
480
+ anchor: ownRow,
481
+ content: (0, react_jsx_runtime.jsx)(WorkspaceHoverContent, {
482
+ label: row.label,
483
+ cwd: row.cwd,
484
+ createdAt: row.createdAt,
485
+ t
486
+ }),
487
+ disabled: menuOpen,
488
+ copyText: row.cwd,
489
+ copyLabel: t("copy"),
490
+ copiedLabel: t("hover.copied")
491
+ });
492
+ }
493
+ /* v8 ignore next 3 -- closed-union backstop; only reached if the status is forged */
494
+ function assertNever(value) {
495
+ throw new Error(`unknown pending interaction: ${String(value)}`);
496
+ }
497
+ /**
498
+ * Session status presentation; pending interaction is primary and live activity
499
+ * outranks completion reminders.
500
+ */
501
+ function sessionStatuses(node, t) {
502
+ const subagents = node.runningSubagentCount === 0 ? void 0 : {
503
+ state: "ongoing",
504
+ label: t(node.runningSubagentCount === 1 ? "status.subagentsRunning.one" : "status.subagentsRunning.other", { n: node.runningSubagentCount })
505
+ };
506
+ let pending;
507
+ switch (node.pendingInteraction) {
508
+ case "approval":
509
+ pending = {
510
+ state: "warning",
511
+ label: t("status.waitingApproval")
512
+ };
513
+ break;
514
+ case "plan-review":
515
+ pending = {
516
+ state: "warning",
517
+ label: t("status.planReview")
518
+ };
519
+ break;
520
+ case "question":
521
+ pending = {
522
+ state: "warning",
523
+ label: t("status.waitingAnswer")
524
+ };
525
+ break;
526
+ case void 0: break;
527
+ /* v8 ignore next -- closed PendingInteractionStatus union */
528
+ default: return assertNever(node.pendingInteraction);
529
+ }
530
+ if (pending !== void 0) return subagents === void 0 ? [pending] : [pending, subagents];
531
+ if (node.running) {
532
+ const primary = {
533
+ state: "ongoing",
534
+ label: t("status.running")
535
+ };
536
+ return subagents === void 0 ? [primary] : [primary, subagents];
537
+ }
538
+ if (subagents !== void 0) return [subagents];
539
+ if (node.completed) return [{
540
+ state: "done",
541
+ label: t("status.completed")
542
+ }];
543
+ return [{
544
+ state: "done",
545
+ label: t("status.idle")
546
+ }];
547
+ }
548
+ /** Hover-card body: full title, relative time, and every relevant live status. */
549
+ function SessionHoverContent({ node, now, t }) {
550
+ const statuses = sessionStatuses(node, t);
551
+ return (0, react_jsx_runtime.jsxs)("div", {
552
+ className: Rows_module_css_default.hoverContent,
553
+ children: [
554
+ (0, react_jsx_runtime.jsx)("div", {
555
+ className: Rows_module_css_default.hoverTitle,
556
+ children: displayTitle(node, t)
557
+ }),
558
+ !node.blank && (0, react_jsx_runtime.jsx)("div", {
559
+ className: Rows_module_css_default.hoverTime,
560
+ children: hoverTimeLabel(node.updatedAt, now, t)
561
+ }),
562
+ statuses.map((status) => (0, react_jsx_runtime.jsxs)("div", {
563
+ className: Rows_module_css_default.hoverStatus,
564
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: status.state }), (0, react_jsx_runtime.jsx)("span", { children: status.label })]
565
+ }, status.label))
566
+ ]
567
+ });
568
+ }
569
+ /**
570
+ * One flat search result: title, Workspace context, and optional content
571
+ * excerpt. Search navigation opens the session only; it does not address an
572
+ * event inside the conversation.
573
+ * @param props.result - merged local/content search row.
574
+ * @param props.currentId - selected session id.
575
+ * @param props.onOpen - open the selected session.
576
+ * @param props.t - Workspace-browser translation seat.
577
+ * @returns the result button.
578
+ */
579
+ function SearchResultItem({ result, currentId, onOpen, t }) {
580
+ const selected = result.id === currentId;
581
+ const statuses = sessionStatuses(result, t);
582
+ const primaryStatus = statuses[0];
583
+ return (0, react_jsx_runtime.jsxs)("button", {
584
+ type: "button",
585
+ className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected),
586
+ role: "treeitem",
587
+ "aria-selected": selected,
588
+ onClick: () => {
589
+ onOpen(result.id);
590
+ },
591
+ children: [
592
+ (0, react_jsx_runtime.jsxs)("span", {
593
+ className: Rows_module_css_default.searchResultHeading,
594
+ children: [(0, react_jsx_runtime.jsx)("span", {
595
+ className: Rows_module_css_default.slot,
596
+ children: (primaryStatus.state !== "done" || result.completed) && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: primaryStatus.state }), statuses.map((status) => (0, react_jsx_runtime.jsx)("span", {
597
+ className: Rows_module_css_default.visuallyHidden,
598
+ children: status.label
599
+ }, status.label))] })
600
+ }), (0, react_jsx_runtime.jsx)("span", {
601
+ className: Rows_module_css_default.searchResultTitle,
602
+ children: result.title
603
+ })]
604
+ }),
605
+ (0, react_jsx_runtime.jsx)("span", {
606
+ className: Rows_module_css_default.searchResultWorkspace,
607
+ children: result.workspace
608
+ }),
609
+ result.snippet !== void 0 && (0, react_jsx_runtime.jsx)("span", {
610
+ className: Rows_module_css_default.searchResultSnippet,
611
+ children: result.snippet
612
+ })
613
+ ]
614
+ });
615
+ }
616
+ /** Pointer-position half of a row (insert line above or below). */
617
+ function rowHalf(e) {
618
+ const rect = e.currentTarget.getBoundingClientRect();
619
+ return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
620
+ }
621
+ /**
622
+ * One top-level 34px session row: status dot (pending user interaction outranks
623
+ * own or descendant activity), title, relative time, and the row actions menu.
624
+ * @param props.node - derived session node.
625
+ * @param props.currentId - selected session id (row highlight).
626
+ * @param props.now - epoch ms for relative-time formatting.
627
+ * @param props.onOpen - open a session by id.
628
+ * @param props.onRename - open the session rename dialog (id + current title).
629
+ * @param props.onFork - fork a session at its last completed turn.
630
+ * @param props.onArchive - archive a session by id.
631
+ * @param props.drag - optional draggable-row wiring.
632
+ * @param props.t - the browser root's locale seat.
633
+ * @returns the session row.
634
+ */
635
+ function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, drag, t }) {
636
+ const row = node;
637
+ const title = displayTitle(node, t);
638
+ const selected = node.id === currentId;
639
+ const statuses = sessionStatuses(node, t);
640
+ const primaryStatus = statuses[0];
641
+ const [menuOpen, setMenuOpen] = (0, react.useState)(false);
642
+ const sessionMenuItems = [
643
+ {
644
+ id: "rename",
645
+ label: t("rename"),
646
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
647
+ },
648
+ {
649
+ id: "fork",
650
+ label: t("menu.fork"),
651
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
652
+ },
653
+ {
654
+ id: "archive",
655
+ label: t("menu.archiveSession"),
656
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
657
+ }
658
+ ];
659
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
660
+ anchor: (0, react_jsx_runtime.jsxs)("div", {
661
+ className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
662
+ role: "treeitem",
663
+ "aria-selected": selected,
664
+ onClick: () => {
665
+ onOpen(node.id);
666
+ },
667
+ draggable: drag !== void 0,
668
+ onDragStart: drag === void 0 ? void 0 : (e) => {
669
+ e.dataTransfer.effectAllowed = "move";
670
+ drag.start();
671
+ },
672
+ onDragEnd: drag?.end,
673
+ onDragOver: drag === void 0 ? void 0 : (e) => {
674
+ if (!drag.active) return;
675
+ e.preventDefault();
676
+ e.dataTransfer.dropEffect = "move";
677
+ drag.hover(rowHalf(e));
678
+ },
679
+ onDrop: drag === void 0 ? void 0 : (e) => {
680
+ if (!drag.active) return;
681
+ e.preventDefault();
682
+ drag.drop(rowHalf(e));
683
+ },
684
+ children: [
685
+ (0, react_jsx_runtime.jsx)("span", {
686
+ className: Rows_module_css_default.slot,
687
+ children: (primaryStatus.state !== "done" || row.completed) && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: primaryStatus.state }), statuses.map((status) => (0, react_jsx_runtime.jsx)("span", {
688
+ className: Rows_module_css_default.visuallyHidden,
689
+ children: status.label
690
+ }, status.label))] })
691
+ }),
692
+ (0, react_jsx_runtime.jsx)("span", {
693
+ className: Rows_module_css_default.title,
694
+ children: title
695
+ }),
696
+ !row.blank && (0, react_jsx_runtime.jsx)("span", {
697
+ className: Rows_module_css_default.time,
698
+ children: timeLabel(row.updatedAt, now, t)
699
+ }),
700
+ !row.blank && (0, react_jsx_runtime.jsx)("span", {
701
+ className: Rows_module_css_default.rowActions,
702
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
703
+ open: menuOpen,
704
+ onClose: () => {
705
+ setMenuOpen(false);
706
+ },
707
+ items: sessionMenuItems,
708
+ onSelect: (id) => {
709
+ setMenuOpen(false);
710
+ if (id === "rename") onRename(node.id, row.title);
711
+ if (id === "fork") onFork(node.id);
712
+ if (id === "archive") onArchive(node.id);
713
+ },
714
+ portal: true,
715
+ closeOnPointerLeave: true,
716
+ anchor: (0, react_jsx_runtime.jsx)("button", {
717
+ type: "button",
718
+ className: Rows_module_css_default.iconButton,
719
+ "aria-label": t("actions.session.aria", { name: title }),
720
+ onClick: (e) => {
721
+ e.stopPropagation();
722
+ setMenuOpen((v) => !v);
723
+ },
724
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
725
+ })
726
+ })
727
+ })
728
+ ]
729
+ }),
730
+ content: (0, react_jsx_runtime.jsx)(SessionHoverContent, {
731
+ node,
732
+ now,
733
+ t
734
+ }),
735
+ disabled: menuOpen || drag?.active === true,
736
+ copyText: row.blank ? void 0 : row.title,
737
+ copyLabel: t("copy"),
738
+ copiedLabel: t("hover.copied")
739
+ });
740
+ }
741
+ //#endregion
742
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/WorkspacePicker.module.css.mjs
743
+ const css$1 = "._G5b-a_modalAction{min-width:72px}._G5b-a_modalError,._G5b-a_menuStatus{margin-top:8px;font-size:12px;line-height:18px}._G5b-a_modalError{color:var(--dsw-alias-state-error-primary)}._G5b-a_menuStatus{color:var(--dsw-alias-label-secondary)}";
744
+ const tagId$1 = "@deepseek-ai/dsh-client-ui-workspace/WorkspacePicker.module.css";
745
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
746
+ const tag = document.createElement("style");
747
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
748
+ tag.dataset.pluginCss = tagId$1;
749
+ tag.textContent = css$1;
750
+ document.head.appendChild(tag);
751
+ }
752
+ var WorkspacePicker_module_css_default = {
753
+ "menuStatus": "_G5b-a_menuStatus",
754
+ "modalError": "_G5b-a_modalError",
755
+ "modalAction": "_G5b-a_modalAction"
756
+ };
757
+ //#endregion
758
+ //#region lib/types/client/WorkspacePicker.js
759
+ const ADD_WORKSPACE = "::add-workspace";
760
+ /**
761
+ * Render the pick menu plus the adoption error dialog.
762
+ * @param props - owner-controlled flow props.
763
+ * @returns menu + dialog elements.
764
+ */
765
+ function WorkspacePickFlow({ t, open, anchorRef, useWorkspaces, createWorkspace, useDirectoryFlow, renderDirectoryFlow, onPick, onClose, addOnly = false, side = "bottom", selectedId }) {
766
+ const workspaceSnapshot = useWorkspaces((state) => state);
767
+ const workspaces = workspaceSnapshot.items;
768
+ const getAnchorRect = (0, react.useCallback)(() => anchorRef?.current?.getBoundingClientRect() ?? null, [anchorRef]);
769
+ const [errorOpen, setErrorOpen] = (0, react.useState)(false);
770
+ const [modalError, setModalError] = (0, react.useState)(null);
771
+ const [flowOpen, setFlowOpen] = (0, react.useState)(false);
772
+ const [pickingFolder, setPickingFolder] = (0, react.useState)(false);
773
+ const flowBusy = flowOpen || pickingFolder;
774
+ const flowAvailable = useDirectoryFlow((occupied) => occupied);
775
+ (0, react.useEffect)(() => {
776
+ if (flowOpen && !flowAvailable) setFlowOpen(false);
777
+ }, [flowOpen, flowAvailable]);
778
+ const addEntries = flowAvailable ? [{
779
+ id: ADD_WORKSPACE,
780
+ label: t("menu.addWorkspace"),
781
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 16 }),
782
+ disabled: flowBusy
783
+ }] : [];
784
+ const pinAdd = !addOnly && workspaces.length > 0;
785
+ const items = pinAdd ? workspaces.map((workspace) => ({
786
+ id: workspace.workspaceId,
787
+ label: workspace.title,
788
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 16 }),
789
+ disabled: flowBusy
790
+ })) : addEntries;
791
+ const menuIsEmpty = items.length === 0;
792
+ const closeModal = () => {
793
+ setErrorOpen(false);
794
+ setModalError(null);
795
+ };
796
+ /** Adopt a picked directory; failures land in the folder-error dialog (Choose again reopens the flow). */
797
+ const adoptDirectory = (path) => createWorkspace({ path }).then((workspace) => {
798
+ setFlowOpen(false);
799
+ onPick(workspace.workspaceId);
800
+ }).catch((reason) => {
801
+ setModalError(reason instanceof Error ? reason.message : String(reason));
802
+ setFlowOpen(false);
803
+ setErrorOpen(true);
804
+ });
805
+ const openDirectoryFlow = (0, react.useCallback)(() => {
806
+ onClose();
807
+ setErrorOpen(false);
808
+ setModalError(null);
809
+ setFlowOpen(true);
810
+ }, [onClose]);
811
+ const listSettled = addOnly || workspaceSnapshot.phase === "ready";
812
+ const addIsTheOnlyEntry = !pinAdd && listSettled && addEntries.length === 1;
813
+ (0, react.useEffect)(() => {
814
+ if (open && addIsTheOnlyEntry && !flowBusy) openDirectoryFlow();
815
+ }, [
816
+ open,
817
+ addIsTheOnlyEntry,
818
+ flowBusy,
819
+ openDirectoryFlow
820
+ ]);
821
+ /** Owner side of the flow conversation: adopt keeps the flow open (busy) until the Host answers. */
822
+ const flowOwner = {
823
+ open: flowOpen,
824
+ busy: pickingFolder,
825
+ onPicked: (path) => {
826
+ setPickingFolder(true);
827
+ adoptDirectory(path).finally(() => {
828
+ setPickingFolder(false);
829
+ });
830
+ },
831
+ onCancel: () => {
832
+ setFlowOpen(false);
833
+ },
834
+ onError: (message) => {
835
+ setFlowOpen(false);
836
+ setModalError(message);
837
+ setErrorOpen(true);
838
+ }
839
+ };
840
+ const handleSelect = (id) => {
841
+ if (id === ADD_WORKSPACE) {
842
+ openDirectoryFlow();
843
+ return;
844
+ }
845
+ onPick(id);
846
+ };
847
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
848
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
849
+ open: open && !addIsTheOnlyEntry && !menuIsEmpty,
850
+ anchor: null,
851
+ items,
852
+ ...pinAdd ? { footer: addEntries } : {},
853
+ selectedId,
854
+ onSelect: handleSelect,
855
+ onClose,
856
+ side,
857
+ portal: true,
858
+ getAnchorRect
859
+ }),
860
+ open && !addIsTheOnlyEntry && !menuIsEmpty && workspaceSnapshot.phase === "pending" && (0, react_jsx_runtime.jsx)("div", {
861
+ className: WorkspacePicker_module_css_default.menuStatus,
862
+ role: "status",
863
+ children: t("picker.loading")
864
+ }),
865
+ renderDirectoryFlow(flowOwner),
866
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
867
+ open: errorOpen,
868
+ onClose: closeModal,
869
+ closeLabel: t("close"),
870
+ title: t("folderError.title"),
871
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
872
+ variant: "outline",
873
+ className: WorkspacePicker_module_css_default.modalAction,
874
+ onClick: closeModal,
875
+ children: t("cancel")
876
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
877
+ variant: "primary",
878
+ className: WorkspacePicker_module_css_default.modalAction,
879
+ disabled: !flowAvailable,
880
+ onClick: openDirectoryFlow,
881
+ children: t("folderError.retry")
882
+ })] }),
883
+ children: (0, react_jsx_runtime.jsx)("div", {
884
+ className: WorkspacePicker_module_css_default.modalError,
885
+ role: "alert",
886
+ children: modalError
887
+ })
888
+ })
889
+ ] });
890
+ }
891
+ /**
892
+ * The conversation empty-state registration: adapts the owner share to the
893
+ * core flow (all state and semantics live in the flow / the owner).
894
+ * @param props - empty-state slot props (owner share + injected creation callback).
895
+ * @returns the flow element.
896
+ */
897
+ function WorkspacePicker({ open, anchorRef, useWorkspaces, selectedId, onPick, onClose, createWorkspace, useDirectoryFlow, renderSlot, t }) {
898
+ return (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
899
+ t,
900
+ open,
901
+ anchorRef,
902
+ useWorkspaces,
903
+ createWorkspace,
904
+ useDirectoryFlow,
905
+ renderDirectoryFlow: (owner) => renderSlot("conversation.hero.workspace.directoryFlow", owner),
906
+ selectedId,
907
+ onPick,
908
+ onClose
909
+ });
910
+ }
911
+ //#endregion
912
+ //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css.mjs
913
+ const css = ".qDHVXG_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:8px;--dsh-session-list-scrollbar-offset:2px;box-sizing:border-box;min-height:0;padding-right:var(--dsh-session-list-edge-inset);flex-direction:column;flex:1;display:flex}.qDHVXG_root.qDHVXG_rail{padding-right:0}.qDHVXG_iconButton{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_sectionHeader{box-sizing:border-box;height:36px;color:var(--dsw-alias-label-tertiary);border-radius:12px;flex:none;justify-content:flex-end;align-items:center;gap:4px;margin-bottom:4px;padding-left:12px;display:flex;overflow:hidden}.qDHVXG_sectionLabel{white-space:nowrap;flex:1;min-width:0;line-height:20px;overflow:hidden}.qDHVXG_search{--dsh-search-input-fill:var(--dsw-static-neutral-bluish-75);box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsh-search-input-fill);height:38px;color:var(--dsw-alias-label-caption);border-radius:24px;flex:none;align-items:center;gap:8px;margin:0 2px 12px;padding:0 14px;display:flex;overflow:hidden}body[data-ds-dark-theme] .qDHVXG_search{--dsh-search-input-fill:var(--dsw-static-neutral-bluish-900)}.qDHVXG_searchButton{pointer-events:none;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_searchInput{min-width:0;color:var(--dsw-alias-label-primary);background:0 0;border:none;outline:none;flex:1;font-size:14px;line-height:20px}.qDHVXG_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.qDHVXG_clearButton{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-secondary);background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.qDHVXG_rail .qDHVXG_sectionHeader{gap:0;margin-bottom:12px;padding-left:0}.qDHVXG_rail .qDHVXG_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_search{background:0 0;border-color:#0000;gap:0;height:36px;margin:0 0 12px;padding:0}.qDHVXG_rail .qDHVXG_searchButton{pointer-events:auto;cursor:pointer;width:36px;height:36px;color:var(--dsw-alias-label-primary)}.qDHVXG_rail .qDHVXG_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.qDHVXG_listArea{min-height:0;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;display:flex;overflow:hidden}.qDHVXG_rail .qDHVXG_listArea{margin-right:0}.qDHVXG_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.qDHVXG_fade{left:0;right:var(--dsh-session-list-edge-inset);background:linear-gradient(to bottom, transparent, var(--dsw-specific-sidebar-fill));pointer-events:none;height:72px;position:absolute;bottom:0}.qDHVXG_wide{animation:qDHVXG_wide-in .2s var(--ds-ease-in-out)}@keyframes qDHVXG_wide-in{0%{opacity:0}}.qDHVXG_list{min-height:0;margin-right:var(--dsh-session-list-scrollbar-offset);padding-right:calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width) - var(--dsh-session-list-scrollbar-offset));scrollbar-gutter:stable;flex:1;padding-bottom:48px;overflow-y:auto}.qDHVXG_flatList>*+*,.qDHVXG_searchTree>[role=treeitem]+[role=treeitem],.qDHVXG_groupSection>*+*{margin-top:2px}.qDHVXG_searchStatus,.qDHVXG_searchWarning{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.qDHVXG_searchWarning{color:var(--dsw-alias-label-secondary)}.qDHVXG_groupSection+.qDHVXG_groupSection{margin-top:4px}.qDHVXG_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.qDHVXG_renameInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.qDHVXG_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.qDHVXG_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.qDHVXG_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.qDHVXG_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.qDHVXG_wide{animation:none}}";
914
+ const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
915
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
916
+ const tag = document.createElement("style");
917
+ tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
918
+ tag.dataset.pluginCss = tagId;
919
+ tag.textContent = css;
920
+ document.head.appendChild(tag);
921
+ }
922
+ var WorkspaceBrowser_module_css_default = {
923
+ "root": "qDHVXG_root",
924
+ "wide": "qDHVXG_wide",
925
+ "searchButton": "qDHVXG_searchButton",
926
+ "sectionLabel": "qDHVXG_sectionLabel",
927
+ "clearButton": "qDHVXG_clearButton",
928
+ "list": "qDHVXG_list",
929
+ "renameError": "qDHVXG_renameError",
930
+ "deleteAction": "qDHVXG_deleteAction",
931
+ "searchInput": "qDHVXG_searchInput",
932
+ "fade": "qDHVXG_fade",
933
+ "deleteStatus": "qDHVXG_deleteStatus",
934
+ "listArea": "qDHVXG_listArea",
935
+ "iconButton": "qDHVXG_iconButton",
936
+ "wide-in": "qDHVXG_wide-in",
937
+ "groupSection": "qDHVXG_groupSection",
938
+ "rail": "qDHVXG_rail",
939
+ "renameInput": "qDHVXG_renameInput",
940
+ "flatList": "qDHVXG_flatList",
941
+ "sectionHeader": "qDHVXG_sectionHeader",
942
+ "searchTree": "qDHVXG_searchTree",
943
+ "searchStatus": "qDHVXG_searchStatus",
944
+ "searchWarning": "qDHVXG_searchWarning",
945
+ "search": "qDHVXG_search",
946
+ "treeBody": "qDHVXG_treeBody",
947
+ "empty": "qDHVXG_empty"
948
+ };
949
+ //#endregion
950
+ //#region lib/types/client/WorkspaceBrowser.js
951
+ /**
952
+ * The workspace/session browsing region filling the sidebar shell's
953
+ * `sidebar.workspaces` hole: section header (title + group-by + add
954
+ * workspace), search, the grouped tree or flat list, and the workspace
955
+ * dialogs. Wide state renders the full browser; rail state renders the two
956
+ * region icons (search / add workspace), each requesting shell expansion
957
+ * through the owner share. Adding is the header button's one action, so it
958
+ * raises the directory flow with no menu in between; the flow and its error
959
+ * dialog live in WorkspacePicker (same package — direct composition, no slot
960
+ * between them).
961
+ */
962
+ /**
963
+ * Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
964
+ * focus() forces a synchronous layout and would jank the slide.
965
+ */
966
+ const EXPAND_SLIDE_MS = 300;
967
+ /** Pause between the latest keystroke and a Host content-search request. */
968
+ const SEARCH_DEBOUNCE_MS = 250;
969
+ /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
970
+ const SEARCH_QUERY_MAX_CODE_UNITS = 500;
971
+ /** Keep controlled input and RPC payload inside the session.search wire contract. */
972
+ function sanitizeSearchQuery(value) {
973
+ const withoutNul = value.replaceAll("\0", "");
974
+ if (withoutNul.length <= SEARCH_QUERY_MAX_CODE_UNITS) return withoutNul;
975
+ let end = SEARCH_QUERY_MAX_CODE_UNITS;
976
+ const last = withoutNul.charCodeAt(end - 1);
977
+ const next = withoutNul.charCodeAt(end);
978
+ if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
979
+ return withoutNul.slice(0, end);
980
+ }
981
+ /** Immutable membership toggle for the local expansion arrays. */
982
+ function toggled(list, key) {
983
+ return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
984
+ }
985
+ /** Group-by strategy menu; own open state so it resets with the wide chrome. */
986
+ function GroupByMenu({ groupBy, onPick, t }) {
987
+ const [open, setOpen] = (0, react.useState)(false);
988
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
989
+ open,
990
+ onClose: () => {
991
+ setOpen(false);
992
+ },
993
+ items: [
994
+ {
995
+ type: "label",
996
+ id: "group-by",
997
+ text: t("groupBy.label")
998
+ },
999
+ {
1000
+ id: "workspace",
1001
+ label: t("groupBy.workspace")
1002
+ },
1003
+ {
1004
+ id: "flat",
1005
+ label: t("groupBy.flat")
1006
+ }
1007
+ ],
1008
+ selectedId: groupBy,
1009
+ onSelect: (id) => {
1010
+ /* v8 ignore next -- narrowing guard: the heading label is not selectable, so the only arriving ids are the two modes. */
1011
+ if (id === "workspace" || id === "flat") onPick(id);
1012
+ setOpen(false);
1013
+ },
1014
+ align: "end",
1015
+ portal: true,
1016
+ anchor: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1017
+ label: t("groupBy.label"),
1018
+ side: "bottom",
1019
+ delayMs: 500,
1020
+ children: (0, react_jsx_runtime.jsx)("button", {
1021
+ type: "button",
1022
+ className: clsx(WorkspaceBrowser_module_css_default.iconButton, WorkspaceBrowser_module_css_default.wide),
1023
+ "aria-label": t("groupBy.label"),
1024
+ onClick: () => {
1025
+ setOpen((v) => !v);
1026
+ },
1027
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPersonalizationOutline16, {})
1028
+ })
1029
+ })
1030
+ });
1031
+ }
1032
+ /** The scrolling session tree; unmounting at collapse settle drops the sessions subscription and expansion state. */
1033
+ function SessionTree({ useSessions, startSession, open, forkSession, workspaces, archivedSessionIds, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertSessionBefore, t }) {
1034
+ const list = useSessions((s) => s);
1035
+ const current = list.current;
1036
+ const [expandedProjects, setExpandedProjects] = (0, react.useState)([]);
1037
+ const [drag, setDrag] = (0, react.useState)(null);
1038
+ const currentGroup = current === void 0 ? void 0 : workspaces.find((w) => w.sessionIds.includes(current))?.workspaceId ?? "";
1039
+ (0, react.useEffect)(() => {
1040
+ if (current === void 0 || currentGroup === void 0) return;
1041
+ setExpandedProjects((l) => l.includes(currentGroup) ? l : [...l, currentGroup]);
1042
+ }, [current, currentGroup]);
1043
+ const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, archivedSessionIds, { expandedProjects }), [
1044
+ list,
1045
+ workspaces,
1046
+ archivedSessionIds,
1047
+ expandedProjects
1048
+ ]);
1049
+ const now = Date.now();
1050
+ return (0, react_jsx_runtime.jsxs)("div", {
1051
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1052
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1053
+ className: WorkspaceBrowser_module_css_default.list,
1054
+ role: "tree",
1055
+ "aria-label": t("section.sessions"),
1056
+ children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1057
+ className: WorkspaceBrowser_module_css_default.empty,
1058
+ children: t("empty.none")
1059
+ }), groups.map((group) => (0, react_jsx_runtime.jsxs)("div", {
1060
+ className: WorkspaceBrowser_module_css_default.groupSection,
1061
+ children: [(0, react_jsx_runtime.jsx)(ProjectRowItem, {
1062
+ group,
1063
+ t,
1064
+ onToggle: () => {
1065
+ setExpandedProjects((l) => toggled(l, group.key));
1066
+ },
1067
+ onCreate: () => {
1068
+ if (group.workspaceId !== void 0) startSession(group.workspaceId);
1069
+ },
1070
+ actions: group.workspaceId === void 0 ? void 0 : {
1071
+ rename: () => {
1072
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1073
+ if (group.workspaceId !== void 0) onRenameRequest(group.workspaceId, group.label);
1074
+ },
1075
+ delete: () => {
1076
+ /* v8 ignore next -- narrowing guard: the actions object exists only for real-workspace groups. */
1077
+ if (group.workspaceId !== void 0) onDeleteRequest(group.workspaceId, group.label);
1078
+ }
1079
+ }
1080
+ }), group.sessions.map((node, index) => {
1081
+ const draggable = group.workspaceId !== void 0;
1082
+ const sameGroupDrag = drag !== null && drag.workspaceId === group.workspaceId;
1083
+ return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1084
+ node,
1085
+ currentId: current,
1086
+ now,
1087
+ onOpen: open,
1088
+ onRename: onSessionRename,
1089
+ onFork: forkSession,
1090
+ onArchive: onSessionArchive,
1091
+ drag: !draggable || group.workspaceId === void 0 ? void 0 : {
1092
+ start: () => {
1093
+ setDrag({
1094
+ workspaceId: group.workspaceId,
1095
+ sessionId: node.id,
1096
+ over: null
1097
+ });
1098
+ },
1099
+ active: sameGroupDrag,
1100
+ marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
1101
+ hover: (half) => {
1102
+ /* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
1103
+ setDrag((d) => d === null ? d : {
1104
+ ...d,
1105
+ over: {
1106
+ id: node.id,
1107
+ half
1108
+ }
1109
+ });
1110
+ },
1111
+ drop: (half) => {
1112
+ /* v8 ignore next -- narrowing guard: Rows gates drop on `active`, which is false while the drag state is null. */
1113
+ if (drag === null) return;
1114
+ const sessions = group.sessions;
1115
+ const anchor = half === "before" ? node.id : sessions[index + 1]?.id;
1116
+ setDrag(null);
1117
+ if (anchor === drag.sessionId) return;
1118
+ const sourceIndex = sessions.findIndex((r) => r.id === drag.sessionId);
1119
+ const anchorIndex = anchor === void 0 ? sessions.length : sessions.findIndex((r) => r.id === anchor);
1120
+ if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
1121
+ insertSessionBefore(drag.workspaceId, drag.sessionId, anchor).catch((reason) => {
1122
+ console.warn("session reorder rejected:", reason);
1123
+ });
1124
+ },
1125
+ end: () => {
1126
+ setDrag(null);
1127
+ }
1128
+ },
1129
+ t
1130
+ }, node.id);
1131
+ })]
1132
+ }, group.key))]
1133
+ }), (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
1134
+ });
1135
+ }
1136
+ /** The flat "In one list" body: every session a top-level row, newest-first. */
1137
+ function FlatList({ useSessions, open, forkSession, onSessionRename, onSessionArchive, archivedSessionIds, t }) {
1138
+ const list = useSessions((s) => s);
1139
+ const rows = (0, react.useMemo)(() => deriveFlat(list, archivedSessionIds), [list, archivedSessionIds]);
1140
+ const now = Date.now();
1141
+ return (0, react_jsx_runtime.jsxs)("div", {
1142
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1143
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1144
+ className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
1145
+ role: "tree",
1146
+ "aria-label": t("section.sessions"),
1147
+ children: [rows.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1148
+ className: WorkspaceBrowser_module_css_default.empty,
1149
+ children: t("empty.none")
1150
+ }), rows.map((node) => (0, react_jsx_runtime.jsx)(SessionNodeItem, {
1151
+ node,
1152
+ currentId: list.current,
1153
+ now,
1154
+ onOpen: open,
1155
+ onRename: onSessionRename,
1156
+ onFork: forkSession,
1157
+ onArchive: onSessionArchive,
1158
+ t
1159
+ }, node.id))]
1160
+ }), (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
1161
+ });
1162
+ }
1163
+ /** Flat search body: local metadata matches plus the current Host result page. */
1164
+ function SearchResults({ useSessions, open, workspaces, archivedSessionIds, query, remote, resultLimit, t }) {
1165
+ const list = useSessions((s) => s);
1166
+ const currentRemote = remote.query === query ? remote : {
1167
+ query,
1168
+ status: "loading",
1169
+ items: [],
1170
+ hasMore: false
1171
+ };
1172
+ const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, currentRemote, resultLimit), [
1173
+ list,
1174
+ workspaces,
1175
+ query,
1176
+ archivedSessionIds,
1177
+ currentRemote,
1178
+ resultLimit
1179
+ ]);
1180
+ const pending = currentRemote.status === "loading";
1181
+ const failed = currentRemote.status === "error";
1182
+ return (0, react_jsx_runtime.jsxs)("div", {
1183
+ className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
1184
+ children: [(0, react_jsx_runtime.jsxs)("div", {
1185
+ className: WorkspaceBrowser_module_css_default.list,
1186
+ children: [
1187
+ (0, react_jsx_runtime.jsx)("div", {
1188
+ className: WorkspaceBrowser_module_css_default.searchTree,
1189
+ role: "tree",
1190
+ "aria-label": t("search.results.aria"),
1191
+ children: results.items.map((result) => (0, react_jsx_runtime.jsx)(SearchResultItem, {
1192
+ result,
1193
+ currentId: list.current,
1194
+ onOpen: open,
1195
+ t
1196
+ }, result.id))
1197
+ }),
1198
+ pending && (0, react_jsx_runtime.jsx)("div", {
1199
+ className: WorkspaceBrowser_module_css_default.searchStatus,
1200
+ role: "status",
1201
+ children: t("search.pending")
1202
+ }),
1203
+ failed && (0, react_jsx_runtime.jsx)("div", {
1204
+ className: WorkspaceBrowser_module_css_default.searchWarning,
1205
+ role: "status",
1206
+ children: t("search.unavailable")
1207
+ }),
1208
+ !pending && results.items.length === 0 && (0, react_jsx_runtime.jsx)("div", {
1209
+ className: WorkspaceBrowser_module_css_default.empty,
1210
+ children: t("search.noMatches")
1211
+ }),
1212
+ results.hasMore && (0, react_jsx_runtime.jsx)("div", {
1213
+ className: WorkspaceBrowser_module_css_default.searchStatus,
1214
+ children: t("search.hasMore", { n: resultLimit })
1215
+ })
1216
+ ]
1217
+ }), (0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })]
1218
+ });
1219
+ }
1220
+ /**
1221
+ * Render the browsing region.
1222
+ * @param props - composed slot props (shell owner share + store + injected actions).
1223
+ * @returns the region element tree.
1224
+ */
1225
+ function WorkspaceBrowser({ wide, expandSidebar, useSessions, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, archiveSession, insertSessionBefore, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, renderSlot, t }) {
1226
+ const workspaces = useWorkspaces((state) => state.items);
1227
+ const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
1228
+ const directoryFlowAvailable = useDirectoryFlow((occupied) => occupied);
1229
+ const groupBy = useStore((s) => s.groupBy);
1230
+ const [query, setQuery] = (0, react.useState)("");
1231
+ const normalizedQuery = sanitizeSearchQuery(query).trim();
1232
+ const [remoteSearch, setRemoteSearch] = (0, react.useState)({
1233
+ query: "",
1234
+ status: "idle",
1235
+ items: [],
1236
+ hasMore: false
1237
+ });
1238
+ const searchInput = (0, react.useRef)(null);
1239
+ const [wsPickerOpen, setWsPickerOpen] = (0, react.useState)(false);
1240
+ const wsPlusRef = (0, react.useRef)(null);
1241
+ const composingRef = (0, react.useRef)(false);
1242
+ const [searchOnExpand, setSearchOnExpand] = (0, react.useState)(false);
1243
+ (0, react.useEffect)(() => {
1244
+ if (wide && searchOnExpand) {
1245
+ const timer = window.setTimeout(() => {
1246
+ searchInput.current?.focus({ preventScroll: true });
1247
+ setSearchOnExpand(false);
1248
+ }, EXPAND_SLIDE_MS);
1249
+ return () => {
1250
+ window.clearTimeout(timer);
1251
+ };
1252
+ }
1253
+ }, [wide, searchOnExpand]);
1254
+ (0, react.useEffect)(() => {
1255
+ if (normalizedQuery === "") {
1256
+ setRemoteSearch({
1257
+ query: "",
1258
+ status: "idle",
1259
+ items: [],
1260
+ hasMore: false
1261
+ });
1262
+ return;
1263
+ }
1264
+ const controller = new AbortController();
1265
+ setRemoteSearch({
1266
+ query: normalizedQuery,
1267
+ status: "loading",
1268
+ items: [],
1269
+ hasMore: false
1270
+ });
1271
+ const timer = window.setTimeout(() => {
1272
+ searchSessions(normalizedQuery, controller.signal).then((result) => {
1273
+ if (controller.signal.aborted) return;
1274
+ setRemoteSearch({
1275
+ query: normalizedQuery,
1276
+ status: "ready",
1277
+ items: result.items,
1278
+ hasMore: result.hasMore
1279
+ });
1280
+ }).catch(() => {
1281
+ if (controller.signal.aborted) return;
1282
+ setRemoteSearch({
1283
+ query: normalizedQuery,
1284
+ status: "error",
1285
+ items: [],
1286
+ hasMore: false
1287
+ });
1288
+ });
1289
+ }, SEARCH_DEBOUNCE_MS);
1290
+ return () => {
1291
+ window.clearTimeout(timer);
1292
+ controller.abort();
1293
+ };
1294
+ }, [normalizedQuery, searchSessions]);
1295
+ const [renameTarget, setRenameTarget] = (0, react.useState)(null);
1296
+ const [renameDraft, setRenameDraft] = (0, react.useState)("");
1297
+ const [renaming, setRenaming] = (0, react.useState)(false);
1298
+ const [renameError, setRenameError] = (0, react.useState)(null);
1299
+ const renameTrimmed = renameDraft.trim();
1300
+ const renameDuplicate = renameTarget !== null && renameTrimmed !== "" && renameTrimmed !== renameTarget.currentTitle && workspaces.some((w) => w.title === renameTrimmed);
1301
+ const renameBlocked = renaming || renameTrimmed === "" || renameTarget === null || renameTrimmed === renameTarget.currentTitle || renameDuplicate;
1302
+ const closeRename = () => {
1303
+ if (renaming) return;
1304
+ setRenameTarget(null);
1305
+ setRenameError(null);
1306
+ };
1307
+ const confirmRename = () => {
1308
+ if (renameBlocked) return;
1309
+ setRenaming(true);
1310
+ setRenameError(null);
1311
+ renameWorkspace(renameTarget.workspaceId, renameTrimmed).then(() => {
1312
+ setRenaming(false);
1313
+ setRenameTarget(null);
1314
+ }).catch((reason) => {
1315
+ setRenaming(false);
1316
+ setRenameError(reason instanceof Error ? reason.message : String(reason));
1317
+ });
1318
+ };
1319
+ const [sessionRenameTarget, setSessionRenameTarget] = (0, react.useState)(null);
1320
+ const [sessionRenameDraft, setSessionRenameDraft] = (0, react.useState)("");
1321
+ const [sessionRenaming, setSessionRenaming] = (0, react.useState)(false);
1322
+ const [sessionRenameError, setSessionRenameError] = (0, react.useState)(null);
1323
+ const sessionRenameTrimmed = sessionRenameDraft.trim();
1324
+ const sessionRenameBlocked = sessionRenaming || sessionRenameTrimmed === "" || sessionRenameTarget === null;
1325
+ const closeSessionRename = () => {
1326
+ if (sessionRenaming) return;
1327
+ setSessionRenameTarget(null);
1328
+ setSessionRenameError(null);
1329
+ };
1330
+ const confirmSessionRename = () => {
1331
+ if (sessionRenameBlocked) return;
1332
+ setSessionRenaming(true);
1333
+ setSessionRenameError(null);
1334
+ renameSession(sessionRenameTarget.sessionId, sessionRenameTrimmed).then(() => {
1335
+ setSessionRenaming(false);
1336
+ setSessionRenameTarget(null);
1337
+ }).catch((reason) => {
1338
+ setSessionRenaming(false);
1339
+ setSessionRenameError(reason instanceof Error ? reason.message : String(reason));
1340
+ });
1341
+ };
1342
+ const onSessionRename = (sessionId, currentTitle) => {
1343
+ setSessionRenameTarget({
1344
+ sessionId,
1345
+ currentTitle
1346
+ });
1347
+ setSessionRenameDraft(currentTitle);
1348
+ setSessionRenameError(null);
1349
+ };
1350
+ const onSessionArchive = (sessionId) => {
1351
+ archiveSession(sessionId).catch((reason) => {
1352
+ console.warn("session archive rejected:", reason);
1353
+ });
1354
+ };
1355
+ const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
1356
+ const [deleting, setDeleting] = (0, react.useState)(false);
1357
+ const [deleteCommittedId, setDeleteCommittedId] = (0, react.useState)(null);
1358
+ const [deleteError, setDeleteError] = (0, react.useState)(null);
1359
+ (0, react.useEffect)(() => {
1360
+ if (deleteCommittedId === null || workspaces.some((workspace) => workspace.workspaceId === deleteCommittedId)) return;
1361
+ setDeleting(false);
1362
+ setDeleteCommittedId(null);
1363
+ setDeleteTarget(null);
1364
+ }, [deleteCommittedId, workspaces]);
1365
+ const closeDelete = () => {
1366
+ if (deleting) return;
1367
+ setDeleteTarget(null);
1368
+ setDeleteError(null);
1369
+ };
1370
+ const confirmDelete = () => {
1371
+ /* v8 ignore next -- the Modal is absent without a target and its button is disabled while deleting. */
1372
+ if (deleting || deleteTarget === null) return;
1373
+ setDeleting(true);
1374
+ setDeleteCommittedId(null);
1375
+ setDeleteError(null);
1376
+ deleteWorkspace(deleteTarget.workspaceId).then(() => {
1377
+ setDeleteCommittedId(deleteTarget.workspaceId);
1378
+ }).catch((reason) => {
1379
+ setDeleting(false);
1380
+ setDeleteError(reason instanceof Error ? reason.message : String(reason));
1381
+ });
1382
+ };
1383
+ return (0, react_jsx_runtime.jsxs)("div", {
1384
+ className: clsx(WorkspaceBrowser_module_css_default.root, !wide && WorkspaceBrowser_module_css_default.rail),
1385
+ children: [
1386
+ (0, react_jsx_runtime.jsxs)("div", {
1387
+ className: WorkspaceBrowser_module_css_default.sectionHeader,
1388
+ children: [
1389
+ wide && (0, react_jsx_runtime.jsx)("span", {
1390
+ className: clsx(WorkspaceBrowser_module_css_default.sectionLabel, WorkspaceBrowser_module_css_default.wide),
1391
+ children: groupBy === "flat" ? t("section.sessions") : t("section.workspaces")
1392
+ }),
1393
+ wide && (0, react_jsx_runtime.jsx)(GroupByMenu, {
1394
+ groupBy,
1395
+ onPick: (mode) => {
1396
+ actions.setGroupBy(mode);
1397
+ },
1398
+ t
1399
+ }),
1400
+ directoryFlowAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1401
+ label: t("workspace.add"),
1402
+ side: "bottom",
1403
+ delayMs: 500,
1404
+ children: (0, react_jsx_runtime.jsx)("button", {
1405
+ ref: wsPlusRef,
1406
+ type: "button",
1407
+ className: WorkspaceBrowser_module_css_default.iconButton,
1408
+ "aria-label": t("workspace.add"),
1409
+ onClick: () => {
1410
+ setWsPickerOpen((v) => !v);
1411
+ },
1412
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconProjectAddOutline16, { size: wide ? 16 : 18 })
1413
+ })
1414
+ }),
1415
+ (0, react_jsx_runtime.jsx)(WorkspacePickFlow, {
1416
+ t,
1417
+ open: wsPickerOpen,
1418
+ anchorRef: wsPlusRef,
1419
+ useWorkspaces,
1420
+ createWorkspace,
1421
+ useDirectoryFlow,
1422
+ renderDirectoryFlow: (owner) => renderSlot("sidebar.workspaces.directoryFlow", owner),
1423
+ addOnly: true,
1424
+ side: "right",
1425
+ onPick: (workspaceId) => {
1426
+ setWsPickerOpen(false);
1427
+ startSession(workspaceId);
1428
+ },
1429
+ onClose: () => {
1430
+ setWsPickerOpen(false);
1431
+ }
1432
+ })
1433
+ ]
1434
+ }),
1435
+ (0, react_jsx_runtime.jsxs)("div", {
1436
+ className: WorkspaceBrowser_module_css_default.search,
1437
+ onClick: () => {
1438
+ if (wide) searchInput.current?.focus();
1439
+ },
1440
+ children: [
1441
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1442
+ label: t("search"),
1443
+ disabled: wide,
1444
+ children: (0, react_jsx_runtime.jsx)("button", {
1445
+ type: "button",
1446
+ className: WorkspaceBrowser_module_css_default.searchButton,
1447
+ "aria-label": t("search.sessions.aria"),
1448
+ tabIndex: wide ? -1 : 0,
1449
+ onClick: () => {
1450
+ if (!wide) {
1451
+ setSearchOnExpand(true);
1452
+ expandSidebar();
1453
+ }
1454
+ },
1455
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: wide ? 14 : 18 })
1456
+ })
1457
+ }),
1458
+ wide && (0, react_jsx_runtime.jsx)("input", {
1459
+ ref: searchInput,
1460
+ className: clsx(WorkspaceBrowser_module_css_default.searchInput, WorkspaceBrowser_module_css_default.wide),
1461
+ type: "text",
1462
+ placeholder: t("search.placeholder"),
1463
+ maxLength: SEARCH_QUERY_MAX_CODE_UNITS,
1464
+ value: query,
1465
+ onChange: (e) => {
1466
+ setQuery(sanitizeSearchQuery(e.target.value));
1467
+ }
1468
+ }),
1469
+ wide && query !== "" && (0, react_jsx_runtime.jsx)("button", {
1470
+ type: "button",
1471
+ className: clsx(WorkspaceBrowser_module_css_default.clearButton, WorkspaceBrowser_module_css_default.wide),
1472
+ "aria-label": t("search.clear"),
1473
+ onClick: () => {
1474
+ setQuery("");
1475
+ },
1476
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFill14, {})
1477
+ })
1478
+ ]
1479
+ }),
1480
+ (0, react_jsx_runtime.jsx)("div", {
1481
+ className: WorkspaceBrowser_module_css_default.listArea,
1482
+ children: wide && (normalizedQuery !== "" ? (0, react_jsx_runtime.jsx)(SearchResults, {
1483
+ useSessions,
1484
+ open,
1485
+ workspaces,
1486
+ archivedSessionIds,
1487
+ query: normalizedQuery,
1488
+ remote: remoteSearch,
1489
+ resultLimit: searchResultLimit,
1490
+ t
1491
+ }) : groupBy === "flat" ? (0, react_jsx_runtime.jsx)(FlatList, {
1492
+ useSessions,
1493
+ open,
1494
+ forkSession,
1495
+ onSessionRename,
1496
+ onSessionArchive,
1497
+ archivedSessionIds,
1498
+ t
1499
+ }) : (0, react_jsx_runtime.jsx)(SessionTree, {
1500
+ useSessions,
1501
+ onSessionRename,
1502
+ onSessionArchive,
1503
+ forkSession,
1504
+ workspaces,
1505
+ archivedSessionIds,
1506
+ startSession,
1507
+ open,
1508
+ insertSessionBefore,
1509
+ t,
1510
+ onRenameRequest: (workspaceId, currentTitle) => {
1511
+ setRenameTarget({
1512
+ workspaceId,
1513
+ currentTitle
1514
+ });
1515
+ setRenameDraft(currentTitle);
1516
+ setRenameError(null);
1517
+ },
1518
+ onDeleteRequest: (workspaceId, title) => {
1519
+ setDeleteTarget({
1520
+ workspaceId,
1521
+ title
1522
+ });
1523
+ setDeleteError(null);
1524
+ }
1525
+ }))
1526
+ }),
1527
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1528
+ open: renameTarget !== null,
1529
+ onClose: closeRename,
1530
+ closeLabel: t("close"),
1531
+ title: t("rename.workspace.title"),
1532
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1533
+ variant: "outline",
1534
+ disabled: renaming,
1535
+ onClick: closeRename,
1536
+ children: t("cancel")
1537
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1538
+ variant: "primary",
1539
+ disabled: renameBlocked,
1540
+ onClick: confirmRename,
1541
+ children: t("rename")
1542
+ })] }),
1543
+ children: [
1544
+ (0, react_jsx_runtime.jsx)("input", {
1545
+ className: WorkspaceBrowser_module_css_default.renameInput,
1546
+ value: renameDraft,
1547
+ "aria-label": t("field.workspaceName"),
1548
+ autoFocus: true,
1549
+ disabled: renaming,
1550
+ onFocus: (e) => {
1551
+ e.target.select();
1552
+ },
1553
+ onChange: (e) => {
1554
+ setRenameDraft(e.target.value);
1555
+ setRenameError(null);
1556
+ },
1557
+ onCompositionStart: () => {
1558
+ composingRef.current = true;
1559
+ },
1560
+ onCompositionEnd: () => {
1561
+ composingRef.current = false;
1562
+ },
1563
+ onKeyDown: (e) => {
1564
+ if (e.key === "Enter" && !composingRef.current) {
1565
+ e.preventDefault();
1566
+ confirmRename();
1567
+ }
1568
+ }
1569
+ }),
1570
+ renameDuplicate && (0, react_jsx_runtime.jsx)("div", {
1571
+ className: WorkspaceBrowser_module_css_default.renameError,
1572
+ role: "alert",
1573
+ children: t("conflict.named", { name: renameTrimmed })
1574
+ }),
1575
+ renameError !== null && (0, react_jsx_runtime.jsx)("div", {
1576
+ className: WorkspaceBrowser_module_css_default.renameError,
1577
+ role: "alert",
1578
+ children: renameError
1579
+ })
1580
+ ]
1581
+ }),
1582
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1583
+ open: sessionRenameTarget !== null,
1584
+ onClose: closeSessionRename,
1585
+ closeLabel: t("close"),
1586
+ title: t("rename.session.title"),
1587
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1588
+ variant: "outline",
1589
+ disabled: sessionRenaming,
1590
+ onClick: closeSessionRename,
1591
+ children: t("cancel")
1592
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1593
+ variant: "primary",
1594
+ disabled: sessionRenameBlocked,
1595
+ onClick: confirmSessionRename,
1596
+ children: t("rename")
1597
+ })] }),
1598
+ children: [(0, react_jsx_runtime.jsx)("input", {
1599
+ className: WorkspaceBrowser_module_css_default.renameInput,
1600
+ value: sessionRenameDraft,
1601
+ "aria-label": t("field.sessionName"),
1602
+ autoFocus: true,
1603
+ disabled: sessionRenaming,
1604
+ onFocus: (e) => {
1605
+ e.target.select();
1606
+ },
1607
+ onChange: (e) => {
1608
+ setSessionRenameDraft(e.target.value);
1609
+ setSessionRenameError(null);
1610
+ },
1611
+ onCompositionStart: () => {
1612
+ composingRef.current = true;
1613
+ },
1614
+ onCompositionEnd: () => {
1615
+ composingRef.current = false;
1616
+ },
1617
+ onKeyDown: (e) => {
1618
+ if (e.key === "Enter" && !composingRef.current) {
1619
+ e.preventDefault();
1620
+ confirmSessionRename();
1621
+ }
1622
+ }
1623
+ }), sessionRenameError !== null && (0, react_jsx_runtime.jsx)("div", {
1624
+ className: WorkspaceBrowser_module_css_default.renameError,
1625
+ role: "alert",
1626
+ children: sessionRenameError
1627
+ })]
1628
+ }),
1629
+ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1630
+ open: deleteTarget !== null,
1631
+ onClose: closeDelete,
1632
+ closeLabel: t("close"),
1633
+ title: t("delete.workspace"),
1634
+ ...deleteTarget === null ? {} : { description: t("delete.desc", { name: deleteTarget.title }) },
1635
+ footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1636
+ variant: "outline",
1637
+ disabled: deleting,
1638
+ onClick: closeDelete,
1639
+ children: t("cancel")
1640
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1641
+ variant: "outline",
1642
+ className: WorkspaceBrowser_module_css_default.deleteAction,
1643
+ disabled: deleting,
1644
+ onClick: confirmDelete,
1645
+ children: t("delete.workspace")
1646
+ })] }),
1647
+ children: [deleting && (0, react_jsx_runtime.jsx)("div", {
1648
+ className: WorkspaceBrowser_module_css_default.deleteStatus,
1649
+ role: "status",
1650
+ children: t("delete.pending")
1651
+ }), deleteError !== null && (0, react_jsx_runtime.jsx)("div", {
1652
+ className: WorkspaceBrowser_module_css_default.renameError,
1653
+ role: "alert",
1654
+ children: deleteError
1655
+ })]
1656
+ })
1657
+ ]
1658
+ });
1659
+ }
1660
+ //#endregion
1661
+ //#region lib/types/client/locales.js
1662
+ /**
1663
+ * `workspace` namespace dictionaries: the browsing region (section header,
1664
+ * search, tree rows, dialogs) and the pick/add flow. Runtime failure
1665
+ * messages (wire error strings) pass through untranslated by policy.
1666
+ */
1667
+ /** Simplified Chinese dictionary (the key-set source of truth). */
1668
+ const zh = {
1669
+ "group.ungrouped": "未分组",
1670
+ "session.new": "新会话",
1671
+ "section.workspaces": "工作区",
1672
+ "section.sessions": "会话",
1673
+ "groupBy.label": "分组方式",
1674
+ "groupBy.workspace": "按工作区",
1675
+ "groupBy.flat": "单列表",
1676
+ "empty.none": "暂无会话",
1677
+ "empty.noMatches": "无匹配结果",
1678
+ "workspace.add": "添加工作区",
1679
+ "search.sessions.aria": "搜索会话",
1680
+ "search.placeholder": "搜索名称、关键词…",
1681
+ "search.clear": "清除搜索",
1682
+ "search.results.aria": "搜索结果",
1683
+ "search.pending": "正在搜索会话历史…",
1684
+ "search.unavailable": "内容搜索暂不可用,仅显示名称匹配。",
1685
+ "search.noMatches": "无匹配会话",
1686
+ "search.hasMore": "仅显示前 {n} 条结果,请缩小搜索范围。",
1687
+ "menu.addWorkspace": "添加工作区…",
1688
+ "picker.loading": "正在加载工作区…",
1689
+ "conflict.named": "已存在名为“{name}”的工作区。",
1690
+ "folderError.title": "无法打开文件夹",
1691
+ "folderError.retry": "重新选择",
1692
+ "rename": "重命名",
1693
+ "rename.workspace.title": "重命名工作区",
1694
+ "rename.session.title": "重命名会话",
1695
+ "field.workspaceName": "工作区名称",
1696
+ "field.sessionName": "会话名称",
1697
+ "delete.workspace": "删除工作区",
1698
+ "delete.desc": "将把“{name}”从工作区列表中移除。文件夹与会话记录会保留,其会话将显示在“未分组”下。",
1699
+ "delete.pending": "正在删除工作区…",
1700
+ "menu.fork": "分叉会话",
1701
+ "menu.archiveSession": "归档会话",
1702
+ "sessions.count.one": "{n} 个会话",
1703
+ "sessions.count.other": "{n} 个会话",
1704
+ "actions.workspace.aria": "工作区“{name}”的操作",
1705
+ "actions.session.aria": "会话“{name}”的操作",
1706
+ "actions.newSession.aria": "在“{name}”中新建会话",
1707
+ "status.running": "进行中",
1708
+ "status.subagentsRunning.one": "{n} 个子代理运行中",
1709
+ "status.subagentsRunning.other": "{n} 个子代理运行中",
1710
+ "status.idle": "空闲",
1711
+ "status.waitingApproval": "等待审批",
1712
+ "status.planReview": "计划待审",
1713
+ "status.waitingAnswer": "等待回答",
1714
+ "status.completed": "已完成",
1715
+ "hover.created": "创建于 {time}",
1716
+ "hover.copied": "已复制",
1717
+ "date.ymd": "{y}年{m}月{d}日",
1718
+ "time.now": "刚刚",
1719
+ "time.minutes": "{n}分钟",
1720
+ "time.hours": "{n}小时",
1721
+ "time.days": "{n}天",
1722
+ "time.months": "{n}个月",
1723
+ "time.years": "{n}年",
1724
+ "time.ago": "{t}前"
1725
+ };
1726
+ /** English dictionary, checked complete against the zh key set. */
1727
+ const en = {
1728
+ "group.ungrouped": "Ungrouped",
1729
+ "session.new": "New Session",
1730
+ "section.workspaces": "Workspaces",
1731
+ "section.sessions": "Sessions",
1732
+ "groupBy.label": "Group by",
1733
+ "groupBy.workspace": "WorkSpace",
1734
+ "groupBy.flat": "In one list",
1735
+ "empty.none": "No sessions yet",
1736
+ "empty.noMatches": "No matches",
1737
+ "workspace.add": "Add workspace",
1738
+ "search.sessions.aria": "Search sessions",
1739
+ "search.placeholder": "Search name, keywords...",
1740
+ "search.clear": "Clear search",
1741
+ "search.results.aria": "Search results",
1742
+ "search.pending": "Searching session history…",
1743
+ "search.unavailable": "Content search is temporarily unavailable. Showing name matches.",
1744
+ "search.noMatches": "No matching sessions",
1745
+ "search.hasMore": "Showing the first {n} results. Narrow your search.",
1746
+ "menu.addWorkspace": "Add workspace…",
1747
+ "picker.loading": "Loading workspaces…",
1748
+ "conflict.named": "A workspace named “{name}” already exists.",
1749
+ "folderError.title": "Couldn’t open folder",
1750
+ "folderError.retry": "Choose again",
1751
+ "rename": "Rename",
1752
+ "rename.workspace.title": "Rename workspace",
1753
+ "rename.session.title": "Rename session",
1754
+ "field.workspaceName": "Workspace name",
1755
+ "field.sessionName": "Session name",
1756
+ "delete.workspace": "Delete workspace",
1757
+ "delete.desc": "This removes “{name}” from the workspace list. The folder and session logs will be kept. Its sessions will appear under Ungrouped.",
1758
+ "delete.pending": "Deleting workspace…",
1759
+ "menu.fork": "Fork session",
1760
+ "menu.archiveSession": "Archive session",
1761
+ "sessions.count.one": "{n} session",
1762
+ "sessions.count.other": "{n} sessions",
1763
+ "actions.workspace.aria": "Workspace actions for {name}",
1764
+ "actions.session.aria": "Session actions for {name}",
1765
+ "actions.newSession.aria": "New session in {name}",
1766
+ "status.running": "Running",
1767
+ "status.subagentsRunning.one": "{n} subagent running",
1768
+ "status.subagentsRunning.other": "{n} subagents running",
1769
+ "status.idle": "Idle",
1770
+ "status.waitingApproval": "Waiting for approval",
1771
+ "status.planReview": "Plan awaiting review",
1772
+ "status.waitingAnswer": "Waiting for answer",
1773
+ "status.completed": "Completed",
1774
+ "hover.created": "Created {time}",
1775
+ "hover.copied": "Copied",
1776
+ "date.ymd": "{y}-{m}-{d}",
1777
+ "time.now": "now",
1778
+ "time.minutes": "{n}min",
1779
+ "time.hours": "{n}h",
1780
+ "time.days": "{n}d",
1781
+ "time.months": "{n}mo",
1782
+ "time.years": "{n}y",
1783
+ "time.ago": "{t} ago"
1784
+ };
1785
+ //#endregion
1786
+ //#region lib/types/client/index.js
1787
+ /** Dictionary namespace owned by this plugin. */
1788
+ const NS = "workspace";
1789
+ /**
1790
+ * Required services (cordis fiber inject). The target slots are declared by
1791
+ * the ui-sidebar / ui-conversation applies, whose activation order relative
1792
+ * to this one is NOT constrained: dsh.client.inject edges are informational
1793
+ * (loading/prefetch metadata, never apply sequencing) and neither owner
1794
+ * provides a waitable service. apply therefore depends on each slot
1795
+ * declaration through `slots.inject()` instead of assuming order.
1796
+ */
1797
+ const inject = [
1798
+ "slots",
1799
+ "sessions",
1800
+ "workspaces",
1801
+ "locale"
1802
+ ];
1803
+ /**
1804
+ * Register the browser and picker once their slot declarations are on the
1805
+ * ledger. Inject factories return plain callbacks; data reads use the
1806
+ * framework's global hooks.
1807
+ * @param ctx - client root context.
1808
+ */
1809
+ function apply(ctx) {
1810
+ ctx.effect(() => ctx.locale.register(NS, {
1811
+ zh,
1812
+ en
1813
+ }), "ui-workspace: dictionaries");
1814
+ const searchSessions = async (query, signal) => {
1815
+ const result = await ctx.sessions.search(query, signal);
1816
+ if (!result.ok) throw new Error(result.error.message);
1817
+ return result.value;
1818
+ };
1819
+ const flowSource = (hole) => ({
1820
+ getSnapshot: () => ctx.slots.entries(hole).length > 0,
1821
+ subscribe: (listener) => ctx.slots.subscribe(hole, listener)
1822
+ });
1823
+ const browserFlowSource = flowSource("sidebar.workspaces.directoryFlow");
1824
+ const pickerFlowSource = flowSource("conversation.hero.workspace.directoryFlow");
1825
+ const browserInjected = () => ({
1826
+ startSession: (workspaceId) => {
1827
+ ctx.workspaces.startSession(workspaceId);
1828
+ },
1829
+ open: (sessionId) => {
1830
+ ctx.sessions.open(sessionId);
1831
+ },
1832
+ searchSessions,
1833
+ searchResultLimit: ctx.sessions.searchResultLimit,
1834
+ renameSession: async (sessionId, title) => {
1835
+ const session = ctx.sessions.binding(sessionId)?.session;
1836
+ if (session === void 0) throw new Error(`unknown session "${sessionId}"`);
1837
+ const result = await session.rename(title);
1838
+ if (!result.ok) throw new Error(result.error.message);
1839
+ },
1840
+ forkSession: (sessionId) => {
1841
+ ctx.sessions.fork({
1842
+ sessionId,
1843
+ increaseTitle: true
1844
+ }).then((childId) => {
1845
+ ctx.sessions.open(childId);
1846
+ }).catch(() => {});
1847
+ },
1848
+ renameWorkspace: async (workspaceId, title) => {
1849
+ await ctx.workspaces.rename(workspaceId, title);
1850
+ },
1851
+ deleteWorkspace: async (workspaceId) => {
1852
+ await ctx.workspaces.delete(workspaceId);
1853
+ },
1854
+ archiveSession: async (sessionId) => {
1855
+ await ctx.workspaces.archiveSession(sessionId);
1856
+ },
1857
+ insertSessionBefore: async (workspaceId, sessionId, beforeSessionId) => {
1858
+ await ctx.workspaces.insertSessionBefore(workspaceId, sessionId, beforeSessionId);
1859
+ },
1860
+ createWorkspace: (input) => ctx.workspaces.create(input),
1861
+ hooks: { directoryFlow: browserFlowSource }
1862
+ });
1863
+ const pickerInjected = () => ({
1864
+ createWorkspace: (input) => ctx.workspaces.create(input),
1865
+ hooks: { directoryFlow: pickerFlowSource }
1866
+ });
1867
+ ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
1868
+ name: "sidebar.workspaces",
1869
+ children: { "sidebar.workspaces.directoryFlow": {
1870
+ kind: "single",
1871
+ scope: "root"
1872
+ } },
1873
+ store: createWorkspaceViewStore(),
1874
+ inject: browserInjected,
1875
+ locale: NS
1876
+ }, WorkspaceBrowser));
1877
+ ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
1878
+ name: "conversation.hero.workspace",
1879
+ children: { "conversation.hero.workspace.directoryFlow": {
1880
+ kind: "single",
1881
+ scope: "root"
1882
+ } },
1883
+ inject: pickerInjected,
1884
+ locale: NS
1885
+ }, WorkspacePicker));
1886
+ }
1887
+ //#endregion
1888
+ exports.apply = apply;
1889
+ exports.inject = inject;
1890
+ return module.exports;
1891
+ }
1892
+ });
1893
+
1894
+ //# sourceMappingURL=client.js.map