@qilin-agent/client-ui-workspace 3.1.5 → 3.1.7

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
@@ -4,11 +4,11 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
7
8
  let _qilin_agent_client_store = require("@qilin-agent/client-store");
8
9
  let _qilin_agent_kylin = require("@qilin-agent/kylin");
9
10
  let react_jsx_runtime = require("react/jsx-runtime");
10
11
  let react = require("react");
11
- let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
12
12
  //#region lib/types/client/shortcuts.js
13
13
  /**
14
14
  * Create the private browser request source shared by commands and controls.
@@ -688,6 +688,7 @@ window.__ModuleLoader__.load({
688
688
  workspaces;
689
689
  sessions;
690
690
  view;
691
+ notify;
691
692
  connecting = /* @__PURE__ */ new Map();
692
693
  lifetime = new AbortController();
693
694
  /**
@@ -703,13 +704,15 @@ window.__ModuleLoader__.load({
703
704
  * @param workspaces - pure Workspace Controller.
704
705
  * @param sessions - pure Session Controller.
705
706
  * @param view - the browser's viewing-store write set, which owns saved Session order.
707
+ * @param notify - show one notice through the Workspace notice channel.
706
708
  */
707
- constructor(ctx, directoryPicker, workspaces, sessions, view) {
709
+ constructor(ctx, directoryPicker, workspaces, sessions, view, notify) {
708
710
  super(ctx, "uiWorkspace");
709
711
  this.directoryPicker = directoryPicker;
710
712
  this.workspaces = workspaces;
711
713
  this.sessions = sessions;
712
714
  this.view = view;
715
+ this.notify = notify;
713
716
  ctx.effect(() => {
714
717
  const stop = this.watchNavigation();
715
718
  return () => {
@@ -721,29 +724,49 @@ window.__ModuleLoader__.load({
721
724
  };
722
725
  }, "ui-workspace: Workspace navigation policy");
723
726
  }
724
- async connectWorkspace(workspaceId) {
727
+ async connectWorkspace(workspaceId, options) {
725
728
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
726
729
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
727
730
  const inflight = this.connecting.get(workspaceId);
728
- if (inflight !== void 0) return inflight;
731
+ if (inflight !== void 0 && inflight.agentPreset === options?.agentPreset) return inflight.attempt;
729
732
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
730
733
  const sessions = this.sessions.list.getSnapshot();
731
734
  for (const id of sessions.ids) {
732
735
  const summary = sessions.byId[id];
733
- if (summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(summary.id) && !archived.includes(summary.id)) return summary.id;
736
+ const listed = summary?.projectionValues?.agentPreset;
737
+ if (summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(summary.id) && !archived.includes(summary.id) && (options?.agentPreset === void 0 || typeof listed !== "string" || listed === options.agentPreset)) {
738
+ const reuse = this.sessions.create({
739
+ workspaceId,
740
+ sessionId: summary.id,
741
+ ...options?.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
742
+ }).finally(() => {
743
+ this.connecting.delete(workspaceId);
744
+ });
745
+ this.connecting.set(workspaceId, {
746
+ agentPreset: options?.agentPreset,
747
+ attempt: reuse
748
+ });
749
+ return reuse;
750
+ }
734
751
  }
735
- const attempt = this.sessions.create({ workspaceId }).finally(() => {
752
+ const attempt = this.sessions.create({
753
+ workspaceId,
754
+ ...options?.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
755
+ }).finally(() => {
736
756
  this.connecting.delete(workspaceId);
737
757
  });
738
- this.connecting.set(workspaceId, attempt);
758
+ this.connecting.set(workspaceId, {
759
+ agentPreset: options?.agentPreset,
760
+ attempt
761
+ });
739
762
  return attempt;
740
763
  }
741
764
  openSession(target) {
742
765
  this.replaceMain(target, this.lifetime.signal);
743
766
  }
744
- async openWorkspace(workspaceId, beforeOpen) {
767
+ async openWorkspace(workspaceId, beforeOpen, options) {
745
768
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
746
- const sessionId = await this.connectWorkspace(workspaceId);
769
+ const sessionId = await this.connectWorkspace(workspaceId, options);
747
770
  if (navigation.aborted) return;
748
771
  this.replaceMain(sessionId, navigation, beforeOpen);
749
772
  }
@@ -755,7 +778,10 @@ window.__ModuleLoader__.load({
755
778
  });
756
779
  if (!navigation.aborted) this.replaceMain(childId, navigation);
757
780
  }
758
- startSession(workspaceId) {
781
+ startSession(workspaceId, options) {
782
+ const { agentPreset, ...draft } = options ?? {};
783
+ const draftOptions = options === void 0 ? void 0 : structuredClone(draft);
784
+ const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || (draftOptions.whenDraftExists ?? (draftOptions.clearPreviousDraft === true ? "replace" : "keep")) === "replace");
759
785
  const workspace = this.workspaces.list.getSnapshot();
760
786
  const sessions = this.sessions.list.getSnapshot();
761
787
  const current = this.mainReference?.sessionId;
@@ -763,13 +789,37 @@ window.__ModuleLoader__.load({
763
789
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
764
790
  const target = workspaceId ?? currentWorkspaceId ?? recent;
765
791
  if (target === void 0) {
792
+ if (initializeDraft) {
793
+ this.notify({
794
+ kind: "createFailed",
795
+ message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
796
+ });
797
+ return;
798
+ }
766
799
  this.clearMain();
767
800
  return;
768
801
  }
769
- this.openWorkspace(target).catch((reason) => {
802
+ this.openWorkspace(target, initializeDraft ? (id) => {
803
+ const binding = this.sessions.binding(id);
804
+ if (binding === void 0) this.draftPreparationFailed();
805
+ this.prepareDraft(binding, draftOptions);
806
+ } : void 0, agentPreset === void 0 ? void 0 : { agentPreset }).catch((reason) => {
770
807
  console.warn("new session failed:", reason);
771
808
  });
772
809
  }
810
+ prepareDraft(binding, options) {
811
+ const conversation = this.ctx.get("conversation");
812
+ if (conversation === void 0) this.draftPreparationFailed();
813
+ if (conversation.input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
814
+ }
815
+ draftPreparationFailed() {
816
+ const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
817
+ this.notify({
818
+ kind: "createFailed",
819
+ message
820
+ });
821
+ throw new Error(message);
822
+ }
773
823
  async archiveSession(sessionId) {
774
824
  await this.workspaces.archiveSession(sessionId);
775
825
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
@@ -927,7 +977,7 @@ window.__ModuleLoader__.load({
927
977
  }
928
978
  //#endregion
929
979
  //#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-workspace/src/client/rows/Rows.module.css.mjs
930
- const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover,.RrwzeW_sessionRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--qilin-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.RrwzeW_searchResultRow:hover,.RrwzeW_searchResultRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{height:32px;animation:RrwzeW_row-in .15s var(--ds-ease-in-out);gap:0}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 4px}@keyframes RrwzeW_row-in{0%{opacity:0}}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{scroll-behavior:smooth;flex:1}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_title{text-overflow:clip}}.RrwzeW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.RrwzeW_time{color:var(--qilin-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.RrwzeW_dot{flex:none}.RrwzeW_pinIndicator{width:16px;height:20px;color:var(--qilin-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_pinIndicator,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_pinIndicator{display:none}.RrwzeW_rowActions{flex:none;align-items:center;gap:12px;display:none}.RrwzeW_projectRow:hover .RrwzeW_rowActions,.RrwzeW_sessionRow:hover .RrwzeW_rowActions,.RrwzeW_projectRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_time,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_time{display:none}.RrwzeW_projectRow.RrwzeW_menuOpen,.RrwzeW_sessionRow.RrwzeW_menuOpen{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_sessionRow.RrwzeW_dropBefore,.RrwzeW_sessionRow.RrwzeW_dropAfter{position:relative}.RrwzeW_sessionRow.RrwzeW_dropBefore:before,.RrwzeW_sessionRow.RrwzeW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-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(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-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}.RrwzeW_sessionRow.RrwzeW_dropBefore:before{top:-7px}.RrwzeW_sessionRow.RrwzeW_dropAfter:after{bottom:-7px}.RrwzeW_hoverContent{flex-direction:column;gap:8px;display:flex}.RrwzeW_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.RrwzeW_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.RrwzeW_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.RrwzeW_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.RrwzeW_iconButton{cursor:pointer;width:16px;height:16px;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.RrwzeW_iconButton:hover{color:var(--qilin-alias-label-primary)}.RrwzeW_chevron{color:var(--qilin-alias-label-caption)}@media (prefers-reduced-motion:reduce){.RrwzeW_sessionRow,.RrwzeW_arrow{transition:none;animation:none}.RrwzeW_sessionRow .RrwzeW_title{scroll-behavior:auto}}";
980
+ const css$2 = ".RrwzeW_projectRow,.RrwzeW_sessionRow{padding:0 8px;cursor:pointer;user-select:none;color:var(--qilin-alias-label-primary);border-radius:8px;align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.RrwzeW_projectRow:hover,.RrwzeW_sessionRow:hover,.RrwzeW_sessionRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultRow{box-sizing:border-box;cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--qilin-alias-label-primary);background:0 0;border:none;border-radius:8px;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.RrwzeW_searchResultRow:hover,.RrwzeW_searchResultRow.RrwzeW_selected{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_searchResultHeading{align-items:center;min-width:0;display:flex}.RrwzeW_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.RrwzeW_searchResultWorkspace,.RrwzeW_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.RrwzeW_searchResultWorkspace{max-width:40%;color:var(--qilin-alias-label-tertiary);flex:none}.RrwzeW_searchResultSnippet{min-width:0;color:var(--qilin-alias-label-secondary);flex:1}.RrwzeW_projectRow{box-sizing:border-box;align-items:center;height:34px}.RrwzeW_projectRow .RrwzeW_rowActions{height:20px}.RrwzeW_sessionRow{height:32px;animation:RrwzeW_row-in .15s var(--ds-ease-in-out);gap:0}.RrwzeW_sessionRow .RrwzeW_title{margin:0 6px 0 4px}@keyframes RrwzeW_row-in{0%{opacity:0}}.RrwzeW_slot{width:16px;height:20px;color:var(--qilin-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.RrwzeW_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.RrwzeW_folderActive{color:var(--qilin-alias-state-business-primary)}.RrwzeW_projectRow .RrwzeW_chevron{display:none}.RrwzeW_projectRow:hover .RrwzeW_chevron{display:inline-flex}.RrwzeW_projectRow:hover .RrwzeW_folder{display:none}.RrwzeW_arrow{transition:transform .15s var(--ds-ease-in-out)}.RrwzeW_arrowOpen{transform:rotate(90deg)}.RrwzeW_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.RrwzeW_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.RrwzeW_renameInput{border:.5px solid var(--qilin-alias-border-l4);background:var(--qilin-alias-button-elevated-fill);min-width:0;color:inherit;border-radius:4px;outline:none;padding:0 2px;font-size:14px;line-height:20px}.RrwzeW_sessionRow .RrwzeW_title{flex:1}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.RrwzeW_sessionRow .RrwzeW_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}@media (hover:hover){.RrwzeW_sessionRow:hover .RrwzeW_title,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_title{text-overflow:clip}}.RrwzeW_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.RrwzeW_time{color:var(--qilin-alias-label-tertiary);flex:none;font-size:12px;line-height:20px}.RrwzeW_dot{flex:none}.RrwzeW_pinIndicator{width:16px;height:20px;color:var(--qilin-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_pinIndicator,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_pinIndicator{display:none}.RrwzeW_rowActions{flex:none;align-items:center;gap:15px;display:none}.RrwzeW_projectRow:hover .RrwzeW_rowActions,.RrwzeW_sessionRow:hover .RrwzeW_rowActions,.RrwzeW_projectRow.RrwzeW_menuOpen .RrwzeW_rowActions,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_rowActions{display:inline-flex}.RrwzeW_sessionRow:hover .RrwzeW_time,.RrwzeW_sessionRow.RrwzeW_menuOpen .RrwzeW_time{display:none}.RrwzeW_projectRow.RrwzeW_menuOpen,.RrwzeW_sessionRow.RrwzeW_menuOpen{background:var(--qilin-alias-interactive-bg-hover)}.RrwzeW_sessionRow.RrwzeW_dropBefore,.RrwzeW_sessionRow.RrwzeW_dropAfter{position:relative}.RrwzeW_sessionRow.RrwzeW_dropBefore:before,.RrwzeW_sessionRow.RrwzeW_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--qilin-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(--qilin-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--qilin-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}.RrwzeW_sessionRow.RrwzeW_dropBefore:before{top:-7px}.RrwzeW_sessionRow.RrwzeW_dropAfter:after{bottom:-7px}.RrwzeW_hoverContent{flex-direction:column;gap:8px;display:flex}.RrwzeW_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.RrwzeW_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.RrwzeW_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.RrwzeW_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.RrwzeW_iconButton{cursor:pointer;width:16px;height:16px;color:var(--qilin-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.RrwzeW_iconButton:hover{color:var(--qilin-alias-label-primary)}.RrwzeW_chevron{color:var(--qilin-alias-label-caption)}@media (prefers-reduced-motion:reduce){.RrwzeW_sessionRow,.RrwzeW_arrow{transition:none;animation:none}}";
931
981
  const tagId$2 = "@qilin-agent/client-ui-workspace/Rows.module.css";
932
982
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
933
983
  const tag = document.createElement("style");
@@ -978,7 +1028,8 @@ window.__ModuleLoader__.load({
978
1028
  * Workspace browser tree row components (figma Cell set 14:3080): pure presentational —
979
1029
  * all data and callbacks arrive via props. Hover swaps (folder->chevron,
980
1030
  * time->ellipsis, action buttons) are CSS-only, and a session row's clipped
981
- * title is scrolled programmatically while the row is hovered. Row ... menus are
1031
+ * title marquees programmatically while the row is hovered, fading both
1032
+ * clipping edges from the depth of its travel. Row ... menus are
982
1033
  * visual-only except workspace Rename/Delete and session Rename/Fork/Archive; the
983
1034
  * session and workspace hover cards are suppressed while a menu is open.
984
1035
  */
@@ -986,29 +1037,91 @@ window.__ModuleLoader__.load({
986
1037
  function displayTitle(node, t) {
987
1038
  return node.blank ? t("session.new") : node.title || t("session.untitled");
988
1039
  }
1040
+ const MIN_TITLE_REVEAL_PX = 8;
1041
+ const TITLE_MARQUEE_PX_PER_MS = .03;
1042
+ /** Whether the platform asked for reduced motion; jsdom implements no matchMedia. */
1043
+ function prefersReducedMotion() {
1044
+ return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1045
+ }
989
1046
  /**
990
- * Reveal a title wider than its one-line cell while its row is hovered: the
991
- * title clips its own text, so the far edge (a fork's incremented title, for
992
- * example) is reachable by scrolling the element to its end. Leaving returns it
993
- * to the start in one step, because the resting ellipsis and the narrowed cell
994
- * would otherwise meet the text while it travelled back. A title that fits has
995
- * no scroll range to move, and the stylesheet decides whether either move
996
- * glides or jumps.
1047
+ * Place the title's scroll position and publish the stylesheet's fade-mask
1048
+ * hooks: `data-scrolled` while the title has left its start (left fade) and
1049
+ * `data-clipped` while text remains beyond the right edge (right fade).
997
1050
  * @param title - the row's clipping title element.
998
- * @param revealed - whether the pointer is on the row.
1051
+ * @param left - scroll offset in CSS pixels.
1052
+ * @param range - the title's maximum scroll offset in CSS pixels.
999
1053
  */
1000
- function revealClippedTitle(title, revealed) {
1001
- /* v8 ignore next -- defensive: the title span renders unconditionally. */
1002
- if (title === null) return;
1003
- if (revealed) {
1004
- title.scrollLeft = title.scrollWidth - title.clientWidth;
1005
- return;
1006
- }
1054
+ function placeTitle(title, left, range) {
1055
+ if (typeof title.scrollTo === "function") title.scrollTo({
1056
+ left,
1057
+ behavior: "instant"
1058
+ });
1059
+ else title.scrollLeft = left;
1060
+ if (left > 0) title.dataset.scrolled = "";
1061
+ else delete title.dataset.scrolled;
1062
+ if (left < range) title.dataset.clipped = "";
1063
+ else delete title.dataset.clipped;
1064
+ }
1065
+ /**
1066
+ * Return the title to its resting state: scrolled to the start with both fade
1067
+ * masks off, so the resting ellipsis renders at full strength.
1068
+ * @param title - the row's clipping title element.
1069
+ */
1070
+ function restTitle(title) {
1007
1071
  if (typeof title.scrollTo === "function") title.scrollTo({
1008
1072
  left: 0,
1009
1073
  behavior: "instant"
1010
1074
  });
1011
1075
  else title.scrollLeft = 0;
1076
+ delete title.dataset.scrolled;
1077
+ delete title.dataset.clipped;
1078
+ }
1079
+ /**
1080
+ * Marquee a title wider than its one-line cell while its row is hovered: the
1081
+ * title clips its own text, so entering crawls it at a constant speed until the
1082
+ * far edge (a fork's incremented title, for example) is in view, then rests
1083
+ * there under the pointer. Overflow of at most {@link MIN_TITLE_REVEAL_PX}
1084
+ * stays put — a barely-clipped title moving a few pixels reads as jitter, not a
1085
+ * reveal. Leaving returns the title to the start in one step, because the
1086
+ * resting ellipsis and the narrowed cell would otherwise meet the text while it
1087
+ * travelled back. Reduced motion jumps to the far edge instead of crawling.
1088
+ * @param title - ref to the row's clipping title element.
1089
+ * @returns stable pointer enter/leave handlers for the row.
1090
+ */
1091
+ function useTitleMarquee(title) {
1092
+ const frame = (0, react.useRef)(0);
1093
+ (0, react.useEffect)(() => () => {
1094
+ cancelAnimationFrame(frame.current);
1095
+ }, []);
1096
+ return (0, react.useMemo)(() => ({
1097
+ enter: () => {
1098
+ /* v8 ignore next -- defensive: the title span renders unconditionally. */
1099
+ if (title.current === null) return;
1100
+ const element = title.current;
1101
+ const range = element.scrollWidth - element.clientWidth;
1102
+ if (range <= MIN_TITLE_REVEAL_PX) return;
1103
+ if (prefersReducedMotion()) {
1104
+ placeTitle(element, range, range);
1105
+ return;
1106
+ }
1107
+ cancelAnimationFrame(frame.current);
1108
+ let previous;
1109
+ let position = 0;
1110
+ const step = (now) => {
1111
+ position += previous === void 0 ? 0 : (now - previous) * TITLE_MARQUEE_PX_PER_MS;
1112
+ previous = now;
1113
+ placeTitle(element, Math.min(position, range), range);
1114
+ if (position < range) frame.current = requestAnimationFrame(step);
1115
+ };
1116
+ frame.current = requestAnimationFrame(step);
1117
+ },
1118
+ leave: () => {
1119
+ cancelAnimationFrame(frame.current);
1120
+ /* v8 ignore next -- defensive: the title span renders unconditionally. */
1121
+ if (title.current === null) return;
1122
+ restTitle(title.current);
1123
+ }
1124
+ }), [title]);
1012
1125
  }
1013
1126
  /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
1014
1127
  function timeLabel(updatedAt, now, t) {
@@ -1081,11 +1194,11 @@ window.__ModuleLoader__.load({
1081
1194
  const workspaceMenuItems = [{
1082
1195
  id: "rename",
1083
1196
  label: t("rename"),
1084
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEditOutline16, {})
1197
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEditOutlineRegular, {})
1085
1198
  }, {
1086
1199
  id: "delete",
1087
1200
  label: t("delete.workspace"),
1088
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTrashOutline16, {}),
1201
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTrashOutlineRegular, {}),
1089
1202
  danger: true
1090
1203
  }];
1091
1204
  const ownRow = (0, react_jsx_runtime.jsxs)("div", {
@@ -1103,11 +1216,11 @@ window.__ModuleLoader__.load({
1103
1216
  children: [
1104
1217
  (0, react_jsx_runtime.jsx)("span", {
1105
1218
  className: clsx(Rows_module_css_default.slot, Rows_module_css_default.folder, active && Rows_module_css_default.folderActive),
1106
- children: row.expanded ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderOpen16, {}) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderClose16, {})
1219
+ children: row.expanded ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderOpenRegular, {}) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderCloseRegular, {})
1107
1220
  }),
1108
1221
  (0, react_jsx_runtime.jsx)("span", {
1109
1222
  className: clsx(Rows_module_css_default.slot, Rows_module_css_default.chevron),
1110
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTriangleRightFill14, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
1223
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconTriangleRightFillRegular, { className: clsx(Rows_module_css_default.arrow, row.expanded && Rows_module_css_default.arrowOpen) })
1111
1224
  }),
1112
1225
  (0, react_jsx_runtime.jsx)("span", {
1113
1226
  className: Rows_module_css_default.projectText,
@@ -1141,7 +1254,7 @@ window.__ModuleLoader__.load({
1141
1254
  e.stopPropagation();
1142
1255
  setMenuOpen((v) => !v);
1143
1256
  },
1144
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEllipsisOutline16, {})
1257
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEllipsisOutlineRegular, {})
1145
1258
  })
1146
1259
  }), (0, react_jsx_runtime.jsx)("button", {
1147
1260
  type: "button",
@@ -1151,7 +1264,7 @@ window.__ModuleLoader__.load({
1151
1264
  e.stopPropagation();
1152
1265
  onCreate();
1153
1266
  },
1154
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutline16, {})
1267
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutlineRegular, {})
1155
1268
  })]
1156
1269
  })
1157
1270
  ]
@@ -1314,8 +1427,9 @@ window.__ModuleLoader__.load({
1314
1427
  });
1315
1428
  }
1316
1429
  /**
1317
- * One top-level 34px session row: status dot (pending user interaction outranks
1318
- * own or descendant activity), title, relative time, and the row actions menu.
1430
+ * One top-level 32px session row: status dot (pending user interaction outranks
1431
+ * own or descendant activity), a title that marquees while the row is hovered,
1432
+ * relative time, and the hover action strip (menu, archive, pin).
1319
1433
  * @param props.node - derived session node.
1320
1434
  * @param props.currentId - selected session id (row highlight).
1321
1435
  * @param props.now - epoch ms for relative-time formatting.
@@ -1323,6 +1437,7 @@ window.__ModuleLoader__.load({
1323
1437
  * @param props.onRename - open the session rename dialog (id + current title).
1324
1438
  * @param props.onFork - fork a session at its last completed turn.
1325
1439
  * @param props.onArchive - archive a session by id.
1440
+ * @param props.onCopyId - copy a session's complete id without opening it.
1326
1441
  * @param props.onPin - pin a session by id (leading its group and the flat list).
1327
1442
  * @param props.onUnpin - drop a session's pin.
1328
1443
  * @param props.onReveal - scroll this row into view after search navigation, then acknowledge it.
@@ -1330,7 +1445,7 @@ window.__ModuleLoader__.load({
1330
1445
  * @param props.t - the browser root's locale seat.
1331
1446
  * @returns the session row.
1332
1447
  */
1333
- function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onPin, onUnpin, onReveal, drag, renderSlot, t }) {
1448
+ function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork, onArchive, onCopyId, onPin, onUnpin, onReveal, drag, renderSlot, t }) {
1334
1449
  const row = node;
1335
1450
  const title = displayTitle(node, t);
1336
1451
  const selected = node.id === currentId;
@@ -1340,6 +1455,7 @@ window.__ModuleLoader__.load({
1340
1455
  const [menuOpen, setMenuOpen] = (0, react.useState)(false);
1341
1456
  const rowRef = (0, react.useRef)(null);
1342
1457
  const titleRef = (0, react.useRef)(null);
1458
+ const marquee = useTitleMarquee(titleRef);
1343
1459
  (0, react.useEffect)(() => {
1344
1460
  if (onReveal === void 0) return;
1345
1461
  rowRef.current?.scrollIntoView({ block: "nearest" });
@@ -1354,17 +1470,22 @@ window.__ModuleLoader__.load({
1354
1470
  {
1355
1471
  id: "rename",
1356
1472
  label: t("rename"),
1357
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEditOutline16, {})
1473
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEditOutlineRegular, {})
1358
1474
  },
1359
1475
  {
1360
1476
  id: "fork",
1361
1477
  label: t("menu.fork"),
1362
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconBranchOutline16, {})
1478
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconBranchOutlineRegular, {})
1363
1479
  },
1364
1480
  {
1365
1481
  id: "archive",
1366
1482
  label: t("menu.archiveSession"),
1367
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconArchiveOutline20, { size: 16 })
1483
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconArchiveOutlineRegular, { size: 16 })
1484
+ },
1485
+ {
1486
+ id: "copyId",
1487
+ label: t("menu.sessionId"),
1488
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCopyOutlineRegular, {})
1368
1489
  }
1369
1490
  ];
1370
1491
  return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.HoverCard, {
@@ -1376,12 +1497,8 @@ window.__ModuleLoader__.load({
1376
1497
  onClick: () => {
1377
1498
  onOpen(node.id);
1378
1499
  },
1379
- onPointerEnter: () => {
1380
- revealClippedTitle(titleRef.current, true);
1381
- },
1382
- onPointerLeave: () => {
1383
- revealClippedTitle(titleRef.current, false);
1384
- },
1500
+ onPointerEnter: marquee.enter,
1501
+ onPointerLeave: marquee.leave,
1385
1502
  draggable,
1386
1503
  onDragStart: drag === void 0 || row.blank ? void 0 : (e) => {
1387
1504
  e.dataTransfer.effectAllowed = "move";
@@ -1417,41 +1534,67 @@ window.__ModuleLoader__.load({
1417
1534
  row.pinned && !row.blank && (0, react_jsx_runtime.jsx)(PinnedIndicator, { t }),
1418
1535
  !row.blank && (0, react_jsx_runtime.jsxs)("span", {
1419
1536
  className: Rows_module_css_default.rowActions,
1420
- children: [(0, react_jsx_runtime.jsx)("button", {
1421
- type: "button",
1422
- className: Rows_module_css_default.iconButton,
1423
- "aria-label": t(row.pinned ? "actions.unpin" : "actions.pin"),
1424
- onClick: (e) => {
1425
- e.stopPropagation();
1426
- (row.pinned ? onUnpin : onPin)(node.id);
1427
- },
1428
- children: row.pinned ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinFillRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinOutlineRegular, { size: 14 })
1429
- }), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Menu, {
1430
- open: menuOpen,
1431
- onClose: () => {
1432
- setMenuOpen(false);
1433
- },
1434
- items: sessionMenuItems,
1435
- onSelect: (id) => {
1436
- setMenuOpen(false);
1437
- if (id === "pin") (row.pinned ? onUnpin : onPin)(node.id);
1438
- if (id === "rename") onRename(node.id, row.title);
1439
- if (id === "fork") onFork(node.id);
1440
- if (id === "archive") onArchive(node.id);
1441
- },
1442
- portal: true,
1443
- closeOnPointerLeave: true,
1444
- anchor: (0, react_jsx_runtime.jsx)("button", {
1445
- type: "button",
1446
- className: Rows_module_css_default.iconButton,
1447
- "aria-label": t("actions.session.aria", { name: title }),
1448
- onClick: (e) => {
1449
- e.stopPropagation();
1450
- setMenuOpen((v) => !v);
1537
+ children: [
1538
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Menu, {
1539
+ open: menuOpen,
1540
+ onClose: () => {
1541
+ setMenuOpen(false);
1542
+ },
1543
+ items: sessionMenuItems,
1544
+ onSelect: (id) => {
1545
+ setMenuOpen(false);
1546
+ if (id === "pin") (row.pinned ? onUnpin : onPin)(node.id);
1547
+ if (id === "rename") onRename(node.id, row.title);
1548
+ if (id === "fork") onFork(node.id);
1549
+ if (id === "archive") onArchive(node.id);
1550
+ if (id === "copyId") onCopyId(node.id);
1451
1551
  },
1452
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEllipsisOutline16, {})
1552
+ portal: true,
1553
+ closeOnPointerLeave: true,
1554
+ anchor: (0, react_jsx_runtime.jsx)("button", {
1555
+ type: "button",
1556
+ className: Rows_module_css_default.iconButton,
1557
+ "aria-label": t("actions.session.aria", { name: title }),
1558
+ onClick: (e) => {
1559
+ e.stopPropagation();
1560
+ setMenuOpen((v) => !v);
1561
+ },
1562
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconEllipsisOutlineRegular, {})
1563
+ })
1564
+ }),
1565
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
1566
+ label: t("actions.archive"),
1567
+ side: "bottom",
1568
+ delayMs: 500,
1569
+ portal: true,
1570
+ children: (0, react_jsx_runtime.jsx)("button", {
1571
+ type: "button",
1572
+ className: Rows_module_css_default.iconButton,
1573
+ "aria-label": t("menu.archiveSession"),
1574
+ onClick: (e) => {
1575
+ e.stopPropagation();
1576
+ onArchive(node.id);
1577
+ },
1578
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconArchiveOutlineRegular, { size: 16 })
1579
+ })
1580
+ }),
1581
+ (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Tooltip, {
1582
+ label: t(row.pinned ? "actions.unpin" : "actions.pin"),
1583
+ side: "bottom",
1584
+ delayMs: 500,
1585
+ portal: true,
1586
+ children: (0, react_jsx_runtime.jsx)("button", {
1587
+ type: "button",
1588
+ className: Rows_module_css_default.iconButton,
1589
+ "aria-label": t(row.pinned ? "actions.unpin" : "actions.pin"),
1590
+ onClick: (e) => {
1591
+ e.stopPropagation();
1592
+ (row.pinned ? onUnpin : onPin)(node.id);
1593
+ },
1594
+ children: row.pinned ? (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinFillRegular, { size: 14 }) : (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPinOutlineRegular, { size: 14 })
1595
+ })
1453
1596
  })
1454
- })]
1597
+ ]
1455
1598
  })
1456
1599
  ]
1457
1600
  }),
@@ -1507,14 +1650,14 @@ window.__ModuleLoader__.load({
1507
1650
  const addEntries = flowAvailable ? [{
1508
1651
  id: ADD_WORKSPACE,
1509
1652
  label: t("menu.addWorkspace"),
1510
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutline16, { size: 16 }),
1653
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlusOutlineRegular, { size: 16 }),
1511
1654
  disabled: flowBusy
1512
1655
  }] : [];
1513
1656
  const pinAdd = !addOnly && workspaces.length > 0;
1514
1657
  const items = pinAdd ? workspaces.map((workspace) => ({
1515
1658
  id: workspace.workspaceId,
1516
1659
  label: workspace.title,
1517
- icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderClose16, { size: 16 }),
1660
+ icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconFolderCloseRegular, { size: 16 }),
1518
1661
  disabled: flowBusy
1519
1662
  })) : addEntries;
1520
1663
  const menuIsEmpty = items.length === 0;
@@ -1710,22 +1853,46 @@ window.__ModuleLoader__.load({
1710
1853
  const SEARCH_DEBOUNCE_MS = 250;
1711
1854
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
1712
1855
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
1713
- /** Session rows visible per Workspace before the local overflow control. */
1714
- const COLLAPSED_SESSION_LIMIT = 5;
1715
- /** Fold one Workspace without charging its provisional New Session against the ordinary-row limit. */
1716
- function collapsedSessionRows(sessions) {
1717
- let ordinaryCount = 0;
1856
+ /** Non-blank Session rows visible per Workspace while no Session raises its watermark. */
1857
+ const SESSION_WATERMARK_BASE = 5;
1858
+ /** Highest watermark that attention-bearing Sessions raise automatically; Show more may exceed it. */
1859
+ const SESSION_WATERMARK_AUTO_MAX = 50;
1860
+ /** Show the blank row plus the first `watermark` non-blank rows, in list order. */
1861
+ function watermarkRows(sessions, watermark) {
1862
+ let count = 0;
1718
1863
  const rows = sessions.filter((session) => {
1719
1864
  if (session.blank) return true;
1720
- if (ordinaryCount >= COLLAPSED_SESSION_LIMIT) return false;
1721
- ordinaryCount += 1;
1722
- return true;
1865
+ count += 1;
1866
+ return count <= watermark;
1723
1867
  });
1724
1868
  return {
1725
1869
  rows,
1726
1870
  hiddenCount: sessions.length - rows.length
1727
1871
  };
1728
1872
  }
1873
+ /**
1874
+ * Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
1875
+ * child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
1876
+ */
1877
+ function requiredWatermark(sessions, current, revealSessionId) {
1878
+ let index = 0;
1879
+ let required = 0;
1880
+ for (const session of sessions) {
1881
+ if (session.blank) continue;
1882
+ index += 1;
1883
+ 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;
1884
+ }
1885
+ return required;
1886
+ }
1887
+ /** Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises. */
1888
+ function raiseWatermarks(marks, required) {
1889
+ let raised = marks;
1890
+ for (const [key, watermark] of required) if (watermark > (raised[key] ?? SESSION_WATERMARK_BASE)) raised = {
1891
+ ...raised,
1892
+ [key]: watermark
1893
+ };
1894
+ return raised;
1895
+ }
1729
1896
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
1730
1897
  function sanitizeSearchQuery(value) {
1731
1898
  const withoutNul = value.replaceAll("\0", "");
@@ -1736,10 +1903,6 @@ window.__ModuleLoader__.load({
1736
1903
  if (last >= 55296 && last <= 56319 && next >= 56320 && next <= 57343) end--;
1737
1904
  return withoutNul.slice(0, end);
1738
1905
  }
1739
- /** Immutable membership toggle for the local expand-all array. */
1740
- function toggled(list, key) {
1741
- return list.includes(key) ? list.filter((k) => k !== key) : [...list, key];
1742
- }
1743
1906
  /**
1744
1907
  * Accept the native drag at document level while a row drag is active: row
1745
1908
  * hover still owns the insertion marker, and releasing outside the list must
@@ -1827,7 +1990,7 @@ window.__ModuleLoader__.load({
1827
1990
  onClick: () => {
1828
1991
  setOpen((v) => !v);
1829
1992
  },
1830
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPersonalizationOutline16, {})
1993
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPersonalizationOutlineRegular, {})
1831
1994
  })
1832
1995
  })
1833
1996
  });
@@ -1838,12 +2001,12 @@ window.__ModuleLoader__.load({
1838
2001
  return e.clientY < rect.top + rect.height / 2 ? "before" : "after";
1839
2002
  }
1840
2003
  /** The scrolling session tree; unmounting drops the sessions subscription and expand-all state. */
1841
- function SessionTree({ list, useSessionStatus, startSession, open, forkSession, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, revealSessionId, onSessionRevealed }) {
2004
+ function SessionTree({ list, useSessionStatus, startSession, open, forkSession, copySessionId, workspaces, ungroupedSessionIds, archivedSessionIds, pinnedSessionIds, onSessionPin, onSessionUnpin, workspaceReady, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRename, onSessionArchive, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, renderSlot, revealSessionId, onSessionRevealed }) {
1842
2005
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
1843
2006
  const statuses = useSessionStatus((s) => s);
1844
2007
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
1845
2008
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
1846
- const [expandedSessionGroups, setExpandedSessionGroups] = (0, react.useState)([]);
2009
+ const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
1847
2010
  const [drag, setDrag] = (0, react.useState)(null);
1848
2011
  const sessionDropCommitted = (0, react.useRef)(false);
1849
2012
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
@@ -1904,17 +2067,23 @@ window.__ModuleLoader__.load({
1904
2067
  revealGroup,
1905
2068
  setGroupExpanded
1906
2069
  ]);
1907
- (0, react.useEffect)(() => {
1908
- if (revealSessionId === void 0 || revealGroup === void 0) return;
1909
- const group = groups.find((candidate) => candidate.key === revealGroup);
1910
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
1911
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
1912
- setExpandedSessionGroups((keys) => keys.includes(revealGroup) ? keys : [...keys, revealGroup]);
1913
- }, [
1914
- groups,
1915
- revealGroup,
1916
- revealSessionId
1917
- ]);
2070
+ const baselines = new Map(groups.map((group) => [group.key, Math.max(SESSION_WATERMARK_BASE, requiredWatermark(group.sessions, current, group.key === revealGroup ? revealSessionId : void 0))]));
2071
+ const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
2072
+ const underFoldedAncestor = (key) => {
2073
+ for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
2074
+ return false;
2075
+ };
2076
+ const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
2077
+ const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
2078
+ if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
2079
+ const dropWatermarks = (drop) => {
2080
+ if (!Object.keys(storedWatermarks).some(drop)) return;
2081
+ setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
2082
+ };
2083
+ const withinGroup = (key, root) => {
2084
+ for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
2085
+ return false;
2086
+ };
1918
2087
  const now = Date.now();
1919
2088
  const commitSessionDrag = (activeDrag, over) => {
1920
2089
  if (sessionDropCommitted.current) return;
@@ -1922,8 +2091,10 @@ window.__ModuleLoader__.load({
1922
2091
  setDrag(null);
1923
2092
  const group = groups.find((candidate) => candidate.key === activeDrag.accountKey);
1924
2093
  if (group === void 0) return;
1925
- const sessionsExpanded = expandedSessionGroups.includes(group.key);
1926
- const renderedSessions = sessionsExpanded ? group.sessions : collapsedSessionRows(group.sessions).rows;
2094
+ const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
2095
+ const visible = watermarkRows(group.sessions, watermark);
2096
+ const sessionsExpanded = visible.hiddenCount === 0;
2097
+ const renderedSessions = visible.rows;
1927
2098
  const targetIndex = renderedSessions.findIndex((session) => session.id === over.id);
1928
2099
  if (targetIndex === -1) return;
1929
2100
  const sourceIndex = renderedSessions.findIndex((session) => session.id === activeDrag.sessionId);
@@ -1951,10 +2122,10 @@ window.__ModuleLoader__.load({
1951
2122
  nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId);
1952
2123
  if (!sessionsExpanded && sourceIndex !== -1) {
1953
2124
  const nodes = new Map(group.sessions.map((node) => [node.id, node]));
1954
- if (!collapsedSessionRows(nextOrder.flatMap((id) => {
2125
+ if (!watermarkRows(nextOrder.flatMap((id) => {
1955
2126
  const node = nodes.get(id);
1956
2127
  return node === void 0 ? [] : [node];
1957
- })).rows.some((node) => node.id === activeDrag.sessionId)) return;
2128
+ }), watermark).rows.some((node) => node.id === activeDrag.sessionId)) return;
1958
2129
  }
1959
2130
  const currentBlank = group.sessions.find((node) => node.blank)?.id;
1960
2131
  setSessionOrder(activeDrag.accountKey, pinCurrentBlank(nextOrder, currentBlank));
@@ -1992,8 +2163,10 @@ window.__ModuleLoader__.load({
1992
2163
  const workspaceId = group.workspaceId;
1993
2164
  const children = childrenByParent.get(group.key) ?? [];
1994
2165
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
1995
- const collapsed = collapsedSessionRows(group.sessions);
1996
- const sessionsExpanded = expandedSessionGroups.includes(group.key);
2166
+ const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
2167
+ const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? SESSION_WATERMARK_BASE);
2168
+ const visible = watermarkRows(group.sessions, watermark);
2169
+ const sessionsExpanded = visible.hiddenCount === 0;
1997
2170
  const workspaceMarker = workspaceId !== void 0 && workspaceDrag?.over?.id === workspaceId ? workspaceDrag.over.half : null;
1998
2171
  const workspaceDragProps = workspaceId === void 0 ? void 0 : {
1999
2172
  start: () => {
@@ -2058,7 +2231,7 @@ window.__ModuleLoader__.load({
2058
2231
  home,
2059
2232
  t,
2060
2233
  onToggle: () => {
2061
- if (group.expanded) setExpandedSessionGroups((keys) => keys.filter((key) => key !== group.key));
2234
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
2062
2235
  setGroupExpanded(group.key, !group.expanded);
2063
2236
  },
2064
2237
  onCreate: () => {
@@ -2083,7 +2256,7 @@ window.__ModuleLoader__.load({
2083
2256
  role: "group",
2084
2257
  children: children.map((child) => renderGroup(child, depth + 1))
2085
2258
  }),
2086
- (sessionsExpanded ? group.sessions : collapsed.rows).map((node) => {
2259
+ visible.rows.map((node) => {
2087
2260
  const sameGroupDrag = drag !== null && drag.accountKey === group.key;
2088
2261
  const normalizeHalf = (half) => node.blank ? "after" : half;
2089
2262
  const dragProps = {
@@ -2129,6 +2302,9 @@ window.__ModuleLoader__.load({
2129
2302
  onRename: onSessionRename,
2130
2303
  onFork: forkSession,
2131
2304
  onArchive: onSessionArchive,
2305
+ onCopyId: (sessionId) => {
2306
+ copySessionId(sessionId);
2307
+ },
2132
2308
  onPin: onSessionPin,
2133
2309
  onUnpin: onSessionUnpin,
2134
2310
  onReveal: node.id === revealSessionId && group.key === revealGroup ? () => {
@@ -2144,9 +2320,16 @@ window.__ModuleLoader__.load({
2144
2320
  className: WorkspaceBrowser_module_css_default.sessionOverflowButton,
2145
2321
  "aria-expanded": sessionsExpanded,
2146
2322
  onClick: () => {
2147
- setExpandedSessionGroups((keys) => toggled(keys, group.key));
2323
+ if (sessionsExpanded) {
2324
+ dropWatermarks((key) => key === group.key);
2325
+ return;
2326
+ }
2327
+ setStoredWatermarks((marks) => ({
2328
+ ...marks,
2329
+ [group.key]: visible.hiddenCount <= SESSION_WATERMARK_BASE ? Infinity : watermark + SESSION_WATERMARK_BASE
2330
+ }));
2148
2331
  },
2149
- children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: collapsed.hiddenCount })
2332
+ children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
2150
2333
  })
2151
2334
  ]
2152
2335
  }, group.key);
@@ -2172,7 +2355,7 @@ window.__ModuleLoader__.load({
2172
2355
  });
2173
2356
  }
2174
2357
  /** The flat "In one list" body: every session is one draggable top-level row. */
2175
- function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, renderSlot }) {
2358
+ function FlatList({ list, sessionIds, pinnedSessionIds, useSessionStatus, open, forkSession, copySessionId, onSessionRename, onSessionArchive, onSessionPin, onSessionUnpin, usePanelInfo, setSessionOrder, revealSessionId, onSessionRevealed, t, renderSlot }) {
2176
2359
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
2177
2360
  const statuses = useSessionStatus((s) => s);
2178
2361
  const rows = (0, react.useMemo)(() => deriveFlat(list, sessionIds, pinnedSessionIds, statuses), [
@@ -2223,6 +2406,9 @@ window.__ModuleLoader__.load({
2223
2406
  onRename: onSessionRename,
2224
2407
  onFork: forkSession,
2225
2408
  onArchive: onSessionArchive,
2409
+ onCopyId: (sessionId) => {
2410
+ copySessionId(sessionId);
2411
+ },
2226
2412
  onPin: onSessionPin,
2227
2413
  onUnpin: onSessionUnpin,
2228
2414
  onReveal: node.id === revealSessionId ? () => {
@@ -2333,7 +2519,7 @@ window.__ModuleLoader__.load({
2333
2519
  * @param props - composed slot props (shell owner share + store + injected actions).
2334
2520
  * @returns the region element tree.
2335
2521
  */
2336
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t }) {
2522
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, archiveSession, copySessionId, pinSession, unpinSession, createWorkspace, searchSessions, searchResultLimit, requestSearch, requestAddWorkspace, closeAddWorkspace, closeRenameRequest, setDirectoryBusy, useDirectoryFlow, useHostInfo, useWorkspaceShortcuts, useShortcuts, renderSlot, t }) {
2337
2523
  const home = useHostInfo((info) => info.home);
2338
2524
  const list = useSessions((state) => state);
2339
2525
  const workspaces = useWorkspaces((state) => state.items);
@@ -2735,7 +2921,7 @@ window.__ModuleLoader__.load({
2735
2921
  setSearchExpanded(true);
2736
2922
  requestSearch();
2737
2923
  },
2738
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconSearchOutline16, { size: searchExpanded ? 11 : 14 })
2924
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconSearchOutlineRegular, { size: searchExpanded ? 11 : 14 })
2739
2925
  })
2740
2926
  }),
2741
2927
  (0, react_jsx_runtime.jsx)("input", {
@@ -2764,7 +2950,7 @@ window.__ModuleLoader__.load({
2764
2950
  setQuery("");
2765
2951
  setSearchExpanded(false);
2766
2952
  },
2767
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCloseFill14, {})
2953
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCloseFillRegular, {})
2768
2954
  })
2769
2955
  ]
2770
2956
  })
@@ -2795,7 +2981,7 @@ window.__ModuleLoader__.load({
2795
2981
  onClick: () => {
2796
2982
  requestAddWorkspace();
2797
2983
  },
2798
- children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconProjectAddOutline16, { size: 16 })
2984
+ children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconProjectAddOutlineRegular, { size: 16 })
2799
2985
  })
2800
2986
  })]
2801
2987
  }),
@@ -2839,6 +3025,7 @@ window.__ModuleLoader__.load({
2839
3025
  useSessionStatus,
2840
3026
  open,
2841
3027
  forkSession,
3028
+ copySessionId,
2842
3029
  onSessionRename,
2843
3030
  onSessionArchive,
2844
3031
  onSessionPin,
@@ -2853,6 +3040,7 @@ window.__ModuleLoader__.load({
2853
3040
  usePanelInfo,
2854
3041
  list,
2855
3042
  useSessionStatus,
3043
+ copySessionId,
2856
3044
  renderSlot,
2857
3045
  onSessionRename,
2858
3046
  onSessionArchive,
@@ -3026,6 +3214,91 @@ window.__ModuleLoader__.load({
3026
3214
  });
3027
3215
  }
3028
3216
  //#endregion
3217
+ //#region lib/types/client/toast/RowActionToast.js
3218
+ /**
3219
+ * Dispatch the current notice without interpreting its business payload.
3220
+ * @param props - Current notice, dismissal, and the declared keyed renderer.
3221
+ * @returns The selected notice presentation, or nothing.
3222
+ */
3223
+ function RowActionToast({ useToast, dismissToast, renderSlot }) {
3224
+ const notice = useToast((current) => current);
3225
+ if (notice === null) return null;
3226
+ return renderSlot("workspace.toast", {
3227
+ ...notice,
3228
+ dismissToast
3229
+ }, { entryKey: notice.kind });
3230
+ }
3231
+ //#endregion
3232
+ //#region lib/types/client/toast/Toast.js
3233
+ /** One presentation Factory and the adapters for keyed Workspace notices. */
3234
+ /**
3235
+ * Render one notice using the shared Factory.
3236
+ * @param props - Keyed payload, injected display mapping, locale, and Factory renderer.
3237
+ * @returns The shared Toast occurrence.
3238
+ */
3239
+ function ToastEntry(props) {
3240
+ const { dismissToast, display, t, renderFactorySlot, seq } = props;
3241
+ return renderFactorySlot("workspace.toast.content", {
3242
+ ...display(props, t, dismissToast),
3243
+ seq,
3244
+ onDone: dismissToast
3245
+ });
3246
+ }
3247
+ /**
3248
+ * Present plain display data with the shared primitive's timing and portal.
3249
+ * @param props - Localized display fields, display identity, and dismissal.
3250
+ * @returns One Toast, remounted only for a new display identity.
3251
+ */
3252
+ function WorkspaceToast({ seq, text, tone, onDone }) {
3253
+ return (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.Toast, {
3254
+ text,
3255
+ onDone,
3256
+ ...tone === "success" ? { tone: "success" } : {},
3257
+ ...tone === "warning" ? { icon: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconWarningOutlineRegular, {}) } : {}
3258
+ }, seq);
3259
+ }
3260
+ //#endregion
3261
+ //#region lib/types/client/toast/register.js
3262
+ /**
3263
+ * Register notice presentations with their business-owned display fields.
3264
+ * @param ctx - Workspace plugin context.
3265
+ */
3266
+ function registerToasts(ctx) {
3267
+ ctx.slots.registerFactory({
3268
+ name: "workspace.toast.content",
3269
+ scope: "root"
3270
+ }, WorkspaceToast);
3271
+ ctx.slots.inject("workspace.toast", function* () {
3272
+ yield ctx.slots.register({
3273
+ name: "workspace.toast",
3274
+ key: "sessionIdCopied",
3275
+ locale: "workspace",
3276
+ inject: () => ({ display: (_notice, t) => ({
3277
+ text: t("toast.sessionIdCopied"),
3278
+ tone: "success"
3279
+ }) })
3280
+ }, ToastEntry);
3281
+ yield ctx.slots.register({
3282
+ name: "workspace.toast",
3283
+ key: "createFailed",
3284
+ locale: "workspace",
3285
+ inject: () => ({ display: (notice) => ({
3286
+ text: notice.message,
3287
+ tone: "warning"
3288
+ }) })
3289
+ }, ToastEntry);
3290
+ yield ctx.slots.register({
3291
+ name: "workspace.toast",
3292
+ key: "sessionIdCopyFailed",
3293
+ locale: "workspace",
3294
+ inject: () => ({ display: (_notice, t) => ({
3295
+ text: t("toast.sessionIdCopyFailed"),
3296
+ tone: "warning"
3297
+ }) })
3298
+ }, ToastEntry);
3299
+ });
3300
+ }
3301
+ //#endregion
3029
3302
  //#region lib/types/client/locales.js
3030
3303
  /**
3031
3304
  * `workspace` namespace dictionaries: the browsing region (section header,
@@ -3034,6 +3307,8 @@ window.__ModuleLoader__.load({
3034
3307
  */
3035
3308
  /** Simplified Chinese dictionary (the key-set source of truth). */
3036
3309
  const zh = {
3310
+ "draft.workspaceRequired": "请先选择工作区",
3311
+ "draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
3037
3312
  "group.ungrouped": "未分组",
3038
3313
  "session.new": "新会话",
3039
3314
  "session.untitled": "未命名",
@@ -3081,12 +3356,16 @@ window.__ModuleLoader__.load({
3081
3356
  "menu.unpinSession": "取消置顶",
3082
3357
  "menu.fork": "分叉会话",
3083
3358
  "menu.archiveSession": "归档会话",
3359
+ "menu.sessionId": "复制 Session ID",
3360
+ "toast.sessionIdCopied": "Session ID 已复制",
3361
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
3084
3362
  "row.pinned": "已置顶",
3085
3363
  "sessions.count.one": "{n} 个会话",
3086
3364
  "sessions.count.other": "{n} 个会话",
3087
3365
  "actions.workspace.aria": "工作区“{name}”的操作",
3088
3366
  "actions.session.aria": "会话“{name}”的操作",
3089
3367
  "actions.newSession.aria": "在“{name}”中新建会话",
3368
+ "actions.archive": "归档会话",
3090
3369
  "actions.pin": "置顶会话",
3091
3370
  "actions.unpin": "取消置顶",
3092
3371
  "status.running": "进行中",
@@ -3110,6 +3389,8 @@ window.__ModuleLoader__.load({
3110
3389
  };
3111
3390
  /** English dictionary, checked complete against the zh key set. */
3112
3391
  const en = {
3392
+ "draft.workspaceRequired": "Choose a workspace first.",
3393
+ "draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
3113
3394
  "group.ungrouped": "Ungrouped",
3114
3395
  "session.new": "New Session",
3115
3396
  "session.untitled": "Untitled",
@@ -3157,12 +3438,16 @@ window.__ModuleLoader__.load({
3157
3438
  "menu.unpinSession": "Unpin session",
3158
3439
  "menu.fork": "Fork session",
3159
3440
  "menu.archiveSession": "Archive session",
3441
+ "menu.sessionId": "Copy Session ID",
3442
+ "toast.sessionIdCopied": "Session ID copied",
3443
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
3160
3444
  "row.pinned": "Pinned",
3161
3445
  "sessions.count.one": "{n} session",
3162
3446
  "sessions.count.other": "{n} sessions",
3163
3447
  "actions.workspace.aria": "Workspace actions for {name}",
3164
3448
  "actions.session.aria": "Session actions for {name}",
3165
3449
  "actions.newSession.aria": "New session in {name}",
3450
+ "actions.archive": "Archive",
3166
3451
  "actions.pin": "Pin",
3167
3452
  "actions.unpin": "Unpin",
3168
3453
  "status.running": "Running",
@@ -3221,7 +3506,15 @@ window.__ModuleLoader__.load({
3221
3506
  ...viewHandle,
3222
3507
  create: () => viewInstance
3223
3508
  };
3224
- const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions);
3509
+ const rowToast = (0, _qilin_agent_client_store.createSnapshotStore)(null);
3510
+ let toastSeq = 0;
3511
+ const notify = (toast) => {
3512
+ rowToast.set({
3513
+ ...toast,
3514
+ seq: ++toastSeq
3515
+ });
3516
+ };
3517
+ const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
3225
3518
  ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
3226
3519
  ctx.effect(() => ctx.locale.register(NS, {
3227
3520
  zh,
@@ -3280,6 +3573,9 @@ window.__ModuleLoader__.load({
3280
3573
  closeAddWorkspace: shortcutControls.closeAdd,
3281
3574
  closeRenameRequest: shortcutControls.closeRename,
3282
3575
  setDirectoryBusy: shortcutControls.directoryBusy,
3576
+ copySessionId: async (sessionId) => {
3577
+ notify({ kind: await (0, _qilin_agent_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
3578
+ },
3283
3579
  hooks: {
3284
3580
  directoryFlow: browserFlowSource,
3285
3581
  hostInfo,
@@ -3290,6 +3586,13 @@ window.__ModuleLoader__.load({
3290
3586
  installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, (sessionId) => {
3291
3587
  uiWorkspace.archiveSession(sessionId);
3292
3588
  });
3589
+ registerToasts(ctx);
3590
+ const rowToastInjected = () => ({
3591
+ hooks: { toast: rowToast },
3592
+ dismissToast: () => {
3593
+ rowToast.set(null);
3594
+ }
3595
+ });
3293
3596
  const pickerInjected = () => ({
3294
3597
  createWorkspace: (input) => workspaces.create(input),
3295
3598
  hooks: { directoryFlow: pickerFlowSource }
@@ -3314,6 +3617,15 @@ window.__ModuleLoader__.load({
3314
3617
  inject: browserInjected,
3315
3618
  locale: NS
3316
3619
  }, WorkspaceBrowser));
3620
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
3621
+ name: "shell.overlay",
3622
+ id: "workspace.row-toast",
3623
+ inject: rowToastInjected,
3624
+ children: { "workspace.toast": {
3625
+ kind: "keyed",
3626
+ scope: "root"
3627
+ } }
3628
+ }, RowActionToast));
3317
3629
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
3318
3630
  name: "conversation.hero.workspace",
3319
3631
  children: { "conversation.hero.workspace.directoryFlow": {