@deepseek-ai/dsh-client-ui-workspace 0.1.6-alpha.2 → 0.1.7-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +102 -11
- package/README.zh.md +102 -11
- package/lib/client.js +2182 -1015
- package/lib/types/client/contract/slots.d.ts +253 -16
- package/lib/types/client/index.d.ts +6 -2
- package/lib/types/client/locales.d.ts +84 -2
- package/lib/types/client/navigation.d.ts +39 -6
- package/lib/types/client/pin-order.d.ts +31 -0
- package/lib/types/client/rows/AnimatedRows.d.ts +44 -0
- package/lib/types/client/rows/Rows.d.ts +15 -15
- package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
- package/lib/types/client/session-actions/ArchiveSession.d.ts +23 -0
- package/lib/types/client/session-actions/ForkSession.d.ts +9 -0
- package/lib/types/client/session-actions/PinSession.d.ts +14 -0
- package/lib/types/client/session-actions/RenameSession.d.ts +18 -0
- package/lib/types/client/session-actions/RowActionToast.d.ts +11 -0
- package/lib/types/client/session-actions/derived.d.ts +12 -0
- package/lib/types/client/stores.d.ts +14 -0
- package/lib/types/client/tree.d.ts +53 -20
- package/package.json +26 -23
- package/lib/types/client/subagent-lineage.d.ts +0 -21
package/lib/client.js
CHANGED
|
@@ -4,11 +4,781 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
8
7
|
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
8
|
+
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
let react = require("react");
|
|
11
11
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
12
|
+
//#region lib/types/client/contract/slots.js
|
|
13
|
+
/**
|
|
14
|
+
* Bind the row's render occurrence into the entries' `useMenuOpenState` hook:
|
|
15
|
+
* the owner supplies its open-state pair as the occurrence's `hookContext`,
|
|
16
|
+
* and the hook hands that pair back.
|
|
17
|
+
* @param _standard - framework standard props (unused).
|
|
18
|
+
* @param state - the menu's open-state pair from the render occurrence.
|
|
19
|
+
* @returns the hook the entry calls.
|
|
20
|
+
*/
|
|
21
|
+
const menuOpenStateFactory = (_standard, state) => () => state;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region lib/types/client/locales.js
|
|
24
|
+
/**
|
|
25
|
+
* `workspace` namespace dictionaries: the browsing region (section header,
|
|
26
|
+
* search, tree rows, dialogs) and the pick/add flow. Runtime failure
|
|
27
|
+
* messages (wire error strings) pass through untranslated by policy.
|
|
28
|
+
*/
|
|
29
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
30
|
+
const zh = {
|
|
31
|
+
"defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
|
|
32
|
+
"defaultWorkspace.title": "默认工作区",
|
|
33
|
+
"group.ungrouped": "未分组",
|
|
34
|
+
"session.new": "新会话",
|
|
35
|
+
"section.workspaces": "工作区",
|
|
36
|
+
"section.sessions": "会话",
|
|
37
|
+
"viewOptions.label": "视图选项",
|
|
38
|
+
"groupBy.label": "分组方式",
|
|
39
|
+
"groupBy.workspace": "按工作区",
|
|
40
|
+
"groupBy.workspaceTree": "按工作区树",
|
|
41
|
+
"groupBy.flat": "单列表",
|
|
42
|
+
"orderBy.label": "排序方式",
|
|
43
|
+
"orderBy.manual": "手动排序",
|
|
44
|
+
"orderBy.updated": "最近更新",
|
|
45
|
+
"filterBy.label": "筛选会话",
|
|
46
|
+
"viewOptions.showArchived": "显示已归档",
|
|
47
|
+
"viewOptions.onlyArchived": "仅显示已归档",
|
|
48
|
+
"sessions.expand": "展开其余 {n} 个会话",
|
|
49
|
+
"sessions.collapse": "收起",
|
|
50
|
+
"empty.none": "暂无会话",
|
|
51
|
+
"empty.noMatches": "无匹配结果",
|
|
52
|
+
"workspace.add": "添加工作区",
|
|
53
|
+
"search.sessions.aria": "搜索会话",
|
|
54
|
+
"search.placeholder": "搜索会话名称",
|
|
55
|
+
"search.clear": "清除搜索",
|
|
56
|
+
"search.results.aria": "搜索结果",
|
|
57
|
+
"search.pending": "正在搜索会话历史…",
|
|
58
|
+
"search.noMatches": "无匹配会话",
|
|
59
|
+
"search.hasMore": "仅显示前 {n} 条结果,请缩小搜索范围。",
|
|
60
|
+
"menu.addWorkspace": "添加工作区…",
|
|
61
|
+
"picker.loading": "正在加载工作区…",
|
|
62
|
+
"conflict.named": "已存在名为“{name}”的工作区。",
|
|
63
|
+
"folderError.title": "无法打开文件夹",
|
|
64
|
+
"folderError.retry": "重新选择",
|
|
65
|
+
"rename": "重命名",
|
|
66
|
+
"rename.workspace.title": "重命名工作区",
|
|
67
|
+
"rename.session.title": "重命名会话",
|
|
68
|
+
"field.workspaceName": "工作区名称",
|
|
69
|
+
"field.sessionName": "会话名称",
|
|
70
|
+
"delete.workspace": "删除工作区",
|
|
71
|
+
"delete.desc": "将把“{name}”从工作区列表中移除。文件夹与会话记录会保留,其会话将显示在“未分组”下。",
|
|
72
|
+
"delete.pending": "正在删除工作区…",
|
|
73
|
+
"menu.fork": "分叉会话",
|
|
74
|
+
"menu.archiveSession": "归档会话",
|
|
75
|
+
"menu.unarchiveSession": "取消归档",
|
|
76
|
+
"menu.pinSession": "置顶会话",
|
|
77
|
+
"menu.unpinSession": "取消置顶",
|
|
78
|
+
"row.archived": "已归档",
|
|
79
|
+
"row.pinned": "已置顶",
|
|
80
|
+
"toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
|
|
81
|
+
"toast.archived": "会话已归档,可",
|
|
82
|
+
"toast.stoppedAndArchived": "已停止并归档,可",
|
|
83
|
+
"archive.confirm.title": "停止并归档此会话?",
|
|
84
|
+
"archive.confirm.desc": "“{title}”仍有正在进行的工作。归档会先停止这些工作;之后可在侧栏“显示已归档”中恢复会话,被停止的工作不会自动继续。",
|
|
85
|
+
"archive.confirm.activity": "将被停止的工作",
|
|
86
|
+
"archive.confirm.turn": "进行中的回合",
|
|
87
|
+
"archive.confirm.subagents.one": "{n} 个运行中的子代理:{names}",
|
|
88
|
+
"archive.confirm.subagents.other": "{n} 个运行中的子代理:{names}",
|
|
89
|
+
"archive.confirm.jobs.one": "{n} 个后台任务:{names}",
|
|
90
|
+
"archive.confirm.jobs.other": "{n} 个后台任务:{names}",
|
|
91
|
+
"archive.confirm.schedules.one": "{n} 条定时提醒:{names}",
|
|
92
|
+
"archive.confirm.schedules.other": "{n} 条定时提醒:{names}",
|
|
93
|
+
"archive.confirm.other.one": "{n} 项其他工作({kind})",
|
|
94
|
+
"archive.confirm.other.other": "{n} 项其他工作({kind})",
|
|
95
|
+
"archive.confirm.listSeparator": "、",
|
|
96
|
+
"archive.confirm.action": "停止并归档",
|
|
97
|
+
"archive.confirm.pending": "正在停止并归档…",
|
|
98
|
+
"toast.archivedUndo": "撤销",
|
|
99
|
+
"toast.archivedOr": "或",
|
|
100
|
+
"toast.archivedFilter": "筛选已归档会话",
|
|
101
|
+
"toast.pinFailed": "置顶失败,请稍后重试",
|
|
102
|
+
"toast.unpinFailed": "取消置顶失败,请稍后重试",
|
|
103
|
+
"toast.createFailed": "新建会话失败:{message}",
|
|
104
|
+
"sessions.count.one": "{n} 个会话",
|
|
105
|
+
"sessions.count.other": "{n} 个会话",
|
|
106
|
+
"actions.workspace.aria": "工作区“{name}”的操作",
|
|
107
|
+
"actions.session.aria": "会话“{name}”的操作",
|
|
108
|
+
"actions.archive": "归档会话",
|
|
109
|
+
"actions.unarchive": "取消归档",
|
|
110
|
+
"actions.pin": "置顶会话",
|
|
111
|
+
"actions.unpin": "取消置顶",
|
|
112
|
+
"actions.newSession": "新会话",
|
|
113
|
+
"actions.newSession.aria": "在“{name}”中新建会话",
|
|
114
|
+
"status.running": "进行中",
|
|
115
|
+
"status.subagentsRunning.one": "{n} 个子代理运行中",
|
|
116
|
+
"status.subagentsRunning.other": "{n} 个子代理运行中",
|
|
117
|
+
"status.idle": "空闲",
|
|
118
|
+
"status.waitingApproval": "等待审批",
|
|
119
|
+
"status.planReview": "计划待审",
|
|
120
|
+
"status.waitingAnswer": "等待回答",
|
|
121
|
+
"status.compact.approval": "待审批",
|
|
122
|
+
"status.compact.planReview": "计划待审",
|
|
123
|
+
"status.compact.answer": "待回答",
|
|
124
|
+
"status.completed": "已完成",
|
|
125
|
+
"schedule.active": "有活动定时任务",
|
|
126
|
+
"hover.created": "创建于 {time}",
|
|
127
|
+
"hover.copied": "已复制",
|
|
128
|
+
"date.ymd": "{y}年{m}月{d}日",
|
|
129
|
+
"time.now": "刚刚",
|
|
130
|
+
"time.minutes": "{n}分钟",
|
|
131
|
+
"time.hours": "{n}小时",
|
|
132
|
+
"time.days": "{n}天",
|
|
133
|
+
"time.months": "{n}个月",
|
|
134
|
+
"time.years": "{n}年",
|
|
135
|
+
"time.ago": "{t}前"
|
|
136
|
+
};
|
|
137
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
138
|
+
const en = {
|
|
139
|
+
"defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
|
|
140
|
+
"defaultWorkspace.title": "Default workspace",
|
|
141
|
+
"group.ungrouped": "Ungrouped",
|
|
142
|
+
"session.new": "New Session",
|
|
143
|
+
"section.workspaces": "Workspaces",
|
|
144
|
+
"section.sessions": "Sessions",
|
|
145
|
+
"viewOptions.label": "View options",
|
|
146
|
+
"groupBy.label": "Group by",
|
|
147
|
+
"groupBy.workspace": "WorkSpace",
|
|
148
|
+
"groupBy.workspaceTree": "Workspace Tree",
|
|
149
|
+
"groupBy.flat": "In one list",
|
|
150
|
+
"orderBy.label": "Order by",
|
|
151
|
+
"orderBy.manual": "Manual",
|
|
152
|
+
"orderBy.updated": "Last updated",
|
|
153
|
+
"filterBy.label": "Filter sessions",
|
|
154
|
+
"viewOptions.showArchived": "Show archived",
|
|
155
|
+
"viewOptions.onlyArchived": "Archived only",
|
|
156
|
+
"sessions.expand": "Show {n} more sessions",
|
|
157
|
+
"sessions.collapse": "Show less",
|
|
158
|
+
"empty.none": "No sessions yet",
|
|
159
|
+
"empty.noMatches": "No matches",
|
|
160
|
+
"workspace.add": "Add workspace",
|
|
161
|
+
"search.sessions.aria": "Search sessions",
|
|
162
|
+
"search.placeholder": "Search session names",
|
|
163
|
+
"search.clear": "Clear search",
|
|
164
|
+
"search.results.aria": "Search results",
|
|
165
|
+
"search.pending": "Searching session history…",
|
|
166
|
+
"search.noMatches": "No matching sessions",
|
|
167
|
+
"search.hasMore": "Showing the first {n} results. Narrow your search.",
|
|
168
|
+
"menu.addWorkspace": "Add workspace…",
|
|
169
|
+
"picker.loading": "Loading workspaces…",
|
|
170
|
+
"conflict.named": "A workspace named “{name}” already exists.",
|
|
171
|
+
"folderError.title": "Couldn’t open folder",
|
|
172
|
+
"folderError.retry": "Choose again",
|
|
173
|
+
"rename": "Rename",
|
|
174
|
+
"rename.workspace.title": "Rename workspace",
|
|
175
|
+
"rename.session.title": "Rename session",
|
|
176
|
+
"field.workspaceName": "Workspace name",
|
|
177
|
+
"field.sessionName": "Session name",
|
|
178
|
+
"delete.workspace": "Delete workspace",
|
|
179
|
+
"delete.desc": "This removes “{name}” from the workspace list. The folder and session logs will be kept. Its sessions will appear under Ungrouped.",
|
|
180
|
+
"delete.pending": "Deleting workspace…",
|
|
181
|
+
"menu.fork": "Fork session",
|
|
182
|
+
"menu.archiveSession": "Archive session",
|
|
183
|
+
"menu.unarchiveSession": "Unarchive session",
|
|
184
|
+
"menu.pinSession": "Pin session",
|
|
185
|
+
"menu.unpinSession": "Unpin session",
|
|
186
|
+
"row.archived": "Archived",
|
|
187
|
+
"row.pinned": "Pinned",
|
|
188
|
+
"toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
|
|
189
|
+
"toast.archived": "Session archived. You can ",
|
|
190
|
+
"toast.stoppedAndArchived": "Session stopped and archived. You can ",
|
|
191
|
+
"archive.confirm.title": "Stop and archive this session?",
|
|
192
|
+
"archive.confirm.desc": "“{title}” still has work in progress. Archiving stops it first; you can restore the session later from “Show archived” in the sidebar, and the stopped work will not resume on its own.",
|
|
193
|
+
"archive.confirm.activity": "Work that will be stopped",
|
|
194
|
+
"archive.confirm.turn": "The turn in progress",
|
|
195
|
+
"archive.confirm.subagents.one": "{n} running subagent: {names}",
|
|
196
|
+
"archive.confirm.subagents.other": "{n} running subagents: {names}",
|
|
197
|
+
"archive.confirm.jobs.one": "{n} background job: {names}",
|
|
198
|
+
"archive.confirm.jobs.other": "{n} background jobs: {names}",
|
|
199
|
+
"archive.confirm.schedules.one": "{n} scheduled reminder: {names}",
|
|
200
|
+
"archive.confirm.schedules.other": "{n} scheduled reminders: {names}",
|
|
201
|
+
"archive.confirm.other.one": "{n} other item of work ({kind})",
|
|
202
|
+
"archive.confirm.other.other": "{n} other items of work ({kind})",
|
|
203
|
+
"archive.confirm.listSeparator": ", ",
|
|
204
|
+
"archive.confirm.action": "Stop and archive",
|
|
205
|
+
"archive.confirm.pending": "Stopping and archiving…",
|
|
206
|
+
"toast.archivedUndo": "undo",
|
|
207
|
+
"toast.archivedOr": " or ",
|
|
208
|
+
"toast.archivedFilter": "filter archived sessions",
|
|
209
|
+
"toast.pinFailed": "Pin failed. Try again later.",
|
|
210
|
+
"toast.unpinFailed": "Unpin failed. Try again later.",
|
|
211
|
+
"toast.createFailed": "New session failed: {message}",
|
|
212
|
+
"sessions.count.one": "{n} session",
|
|
213
|
+
"sessions.count.other": "{n} sessions",
|
|
214
|
+
"actions.workspace.aria": "Workspace actions for {name}",
|
|
215
|
+
"actions.session.aria": "Session actions for {name}",
|
|
216
|
+
"actions.archive": "Archive",
|
|
217
|
+
"actions.unarchive": "Unarchive",
|
|
218
|
+
"actions.pin": "Pin",
|
|
219
|
+
"actions.unpin": "Unpin",
|
|
220
|
+
"actions.newSession": "New session",
|
|
221
|
+
"actions.newSession.aria": "New session in {name}",
|
|
222
|
+
"status.running": "Running",
|
|
223
|
+
"status.subagentsRunning.one": "{n} subagent running",
|
|
224
|
+
"status.subagentsRunning.other": "{n} subagents running",
|
|
225
|
+
"status.idle": "Idle",
|
|
226
|
+
"status.waitingApproval": "Waiting for approval",
|
|
227
|
+
"status.planReview": "Plan awaiting review",
|
|
228
|
+
"status.waitingAnswer": "Waiting for answer",
|
|
229
|
+
"status.compact.approval": "Approval",
|
|
230
|
+
"status.compact.planReview": "Plan review",
|
|
231
|
+
"status.compact.answer": "Answer",
|
|
232
|
+
"status.completed": "Completed",
|
|
233
|
+
"schedule.active": "Has active scheduled task",
|
|
234
|
+
"hover.created": "Created {time}",
|
|
235
|
+
"hover.copied": "Copied",
|
|
236
|
+
"date.ymd": "{y}-{m}-{d}",
|
|
237
|
+
"time.now": "now",
|
|
238
|
+
"time.minutes": "{n}min",
|
|
239
|
+
"time.hours": "{n}h",
|
|
240
|
+
"time.days": "{n}d",
|
|
241
|
+
"time.months": "{n}mo",
|
|
242
|
+
"time.years": "{n}y",
|
|
243
|
+
"time.ago": "{t} ago"
|
|
244
|
+
};
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region ../../util/values/src/index.ts
|
|
247
|
+
/**
|
|
248
|
+
* Mark an unreachable closed-union branch.
|
|
249
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
250
|
+
* @param context - optional switch-site label included in the failure message.
|
|
251
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
252
|
+
*/
|
|
253
|
+
function assertNever$1(value, context) {
|
|
254
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
255
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region ../../util/workspace-path/src/index.ts
|
|
259
|
+
/** Whether a path uses a Windows drive or UNC prefix. */
|
|
260
|
+
function isWindowsStylePath(value) {
|
|
261
|
+
return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Abbreviate a POSIX home directory for display.
|
|
265
|
+
* @param path - Absolute or already-short display path.
|
|
266
|
+
* @param home - Host account home; absent skips abbreviation.
|
|
267
|
+
* @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
|
|
268
|
+
*/
|
|
269
|
+
function abbreviateHomePath(path, home) {
|
|
270
|
+
if (home === void 0 || home === "") return path;
|
|
271
|
+
if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
|
|
272
|
+
const root = home.replace(/\/+$/, "");
|
|
273
|
+
if (root === "" || root === "/") return path;
|
|
274
|
+
if (path.replace(/\/+$/, "") === root) return "~";
|
|
275
|
+
if (path.startsWith(`${root}/`)) return `~${path.slice(root.length)}`;
|
|
276
|
+
return path;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Read the final non-empty segment of a Workspace path for display.
|
|
280
|
+
* Workspace-label surfaces use this helper instead of deriving another basename.
|
|
281
|
+
* @param path - Workspace directory path using POSIX or Windows separators.
|
|
282
|
+
* @returns the final segment, or an empty string for a separator-only path.
|
|
283
|
+
*/
|
|
284
|
+
function workspaceTitleOf(path) {
|
|
285
|
+
const trimmed = path.replace(/[/\\]+$/, "");
|
|
286
|
+
const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
287
|
+
return trimmed.slice(separator + 1);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Resolve the Workspace browser group that owns one Session.
|
|
291
|
+
* @param workspaces - authoritative Workspace membership.
|
|
292
|
+
* @param sessionId - Session whose browser group is required.
|
|
293
|
+
* @returns owning Workspace id, or {@link UNGROUPED_KEY} when no Workspace accounts for it.
|
|
294
|
+
*/
|
|
295
|
+
function owningGroupKey(workspaces, sessionId) {
|
|
296
|
+
return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
|
|
297
|
+
}
|
|
298
|
+
function mainSessionId(list) {
|
|
299
|
+
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Directory display label: basename of the path (both separators accepted).
|
|
303
|
+
* Ungrouped-bucket fallback for surfaces without a workspace title.
|
|
304
|
+
* @param cwd - directory path, or undefined for the ungrouped bucket.
|
|
305
|
+
* @returns basename, the raw cwd when it has no basename, or an empty ungrouped marker.
|
|
306
|
+
*/
|
|
307
|
+
function workspaceLabel(cwd) {
|
|
308
|
+
if (cwd === void 0 || cwd === "") return "";
|
|
309
|
+
const base = workspaceTitleOf(cwd);
|
|
310
|
+
return base !== "" ? base : cwd;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Project known account members by current Session recency.
|
|
314
|
+
* @param sessionIds - authoritative account membership.
|
|
315
|
+
* @param summaries - current Session summaries; members without a summary are omitted until it arrives.
|
|
316
|
+
* @returns known members newest first, with Session identity as the deterministic tie-break.
|
|
317
|
+
*/
|
|
318
|
+
function orderByRecency(sessionIds, summaries) {
|
|
319
|
+
return sessionIds.flatMap((id) => {
|
|
320
|
+
const summary = summaries[id];
|
|
321
|
+
if (summary === void 0) return [];
|
|
322
|
+
return [{
|
|
323
|
+
id,
|
|
324
|
+
rank: summary.updatedAt
|
|
325
|
+
}];
|
|
326
|
+
}).sort((a, b) => {
|
|
327
|
+
if (a.rank !== b.rank) return b.rank - a.rank;
|
|
328
|
+
return a.id < b.id ? -1 : 1;
|
|
329
|
+
}).map((member) => member.id);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Reconcile a browser-local manual order with current account membership.
|
|
333
|
+
* New ordinary forks precede their sources without changing saved entries' relative order.
|
|
334
|
+
* @param memberIds - authoritative account membership.
|
|
335
|
+
* @param savedOrder - previously saved browser-local order.
|
|
336
|
+
* @param summaries - current Session metadata; unknown new members wait for their summaries.
|
|
337
|
+
* @param rowState - global pin and archive membership; only account members can supplement the order.
|
|
338
|
+
* @returns saved relative positions plus missing members ordered by pin, fork source, recency, and archive status.
|
|
339
|
+
*/
|
|
340
|
+
function reconcileManualOrder(memberIds, savedOrder, summaries, rowState) {
|
|
341
|
+
const members = new Map(memberIds.map((id) => [id, id]));
|
|
342
|
+
const included = /* @__PURE__ */ new Set();
|
|
343
|
+
const ordered = [];
|
|
344
|
+
for (const key of savedOrder ?? []) {
|
|
345
|
+
const id = members.get(key);
|
|
346
|
+
if (id === void 0 || included.has(key)) continue;
|
|
347
|
+
ordered.push(id);
|
|
348
|
+
included.add(key);
|
|
349
|
+
}
|
|
350
|
+
const archived = new Set(rowState?.archivedSessionIds);
|
|
351
|
+
const pins = [];
|
|
352
|
+
for (const sessionId of rowState?.pinnedSessionIds ?? []) {
|
|
353
|
+
const id = members.get(sessionId);
|
|
354
|
+
if (id === void 0 || included.has(id) || archived.has(id) || summaries[id] === void 0) continue;
|
|
355
|
+
pins.push(id);
|
|
356
|
+
included.add(id);
|
|
357
|
+
}
|
|
358
|
+
const ordinary = [];
|
|
359
|
+
const archives = [];
|
|
360
|
+
for (const id of orderByRecency([...members.values()].filter((id) => !included.has(id)), summaries)) if (archived.has(id)) archives.push(id);
|
|
361
|
+
else ordinary.push(id);
|
|
362
|
+
const result = [
|
|
363
|
+
...pins,
|
|
364
|
+
...ordered,
|
|
365
|
+
...ordinary,
|
|
366
|
+
...archives
|
|
367
|
+
];
|
|
368
|
+
const pending = new Set(ordinary);
|
|
369
|
+
const placeFork = (id) => {
|
|
370
|
+
if (!pending.delete(id)) return;
|
|
371
|
+
const parentId = summaries[id]?.parentId;
|
|
372
|
+
if (parentId === void 0 || parentId === id || !result.includes(parentId)) return;
|
|
373
|
+
placeFork(parentId);
|
|
374
|
+
result.splice(result.indexOf(id), 1);
|
|
375
|
+
result.splice(result.indexOf(parentId), 0, id);
|
|
376
|
+
};
|
|
377
|
+
for (const id of [...ordinary].reverse()) placeFork(id);
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Keep the selected provisional New Session ahead of either base order.
|
|
382
|
+
* @param order - recency or reconciled manual order.
|
|
383
|
+
* @param currentBlank - selected blank Session in this account, when present.
|
|
384
|
+
* @returns a copy with the selected blank first and no duplicate slot.
|
|
385
|
+
*/
|
|
386
|
+
function pinCurrentBlank(order, currentBlank) {
|
|
387
|
+
if (currentBlank === void 0) return [...order];
|
|
388
|
+
return [currentBlank, ...order.filter((id) => id !== currentBlank)];
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Ordinary sessions are visible; among blank sessions, only the current one
|
|
392
|
+
* is visible. Subagent children use their parent header catalog; archived
|
|
393
|
+
* sessions follow the archived filter, while their accounting slots remain
|
|
394
|
+
* either way so unarchiving restores position.
|
|
395
|
+
*/
|
|
396
|
+
function sessionVisible(session, current, archived, archivedFilter) {
|
|
397
|
+
if (session.origin === "subagent") return false;
|
|
398
|
+
if (session.blank && session.id !== current) return false;
|
|
399
|
+
switch (archivedFilter) {
|
|
400
|
+
case "default": return !archived.has(session.id);
|
|
401
|
+
case "show": return true;
|
|
402
|
+
case "only": return archived.has(session.id);
|
|
403
|
+
/* v8 ignore next 2 -- closed-union backstop; only reached if the filter is forged */
|
|
404
|
+
default: return assertNever$1(archivedFilter);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Keep the visible New Session placeholder first, then partition pinned and
|
|
409
|
+
* ordinary rows without changing either partition's caller order.
|
|
410
|
+
*/
|
|
411
|
+
function sectionMembers(members, pinned, archived) {
|
|
412
|
+
const placeholders = [];
|
|
413
|
+
const leading = [];
|
|
414
|
+
const rest = [];
|
|
415
|
+
for (const member of members) if (member.blank) placeholders.push(member);
|
|
416
|
+
else if (!archived.has(member.id) && pinned.has(member.id)) leading.push(member);
|
|
417
|
+
else rest.push(member);
|
|
418
|
+
return [
|
|
419
|
+
...placeholders,
|
|
420
|
+
...leading,
|
|
421
|
+
...rest
|
|
422
|
+
];
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* A blank session is the selected Workspace's provisional New Session row;
|
|
426
|
+
* its canonical title never enters search (blank rows are query-excluded)
|
|
427
|
+
* and the renderer localizes its display label.
|
|
428
|
+
*/
|
|
429
|
+
function sessionTitle(session) {
|
|
430
|
+
return session.blank ? "" : session.displayTitle;
|
|
431
|
+
}
|
|
432
|
+
/** The list projection alone owns the best-effort active-Schedule indicator. */
|
|
433
|
+
function hasActiveSchedule(session) {
|
|
434
|
+
return (session.projectionValues?.schedule?.length ?? 0) > 0;
|
|
435
|
+
}
|
|
436
|
+
/** Build one group without projecting session lineage into presentation. */
|
|
437
|
+
function buildGroup(key, workspaceId, cwd, createdAt, label, members) {
|
|
438
|
+
return {
|
|
439
|
+
key,
|
|
440
|
+
workspaceId,
|
|
441
|
+
cwd,
|
|
442
|
+
createdAt,
|
|
443
|
+
label,
|
|
444
|
+
sessions: [...members]
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
|
|
448
|
+
function orderedUngrouped(members, stored, summaries) {
|
|
449
|
+
const byId = new Map(members.map((session) => [session.id, session]));
|
|
450
|
+
return (stored === void 0 ? orderByRecency(members.map((session) => session.id), summaries) : reconcileManualOrder(members.map((session) => session.id), stored, summaries)).flatMap((id) => {
|
|
451
|
+
const session = byId.get(id);
|
|
452
|
+
/* v8 ignore next -- ids are projected exclusively from the members used to build byId. */
|
|
453
|
+
return session === void 0 ? [] : [session];
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Group Sessions by Workspace: one group per caller-ordered entity, with
|
|
458
|
+
* members resolved from caller-ordered sessionIds. Sessions outside every
|
|
459
|
+
* Workspace trail in the browser-local Ungrouped order, which falls back to
|
|
460
|
+
* recency before that order is initialized.
|
|
461
|
+
*/
|
|
462
|
+
function groupByWorkspace(list, workspaces, archived, archivedFilter, ungroupedOrder) {
|
|
463
|
+
const current = mainSessionId(list);
|
|
464
|
+
const groups = [];
|
|
465
|
+
const accounted = /* @__PURE__ */ new Set();
|
|
466
|
+
for (const workspace of workspaces) {
|
|
467
|
+
const members = [];
|
|
468
|
+
for (const id of workspace.sessionIds) {
|
|
469
|
+
const summary = list.byId[id];
|
|
470
|
+
if (summary === void 0) continue;
|
|
471
|
+
accounted.add(id);
|
|
472
|
+
if (!sessionVisible(summary, current, archived, archivedFilter)) continue;
|
|
473
|
+
members.push(summary);
|
|
474
|
+
}
|
|
475
|
+
groups.push(buildGroup(workspace.workspaceId, workspace.workspaceId, workspace.path, Date.parse(workspace.createdAt), workspace.title, members));
|
|
476
|
+
}
|
|
477
|
+
const stray = list.ids.map((id) => list.byId[id]).filter((s) => s !== void 0 && !accounted.has(s.id) && sessionVisible(s, current, archived, archivedFilter));
|
|
478
|
+
if (stray.length > 0) groups.push(buildGroup("", void 0, void 0, void 0, "", orderedUngrouped(stray, ungroupedOrder, list.byId)));
|
|
479
|
+
return groups;
|
|
480
|
+
}
|
|
481
|
+
/** Keep navigation presentation independent from domain-owned interaction objects. */
|
|
482
|
+
function visiblePendingKind(kind) {
|
|
483
|
+
switch (kind) {
|
|
484
|
+
case "approval":
|
|
485
|
+
case "plan-review":
|
|
486
|
+
case "question": return kind;
|
|
487
|
+
default: return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function runningChildCount(list, parentId, statuses) {
|
|
491
|
+
return list.projectionsBySession[parentId]?.values.subagentCatalog?.reduce((count, child) => count + ((statuses.get(child.id)?.running ?? list.byId[child.id]?.running) === true ? 1 : 0), 0) ?? 0;
|
|
492
|
+
}
|
|
493
|
+
function sessionNode(s, list, statuses, pinned, archived) {
|
|
494
|
+
const status = statuses.get(s.id);
|
|
495
|
+
const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
|
|
496
|
+
return {
|
|
497
|
+
id: s.id,
|
|
498
|
+
title: sessionTitle(s),
|
|
499
|
+
blank: s.blank,
|
|
500
|
+
running: status?.running ?? s.running,
|
|
501
|
+
runningSubagentCount: runningChildCount(list, s.id, statuses),
|
|
502
|
+
completed: status?.completionUnread === true,
|
|
503
|
+
hasActiveSchedule: hasActiveSchedule(s),
|
|
504
|
+
pinned: !archived.has(s.id) && pinned.has(s.id),
|
|
505
|
+
archived: archived.has(s.id),
|
|
506
|
+
updatedAt: s.updatedAt,
|
|
507
|
+
...pendingInteraction === void 0 ? {} : { pendingInteraction }
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Derive the workspace browser groups with every session as a top-level row.
|
|
512
|
+
*
|
|
513
|
+
* Every group shows; sessions populate under expanded groups with pinned rows
|
|
514
|
+
* leading in the selected local order. Blank sessions are
|
|
515
|
+
* excluded except for the selected provisional New Session row; archived
|
|
516
|
+
* sessions keep their slots and appear per the archived filter. Content
|
|
517
|
+
* search lives outside this derivation (see {@link deriveSearchResults}).
|
|
518
|
+
* @param list - sessions list snapshot (`mainView` retention feeds containsCurrent).
|
|
519
|
+
* @param workspaces - real Workspaces in Host group order with caller-projected Session order.
|
|
520
|
+
* @param rowState - registry-global pin and archive sets plus the archived filter.
|
|
521
|
+
* @param statuses - unified UI status by Session.
|
|
522
|
+
* @param view - local expansion arrays.
|
|
523
|
+
* @returns group sections in render order.
|
|
524
|
+
*/
|
|
525
|
+
function deriveGroups(list, workspaces, rowState, statuses, view) {
|
|
526
|
+
const archived = new Set(rowState.archivedSessionIds);
|
|
527
|
+
const pinned = new Set(rowState.pinnedSessionIds);
|
|
528
|
+
const expandedGroups = new Set(view.expandedGroups);
|
|
529
|
+
const current = mainSessionId(list);
|
|
530
|
+
const currentGroup = current === void 0 ? void 0 : owningGroupKey(workspaces, current);
|
|
531
|
+
const groups = [];
|
|
532
|
+
for (const g of groupByWorkspace(list, workspaces, archived, rowState.archivedFilter, view.ungroupedOrder)) {
|
|
533
|
+
const expanded = expandedGroups.has(g.key);
|
|
534
|
+
groups.push({
|
|
535
|
+
key: g.key,
|
|
536
|
+
workspaceId: g.workspaceId,
|
|
537
|
+
cwd: g.cwd,
|
|
538
|
+
createdAt: g.createdAt,
|
|
539
|
+
label: g.label,
|
|
540
|
+
sessionCount: g.sessions.length,
|
|
541
|
+
expanded,
|
|
542
|
+
containsCurrent: g.key === currentGroup,
|
|
543
|
+
sessions: expanded ? sectionMembers(g.sessions, pinned, archived).map((session) => sessionNode(session, list, statuses, pinned, archived)) : []
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
return groups;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Select complete flat-list membership, independently of archive visibility.
|
|
550
|
+
* @param list - sessions list snapshot.
|
|
551
|
+
* @returns known ordinary Session ids, including archives and only the current blank.
|
|
552
|
+
*/
|
|
553
|
+
function sessionMemberIds(list) {
|
|
554
|
+
return visibleSessionIds(list, [], "show");
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Select visible flat-list members without deriving row presentation or ordering.
|
|
558
|
+
* @param list - sessions list snapshot.
|
|
559
|
+
* @param archivedSessionIds - registry-global archive set.
|
|
560
|
+
* @param archivedFilter - archived-row visibility choice.
|
|
561
|
+
* @returns known visible Session ids in list order, including ordinary forks and only the current blank.
|
|
562
|
+
*/
|
|
563
|
+
function visibleSessionIds(list, archivedSessionIds, archivedFilter) {
|
|
564
|
+
const archived = new Set(archivedSessionIds);
|
|
565
|
+
const current = mainSessionId(list);
|
|
566
|
+
return list.ids.filter((id) => {
|
|
567
|
+
const s = list.byId[id];
|
|
568
|
+
return s !== void 0 && sessionVisible(s, current, archived, archivedFilter);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Derive flat rows from the browser's complete ordered Session ids, with
|
|
573
|
+
* pinned rows fronted ahead of the supplied order.
|
|
574
|
+
* @param list - sessions list snapshot used to select the ids.
|
|
575
|
+
* @param sessionIds - complete account members in the selected order, including hidden archives.
|
|
576
|
+
* @param rowState - registry-global pin and archive sets plus the archived filter.
|
|
577
|
+
* @param statuses - unified UI status by Session.
|
|
578
|
+
* @returns flat rows in sectioned order with current status indicators.
|
|
579
|
+
*/
|
|
580
|
+
function deriveFlat(list, sessionIds, rowState, statuses) {
|
|
581
|
+
const archived = new Set(rowState.archivedSessionIds);
|
|
582
|
+
const pinned = new Set(rowState.pinnedSessionIds);
|
|
583
|
+
const current = mainSessionId(list);
|
|
584
|
+
return sectionMembers(sessionIds.flatMap((id) => {
|
|
585
|
+
const session = list.byId[id];
|
|
586
|
+
return session !== void 0 && sessionVisible(session, current, archived, rowState.archivedFilter) ? [session] : [];
|
|
587
|
+
}), pinned, archived).map((session) => sessionNode(session, list, statuses, pinned, archived));
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Merge immediate title/Workspace substring matches with ranked Host content
|
|
591
|
+
* matches. Local rows lead newest-first, content-only rows retain backend
|
|
592
|
+
* order, and duplicate sessions receive the backend snippet in place.
|
|
593
|
+
* @param list - session metadata authority.
|
|
594
|
+
* @param workspaces - Workspace membership and display labels.
|
|
595
|
+
* @param query - caller text; surrounding whitespace is ignored.
|
|
596
|
+
* @param archivedSessionIds - registry-global archive set (members match per the archived filter).
|
|
597
|
+
* @param archivedFilter - archived-row visibility choice; search follows it.
|
|
598
|
+
* @param statuses - unified UI status by Session.
|
|
599
|
+
* @param content - ranked Host content-search page.
|
|
600
|
+
* @param limit - protocol-owned maximum merged row count.
|
|
601
|
+
* @returns bounded deduplicated flat rows and a refine-query hint bit.
|
|
602
|
+
*/
|
|
603
|
+
function deriveSearchResults(list, workspaces, query, archivedSessionIds, archivedFilter, statuses, content, limit) {
|
|
604
|
+
const q = query.trim().toLowerCase();
|
|
605
|
+
if (q === "") return {
|
|
606
|
+
items: [],
|
|
607
|
+
hasMore: false
|
|
608
|
+
};
|
|
609
|
+
const archived = new Set(archivedSessionIds);
|
|
610
|
+
const current = mainSessionId(list);
|
|
611
|
+
const workspaceBySession = /* @__PURE__ */ new Map();
|
|
612
|
+
for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
|
|
613
|
+
const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
|
|
614
|
+
const contentBySession = /* @__PURE__ */ new Map();
|
|
615
|
+
for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
|
|
616
|
+
const local = [];
|
|
617
|
+
for (const id of list.ids) {
|
|
618
|
+
const summary = list.byId[id];
|
|
619
|
+
if (summary === void 0 || summary.blank || !sessionVisible(summary, current, archived, archivedFilter)) continue;
|
|
620
|
+
if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
|
|
621
|
+
}
|
|
622
|
+
const localById = new Map(local.map((summary) => [summary.id, summary]));
|
|
623
|
+
const orderedLocal = orderByRecency(local.map((summary) => summary.id), list.byId).map((id) => localById.get(id));
|
|
624
|
+
const ordered = [];
|
|
625
|
+
const included = /* @__PURE__ */ new Set();
|
|
626
|
+
const include = (summary) => {
|
|
627
|
+
if (included.has(summary.id)) return;
|
|
628
|
+
included.add(summary.id);
|
|
629
|
+
ordered.push(summary);
|
|
630
|
+
};
|
|
631
|
+
for (const summary of orderedLocal) include(summary);
|
|
632
|
+
for (const item of content.items) {
|
|
633
|
+
const summary = list.byId[item.sessionId];
|
|
634
|
+
if (summary !== void 0 && !summary.blank && sessionVisible(summary, current, archived, archivedFilter)) include(summary);
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
items: ordered.slice(0, limit).map((summary) => {
|
|
638
|
+
const match = contentBySession.get(summary.id);
|
|
639
|
+
const status = statuses.get(summary.id);
|
|
640
|
+
const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
|
|
641
|
+
return {
|
|
642
|
+
id: summary.id,
|
|
643
|
+
title: sessionTitle(summary),
|
|
644
|
+
workspace: labelOf(summary),
|
|
645
|
+
running: status?.running ?? summary.running,
|
|
646
|
+
runningSubagentCount: runningChildCount(list, summary.id, statuses),
|
|
647
|
+
...pendingInteraction === void 0 ? {} : { pendingInteraction },
|
|
648
|
+
completed: status?.completionUnread === true,
|
|
649
|
+
hasActiveSchedule: hasActiveSchedule(summary),
|
|
650
|
+
archived: archived.has(summary.id),
|
|
651
|
+
...match === void 0 ? {} : { snippet: match.snippet }
|
|
652
|
+
};
|
|
653
|
+
}),
|
|
654
|
+
hasMore: content.hasMore || ordered.length > limit
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
/** Normalize separators for comparison without interpreting POSIX backslashes as separators. */
|
|
658
|
+
function folderPath(path) {
|
|
659
|
+
return (/^[A-Za-z]:[/\\]/.test(path) || path.startsWith("\\\\") ? path.replaceAll("\\", "/") : path).replace(/\/+$/, "");
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Find the nearest registered ancestor, excluding the Workspace directory itself.
|
|
663
|
+
* Paths use Host spelling; matching is case-sensitive, like Workspace identity.
|
|
664
|
+
* @param path - Workspace directory.
|
|
665
|
+
* @param parents - registered Workspace directory paths.
|
|
666
|
+
* @returns the owning parent path, or undefined when no parent contains the Workspace.
|
|
667
|
+
*/
|
|
668
|
+
function owningParentFolder(path, parents) {
|
|
669
|
+
const child = folderPath(path);
|
|
670
|
+
let owner;
|
|
671
|
+
let length = -1;
|
|
672
|
+
for (const parent of parents) {
|
|
673
|
+
const root = folderPath(parent);
|
|
674
|
+
if (root.length > length && child !== root && child.startsWith(`${root}/`)) {
|
|
675
|
+
owner = parent;
|
|
676
|
+
length = root.length;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return owner;
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region lib/types/client/stores.js
|
|
683
|
+
/**
|
|
684
|
+
* The workspace browser's viewing store: the session-list grouping mode,
|
|
685
|
+
* persisted across reloads. Module level exports the factory only (a
|
|
686
|
+
* module-level handle would pin the store identity across plugin reloads);
|
|
687
|
+
* register() receives the factory and the browser derives its PropsStore
|
|
688
|
+
* share from the return type.
|
|
689
|
+
*/
|
|
690
|
+
/** Browser-local order account for the hierarchy-free flat Session list. */
|
|
691
|
+
const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
|
|
692
|
+
/** Copy read-only projections into the persisted mutable store representation. */
|
|
693
|
+
function copySessionOrders(orders) {
|
|
694
|
+
return Object.fromEntries(Object.entries(orders).map(([key, order]) => [key, [...order]]));
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Create the workspace browser viewing store handle.
|
|
698
|
+
* @returns the store handle (spec + type + identity + factory in one).
|
|
699
|
+
*/
|
|
700
|
+
function createWorkspaceViewStore() {
|
|
701
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
702
|
+
init: () => ({
|
|
703
|
+
groupBy: "workspace",
|
|
704
|
+
orderBy: "updated",
|
|
705
|
+
groupExpansion: {},
|
|
706
|
+
sessionOrderByAccount: {},
|
|
707
|
+
archivedFilter: "default"
|
|
708
|
+
}),
|
|
709
|
+
persist: "dsh.workspace.view.v5",
|
|
710
|
+
actions: {
|
|
711
|
+
setGroupBy: (d, mode) => {
|
|
712
|
+
d.groupBy = mode;
|
|
713
|
+
},
|
|
714
|
+
setOrderBy: (d, mode, initialOrders) => {
|
|
715
|
+
if (mode === d.orderBy) return;
|
|
716
|
+
d.sessionOrderByAccount = mode === "manual" ? copySessionOrders(initialOrders) : {};
|
|
717
|
+
d.orderBy = mode;
|
|
718
|
+
},
|
|
719
|
+
setGroupExpanded: (d, key, expanded) => {
|
|
720
|
+
d.groupExpansion[key] = expanded;
|
|
721
|
+
},
|
|
722
|
+
retainAccountKeys: (d, workspaceKeys) => {
|
|
723
|
+
const retained = new Set(workspaceKeys);
|
|
724
|
+
d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
|
|
725
|
+
d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
|
|
726
|
+
delete d.sessionUpdatedAtByAccount;
|
|
727
|
+
},
|
|
728
|
+
syncSessionOrders: (d, orders) => {
|
|
729
|
+
if (d.orderBy !== "manual") return;
|
|
730
|
+
Object.assign(d.sessionOrderByAccount, copySessionOrders(orders));
|
|
731
|
+
},
|
|
732
|
+
setSessionOrder: (d, accountKey, order, initialOrders) => {
|
|
733
|
+
if (d.orderBy === "updated") d.sessionOrderByAccount = copySessionOrders(initialOrders);
|
|
734
|
+
else Object.assign(d.sessionOrderByAccount, copySessionOrders(initialOrders));
|
|
735
|
+
d.orderBy = "manual";
|
|
736
|
+
d.sessionOrderByAccount[accountKey] = [...order];
|
|
737
|
+
},
|
|
738
|
+
pinSessionOrder: (d, sessionId, accountKeys, source) => {
|
|
739
|
+
const selected = new Set(accountKeys);
|
|
740
|
+
d.sessionOrderByAccount = Object.fromEntries(Object.entries(source.members).map(([key, members]) => {
|
|
741
|
+
const order = reconcileManualOrder(members, d.sessionOrderByAccount[key], source.summaries, source.rowState);
|
|
742
|
+
return [key, selected.has(key) ? [sessionId, ...order.filter((id) => id !== sessionId)] : order];
|
|
743
|
+
}));
|
|
744
|
+
},
|
|
745
|
+
setArchivedFilter: (d, filter) => {
|
|
746
|
+
d.archivedFilter = filter;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
//#endregion
|
|
752
|
+
//#region lib/types/client/pin-order.js
|
|
753
|
+
/**
|
|
754
|
+
* Every account's complete membership: each Workspace, Ungrouped, and the flat list.
|
|
755
|
+
* @param workspaces - current Host Workspaces.
|
|
756
|
+
* @param list - current Session list snapshot.
|
|
757
|
+
* @param rowState - registry-global pin and archive sets.
|
|
758
|
+
* @returns the order source for one pin write.
|
|
759
|
+
*/
|
|
760
|
+
function pinOrderSource(workspaces, list, rowState) {
|
|
761
|
+
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
762
|
+
return {
|
|
763
|
+
members: Object.fromEntries([
|
|
764
|
+
...workspaces.map((workspace) => [workspace.workspaceId, workspace.sessionIds]),
|
|
765
|
+
["", list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id))],
|
|
766
|
+
[FLAT_SESSION_ORDER_KEY, sessionMemberIds(list)]
|
|
767
|
+
]),
|
|
768
|
+
summaries: list.byId,
|
|
769
|
+
rowState
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* The accounts a pinned Session leads: its group (or Ungrouped) and the flat list.
|
|
774
|
+
* @param workspaces - current Host Workspaces.
|
|
775
|
+
* @param sessionId - the Session being pinned.
|
|
776
|
+
* @returns the account keys `pinSessionOrder` fronts.
|
|
777
|
+
*/
|
|
778
|
+
function pinOrderAccounts(workspaces, sessionId) {
|
|
779
|
+
return [owningGroupKey(workspaces, sessionId), FLAT_SESSION_ORDER_KEY];
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
12
782
|
//#region lib/types/client/navigation.js
|
|
13
783
|
/** Workspace archive and directory UI capability. */
|
|
14
784
|
/** Structured directory failure exposed to directory UI consumers. */
|
|
@@ -26,6 +796,8 @@ window.__ModuleLoader__.load({
|
|
|
26
796
|
directoryPicker;
|
|
27
797
|
workspaces;
|
|
28
798
|
sessions;
|
|
799
|
+
view;
|
|
800
|
+
notify;
|
|
29
801
|
connecting = /* @__PURE__ */ new Map();
|
|
30
802
|
lifetime = new AbortController();
|
|
31
803
|
selection = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({}, { persist: { name: "dsh.sessions.current" } });
|
|
@@ -35,12 +807,16 @@ window.__ModuleLoader__.load({
|
|
|
35
807
|
* @param directoryPicker - the directory-picking Remote namespace.
|
|
36
808
|
* @param workspaces - pure Workspace Controller.
|
|
37
809
|
* @param sessions - pure Session Controller.
|
|
810
|
+
* @param view - the browser's viewing-store write set (one instance shared with its registration).
|
|
811
|
+
* @param notify - show one notice through the Workspace notice channel.
|
|
38
812
|
*/
|
|
39
|
-
constructor(ctx, directoryPicker, workspaces, sessions) {
|
|
813
|
+
constructor(ctx, directoryPicker, workspaces, sessions, view, notify) {
|
|
40
814
|
super(ctx, "uiWorkspace");
|
|
41
815
|
this.directoryPicker = directoryPicker;
|
|
42
816
|
this.workspaces = workspaces;
|
|
43
817
|
this.sessions = sessions;
|
|
818
|
+
this.view = view;
|
|
819
|
+
this.notify = notify;
|
|
44
820
|
ctx.effect(() => {
|
|
45
821
|
const stop = this.watchNavigation();
|
|
46
822
|
return () => {
|
|
@@ -57,34 +833,56 @@ window.__ModuleLoader__.load({
|
|
|
57
833
|
if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
|
|
58
834
|
const inflight = this.connecting.get(workspaceId);
|
|
59
835
|
if (inflight !== void 0) return inflight;
|
|
836
|
+
const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
|
|
837
|
+
this.connecting.delete(workspaceId);
|
|
838
|
+
});
|
|
839
|
+
this.connecting.set(workspaceId, attempt);
|
|
840
|
+
return attempt;
|
|
841
|
+
}
|
|
842
|
+
reuseOrCreateBlank(workspace) {
|
|
60
843
|
const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
|
|
61
844
|
const sessions = this.sessions.list.getSnapshot();
|
|
62
845
|
for (const id of sessions.ids) {
|
|
63
846
|
const summary = sessions.byId[id];
|
|
64
|
-
if (summary
|
|
847
|
+
if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
|
|
848
|
+
return this.reuseBlank(workspace.workspaceId, id);
|
|
849
|
+
}
|
|
850
|
+
return this.sessions.create({ workspaceId: workspace.workspaceId });
|
|
851
|
+
}
|
|
852
|
+
async reuseBlank(workspaceId, sessionId) {
|
|
853
|
+
try {
|
|
854
|
+
return await this.sessions.create({
|
|
855
|
+
workspaceId,
|
|
856
|
+
sessionId
|
|
857
|
+
});
|
|
858
|
+
} catch (error) {
|
|
859
|
+
if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
|
|
860
|
+
return this.sessions.create({ workspaceId });
|
|
65
861
|
}
|
|
66
|
-
const attempt = this.sessions.create({ workspaceId }).finally(() => {
|
|
67
|
-
this.connecting.delete(workspaceId);
|
|
68
|
-
});
|
|
69
|
-
this.connecting.set(workspaceId, attempt);
|
|
70
|
-
return attempt;
|
|
71
862
|
}
|
|
72
863
|
openSession(target) {
|
|
73
|
-
this.replaceMain(target, this.lifetime.signal);
|
|
864
|
+
this.replaceMain(target, this.lifetime.signal, "reveal");
|
|
74
865
|
}
|
|
75
866
|
async openWorkspace(workspaceId, beforeOpen) {
|
|
76
867
|
const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
|
|
77
|
-
|
|
868
|
+
let sessionId;
|
|
869
|
+
try {
|
|
870
|
+
sessionId = await this.connectWorkspace(workspaceId);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
if (!navigation.aborted) this.notify({
|
|
873
|
+
kind: "createFailed",
|
|
874
|
+
message: creationFailureMessage(error)
|
|
875
|
+
});
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
78
878
|
if (navigation.aborted) return;
|
|
79
|
-
this.replaceMain(sessionId, navigation, beforeOpen);
|
|
879
|
+
this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
|
|
80
880
|
}
|
|
81
881
|
async forkSession(sessionId) {
|
|
82
|
-
|
|
83
|
-
const childId = await this.sessions.fork({
|
|
882
|
+
await this.sessions.fork({
|
|
84
883
|
sessionId,
|
|
85
884
|
increaseTitle: true
|
|
86
885
|
});
|
|
87
|
-
if (!navigation.aborted) this.replaceMain(childId, navigation);
|
|
88
886
|
}
|
|
89
887
|
startSession(workspaceId) {
|
|
90
888
|
const workspace = this.workspaces.list.getSnapshot();
|
|
@@ -101,13 +899,24 @@ window.__ModuleLoader__.load({
|
|
|
101
899
|
console.warn("new session failed:", reason);
|
|
102
900
|
});
|
|
103
901
|
}
|
|
104
|
-
async archiveSession(sessionId) {
|
|
105
|
-
await this.workspaces.archiveSession(sessionId);
|
|
902
|
+
async archiveSession(sessionId, options = {}) {
|
|
903
|
+
await this.workspaces.archiveSession(sessionId, options);
|
|
106
904
|
if (this.mainReference?.sessionId === sessionId) this.clearMain();
|
|
107
905
|
}
|
|
108
906
|
async unarchiveSession(sessionId) {
|
|
109
907
|
await this.workspaces.unarchiveSession(sessionId);
|
|
110
908
|
}
|
|
909
|
+
async pinSession(sessionId) {
|
|
910
|
+
await this.workspaces.pinSession(sessionId);
|
|
911
|
+
const { items, pinnedSessionIds, archivedSessionIds } = this.workspaces.list.getSnapshot();
|
|
912
|
+
this.view.pinSessionOrder(sessionId, pinOrderAccounts(items, sessionId), pinOrderSource(items, this.sessions.list.getSnapshot(), {
|
|
913
|
+
pinnedSessionIds,
|
|
914
|
+
archivedSessionIds
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
async unpinSession(sessionId) {
|
|
918
|
+
await this.workspaces.unpinSession(sessionId);
|
|
919
|
+
}
|
|
111
920
|
async pickDirectory() {
|
|
112
921
|
const result = await this.directoryPicker.pick();
|
|
113
922
|
if (!result.ok) throw new Error(`directory picker failed: ${result.error.message}`);
|
|
@@ -136,34 +945,13 @@ window.__ModuleLoader__.load({
|
|
|
136
945
|
initial = "done";
|
|
137
946
|
return;
|
|
138
947
|
}
|
|
139
|
-
const saved = this.selection.getSnapshot();
|
|
140
|
-
const savedTarget = saved.subagentAddress ?? (saved.sessionId !== void 0 && sessions.byId[saved.sessionId] !== void 0 ? saved.sessionId : void 0);
|
|
141
|
-
if (savedTarget !== void 0) {
|
|
142
|
-
initial = "connecting";
|
|
143
|
-
try {
|
|
144
|
-
if (saved.subagentAddress !== void 0) this.sessions.refreshSubagents(saved.subagentAddress.parentSessionId);
|
|
145
|
-
this.openSession(savedTarget);
|
|
146
|
-
initial = "done";
|
|
147
|
-
} catch (reason) {
|
|
148
|
-
initial = "waiting";
|
|
149
|
-
console.warn("initial Session restoration failed:", reason);
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const target = recentWorkspace(workspace.items, sessions.byId);
|
|
154
|
-
if (target === void 0) {
|
|
155
|
-
initial = "done";
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
948
|
initial = "connecting";
|
|
159
|
-
this.
|
|
160
|
-
if (this.mainReference === void 0) this.openSession(sessionId);
|
|
161
|
-
}).then(() => {
|
|
949
|
+
this.restoreSelection(workspace, sessions).then(() => {
|
|
162
950
|
initial = "done";
|
|
163
951
|
}, (reason) => {
|
|
164
952
|
if (this.lifetime.signal.aborted) return;
|
|
165
953
|
initial = "waiting";
|
|
166
|
-
console.warn("initial
|
|
954
|
+
console.warn("initial Session restoration failed:", reason);
|
|
167
955
|
});
|
|
168
956
|
};
|
|
169
957
|
const disposeWorkspaces = this.workspaces.list.subscribe(reconcile);
|
|
@@ -171,534 +959,149 @@ window.__ModuleLoader__.load({
|
|
|
171
959
|
reconcile();
|
|
172
960
|
return () => {
|
|
173
961
|
this.lifetime.abort();
|
|
174
|
-
disposeSessions();
|
|
175
|
-
disposeWorkspaces();
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
/** @returns true when an archived current selection was cleared. */
|
|
179
|
-
clearArchivedCurrent() {
|
|
180
|
-
const current = this.mainReference?.sessionId;
|
|
181
|
-
if (current === void 0 || !this.workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
|
|
182
|
-
this.clearMain();
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
clearMain() {
|
|
186
|
-
const previous = this.mainReference;
|
|
187
|
-
this.mainReference = void 0;
|
|
188
|
-
this.selection.set({});
|
|
189
|
-
previous?.release();
|
|
190
|
-
this.ctx.layout.selectPanel(null);
|
|
191
|
-
}
|
|
192
|
-
replaceMain(target, signal, beforeOpen) {
|
|
193
|
-
signal.throwIfAborted();
|
|
194
|
-
const reference = this.sessions.retain(target, { source: "mainView" });
|
|
195
|
-
try {
|
|
196
|
-
signal.throwIfAborted();
|
|
197
|
-
beforeOpen?.(reference.sessionId);
|
|
198
|
-
if (signal.aborted) {
|
|
199
|
-
reference.release();
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const subagentAddress = typeof target === "string" ? this.sessions.subagentAddress(reference.sessionId) : target;
|
|
203
|
-
this.selection.set({
|
|
204
|
-
sessionId: reference.sessionId,
|
|
205
|
-
...subagentAddress === void 0 ? {} : { subagentAddress }
|
|
206
|
-
});
|
|
207
|
-
} catch (error) {
|
|
208
|
-
reference.release();
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
const previous = this.mainReference;
|
|
212
|
-
this.mainReference = reference;
|
|
213
|
-
previous?.release();
|
|
214
|
-
this.sessions.refreshSubagents(reference.sessionId);
|
|
215
|
-
this.ctx.layout.selectPanel(null);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
/** Stable tie-breaking follows Host Workspace order. */
|
|
219
|
-
function recentWorkspace(workspaces, sessions) {
|
|
220
|
-
let selected;
|
|
221
|
-
let selectedTime = Number.NEGATIVE_INFINITY;
|
|
222
|
-
for (const workspace of workspaces) {
|
|
223
|
-
let latest = Number.NEGATIVE_INFINITY;
|
|
224
|
-
for (const sessionId of workspace.sessionIds) {
|
|
225
|
-
const session = sessions[sessionId];
|
|
226
|
-
if (session !== void 0) latest = Math.max(latest, session.updatedAt);
|
|
227
|
-
}
|
|
228
|
-
if (latest === Number.NEGATIVE_INFINITY) latest = Date.parse(workspace.createdAt);
|
|
229
|
-
if (selected === void 0 || latest > selectedTime) {
|
|
230
|
-
selected = workspace.workspaceId;
|
|
231
|
-
selectedTime = latest;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return selected;
|
|
235
|
-
}
|
|
236
|
-
//#endregion
|
|
237
|
-
//#region lib/types/client/stores.js
|
|
238
|
-
/**
|
|
239
|
-
* The workspace browser's viewing store: the session-list grouping mode,
|
|
240
|
-
* persisted across reloads. Module level exports the factory only (a
|
|
241
|
-
* module-level handle would pin the store identity across plugin reloads);
|
|
242
|
-
* register() receives the factory and the browser derives its PropsStore
|
|
243
|
-
* share from the return type.
|
|
244
|
-
*/
|
|
245
|
-
/** Browser-local order account for the hierarchy-free flat Session list. */
|
|
246
|
-
const FLAT_SESSION_ORDER_KEY = "__flat_session_order__";
|
|
247
|
-
/** Copy read-only projections into the persisted mutable store representation. */
|
|
248
|
-
function copySessionOrders(orders) {
|
|
249
|
-
return Object.fromEntries(Object.entries(orders).map(([key, order]) => [key, [...order]]));
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Create the workspace browser viewing store handle.
|
|
253
|
-
* @returns the store handle (spec + type + identity + factory in one).
|
|
254
|
-
*/
|
|
255
|
-
function createWorkspaceViewStore() {
|
|
256
|
-
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
257
|
-
init: () => ({
|
|
258
|
-
groupBy: "workspace",
|
|
259
|
-
orderBy: "updated",
|
|
260
|
-
groupExpansion: {},
|
|
261
|
-
sessionOrderByAccount: {}
|
|
262
|
-
}),
|
|
263
|
-
persist: "dsh.workspace.view.v5",
|
|
264
|
-
actions: {
|
|
265
|
-
setGroupBy: (d, mode) => {
|
|
266
|
-
d.groupBy = mode;
|
|
267
|
-
},
|
|
268
|
-
setOrderBy: (d, mode, initialOrders) => {
|
|
269
|
-
if (mode === d.orderBy) return;
|
|
270
|
-
d.sessionOrderByAccount = mode === "manual" ? copySessionOrders(initialOrders) : {};
|
|
271
|
-
d.orderBy = mode;
|
|
272
|
-
},
|
|
273
|
-
setGroupExpanded: (d, key, expanded) => {
|
|
274
|
-
d.groupExpansion[key] = expanded;
|
|
275
|
-
},
|
|
276
|
-
retainAccountKeys: (d, workspaceKeys) => {
|
|
277
|
-
const retained = new Set(workspaceKeys);
|
|
278
|
-
d.groupExpansion = Object.fromEntries(Object.entries(d.groupExpansion).filter(([key]) => retained.has(key)));
|
|
279
|
-
d.sessionOrderByAccount = Object.fromEntries(Object.entries(d.sessionOrderByAccount).filter(([key]) => retained.has(key)));
|
|
280
|
-
delete d.sessionUpdatedAtByAccount;
|
|
281
|
-
},
|
|
282
|
-
syncSessionOrders: (d, orders) => {
|
|
283
|
-
if (d.orderBy !== "manual") return;
|
|
284
|
-
Object.assign(d.sessionOrderByAccount, copySessionOrders(orders));
|
|
285
|
-
},
|
|
286
|
-
setSessionOrder: (d, accountKey, order, initialOrders) => {
|
|
287
|
-
if (d.orderBy === "updated") d.sessionOrderByAccount = copySessionOrders(initialOrders);
|
|
288
|
-
d.orderBy = "manual";
|
|
289
|
-
d.sessionOrderByAccount[accountKey] = [...order];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
//#endregion
|
|
295
|
-
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
296
|
-
function r(e) {
|
|
297
|
-
var t, f, n = "";
|
|
298
|
-
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
299
|
-
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
300
|
-
var o = e.length;
|
|
301
|
-
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
302
|
-
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
303
|
-
return n;
|
|
304
|
-
}
|
|
305
|
-
function clsx() {
|
|
306
|
-
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
307
|
-
return n;
|
|
308
|
-
}
|
|
309
|
-
//#endregion
|
|
310
|
-
//#region ../../util/workspace-path/src/index.ts
|
|
311
|
-
/** Whether a path uses a Windows drive or UNC prefix. */
|
|
312
|
-
function isWindowsStylePath(value) {
|
|
313
|
-
return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Abbreviate a POSIX home directory for display.
|
|
317
|
-
* @param path - Absolute or already-short display path.
|
|
318
|
-
* @param home - Host account home; absent skips abbreviation.
|
|
319
|
-
* @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
|
|
320
|
-
*/
|
|
321
|
-
function abbreviateHomePath(path, home) {
|
|
322
|
-
if (home === void 0 || home === "") return path;
|
|
323
|
-
if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
|
|
324
|
-
const root = home.replace(/\/+$/, "");
|
|
325
|
-
if (root === "" || root === "/") return path;
|
|
326
|
-
if (path.replace(/\/+$/, "") === root) return "~";
|
|
327
|
-
if (path.startsWith(`${root}/`)) return `~${path.slice(root.length)}`;
|
|
328
|
-
return path;
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Read the final non-empty segment of a Workspace path for display.
|
|
332
|
-
* Workspace-label surfaces use this helper instead of deriving another basename.
|
|
333
|
-
* @param path - Workspace directory path using POSIX or Windows separators.
|
|
334
|
-
* @returns the final segment, or an empty string for a separator-only path.
|
|
335
|
-
*/
|
|
336
|
-
function workspaceTitleOf(path) {
|
|
337
|
-
const trimmed = path.replace(/[/\\]+$/, "");
|
|
338
|
-
const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
339
|
-
return trimmed.slice(separator + 1);
|
|
340
|
-
}
|
|
341
|
-
//#endregion
|
|
342
|
-
//#region lib/types/client/subagent-lineage.js
|
|
343
|
-
/** UI Workspace-owned projection of descendant counts from Session summaries. */
|
|
344
|
-
/**
|
|
345
|
-
* Index uninterrupted subagent descendants under each ancestor.
|
|
346
|
-
* @param summaries - Session summaries keyed by id.
|
|
347
|
-
* @returns descendant totals keyed by possible parent id.
|
|
348
|
-
*/
|
|
349
|
-
function indexSubagentDescendants(summaries) {
|
|
350
|
-
const indexed = /* @__PURE__ */ new Map();
|
|
351
|
-
for (const descendant of Object.values(summaries)) {
|
|
352
|
-
if (descendant.origin !== "subagent") continue;
|
|
353
|
-
const seen = /* @__PURE__ */ new Set();
|
|
354
|
-
let current = descendant;
|
|
355
|
-
while (current?.origin === "subagent" && current.parentId !== void 0 && !seen.has(current.id)) {
|
|
356
|
-
seen.add(current.id);
|
|
357
|
-
const aggregate = indexed.get(current.parentId);
|
|
358
|
-
if (aggregate === void 0) indexed.set(current.parentId, {
|
|
359
|
-
count: 1,
|
|
360
|
-
runningCount: descendant.running ? 1 : 0
|
|
361
|
-
});
|
|
362
|
-
else {
|
|
363
|
-
aggregate.count += 1;
|
|
364
|
-
if (descendant.running) aggregate.runningCount += 1;
|
|
365
|
-
}
|
|
366
|
-
current = summaries[current.parentId];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return indexed;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Resolve the Workspace browser group that owns one Session.
|
|
373
|
-
* @param workspaces - authoritative Workspace membership.
|
|
374
|
-
* @param sessionId - Session whose browser group is required.
|
|
375
|
-
* @returns owning Workspace id, or {@link UNGROUPED_KEY} when no Workspace accounts for it.
|
|
376
|
-
*/
|
|
377
|
-
function owningGroupKey(workspaces, sessionId) {
|
|
378
|
-
return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
|
|
379
|
-
}
|
|
380
|
-
function mainSessionId(list) {
|
|
381
|
-
return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Directory display label: basename of the path (both separators accepted).
|
|
385
|
-
* Ungrouped-bucket fallback for surfaces without a workspace title.
|
|
386
|
-
* @param cwd - directory path, or undefined for the ungrouped bucket.
|
|
387
|
-
* @returns basename, the raw cwd when it has no basename, or an empty ungrouped marker.
|
|
388
|
-
*/
|
|
389
|
-
function workspaceLabel(cwd) {
|
|
390
|
-
if (cwd === void 0 || cwd === "") return "";
|
|
391
|
-
const base = workspaceTitleOf(cwd);
|
|
392
|
-
return base !== "" ? base : cwd;
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Project known account members by current Session recency.
|
|
396
|
-
* @param sessionIds - authoritative account membership.
|
|
397
|
-
* @param summaries - current Session summaries; members without a summary are omitted until it arrives.
|
|
398
|
-
* @returns known members newest first, with Session identity as the deterministic tie-break.
|
|
399
|
-
*/
|
|
400
|
-
function orderByRecency(sessionIds, summaries) {
|
|
401
|
-
return sessionIds.flatMap((id) => {
|
|
402
|
-
const summary = summaries[id];
|
|
403
|
-
return summary === void 0 ? [] : [{
|
|
404
|
-
id,
|
|
405
|
-
updatedAt: summary.updatedAt
|
|
406
|
-
}];
|
|
407
|
-
}).sort((a, b) => {
|
|
408
|
-
if (a.updatedAt !== b.updatedAt) return b.updatedAt - a.updatedAt;
|
|
409
|
-
return a.id < b.id ? -1 : 1;
|
|
410
|
-
}).map((member) => member.id);
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Reconcile a browser-local manual order with current account membership.
|
|
414
|
-
* @param memberIds - authoritative account membership.
|
|
415
|
-
* @param savedOrder - previously saved browser-local order.
|
|
416
|
-
* @param summaries - current Session summaries used to append newly known members by recency.
|
|
417
|
-
* @returns retained saved slots followed by newly known members; departed members and unknown new members are omitted.
|
|
418
|
-
*/
|
|
419
|
-
function reconcileManualOrder(memberIds, savedOrder, summaries) {
|
|
420
|
-
const members = new Map(memberIds.map((id) => [id, id]));
|
|
421
|
-
const included = /* @__PURE__ */ new Set();
|
|
422
|
-
const ordered = [];
|
|
423
|
-
for (const key of savedOrder ?? []) {
|
|
424
|
-
const id = members.get(key);
|
|
425
|
-
if (id === void 0 || included.has(key)) continue;
|
|
426
|
-
ordered.push(id);
|
|
427
|
-
included.add(key);
|
|
428
|
-
}
|
|
429
|
-
for (const id of orderByRecency(memberIds, summaries)) {
|
|
430
|
-
if (included.has(id)) continue;
|
|
431
|
-
ordered.push(id);
|
|
432
|
-
included.add(id);
|
|
962
|
+
disposeSessions();
|
|
963
|
+
disposeWorkspaces();
|
|
964
|
+
};
|
|
433
965
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
* @param currentBlank - selected blank Session in this account, when present.
|
|
440
|
-
* @returns a copy with the selected blank first and no duplicate slot.
|
|
441
|
-
*/
|
|
442
|
-
function pinCurrentBlank(order, currentBlank) {
|
|
443
|
-
if (currentBlank === void 0) return [...order];
|
|
444
|
-
return [currentBlank, ...order.filter((id) => id !== currentBlank)];
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Ordinary sessions are visible; among blank sessions, only the current one
|
|
448
|
-
* is visible. Subagent children use their parent header catalog; archived
|
|
449
|
-
* sessions are visible nowhere, while their accounting slots remain so
|
|
450
|
-
* unarchiving restores position.
|
|
451
|
-
*/
|
|
452
|
-
function sessionVisible(session, current, archived) {
|
|
453
|
-
return session.origin !== "subagent" && !archived.has(session.id) && (!session.blank || session.id === current);
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* A blank session is the selected Workspace's provisional New Session row;
|
|
457
|
-
* its canonical title never enters search (blank rows are query-excluded)
|
|
458
|
-
* and the renderer localizes its display label.
|
|
459
|
-
*/
|
|
460
|
-
function sessionTitle(session) {
|
|
461
|
-
return session.blank ? "" : session.displayTitle;
|
|
462
|
-
}
|
|
463
|
-
/** The list projection alone owns the best-effort active-Schedule indicator. */
|
|
464
|
-
function hasActiveSchedule(session) {
|
|
465
|
-
return (session.projectionValues?.schedule?.length ?? 0) > 0;
|
|
466
|
-
}
|
|
467
|
-
/** Build one group without projecting session lineage into presentation. */
|
|
468
|
-
function buildGroup(key, workspaceId, cwd, createdAt, label, members) {
|
|
469
|
-
return {
|
|
470
|
-
key,
|
|
471
|
-
workspaceId,
|
|
472
|
-
cwd,
|
|
473
|
-
createdAt,
|
|
474
|
-
label,
|
|
475
|
-
sessions: [...members]
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
/** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
|
|
479
|
-
function orderedUngrouped(members, stored, summaries) {
|
|
480
|
-
const byId = new Map(members.map((session) => [session.id, session]));
|
|
481
|
-
return (stored === void 0 ? orderByRecency(members.map((session) => session.id), summaries) : reconcileManualOrder(members.map((session) => session.id), stored, summaries)).flatMap((id) => {
|
|
482
|
-
const session = byId.get(id);
|
|
483
|
-
/* v8 ignore next -- ids are projected exclusively from the members used to build byId. */
|
|
484
|
-
return session === void 0 ? [] : [session];
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Group Sessions by Workspace: one group per caller-ordered entity, with
|
|
489
|
-
* members resolved from caller-ordered sessionIds. Sessions outside every
|
|
490
|
-
* Workspace trail in the browser-local Ungrouped order, which falls back to
|
|
491
|
-
* recency before that order is initialized.
|
|
492
|
-
*/
|
|
493
|
-
function groupByWorkspace(list, workspaces, archived, ungroupedOrder) {
|
|
494
|
-
const current = mainSessionId(list);
|
|
495
|
-
const groups = [];
|
|
496
|
-
const accounted = /* @__PURE__ */ new Set();
|
|
497
|
-
for (const workspace of workspaces) {
|
|
498
|
-
const members = [];
|
|
499
|
-
for (const id of workspace.sessionIds) {
|
|
500
|
-
const summary = list.byId[id];
|
|
501
|
-
if (summary === void 0) continue;
|
|
502
|
-
accounted.add(id);
|
|
503
|
-
if (!sessionVisible(summary, current, archived)) continue;
|
|
504
|
-
members.push(summary);
|
|
966
|
+
async restoreSelection(workspaces, sessions) {
|
|
967
|
+
const saved = this.selection.getSnapshot();
|
|
968
|
+
if (saved.subagentAddress !== void 0) {
|
|
969
|
+
this.replaceMain(saved.subagentAddress, this.lifetime.signal, "preserve");
|
|
970
|
+
return;
|
|
505
971
|
}
|
|
506
|
-
|
|
972
|
+
const summary = saved.sessionId === void 0 ? void 0 : sessions.byId[saved.sessionId];
|
|
973
|
+
const workspace = summary === void 0 ? void 0 : workspaces.items.find((item) => item.sessionIds.includes(summary.id));
|
|
974
|
+
if (summary !== void 0 && (!summary.blank || workspace === void 0)) {
|
|
975
|
+
this.replaceMain(summary.id, this.lifetime.signal, "preserve");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
|
|
979
|
+
let sessionId;
|
|
980
|
+
if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.reuseBlank(workspace.workspaceId, summary.id);
|
|
981
|
+
let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
|
|
982
|
+
if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
|
|
983
|
+
const prepared = await this.initializeDefaultWorkspace(navigation);
|
|
984
|
+
if (navigation.aborted) return;
|
|
985
|
+
target = prepared?.workspaceId;
|
|
986
|
+
}
|
|
987
|
+
if (sessionId === void 0 && target !== void 0) sessionId = await this.connectWorkspace(target);
|
|
988
|
+
if (sessionId !== void 0 && !navigation.aborted) this.replaceMain(sessionId, navigation, "preserve");
|
|
507
989
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
990
|
+
async initializeDefaultWorkspace(signal) {
|
|
991
|
+
const language = this.ctx.locale.getSnapshot().active.toLowerCase().split("-")[0];
|
|
992
|
+
const title = (language === "zh" ? zh : en)["defaultWorkspace.title"];
|
|
993
|
+
try {
|
|
994
|
+
return await this.workspaces.initializeDefault({
|
|
995
|
+
directoryName: language === "zh" || language === "en" ? title : "default-workspace",
|
|
996
|
+
title
|
|
997
|
+
}, signal);
|
|
998
|
+
} catch (_error) {
|
|
999
|
+
if (!signal.aborted) this.notify({ kind: "defaultWorkspaceFailed" });
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
519
1002
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
title: sessionTitle(s),
|
|
527
|
-
blank: s.blank,
|
|
528
|
-
running: status?.running ?? s.running,
|
|
529
|
-
runningSubagentCount: descendants.get(s.id)?.runningCount ?? 0,
|
|
530
|
-
completed: status?.completionUnread === true,
|
|
531
|
-
hasActiveSchedule: hasActiveSchedule(s),
|
|
532
|
-
updatedAt: s.updatedAt,
|
|
533
|
-
...pendingInteraction === void 0 ? {} : { pendingInteraction }
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Derive the workspace browser groups with every session as a top-level row.
|
|
538
|
-
*
|
|
539
|
-
* Every group shows; sessions populate under expanded groups in the selected
|
|
540
|
-
* local order. Blank sessions are excluded except for the selected
|
|
541
|
-
* provisional New Session row; archived sessions are excluded everywhere.
|
|
542
|
-
* Content search lives outside this derivation
|
|
543
|
-
* (see {@link deriveSearchResults}).
|
|
544
|
-
* @param list - sessions list snapshot (`mainView` retention feeds containsCurrent).
|
|
545
|
-
* @param workspaces - real Workspaces in Host group order with caller-projected Session order.
|
|
546
|
-
* @param archivedSessionIds - registry-global archive set.
|
|
547
|
-
* @param statuses - unified UI status by Session.
|
|
548
|
-
* @param view - local expansion arrays.
|
|
549
|
-
* @returns group sections in render order.
|
|
550
|
-
*/
|
|
551
|
-
function deriveGroups(list, workspaces, archivedSessionIds, statuses, view) {
|
|
552
|
-
const archived = new Set(archivedSessionIds);
|
|
553
|
-
const expandedGroups = new Set(view.expandedGroups);
|
|
554
|
-
const descendants = indexSubagentDescendants(list.byId);
|
|
555
|
-
const current = mainSessionId(list);
|
|
556
|
-
const currentGroup = current === void 0 ? void 0 : owningGroupKey(workspaces, current);
|
|
557
|
-
const groups = [];
|
|
558
|
-
for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder)) {
|
|
559
|
-
const expanded = expandedGroups.has(g.key);
|
|
560
|
-
groups.push({
|
|
561
|
-
key: g.key,
|
|
562
|
-
workspaceId: g.workspaceId,
|
|
563
|
-
cwd: g.cwd,
|
|
564
|
-
createdAt: g.createdAt,
|
|
565
|
-
label: g.label,
|
|
566
|
-
sessionCount: g.sessions.length,
|
|
567
|
-
expanded,
|
|
568
|
-
containsCurrent: g.key === currentGroup,
|
|
569
|
-
sessions: expanded ? g.sessions.map((session) => sessionNode(session, descendants, statuses)) : []
|
|
570
|
-
});
|
|
1003
|
+
/** @returns true when an archived current selection was cleared. */
|
|
1004
|
+
clearArchivedCurrent() {
|
|
1005
|
+
const current = this.mainReference?.sessionId;
|
|
1006
|
+
if (current === void 0 || !this.workspaces.list.getSnapshot().archivedSessionIds.includes(current)) return false;
|
|
1007
|
+
this.clearMain();
|
|
1008
|
+
return true;
|
|
571
1009
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
* @returns known visible Session ids in list order, including ordinary forks and only the current blank.
|
|
579
|
-
*/
|
|
580
|
-
function visibleSessionIds(list, archivedSessionIds) {
|
|
581
|
-
const archived = new Set(archivedSessionIds);
|
|
582
|
-
const current = mainSessionId(list);
|
|
583
|
-
return list.ids.filter((id) => {
|
|
584
|
-
const s = list.byId[id];
|
|
585
|
-
return s !== void 0 && sessionVisible(s, current, archived);
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* Derive flat rows from the browser's ordered visible Session ids.
|
|
590
|
-
* @param list - sessions list snapshot used to select the ids.
|
|
591
|
-
* @param sessionIds - known visible members in render order, including any pinned blank.
|
|
592
|
-
* @param statuses - unified UI status by Session.
|
|
593
|
-
* @returns flat rows in the supplied order with current status indicators.
|
|
594
|
-
*/
|
|
595
|
-
function deriveFlat(list, sessionIds, statuses) {
|
|
596
|
-
const descendants = indexSubagentDescendants(list.byId);
|
|
597
|
-
return sessionIds.map((id) => sessionNode(list.byId[id], descendants, statuses));
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Merge immediate title/Workspace substring matches with ranked Host content
|
|
601
|
-
* matches. Local rows lead newest-first, content-only rows retain backend
|
|
602
|
-
* order, and duplicate sessions receive the backend snippet in place.
|
|
603
|
-
* @param list - session metadata authority.
|
|
604
|
-
* @param workspaces - Workspace membership and display labels.
|
|
605
|
-
* @param query - caller text; surrounding whitespace is ignored.
|
|
606
|
-
* @param archivedSessionIds - registry-global archive set (members never match).
|
|
607
|
-
* @param statuses - unified UI status by Session.
|
|
608
|
-
* @param content - ranked Host content-search page.
|
|
609
|
-
* @param limit - protocol-owned maximum merged row count.
|
|
610
|
-
* @returns bounded deduplicated flat rows and a refine-query hint bit.
|
|
611
|
-
*/
|
|
612
|
-
function deriveSearchResults(list, workspaces, query, archivedSessionIds, statuses, content, limit) {
|
|
613
|
-
const q = query.trim().toLowerCase();
|
|
614
|
-
if (q === "") return {
|
|
615
|
-
items: [],
|
|
616
|
-
hasMore: false
|
|
617
|
-
};
|
|
618
|
-
const archived = new Set(archivedSessionIds);
|
|
619
|
-
const descendants = indexSubagentDescendants(list.byId);
|
|
620
|
-
const current = mainSessionId(list);
|
|
621
|
-
const workspaceBySession = /* @__PURE__ */ new Map();
|
|
622
|
-
for (const workspace of workspaces) for (const sessionId of workspace.sessionIds) if (!workspaceBySession.has(sessionId)) workspaceBySession.set(sessionId, workspace.title);
|
|
623
|
-
const labelOf = (summary) => workspaceBySession.get(summary.id) ?? workspaceLabel(summary.cwd);
|
|
624
|
-
const contentBySession = /* @__PURE__ */ new Map();
|
|
625
|
-
for (const item of content.items) if (!contentBySession.has(item.sessionId)) contentBySession.set(item.sessionId, item);
|
|
626
|
-
const local = [];
|
|
627
|
-
for (const id of list.ids) {
|
|
628
|
-
const summary = list.byId[id];
|
|
629
|
-
if (summary === void 0 || summary.blank || !sessionVisible(summary, current, archived)) continue;
|
|
630
|
-
if (sessionTitle(summary).toLowerCase().includes(q) || labelOf(summary).toLowerCase().includes(q)) local.push(summary);
|
|
1010
|
+
clearMain() {
|
|
1011
|
+
const previous = this.mainReference;
|
|
1012
|
+
this.mainReference = void 0;
|
|
1013
|
+
this.selection.set({});
|
|
1014
|
+
previous?.release();
|
|
1015
|
+
this.ctx.layout.selectPanel(null);
|
|
631
1016
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1017
|
+
replaceMain(target, signal, panel, beforeOpen) {
|
|
1018
|
+
signal.throwIfAborted();
|
|
1019
|
+
const reference = this.sessions.retain(target, { source: "mainView" });
|
|
1020
|
+
try {
|
|
1021
|
+
signal.throwIfAborted();
|
|
1022
|
+
beforeOpen?.(reference.sessionId);
|
|
1023
|
+
if (signal.aborted) {
|
|
1024
|
+
reference.release();
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
const subagentAddress = typeof target === "string" ? this.sessions.subagentAddress(reference.sessionId) : target;
|
|
1028
|
+
this.selection.set({
|
|
1029
|
+
sessionId: reference.sessionId,
|
|
1030
|
+
...subagentAddress === void 0 ? {} : { subagentAddress }
|
|
1031
|
+
});
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
reference.release();
|
|
1034
|
+
throw error;
|
|
1035
|
+
}
|
|
1036
|
+
const previous = this.mainReference;
|
|
1037
|
+
this.mainReference = reference;
|
|
1038
|
+
previous?.release();
|
|
1039
|
+
if (panel === "reveal") this.ctx.layout.selectPanel(null);
|
|
645
1040
|
}
|
|
646
|
-
|
|
647
|
-
items: ordered.slice(0, limit).map((summary) => {
|
|
648
|
-
const match = contentBySession.get(summary.id);
|
|
649
|
-
const status = statuses.get(summary.id);
|
|
650
|
-
const pendingInteraction = visiblePendingKind(status?.pendingInteraction?.kind);
|
|
651
|
-
return {
|
|
652
|
-
id: summary.id,
|
|
653
|
-
title: sessionTitle(summary),
|
|
654
|
-
workspace: labelOf(summary),
|
|
655
|
-
running: status?.running ?? summary.running,
|
|
656
|
-
runningSubagentCount: descendants.get(summary.id)?.runningCount ?? 0,
|
|
657
|
-
...pendingInteraction === void 0 ? {} : { pendingInteraction },
|
|
658
|
-
completed: status?.completionUnread === true,
|
|
659
|
-
hasActiveSchedule: hasActiveSchedule(summary),
|
|
660
|
-
...match === void 0 ? {} : { snippet: match.snippet }
|
|
661
|
-
};
|
|
662
|
-
}),
|
|
663
|
-
hasMore: content.hasMore || ordered.length > limit
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
/** Normalize separators for comparison without interpreting POSIX backslashes as separators. */
|
|
667
|
-
function folderPath(path) {
|
|
668
|
-
return (/^[A-Za-z]:[/\\]/.test(path) || path.startsWith("\\\\") ? path.replaceAll("\\", "/") : path).replace(/\/+$/, "");
|
|
669
|
-
}
|
|
1041
|
+
};
|
|
670
1042
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
* @param parents - registered Workspace directory paths.
|
|
675
|
-
* @returns the owning parent path, or undefined when no parent contains the Workspace.
|
|
1043
|
+
* `error` as the Session Controller's creation failure, or undefined when it
|
|
1044
|
+
* is not one. Client plugin bundles do not share error-class identity, so the
|
|
1045
|
+
* name decides.
|
|
676
1046
|
*/
|
|
677
|
-
function
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1047
|
+
function sessionCreateErrorOf(error) {
|
|
1048
|
+
return error instanceof Error && error.name === "SessionCreateError" ? error : void 0;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* The words a failed Session creation is reported in: a Host refusal keeps its
|
|
1052
|
+
* stable code and message; any other failure keeps its own message.
|
|
1053
|
+
*/
|
|
1054
|
+
function creationFailureMessage(error) {
|
|
1055
|
+
const refused = sessionCreateErrorOf(error);
|
|
1056
|
+
if (refused !== void 0) return `${refused.rpcError.code}: ${refused.rpcError.message}`;
|
|
1057
|
+
return error instanceof Error ? error.message : String(error);
|
|
1058
|
+
}
|
|
1059
|
+
/** Stable tie-breaking follows Host Workspace order. */
|
|
1060
|
+
function recentWorkspace(workspaces, sessions) {
|
|
1061
|
+
let selected;
|
|
1062
|
+
let selectedTime = Number.NEGATIVE_INFINITY;
|
|
1063
|
+
for (const workspace of workspaces) {
|
|
1064
|
+
let latest = Number.NEGATIVE_INFINITY;
|
|
1065
|
+
for (const sessionId of workspace.sessionIds) {
|
|
1066
|
+
const session = sessions[sessionId];
|
|
1067
|
+
if (session !== void 0) latest = Math.max(latest, session.updatedAt);
|
|
1068
|
+
}
|
|
1069
|
+
if (latest === Number.NEGATIVE_INFINITY) latest = Date.parse(workspace.createdAt);
|
|
1070
|
+
if (selected === void 0 || latest > selectedTime) {
|
|
1071
|
+
selected = workspace.workspaceId;
|
|
1072
|
+
selectedTime = latest;
|
|
686
1073
|
}
|
|
687
1074
|
}
|
|
688
|
-
return
|
|
1075
|
+
return selected;
|
|
1076
|
+
}
|
|
1077
|
+
//#endregion
|
|
1078
|
+
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
1079
|
+
function r(e) {
|
|
1080
|
+
var t, f, n = "";
|
|
1081
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
1082
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
1083
|
+
var o = e.length;
|
|
1084
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
1085
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
1086
|
+
return n;
|
|
1087
|
+
}
|
|
1088
|
+
function clsx() {
|
|
1089
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
1090
|
+
return n;
|
|
689
1091
|
}
|
|
690
1092
|
//#endregion
|
|
691
1093
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
|
|
692
|
-
const css$
|
|
693
|
-
const tagId$
|
|
694
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
1094
|
+
const css$3 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover,.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.YDXeBa_searchResultRow:hover,.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.YDXeBa_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.YDXeBa_projectRow{box-sizing:border-box;align-items:center;height:34px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{gap:0;height:32px}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}.YDXeBa_flatSessionRowWithoutStatus .YDXeBa_title{margin-left: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:.5px solid var(--dsw-alias-border-l4);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_sessionRow .YDXeBa_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}.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-caption);flex:none;font-size:10px;line-height:16px}.YDXeBa_scheduleIndicator{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;margin-right:6px;display:inline-flex}.YDXeBa_searchScheduleIndicator{margin-left:4px;margin-right:0}.YDXeBa_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.YDXeBa_sessionRow.YDXeBa_archived .YDXeBa_title,.YDXeBa_sessionRow.YDXeBa_archived .YDXeBa_time,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultTitle,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultWorkspace,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultSnippet{color:var(--dsw-alias-label-caption)}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:10px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_searchResultRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_searchResultHeading .YDXeBa_rowActions{margin-left:auto}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time,.YDXeBa_sessionRow:hover .YDXeBa_pinIndicator,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_pinIndicator{display:none}@media (hover:hover){.YDXeBa_sessionRow:hover .YDXeBa_title,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_title{text-overflow:clip}}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore,.YDXeBa_sessionRow.YDXeBa_dropAfter{position:relative}.YDXeBa_sessionRow.YDXeBa_dropBefore:before,.YDXeBa_sessionRow.YDXeBa_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.YDXeBa_sessionRow.YDXeBa_dropBefore:before{top:-7px}.YDXeBa_sessionRow.YDXeBa_dropAfter:after{bottom:-7px}.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_hoverArchived svg{flex-shrink:0;margin:0 -4px}.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_arrow{transition:none;animation:none}}";
|
|
1095
|
+
const tagId$3 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
|
|
1096
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
695
1097
|
const tag = document.createElement("style");
|
|
696
1098
|
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
|
|
697
|
-
tag.dataset.pluginCss = tagId$
|
|
698
|
-
tag.textContent = css$
|
|
1099
|
+
tag.dataset.pluginCss = tagId$3;
|
|
1100
|
+
tag.textContent = css$3;
|
|
699
1101
|
document.head.appendChild(tag);
|
|
700
1102
|
}
|
|
701
1103
|
var Rows_module_css_default = {
|
|
1104
|
+
"archived": "YDXeBa_archived",
|
|
702
1105
|
"arrow": "YDXeBa_arrow",
|
|
703
1106
|
"arrowOpen": "YDXeBa_arrowOpen",
|
|
704
1107
|
"chevron": "YDXeBa_chevron",
|
|
@@ -708,6 +1111,7 @@ window.__ModuleLoader__.load({
|
|
|
708
1111
|
"flatSessionRowWithoutStatus": "YDXeBa_flatSessionRowWithoutStatus",
|
|
709
1112
|
"folder": "YDXeBa_folder",
|
|
710
1113
|
"folderActive": "YDXeBa_folderActive",
|
|
1114
|
+
"hoverArchived": "YDXeBa_hoverArchived",
|
|
711
1115
|
"hoverContent": "YDXeBa_hoverContent",
|
|
712
1116
|
"hoverPath": "YDXeBa_hoverPath",
|
|
713
1117
|
"hoverStatus": "YDXeBa_hoverStatus",
|
|
@@ -716,10 +1120,10 @@ window.__ModuleLoader__.load({
|
|
|
716
1120
|
"iconButton": "YDXeBa_iconButton",
|
|
717
1121
|
"menuOpen": "YDXeBa_menuOpen",
|
|
718
1122
|
"meta": "YDXeBa_meta",
|
|
1123
|
+
"pinIndicator": "YDXeBa_pinIndicator",
|
|
719
1124
|
"projectRow": "YDXeBa_projectRow",
|
|
720
1125
|
"projectText": "YDXeBa_projectText",
|
|
721
1126
|
"renameInput": "YDXeBa_renameInput",
|
|
722
|
-
"row-in": "YDXeBa_row-in",
|
|
723
1127
|
"rowActions": "YDXeBa_rowActions",
|
|
724
1128
|
"scheduleIndicator": "YDXeBa_scheduleIndicator",
|
|
725
1129
|
"searchResultHeading": "YDXeBa_searchResultHeading",
|
|
@@ -742,37 +1146,99 @@ window.__ModuleLoader__.load({
|
|
|
742
1146
|
* Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
|
|
743
1147
|
* all data and callbacks arrive via props. Hover swaps (folder->chevron,
|
|
744
1148
|
* time->ellipsis, action buttons) are CSS-only, and a session row's clipped
|
|
745
|
-
* title
|
|
746
|
-
* visual-only except
|
|
1149
|
+
* title marquees programmatically while the row is hovered. Workspace row
|
|
1150
|
+
* menus are visual-only except Rename/Delete. A Session row's "..." menu and
|
|
1151
|
+
* its hover buttons are the `sidebar.workspaces.session.menu.item` and
|
|
1152
|
+
* `sidebar.workspaces.session.row.action` lists, rendered through the
|
|
1153
|
+
* browser's `renderSlot` with the menu's open state as the occurrence's hook
|
|
1154
|
+
* context; this package's own actions are entries like any plugin's. The
|
|
747
1155
|
* session and workspace hover cards are suppressed while a menu is open.
|
|
748
1156
|
*/
|
|
749
1157
|
/** Row display title: blank rows show the localized New Session label. */
|
|
750
1158
|
function displayTitle(node, t) {
|
|
751
1159
|
return node.blank ? t("session.new") : node.title;
|
|
752
1160
|
}
|
|
1161
|
+
const MIN_TITLE_REVEAL_PX = 8;
|
|
1162
|
+
const TITLE_MARQUEE_PX_PER_MS = .03;
|
|
753
1163
|
/**
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
* to the start in one step, because the resting ellipsis and the narrowed cell
|
|
758
|
-
* would otherwise meet the text while it travelled back. A title that fits has
|
|
759
|
-
* no scroll range to move, and the stylesheet decides whether either move
|
|
760
|
-
* glides or jumps.
|
|
1164
|
+
* Place the title's scroll position and publish the stylesheet's fade-mask
|
|
1165
|
+
* hooks: `data-scrolled` while the title has left its start (left fade) and
|
|
1166
|
+
* `data-clipped` while text remains beyond the right edge (right fade).
|
|
761
1167
|
* @param title - the row's clipping title element.
|
|
762
|
-
* @param
|
|
1168
|
+
* @param left - scroll offset in CSS pixels.
|
|
1169
|
+
* @param range - the title's maximum scroll offset in CSS pixels.
|
|
763
1170
|
*/
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1171
|
+
function placeTitle(title, left, range) {
|
|
1172
|
+
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
1173
|
+
left,
|
|
1174
|
+
behavior: "instant"
|
|
1175
|
+
});
|
|
1176
|
+
else title.scrollLeft = left;
|
|
1177
|
+
if (left > 0) title.dataset.scrolled = "";
|
|
1178
|
+
else delete title.dataset.scrolled;
|
|
1179
|
+
if (left < range) title.dataset.clipped = "";
|
|
1180
|
+
else delete title.dataset.clipped;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Return the title to its resting state: scrolled to the start with both fade
|
|
1184
|
+
* masks off, so the resting ellipsis renders at full strength.
|
|
1185
|
+
* @param title - the row's clipping title element.
|
|
1186
|
+
*/
|
|
1187
|
+
function restTitle(title) {
|
|
771
1188
|
if (typeof title.scrollTo === "function") title.scrollTo({
|
|
772
1189
|
left: 0,
|
|
773
1190
|
behavior: "instant"
|
|
774
1191
|
});
|
|
775
1192
|
else title.scrollLeft = 0;
|
|
1193
|
+
delete title.dataset.scrolled;
|
|
1194
|
+
delete title.dataset.clipped;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Marquee a title wider than its one-line cell while its row is hovered: the
|
|
1198
|
+
* title clips its own text, so entering crawls it at a constant speed until the
|
|
1199
|
+
* far edge (a fork's incremented title, for example) is in view, then rests
|
|
1200
|
+
* there under the pointer. Overflow of at most {@link MIN_TITLE_REVEAL_PX}
|
|
1201
|
+
* stays put — a barely-clipped title moving a few pixels reads as jitter, not a
|
|
1202
|
+
* reveal. Leaving returns the title to the start in one step, because the
|
|
1203
|
+
* resting ellipsis and the narrowed cell would otherwise meet the text while it
|
|
1204
|
+
* travelled back. Reduced motion jumps to the far edge instead of crawling.
|
|
1205
|
+
* @param title - ref to the row's clipping title element.
|
|
1206
|
+
* @returns stable pointer enter/leave handlers for the row.
|
|
1207
|
+
*/
|
|
1208
|
+
function useTitleMarquee(title) {
|
|
1209
|
+
const frame = (0, react.useRef)(0);
|
|
1210
|
+
(0, react.useEffect)(() => () => {
|
|
1211
|
+
cancelAnimationFrame(frame.current);
|
|
1212
|
+
}, []);
|
|
1213
|
+
return (0, react.useMemo)(() => ({
|
|
1214
|
+
enter: () => {
|
|
1215
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
1216
|
+
if (title.current === null) return;
|
|
1217
|
+
const element = title.current;
|
|
1218
|
+
const range = element.scrollWidth - element.clientWidth;
|
|
1219
|
+
if (range <= MIN_TITLE_REVEAL_PX) return;
|
|
1220
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
1221
|
+
placeTitle(element, range, range);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
cancelAnimationFrame(frame.current);
|
|
1225
|
+
let previous;
|
|
1226
|
+
let position = 0;
|
|
1227
|
+
const step = (now) => {
|
|
1228
|
+
position += previous === void 0 ? 0 : (now - previous) * TITLE_MARQUEE_PX_PER_MS;
|
|
1229
|
+
previous = now;
|
|
1230
|
+
placeTitle(element, Math.min(position, range), range);
|
|
1231
|
+
if (position < range) frame.current = requestAnimationFrame(step);
|
|
1232
|
+
};
|
|
1233
|
+
frame.current = requestAnimationFrame(step);
|
|
1234
|
+
},
|
|
1235
|
+
leave: () => {
|
|
1236
|
+
cancelAnimationFrame(frame.current);
|
|
1237
|
+
/* v8 ignore next -- defensive: the title span renders unconditionally. */
|
|
1238
|
+
if (title.current === null) return;
|
|
1239
|
+
restTitle(title.current);
|
|
1240
|
+
}
|
|
1241
|
+
}), [title]);
|
|
776
1242
|
}
|
|
777
1243
|
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
778
1244
|
function timeLabel(updatedAt, now, t) {
|
|
@@ -845,15 +1311,16 @@ window.__ModuleLoader__.load({
|
|
|
845
1311
|
const workspaceMenuItems = [{
|
|
846
1312
|
id: "rename",
|
|
847
1313
|
label: t("rename"),
|
|
848
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1314
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {})
|
|
849
1315
|
}, {
|
|
850
1316
|
id: "delete",
|
|
851
1317
|
label: t("delete.workspace"),
|
|
852
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1318
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutlineRegular, {}),
|
|
853
1319
|
danger: true
|
|
854
1320
|
}];
|
|
855
1321
|
const ownRow = (0, react_jsx_runtime.jsxs)("div", {
|
|
856
1322
|
className: clsx(Rows_module_css_default.projectRow, menuOpen && Rows_module_css_default.menuOpen),
|
|
1323
|
+
"data-row-key": `workspace:${group.key}`,
|
|
857
1324
|
role: "treeitem",
|
|
858
1325
|
"aria-expanded": row.expanded,
|
|
859
1326
|
onClick: onToggle,
|
|
@@ -867,11 +1334,11 @@ window.__ModuleLoader__.load({
|
|
|
867
1334
|
children: [
|
|
868
1335
|
(0, react_jsx_runtime.jsx)("span", {
|
|
869
1336
|
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
|
|
870
|
-
children: row.expanded ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1337
|
+
children: row.expanded ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenRegular, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
871
1338
|
}),
|
|
872
1339
|
(0, react_jsx_runtime.jsx)("span", {
|
|
873
1340
|
className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
|
|
874
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1341
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFillRegular, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
|
|
875
1342
|
}),
|
|
876
1343
|
(0, react_jsx_runtime.jsx)("span", {
|
|
877
1344
|
className: Rows_module_css_default.projectText,
|
|
@@ -905,17 +1372,23 @@ window.__ModuleLoader__.load({
|
|
|
905
1372
|
e.stopPropagation();
|
|
906
1373
|
setMenuOpen((v) => !v);
|
|
907
1374
|
},
|
|
908
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1375
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
1376
|
+
})
|
|
1377
|
+
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1378
|
+
label: t("actions.newSession"),
|
|
1379
|
+
side: "bottom",
|
|
1380
|
+
align: "end",
|
|
1381
|
+
delayMs: 500,
|
|
1382
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
1383
|
+
type: "button",
|
|
1384
|
+
className: Rows_module_css_default.iconButton,
|
|
1385
|
+
"aria-label": t("actions.newSession.aria", { name: label }),
|
|
1386
|
+
onClick: (e) => {
|
|
1387
|
+
e.stopPropagation();
|
|
1388
|
+
onCreate();
|
|
1389
|
+
},
|
|
1390
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconNewChatOutlineRegular, {})
|
|
909
1391
|
})
|
|
910
|
-
}), (0, react_jsx_runtime.jsx)("button", {
|
|
911
|
-
type: "button",
|
|
912
|
-
className: Rows_module_css_default.iconButton,
|
|
913
|
-
"aria-label": t("actions.newSession.aria", { name: label }),
|
|
914
|
-
onClick: (e) => {
|
|
915
|
-
e.stopPropagation();
|
|
916
|
-
onCreate();
|
|
917
|
-
},
|
|
918
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, {})
|
|
919
1392
|
})]
|
|
920
1393
|
})
|
|
921
1394
|
]
|
|
@@ -929,6 +1402,7 @@ window.__ModuleLoader__.load({
|
|
|
929
1402
|
createdAt: row.createdAt,
|
|
930
1403
|
t
|
|
931
1404
|
}),
|
|
1405
|
+
openDelayMs: 800,
|
|
932
1406
|
disabled: menuOpen,
|
|
933
1407
|
copyText: row.cwd,
|
|
934
1408
|
copyLabel: t("copy"),
|
|
@@ -953,19 +1427,22 @@ window.__ModuleLoader__.load({
|
|
|
953
1427
|
case "approval":
|
|
954
1428
|
pending = {
|
|
955
1429
|
state: "warning",
|
|
956
|
-
label: t("status.waitingApproval")
|
|
1430
|
+
label: t("status.waitingApproval"),
|
|
1431
|
+
trailingLabel: t("status.compact.approval")
|
|
957
1432
|
};
|
|
958
1433
|
break;
|
|
959
1434
|
case "plan-review":
|
|
960
1435
|
pending = {
|
|
961
1436
|
state: "warning",
|
|
962
|
-
label: t("status.planReview")
|
|
1437
|
+
label: t("status.planReview"),
|
|
1438
|
+
trailingLabel: t("status.compact.planReview")
|
|
963
1439
|
};
|
|
964
1440
|
break;
|
|
965
1441
|
case "question":
|
|
966
1442
|
pending = {
|
|
967
1443
|
state: "warning",
|
|
968
|
-
label: t("status.waitingAnswer")
|
|
1444
|
+
label: t("status.waitingAnswer"),
|
|
1445
|
+
trailingLabel: t("status.compact.answer")
|
|
969
1446
|
};
|
|
970
1447
|
break;
|
|
971
1448
|
case void 0: break;
|
|
@@ -986,7 +1463,7 @@ window.__ModuleLoader__.load({
|
|
|
986
1463
|
label: t("status.completed")
|
|
987
1464
|
}];
|
|
988
1465
|
return [{
|
|
989
|
-
state: "
|
|
1466
|
+
state: "idle",
|
|
990
1467
|
label: t("status.idle")
|
|
991
1468
|
}];
|
|
992
1469
|
}
|
|
@@ -1005,12 +1482,23 @@ window.__ModuleLoader__.load({
|
|
|
1005
1482
|
role: "img",
|
|
1006
1483
|
"aria-label": label,
|
|
1007
1484
|
title: label,
|
|
1008
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1485
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconAlarmClockOutlineRegular, {})
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
/** Non-interactive pinned-row marker; the enclosing row remains the only action. */
|
|
1489
|
+
function PinnedIndicator({ t }) {
|
|
1490
|
+
const label = t("row.pinned");
|
|
1491
|
+
return (0, react_jsx_runtime.jsx)("span", {
|
|
1492
|
+
className: Rows_module_css_default.pinIndicator,
|
|
1493
|
+
role: "img",
|
|
1494
|
+
"aria-label": label,
|
|
1495
|
+
title: label,
|
|
1496
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinFillRegular, { size: 14 })
|
|
1009
1497
|
});
|
|
1010
1498
|
}
|
|
1011
1499
|
/** Hover-card body: full title, relative time, and every relevant live status. */
|
|
1012
1500
|
function SessionHoverContent({ node, now, t }) {
|
|
1013
|
-
const statuses = sessionStatuses(node, t);
|
|
1501
|
+
const statuses = sessionStatuses(node, t).filter((status) => !(node.archived && (status.state === "done" || status.state === "idle")));
|
|
1014
1502
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1015
1503
|
className: Rows_module_css_default.hoverContent,
|
|
1016
1504
|
children: [
|
|
@@ -1025,29 +1513,35 @@ window.__ModuleLoader__.load({
|
|
|
1025
1513
|
statuses.map((status) => (0, react_jsx_runtime.jsxs)("div", {
|
|
1026
1514
|
className: Rows_module_css_default.hoverStatus,
|
|
1027
1515
|
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 })]
|
|
1028
|
-
}, status.label))
|
|
1516
|
+
}, status.label)),
|
|
1517
|
+
node.archived && (0, react_jsx_runtime.jsxs)("div", {
|
|
1518
|
+
className: clsx(Rows_module_css_default.hoverStatus, Rows_module_css_default.hoverArchived),
|
|
1519
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("row.archived") })]
|
|
1520
|
+
})
|
|
1029
1521
|
]
|
|
1030
1522
|
});
|
|
1031
1523
|
}
|
|
1032
1524
|
/**
|
|
1033
1525
|
* One flat search result: title, Workspace context, and optional content
|
|
1034
1526
|
* excerpt. Search navigation opens the session only; it does not address an
|
|
1035
|
-
* event inside the conversation.
|
|
1527
|
+
* event inside the conversation. Archived rows carry a hover unarchive
|
|
1528
|
+
* button, because search is where the filter surfaces them for recovery.
|
|
1036
1529
|
* @param props.result - merged local/content search row.
|
|
1037
1530
|
* @param props.currentId - selected session id.
|
|
1038
1531
|
* @param props.onOpen - open the selected session.
|
|
1532
|
+
* @param props.onUnarchive - unarchive an archived result row.
|
|
1039
1533
|
* @param props.t - Workspace-browser translation seat.
|
|
1040
|
-
* @returns the result
|
|
1534
|
+
* @returns the result row.
|
|
1041
1535
|
*/
|
|
1042
|
-
function SearchResultItem({ result, currentId, onOpen, t }) {
|
|
1536
|
+
function SearchResultItem({ result, currentId, onOpen, onUnarchive, t }) {
|
|
1043
1537
|
const selected = result.id === currentId;
|
|
1044
1538
|
const statuses = sessionStatuses(result, t);
|
|
1045
1539
|
const primaryStatus = statuses[0];
|
|
1046
|
-
return (0, react_jsx_runtime.jsxs)("
|
|
1047
|
-
|
|
1048
|
-
className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected),
|
|
1540
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1541
|
+
className: clsx(Rows_module_css_default.searchResultRow, selected && Rows_module_css_default.selected, result.archived && Rows_module_css_default.archived),
|
|
1049
1542
|
role: "treeitem",
|
|
1050
1543
|
"aria-selected": selected,
|
|
1544
|
+
"aria-description": result.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
1051
1545
|
onClick: () => {
|
|
1052
1546
|
onOpen(result.id);
|
|
1053
1547
|
},
|
|
@@ -1056,7 +1550,7 @@ window.__ModuleLoader__.load({
|
|
|
1056
1550
|
children: [
|
|
1057
1551
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1058
1552
|
className: Rows_module_css_default.slot,
|
|
1059
|
-
children:
|
|
1553
|
+
children: !result.archived && primaryStatus.state !== "idle" && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
|
|
1060
1554
|
}),
|
|
1061
1555
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1062
1556
|
className: Rows_module_css_default.searchResultTitle,
|
|
@@ -1065,6 +1559,25 @@ window.__ModuleLoader__.load({
|
|
|
1065
1559
|
result.hasActiveSchedule && (0, react_jsx_runtime.jsx)(ActiveScheduleIndicator, {
|
|
1066
1560
|
t,
|
|
1067
1561
|
search: true
|
|
1562
|
+
}),
|
|
1563
|
+
result.archived && (0, react_jsx_runtime.jsx)("span", {
|
|
1564
|
+
className: Rows_module_css_default.rowActions,
|
|
1565
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1566
|
+
label: t("actions.unarchive"),
|
|
1567
|
+
side: "bottom",
|
|
1568
|
+
align: "end",
|
|
1569
|
+
delayMs: 500,
|
|
1570
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
1571
|
+
type: "button",
|
|
1572
|
+
className: Rows_module_css_default.iconButton,
|
|
1573
|
+
"aria-label": t("menu.unarchiveSession"),
|
|
1574
|
+
onClick: (e) => {
|
|
1575
|
+
e.stopPropagation();
|
|
1576
|
+
onUnarchive(result.id);
|
|
1577
|
+
},
|
|
1578
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUnarchiveOutlineRegular, { size: 14 })
|
|
1579
|
+
})
|
|
1580
|
+
})
|
|
1068
1581
|
})
|
|
1069
1582
|
]
|
|
1070
1583
|
}), (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -1081,74 +1594,58 @@ window.__ModuleLoader__.load({
|
|
|
1081
1594
|
}
|
|
1082
1595
|
/**
|
|
1083
1596
|
* One top-level 34px session row: status dot (pending user interaction outranks
|
|
1084
|
-
* own or descendant activity), title, relative time
|
|
1597
|
+
* own or descendant activity), title, relative time or compact pending label,
|
|
1598
|
+
* and the row actions menu.
|
|
1085
1599
|
* @param props.node - derived session node.
|
|
1086
1600
|
* @param props.currentId - selected session id (row highlight).
|
|
1087
1601
|
* @param props.now - epoch ms for relative-time formatting.
|
|
1088
1602
|
* @param props.onOpen - open a session by id.
|
|
1089
|
-
* @param props.
|
|
1090
|
-
* @param props.
|
|
1091
|
-
* @param props.onArchive - archive a session by id.
|
|
1603
|
+
* @param props.onRenameRequest - open the rename dialog from a title double-click (id + current title).
|
|
1604
|
+
* @param props.renderSlot - render the row's `sidebar.workspaces.session.menu.item` and `sidebar.workspaces.session.row.action` lists.
|
|
1092
1605
|
* @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
|
|
1093
1606
|
* @param props.drag - optional row-drag target wiring; blank rows cannot start a drag.
|
|
1094
1607
|
* @param props.flat - omit the empty status slot in the hierarchy-free flat list.
|
|
1095
1608
|
* @param props.t - the browser root's locale seat.
|
|
1096
1609
|
* @returns the session row.
|
|
1097
1610
|
*/
|
|
1098
|
-
function SessionNodeItem({ node, currentId, now, onOpen,
|
|
1611
|
+
function SessionNodeItem({ node, currentId, now, onOpen, onRenameRequest, renderSlot, onReveal, drag, flat = false, t }) {
|
|
1099
1612
|
const row = node;
|
|
1100
1613
|
const title = displayTitle(node, t);
|
|
1101
1614
|
const selected = node.id === currentId;
|
|
1102
1615
|
const statuses = sessionStatuses(node, t);
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
1616
|
+
const primaryStatus = statuses[0];
|
|
1617
|
+
const showStatus = primaryStatus.state !== "idle";
|
|
1618
|
+
const draggable = drag !== void 0 && !row.blank && !row.archived;
|
|
1105
1619
|
const [menuOpen, setMenuOpen] = (0, react.useState)(false);
|
|
1620
|
+
const menuOpenState = (0, react.useMemo)(() => [menuOpen, setMenuOpen], [menuOpen]);
|
|
1106
1621
|
const rowRef = (0, react.useRef)(null);
|
|
1107
1622
|
const titleRef = (0, react.useRef)(null);
|
|
1623
|
+
const marquee = useTitleMarquee(titleRef);
|
|
1108
1624
|
(0, react.useEffect)(() => {
|
|
1109
1625
|
if (onReveal === void 0) return;
|
|
1110
1626
|
rowRef.current?.scrollIntoView({ block: "nearest" });
|
|
1111
1627
|
onReveal();
|
|
1112
1628
|
}, [onReveal]);
|
|
1113
|
-
const sessionMenuItems = [
|
|
1114
|
-
{
|
|
1115
|
-
id: "rename",
|
|
1116
|
-
label: t("rename"),
|
|
1117
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
|
|
1118
|
-
},
|
|
1119
|
-
{
|
|
1120
|
-
id: "fork",
|
|
1121
|
-
label: t("menu.fork"),
|
|
1122
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
|
|
1123
|
-
},
|
|
1124
|
-
{
|
|
1125
|
-
id: "archive",
|
|
1126
|
-
label: t("menu.archiveSession"),
|
|
1127
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
|
|
1128
|
-
}
|
|
1129
|
-
];
|
|
1130
1629
|
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
|
|
1131
1630
|
anchor: (0, react_jsx_runtime.jsxs)("div", {
|
|
1132
1631
|
ref: rowRef,
|
|
1133
|
-
|
|
1632
|
+
"data-row-key": `session:${node.id}`,
|
|
1633
|
+
className: clsx(Rows_module_css_default.sessionRow, selected && Rows_module_css_default.selected, menuOpen && Rows_module_css_default.menuOpen, row.archived && Rows_module_css_default.archived, flat && !showStatus && Rows_module_css_default.flatSessionRowWithoutStatus, drag?.marker === "before" && Rows_module_css_default.dropBefore, drag?.marker === "after" && Rows_module_css_default.dropAfter),
|
|
1134
1634
|
role: "treeitem",
|
|
1135
1635
|
"aria-selected": selected,
|
|
1636
|
+
"aria-description": row.archived ? t("toast.archivedNotOpenable") : void 0,
|
|
1136
1637
|
onClick: () => {
|
|
1137
1638
|
onOpen(node.id);
|
|
1138
1639
|
},
|
|
1139
|
-
onPointerEnter:
|
|
1140
|
-
|
|
1141
|
-
},
|
|
1142
|
-
onPointerLeave: () => {
|
|
1143
|
-
revealClippedTitle(titleRef.current, false);
|
|
1144
|
-
},
|
|
1640
|
+
onPointerEnter: marquee.enter,
|
|
1641
|
+
onPointerLeave: marquee.leave,
|
|
1145
1642
|
draggable,
|
|
1146
|
-
onDragStart:
|
|
1643
|
+
onDragStart: !draggable ? void 0 : (e) => {
|
|
1147
1644
|
e.dataTransfer.effectAllowed = "move";
|
|
1148
1645
|
e.dataTransfer.setData("text/plain", node.id);
|
|
1149
1646
|
drag.start();
|
|
1150
1647
|
},
|
|
1151
|
-
onDragEnd:
|
|
1648
|
+
onDragEnd: !draggable ? void 0 : drag.end,
|
|
1152
1649
|
onDragOver: drag === void 0 ? void 0 : (e) => {
|
|
1153
1650
|
if (!drag.active) return;
|
|
1154
1651
|
e.preventDefault();
|
|
@@ -1163,45 +1660,53 @@ window.__ModuleLoader__.load({
|
|
|
1163
1660
|
children: [
|
|
1164
1661
|
(!flat || showStatus) && (0, react_jsx_runtime.jsx)("span", {
|
|
1165
1662
|
className: Rows_module_css_default.slot,
|
|
1166
|
-
children: showStatus && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
|
|
1663
|
+
children: !row.archived && showStatus && (0, react_jsx_runtime.jsx)(SessionStatusDots, { statuses })
|
|
1167
1664
|
}),
|
|
1168
1665
|
(0, react_jsx_runtime.jsx)("span", {
|
|
1169
1666
|
ref: titleRef,
|
|
1170
1667
|
className: Rows_module_css_default.title,
|
|
1668
|
+
onDoubleClick: row.blank ? void 0 : (e) => {
|
|
1669
|
+
e.stopPropagation();
|
|
1670
|
+
onRenameRequest(node.id, row.title);
|
|
1671
|
+
},
|
|
1171
1672
|
children: title
|
|
1172
1673
|
}),
|
|
1173
1674
|
row.hasActiveSchedule && (0, react_jsx_runtime.jsx)(ActiveScheduleIndicator, { t }),
|
|
1174
1675
|
!row.blank && (0, react_jsx_runtime.jsx)("span", {
|
|
1175
1676
|
className: Rows_module_css_default.time,
|
|
1176
|
-
|
|
1677
|
+
"aria-hidden": primaryStatus.trailingLabel === void 0 ? void 0 : true,
|
|
1678
|
+
children: primaryStatus.trailingLabel ?? timeLabel(row.updatedAt, now, t)
|
|
1177
1679
|
}),
|
|
1178
|
-
!row.
|
|
1680
|
+
row.pinned && !row.archived && (0, react_jsx_runtime.jsx)(PinnedIndicator, { t }),
|
|
1681
|
+
!row.blank && (0, react_jsx_runtime.jsxs)("span", {
|
|
1179
1682
|
className: Rows_module_css_default.rowActions,
|
|
1180
|
-
|
|
1683
|
+
onClick: (e) => {
|
|
1684
|
+
e.stopPropagation();
|
|
1685
|
+
},
|
|
1686
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1181
1687
|
open: menuOpen,
|
|
1182
1688
|
onClose: () => {
|
|
1183
1689
|
setMenuOpen(false);
|
|
1184
1690
|
},
|
|
1185
|
-
items: sessionMenuItems,
|
|
1186
|
-
onSelect: (id) => {
|
|
1187
|
-
setMenuOpen(false);
|
|
1188
|
-
if (id === "rename") onRename(node.id, row.title);
|
|
1189
|
-
if (id === "fork") onFork(node.id);
|
|
1190
|
-
if (id === "archive") onArchive(node.id);
|
|
1191
|
-
},
|
|
1192
1691
|
portal: true,
|
|
1193
1692
|
closeOnPointerLeave: true,
|
|
1194
1693
|
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
1195
1694
|
type: "button",
|
|
1196
1695
|
className: Rows_module_css_default.iconButton,
|
|
1197
1696
|
"aria-label": t("actions.session.aria", { name: title }),
|
|
1198
|
-
onClick: (
|
|
1199
|
-
e.stopPropagation();
|
|
1697
|
+
onClick: () => {
|
|
1200
1698
|
setMenuOpen((v) => !v);
|
|
1201
1699
|
},
|
|
1202
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1203
|
-
})
|
|
1204
|
-
|
|
1700
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutlineRegular, {})
|
|
1701
|
+
}),
|
|
1702
|
+
children: renderSlot("sidebar.workspaces.session.menu.item", {
|
|
1703
|
+
sessionId: node.id,
|
|
1704
|
+
displayTitle: row.title
|
|
1705
|
+
}, { hookContext: menuOpenState })
|
|
1706
|
+
}), renderSlot("sidebar.workspaces.session.row.action", {
|
|
1707
|
+
sessionId: node.id,
|
|
1708
|
+
displayTitle: row.title
|
|
1709
|
+
})]
|
|
1205
1710
|
})
|
|
1206
1711
|
]
|
|
1207
1712
|
}),
|
|
@@ -1210,6 +1715,7 @@ window.__ModuleLoader__.load({
|
|
|
1210
1715
|
now,
|
|
1211
1716
|
t
|
|
1212
1717
|
}),
|
|
1718
|
+
openDelayMs: 800,
|
|
1213
1719
|
disabled: menuOpen || drag?.active === true,
|
|
1214
1720
|
copyText: row.blank ? void 0 : row.title,
|
|
1215
1721
|
copyLabel: t("copy"),
|
|
@@ -1217,6 +1723,182 @@ window.__ModuleLoader__.load({
|
|
|
1217
1723
|
});
|
|
1218
1724
|
}
|
|
1219
1725
|
//#endregion
|
|
1726
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/AnimatedRows.module.css.mjs
|
|
1727
|
+
const css$2 = ".Qg_5Eq_exits{contain:strict;pointer-events:none;position:absolute;inset:0;overflow:clip}";
|
|
1728
|
+
const tagId$2 = "@deepseek-ai/dsh-client-ui-workspace/AnimatedRows.module.css";
|
|
1729
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
1730
|
+
const tag = document.createElement("style");
|
|
1731
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-workspace";
|
|
1732
|
+
tag.dataset.pluginCss = tagId$2;
|
|
1733
|
+
tag.textContent = css$2;
|
|
1734
|
+
document.head.appendChild(tag);
|
|
1735
|
+
}
|
|
1736
|
+
var AnimatedRows_module_css_default = { "exits": "Qg_5Eq_exits" };
|
|
1737
|
+
//#endregion
|
|
1738
|
+
//#region lib/types/client/rows/AnimatedRows.js
|
|
1739
|
+
/** React-commit-driven movement and entry/exit fades for the sidebar's keyed rows. */
|
|
1740
|
+
const ROW_FADE_MS = 100;
|
|
1741
|
+
const ROW_GLIDE_MS = 200;
|
|
1742
|
+
function sameRows(previous, next) {
|
|
1743
|
+
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
1744
|
+
}
|
|
1745
|
+
function intersects(row, viewport) {
|
|
1746
|
+
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Animates keyed sidebar rows only when their rendered membership or order changes.
|
|
1750
|
+
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
1751
|
+
* The parent supplies a positioned container for the inert exit overlay.
|
|
1752
|
+
*/
|
|
1753
|
+
var AnimatedRows = class extends react.Component {
|
|
1754
|
+
armed = false;
|
|
1755
|
+
list = (0, react.createRef)();
|
|
1756
|
+
overlay = (0, react.createRef)();
|
|
1757
|
+
movements = /* @__PURE__ */ new Map();
|
|
1758
|
+
exits = /* @__PURE__ */ new Map();
|
|
1759
|
+
getSnapshotBeforeUpdate(previous) {
|
|
1760
|
+
const list = this.list.current;
|
|
1761
|
+
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return null;
|
|
1762
|
+
const viewport = list.getBoundingClientRect();
|
|
1763
|
+
const positions = this.readPositions();
|
|
1764
|
+
const nextKeys = new Set(this.props.rowKeys);
|
|
1765
|
+
const removed = /* @__PURE__ */ new Map();
|
|
1766
|
+
for (const [key, row] of positions) {
|
|
1767
|
+
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
1768
|
+
const clone = row.element.cloneNode(true);
|
|
1769
|
+
clone.removeAttribute("data-row-key");
|
|
1770
|
+
clone.inert = true;
|
|
1771
|
+
clone.style.setProperty("--dsh-workspace-indent", getComputedStyle(row.element).getPropertyValue("--dsh-workspace-indent"));
|
|
1772
|
+
removed.set(key, {
|
|
1773
|
+
...row,
|
|
1774
|
+
element: clone
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
return {
|
|
1778
|
+
positions,
|
|
1779
|
+
removed
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
componentDidUpdate(previous, _state, snapshot) {
|
|
1783
|
+
if (snapshot === null) {
|
|
1784
|
+
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
this.cancelMovements();
|
|
1788
|
+
const list = this.list.current;
|
|
1789
|
+
const overlay = this.overlay.current;
|
|
1790
|
+
const viewport = list.getBoundingClientRect();
|
|
1791
|
+
const origin = overlay.getBoundingClientRect();
|
|
1792
|
+
const positions = this.readPositions();
|
|
1793
|
+
for (const [key, row] of positions) {
|
|
1794
|
+
this.removeExit(key);
|
|
1795
|
+
const previousRow = snapshot.positions.get(key);
|
|
1796
|
+
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
1797
|
+
if (previousRow === void 0) {
|
|
1798
|
+
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
const dx = previousRow.rect.left - row.rect.left;
|
|
1802
|
+
const dy = previousRow.rect.top - row.rect.top;
|
|
1803
|
+
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
1804
|
+
this.move(row.element, [{
|
|
1805
|
+
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
1806
|
+
opacity: previousRow.opacity
|
|
1807
|
+
}, {
|
|
1808
|
+
transform: "translate(0, 0)",
|
|
1809
|
+
opacity: 1
|
|
1810
|
+
}], ROW_GLIDE_MS);
|
|
1811
|
+
}
|
|
1812
|
+
for (const [key, row] of snapshot.removed) {
|
|
1813
|
+
const { element } = row;
|
|
1814
|
+
this.removeExit(key);
|
|
1815
|
+
Object.assign(element.style, {
|
|
1816
|
+
position: "absolute",
|
|
1817
|
+
margin: "0",
|
|
1818
|
+
transform: "none",
|
|
1819
|
+
boxSizing: "border-box",
|
|
1820
|
+
left: `${String(row.rect.left - origin.left)}px`,
|
|
1821
|
+
top: `${String(row.rect.top - origin.top)}px`,
|
|
1822
|
+
width: `${String(row.rect.width)}px`,
|
|
1823
|
+
height: `${String(row.rect.height)}px`
|
|
1824
|
+
});
|
|
1825
|
+
overlay.append(element);
|
|
1826
|
+
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
1827
|
+
duration: ROW_FADE_MS,
|
|
1828
|
+
easing: "ease-out",
|
|
1829
|
+
fill: "forwards"
|
|
1830
|
+
});
|
|
1831
|
+
this.exits.set(key, {
|
|
1832
|
+
element,
|
|
1833
|
+
animation
|
|
1834
|
+
});
|
|
1835
|
+
animation.onfinish = () => {
|
|
1836
|
+
this.removeExit(key);
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
componentWillUnmount() {
|
|
1841
|
+
this.clear();
|
|
1842
|
+
}
|
|
1843
|
+
readPositions() {
|
|
1844
|
+
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
1845
|
+
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
1846
|
+
element,
|
|
1847
|
+
rect: element.getBoundingClientRect(),
|
|
1848
|
+
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
1849
|
+
}]));
|
|
1850
|
+
}
|
|
1851
|
+
move(element, keyframes, duration) {
|
|
1852
|
+
const animation = element.animate(keyframes, {
|
|
1853
|
+
duration,
|
|
1854
|
+
easing: "ease-out"
|
|
1855
|
+
});
|
|
1856
|
+
this.movements.set(element, animation);
|
|
1857
|
+
animation.onfinish = () => {
|
|
1858
|
+
this.movements.delete(element);
|
|
1859
|
+
animation.cancel();
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
cancelMovements() {
|
|
1863
|
+
for (const animation of this.movements.values()) {
|
|
1864
|
+
animation.onfinish = null;
|
|
1865
|
+
animation.cancel();
|
|
1866
|
+
}
|
|
1867
|
+
this.movements.clear();
|
|
1868
|
+
}
|
|
1869
|
+
removeExit(key) {
|
|
1870
|
+
const exit = this.exits.get(key);
|
|
1871
|
+
if (exit === void 0) return;
|
|
1872
|
+
exit.animation.onfinish = null;
|
|
1873
|
+
exit.animation.cancel();
|
|
1874
|
+
exit.element.remove();
|
|
1875
|
+
this.exits.delete(key);
|
|
1876
|
+
}
|
|
1877
|
+
clear() {
|
|
1878
|
+
this.cancelMovements();
|
|
1879
|
+
for (const key of this.exits.keys()) this.removeExit(key);
|
|
1880
|
+
}
|
|
1881
|
+
render() {
|
|
1882
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1883
|
+
ref: this.list,
|
|
1884
|
+
className: this.props.className,
|
|
1885
|
+
role: "tree",
|
|
1886
|
+
"aria-label": this.props.label,
|
|
1887
|
+
onPointerDownCapture: () => {
|
|
1888
|
+
this.armed = true;
|
|
1889
|
+
},
|
|
1890
|
+
onKeyDownCapture: () => {
|
|
1891
|
+
this.armed = true;
|
|
1892
|
+
},
|
|
1893
|
+
children: this.props.children
|
|
1894
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
1895
|
+
ref: this.overlay,
|
|
1896
|
+
className: AnimatedRows_module_css_default.exits,
|
|
1897
|
+
"aria-hidden": "true"
|
|
1898
|
+
})] });
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
//#endregion
|
|
1220
1902
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/WorkspacePicker.module.css.mjs
|
|
1221
1903
|
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)}";
|
|
1222
1904
|
const tagId$1 = "@deepseek-ai/dsh-client-ui-workspace/WorkspacePicker.module.css";
|
|
@@ -1256,14 +1938,14 @@ window.__ModuleLoader__.load({
|
|
|
1256
1938
|
const addEntries = flowAvailable ? [{
|
|
1257
1939
|
id: ADD_WORKSPACE,
|
|
1258
1940
|
label: t("menu.addWorkspace"),
|
|
1259
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1941
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
|
|
1260
1942
|
disabled: flowBusy
|
|
1261
1943
|
}] : [];
|
|
1262
1944
|
const pinAdd = !addOnly && workspaces.length > 0;
|
|
1263
1945
|
const items = pinAdd ? workspaces.map((workspace) => ({
|
|
1264
1946
|
id: workspace.workspaceId,
|
|
1265
1947
|
label: workspace.title,
|
|
1266
|
-
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1948
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
|
|
1267
1949
|
disabled: flowBusy
|
|
1268
1950
|
})) : addEntries;
|
|
1269
1951
|
const menuIsEmpty = items.length === 0;
|
|
@@ -1388,7 +2070,7 @@ window.__ModuleLoader__.load({
|
|
|
1388
2070
|
}
|
|
1389
2071
|
//#endregion
|
|
1390
2072
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.module.css.mjs
|
|
1391
|
-
const css = ".bhn1Oq_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:
|
|
2073
|
+
const css = ".bhn1Oq_root{--dsh-session-list-edge-inset:var(--dsh-sidebar-inline-padding);--dsh-session-list-scrollbar-width:5px;--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}.bhn1Oq_root.bhn1Oq_rail{padding-right:0}.bhn1Oq_iconButton{corner-shape:round;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}.bhn1Oq_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_viewOptionsMenu{min-width:200px}.bhn1Oq_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:4px;display:flex;overflow:hidden}.bhn1Oq_root:not(.bhn1Oq_rail) .bhn1Oq_sectionHeader{margin-top:2px;margin-right:-4px}.bhn1Oq_sectionLabel{white-space:nowrap;opacity:1;visibility:visible;min-width:0;max-width:45%;transition:max-width .18s var(--ds-ease-in-out), margin-right .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;line-height:20px;overflow:hidden}.bhn1Oq_sectionLabelHidden{opacity:0;visibility:hidden;max-width:0;margin-right:-4px;transition-delay:0s,0s,0s,0s,.18s;transform:translate(-4px)}.bhn1Oq_searchSlot{box-sizing:border-box;min-width:0;max-width:28px;transition:max-width .18s var(--ds-ease-in-out), padding-left .18s var(--ds-ease-in-out);flex:1;align-items:center;margin-left:auto;padding-left:0;display:flex}.bhn1Oq_searchSlotExpanded{max-width:100%;padding-left:0}.bhn1Oq_headerActions{opacity:1;visibility:visible;max-width:60px;transition:max-width .18s var(--ds-ease-in-out), opacity .12s var(--ds-ease-in-out), transform .18s var(--ds-ease-in-out), visibility 0s linear;flex:none;align-items:center;gap:4px;display:flex;overflow:hidden}.bhn1Oq_headerActionsHidden{opacity:0;visibility:hidden;pointer-events:none;max-width:0;transition-delay:0s,0s,0s,.18s;transform:translate(4px)}.bhn1Oq_search{box-sizing:border-box;corner-shape:round;cursor:text;width:100%;height:28px;color:var(--dsw-alias-label-secondary);transition:width .18s var(--ds-ease-in-out), padding .18s var(--ds-ease-in-out), border-color .18s var(--ds-ease-in-out), background-color .18s var(--ds-ease-in-out);background:0 0;border:none;border-radius:50%;flex:none;align-items:center;gap:0;margin:0;padding:0;display:flex;overflow:hidden}.bhn1Oq_searchExpanded{border:.5px solid var(--dsw-alias-border-l4);width:calc(100% + 4px);height:30px;color:var(--dsw-alias-label-caption);background:0 0;border-radius:10px;margin-inline:-2px;padding:0 4px 0 0}.bhn1Oq_searchButton{corner-shape:round;cursor:pointer;width:28px;height:28px;color:inherit;background:0 0;border:none;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.bhn1Oq_searchExpanded .bhn1Oq_searchButton{width:28px;height:30px}.bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_searchExpanded .bhn1Oq_searchButton:hover{background:0 0}.bhn1Oq_searchInput{opacity:0;pointer-events:none;width:0;min-width:0;color:var(--dsw-alias-label-primary);transition:opacity .12s var(--ds-ease-in-out);background:0 0;border:none;outline:none;flex:1;font-size:13px;line-height:18px}.bhn1Oq_searchExpanded .bhn1Oq_searchInput{opacity:1;pointer-events:auto;margin-left:-2px}.bhn1Oq_searchInput::placeholder{color:var(--dsw-alias-label-tertiary)}.bhn1Oq_clearButton{corner-shape:round;cursor:pointer;width:24px;height:24px;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}.bhn1Oq_clearButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_rail .bhn1Oq_sectionHeader{justify-content:flex-start;gap:0;margin-bottom:12px;padding-left:0}.bhn1Oq_rail .bhn1Oq_headerActions{max-width:none}.bhn1Oq_rail .bhn1Oq_iconButton{width:36px;height:36px;color:var(--dsw-alias-label-primary);border-radius:12px}.bhn1Oq_rail .bhn1Oq_search{background:0 0;border-color:#0000;border-radius:12px;gap:0;width:36px;height:36px;margin:0 0 12px;padding:0}.bhn1Oq_rail .bhn1Oq_searchButton{width:36px;height:36px;color:var(--dsw-alias-label-primary);border-radius:12px}.bhn1Oq_rail .bhn1Oq_searchButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.bhn1Oq_listArea{min-height:0;margin-left:-4px;margin-right:calc(-1 * var(--dsh-session-list-edge-inset));flex-direction:column;flex:1;padding-left:4px;display:flex;overflow:visible}.bhn1Oq_rail .bhn1Oq_listArea{margin-left:0;margin-right:0;padding-left:0}.bhn1Oq_treeBody{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.bhn1Oq_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:24px;position:absolute;bottom:0}[data-platform=darwin] .bhn1Oq_fade{display:none}.bhn1Oq_wide{animation:bhn1Oq_wide-in .2s var(--ds-ease-in-out)}@keyframes bhn1Oq_wide-in{0%{opacity:0}}.bhn1Oq_list{min-height:0;margin-left:-4px;margin-right:var(--dsh-session-list-scrollbar-offset);padding-left:4px;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:16px;overflow-y:auto}.bhn1Oq_flatList>*+*,.bhn1Oq_searchTree>[role=treeitem]+[role=treeitem],.bhn1Oq_groupSection>*+*{margin-top:2px}.bhn1Oq_searchStatus{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.bhn1Oq_skeletonRow{box-sizing:border-box;align-items:flex-start;gap:8px;min-height:48px;padding:6px 8px 7px;display:flex}.bhn1Oq_skeletonDot{corner-shape:round;border-radius:50%;flex:none;width:16px;height:16px}.bhn1Oq_skeletonBars{flex-direction:column;flex:1;gap:6px;min-width:0;display:flex}.bhn1Oq_skeletonDot,.bhn1Oq_skeletonBar{background:var(--dsw-alias-bg-skeleton);animation:2s cubic-bezier(.36,0,.64,1) infinite bhn1Oq_search-skeleton}.bhn1Oq_skeletonBar{border-radius:4px;width:65%;height:16px}.bhn1Oq_skeletonBarWide{width:90%;height:13px}@keyframes bhn1Oq_search-skeleton{0%{opacity:1}40%{opacity:.6}80%,to{opacity:1}}.bhn1Oq_groupSection{position:relative}.bhn1Oq_groupSection+.bhn1Oq_groupSection{margin-top:4px}.bhn1Oq_listTopDropIndicator,.bhn1Oq_workspaceDropBefore:before,.bhn1Oq_workspaceDropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:0}.bhn1Oq_listTopDropIndicator{top:-8px;left:0;right:var(--dsh-session-list-edge-inset)}.bhn1Oq_listTopDropActive>.bhn1Oq_workspaceDropBefore:first-child:before{display:none}.bhn1Oq_workspaceDropBefore:before{top:-8px}.bhn1Oq_workspaceDropAfter:after{bottom:-8px}.bhn1Oq_sessionOverflowButton{width:100%;height:28px;padding:0 12px 0 calc(28px + var(--dsh-workspace-indent,0px));cursor:pointer;text-align:left;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:8px;font-size:12px}.bhn1Oq_groupSection>.bhn1Oq_sessionOverflowButton{margin-top:0}.bhn1Oq_sessionOverflowButton:hover{color:var(--dsw-alias-label-secondary);background:0 0}.bhn1Oq_empty{color:var(--dsw-alias-label-tertiary);padding:16px 12px;font-size:13px}.bhn1Oq_renameInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);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}.bhn1Oq_renameInput:disabled{color:var(--dsw-alias-label-dimmed)}.bhn1Oq_renameError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}.bhn1Oq_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}.bhn1Oq_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.bhn1Oq_archiveActivity{color:var(--dsw-alias-label-primary);margin:0 0 8px;padding-left:18px;font-size:13px;line-height:20px}.bhn1Oq_archiveActivity li{overflow-wrap:anywhere}@media (prefers-reduced-motion:reduce){.bhn1Oq_wide,.bhn1Oq_skeletonDot,.bhn1Oq_skeletonBar{animation:none}.bhn1Oq_search,.bhn1Oq_sectionLabel,.bhn1Oq_searchSlot,.bhn1Oq_searchInput,.bhn1Oq_headerActions{transition:none}}";
|
|
1392
2074
|
const tagId = "@deepseek-ai/dsh-client-ui-workspace/WorkspaceBrowser.module.css";
|
|
1393
2075
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1394
2076
|
const tag = document.createElement("style");
|
|
@@ -1398,6 +2080,7 @@ window.__ModuleLoader__.load({
|
|
|
1398
2080
|
document.head.appendChild(tag);
|
|
1399
2081
|
}
|
|
1400
2082
|
var WorkspaceBrowser_module_css_default = {
|
|
2083
|
+
"archiveActivity": "bhn1Oq_archiveActivity",
|
|
1401
2084
|
"clearButton": "bhn1Oq_clearButton",
|
|
1402
2085
|
"deleteAction": "bhn1Oq_deleteAction",
|
|
1403
2086
|
"deleteStatus": "bhn1Oq_deleteStatus",
|
|
@@ -1417,6 +2100,7 @@ window.__ModuleLoader__.load({
|
|
|
1417
2100
|
"renameInput": "bhn1Oq_renameInput",
|
|
1418
2101
|
"root": "bhn1Oq_root",
|
|
1419
2102
|
"search": "bhn1Oq_search",
|
|
2103
|
+
"search-skeleton": "bhn1Oq_search-skeleton",
|
|
1420
2104
|
"searchButton": "bhn1Oq_searchButton",
|
|
1421
2105
|
"searchExpanded": "bhn1Oq_searchExpanded",
|
|
1422
2106
|
"searchInput": "bhn1Oq_searchInput",
|
|
@@ -1424,12 +2108,17 @@ window.__ModuleLoader__.load({
|
|
|
1424
2108
|
"searchSlotExpanded": "bhn1Oq_searchSlotExpanded",
|
|
1425
2109
|
"searchStatus": "bhn1Oq_searchStatus",
|
|
1426
2110
|
"searchTree": "bhn1Oq_searchTree",
|
|
1427
|
-
"searchWarning": "bhn1Oq_searchWarning",
|
|
1428
2111
|
"sectionHeader": "bhn1Oq_sectionHeader",
|
|
1429
2112
|
"sectionLabel": "bhn1Oq_sectionLabel",
|
|
1430
2113
|
"sectionLabelHidden": "bhn1Oq_sectionLabelHidden",
|
|
1431
2114
|
"sessionOverflowButton": "bhn1Oq_sessionOverflowButton",
|
|
2115
|
+
"skeletonBar": "bhn1Oq_skeletonBar",
|
|
2116
|
+
"skeletonBarWide": "bhn1Oq_skeletonBarWide",
|
|
2117
|
+
"skeletonBars": "bhn1Oq_skeletonBars",
|
|
2118
|
+
"skeletonDot": "bhn1Oq_skeletonDot",
|
|
2119
|
+
"skeletonRow": "bhn1Oq_skeletonRow",
|
|
1432
2120
|
"treeBody": "bhn1Oq_treeBody",
|
|
2121
|
+
"viewOptionsMenu": "bhn1Oq_viewOptionsMenu",
|
|
1433
2122
|
"wide": "bhn1Oq_wide",
|
|
1434
2123
|
"wide-in": "bhn1Oq_wide-in",
|
|
1435
2124
|
"workspaceDropAfter": "bhn1Oq_workspaceDropAfter",
|
|
@@ -1446,7 +2135,12 @@ window.__ModuleLoader__.load({
|
|
|
1446
2135
|
* rail entry path, each requesting expansion through the owner share. Adding
|
|
1447
2136
|
* is the header button's one action, so it raises the directory flow with no
|
|
1448
2137
|
* menu in between; the flow and its error dialog live in WorkspacePicker
|
|
1449
|
-
* (same package — direct composition, no slot between them).
|
|
2138
|
+
* (same package — direct composition, no slot between them). A Session row's
|
|
2139
|
+
* "..." menu and hover buttons are the `sidebar.workspaces.session.menu.item`
|
|
2140
|
+
* and `sidebar.workspaces.session.row.action` lists rendered through this
|
|
2141
|
+
* entry's `renderSlot`; the actions in them, this package's own included,
|
|
2142
|
+
* are slot entries with their own behavior, so this component threads no
|
|
2143
|
+
* action callbacks and hosts no action surface.
|
|
1450
2144
|
*/
|
|
1451
2145
|
/**
|
|
1452
2146
|
* Column slide length (--ds-transition-duration-slow): rail-search focus waits it out —
|
|
@@ -1457,15 +2151,15 @@ window.__ModuleLoader__.load({
|
|
|
1457
2151
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
1458
2152
|
/** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
|
|
1459
2153
|
const SEARCH_QUERY_MAX_CODE_UNITS = 500;
|
|
1460
|
-
/** Session rows visible per Workspace before the local overflow control. */
|
|
2154
|
+
/** Idle Session rows visible per Workspace before the local overflow control. */
|
|
1461
2155
|
const COLLAPSED_SESSION_LIMIT = 5;
|
|
1462
|
-
/**
|
|
1463
|
-
function collapsedSessionRows(sessions) {
|
|
1464
|
-
let
|
|
2156
|
+
/** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
|
|
2157
|
+
function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
|
|
2158
|
+
let idleCount = 0;
|
|
1465
2159
|
const rows = sessions.filter((session) => {
|
|
1466
|
-
if (session.blank) return true;
|
|
1467
|
-
if (
|
|
1468
|
-
|
|
2160
|
+
if (session.blank || session.running || session.runningSubagentCount > 0) return true;
|
|
2161
|
+
if (idleCount >= limit) return false;
|
|
2162
|
+
idleCount += 1;
|
|
1469
2163
|
return true;
|
|
1470
2164
|
});
|
|
1471
2165
|
return {
|
|
@@ -1483,10 +2177,6 @@ window.__ModuleLoader__.load({
|
|
|
1483
2177
|
if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
|
|
1484
2178
|
return withoutNul.slice(0, end);
|
|
1485
2179
|
}
|
|
1486
|
-
/** Immutable membership toggle for the local expand-all array. */
|
|
1487
|
-
function toggled(list, key) {
|
|
1488
|
-
return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
|
|
1489
|
-
}
|
|
1490
2180
|
/**
|
|
1491
2181
|
* Accept the native drag at document level while a row drag is active: row
|
|
1492
2182
|
* hover still owns the insertion marker, and releasing outside the list must
|
|
@@ -1510,8 +2200,8 @@ window.__ModuleLoader__.load({
|
|
|
1510
2200
|
};
|
|
1511
2201
|
}, [active]);
|
|
1512
2202
|
}
|
|
1513
|
-
/** Grouping and
|
|
1514
|
-
function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }) {
|
|
2203
|
+
/** Grouping, ordering, and archived-filter menu; own open state so it resets with the wide chrome. */
|
|
2204
|
+
function ViewOptionsMenu({ groupBy, orderBy, archivedFilter, onGroupPick, onOrderPick, onArchivedFilterPick, t }) {
|
|
1515
2205
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1516
2206
|
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1517
2207
|
open,
|
|
@@ -1526,15 +2216,18 @@ window.__ModuleLoader__.load({
|
|
|
1526
2216
|
},
|
|
1527
2217
|
{
|
|
1528
2218
|
id: "workspace",
|
|
1529
|
-
label: t("groupBy.workspace")
|
|
2219
|
+
label: t("groupBy.workspace"),
|
|
2220
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderCloseRegular, {})
|
|
1530
2221
|
},
|
|
1531
2222
|
{
|
|
1532
2223
|
id: "workspace-tree",
|
|
1533
|
-
label: t("groupBy.workspaceTree")
|
|
2224
|
+
label: t("groupBy.workspaceTree"),
|
|
2225
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWorkspaceTreeOutlineRegular, {})
|
|
1534
2226
|
},
|
|
1535
2227
|
{
|
|
1536
2228
|
id: "flat",
|
|
1537
|
-
label: t("groupBy.flat")
|
|
2229
|
+
label: t("groupBy.flat"),
|
|
2230
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFlatListOutlineRegular, {})
|
|
1538
2231
|
},
|
|
1539
2232
|
{
|
|
1540
2233
|
type: "separator",
|
|
@@ -1547,21 +2240,50 @@ window.__ModuleLoader__.load({
|
|
|
1547
2240
|
},
|
|
1548
2241
|
{
|
|
1549
2242
|
id: "manual",
|
|
1550
|
-
label: t("orderBy.manual")
|
|
2243
|
+
label: t("orderBy.manual"),
|
|
2244
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronsUpDownOutlineRegular, {})
|
|
1551
2245
|
},
|
|
1552
2246
|
{
|
|
1553
2247
|
id: "updated",
|
|
1554
|
-
label: t("orderBy.updated")
|
|
2248
|
+
label: t("orderBy.updated"),
|
|
2249
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconClockOutlineRegular, {})
|
|
2250
|
+
},
|
|
2251
|
+
{
|
|
2252
|
+
type: "separator",
|
|
2253
|
+
id: "archived-filter-separator"
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
type: "label",
|
|
2257
|
+
id: "filter-by",
|
|
2258
|
+
text: t("filterBy.label")
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
id: "show-archived",
|
|
2262
|
+
label: t("viewOptions.showArchived"),
|
|
2263
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, {})
|
|
2264
|
+
},
|
|
2265
|
+
{
|
|
2266
|
+
id: "only-archived",
|
|
2267
|
+
label: t("viewOptions.onlyArchived"),
|
|
2268
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveCheckOutlineRegular, {})
|
|
1555
2269
|
}
|
|
1556
2270
|
],
|
|
1557
|
-
selectedIds: [
|
|
2271
|
+
selectedIds: [
|
|
2272
|
+
groupBy,
|
|
2273
|
+
orderBy,
|
|
2274
|
+
...archivedFilter === "show" ? ["show-archived"] : [],
|
|
2275
|
+
...archivedFilter === "only" ? ["only-archived"] : []
|
|
2276
|
+
],
|
|
1558
2277
|
onSelect: (id) => {
|
|
1559
2278
|
if (id === "workspace" || id === "workspace-tree" || id === "flat") onGroupPick(id);
|
|
1560
2279
|
else if (id === "manual" || id === "updated") onOrderPick(id);
|
|
2280
|
+
else if (id === "show-archived") onArchivedFilterPick(archivedFilter === "show" ? "default" : "show");
|
|
2281
|
+
else if (id === "only-archived") onArchivedFilterPick(archivedFilter === "only" ? "default" : "only");
|
|
1561
2282
|
setOpen(false);
|
|
1562
2283
|
},
|
|
1563
2284
|
align: "end",
|
|
1564
2285
|
dense: true,
|
|
2286
|
+
listClassName: WorkspaceBrowser_module_css_default.viewOptionsMenu,
|
|
1565
2287
|
portal: true,
|
|
1566
2288
|
anchor: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1567
2289
|
label: t("viewOptions.label"),
|
|
@@ -1574,28 +2296,43 @@ window.__ModuleLoader__.load({
|
|
|
1574
2296
|
onClick: () => {
|
|
1575
2297
|
setOpen((v) => !v);
|
|
1576
2298
|
},
|
|
1577
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
2299
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSlidersTwoOutlineRegular, {})
|
|
1578
2300
|
})
|
|
1579
2301
|
})
|
|
1580
2302
|
});
|
|
1581
2303
|
}
|
|
2304
|
+
/** Apply a visible drop to the complete account without removing hidden members. */
|
|
2305
|
+
function sessionDragOrder(order, rows, drag, over) {
|
|
2306
|
+
const source = rows.find((row) => row.id === drag.sessionId);
|
|
2307
|
+
const target = rows.find((row) => row.id === over.id);
|
|
2308
|
+
if (source === void 0 || target === void 0 || source.blank || source.pinned !== drag.pinned || target.pinned !== drag.pinned || source.id === target.id || !order.includes(source.id)) return;
|
|
2309
|
+
const section = rows.filter((row) => row.pinned === drag.pinned);
|
|
2310
|
+
const sourceIndex = section.findIndex((row) => row.id === source.id);
|
|
2311
|
+
if (section.filter((row) => row.id !== source.id).findIndex((row) => row.id === target.id) + (over.half === "after" ? 1 : 0) === sourceIndex) return;
|
|
2312
|
+
const next = order.filter((id) => id !== source.id);
|
|
2313
|
+
const targetIndex = next.indexOf(target.id);
|
|
2314
|
+
if (targetIndex === -1) return;
|
|
2315
|
+
next.splice(targetIndex + (over.half === "after" ? 1 : 0), 0, source.id);
|
|
2316
|
+
return pinCurrentBlank(next, rows.find((row) => row.blank)?.id);
|
|
2317
|
+
}
|
|
1582
2318
|
/** Resolve an insertion side across the Workspace header, descendants, and Sessions. */
|
|
1583
2319
|
function workspaceGroupHalf(e) {
|
|
1584
2320
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
1585
2321
|
return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1586
2322
|
}
|
|
1587
|
-
/** The scrolling session tree; unmounting drops the sessions subscription and
|
|
1588
|
-
function SessionTree({ list, useSessionStatus, startSession, open,
|
|
2323
|
+
/** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
|
|
2324
|
+
function SessionTree({ list, useSessionStatus, startSession, open, workspaces, ungroupedSessionIds, rowState, workspaceReady, animationResetKey, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRenameRequest, renderSlot, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed }) {
|
|
1589
2325
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
1590
2326
|
const statuses = useSessionStatus((s) => s);
|
|
1591
2327
|
const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
1592
2328
|
const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
|
|
1593
|
-
const [
|
|
2329
|
+
const [sessionLimits, setSessionLimits] = (0, react.useState)({});
|
|
1594
2330
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
1595
2331
|
const sessionDropCommitted = (0, react.useRef)(false);
|
|
1596
2332
|
const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
|
|
1597
2333
|
const workspaceDropCommitted = (0, react.useRef)(false);
|
|
1598
|
-
|
|
2334
|
+
const nativeDragActive = drag !== null || workspaceDrag !== null;
|
|
2335
|
+
useNativeDragAcceptance(nativeDragActive);
|
|
1599
2336
|
const currentGroup = current === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, current);
|
|
1600
2337
|
(0, react.useEffect)(() => {
|
|
1601
2338
|
if (current === void 0 || currentGroup === void 0 || Object.hasOwn(groupExpansion, currentGroup)) return;
|
|
@@ -1628,13 +2365,13 @@ window.__ModuleLoader__.load({
|
|
|
1628
2365
|
parents,
|
|
1629
2366
|
workspaces
|
|
1630
2367
|
]);
|
|
1631
|
-
const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces,
|
|
2368
|
+
const groups = (0, react.useMemo)(() => deriveGroups(list, workspaces, rowState, statuses, {
|
|
1632
2369
|
expandedGroups,
|
|
1633
2370
|
ungroupedOrder: ungroupedSessionIds
|
|
1634
2371
|
}), [
|
|
1635
2372
|
list,
|
|
1636
2373
|
workspaces,
|
|
1637
|
-
|
|
2374
|
+
rowState,
|
|
1638
2375
|
statuses,
|
|
1639
2376
|
expandedGroups,
|
|
1640
2377
|
ungroupedSessionIds
|
|
@@ -1652,7 +2389,10 @@ window.__ModuleLoader__.load({
|
|
|
1652
2389
|
const group = groups.find((candidate) => candidate.key === revealGroup);
|
|
1653
2390
|
if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
|
|
1654
2391
|
if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
|
|
1655
|
-
|
|
2392
|
+
setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
|
|
2393
|
+
...limits,
|
|
2394
|
+
[revealGroup]: Infinity
|
|
2395
|
+
});
|
|
1656
2396
|
}, [
|
|
1657
2397
|
groups,
|
|
1658
2398
|
revealGroup,
|
|
@@ -1665,42 +2405,12 @@ window.__ModuleLoader__.load({
|
|
|
1665
2405
|
setDrag(null);
|
|
1666
2406
|
const group = groups.find((candidate) => candidate.key === activeDrag.accountKey);
|
|
1667
2407
|
if (group === void 0) return;
|
|
1668
|
-
const sessionsExpanded = expandedSessionGroups.includes(group.key);
|
|
1669
|
-
const renderedSessions = sessionsExpanded ? group.sessions : collapsedSessionRows(group.sessions).rows;
|
|
1670
|
-
const targetIndex = renderedSessions.findIndex((session) => session.id === over.id);
|
|
1671
|
-
if (targetIndex === -1) return;
|
|
1672
|
-
const sourceIndex = renderedSessions.findIndex((session) => session.id === activeDrag.sessionId);
|
|
1673
2408
|
if (over.id === activeDrag.sessionId) return;
|
|
1674
|
-
const withoutSource = renderedSessions.filter((session) => session.id !== activeDrag.sessionId);
|
|
1675
|
-
const targetWithoutSourceIndex = withoutSource.findIndex((session) => session.id === over.id);
|
|
1676
|
-
if (targetWithoutSourceIndex === -1) return;
|
|
1677
|
-
const visibleInsertAt = over.half === "before" ? targetWithoutSourceIndex : targetWithoutSourceIndex + 1;
|
|
1678
|
-
if (sourceIndex !== -1 && visibleInsertAt === sourceIndex) return;
|
|
1679
2409
|
const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
|
|
1680
|
-
if (accountSessionIds === void 0
|
|
1681
|
-
const
|
|
1682
|
-
|
|
1683
|
-
if (
|
|
1684
|
-
else {
|
|
1685
|
-
const previousVisible = withoutSource[visibleInsertAt - 1]?.id;
|
|
1686
|
-
if (previousVisible === void 0) anchor = nextOrder[0];
|
|
1687
|
-
else {
|
|
1688
|
-
const previousIndex = nextOrder.indexOf(previousVisible);
|
|
1689
|
-
if (previousIndex === -1) return;
|
|
1690
|
-
anchor = nextOrder[previousIndex + 1];
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
|
|
1694
|
-
nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
|
|
1695
|
-
if (!sessionsExpanded && sourceIndex !== -1) {
|
|
1696
|
-
const nodes = new Map(group.sessions.map((node) => [node.id, node]));
|
|
1697
|
-
if (!collapsedSessionRows(nextOrder.flatMap((id) => {
|
|
1698
|
-
const node = nodes.get(id);
|
|
1699
|
-
return node === void 0 ? [] : [node];
|
|
1700
|
-
})).rows.some((node) => node.id === activeDrag.sessionId)) return;
|
|
1701
|
-
}
|
|
1702
|
-
const currentBlank = group.sessions.find((node) => node.blank)?.id;
|
|
1703
|
-
setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank));
|
|
2410
|
+
if (accountSessionIds === void 0) return;
|
|
2411
|
+
const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
|
|
2412
|
+
const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
|
|
2413
|
+
if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
|
|
1704
2414
|
};
|
|
1705
2415
|
const commitWorkspaceDrag = (activeDrag, over) => {
|
|
1706
2416
|
if (workspaceDropCommitted.current) return;
|
|
@@ -1731,12 +2441,19 @@ window.__ModuleLoader__.load({
|
|
|
1731
2441
|
}, [groups, parents]);
|
|
1732
2442
|
const rootGroups = childrenByParent.get(void 0) ?? [];
|
|
1733
2443
|
const workspaceDropAtListStart = rootGroups[0]?.workspaceId !== void 0 && workspaceDrag?.over?.id === rootGroups[0].workspaceId && workspaceDrag.over.half === "before";
|
|
2444
|
+
const rowKeys = groups.length === 0 ? ["empty"] : [];
|
|
1734
2445
|
const renderGroup = (group, depth) => {
|
|
1735
2446
|
const workspaceId = group.workspaceId;
|
|
1736
2447
|
const children = childrenByParent.get(group.key) ?? [];
|
|
1737
2448
|
const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
|
|
1738
2449
|
const collapsed = collapsedSessionRows(group.sessions);
|
|
1739
|
-
const
|
|
2450
|
+
const visible = collapsedSessionRows(group.sessions, sessionLimits[group.key]);
|
|
2451
|
+
const sessionsExpanded = visible.hiddenCount === 0;
|
|
2452
|
+
rowKeys.push(`workspace:${group.key}`);
|
|
2453
|
+
const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
|
|
2454
|
+
const sessions = visible.rows;
|
|
2455
|
+
for (const node of sessions) rowKeys.push(`session:${node.id}`);
|
|
2456
|
+
if (collapsed.hiddenCount > 0) rowKeys.push(`overflow:${group.key}`);
|
|
1740
2457
|
const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
|
|
1741
2458
|
const workspaceDragProps = workspaceId === void 0 ? void 0 : {
|
|
1742
2459
|
start: () => {
|
|
@@ -1801,7 +2518,10 @@ window.__ModuleLoader__.load({
|
|
|
1801
2518
|
home,
|
|
1802
2519
|
t,
|
|
1803
2520
|
onToggle: () => {
|
|
1804
|
-
if (group.expanded)
|
|
2521
|
+
if (group.expanded) setSessionLimits((limits) => ({
|
|
2522
|
+
...limits,
|
|
2523
|
+
[group.key]: COLLAPSED_SESSION_LIMIT
|
|
2524
|
+
}));
|
|
1805
2525
|
setGroupExpanded(group.key, !group.expanded);
|
|
1806
2526
|
},
|
|
1807
2527
|
onCreate: () => {
|
|
@@ -1822,12 +2542,13 @@ window.__ModuleLoader__.load({
|
|
|
1822
2542
|
}
|
|
1823
2543
|
}
|
|
1824
2544
|
}),
|
|
1825
|
-
|
|
2545
|
+
childRows.length > 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
1826
2546
|
role: "group",
|
|
1827
|
-
children:
|
|
2547
|
+
children: childRows
|
|
1828
2548
|
}),
|
|
1829
|
-
|
|
2549
|
+
sessions.map((node) => {
|
|
1830
2550
|
const sameGroupDrag = drag !== null && drag.accountKey === group.key;
|
|
2551
|
+
const compatibleTarget = sameGroupDrag && drag.pinned === node.pinned;
|
|
1831
2552
|
const normalizeHalf = (half) => node.blank ? "after" : half;
|
|
1832
2553
|
const dragProps = {
|
|
1833
2554
|
start: () => {
|
|
@@ -1835,10 +2556,11 @@ window.__ModuleLoader__.load({
|
|
|
1835
2556
|
setDrag({
|
|
1836
2557
|
accountKey: group.key,
|
|
1837
2558
|
sessionId: node.id,
|
|
2559
|
+
pinned: node.pinned,
|
|
1838
2560
|
over: null
|
|
1839
2561
|
});
|
|
1840
2562
|
},
|
|
1841
|
-
active:
|
|
2563
|
+
active: compatibleTarget,
|
|
1842
2564
|
marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
|
|
1843
2565
|
hover: (half) => {
|
|
1844
2566
|
/* v8 ignore next -- narrowing guard: Rows gates hover on `active`, which is false while the drag state is null. */
|
|
@@ -1869,9 +2591,8 @@ window.__ModuleLoader__.load({
|
|
|
1869
2591
|
currentId: current,
|
|
1870
2592
|
now,
|
|
1871
2593
|
onOpen: open,
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
onArchive: onSessionArchive,
|
|
2594
|
+
onRenameRequest: onSessionRenameRequest,
|
|
2595
|
+
renderSlot,
|
|
1875
2596
|
onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
|
|
1876
2597
|
onSessionRevealed(node.id);
|
|
1877
2598
|
} : void 0,
|
|
@@ -1882,15 +2603,20 @@ window.__ModuleLoader__.load({
|
|
|
1882
2603
|
collapsed.hiddenCount > 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
1883
2604
|
type: "button",
|
|
1884
2605
|
className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
|
|
2606
|
+
"data-row-key": `overflow:${group.key}`,
|
|
1885
2607
|
"aria-expanded": sessionsExpanded,
|
|
1886
2608
|
onClick: () => {
|
|
1887
|
-
|
|
2609
|
+
setSessionLimits((limits) => ({
|
|
2610
|
+
...limits,
|
|
2611
|
+
[group.key]: sessionsExpanded ? COLLAPSED_SESSION_LIMIT : visible.hiddenCount <= COLLAPSED_SESSION_LIMIT ? Infinity : (limits[group.key] ?? COLLAPSED_SESSION_LIMIT) + COLLAPSED_SESSION_LIMIT
|
|
2612
|
+
}));
|
|
1888
2613
|
},
|
|
1889
|
-
children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n:
|
|
2614
|
+
children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
|
|
1890
2615
|
})
|
|
1891
2616
|
]
|
|
1892
2617
|
}, group.key);
|
|
1893
2618
|
};
|
|
2619
|
+
const groupRows = rootGroups.map((group) => renderGroup(group, 0));
|
|
1894
2620
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1895
2621
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
1896
2622
|
children: [
|
|
@@ -1898,26 +2624,30 @@ window.__ModuleLoader__.load({
|
|
|
1898
2624
|
className: WorkspaceBrowser_module_css_default.listTopDropIndicator,
|
|
1899
2625
|
"aria-hidden": "true"
|
|
1900
2626
|
}),
|
|
1901
|
-
(0, react_jsx_runtime.jsxs)(
|
|
2627
|
+
(0, react_jsx_runtime.jsxs)(AnimatedRows, {
|
|
1902
2628
|
className: clsx(WorkspaceBrowser_module_css_default.list, workspaceDropAtListStart && WorkspaceBrowser_module_css_default.listTopDropActive),
|
|
1903
|
-
|
|
1904
|
-
|
|
2629
|
+
label: t("section.sessions"),
|
|
2630
|
+
rowKeys,
|
|
2631
|
+
ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
|
|
2632
|
+
resetKey: JSON.stringify([animationResetKey, sessionLimits]),
|
|
1905
2633
|
children: [groups.length === 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
1906
2634
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
2635
|
+
"data-row-key": "empty",
|
|
1907
2636
|
children: t("empty.none")
|
|
1908
|
-
}),
|
|
2637
|
+
}), groupRows]
|
|
1909
2638
|
}),
|
|
1910
2639
|
(0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.fade })
|
|
1911
2640
|
]
|
|
1912
2641
|
});
|
|
1913
2642
|
}
|
|
1914
2643
|
/** The flat "In one list" body: every session is one draggable top-level row. */
|
|
1915
|
-
function FlatList({ list, sessionIds, useSessionStatus, open,
|
|
2644
|
+
function FlatList({ list, sessionIds, rowState, useSessionStatus, open, onSessionRenameRequest, renderSlot, usePanelInfo, setSessionOrder, workspaceReady, animationResetKey, revealSessionId, onSessionRevealed, t }) {
|
|
1916
2645
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
1917
2646
|
const statuses = useSessionStatus((s) => s);
|
|
1918
|
-
const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, statuses), [
|
|
2647
|
+
const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, rowState, statuses), [
|
|
1919
2648
|
list,
|
|
1920
2649
|
sessionIds,
|
|
2650
|
+
rowState,
|
|
1921
2651
|
statuses
|
|
1922
2652
|
]);
|
|
1923
2653
|
const [drag, setDrag] = (0, react.useState)(null);
|
|
@@ -1928,40 +2658,32 @@ window.__ModuleLoader__.load({
|
|
|
1928
2658
|
if (dropCommitted.current) return;
|
|
1929
2659
|
dropCommitted.current = true;
|
|
1930
2660
|
setDrag(null);
|
|
1931
|
-
const
|
|
1932
|
-
if (
|
|
1933
|
-
const anchor = over.half === "before" ? over.id : rows[targetIndex + 1]?.id;
|
|
1934
|
-
if (anchor === activeDrag.sessionId) return;
|
|
1935
|
-
const sourceIndex = rows.findIndex((row) => row.id === activeDrag.sessionId);
|
|
1936
|
-
const anchorIndex = anchor === void 0 ? rows.length : rows.findIndex((row) => row.id === anchor);
|
|
1937
|
-
if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return;
|
|
1938
|
-
const nextOrder = rows.map((row) => row.id).filter((id) => id !== activeDrag.sessionId);
|
|
1939
|
-
const insertAt = anchor === void 0 ? nextOrder.length : nextOrder.indexOf(anchor);
|
|
1940
|
-
nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
|
|
1941
|
-
const currentBlank = rows.find((node) => node.blank)?.id;
|
|
1942
|
-
setSessionOrder(FLAT_SESSION_ORDER_KEY, pinCurrentBlank(nextOrder, currentBlank));
|
|
2661
|
+
const nextOrder = sessionDragOrder(sessionIds, rows, activeDrag, over);
|
|
2662
|
+
if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
|
|
1943
2663
|
};
|
|
1944
2664
|
const now = Date.now();
|
|
1945
2665
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1946
2666
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
1947
|
-
children: [(0, react_jsx_runtime.jsxs)(
|
|
2667
|
+
children: [(0, react_jsx_runtime.jsxs)(AnimatedRows, {
|
|
1948
2668
|
className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
|
|
1949
|
-
|
|
1950
|
-
"
|
|
2669
|
+
label: t("section.sessions"),
|
|
2670
|
+
rowKeys: rows.length === 0 ? ["empty"] : rows.map((row) => `session:${row.id}`),
|
|
2671
|
+
ready: list.phase === "ready" && workspaceReady && drag === null,
|
|
2672
|
+
resetKey: animationResetKey,
|
|
1951
2673
|
children: [rows.length === 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
1952
2674
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
2675
|
+
"data-row-key": "empty",
|
|
1953
2676
|
children: t("empty.none")
|
|
1954
2677
|
}), rows.map((node) => {
|
|
1955
|
-
const active = drag !== null;
|
|
2678
|
+
const active = drag !== null && drag.pinned === node.pinned;
|
|
1956
2679
|
const normalizeHalf = (half) => node.blank ? "after" : half;
|
|
1957
2680
|
return (0, react_jsx_runtime.jsx)(SessionNodeItem, {
|
|
1958
2681
|
node,
|
|
1959
2682
|
currentId,
|
|
1960
2683
|
now,
|
|
1961
2684
|
onOpen: open,
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
onArchive: onSessionArchive,
|
|
2685
|
+
onRenameRequest: onSessionRenameRequest,
|
|
2686
|
+
renderSlot,
|
|
1965
2687
|
onReveal: node.id === revealSessionId ? () => {
|
|
1966
2688
|
onSessionRevealed(node.id);
|
|
1967
2689
|
} : void 0,
|
|
@@ -1972,6 +2694,7 @@ window.__ModuleLoader__.load({
|
|
|
1972
2694
|
setDrag({
|
|
1973
2695
|
accountKey: FLAT_SESSION_ORDER_KEY,
|
|
1974
2696
|
sessionId: node.id,
|
|
2697
|
+
pinned: node.pinned,
|
|
1975
2698
|
over: null
|
|
1976
2699
|
});
|
|
1977
2700
|
},
|
|
@@ -2005,7 +2728,7 @@ window.__ModuleLoader__.load({
|
|
|
2005
2728
|
});
|
|
2006
2729
|
}
|
|
2007
2730
|
/** Flat search body: local metadata matches plus the current Host result page. */
|
|
2008
|
-
function SearchResults({ useSessions, useSessionStatus, open, workspaces, archivedSessionIds, query, remote, resultLimit, usePanelInfo, t }) {
|
|
2731
|
+
function SearchResults({ useSessions, useSessionStatus, open, onUnarchive, workspaces, archivedSessionIds, archivedFilter, query, remote, resultLimit, usePanelInfo, t }) {
|
|
2009
2732
|
const panelActive = usePanelInfo((info) => info.activePanelId !== null);
|
|
2010
2733
|
const list = useSessions((s) => s);
|
|
2011
2734
|
const statuses = useSessionStatus((s) => s);
|
|
@@ -2015,17 +2738,17 @@ window.__ModuleLoader__.load({
|
|
|
2015
2738
|
items: [],
|
|
2016
2739
|
hasMore: false
|
|
2017
2740
|
};
|
|
2018
|
-
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, statuses, currentRemote, resultLimit), [
|
|
2741
|
+
const results = (0, react.useMemo)(() => deriveSearchResults(list, workspaces, query, archivedSessionIds, archivedFilter, statuses, currentRemote, resultLimit), [
|
|
2019
2742
|
list,
|
|
2020
2743
|
workspaces,
|
|
2021
2744
|
query,
|
|
2022
2745
|
archivedSessionIds,
|
|
2746
|
+
archivedFilter,
|
|
2023
2747
|
statuses,
|
|
2024
2748
|
currentRemote,
|
|
2025
2749
|
resultLimit
|
|
2026
2750
|
]);
|
|
2027
2751
|
const pending = currentRemote.status === "loading";
|
|
2028
|
-
const failed = currentRemote.status === "error";
|
|
2029
2752
|
const currentId = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
2030
2753
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2031
2754
|
className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
|
|
@@ -2040,18 +2763,21 @@ window.__ModuleLoader__.load({
|
|
|
2040
2763
|
result,
|
|
2041
2764
|
currentId,
|
|
2042
2765
|
onOpen: open,
|
|
2766
|
+
onUnarchive,
|
|
2043
2767
|
t
|
|
2044
2768
|
}, result.id))
|
|
2045
2769
|
}),
|
|
2046
2770
|
pending && (0, react_jsx_runtime.jsx)("div", {
|
|
2047
|
-
className: WorkspaceBrowser_module_css_default.searchStatus,
|
|
2048
|
-
role: "status",
|
|
2049
|
-
children: t("search.pending")
|
|
2050
|
-
}),
|
|
2051
|
-
failed && (0, react_jsx_runtime.jsx)("div", {
|
|
2052
|
-
className: WorkspaceBrowser_module_css_default.searchWarning,
|
|
2053
2771
|
role: "status",
|
|
2054
|
-
|
|
2772
|
+
"aria-label": t("search.pending"),
|
|
2773
|
+
children: (results.items.length === 0 ? [0, 1] : [0]).map((i) => (0, react_jsx_runtime.jsxs)("div", {
|
|
2774
|
+
className: WorkspaceBrowser_module_css_default.skeletonRow,
|
|
2775
|
+
"aria-hidden": "true",
|
|
2776
|
+
children: [(0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.skeletonDot }), (0, react_jsx_runtime.jsxs)("span", {
|
|
2777
|
+
className: WorkspaceBrowser_module_css_default.skeletonBars,
|
|
2778
|
+
children: [(0, react_jsx_runtime.jsx)("span", { className: WorkspaceBrowser_module_css_default.skeletonBar }), (0, react_jsx_runtime.jsx)("span", { className: clsx(WorkspaceBrowser_module_css_default.skeletonBar, WorkspaceBrowser_module_css_default.skeletonBarWide) })]
|
|
2779
|
+
})]
|
|
2780
|
+
}, i))
|
|
2055
2781
|
}),
|
|
2056
2782
|
!pending && results.items.length === 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
2057
2783
|
className: WorkspaceBrowser_module_css_default.empty,
|
|
@@ -2070,18 +2796,27 @@ window.__ModuleLoader__.load({
|
|
|
2070
2796
|
* @param props - composed slot props (shell owner share + store + injected actions).
|
|
2071
2797
|
* @returns the region element tree.
|
|
2072
2798
|
*/
|
|
2073
|
-
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open,
|
|
2799
|
+
function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, renderSlot, t }) {
|
|
2074
2800
|
const home = useHostInfo((info) => info.home);
|
|
2075
2801
|
const list = useSessions((state) => state);
|
|
2076
2802
|
const workspaces = useWorkspaces((state) => state.items);
|
|
2077
2803
|
const workspacePhase = useWorkspaces((state) => state.phase);
|
|
2078
2804
|
const workspaceStreamState = useWorkspaces((state) => state.state);
|
|
2079
2805
|
const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
|
|
2806
|
+
const pinnedSessionIds = useWorkspaces((state) => state.pinnedSessionIds);
|
|
2080
2807
|
const directoryFlowAvailable = useDirectoryFlow((occupied) => occupied);
|
|
2081
2808
|
const groupBy = useStore((s) => s.groupBy);
|
|
2082
2809
|
const orderBy = useStore((s) => s.orderBy);
|
|
2810
|
+
const archivedFilter = useStore((s) => s.archivedFilter ?? "default");
|
|
2083
2811
|
const groupExpansion = useStore((s) => s.groupExpansion);
|
|
2084
2812
|
const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
|
|
2813
|
+
const guardedOpen = (sessionId) => {
|
|
2814
|
+
if (archivedSessionIds.includes(sessionId)) {
|
|
2815
|
+
notifyArchivedNotOpenable();
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
open(sessionId);
|
|
2819
|
+
};
|
|
2085
2820
|
const workspaceReady = workspacePhase === "ready" && workspaceStreamState !== "loading";
|
|
2086
2821
|
const mainSessionId = Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
|
|
2087
2822
|
const currentBlank = mainSessionId !== void 0 && list.byId[mainSessionId]?.blank === true ? mainSessionId : void 0;
|
|
@@ -2089,10 +2824,18 @@ window.__ModuleLoader__.load({
|
|
|
2089
2824
|
const accounted = new Set(workspaces.flatMap((workspace) => workspace.sessionIds));
|
|
2090
2825
|
return list.ids.filter((id) => list.byId[id] !== void 0 && !accounted.has(id));
|
|
2091
2826
|
}, [list, workspaces]);
|
|
2092
|
-
const
|
|
2827
|
+
const orderState = (0, react.useMemo)(() => ({
|
|
2828
|
+
pinnedSessionIds,
|
|
2829
|
+
archivedSessionIds
|
|
2830
|
+
}), [archivedSessionIds, pinnedSessionIds]);
|
|
2831
|
+
const rowState = (0, react.useMemo)(() => ({
|
|
2832
|
+
...orderState,
|
|
2833
|
+
archivedFilter
|
|
2834
|
+
}), [orderState, archivedFilter]);
|
|
2835
|
+
const flatMemberIds = (0, react.useMemo)(() => sessionMemberIds(list), [list]);
|
|
2093
2836
|
const orderedWorkspaces = (0, react.useMemo)(() => workspaces.map((workspace) => {
|
|
2094
2837
|
const memberIds = workspace.sessionIds;
|
|
2095
|
-
const baseOrder = orderBy === "updated" ? orderByRecency(memberIds, list.byId) : reconcileManualOrder(memberIds, sessionOrderByAccount[workspace.workspaceId], list.byId);
|
|
2838
|
+
const baseOrder = orderBy === "updated" ? orderByRecency(memberIds, list.byId) : reconcileManualOrder(memberIds, sessionOrderByAccount[workspace.workspaceId], list.byId, orderState);
|
|
2096
2839
|
return {
|
|
2097
2840
|
...workspace,
|
|
2098
2841
|
sessionIds: pinCurrentBlank(baseOrder, currentBlank !== void 0 && memberIds.includes(currentBlank) ? currentBlank : void 0)
|
|
@@ -2101,25 +2844,28 @@ window.__ModuleLoader__.load({
|
|
|
2101
2844
|
currentBlank,
|
|
2102
2845
|
list.byId,
|
|
2103
2846
|
orderBy,
|
|
2847
|
+
orderState,
|
|
2104
2848
|
sessionOrderByAccount,
|
|
2105
2849
|
workspaces
|
|
2106
2850
|
]);
|
|
2107
2851
|
const orderedUngroupedSessionIds = (0, react.useMemo)(() => {
|
|
2108
|
-
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(ungroupedMemberIds, list.byId) : reconcileManualOrder(ungroupedMemberIds, sessionOrderByAccount[""], list.byId), currentBlank !== void 0 && ungroupedMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
2852
|
+
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(ungroupedMemberIds, list.byId) : reconcileManualOrder(ungroupedMemberIds, sessionOrderByAccount[""], list.byId, orderState), currentBlank !== void 0 && ungroupedMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
2109
2853
|
}, [
|
|
2110
2854
|
currentBlank,
|
|
2111
2855
|
list.byId,
|
|
2112
2856
|
orderBy,
|
|
2857
|
+
orderState,
|
|
2113
2858
|
sessionOrderByAccount,
|
|
2114
2859
|
ungroupedMemberIds
|
|
2115
2860
|
]);
|
|
2116
2861
|
const orderedFlatSessionIds = (0, react.useMemo)(() => {
|
|
2117
|
-
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(flatMemberIds, list.byId) : reconcileManualOrder(flatMemberIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY], list.byId), currentBlank !== void 0 && flatMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
2862
|
+
return pinCurrentBlank(orderBy === "updated" ? orderByRecency(flatMemberIds, list.byId) : reconcileManualOrder(flatMemberIds, sessionOrderByAccount[FLAT_SESSION_ORDER_KEY], list.byId, orderState), currentBlank !== void 0 && flatMemberIds.includes(currentBlank) ? currentBlank : void 0);
|
|
2118
2863
|
}, [
|
|
2119
2864
|
currentBlank,
|
|
2120
2865
|
flatMemberIds,
|
|
2121
2866
|
list.byId,
|
|
2122
2867
|
orderBy,
|
|
2868
|
+
orderState,
|
|
2123
2869
|
sessionOrderByAccount
|
|
2124
2870
|
]);
|
|
2125
2871
|
const activeSessionOrders = (0, react.useMemo)(() => Object.fromEntries([
|
|
@@ -2164,15 +2910,12 @@ window.__ModuleLoader__.load({
|
|
|
2164
2910
|
workspaceReady
|
|
2165
2911
|
]);
|
|
2166
2912
|
(0, react.useEffect)(() => {
|
|
2167
|
-
if (list.phase !== "ready" || !workspaceReady || orderBy !== "manual") return;
|
|
2168
|
-
|
|
2169
|
-
const saved = sessionOrderByAccount[key];
|
|
2170
|
-
return saved === void 0 || saved.length !== ids.length || ids.some((id, index) => id !== saved[index]);
|
|
2171
|
-
}));
|
|
2172
|
-
if (Object.keys(changed).length > 0) actions.syncSessionOrders(changed);
|
|
2913
|
+
if (list.phase !== "ready" || !workspaceReady || orderBy !== "manual" || currentBlank === void 0) return;
|
|
2914
|
+
if (Object.entries(activeSessionOrders).some(([key, ids]) => ids[0] === currentBlank && sessionOrderByAccount[key]?.[0] !== currentBlank)) actions.syncSessionOrders(activeSessionOrders);
|
|
2173
2915
|
}, [
|
|
2174
2916
|
actions.syncSessionOrders,
|
|
2175
2917
|
activeSessionOrders,
|
|
2918
|
+
currentBlank,
|
|
2176
2919
|
list.phase,
|
|
2177
2920
|
orderBy,
|
|
2178
2921
|
sessionOrderByAccount,
|
|
@@ -2197,6 +2940,10 @@ window.__ModuleLoader__.load({
|
|
|
2197
2940
|
const wsPlusRef = (0, react.useRef)(null);
|
|
2198
2941
|
const composingRef = (0, react.useRef)(false);
|
|
2199
2942
|
const openSearchResult = (sessionId) => {
|
|
2943
|
+
if (archivedSessionIds.includes(sessionId)) {
|
|
2944
|
+
notifyArchivedNotOpenable();
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2200
2947
|
setRevealSessionId(sessionId);
|
|
2201
2948
|
setQuery("");
|
|
2202
2949
|
setSearchExpanded(false);
|
|
@@ -2311,40 +3058,9 @@ window.__ModuleLoader__.load({
|
|
|
2311
3058
|
setRenameError(reason instanceof Error ? reason.message : String(reason));
|
|
2312
3059
|
});
|
|
2313
3060
|
};
|
|
2314
|
-
const
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
const [sessionRenameError, setSessionRenameError] = (0, react.useState)(null);
|
|
2318
|
-
const sessionRenameTrimmed = sessionRenameDraft.trim();
|
|
2319
|
-
const sessionRenameBlocked = sessionRenaming || sessionRenameTrimmed === "" || sessionRenameTarget === null;
|
|
2320
|
-
const closeSessionRename = () => {
|
|
2321
|
-
if (sessionRenaming) return;
|
|
2322
|
-
setSessionRenameTarget(null);
|
|
2323
|
-
setSessionRenameError(null);
|
|
2324
|
-
};
|
|
2325
|
-
const confirmSessionRename = () => {
|
|
2326
|
-
if (sessionRenameBlocked) return;
|
|
2327
|
-
setSessionRenaming(true);
|
|
2328
|
-
setSessionRenameError(null);
|
|
2329
|
-
renameSession(sessionRenameTarget.sessionId, sessionRenameTrimmed).then(() => {
|
|
2330
|
-
setSessionRenaming(false);
|
|
2331
|
-
setSessionRenameTarget(null);
|
|
2332
|
-
}).catch((reason) => {
|
|
2333
|
-
setSessionRenaming(false);
|
|
2334
|
-
setSessionRenameError(reason instanceof Error ? reason.message : String(reason));
|
|
2335
|
-
});
|
|
2336
|
-
};
|
|
2337
|
-
const onSessionRename = (sessionId, currentTitle) => {
|
|
2338
|
-
setSessionRenameTarget({
|
|
2339
|
-
sessionId,
|
|
2340
|
-
currentTitle
|
|
2341
|
-
});
|
|
2342
|
-
setSessionRenameDraft(currentTitle);
|
|
2343
|
-
setSessionRenameError(null);
|
|
2344
|
-
};
|
|
2345
|
-
const onSessionArchive = (sessionId) => {
|
|
2346
|
-
archiveSession(sessionId).catch((reason) => {
|
|
2347
|
-
console.warn("session archive rejected:", reason);
|
|
3061
|
+
const onSessionUnarchive = (sessionId) => {
|
|
3062
|
+
unarchiveSession(sessionId).catch((reason) => {
|
|
3063
|
+
console.warn("session unarchive rejected:", reason);
|
|
2348
3064
|
});
|
|
2349
3065
|
};
|
|
2350
3066
|
const [deleteTarget, setDeleteTarget] = (0, react.useState)(null);
|
|
@@ -2410,7 +3126,7 @@ window.__ModuleLoader__.load({
|
|
|
2410
3126
|
setWsPickerOpen(false);
|
|
2411
3127
|
setSearchExpanded(true);
|
|
2412
3128
|
},
|
|
2413
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
3129
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: searchExpanded ? 11 : 14 })
|
|
2414
3130
|
})
|
|
2415
3131
|
}),
|
|
2416
3132
|
(0, react_jsx_runtime.jsx)("input", {
|
|
@@ -2439,7 +3155,7 @@ window.__ModuleLoader__.load({
|
|
|
2439
3155
|
setQuery("");
|
|
2440
3156
|
setSearchExpanded(false);
|
|
2441
3157
|
},
|
|
2442
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
3158
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseFillRegular, {})
|
|
2443
3159
|
})
|
|
2444
3160
|
]
|
|
2445
3161
|
})
|
|
@@ -2449,12 +3165,12 @@ window.__ModuleLoader__.load({
|
|
|
2449
3165
|
children: [wide && (0, react_jsx_runtime.jsx)(ViewOptionsMenu, {
|
|
2450
3166
|
groupBy,
|
|
2451
3167
|
orderBy,
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
},
|
|
3168
|
+
archivedFilter,
|
|
3169
|
+
onGroupPick: actions.setGroupBy,
|
|
2455
3170
|
onOrderPick: (mode) => {
|
|
2456
3171
|
actions.setOrderBy(mode, activeSessionOrders);
|
|
2457
3172
|
},
|
|
3173
|
+
onArchivedFilterPick: actions.setArchivedFilter,
|
|
2458
3174
|
t
|
|
2459
3175
|
}), directoryFlowAvailable && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
2460
3176
|
label: t("workspace.add"),
|
|
@@ -2468,7 +3184,7 @@ window.__ModuleLoader__.load({
|
|
|
2468
3184
|
onClick: () => {
|
|
2469
3185
|
setWsPickerOpen((v) => !v);
|
|
2470
3186
|
},
|
|
2471
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
3187
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconProjectAddOutlineRegular, { size: wide ? 16 : 18 })
|
|
2472
3188
|
})
|
|
2473
3189
|
})]
|
|
2474
3190
|
}),
|
|
@@ -2505,7 +3221,7 @@ window.__ModuleLoader__.load({
|
|
|
2505
3221
|
setSearchOnExpand(true);
|
|
2506
3222
|
expandSidebar();
|
|
2507
3223
|
},
|
|
2508
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
3224
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 18 })
|
|
2509
3225
|
})
|
|
2510
3226
|
})
|
|
2511
3227
|
}),
|
|
@@ -2516,8 +3232,10 @@ window.__ModuleLoader__.load({
|
|
|
2516
3232
|
useSessions,
|
|
2517
3233
|
useSessionStatus,
|
|
2518
3234
|
open: openSearchResult,
|
|
3235
|
+
onUnarchive: onSessionUnarchive,
|
|
2519
3236
|
workspaces,
|
|
2520
3237
|
archivedSessionIds,
|
|
3238
|
+
archivedFilter,
|
|
2521
3239
|
query: normalizedQuery,
|
|
2522
3240
|
remote: remoteSearch,
|
|
2523
3241
|
resultLimit: searchResultLimit,
|
|
@@ -2526,11 +3244,13 @@ window.__ModuleLoader__.load({
|
|
|
2526
3244
|
usePanelInfo,
|
|
2527
3245
|
list,
|
|
2528
3246
|
sessionIds: orderedFlatSessionIds,
|
|
3247
|
+
rowState,
|
|
3248
|
+
workspaceReady,
|
|
3249
|
+
animationResetKey: `${groupBy}/${orderBy}/${archivedFilter}`,
|
|
2529
3250
|
useSessionStatus,
|
|
2530
|
-
open,
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
onSessionArchive,
|
|
3251
|
+
open: guardedOpen,
|
|
3252
|
+
onSessionRenameRequest: requestSessionRename,
|
|
3253
|
+
renderSlot,
|
|
2534
3254
|
setSessionOrder: saveSessionOrder,
|
|
2535
3255
|
revealSessionId,
|
|
2536
3256
|
onSessionRevealed: acknowledgeSessionReveal,
|
|
@@ -2539,19 +3259,19 @@ window.__ModuleLoader__.load({
|
|
|
2539
3259
|
usePanelInfo,
|
|
2540
3260
|
list,
|
|
2541
3261
|
useSessionStatus,
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
forkSession,
|
|
3262
|
+
onSessionRenameRequest: requestSessionRename,
|
|
3263
|
+
renderSlot,
|
|
2545
3264
|
workspaces: orderedWorkspaces,
|
|
2546
3265
|
ungroupedSessionIds: orderedUngroupedSessionIds,
|
|
2547
3266
|
workspaceReady,
|
|
2548
3267
|
nestWorkspaces: groupBy === "workspace-tree",
|
|
3268
|
+
animationResetKey: `${groupBy}/${orderBy}/${archivedFilter}`,
|
|
2549
3269
|
groupExpansion,
|
|
2550
3270
|
setGroupExpanded: actions.setGroupExpanded,
|
|
2551
3271
|
setSessionOrder: saveSessionOrder,
|
|
2552
|
-
|
|
3272
|
+
rowState,
|
|
2553
3273
|
startSession,
|
|
2554
|
-
open,
|
|
3274
|
+
open: guardedOpen,
|
|
2555
3275
|
insertWorkspaceBefore,
|
|
2556
3276
|
revealSessionId,
|
|
2557
3277
|
onSessionRevealed: acknowledgeSessionReveal,
|
|
@@ -2629,53 +3349,6 @@ window.__ModuleLoader__.load({
|
|
|
2629
3349
|
})
|
|
2630
3350
|
]
|
|
2631
3351
|
}),
|
|
2632
|
-
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2633
|
-
open: sessionRenameTarget !== null,
|
|
2634
|
-
onClose: closeSessionRename,
|
|
2635
|
-
closeLabel: t("close"),
|
|
2636
|
-
title: t("rename.session.title"),
|
|
2637
|
-
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2638
|
-
variant: "outline",
|
|
2639
|
-
disabled: sessionRenaming,
|
|
2640
|
-
onClick: closeSessionRename,
|
|
2641
|
-
children: t("cancel")
|
|
2642
|
-
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2643
|
-
variant: "primary",
|
|
2644
|
-
disabled: sessionRenameBlocked,
|
|
2645
|
-
onClick: confirmSessionRename,
|
|
2646
|
-
children: t("rename")
|
|
2647
|
-
})] }),
|
|
2648
|
-
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
2649
|
-
className: WorkspaceBrowser_module_css_default.renameInput,
|
|
2650
|
-
value: sessionRenameDraft,
|
|
2651
|
-
"aria-label": t("field.sessionName"),
|
|
2652
|
-
autoFocus: true,
|
|
2653
|
-
disabled: sessionRenaming,
|
|
2654
|
-
onFocus: (e) => {
|
|
2655
|
-
e.target.select();
|
|
2656
|
-
},
|
|
2657
|
-
onChange: (e) => {
|
|
2658
|
-
setSessionRenameDraft(e.target.value);
|
|
2659
|
-
setSessionRenameError(null);
|
|
2660
|
-
},
|
|
2661
|
-
onCompositionStart: () => {
|
|
2662
|
-
composingRef.current = true;
|
|
2663
|
-
},
|
|
2664
|
-
onCompositionEnd: () => {
|
|
2665
|
-
composingRef.current = false;
|
|
2666
|
-
},
|
|
2667
|
-
onKeyDown: (e) => {
|
|
2668
|
-
if (e.key === "Enter" && !composingRef.current) {
|
|
2669
|
-
e.preventDefault();
|
|
2670
|
-
confirmSessionRename();
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
}), sessionRenameError !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
2674
|
-
className: WorkspaceBrowser_module_css_default.renameError,
|
|
2675
|
-
role: "alert",
|
|
2676
|
-
children: sessionRenameError
|
|
2677
|
-
})]
|
|
2678
|
-
}),
|
|
2679
3352
|
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
2680
3353
|
open: deleteTarget !== null,
|
|
2681
3354
|
onClose: closeDelete,
|
|
@@ -2708,146 +3381,449 @@ window.__ModuleLoader__.load({
|
|
|
2708
3381
|
});
|
|
2709
3382
|
}
|
|
2710
3383
|
//#endregion
|
|
2711
|
-
//#region lib/types/client/
|
|
3384
|
+
//#region lib/types/client/session-actions/ArchiveSession.js
|
|
3385
|
+
/**
|
|
3386
|
+
* The archive action: a `sidebar.workspaces.session.menu.item` row and a
|
|
3387
|
+
* `sidebar.workspaces.session.row.action` button over one injected behavior,
|
|
3388
|
+
* plus the `shell.overlay` dialog that confirms stopping a Session's running
|
|
3389
|
+
* work before archiving it. The same entries restore an archived row; the
|
|
3390
|
+
* notice a successful archive raises and the diagnostics for Host rejections
|
|
3391
|
+
* live in the injected callbacks, not here.
|
|
3392
|
+
*/
|
|
3393
|
+
/**
|
|
3394
|
+
* Menu row (order 400): archive, or restore an archived row.
|
|
3395
|
+
* @param props - owner share, the archive share, and the menu open state.
|
|
3396
|
+
* @returns the row.
|
|
3397
|
+
*/
|
|
3398
|
+
function ArchiveSessionMenuItem({ sessionId, useArchived, useMenuOpenState, archiveSession, unarchiveSession, t }) {
|
|
3399
|
+
const [, setMenuOpen] = useMenuOpenState();
|
|
3400
|
+
const archived = useArchived((set) => set.has(sessionId));
|
|
3401
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
3402
|
+
icon: archived ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUnarchiveOutlineRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size: 14 }),
|
|
3403
|
+
onSelect: () => {
|
|
3404
|
+
setMenuOpen(false);
|
|
3405
|
+
(archived ? unarchiveSession : archiveSession)(sessionId);
|
|
3406
|
+
},
|
|
3407
|
+
children: t(archived ? "menu.unarchiveSession" : "menu.archiveSession")
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
/**
|
|
3411
|
+
* Hover button (order 100): archive, or restore an archived row.
|
|
3412
|
+
* @param props - owner share and the archive share.
|
|
3413
|
+
* @returns the button.
|
|
3414
|
+
*/
|
|
3415
|
+
function ArchiveSessionRowButton({ sessionId, useArchived, archiveSession, unarchiveSession, t }) {
|
|
3416
|
+
const archived = useArchived((set) => set.has(sessionId));
|
|
3417
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3418
|
+
label: t(archived ? "actions.unarchive" : "actions.archive"),
|
|
3419
|
+
side: "bottom",
|
|
3420
|
+
align: "end",
|
|
3421
|
+
delayMs: 500,
|
|
3422
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
3423
|
+
type: "button",
|
|
3424
|
+
className: Rows_module_css_default.iconButton,
|
|
3425
|
+
"aria-label": t(archived ? "menu.unarchiveSession" : "menu.archiveSession"),
|
|
3426
|
+
onClick: () => {
|
|
3427
|
+
(archived ? unarchiveSession : archiveSession)(sessionId);
|
|
3428
|
+
},
|
|
3429
|
+
children: archived ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUnarchiveOutlineRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutlineRegular, { size: 14 })
|
|
3430
|
+
})
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* The `shell.overlay` entry: nothing while no confirmation is pending,
|
|
3435
|
+
* otherwise one dialog per request (keyed by the Session). Confirming asks
|
|
3436
|
+
* the Host to stop the listed work and archive; cancelling leaves the
|
|
3437
|
+
* Session running and visible.
|
|
3438
|
+
* @param props - the request hook, its settlement, the stop-and-archive hop, and the locale seat.
|
|
3439
|
+
* @returns the open dialog, or null.
|
|
3440
|
+
*/
|
|
3441
|
+
function SessionArchiveConfirmDialog({ useArchiveRequest, settleSessionArchive, stopAndArchiveSession, t }) {
|
|
3442
|
+
const request = useArchiveRequest((pending) => pending);
|
|
3443
|
+
if (request === null) return null;
|
|
3444
|
+
return (0, react_jsx_runtime.jsx)(ArchiveConfirmForm, {
|
|
3445
|
+
request,
|
|
3446
|
+
stopAndArchiveSession,
|
|
3447
|
+
onSettle: settleSessionArchive,
|
|
3448
|
+
t
|
|
3449
|
+
}, request.sessionId);
|
|
3450
|
+
}
|
|
3451
|
+
/** One request's dialog: in-flight and error state die with it. */
|
|
3452
|
+
function ArchiveConfirmForm({ request, stopAndArchiveSession, onSettle, t }) {
|
|
3453
|
+
const [archiving, setArchiving] = (0, react.useState)(false);
|
|
3454
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3455
|
+
const close = () => {
|
|
3456
|
+
if (archiving) return;
|
|
3457
|
+
onSettle();
|
|
3458
|
+
};
|
|
3459
|
+
const confirm = () => {
|
|
3460
|
+
setArchiving(true);
|
|
3461
|
+
setError(null);
|
|
3462
|
+
stopAndArchiveSession(request.sessionId).then(() => {
|
|
3463
|
+
setArchiving(false);
|
|
3464
|
+
onSettle();
|
|
3465
|
+
}).catch((reason) => {
|
|
3466
|
+
setArchiving(false);
|
|
3467
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
3468
|
+
});
|
|
3469
|
+
};
|
|
3470
|
+
return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3471
|
+
open: true,
|
|
3472
|
+
onClose: close,
|
|
3473
|
+
closeLabel: t("close"),
|
|
3474
|
+
title: t("archive.confirm.title"),
|
|
3475
|
+
description: t("archive.confirm.desc", { title: request.displayTitle }),
|
|
3476
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3477
|
+
variant: "outline",
|
|
3478
|
+
disabled: archiving,
|
|
3479
|
+
onClick: close,
|
|
3480
|
+
children: t("cancel")
|
|
3481
|
+
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3482
|
+
variant: "outline",
|
|
3483
|
+
className: WorkspaceBrowser_module_css_default.deleteAction,
|
|
3484
|
+
disabled: archiving,
|
|
3485
|
+
onClick: confirm,
|
|
3486
|
+
children: t("archive.confirm.action")
|
|
3487
|
+
})] }),
|
|
3488
|
+
children: [
|
|
3489
|
+
(0, react_jsx_runtime.jsx)("ul", {
|
|
3490
|
+
className: WorkspaceBrowser_module_css_default.archiveActivity,
|
|
3491
|
+
"aria-label": t("archive.confirm.activity"),
|
|
3492
|
+
children: request.activity.map((entry, index) => (0, react_jsx_runtime.jsx)("li", { children: activityLine(entry, t) }, `${entry.kind}-${String(index)}`))
|
|
3493
|
+
}),
|
|
3494
|
+
archiving && (0, react_jsx_runtime.jsx)("div", {
|
|
3495
|
+
className: WorkspaceBrowser_module_css_default.deleteStatus,
|
|
3496
|
+
role: "status",
|
|
3497
|
+
children: t("archive.confirm.pending")
|
|
3498
|
+
}),
|
|
3499
|
+
error !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
3500
|
+
className: WorkspaceBrowser_module_css_default.renameError,
|
|
3501
|
+
role: "alert",
|
|
3502
|
+
children: error
|
|
3503
|
+
})
|
|
3504
|
+
]
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* One family's line: its count and the items' labels (ids when a family
|
|
3509
|
+
* carries no label). A family this dictionary does not know — a provider
|
|
3510
|
+
* merged into the kind map — falls through to the generic line.
|
|
3511
|
+
*/
|
|
3512
|
+
function activityLine(entry, t) {
|
|
3513
|
+
const items = entry.items ?? [];
|
|
3514
|
+
const n = items.length;
|
|
3515
|
+
const names = items.map((item) => item.label ?? item.id).join(t("archive.confirm.listSeparator"));
|
|
3516
|
+
const plural = n === 1 ? "one" : "other";
|
|
3517
|
+
switch (entry.kind) {
|
|
3518
|
+
case "turn": return t("archive.confirm.turn");
|
|
3519
|
+
case "subagent": return t(`archive.confirm.subagents.${plural}`, {
|
|
3520
|
+
n,
|
|
3521
|
+
names
|
|
3522
|
+
});
|
|
3523
|
+
case "job": return t(`archive.confirm.jobs.${plural}`, {
|
|
3524
|
+
n,
|
|
3525
|
+
names
|
|
3526
|
+
});
|
|
3527
|
+
case "schedule": return t(`archive.confirm.schedules.${plural}`, {
|
|
3528
|
+
n,
|
|
3529
|
+
names
|
|
3530
|
+
});
|
|
3531
|
+
default: return t(`archive.confirm.other.${plural}`, {
|
|
3532
|
+
kind: entry.kind,
|
|
3533
|
+
n
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
//#endregion
|
|
3538
|
+
//#region lib/types/client/session-actions/derived.js
|
|
3539
|
+
/**
|
|
3540
|
+
* Project one observable into another, recomputing only when the source
|
|
3541
|
+
* snapshot changes identity, so consumers that select from the projection
|
|
3542
|
+
* (a Set lookup per row) never rebuild it per read.
|
|
3543
|
+
* @param source - the observable to project.
|
|
3544
|
+
* @param project - pure projection of one source snapshot.
|
|
3545
|
+
* @returns the projected observable, subscribing through the source.
|
|
3546
|
+
*/
|
|
3547
|
+
function derive(source, project) {
|
|
3548
|
+
let seen;
|
|
3549
|
+
let value;
|
|
3550
|
+
return {
|
|
3551
|
+
getSnapshot: () => {
|
|
3552
|
+
const snapshot = source.getSnapshot();
|
|
3553
|
+
if (value === void 0 || snapshot !== seen) {
|
|
3554
|
+
seen = snapshot;
|
|
3555
|
+
value = project(snapshot);
|
|
3556
|
+
}
|
|
3557
|
+
return value;
|
|
3558
|
+
},
|
|
3559
|
+
subscribe: (listener) => source.subscribe(listener)
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
//#endregion
|
|
3563
|
+
//#region lib/types/client/session-actions/ForkSession.js
|
|
3564
|
+
/** The fork action: one `sidebar.workspaces.session.menu.item` row. */
|
|
3565
|
+
/**
|
|
3566
|
+
* Menu row (order 300): fork at the Session's last completed turn; the child
|
|
3567
|
+
* arrives through the Host list beside its source.
|
|
3568
|
+
* @param props - owner share, menu open state, and the fork share.
|
|
3569
|
+
* @returns the row.
|
|
3570
|
+
*/
|
|
3571
|
+
function ForkSessionMenuItem({ sessionId, useMenuOpenState, forkSession, t }) {
|
|
3572
|
+
const [, setMenuOpen] = useMenuOpenState();
|
|
3573
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
3574
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
|
|
3575
|
+
onSelect: () => {
|
|
3576
|
+
setMenuOpen(false);
|
|
3577
|
+
forkSession(sessionId);
|
|
3578
|
+
},
|
|
3579
|
+
children: t("menu.fork")
|
|
3580
|
+
});
|
|
3581
|
+
}
|
|
3582
|
+
//#endregion
|
|
3583
|
+
//#region lib/types/client/session-actions/PinSession.js
|
|
2712
3584
|
/**
|
|
2713
|
-
*
|
|
2714
|
-
*
|
|
2715
|
-
*
|
|
3585
|
+
* The pin action: a `sidebar.workspaces.session.menu.item` row and a
|
|
3586
|
+
* `sidebar.workspaces.session.row.action` button over one injected behavior.
|
|
3587
|
+
* Pin and archive are mutually exclusive on the Host, so the action reads both
|
|
3588
|
+
* sets and does not offer itself on an archived row; what a pin does beyond
|
|
3589
|
+
* the Host call (fronting the Session in its saved orders, the failure
|
|
3590
|
+
* notice) lives in the injected callbacks, not here.
|
|
2716
3591
|
*/
|
|
2717
|
-
/**
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3592
|
+
/** The row's pin and archive membership, one Set lookup each. */
|
|
3593
|
+
function usePinState({ sessionId, usePinned, useArchived }) {
|
|
3594
|
+
return {
|
|
3595
|
+
pinned: usePinned((pinned) => pinned.has(sessionId)),
|
|
3596
|
+
archived: useArchived((archived) => archived.has(sessionId))
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Menu row (order 100): pin or unpin by the row's current state; absent on archived rows.
|
|
3601
|
+
* @param props - owner share, the pin share, and the menu open state.
|
|
3602
|
+
* @returns the row, or null for an archived Session.
|
|
3603
|
+
*/
|
|
3604
|
+
function PinSessionMenuItem(props) {
|
|
3605
|
+
const { sessionId, useMenuOpenState, pinSession, unpinSession, t } = props;
|
|
3606
|
+
const [, setMenuOpen] = useMenuOpenState();
|
|
3607
|
+
const { pinned, archived } = usePinState(props);
|
|
3608
|
+
if (archived) return null;
|
|
3609
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
3610
|
+
icon: pinned ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinFillRegular, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinOutlineRegular, {}),
|
|
3611
|
+
onSelect: () => {
|
|
3612
|
+
setMenuOpen(false);
|
|
3613
|
+
(pinned ? unpinSession : pinSession)(sessionId);
|
|
3614
|
+
},
|
|
3615
|
+
children: t(pinned ? "menu.unpinSession" : "menu.pinSession")
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* Hover button (order 200, rightmost: it lands where the rest-state pin marker sits); absent on archived rows.
|
|
3620
|
+
* @param props - owner share and the pin share.
|
|
3621
|
+
* @returns the button, or null for an archived Session.
|
|
3622
|
+
*/
|
|
3623
|
+
function PinSessionRowButton(props) {
|
|
3624
|
+
const { sessionId, pinSession, unpinSession, t } = props;
|
|
3625
|
+
const { pinned, archived } = usePinState(props);
|
|
3626
|
+
if (archived) return null;
|
|
3627
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
3628
|
+
label: t(pinned ? "actions.unpin" : "actions.pin"),
|
|
3629
|
+
side: "bottom",
|
|
3630
|
+
align: "end",
|
|
3631
|
+
delayMs: 500,
|
|
3632
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
3633
|
+
type: "button",
|
|
3634
|
+
className: Rows_module_css_default.iconButton,
|
|
3635
|
+
"aria-label": t(pinned ? "menu.unpinSession" : "menu.pinSession"),
|
|
3636
|
+
onClick: () => {
|
|
3637
|
+
(pinned ? unpinSession : pinSession)(sessionId);
|
|
3638
|
+
},
|
|
3639
|
+
children: pinned ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinFillRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPinOutlineRegular, { size: 14 })
|
|
3640
|
+
})
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
//#endregion
|
|
3644
|
+
//#region lib/types/client/session-actions/RenameSession.js
|
|
3645
|
+
/**
|
|
3646
|
+
* The rename action: a `sidebar.workspaces.session.menu.item` row that raises
|
|
3647
|
+
* the rename request, and the `shell.overlay` dialog entry that answers it.
|
|
3648
|
+
* The dialog lives outside the row menu because the row unmounts with the
|
|
3649
|
+
* menu; the browser raises the same request from a title double-click.
|
|
3650
|
+
*/
|
|
3651
|
+
/**
|
|
3652
|
+
* Menu row (order 200): ask for the rename dialog, seeded with the row's current title.
|
|
3653
|
+
* @param props - owner share, menu open state, and the rename share.
|
|
3654
|
+
* @returns the row.
|
|
3655
|
+
*/
|
|
3656
|
+
function RenameSessionMenuItem({ sessionId, displayTitle, useMenuOpenState, requestSessionRename, t }) {
|
|
3657
|
+
const [, setMenuOpen] = useMenuOpenState();
|
|
3658
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
|
|
3659
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular, {}),
|
|
3660
|
+
onSelect: () => {
|
|
3661
|
+
setMenuOpen(false);
|
|
3662
|
+
requestSessionRename(sessionId, displayTitle);
|
|
3663
|
+
},
|
|
3664
|
+
children: t("rename")
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* The `shell.overlay` entry: nothing while no rename is requested, otherwise
|
|
3669
|
+
* one dialog per request (keyed by the Session, so a new request starts a
|
|
3670
|
+
* fresh draft). Sessions have no client-side name-conflict rule (the host
|
|
3671
|
+
* normalizes), and unlike Workspace rename an unchanged title is NOT
|
|
3672
|
+
* blocked: confirming the current automatic title is the gesture that pins it.
|
|
3673
|
+
* @param props - the request hook, its settlement, the rename hop, and the locale seat.
|
|
3674
|
+
* @returns the open dialog, or null.
|
|
3675
|
+
*/
|
|
3676
|
+
function SessionRenameDialog({ useRenameRequest, settleSessionRename, renameSession, t }) {
|
|
3677
|
+
const request = useRenameRequest((pending) => pending);
|
|
3678
|
+
if (request === null) return null;
|
|
3679
|
+
return (0, react_jsx_runtime.jsx)(RenameForm, {
|
|
3680
|
+
request,
|
|
3681
|
+
renameSession,
|
|
3682
|
+
onSettle: settleSessionRename,
|
|
3683
|
+
t
|
|
3684
|
+
}, request.sessionId);
|
|
3685
|
+
}
|
|
3686
|
+
/** One request's dialog: the draft seeds from the request on mount; in-flight and error state die with it. */
|
|
3687
|
+
function RenameForm({ request, renameSession, onSettle, t }) {
|
|
3688
|
+
const [draft, setDraft] = (0, react.useState)(request.currentTitle);
|
|
3689
|
+
const [renaming, setRenaming] = (0, react.useState)(false);
|
|
3690
|
+
const [error, setError] = (0, react.useState)(null);
|
|
3691
|
+
const composingRef = (0, react.useRef)(false);
|
|
3692
|
+
const trimmed = draft.trim();
|
|
3693
|
+
const blocked = renaming || trimmed === "";
|
|
3694
|
+
const close = () => {
|
|
3695
|
+
if (renaming) return;
|
|
3696
|
+
onSettle();
|
|
3697
|
+
};
|
|
3698
|
+
const confirm = () => {
|
|
3699
|
+
if (blocked) return;
|
|
3700
|
+
setRenaming(true);
|
|
3701
|
+
setError(null);
|
|
3702
|
+
renameSession(request.sessionId, trimmed).then(() => {
|
|
3703
|
+
setRenaming(false);
|
|
3704
|
+
onSettle();
|
|
3705
|
+
}).catch((reason) => {
|
|
3706
|
+
setRenaming(false);
|
|
3707
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
3708
|
+
});
|
|
3709
|
+
};
|
|
3710
|
+
return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3711
|
+
open: true,
|
|
3712
|
+
onClose: close,
|
|
3713
|
+
closeLabel: t("close"),
|
|
3714
|
+
title: t("rename.session.title"),
|
|
3715
|
+
footer: (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3716
|
+
variant: "outline",
|
|
3717
|
+
disabled: renaming,
|
|
3718
|
+
onClick: close,
|
|
3719
|
+
children: t("cancel")
|
|
3720
|
+
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
3721
|
+
variant: "primary",
|
|
3722
|
+
disabled: blocked,
|
|
3723
|
+
onClick: confirm,
|
|
3724
|
+
children: t("rename")
|
|
3725
|
+
})] }),
|
|
3726
|
+
children: [(0, react_jsx_runtime.jsx)("input", {
|
|
3727
|
+
className: WorkspaceBrowser_module_css_default.renameInput,
|
|
3728
|
+
value: draft,
|
|
3729
|
+
"aria-label": t("field.sessionName"),
|
|
3730
|
+
autoFocus: true,
|
|
3731
|
+
disabled: renaming,
|
|
3732
|
+
onFocus: (e) => {
|
|
3733
|
+
e.target.select();
|
|
3734
|
+
},
|
|
3735
|
+
onChange: (e) => {
|
|
3736
|
+
setDraft(e.target.value);
|
|
3737
|
+
setError(null);
|
|
3738
|
+
},
|
|
3739
|
+
onCompositionStart: () => {
|
|
3740
|
+
composingRef.current = true;
|
|
3741
|
+
},
|
|
3742
|
+
onCompositionEnd: () => {
|
|
3743
|
+
composingRef.current = false;
|
|
3744
|
+
},
|
|
3745
|
+
onKeyDown: (e) => {
|
|
3746
|
+
if (e.key === "Enter" && !composingRef.current) {
|
|
3747
|
+
e.preventDefault();
|
|
3748
|
+
confirm();
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}), error !== null && (0, react_jsx_runtime.jsx)("div", {
|
|
3752
|
+
className: WorkspaceBrowser_module_css_default.renameError,
|
|
3753
|
+
role: "alert",
|
|
3754
|
+
children: error
|
|
3755
|
+
})]
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
//#endregion
|
|
3759
|
+
//#region lib/types/client/session-actions/RowActionToast.js
|
|
3760
|
+
/**
|
|
3761
|
+
* The `shell.overlay` entry for Workspace and Session notices.
|
|
3762
|
+
* One notice is visible at a time; a parent rerender does not extend its hold.
|
|
3763
|
+
*/
|
|
3764
|
+
/**
|
|
3765
|
+
* Hold for the notices that take longer to read than a one-line warning: the
|
|
3766
|
+
* actionable archived notice (two buttons to react to) and a refused Session
|
|
3767
|
+
* creation, which quotes the Host's reason.
|
|
3768
|
+
*/
|
|
3769
|
+
const LONG_TOAST_HOLD_MS = 6e3;
|
|
3770
|
+
/**
|
|
3771
|
+
* Render the current notice: the archived and stopped-and-archived notices
|
|
3772
|
+
* with their undo and show-archived actions on a 6 s hold, a refused Session
|
|
3773
|
+
* creation with the Host's reason on the same hold, or a plain warning for a
|
|
3774
|
+
* failed pin, an archived row that was clicked, or default Workspace creation.
|
|
3775
|
+
* @param props - the notice hook, its dismissal, the two archived-notice actions, and the locale seat.
|
|
3776
|
+
* @returns the notice on display, or null.
|
|
3777
|
+
*/
|
|
3778
|
+
function RowActionToast({ useToast, dismissToast, undoArchive, showArchived, t }) {
|
|
3779
|
+
const toast = useToast((current) => current);
|
|
3780
|
+
if (toast === null) return null;
|
|
3781
|
+
if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
|
|
3782
|
+
const { sessionId } = toast;
|
|
3783
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
3784
|
+
text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
|
|
3785
|
+
tone: "success",
|
|
3786
|
+
holdMs: LONG_TOAST_HOLD_MS,
|
|
3787
|
+
actions: [{
|
|
3788
|
+
label: t("toast.archivedUndo"),
|
|
3789
|
+
onClick: () => {
|
|
3790
|
+
dismissToast();
|
|
3791
|
+
undoArchive(sessionId);
|
|
3792
|
+
}
|
|
3793
|
+
}, {
|
|
3794
|
+
prefix: t("toast.archivedOr"),
|
|
3795
|
+
label: t("toast.archivedFilter"),
|
|
3796
|
+
onClick: () => {
|
|
3797
|
+
dismissToast();
|
|
3798
|
+
showArchived();
|
|
3799
|
+
}
|
|
3800
|
+
}],
|
|
3801
|
+
onDone: dismissToast
|
|
3802
|
+
}, `toast-${String(toast.seq)}`);
|
|
3803
|
+
}
|
|
3804
|
+
if (toast.kind === "createFailed") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
3805
|
+
text: t("toast.createFailed", { message: toast.message }),
|
|
3806
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
3807
|
+
holdMs: LONG_TOAST_HOLD_MS,
|
|
3808
|
+
onDone: dismissToast
|
|
3809
|
+
}, `toast-${String(toast.seq)}`);
|
|
3810
|
+
return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
3811
|
+
text: plainNoticeText(toast, t),
|
|
3812
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
|
|
3813
|
+
onDone: dismissToast
|
|
3814
|
+
}, `toast-${String(toast.seq)}`);
|
|
3815
|
+
}
|
|
3816
|
+
/** The copy of one plain warning, keyed by the notice kind the union closes over. */
|
|
3817
|
+
function plainNoticeText(toast, t) {
|
|
3818
|
+
switch (toast.kind) {
|
|
3819
|
+
case "pinFailed": return t("toast.pinFailed");
|
|
3820
|
+
case "unpinFailed": return t("toast.unpinFailed");
|
|
3821
|
+
case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
|
|
3822
|
+
case "archivedNotOpenable": return t("toast.archivedNotOpenable");
|
|
3823
|
+
/* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
|
|
3824
|
+
default: return assertNever$1(toast);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
2851
3827
|
//#endregion
|
|
2852
3828
|
//#region lib/types/client/index.js
|
|
2853
3829
|
/** Dictionary namespace owned by this plugin. */
|
|
@@ -2878,7 +3854,21 @@ window.__ModuleLoader__.load({
|
|
|
2878
3854
|
function apply(ctx) {
|
|
2879
3855
|
const sessions = ctx.get("sessions");
|
|
2880
3856
|
const workspaces = ctx.get("workspaces");
|
|
2881
|
-
const
|
|
3857
|
+
const viewHandle = createWorkspaceViewStore();
|
|
3858
|
+
const viewInstance = viewHandle.create();
|
|
3859
|
+
const viewStore = {
|
|
3860
|
+
...viewHandle,
|
|
3861
|
+
create: () => viewInstance
|
|
3862
|
+
};
|
|
3863
|
+
const rowToast = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(null);
|
|
3864
|
+
let toastSeq = 0;
|
|
3865
|
+
const notify = (toast) => {
|
|
3866
|
+
rowToast.set({
|
|
3867
|
+
...toast,
|
|
3868
|
+
seq: ++toastSeq
|
|
3869
|
+
});
|
|
3870
|
+
};
|
|
3871
|
+
const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
|
|
2882
3872
|
ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
|
|
2883
3873
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
2884
3874
|
zh,
|
|
@@ -2902,6 +3892,99 @@ window.__ModuleLoader__.load({
|
|
|
2902
3892
|
const openSession = (sessionId) => {
|
|
2903
3893
|
uiWorkspace.openSession(sessionId);
|
|
2904
3894
|
};
|
|
3895
|
+
const pinnedSet = derive(workspaces.list, (snapshot) => new Set(snapshot.pinnedSessionIds));
|
|
3896
|
+
const archivedSet = derive(workspaces.list, (snapshot) => new Set(snapshot.archivedSessionIds));
|
|
3897
|
+
const renameRequest = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(null);
|
|
3898
|
+
const archiveRequest = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(null);
|
|
3899
|
+
const requestSessionRename = (sessionId, currentTitle) => {
|
|
3900
|
+
renameRequest.set({
|
|
3901
|
+
sessionId,
|
|
3902
|
+
currentTitle
|
|
3903
|
+
});
|
|
3904
|
+
};
|
|
3905
|
+
const unarchiveSession = (sessionId) => {
|
|
3906
|
+
uiWorkspace.unarchiveSession(sessionId).catch((reason) => {
|
|
3907
|
+
console.warn("session unarchive rejected:", reason);
|
|
3908
|
+
});
|
|
3909
|
+
};
|
|
3910
|
+
const renameSession = async (sessionId, title) => {
|
|
3911
|
+
const result = await sessions.using(sessionId, { source: "workspaceOperation" }, (reference) => reference.binding.session.rename(title));
|
|
3912
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
3913
|
+
};
|
|
3914
|
+
const pinInjected = () => ({
|
|
3915
|
+
hooks: {
|
|
3916
|
+
pinned: pinnedSet,
|
|
3917
|
+
archived: archivedSet
|
|
3918
|
+
},
|
|
3919
|
+
pinSession: (sessionId) => {
|
|
3920
|
+
uiWorkspace.pinSession(sessionId).catch(() => {
|
|
3921
|
+
notify({ kind: "pinFailed" });
|
|
3922
|
+
});
|
|
3923
|
+
},
|
|
3924
|
+
unpinSession: (sessionId) => {
|
|
3925
|
+
uiWorkspace.unpinSession(sessionId).catch(() => {
|
|
3926
|
+
notify({ kind: "unpinFailed" });
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
const archiveInjected = () => ({
|
|
3931
|
+
hooks: { archived: archivedSet },
|
|
3932
|
+
archiveSession: (sessionId) => {
|
|
3933
|
+
uiWorkspace.archiveSession(sessionId).then(() => {
|
|
3934
|
+
notify({
|
|
3935
|
+
kind: "archived",
|
|
3936
|
+
sessionId
|
|
3937
|
+
});
|
|
3938
|
+
}).catch((reason) => {
|
|
3939
|
+
const activity = activeSessionRefusal(reason);
|
|
3940
|
+
if (activity === void 0) {
|
|
3941
|
+
console.warn("session archive rejected:", reason);
|
|
3942
|
+
return;
|
|
3943
|
+
}
|
|
3944
|
+
const displayTitle = sessions.list.getSnapshot().byId[sessionId]?.displayTitle ?? sessionId;
|
|
3945
|
+
archiveRequest.set({
|
|
3946
|
+
sessionId,
|
|
3947
|
+
displayTitle,
|
|
3948
|
+
activity
|
|
3949
|
+
});
|
|
3950
|
+
});
|
|
3951
|
+
},
|
|
3952
|
+
unarchiveSession
|
|
3953
|
+
});
|
|
3954
|
+
const archiveConfirmInjected = () => ({
|
|
3955
|
+
hooks: { archiveRequest },
|
|
3956
|
+
settleSessionArchive: () => {
|
|
3957
|
+
archiveRequest.set(null);
|
|
3958
|
+
},
|
|
3959
|
+
stopAndArchiveSession: async (sessionId) => {
|
|
3960
|
+
await uiWorkspace.archiveSession(sessionId, { stopActivity: true });
|
|
3961
|
+
notify({
|
|
3962
|
+
kind: "stoppedAndArchived",
|
|
3963
|
+
sessionId
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
const forkInjected = () => ({ forkSession: (sessionId) => {
|
|
3968
|
+
uiWorkspace.forkSession(sessionId).catch(() => {});
|
|
3969
|
+
} });
|
|
3970
|
+
const renameInjected = () => ({ requestSessionRename });
|
|
3971
|
+
const renameDialogInjected = () => ({
|
|
3972
|
+
hooks: { renameRequest },
|
|
3973
|
+
settleSessionRename: () => {
|
|
3974
|
+
renameRequest.set(null);
|
|
3975
|
+
},
|
|
3976
|
+
renameSession
|
|
3977
|
+
});
|
|
3978
|
+
const rowToastInjected = () => ({
|
|
3979
|
+
hooks: { toast: rowToast },
|
|
3980
|
+
dismissToast: () => {
|
|
3981
|
+
rowToast.set(null);
|
|
3982
|
+
},
|
|
3983
|
+
undoArchive: unarchiveSession,
|
|
3984
|
+
showArchived: () => {
|
|
3985
|
+
viewInstance.actions.setArchivedFilter("show");
|
|
3986
|
+
}
|
|
3987
|
+
});
|
|
2905
3988
|
const browserInjected = () => ({
|
|
2906
3989
|
startSession: (workspaceId) => {
|
|
2907
3990
|
uiWorkspace.startSession(workspaceId);
|
|
@@ -2909,12 +3992,9 @@ window.__ModuleLoader__.load({
|
|
|
2909
3992
|
open: openSession,
|
|
2910
3993
|
searchSessions,
|
|
2911
3994
|
searchResultLimit: sessions.searchResultLimit,
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
},
|
|
2916
|
-
forkSession: (sessionId) => {
|
|
2917
|
-
uiWorkspace.forkSession(sessionId).catch(() => {});
|
|
3995
|
+
requestSessionRename,
|
|
3996
|
+
notifyArchivedNotOpenable: () => {
|
|
3997
|
+
notify({ kind: "archivedNotOpenable" });
|
|
2918
3998
|
},
|
|
2919
3999
|
renameWorkspace: async (workspaceId, title) => {
|
|
2920
4000
|
await workspaces.rename(workspaceId, title);
|
|
@@ -2925,8 +4005,8 @@ window.__ModuleLoader__.load({
|
|
|
2925
4005
|
insertWorkspaceBefore: async (workspaceId, beforeWorkspaceId) => {
|
|
2926
4006
|
await workspaces.insertBefore(workspaceId, beforeWorkspaceId);
|
|
2927
4007
|
},
|
|
2928
|
-
|
|
2929
|
-
await uiWorkspace.
|
|
4008
|
+
unarchiveSession: async (sessionId) => {
|
|
4009
|
+
await uiWorkspace.unarchiveSession(sessionId);
|
|
2930
4010
|
},
|
|
2931
4011
|
createWorkspace: (input) => workspaces.create(input),
|
|
2932
4012
|
hooks: {
|
|
@@ -2940,14 +4020,91 @@ window.__ModuleLoader__.load({
|
|
|
2940
4020
|
});
|
|
2941
4021
|
ctx.slots.inject("sidebar.workspaces", () => ctx.slots.register({
|
|
2942
4022
|
name: "sidebar.workspaces",
|
|
2943
|
-
children: {
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
4023
|
+
children: {
|
|
4024
|
+
"sidebar.workspaces.directoryFlow": {
|
|
4025
|
+
kind: "single",
|
|
4026
|
+
scope: "root"
|
|
4027
|
+
},
|
|
4028
|
+
"sidebar.workspaces.session.menu.item": {
|
|
4029
|
+
kind: "list",
|
|
4030
|
+
scope: "root",
|
|
4031
|
+
inject: { hooks: { menuOpenState: menuOpenStateFactory } }
|
|
4032
|
+
},
|
|
4033
|
+
"sidebar.workspaces.session.row.action": {
|
|
4034
|
+
kind: "list",
|
|
4035
|
+
scope: "root"
|
|
4036
|
+
}
|
|
4037
|
+
},
|
|
4038
|
+
store: viewStore,
|
|
2948
4039
|
inject: browserInjected,
|
|
2949
4040
|
locale: NS
|
|
2950
4041
|
}, WorkspaceBrowser));
|
|
4042
|
+
ctx.slots.inject("sidebar.workspaces.session.menu.item", function* () {
|
|
4043
|
+
yield ctx.slots.register({
|
|
4044
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
4045
|
+
id: "pin",
|
|
4046
|
+
order: 100,
|
|
4047
|
+
locale: NS,
|
|
4048
|
+
inject: pinInjected
|
|
4049
|
+
}, PinSessionMenuItem);
|
|
4050
|
+
yield ctx.slots.register({
|
|
4051
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
4052
|
+
id: "rename",
|
|
4053
|
+
order: 200,
|
|
4054
|
+
locale: NS,
|
|
4055
|
+
inject: renameInjected
|
|
4056
|
+
}, RenameSessionMenuItem);
|
|
4057
|
+
yield ctx.slots.register({
|
|
4058
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
4059
|
+
id: "fork",
|
|
4060
|
+
order: 300,
|
|
4061
|
+
locale: NS,
|
|
4062
|
+
inject: forkInjected
|
|
4063
|
+
}, ForkSessionMenuItem);
|
|
4064
|
+
yield ctx.slots.register({
|
|
4065
|
+
name: "sidebar.workspaces.session.menu.item",
|
|
4066
|
+
id: "archive",
|
|
4067
|
+
order: 400,
|
|
4068
|
+
locale: NS,
|
|
4069
|
+
inject: archiveInjected
|
|
4070
|
+
}, ArchiveSessionMenuItem);
|
|
4071
|
+
});
|
|
4072
|
+
ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
|
|
4073
|
+
yield ctx.slots.register({
|
|
4074
|
+
name: "sidebar.workspaces.session.row.action",
|
|
4075
|
+
id: "archive",
|
|
4076
|
+
order: 100,
|
|
4077
|
+
locale: NS,
|
|
4078
|
+
inject: archiveInjected
|
|
4079
|
+
}, ArchiveSessionRowButton);
|
|
4080
|
+
yield ctx.slots.register({
|
|
4081
|
+
name: "sidebar.workspaces.session.row.action",
|
|
4082
|
+
id: "pin",
|
|
4083
|
+
order: 200,
|
|
4084
|
+
locale: NS,
|
|
4085
|
+
inject: pinInjected
|
|
4086
|
+
}, PinSessionRowButton);
|
|
4087
|
+
});
|
|
4088
|
+
ctx.slots.inject("shell.overlay", function* () {
|
|
4089
|
+
yield ctx.slots.register({
|
|
4090
|
+
name: "shell.overlay",
|
|
4091
|
+
id: "workspace.session-rename",
|
|
4092
|
+
locale: NS,
|
|
4093
|
+
inject: renameDialogInjected
|
|
4094
|
+
}, SessionRenameDialog);
|
|
4095
|
+
yield ctx.slots.register({
|
|
4096
|
+
name: "shell.overlay",
|
|
4097
|
+
id: "workspace.session-archive",
|
|
4098
|
+
locale: NS,
|
|
4099
|
+
inject: archiveConfirmInjected
|
|
4100
|
+
}, SessionArchiveConfirmDialog);
|
|
4101
|
+
yield ctx.slots.register({
|
|
4102
|
+
name: "shell.overlay",
|
|
4103
|
+
id: "workspace.row-toast",
|
|
4104
|
+
locale: NS,
|
|
4105
|
+
inject: rowToastInjected
|
|
4106
|
+
}, RowActionToast);
|
|
4107
|
+
});
|
|
2951
4108
|
ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
|
|
2952
4109
|
name: "conversation.hero.workspace",
|
|
2953
4110
|
children: { "conversation.hero.workspace.directoryFlow": {
|
|
@@ -2958,6 +4115,16 @@ window.__ModuleLoader__.load({
|
|
|
2958
4115
|
locale: NS
|
|
2959
4116
|
}, WorkspacePicker));
|
|
2960
4117
|
}
|
|
4118
|
+
/**
|
|
4119
|
+
* The activity a Host `workspace/session-active` refusal reported, or nothing
|
|
4120
|
+
* for any other failure. The class identity check goes by name: client plugin
|
|
4121
|
+
* bundles do not share error-class identity.
|
|
4122
|
+
*/
|
|
4123
|
+
function activeSessionRefusal(reason) {
|
|
4124
|
+
if (!(reason instanceof Error) || reason.name !== "WorkspaceArchiveError") return void 0;
|
|
4125
|
+
const { rpcError } = reason;
|
|
4126
|
+
return rpcError.code === "workspace/session-active" ? rpcError.details.activity : void 0;
|
|
4127
|
+
}
|
|
2961
4128
|
//#endregion
|
|
2962
4129
|
exports.apply = apply;
|
|
2963
4130
|
exports.inject = inject;
|