@deepseek-ai/dsh-client-ui-workspace 0.2.1-alpha.1 → 0.2.1-alpha.3

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,46 +1394,90 @@ window.__ModuleLoader__.load({
1410
1394
  };
1411
1395
  }, "ui-workspace: Workspace navigation policy");
1412
1396
  }
1413
- async connectWorkspace(workspaceId) {
1397
+ connectWorkspace(workspaceId, options = {}) {
1398
+ return this.connectBlank(workspaceId, options);
1399
+ }
1400
+ /**
1401
+ * @param reuse - preset for the reused or created blank; absent creates a fresh Session.
1402
+ */
1403
+ async connectBlank(workspaceId, reuse) {
1414
1404
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
1415
1405
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
1406
+ if (reuse === void 0) return this.sessions.create({ workspaceId });
1416
1407
  const inflight = this.connecting.get(workspaceId);
1417
- if (inflight !== void 0) return inflight;
1418
- const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
1419
- 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
1420
1415
  });
1421
- this.connecting.set(workspaceId, attempt);
1422
1416
  return attempt;
1423
1417
  }
1424
- reuseOrCreateBlank(workspace) {
1418
+ createInWorkspace(workspace, options) {
1425
1419
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
1426
1420
  const sessions = this.sessions.list.getSnapshot();
1427
- for (const id of sessions.ids) {
1421
+ const blank = sessions.ids.find((id) => {
1428
1422
  const summary = sessions.byId[id];
1429
- if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
1430
- return this.reuseBlank(workspace.workspaceId, id);
1431
- }
1432
- 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
+ });
1430
+ }
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;
1433
1443
  }
1434
- async reuseBlank(workspaceId, sessionId) {
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);
1435
1459
  try {
1436
- return await this.sessions.create({
1437
- workspaceId,
1438
- sessionId
1460
+ return await this.createNamed({
1461
+ ...request,
1462
+ agentPreset: recorded
1439
1463
  });
1440
1464
  } catch (error) {
1441
- if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
1442
- 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);
1443
1468
  }
1444
1469
  }
1445
1470
  openSession(target) {
1446
1471
  this.replaceMain(target, this.lifetime.signal, "reveal");
1447
1472
  }
