@deepseek-ai/dsh-client-ui-workspace 0.2.1-alpha.2 → 0.2.1-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,10 +5,10 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
9
10
  let react_jsx_runtime = require("react/jsx-runtime");
10
11
  let react = require("react");
11
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
12
12
  //#region lib/types/client/contract/slots.js
13
13
  /**
14
14
  * Bind the row's render occurrence into the entries' `useMenuOpenState` hook:
@@ -30,10 +30,8 @@ window.__ModuleLoader__.load({
30
30
  searchRequest: 0,
31
31
  addRequested: false,
32
32
  directoryBusy: false,
33
- renameTarget: null,
34
- forkError: null
33
+ renameTarget: null
35
34
  });
36
- let forkErrorSeq = 0;
37
35
  return {
38
36
  state,
39
37
  search: () => {
@@ -74,22 +72,6 @@ window.__ModuleLoader__.load({
74
72
  ...state.getSnapshot(),
75
73
  renameTarget: null
76
74
  });
77
- },
78
- forkFailed: (reason) => {
79
- forkErrorSeq += 1;
80
- state.set({
81
- ...state.getSnapshot(),
82
- forkError: {
83
- reason,
84
- seq: forkErrorSeq
85
- }
86
- });
87
- },
88
- dismissForkError: () => {
89
- state.set({
90
- ...state.getSnapshot(),
91
- forkError: null
92
- });
93
75
  }
94
76
  };
95
77
  }
@@ -100,8 +82,9 @@ window.__ModuleLoader__.load({
100
82
  * @param navigation - session creation and forking from the pointer controls' navigation service.
101
83
  * @param controls - browser-owned opening requests.
102
84
  * @param archiveSession - shared archive action, including running-work confirmation and notices.
85
+ * @param reportForkFailure - publish a classified failure through the frame-wide notice channel.
103
86
  */