1448
- async openWorkspace(workspaceId, beforeOpen) {
1473
+ openWorkspace(workspaceId, beforeOpen, options = {}) {
1474
+ return this.openWorkspaceSession(workspaceId, beforeOpen, options);
1475
+ }
1476
+ async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
1449
1477
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1450
1478
  let sessionId;
1451
1479
  try {
1452
- sessionId = await this.connectWorkspace(workspaceId);
1480
+ sessionId = await this.connectBlank(workspaceId, reuse);
1453
1481
  } catch (error) {
1454
1482
  if (!navigation.aborted) this.notify({
1455
1483
  kind: "createFailed",
@@ -1460,15 +1488,17 @@ window.__ModuleLoader__.load({
1460
1488
  if (navigation.aborted) return;
1461
1489
  this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
1462
1490
  }
1463
- async forkSession(sessionId, onCreated) {
1491
+ async forkSession(sessionId, onCreated, options) {
1464
1492
  return this.sessions.fork({
1465
1493
  sessionId,
1466
1494
  increaseTitle: true,
1467
- ...onCreated === void 0 ? {} : { onCreated }
1495
+ ...onCreated === void 0 ? {} : { onCreated },
1496
+ ...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
1468
1497
  });
1469
1498
  }
1470
1499
  startSession(workspaceId, options) {
1471
- const draftOptions = options === void 0 ? void 0 : { ...options };
1500
+ const { agentPreset, ...draft } = options ?? {};
1501
+ const draftOptions = options === void 0 ? void 0 : draft;
1472
1502
  const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
1473
1503
  const workspace = this.workspaces.list.getSnapshot();
1474
1504
  const sessions = this.sessions.list.getSnapshot();
@@ -1487,11 +1517,11 @@ window.__ModuleLoader__.load({
1487
1517
  this.clearMain();
1488
1518
  return;
1489
1519
  }
1490
- this.openWorkspace(target, initializeDraft ? (id) => {
1520
+ (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (id) => {
1491
1521
  const binding = this.sessions.binding(id);
1492
1522
  if (binding === void 0) this.draftPreparationFailed();
1493
1523
  this.prepareDraft(binding, draftOptions);
1494
- } : void 0).catch((reason) => {
1524
+ } : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
1495
1525
  console.warn("new session failed:", reason);
1496
1526
  });
1497
1527
  }
@@ -1586,7 +1616,7 @@ window.__ModuleLoader__.load({
1586
1616
  }
1587
1617
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1588
1618
  let sessionId;
1589
- 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);
1590
1620
  let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
1591
1621
  if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
1592
1622
  const prepared = await this.initializeDefaultWorkspace(navigation);
@@ -1708,7 +1738,7 @@ window.__ModuleLoader__.load({
1708
1738
  }
1709
1739
  //#endregion
1710
1740
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
1711
- 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}}";
1712
1742
  const tagId$3 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
1713
1743
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1714
1744
  const tag = document.createElement("style");
@@ -1994,6 +2024,7 @@ window.__ModuleLoader__.load({
1994
2024
  side: "bottom",
1995
2025
  align: "end",
1996
2026
  delayMs: 500,
2027
+ portal: true,
1997
2028
  children: (0, react_jsx_runtime.jsx)("button", {
1998
2029
  type: "button",
1999
2030
  className: Rows_module_css_default.iconButton,
@@ -2019,6 +2050,7 @@ window.__ModuleLoader__.load({
2019
2050
  t
2020
2051
  }),
2021
2052
  openDelayMs: 800,
2053
+ grouped: true,
2022
2054
  disabled: menuOpen,
2023
2055
  copyText: row.cwd,
2024
2056
  copyLabel: t("copy"),
@@ -2173,6 +2205,7 @@ window.__ModuleLoader__.load({
2173
2205
  side: "bottom",
2174
2206
  align: "end",
2175
2207
  delayMs: 500,
2208
+ portal: true,
2176
2209
  children: (0, react_jsx_runtime.jsx)("button", {
2177
2210
  type: "button",
2178
2211
  className: Rows_module_css_default.iconButton,
@@ -2326,6 +2359,7 @@ window.__ModuleLoader__.load({
2326
2359
  t
2327
2360
  }),
2328
2361
  openDelayMs: 800,
2362
+ grouped: true,
2329
2363
  disabled: menuOpen || drag?.active === true,
2330
2364
  copyText: row.blank || row.title === "" ? void 0 : row.title,
2331
2365
  copyLabel: t("copy"),
@@ -2766,22 +2800,46 @@ window.__ModuleLoader__.load({
2766
2800
  const SEARCH_DEBOUNCE_MS = 250;
2767
2801
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
2768
2802
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
2769
- /** Idle Session rows visible per Workspace before the local overflow control. */
2770
- const COLLAPSED_SESSION_LIMIT = 5;
2771
- /** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
2772
- function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
2773
- 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;
2774
2810
  const rows = sessions.filter((session) => {
2775
- if (session.blank || session.running || session.runningSubagentCount > 0) return true;
2776
- if (idleCount >= limit) return false;
2777
- idleCount += 1;
2778
- return true;
2811
+ if (session.blank) return true;
2812
+ count += 1;
2813
+ return count <= watermark;
2779
2814
  });
2780
2815
  return {
2781
2816
  rows,
2782
2817
  hiddenCount: sessions.length - rows.length
2783
2818
  };
2784
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
+ }
2785
2843
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
2786
2844
  function sanitizeSearchQuery(value) {
2787
2845
  const withoutNul = value.replaceAll("\0", "");
@@ -2962,13 +3020,14 @@ window.__ModuleLoader__.load({
2962
3020
  ]
2963
3021
  });
2964
3022
  }
2965
- /** 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. */
2966
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 }) {
2967
3025
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
2968
3026
  const statuses = useSessionStatus((s) => s);
2969
3027
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
2970
3028
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
2971
- const [sessionLimits, setSessionLimits] = (0, react.useState)({});
3029
+ const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
3030
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
2972
3031
  const [drag, setDrag] = (0, react.useState)(null);
2973
3032
  const sessionDropCommitted = (0, react.useRef)(false);
2974
3033
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
@@ -3026,20 +3085,24 @@ window.__ModuleLoader__.load({
3026
3085
  revealGroup,
3027
3086
  setGroupExpanded
3028
3087
  ]);
3029
- (0, react.useEffect)(() => {
3030
- if (revealSessionId === void 0 || revealGroup === void 0) return;
3031
- const group = groups.find((candidate) => candidate.key === revealGroup);
3032
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
3033
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
3034
- setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
3035
- ...limits,
3036
- [revealGroup]: Infinity
3037
- });
3038
- }, [
3039
- groups,
3040
- revealGroup,
3041
- revealSessionId
3042
- ]);
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
+ };
3043
3106
  const now = Date.now();
3044
3107
  const commitSessionDrag = (activeDrag, over) => {
3045
3108
  if (sessionDropCommitted.current) return;
@@ -3050,7 +3113,7 @@ window.__ModuleLoader__.load({
3050
3113
  if (over.id === activeDrag.sessionId) return;
3051
3114
  const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
3052
3115
  if (accountSessionIds === void 0) return;
3053
- const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
3116
+ const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE).rows;
3054
3117
  const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
3055
3118
  if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
3056
3119
  };
@@ -3090,8 +3153,9 @@ window.__ModuleLoader__.load({
3090
3153
  const workspaceId = group.workspaceId;
3091
3154
  const children = childrenByParent.get(group.key) ?? [];
3092
3155
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
3093
- const collapsed = collapsedSessionRows(group.sessions);
3094
- 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);
3095
3159
  const sessionsExpanded = visible.hiddenCount === 0;
3096
3160
  rowKeys.push(`workspace:${group.key}`);
3097
3161
  const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
@@ -3163,10 +3227,7 @@ window.__ModuleLoader__.load({
3163
3227
  home,
3164
3228
  t,
3165
3229
  onToggle: () => {
3166
- if (group.expanded) setSessionLimits((limits) => ({
3167
- ...limits,
3168
- [group.key]: COLLAPSED_SESSION_LIMIT
3169
- }));
3230
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
3170
3231
  setGroupExpanded(group.key, !group.expanded);
3171
3232
  },
3172
3233
  onCreate: () => {
@@ -3251,10 +3312,15 @@ window.__ModuleLoader__.load({
3251
3312
  "data-row-key": `overflow:${group.key}`,
3252
3313
  "aria-expanded": sessionsExpanded,
3253
3314
  onClick: () => {
3254
- setSessionLimits((limits) => ({
3255
- ...limits,
3256
- [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
3257
3322
  }));
3323
+ setWatermarkMotionKey((key) => key + 1);
3258
3324
  },
3259
3325
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
3260
3326
  })
@@ -3262,7 +3328,7 @@ window.__ModuleLoader__.load({
3262
3328
  }, group.key);
3263
3329
  };
3264
3330
  const groupRows = rootGroups.map((group) => renderGroup(group, 0));
3265
- return (0, react_jsx_runtime.jsxs)("div", {
3331
+ return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3266
3332
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3267
3333
  children: [
3268
3334
  workspaceDropAtListStart && (0, react_jsx_runtime.jsx)("span", {
@@ -3274,7 +3340,7 @@ window.__ModuleLoader__.load({
3274
3340
  label: t("section.sessions"),
3275
3341
  rowKeys,
3276
3342
  ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
3277
- resetKey: JSON.stringify([animationResetKey, sessionLimits]),
3343
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
3278
3344
  children: [groups.length === 0 && (0, react_jsx_runtime.jsx)(EmptySessions, {
3279
3345
  rowState,
3280
3346
  onLeaveArchivedOnly,
@@ -3307,7 +3373,7 @@ window.__ModuleLoader__.load({
3307
3373
  if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
3308
3374
  };
3309
3375
  const now = Date.now();
3310
- return (0, react_jsx_runtime.jsxs)("div", {
3376
+ return (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3311
3377
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3312
3378
  children: [(0, react_jsx_runtime.jsxs)(AnimatedRows, {
3313
3379
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
@@ -3440,7 +3506,7 @@ window.__ModuleLoader__.load({
3440
3506
  * @param props - composed slot props (shell owner share + store + injected actions).
3441
3507
  * @returns the region element tree.
3442
3508
  */
3443
- 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 }) {
3444
3510
  const home = useHostInfo((info) => info.home);
3445
3511
  const shortcuts = useShortcuts((rows) => rows);
3446
3512
  const searchShortcut = shortcuts.find((row) => row.id === "session.search");
@@ -3869,7 +3935,7 @@ window.__ModuleLoader__.load({
3869
3935
  side: "right",
3870
3936
  onPick: (workspaceId) => {
3871
3937
  closeAddWorkspace();
3872
- startSession(workspaceId);
3938
+ startSession(workspaceId, { clearPreviousDraft: false });
3873
3939
  },
3874
3940
  onClose: () => {
3875
3941
  closeAddWorkspace();
@@ -4048,11 +4114,7 @@ window.__ModuleLoader__.load({
4048
4114
  role: "alert",
4049
4115
  children: deleteError
4050
4116
  })]
4051
- }),
4052
- shortcutState.forkError !== null && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4053
- text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
4054
- onDone: dismissForkError
4055
- }, shortcutState.forkError.seq)
4117
+ })
4056
4118
  ]
4057
4119
  });
4058
4120
  }
@@ -4097,6 +4159,7 @@ window.__ModuleLoader__.load({
4097
4159
  side: "bottom",
4098
4160
  align: "end",
4099
4161
  delayMs: 500,
4162
+ portal: true,
4100
4163
  children: (0, react_jsx_runtime.jsx)("button", {
4101
4164
  type: "button",
4102
4165
  className: Rows_module_css_default.iconButton,
@@ -4242,14 +4305,17 @@ window.__ModuleLoader__.load({
4242
4305
  /** The fork action: one `sidebar.workspaces.session.menu.item` row. */
4243
4306
  /**
4244
4307
  * Menu row (order 300): fork at the Session's last completed turn; the child
4245
- * arrives through the Host list beside its source.
4246
- * @param props - owner share, menu open state, and the fork share.
4247
- * @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.
4248
4312
  */
4249
- function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
4313
+ function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
4250
4314
  const [, setMenuOpen] = useMenuOpenState();
4315
+ const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
4316
+ if (!useSessionAccess(sessionId, "session/fork")) return null;
4251
4317
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
4252
- shortcut: useShortcuts((rows) => rows.find((row) => row.id === "session.fork")),
4318
+ shortcut,
4253
4319
  icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
4254
4320
  onSelect: () => {
4255
4321
  setMenuOpen(false);
@@ -4308,6 +4374,7 @@ window.__ModuleLoader__.load({
4308
4374
  side: "bottom",
4309
4375
  align: "end",
4310
4376
  delayMs: 500,
4377
+ portal: true,
4311
4378
  children: (0, react_jsx_runtime.jsx)("button", {
4312
4379
  type: "button",
4313
4380
  className: Rows_module_css_default.iconButton,
@@ -4438,73 +4505,238 @@ window.__ModuleLoader__.load({
4438
4505
  //#endregion
4439
4506
  //#region lib/types/client/session-actions/RowActionToast.js
4440
4507
  /**
4441
- * The `shell.overlay` entry for Workspace and Session notices.
4442
- * 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.
4443
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. */
4444
4523
  /**
4445
- * Hold for the notices that take longer to read than a one-line warning: the
4446
- * actionable archived notice (two buttons to react to) and a refused Session
4447
- * creation, which quotes 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.
4448
4527
  */
4449
- 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
+ }
4450
4536
  /**
4451
- * Render the current notice: the archived and stopped-and-archived notices
4452
- * with their undo action — plus the show-archived action while archived rows
4453
- * are hidden — on a 6 s hold, a refused Session creation with the Host's
4454
- * reason on the same hold, or a plain warning for a failed pin, an archived
4455
- * row that was clicked, or default Workspace creation.
4456
- * @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
4457
- * @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.
4458
4540
  */
4459
- function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }) {
4460
- const toast = useToast((current) => current);
4461
- const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
4462
- if (toast === null) return null;
4463
- if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
4464
- const { sessionId } = toast;
4465
- return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4466
- text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
4467
- tone: "success",
4468
- holdMs: LONG_TOAST_HOLD_MS,
4469
- actions: [{
4470
- label: t("toast.archivedUndo"),
4471
- onClick: () => {
4472
- dismissToast();
4473
- undoArchive(sessionId);
4474
- }
4475
- }, ...archivedRowsVisible ? [] : [{
4476
- prefix: t("toast.archivedOr"),
4477
- label: t("toast.archivedFilter"),
4478
- onClick: () => {
4479
- dismissToast();
4480
- showArchived();
4481
- }
4482
- }]],
4483
- onDone: dismissToast
4484
- }, `toast-${String(toast.seq)}`);
4485
- }
4486
- if (toast.kind === "createFailed") return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4487
- text: t("toast.createFailed", { message: toast.message }),
4488
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4489
- holdMs: LONG_TOAST_HOLD_MS,
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,
4490
4549
  onDone: dismissToast
4491
- }, `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 }) {
4492
4558
  return (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4493
- text: plainNoticeText(toast, t),
4494
- icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
4495
- onDone: dismissToast
4496
- }, `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);
4497
4566
  }
4498
- /** The copy of one plain warning, keyed by the notice kind the union closes over. */
4499
- function plainNoticeText(toast, t) {
4500
- switch (toast.kind) {
4501
- case "pinFailed": return t("toast.pinFailed");
4502
- case "unpinFailed": return t("toast.unpinFailed");
4503
- case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
4504
- case "archivedNotOpenable": return t("toast.archivedNotOpenable");
4505
- /* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
4506
- default: return assertNever$1(toast);
4507
- }
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
+ });
4508
4740
  }
4509
4741
  //#endregion
4510
4742
  //#region lib/types/client/locales.js
@@ -4572,6 +4804,9 @@ window.__ModuleLoader__.load({
4572
4804
  "menu.unarchiveSession": "取消归档",
4573
4805
  "menu.pinSession": "置顶会话",
4574
4806
  "menu.unpinSession": "取消置顶",
4807
+ "menu.sessionId": "复制 Session ID",
4808
+ "toast.sessionIdCopied": "Session ID 已复制",
4809
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
4575
4810
  "row.archived": "已归档",
4576
4811
  "row.pinned": "已置顶",
4577
4812
  "toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
@@ -4598,6 +4833,8 @@ window.__ModuleLoader__.load({
4598
4833
  "toast.pinFailed": "置顶失败,请稍后重试",
4599
4834
  "toast.unpinFailed": "取消置顶失败,请稍后重试",
4600
4835
  "toast.createFailed": "新建会话失败:{message}",
4836
+ "toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
4837
+ "toast.openForkSource": "打开原会话",
4601
4838
  "sessions.count.one": "{n} 个会话",
4602
4839
  "sessions.count.other": "{n} 个会话",
4603
4840
  "actions.workspace.aria": "工作区“{name}”的操作",
@@ -4689,6 +4926,9 @@ window.__ModuleLoader__.load({
4689
4926
  "menu.unarchiveSession": "Unarchive session",
4690
4927
  "menu.pinSession": "Pin session",
4691
4928
  "menu.unpinSession": "Unpin session",
4929
+ "menu.sessionId": "Copy Session ID",
4930
+ "toast.sessionIdCopied": "Session ID copied",
4931
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
4692
4932
  "row.archived": "Archived",
4693
4933
  "row.pinned": "Pinned",
4694
4934
  "toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
@@ -4715,6 +4955,8 @@ window.__ModuleLoader__.load({
4715
4955
  "toast.pinFailed": "Pin failed. Try again later.",
4716
4956
  "toast.unpinFailed": "Unpin failed. Try again later.",
4717
4957
  "toast.createFailed": "New session failed: {message}",
4958
+ "toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
4959
+ "toast.openForkSource": "Open original session",
4718
4960
  "sessions.count.one": "{n} session",
4719
4961
  "sessions.count.other": "{n} sessions",
4720
4962
  "actions.workspace.aria": "Workspace actions for {name}",
@@ -4767,7 +5009,8 @@ window.__ModuleLoader__.load({
4767
5009
  "remote",
4768
5010
  "remote.directoryPicker",
4769
5011
  "layout",
4770
- "shortcuts"
5012
+ "shortcuts",
5013
+ "remoteAccess"
4771
5014
  ];
4772
5015
  /**
4773
5016
  * Register the browser and picker once their slot declarations are on the
@@ -4793,7 +5036,10 @@ window.__ModuleLoader__.load({
4793
5036
  });
4794
5037
  };
4795
5038
  const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
4796
- 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
+ });
4797
5043
  ctx.effect(() => ctx.locale.register(NS, {
4798
5044
  zh,
4799
5045
  en
@@ -4871,7 +5117,12 @@ window.__ModuleLoader__.load({
4871
5117
  },
4872
5118
  unarchiveSession
4873
5119
  });
4874
- installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession);
5120
+ installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
5121
+ notify({
5122
+ kind: "shortcutForkFailed",
5123
+ reason
5124
+ });
5125
+ });
4875
5126
  const archiveConfirmInjected = () => ({
4876
5127
  hooks: { archiveRequest },
4877
5128
  settleSessionArchive: () => {
@@ -4892,7 +5143,15 @@ window.__ModuleLoader__.load({
4892
5143
  parent_session_id: sessionId,
4893
5144
  click_position: "sidebar"
4894
5145
  });
4895
- }).catch(() => {});
5146
+ }, { allowMigration: false }).catch((error) => {
5147
+ if (error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/migration-required") notify({
5148
+ kind: "forkRequiresOpen",
5149
+ sessionId
5150
+ });
5151
+ });
5152
+ } });
5153
+ const sessionIdInjected = () => ({ copySessionId: async (sessionId) => {
5154
+ notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
4896
5155
  } });
4897
5156
  const renameInjected = () => ({ requestSessionRename });
4898
5157
  const renameDialogInjected = () => ({
@@ -4900,19 +5159,28 @@ window.__ModuleLoader__.load({
4900
5159
  settleSessionRename: shortcutControls.closeRename,
4901
5160
  renameSession
4902
5161
  });
5162
+ registerToasts(ctx, viewStore, {
5163
+ undoArchive: unarchiveSession,
5164
+ showArchived: () => {
5165
+ viewInstance.actions.setArchivedFilter("show");
5166
+ },
5167
+ openForkSource: (sessionId) => {
5168
+ if (workspaces.list.getSnapshot().archivedSessionIds.includes(sessionId)) {
5169
+ notify({ kind: "archivedNotOpenable" });
5170
+ return;
5171
+ }
5172
+ openSession(sessionId);
5173
+ }
5174
+ });
4903
5175
  const rowToastInjected = () => ({
4904
5176
  hooks: { toast: rowToast },
4905
5177
  dismissToast: () => {
4906
5178
  rowToast.set(null);
4907
- },
4908
- undoArchive: unarchiveSession,
4909
- showArchived: () => {
4910
- viewInstance.actions.setArchivedFilter("show");
4911
5179
  }
4912
5180
  });
4913
5181
  const browserInjected = () => ({
4914
- startSession: (workspaceId) => {
4915
- uiWorkspace.startSession(workspaceId);
5182
+ startSession: (workspaceId, options) => {
5183
+ uiWorkspace.startSession(workspaceId, options);
4916
5184
  },
4917
5185
  open: openSession,
4918
5186
  searchSessions,
@@ -4938,7 +5206,6 @@ window.__ModuleLoader__.load({
4938
5206
  requestAddWorkspace: shortcutControls.add,
4939
5207
  closeAddWorkspace: shortcutControls.closeAdd,
4940
5208
  setDirectoryBusy: shortcutControls.directoryBusy,
4941
- dismissForkError: shortcutControls.dismissForkError,
4942
5209
  hooks: {
4943
5210
  directoryFlow: browserFlowSource,
4944
5211
  hostInfo,
@@ -5011,6 +5278,13 @@ window.__ModuleLoader__.load({
5011
5278
  locale: NS,
5012
5279
  inject: archiveInjected
5013
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);
5014
5288
  });
5015
5289
  ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
5016
5290
  yield ctx.slots.register({
@@ -5044,9 +5318,11 @@ window.__ModuleLoader__.load({
5044
5318
  yield ctx.slots.register({
5045
5319
  name: "shell.overlay",
5046
5320
  id: "workspace.row-toast",
5047
- locale: NS,
5048
- store: viewStore,
5049
- inject: rowToastInjected
5321
+ inject: rowToastInjected,
5322
+ children: { "workspace.toast": {
5323
+ kind: "keyed",
5324
+ scope: "root"
5325
+ } }
5050
5326
  }, RowActionToast);
5051
5327
  });
5052
5328
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({