104
- function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession) {
87
+ function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession, reportForkFailure) {
105
88
  const t = ctx.locale.bind("workspace");
106
89
  const current = () => Object.values(ctx.sessions.list.getSnapshot().byId).find((row) => (row.retainedBy.mainView ?? 0) > 0);
107
90
  const addReason = () => ctx.slots.entries("sidebar.workspaces.directoryFlow").length === 0 ? t("shortcut.noPicker") : controls.state.getSnapshot().directoryBusy ? t("shortcut.directoryBusy") : null;
@@ -184,7 +167,7 @@ window.__ModuleLoader__.load({
184
167
  run: () => {
185
168
  navigation.forkSession(target.id).catch((error) => {
186
169
  const unavailable = error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/fork-unavailable";
187
- controls.forkFailed(unavailable ? "unavailable" : "failed");
170
+ reportForkFailure(unavailable ? "unavailable" : "failed");
188
171
  if (!unavailable) console.warn("session fork rejected:", error);
189
172
  });
190
173
  }
@@ -1380,6 +1363,7 @@ window.__ModuleLoader__.load({
1380
1363
  sessions;
1381
1364
  view;
1382
1365
  notify;
1366
+ /** The latest in-flight connection per Workspace; a request joins it only when both name the same preset. */
1383
1367
  connecting = /* @__PURE__ */ new Map();
1384
1368
  lifetime = new AbortController();
1385
1369
  selection = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({}, { persist: { name: "dsh.sessions.current" } });
@@ -1410,47 +1394,84 @@ window.__ModuleLoader__.load({
1410
1394
  };
1411
1395
  }, "ui-workspace: Workspace navigation policy");
1412
1396
  }
1413
- connectWorkspace(workspaceId) {
1414
- return this.connectBlank(workspaceId, true);
1397
+ connectWorkspace(workspaceId, options = {}) {
1398
+ return this.connectBlank(workspaceId, options);
1415
1399
  }
1400
+ /**
1401
+ * @param reuse - preset for the reused or created blank; absent creates a fresh Session.
1402
+ */
1416
1403
  async connectBlank(workspaceId, reuse) {
1417
1404
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
1418
1405
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
1419
- if (!reuse) return this.sessions.create({ workspaceId });
1406
+ if (reuse === void 0) return this.sessions.create({ workspaceId });
1420
1407
  const inflight = this.connecting.get(workspaceId);
1421
- if (inflight !== void 0) return inflight;
1422
- const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
1423
- this.connecting.delete(workspaceId);
1408
+ if (inflight !== void 0 && inflight.agentPreset === reuse.agentPreset) return inflight.attempt;
1409
+ const attempt = this.createInWorkspace(workspace, reuse).finally(() => {
1410
+ if (this.connecting.get(workspaceId)?.attempt === attempt) this.connecting.delete(workspaceId);
1411
+ });
1412
+ this.connecting.set(workspaceId, {
1413
+ agentPreset: reuse.agentPreset,
1414
+ attempt
1424
1415
  });
1425
- this.connecting.set(workspaceId, attempt);
1426
1416
  return attempt;
1427
1417
  }
1428
- reuseOrCreateBlank(workspace) {
1418
+ createInWorkspace(workspace, options) {
1429
1419
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
1430
1420
  const sessions = this.sessions.list.getSnapshot();
1431
- for (const id of sessions.ids) {
1421
+ const blank = sessions.ids.find((id) => {
1432
1422
  const summary = sessions.byId[id];
1433
- if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
1434
- return this.reuseBlank(workspace.workspaceId, id);
1435
- }
1436
- return this.sessions.create({ workspaceId: workspace.workspaceId });
1423
+ return summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(id) && !archived.includes(id);
1424
+ });
1425
+ return this.createNamed({
1426
+ workspaceId: workspace.workspaceId,
1427
+ ...blank === void 0 ? {} : { sessionId: blank },
1428
+ ...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
1429
+ });
1437
1430
  }
1438
- async reuseBlank(workspaceId, sessionId) {
1431
+ /**
1432
+ * Create through the Host. A named blank the Host declined may still read as
1433
+ * blank from stale cached list metadata, which hides it from the Session tree;
1434
+ * its projections are reloaded so the row reflects its log.
1435
+ */
1436
+ async createNamed(request) {
1437
+ const sessionId = await this.sessions.create(request);
1438
+ const named = request.sessionId;
1439
+ if (named !== void 0 && named !== sessionId) this.sessions.refreshProjections(named).catch((reason) => {
1440
+ console.warn("declined blank Session projection reload failed:", reason);
1441
+ });
1442
+ return sessionId;
1443
+ }
1444
+ /**
1445
+ * Reopen a saved blank Session under the preset its log recorded, or under
1446
+ * the Host default once that preset no longer activates. Listed values are
1447
+ * cached hints that can lag the log after an unclean Host exit, so the
1448
+ * Session's projections are reloaded before the preset is read; a Session
1449
+ * that still awaits a format migration reloads only those cached hints.
1450
+ */
1451
+ async restoreBlank(workspaceId, sessionId) {
1452
+ await this.sessions.refreshProjections(sessionId);
1453
+ const recorded = this.sessions.list.getSnapshot().byId[sessionId]?.projectionValues?.agentPreset;
1454
+ const request = {
1455
+ workspaceId,
1456
+ sessionId
1457
+ };
1458
+ if (typeof recorded !== "string") return this.createNamed(request);
1439
1459
  try {
1440
- return await this.sessions.create({
1441
- workspaceId,
1442
- sessionId
1460
+ return await this.createNamed({
1461
+ ...request,
1462
+ agentPreset: recorded
1443
1463
  });
1444
1464
  } catch (error) {
1445
- if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
1446
- return this.sessions.create({ workspaceId });
1465
+ const code = sessionCreateErrorOf(error)?.rpcError.code;
1466
+ if (code !== "agent-preset/not-found" && code !== "agent-preset/invalid") throw error;
1467
+ return this.createNamed(request);
1447
1468
  }
1448
1469
  }
1449
1470
  openSession(target) {
1450
1471
  this.replaceMain(target, this.lifetime.signal, "reveal");
1451
1472
  }
1452
- openWorkspace(workspaceId, beforeOpen) {
1453
- return this.openWorkspaceSession(workspaceId, beforeOpen, true);
1473
+ openWorkspace(workspaceId, beforeOpen, options = {}) {
1474
+ return this.openWorkspaceSession(workspaceId, beforeOpen, options);
1454
1475
  }
1455
1476
  async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
1456
1477
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
@@ -1476,7 +1497,8 @@ window.__ModuleLoader__.load({
1476
1497
  });
1477
1498
  }
1478
1499
  startSession(workspaceId, options) {
1479
- const draftOptions = options === void 0 ? void 0 : { ...options };
1500
+ const { agentPreset, ...draft } = options ?? {};
1501
+ const draftOptions = options === void 0 ? void 0 : draft;
1480
1502
  const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
1481
1503
  const workspace = this.workspaces.list.getSnapshot();
1482
1504
  const sessions = this.sessions.list.getSnapshot();
@@ -1495,11 +1517,11 @@ window.__ModuleLoader__.load({
1495
1517
  this.clearMain();
1496
1518
  return;
1497
1519
  }
1498
- (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, false) : this.openWorkspace(target, initializeDraft ? (id) => {
1520
+ (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (id) => {
1499
1521
  const binding = this.sessions.binding(id);
1500
1522
  if (binding === void 0) this.draftPreparationFailed();
1501
1523
  this.prepareDraft(binding, draftOptions);
1502
- } : void 0)).catch((reason) => {
1524
+ } : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
1503
1525
  console.warn("new session failed:", reason);
1504
1526
  });
1505
1527
  }
@@ -1594,7 +1616,7 @@ window.__ModuleLoader__.load({
1594
1616
  }
1595
1617
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1596
1618
  let sessionId;
1597
- if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.reuseBlank(workspace.workspaceId, summary.id);
1619
+ if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.restoreBlank(workspace.workspaceId, summary.id);
1598
1620
  let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
1599
1621
  if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
1600
1622
  const prepared = await this.initializeDefaultWorkspace(navigation);
@@ -1716,7 +1738,7 @@ window.__ModuleLoader__.load({
1716
1738
  }
1717
1739
  //#endregion
1718
1740
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
1719
- const css$3 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);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;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;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_searchResultWorkspace:only-child{max-width:100%}.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_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);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;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-tertiary);flex:none;font-size:10px;line-height:16px}.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_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{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;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}}";
1741
+ const css$3 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);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;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;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_searchResultWorkspace:only-child{max-width:100%}.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_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);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;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-tertiary);flex:none;font-size:10px;line-height:16px}.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_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:15px;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{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;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}}";
1720
1742
  const tagId$3 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
1721
1743
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1722
1744
  const tag = document.createElement("style");
@@ -2002,6 +2024,7 @@ window.__ModuleLoader__.load({
2002
2024
  side: "bottom",
2003
2025
  align: "end",
2004
2026
  delayMs: 500,
2027
+ portal: true,
2005
2028
  children: (0, react_jsx_runtime.jsx)("button", {
2006
2029
  type: "button",
2007
2030
  className: Rows_module_css_default.iconButton,
@@ -2027,6 +2050,7 @@ window.__ModuleLoader__.load({
2027
2050
  t
2028
2051
  }),
2029
2052
  openDelayMs: 800,
2053
+ grouped: true,
2030
2054
  disabled: menuOpen,
2031
2055
  copyText: row.cwd,
2032
2056
  copyLabel: t("copy"),
@@ -2181,6 +2205,7 @@ window.__ModuleLoader__.load({
2181
2205
  side: "bottom",
2182
2206
  align: "end",
2183
2207
  delayMs: 500,
2208
+ portal: true,
2184
2209
  children: (0, react_jsx_runtime.jsx)("button", {
2185
2210
  type: "button",
2186
2211
  className: Rows_module_css_default.iconButton,
@@ -2334,6 +2359,7 @@ window.__ModuleLoader__.load({
2334
2359
  t
2335
2360
  }),
2336
2361
  openDelayMs: 800,
2362
+ grouped: true,
2337
2363
  disabled: menuOpen || drag?.active === true,
2338
2364
  copyText: row.blank || row.title === "" ? void 0 : row.title,
2339
2365
  copyLabel: t("copy"),
@@ -2774,22 +2800,46 @@ window.__ModuleLoader__.load({
2774
2800
  const SEARCH_DEBOUNCE_MS = 250;
2775
2801
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
2776
2802
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
2777
- /** Idle Session rows visible per Workspace before the local overflow control. */
2778
- const COLLAPSED_SESSION_LIMIT = 5;
2779
- /** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
2780
- function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
2781
- let idleCount = 0;
2803
+ /** Non-blank Session rows visible per Workspace while no Session raises its watermark. */
2804
+ const SESSION_WATERMARK_BASE = 5;
2805
+ /** Highest watermark that attention-bearing Sessions raise automatically; Show more and search reveals may exceed it. */
2806
+ const SESSION_WATERMARK_AUTO_MAX = 50;
2807
+ /** Show the blank row plus the first `watermark` non-blank rows, in list order. */
2808
+ function watermarkRows(sessions, watermark) {
2809
+ let count = 0;
2782
2810
  const rows = sessions.filter((session) => {
2783
- if (session.blank || session.running || session.runningSubagentCount > 0) return true;
2784
- if (idleCount >= limit) return false;
2785
- idleCount += 1;
2786
- return true;
2811
+ if (session.blank) return true;
2812
+ count += 1;
2813
+ return count <= watermark;
2787
2814
  });
2788
2815
  return {
2789
2816
  rows,
2790
2817
  hiddenCount: sessions.length - rows.length
2791
2818
  };
2792
2819
  }
2820
+ /**
2821
+ * Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
2822
+ * child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
2823
+ */
2824
+ function requiredWatermark(sessions, current, revealSessionId) {
2825
+ let index = 0;
2826
+ let required = 0;
2827
+ for (const session of sessions) {
2828
+ if (session.blank) continue;
2829
+ index += 1;
2830
+ if (index <= SESSION_WATERMARK_AUTO_MAX && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
2831
+ }
2832
+ return required;
2833
+ }
2834
+ /** Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises. */
2835
+ function raiseWatermarks(marks, required) {
2836
+ let raised = marks;
2837
+ for (const [key, watermark] of required) if (watermark > (raised[key] ?? SESSION_WATERMARK_BASE)) raised = {
2838
+ ...raised,
2839
+ [key]: watermark
2840
+ };
2841
+ return raised;
2842
+ }
2793
2843
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
2794
2844
  function sanitizeSearchQuery(value) {
2795
2845
  const withoutNul = value.replaceAll("\0", "");
@@ -2970,13 +3020,14 @@ window.__ModuleLoader__.load({
2970
3020
  ]
2971
3021
  });
2972
3022
  }
2973
- /** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
3023
+ /** The scrolling session tree; unmounting drops the sessions subscription and the session watermarks. */
2974
3024
  function SessionTree({ list, useSessionStatus, startSession, open, workspaces, ungroupedSessionIds, rowState, onLeaveArchivedOnly, workspaceReady, animationResetKey, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRenameRequest, renderSlot, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed, shortcuts }) {
2975
3025
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
2976
3026
  const statuses = useSessionStatus((s) => s);
2977
3027
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
2978
3028
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
2979
- const [sessionLimits, setSessionLimits] = (0, react.useState)({});
3029
+ const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
3030
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
2980
3031
  const [drag, setDrag] = (0, react.useState)(null);
2981
3032
  const sessionDropCommitted = (0, react.useRef)(false);
2982
3033
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
@@ -3034,20 +3085,24 @@ window.__ModuleLoader__.load({
3034
3085
  revealGroup,
3035
3086
  setGroupExpanded
3036
3087
  ]);
3037
- (0, react.useEffect)(() => {
3038
- if (revealSessionId === void 0 || revealGroup === void 0) return;
3039
- const group = groups.find((candidate) => candidate.key === revealGroup);
3040
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
3041
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
3042
- setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
3043
- ...limits,
3044
- [revealGroup]: Infinity
3045
- });
3046
- }, [
3047
- groups,
3048
- revealGroup,
3049
- revealSessionId
3050
- ]);
3088
+ const baselines = new Map(groups.map((group) => [group.key, Math.max(SESSION_WATERMARK_BASE, requiredWatermark(group.sessions, current, group.key === revealGroup ? revealSessionId : void 0))]));
3089
+ const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
3090
+ const underFoldedAncestor = (key) => {
3091
+ for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
3092
+ return false;
3093
+ };
3094
+ const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
3095
+ const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
3096
+ if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
3097
+ const dropWatermarks = (drop) => {
3098
+ if (!Object.keys(storedWatermarks).some(drop)) return;
3099
+ setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
3100
+ setWatermarkMotionKey((key) => key + 1);
3101
+ };
3102
+ const withinGroup = (key, root) => {
3103
+ for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
3104
+ return false;
3105
+ };
3051
3106
  const now = Date.now();
3052
3107
  const commitSessionDrag = (activeDrag, over) => {
3053
3108
  if (sessionDropCommitted.current) return;
@@ -3058,7 +3113,7 @@ window.__ModuleLoader__.load({
3058
3113
  if (over.id === activeDrag.sessionId) return;
3059
3114
  const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
3060
3115
  if (accountSessionIds === void 0) return;
3061
- const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
3116
+ const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE).rows;
3062
3117
  const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
3063
3118
  if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
3064
3119
  };
@@ -3098,8 +3153,9 @@ window.__ModuleLoader__.load({
3098
3153
  const workspaceId = group.workspaceId;
3099
3154
  const children = childrenByParent.get(group.key) ?? [];
3100
3155
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
3101
- const collapsed = collapsedSessionRows(group.sessions);
3102
- const visible = collapsedSessionRows(group.sessions, sessionLimits[group.key]);
3156
+ const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
3157
+ const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? SESSION_WATERMARK_BASE);
3158
+ const visible = watermarkRows(group.sessions, watermark);
3103
3159
  const sessionsExpanded = visible.hiddenCount === 0;
3104
3160
  rowKeys.push(`workspace:${group.key}`);
3105
3161
  const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
@@ -3171,10 +3227,7 @@ window.__ModuleLoader__.load({
3171
3227
  home,
3172
3228
  t,
3173
3229
  onToggle: () => {
3174
- if (group.expanded) setSessionLimits((limits) => ({
3175
- ...limits,
3176
- [group.key]: COLLAPSED_SESSION_LIMIT
3177
- }));
3230
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
3178
3231
  setGroupExpanded(group.key, !group.expanded);
3179
3232
  },
3180
3233
  onCreate: () => {
@@ -3259,10 +3312,15 @@ window.__ModuleLoader__.load({
3259
3312
  "data-row-key": `overflow:${group.key}`,
3260
3313
  "aria-expanded": sessionsExpanded,
3261
3314
  onClick: () => {
3262
- setSessionLimits((limits) => ({
3263
- ...limits,
3264
- [group.key]: sessionsExpanded ? COLLAPSED_SESSION_LIMIT : visible.hiddenCount <= COLLAPSED_SESSION_LIMIT ? Infinity : (limits[group.key] ?? COLLAPSED_SESSION_LIMIT) + COLLAPSED_SESSION_LIMIT
3315
+ if (sessionsExpanded) {
3316
+ dropWatermarks((key) => key === group.key);
3317
+ return;
3318
+ }
3319
+ setStoredWatermarks((marks) => ({
3320
+ ...marks,
3321
+ [group.key]: visible.hiddenCount <= SESSION_WATERMARK_BASE ? Infinity : watermark + SESSION_WATERMARK_BASE
3265
3322
  }));
3323
+ setWatermarkMotionKey((key) => key + 1);
3266
3324
  },
3267
3325
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
3268
3326
  })
@@ -3270,7 +3328,7 @@ window.__ModuleLoader__.load({
3270
3328
  }, group.key);
3271
3329
  };
3272
3330
  const groupRows = rootGroups.map((group) => renderGroup(group, 0));
3273
- return (0, react_jsx_runtime.jsxs)("div", {
3331
+ return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3274
3332
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3275
3333
  children: [
3276
3334
  workspaceDropAtListStart && (0, react_jsx_runtime.jsx)("span", {
@@ -3282,7 +3340,7 @@ window.__ModuleLoader__.load({
3282
3340
  label: t("section.sessions"),
3283
3341
  rowKeys,
3284
3342
  ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
3285
- resetKey: JSON.stringify([animationResetKey, sessionLimits]),
3343
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
3286
3344
  children: [groups.length === 0 && (0, react_jsx_runtime.jsx)(EmptySessions, {
3287
3345
  rowState,
3288
3346
  onLeaveArchivedOnly,
@@ -3315,7 +3373,7 @@ window.__ModuleLoader__.load({
3315
3373
  if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
3316
3374
  };
3317
3375
  const now = Date.now();
3318
- return (0, react_jsx_runtime.jsxs)("div", {
3376
+ return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3319
3377
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3320
3378
  children: [(0, react_jsx_runtime.jsxs)(AnimatedRows, {
3321
3379
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
@@ -3448,7 +3506,7 @@ window.__ModuleLoader__.load({
3448
3506
  * @param props - composed slot props (shell owner share + store + injected actions).
3449
3507
  * @returns the region element tree.
3450
3508
  */
3451
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, dismissForkError, renderSlot, t }) {
3509
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
3452
3510
  const home = useHostInfo((info) => info.home);
3453
3511
  const shortcuts = useShortcuts((rows) => rows);
3454
3512
  const searchShortcut = shortcuts.find((row) => row.id === "session.search");
@@ -4056,11 +4114,7 @@ window.__ModuleLoader__.load({
4056
4114
  role: "alert",
4057
4115
  children: deleteError
4058
4116
  })]
4059
- }),
4060
- shortcutState.forkError !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4061
- text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
4062
- onDone: dismissForkError
4063
- }, shortcutState.forkError.seq)
4117
+ })
4064
4118
  ]
4065
4119
  });
4066
4120
  }
@@ -4105,6 +4159,7 @@ window.__ModuleLoader__.load({
4105
4159
  side: "bottom",
4106
4160
  align: "end",
4107
4161
  delayMs: 500,
4162
+ portal: true,
4108
4163
  children: (0, react_jsx_runtime.jsx)("button", {
4109
4164
  type: "button",
4110
4165
  className: Rows_module_css_default.iconButton,
@@ -4250,14 +4305,17 @@ window.__ModuleLoader__.load({
4250
4305
  /** The fork action: one `sidebar.workspaces.session.menu.item` row. */
4251
4306
  /**
4252
4307
  * Menu row (order 300): fork at the Session's last completed turn; the child
4253
- * arrives through the Host list beside its source.
4254
- * @param props - owner share, menu open state, and the fork share.
4255
- * @returns the row.
4308
+ * arrives through the Host list beside its source. Absent when the access
4309
+ * snapshot excludes `session/fork` for this Session.
4310
+ * @param props - owner share, menu open state, access Hook, and the fork share.
4311
+ * @returns the row, or nothing when the caller may not fork this Session.
4256
4312
  */
4257
- function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
4313
+ function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
4258
4314
  const [, setMenuOpen] = useMenuOpenState();
4315
+ const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
4316
+ if (!useSessionAccess(sessionId, "session/fork")) return null;
4259
4317
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
4260
- shortcut: useShortcuts((rows) => rows.find((row) => row.id === "session.fork")),
4318
+ shortcut,
4261
4319
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
4262
4320
  onSelect: () => {
4263
4321
  setMenuOpen(false);
@@ -4316,6 +4374,7 @@ window.__ModuleLoader__.load({
4316
4374
  side: "bottom",
4317
4375
  align: "end",
4318
4376
  delayMs: 500,
4377
+ portal: true,
4319
4378
  children: (0, react_jsx_runtime.jsx)("button", {
4320
4379
  type: "button",
4321
4380
  className: Rows_module_css_default.iconButton,
@@ -4446,81 +4505,238 @@ window.__ModuleLoader__.load({
4446
4505
  //#endregion
4447
4506
  //#region lib/types/client/session-actions/RowActionToast.js
4448
4507
  /**
4449
- * The `shell.overlay` entry for Workspace and Session notices.
4450
- * One notice is visible at a time; a parent rerender does not extend its hold.
4508
+ * Dispatch the current notice without interpreting its business payload.
4509
+ * @param props - Current notice, dismissal, and the declared keyed renderer.
4510
+ * @returns The selected notice presentation, or nothing.
4451
4511
  */
4512
+ function RowActionToast({ useToast, dismissToast, renderSlot }) {
4513
+ const notice = useToast((current) => current);
4514
+ if (notice === null) return null;
4515
+ return renderSlot("workspace.toast", {
4516
+ ...notice,
4517
+ dismissToast
4518
+ }, { entryKey: notice.kind });
4519
+ }
4520
+ //#endregion
4521
+ //#region lib/types/client/toast/Toast.js
4522
+ /** One presentation Factory and the adapters for keyed Workspace notices. */
4452
4523
  /**
4453
- * Hold for the notices that take longer to read than a one-line warning: the
4454
- * archive and fork recovery actions, and a refused Session creation quoting the Host's reason.
4524
+ * Render one notice using the shared Factory.
4525
+ * @param props - Keyed payload, injected display mapping, locale, and Factory renderer.
4526
+ * @returns The shared Toast occurrence.
4455
4527
  */
4456
- const LONG_TOAST_HOLD_MS = 6e3;
4528
+ function ToastEntry(props) {
4529
+ const { dismissToast, display, t, renderFactorySlot, seq } = props;
4530
+ return renderFactorySlot("workspace.toast.content", {
4531
+ ...display(props, t, dismissToast),
4532
+ seq,
4533
+ onDone: dismissToast
4534
+ });
4535
+ }
4457
4536
  /**
4458
- * Render an operation notice, retaining recovery actions for six seconds.
4459
- * @param props - notice and viewing sources, dismissal and recovery callbacks, and localized copy.
4460
- * @returns the notice on display, or null.
4537
+ * Keep the archive filter action reactive without subscribing unrelated notices.
4538
+ * @param props - Archive notice, display mapping, and the existing viewing store.
4539
+ * @returns The shared Toast occurrence with its current actions.
4461
4540
  */
4462
- function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, openForkSource, t }) {
4463
- const toast = useToast((current) => current);
4464
- const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
4465
- if (toast === null) return null;
4466
- if (toast.kind === "forkRequiresOpen") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4467
- text: t("toast.forkRequiresOpen"),
4468
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4469
- holdMs: LONG_TOAST_HOLD_MS,
4470
- actions: [{
4471
- label: t("toast.openForkSource"),
4472
- onClick: () => {
4473
- dismissToast();
4474
- openForkSource(toast.sessionId);
4475
- }
4476
- }],
4541
+ function ArchiveToastEntry(props) {
4542
+ const { dismissToast, display, filterAction, useStore, t, renderFactorySlot, seq } = props;
4543
+ const archivedVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
4544
+ const fields = display(props, t, dismissToast);
4545
+ return renderFactorySlot("workspace.toast.content", {
4546
+ ...fields,
4547
+ actions: [...fields.actions ?? [], ...archivedVisible ? [] : [filterAction(t, dismissToast)]],
4548
+ seq,
4477
4549
  onDone: dismissToast
4478
- }, `toast-${String(toast.seq)}`);
4479
- if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
4480
- const { sessionId } = toast;
4481
- return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4482
- text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
4483
- tone: "success",
4484
- holdMs: LONG_TOAST_HOLD_MS,
4485
- actions: [{
4486
- label: t("toast.archivedUndo"),
4487
- onClick: () => {
4488
- dismissToast();
4489
- undoArchive(sessionId);
4490
- }
4491
- }, ...archivedRowsVisible ? [] : [{
4492
- prefix: t("toast.archivedOr"),
4493
- label: t("toast.archivedFilter"),
4494
- onClick: () => {
4495
- dismissToast();
4496
- showArchived();
4497
- }
4498
- }]],
4499
- onDone: dismissToast
4500
- }, `toast-${String(toast.seq)}`);
4501
- }
4502
- if (toast.kind === "createFailed") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4503
- text: t("toast.createFailed", { message: toast.message }),
4504
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4505
- holdMs: LONG_TOAST_HOLD_MS,
4506
- onDone: dismissToast
4507
- }, `toast-${String(toast.seq)}`);
4550
+ });
4551
+ }
4552
+ /**
4553
+ * Present plain display data with the shared primitive's timing and portal.
4554
+ * @param props - Localized display fields, display identity, and dismissal.
4555
+ * @returns One Toast, remounted only for a new display identity.
4556
+ */
4557
+ function WorkspaceToast({ seq, text, tone, holdMs, actions, onDone }) {
4508
4558
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4509
- text: plainNoticeText(toast, t),
4510
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4511
- onDone: dismissToast
4512
- }, `toast-${String(toast.seq)}`);
4559
+ text,
4560
+ onDone,
4561
+ ...tone === "success" ? { tone: "success" } : {},
4562
+ ...tone === "warning" ? { icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}) } : {},
4563
+ ...holdMs === void 0 ? {} : { holdMs },
4564
+ ...actions === void 0 ? {} : { actions }
4565
+ }, seq);
4513
4566
  }
4514
- /** The copy of one plain warning, keyed by the notice kind the union closes over. */
4515
- function plainNoticeText(toast, t) {
4516
- switch (toast.kind) {
4517
- case "pinFailed": return t("toast.pinFailed");
4518
- case "unpinFailed": return t("toast.unpinFailed");
4519
- case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
4520
- case "archivedNotOpenable": return t("toast.archivedNotOpenable");
4521
- /* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
4522
- default: return assertNever$1(toast);
4523
- }
4567
+ //#endregion
4568
+ //#region lib/types/client/toast/register.js
4569
+ /** Register notice presentations with their business-owned display fields and actions.
4570
+ * @param ctx - Workspace plugin context.
4571
+ * @param viewStore - Viewing store, read only by archive notices.
4572
+ * @param actions - Archive and source-opening callbacks.
4573
+ */
4574
+ function registerToasts(ctx, viewStore, actions) {
4575
+ const archiveFilterAction = (t, dismiss) => ({
4576
+ prefix: t("toast.archivedOr"),
4577
+ label: t("toast.archivedFilter"),
4578
+ onClick: () => {
4579
+ dismiss();
4580
+ actions.showArchived();
4581
+ }
4582
+ });
4583
+ ctx.slots.registerFactory({
4584
+ name: "workspace.toast.content",
4585
+ scope: "root"
4586
+ }, WorkspaceToast);
4587
+ ctx.slots.inject("workspace.toast", function* () {
4588
+ yield ctx.slots.register({
4589
+ name: "workspace.toast",
4590
+ key: "sessionIdCopied",
4591
+ locale: "workspace",
4592
+ inject: () => ({ display: (_notice, t) => ({
4593
+ text: t("toast.sessionIdCopied"),
4594
+ tone: "success"
4595
+ }) })
4596
+ }, ToastEntry);
4597
+ yield ctx.slots.register({
4598
+ name: "workspace.toast",
4599
+ key: "sessionIdCopyFailed",
4600
+ locale: "workspace",
4601
+ inject: () => ({ display: (_notice, t) => ({
4602
+ text: t("toast.sessionIdCopyFailed"),
4603
+ tone: "warning"
4604
+ }) })
4605
+ }, ToastEntry);
4606
+ yield ctx.slots.register({
4607
+ name: "workspace.toast",
4608
+ key: "pinFailed",
4609
+ locale: "workspace",
4610
+ inject: () => ({ display: (_notice, t) => ({
4611
+ text: t("toast.pinFailed"),
4612
+ tone: "warning"
4613
+ }) })
4614
+ }, ToastEntry);
4615
+ yield ctx.slots.register({
4616
+ name: "workspace.toast",
4617
+ key: "unpinFailed",
4618
+ locale: "workspace",
4619
+ inject: () => ({ display: (_notice, t) => ({
4620
+ text: t("toast.unpinFailed"),
4621
+ tone: "warning"
4622
+ }) })
4623
+ }, ToastEntry);
4624
+ yield ctx.slots.register({
4625
+ name: "workspace.toast",
4626
+ key: "archivedNotOpenable",
4627
+ locale: "workspace",
4628
+ inject: () => ({ display: (_notice, t) => ({
4629
+ text: t("toast.archivedNotOpenable"),
4630
+ tone: "warning"
4631
+ }) })
4632
+ }, ToastEntry);
4633
+ yield ctx.slots.register({
4634
+ name: "workspace.toast",
4635
+ key: "defaultWorkspaceFailed",
4636
+ locale: "workspace",
4637
+ inject: () => ({ display: (_notice, t) => ({
4638
+ text: t("defaultWorkspace.failed"),
4639
+ tone: "warning"
4640
+ }) })
4641
+ }, ToastEntry);
4642
+ yield ctx.slots.register({
4643
+ name: "workspace.toast",
4644
+ key: "shortcutForkFailed",
4645
+ locale: "workspace",
4646
+ inject: () => ({ display: (notice, t) => ({
4647
+ text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
4648
+ tone: "plain"
4649
+ }) })
4650
+ }, ToastEntry);
4651
+ yield ctx.slots.register({
4652
+ name: "workspace.toast",
4653
+ key: "createFailed",
4654
+ locale: "workspace",
4655
+ inject: () => ({ display: (notice, t) => ({
4656
+ text: t("toast.createFailed", { message: notice.message }),
4657
+ tone: "warning",
4658
+ holdMs: 6e3
4659
+ }) })
4660
+ }, ToastEntry);
4661
+ yield ctx.slots.register({
4662
+ name: "workspace.toast",
4663
+ key: "forkRequiresOpen",
4664
+ locale: "workspace",
4665
+ inject: () => ({ display: (notice, t, dismiss) => ({
4666
+ text: t("toast.forkRequiresOpen"),
4667
+ tone: "warning",
4668
+ holdMs: 6e3,
4669
+ actions: [{
4670
+ label: t("toast.openForkSource"),
4671
+ onClick: () => {
4672
+ dismiss();
4673
+ actions.openForkSource(notice.sessionId);
4674
+ }
4675
+ }]
4676
+ }) })
4677
+ }, ToastEntry);
4678
+ yield ctx.slots.register({
4679
+ name: "workspace.toast",
4680
+ key: "archived",
4681
+ locale: "workspace",
4682
+ store: viewStore,
4683
+ inject: () => ({
4684
+ display: (notice, t, dismiss) => ({
4685
+ text: t("toast.archived"),
4686
+ tone: "success",
4687
+ holdMs: 6e3,
4688
+ actions: [{
4689
+ label: t("toast.archivedUndo"),
4690
+ onClick: () => {
4691
+ dismiss();
4692
+ actions.undoArchive(notice.sessionId);
4693
+ }
4694
+ }]
4695
+ }),
4696
+ filterAction: archiveFilterAction
4697
+ })
4698
+ }, ArchiveToastEntry);
4699
+ yield ctx.slots.register({
4700
+ name: "workspace.toast",
4701
+ key: "stoppedAndArchived",
4702
+ locale: "workspace",
4703
+ store: viewStore,
4704
+ inject: () => ({
4705
+ display: (notice, t, dismiss) => ({
4706
+ text: t("toast.stoppedAndArchived"),
4707
+ tone: "success",
4708
+ holdMs: 6e3,
4709
+ actions: [{
4710
+ label: t("toast.archivedUndo"),
4711
+ onClick: () => {
4712
+ dismiss();
4713
+ actions.undoArchive(notice.sessionId);
4714
+ }
4715
+ }]
4716
+ }),
4717
+ filterAction: archiveFilterAction
4718
+ })
4719
+ }, ArchiveToastEntry);
4720
+ });
4721
+ }
4722
+ //#endregion
4723
+ //#region lib/types/client/session-actions/SessionId.js
4724
+ /** Copy the row's Session ID without opening its history. */
4725
+ /**
4726
+ * Copy the complete ID after dismissing the row menu.
4727
+ * @param props - Row identity, menu dismissal, clipboard action, and locale.
4728
+ * @returns The final built-in menu item.
4729
+ */
4730
+ function SessionIdMenuItem({ sessionId, useMenuOpenState, copySessionId, t }) {
4731
+ const [, setMenuOpen] = useMenuOpenState();
4732
+ return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
4733
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {}),
4734
+ onSelect: () => {
4735
+ setMenuOpen(false);
4736
+ copySessionId(sessionId);
4737
+ },
4738
+ children: t("menu.sessionId")
4739
+ });
4524
4740
  }
4525
4741
  //#endregion
4526
4742
  //#region lib/types/client/locales.js
@@ -4588,6 +4804,9 @@ window.__ModuleLoader__.load({
4588
4804
  "menu.unarchiveSession": "取消归档",
4589
4805
  "menu.pinSession": "置顶会话",
4590
4806
  "menu.unpinSession": "取消置顶",
4807
+ "menu.sessionId": "复制 Session ID",
4808
+ "toast.sessionIdCopied": "Session ID 已复制",
4809
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
4591
4810
  "row.archived": "已归档",
4592
4811
  "row.pinned": "已置顶",
4593
4812
  "toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
@@ -4707,6 +4926,9 @@ window.__ModuleLoader__.load({
4707
4926
  "menu.unarchiveSession": "Unarchive session",
4708
4927
  "menu.pinSession": "Pin session",
4709
4928
  "menu.unpinSession": "Unpin session",
4929
+ "menu.sessionId": "Copy Session ID",
4930
+ "toast.sessionIdCopied": "Session ID copied",
4931
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
4710
4932
  "row.archived": "Archived",
4711
4933
  "row.pinned": "Pinned",
4712
4934
  "toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
@@ -4787,7 +5009,8 @@ window.__ModuleLoader__.load({
4787
5009
  "remote",
4788
5010
  "remote.directoryPicker",
4789
5011
  "layout",
4790
- "shortcuts"
5012
+ "shortcuts",
5013
+ "remoteAccess"
4791
5014
  ];
4792
5015
  /**
4793
5016
  * Register the browser and picker once their slot declarations are on the
@@ -4813,7 +5036,10 @@ window.__ModuleLoader__.load({
4813
5036
  });
4814
5037
  };
4815
5038
  const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
4816
- ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
5039
+ ctx.slots.provideRoot({
5040
+ hooks: { workspaces: workspaces.list },
5041
+ keyedHooks: { workspaceAccess: (workspaceId, action) => action === void 0 ? void 0 : ctx.remoteAccess.source("workspace", workspaceId, action) }
5042
+ });
4817
5043
  ctx.effect(() => ctx.locale.register(NS, {
4818
5044
  zh,
4819
5045
  en
@@ -4891,7 +5117,12 @@ window.__ModuleLoader__.load({
4891
5117
  },
4892
5118
  unarchiveSession
4893
5119
  });
4894
- installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession);
5120
+ installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
5121
+ notify({
5122
+ kind: "shortcutForkFailed",
5123
+ reason
5124
+ });
5125
+ });
4895
5126
  const archiveConfirmInjected = () => ({
4896
5127
  hooks: { archiveRequest },
4897
5128
  settleSessionArchive: () => {
@@ -4919,17 +5150,16 @@ window.__ModuleLoader__.load({
4919
5150
  });
4920
5151
  });
4921
5152
  } });
5153
+ const sessionIdInjected = () => ({ copySessionId: async (sessionId) => {
5154
+ notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
5155
+ } });
4922
5156
  const renameInjected = () => ({ requestSessionRename });
4923
5157
  const renameDialogInjected = () => ({
4924
5158
  hooks: { renameRequest },
4925
5159
  settleSessionRename: shortcutControls.closeRename,
4926
5160
  renameSession
4927
5161
  });
4928
- const rowToastInjected = () => ({
4929
- hooks: { toast: rowToast },
4930
- dismissToast: () => {
4931
- rowToast.set(null);
4932
- },
5162
+ registerToasts(ctx, viewStore, {
4933
5163
  undoArchive: unarchiveSession,
4934
5164
  showArchived: () => {
4935
5165
  viewInstance.actions.setArchivedFilter("show");
@@ -4942,6 +5172,12 @@ window.__ModuleLoader__.load({
4942
5172
  openSession(sessionId);
4943
5173
  }
4944
5174
  });
5175
+ const rowToastInjected = () => ({
5176
+ hooks: { toast: rowToast },
5177
+ dismissToast: () => {
5178
+ rowToast.set(null);
5179
+ }
5180
+ });
4945
5181
  const browserInjected = () => ({
4946
5182
  startSession: (workspaceId, options) => {
4947
5183
  uiWorkspace.startSession(workspaceId, options);
@@ -4970,7 +5206,6 @@ window.__ModuleLoader__.load({
4970
5206
  requestAddWorkspace: shortcutControls.add,
4971
5207
  closeAddWorkspace: shortcutControls.closeAdd,
4972
5208
  setDirectoryBusy: shortcutControls.directoryBusy,
4973
- dismissForkError: shortcutControls.dismissForkError,
4974
5209
  hooks: {
4975
5210
  directoryFlow: browserFlowSource,
4976
5211
  hostInfo,
@@ -5043,6 +5278,13 @@ window.__ModuleLoader__.load({
5043
5278
  locale: NS,
5044
5279
  inject: archiveInjected
5045
5280
  }, ArchiveSessionMenuItem);
5281
+ yield ctx.slots.register({
5282
+ name: "sidebar.workspaces.session.menu.item",
5283
+ id: "session-id",
5284
+ order: 450,
5285
+ locale: NS,
5286
+ inject: sessionIdInjected
5287
+ }, SessionIdMenuItem);
5046
5288
  });
5047
5289
  ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
5048
5290
  yield ctx.slots.register({
@@ -5076,9 +5318,11 @@ window.__ModuleLoader__.load({
5076
5318
  yield ctx.slots.register({
5077
5319
  name: "shell.overlay",
5078
5320
  id: "workspace.row-toast",
5079
- locale: NS,
5080
- store: viewStore,
5081
- inject: rowToastInjected
5321
+ inject: rowToastInjected,
5322
+ children: { "workspace.toast": {
5323
+ kind: "keyed",
5324
+ scope: "root"
5325
+ } }
5082
5326
  }, RowActionToast);
5083
5327
  });
5084
5328
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({