dsh-group-status 0.3.0 → 0.4.0

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.en.md +6 -6
  3. package/README.md +6 -6
  4. package/lib/client.js +1122 -183
  5. package/lib/client.js.map +1 -1
  6. package/lib/types/client/contract/slots.d.ts +39 -21
  7. package/lib/types/client/contract/slots.d.ts.map +1 -1
  8. package/lib/types/client/contract/slots.js.map +1 -1
  9. package/lib/types/client/contract/toast.d.ts +67 -0
  10. package/lib/types/client/contract/toast.d.ts.map +1 -0
  11. package/lib/types/client/contract/toast.js +2 -0
  12. package/lib/types/client/contract/toast.js.map +1 -0
  13. package/lib/types/client/index.d.ts +6 -3
  14. package/lib/types/client/index.d.ts.map +1 -1
  15. package/lib/types/client/index.js +48 -13
  16. package/lib/types/client/index.js.map +1 -1
  17. package/lib/types/client/locales.d.ts +14 -0
  18. package/lib/types/client/locales.d.ts.map +1 -1
  19. package/lib/types/client/locales.js +14 -0
  20. package/lib/types/client/locales.js.map +1 -1
  21. package/lib/types/client/navigation.d.ts +45 -12
  22. package/lib/types/client/navigation.d.ts.map +1 -1
  23. package/lib/types/client/navigation.js +102 -26
  24. package/lib/types/client/navigation.js.map +1 -1
  25. package/lib/types/client/rows/Rows.d.ts.map +1 -1
  26. package/lib/types/client/rows/Rows.js +4 -4
  27. package/lib/types/client/rows/Rows.js.map +1 -1
  28. package/lib/types/client/rows/WorkspaceBrowser.d.ts +1 -1
  29. package/lib/types/client/rows/WorkspaceBrowser.d.ts.map +1 -1
  30. package/lib/types/client/rows/WorkspaceBrowser.js +54 -50
  31. package/lib/types/client/rows/WorkspaceBrowser.js.map +1 -1
  32. package/lib/types/client/session-actions/ArchiveSession.js +1 -1
  33. package/lib/types/client/session-actions/ArchiveSession.js.map +1 -1
  34. package/lib/types/client/session-actions/ForkSession.d.ts +5 -4
  35. package/lib/types/client/session-actions/ForkSession.d.ts.map +1 -1
  36. package/lib/types/client/session-actions/ForkSession.js +7 -4
  37. package/lib/types/client/session-actions/ForkSession.js.map +1 -1
  38. package/lib/types/client/session-actions/PinSession.js +1 -1
  39. package/lib/types/client/session-actions/PinSession.js.map +1 -1
  40. package/lib/types/client/session-actions/RowActionToast.d.ts +5 -8
  41. package/lib/types/client/session-actions/RowActionToast.d.ts.map +1 -1
  42. package/lib/types/client/session-actions/RowActionToast.js +10 -49
  43. package/lib/types/client/session-actions/RowActionToast.js.map +1 -1
  44. package/lib/types/client/session-actions/SessionId.d.ts +8 -0
  45. package/lib/types/client/session-actions/SessionId.d.ts.map +1 -0
  46. package/lib/types/client/session-actions/SessionId.js +16 -0
  47. package/lib/types/client/session-actions/SessionId.js.map +1 -0
  48. package/lib/types/client/session-watermarks.d.ts +30 -0
  49. package/lib/types/client/session-watermarks.d.ts.map +1 -0
  50. package/lib/types/client/session-watermarks.js +54 -0
  51. package/lib/types/client/session-watermarks.js.map +1 -0
  52. package/lib/types/client/shortcuts.d.ts +2 -7
  53. package/lib/types/client/shortcuts.d.ts.map +1 -1
  54. package/lib/types/client/shortcuts.js +7 -11
  55. package/lib/types/client/shortcuts.js.map +1 -1
  56. package/lib/types/client/toast/Toast.d.ts +41 -0
  57. package/lib/types/client/toast/Toast.d.ts.map +1 -0
  58. package/lib/types/client/toast/Toast.js +44 -0
  59. package/lib/types/client/toast/Toast.js.map +1 -0
  60. package/lib/types/client/toast/register.d.ts +11 -0
  61. package/lib/types/client/toast/register.d.ts.map +1 -0
  62. package/lib/types/client/toast/register.js +162 -0
  63. package/lib/types/client/toast/register.js.map +1 -0
  64. package/package.json +33 -30
  65. package/lib/types/client/legacy/bridge.d.ts +0 -30
  66. package/lib/types/client/legacy/bridge.d.ts.map +0 -1
  67. package/lib/types/client/legacy/bridge.js +0 -58
  68. package/lib/types/client/legacy/bridge.js.map +0 -1
  69. package/lib/types/client/legacy/register-legacy-facets.d.ts +0 -14
  70. package/lib/types/client/legacy/register-legacy-facets.d.ts.map +0 -1
  71. package/lib/types/client/legacy/register-legacy-facets.js +0 -77
  72. package/lib/types/client/legacy/register-legacy-facets.js.map +0 -1
  73. package/lib/types/client/legacy/schedule.d.ts +0 -49
  74. package/lib/types/client/legacy/schedule.d.ts.map +0 -1
  75. package/lib/types/client/legacy/schedule.js +0 -73
  76. package/lib/types/client/legacy/schedule.js.map +0 -1
  77. package/lib/types/client/legacy/store-source.d.ts +0 -78
  78. package/lib/types/client/legacy/store-source.d.ts.map +0 -1
  79. package/lib/types/client/legacy/store-source.js +0 -82
  80. package/lib/types/client/legacy/store-source.js.map +0 -1
  81. package/lib/types/client/legacy/waiting-review.d.ts +0 -85
  82. package/lib/types/client/legacy/waiting-review.d.ts.map +0 -1
  83. package/lib/types/client/legacy/waiting-review.js +0 -179
  84. package/lib/types/client/legacy/waiting-review.js.map +0 -1
package/lib/client.js CHANGED
@@ -5,8 +5,8 @@ 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 react = require("react");
9
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react = require("react");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
12
12
  //#region src/client/contract/slots.ts
@@ -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;
@@ -183,8 +166,8 @@ window.__ModuleLoader__.load({
183
166
  status: "handled",
184
167
  run: () => {
185
168
  navigation.forkSession(target.id).catch((error) => {
186
- const unavailable = error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/fork-unavailable";
187
- controls.forkFailed(unavailable ? "unavailable" : "failed");
169
+ const unavailable = (error instanceof Error && error.name === "SessionForkError" ? error : void 0)?.rpcError.code === "session/fork-unavailable";
170
+ reportForkFailure(unavailable ? "unavailable" : "failed");
188
171
  if (!unavailable) console.warn("session fork rejected:", error);
189
172
  });
190
173
  }
@@ -902,7 +885,838 @@ window.__ModuleLoader__.load({
902
885
  });
903
886
  }
904
887
  //#endregion
905
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
888
+ //#region src/client/toast/Toast.tsx
889
+ /** Shared Workspace notice presentation and keyed business adapters. */
890
+ /**
891
+ * Render one notice using the shared Factory.
892
+ * @param props - keyed payload, injected display mapping, locale, and Factory renderer.
893
+ * @returns the shared Toast occurrence.
894
+ */
895
+ function ToastEntry(props) {
896
+ const { dismissToast, display, t, renderFactorySlot, seq } = props;
897
+ return renderFactorySlot("workspace.toast.content", {
898
+ ...display({ ...props }, t, dismissToast),
899
+ seq,
900
+ onDone: dismissToast
901
+ });
902
+ }
903
+ /**
904
+ * Keep the archive filter action reactive without subscribing unrelated notices.
905
+ * @param props - archive notice, display mapping, and the existing viewing store.
906
+ * @returns the shared Toast occurrence with its current actions.
907
+ */
908
+ function ArchiveToastEntry(props) {
909
+ const { dismissToast, display, filterAction, useStore, t, renderFactorySlot, seq } = props;
910
+ const archivedVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
911
+ const fields = display({ ...props }, t, dismissToast);
912
+ return renderFactorySlot("workspace.toast.content", {
913
+ ...fields,
914
+ actions: [...fields.actions ?? [], ...archivedVisible ? [] : [filterAction(t, dismissToast)]],
915
+ seq,
916
+ onDone: dismissToast
917
+ });
918
+ }
919
+ /**
920
+ * Present plain display data with the shared primitive's timing and portal.
921
+ * @param props - localized display fields, display identity, and dismissal.
922
+ * @returns one Toast, remounted only for a new display identity.
923
+ */
924
+ function WorkspaceToast({ seq, text, tone, holdMs, actions, onDone }) {
925
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
926
+ text,
927
+ onDone,
928
+ ...tone === "success" ? { tone: "success" } : {},
929
+ ...tone === "warning" ? { icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}) } : {},
930
+ ...holdMs === void 0 ? {} : { holdMs },
931
+ ...actions === void 0 ? {} : { actions }
932
+ }, seq);
933
+ }
934
+ //#endregion
935
+ //#region src/client/toast/register.tsx
936
+ /** Wrap a business display mapping without making the slot component generic. */
937
+ function plainToast(display) {
938
+ return () => ({ display });
939
+ }
940
+ /**
941
+ * Register notice presentations with their business-owned display fields and actions.
942
+ * @param ctx - workspace plugin context.
943
+ * @param viewStore - viewing store, read only by archive notices.
944
+ * @param actions - archive and source-opening callbacks.
945
+ */
946
+ function registerToasts(ctx, viewStore, actions) {
947
+ const toastEntry = ToastEntry;
948
+ const archiveToastEntry = ArchiveToastEntry;
949
+ const archiveFilterAction = (t, dismiss) => ({
950
+ prefix: t("toast.archivedOr"),
951
+ label: t("toast.archivedFilter"),
952
+ onClick: () => {
953
+ dismiss();
954
+ actions.showArchived();
955
+ }
956
+ });
957
+ ctx.slots.registerFactory({
958
+ name: "workspace.toast.content",
959
+ scope: "root"
960
+ }, WorkspaceToast);
961
+ ctx.slots.inject("workspace.toast", function* () {
962
+ yield ctx.slots.register({
963
+ name: "workspace.toast",
964
+ key: "sessionIdCopied",
965
+ locale: "workspace",
966
+ inject: plainToast((_notice, t) => ({
967
+ text: t("toast.sessionIdCopied"),
968
+ tone: "success"
969
+ }))
970
+ }, toastEntry);
971
+ yield ctx.slots.register({
972
+ name: "workspace.toast",
973
+ key: "sessionIdCopyFailed",
974
+ locale: "workspace",
975
+ inject: plainToast((_notice, t) => ({
976
+ text: t("toast.sessionIdCopyFailed"),
977
+ tone: "warning"
978
+ }))
979
+ }, toastEntry);
980
+ yield ctx.slots.register({
981
+ name: "workspace.toast",
982
+ key: "pinFailed",
983
+ locale: "workspace",
984
+ inject: plainToast((_notice, t) => ({
985
+ text: t("toast.pinFailed"),
986
+ tone: "warning"
987
+ }))
988
+ }, toastEntry);
989
+ yield ctx.slots.register({
990
+ name: "workspace.toast",
991
+ key: "unpinFailed",
992
+ locale: "workspace",
993
+ inject: plainToast((_notice, t) => ({
994
+ text: t("toast.unpinFailed"),
995
+ tone: "warning"
996
+ }))
997
+ }, toastEntry);
998
+ yield ctx.slots.register({
999
+ name: "workspace.toast",
1000
+ key: "archivedNotOpenable",
1001
+ locale: "workspace",
1002
+ inject: plainToast((_notice, t) => ({
1003
+ text: t("toast.archivedNotOpenable"),
1004
+ tone: "warning"
1005
+ }))
1006
+ }, toastEntry);
1007
+ yield ctx.slots.register({
1008
+ name: "workspace.toast",
1009
+ key: "defaultWorkspaceFailed",
1010
+ locale: "workspace",
1011
+ inject: plainToast((_notice, t) => ({
1012
+ text: t("defaultWorkspace.failed"),
1013
+ tone: "warning"
1014
+ }))
1015
+ }, toastEntry);
1016
+ yield ctx.slots.register({
1017
+ name: "workspace.toast",
1018
+ key: "shortcutForkFailed",
1019
+ locale: "workspace",
1020
+ inject: plainToast((notice, t) => ({
1021
+ text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
1022
+ tone: "plain"
1023
+ }))
1024
+ }, toastEntry);
1025
+ yield ctx.slots.register({
1026
+ name: "workspace.toast",
1027
+ key: "createFailed",
1028
+ locale: "workspace",
1029
+ inject: plainToast((notice, t) => ({
1030
+ text: t("toast.createFailed", { message: notice.message }),
1031
+ tone: "warning",
1032
+ holdMs: 6e3
1033
+ }))
1034
+ }, toastEntry);
1035
+ yield ctx.slots.register({
1036
+ name: "workspace.toast",
1037
+ key: "forkRequiresOpen",
1038
+ locale: "workspace",
1039
+ inject: plainToast((notice, t, dismiss) => ({
1040
+ text: t("toast.forkRequiresOpen"),
1041
+ tone: "warning",
1042
+ holdMs: 6e3,
1043
+ actions: [{
1044
+ label: t("toast.openForkSource"),
1045
+ onClick: () => {
1046
+ dismiss();
1047
+ actions.openForkSource(notice.sessionId);
1048
+ }
1049
+ }]
1050
+ }))
1051
+ }, toastEntry);
1052
+ yield ctx.slots.register({
1053
+ name: "workspace.toast",
1054
+ key: "archived",
1055
+ locale: "workspace",
1056
+ store: viewStore,
1057
+ inject: () => ({
1058
+ display: (notice, t, dismiss) => ({
1059
+ text: t("toast.archived"),
1060
+ tone: "success",
1061
+ holdMs: 6e3,
1062
+ actions: [{
1063
+ label: t("toast.archivedUndo"),
1064
+ onClick: () => {
1065
+ dismiss();
1066
+ actions.undoArchive(notice.sessionId);
1067
+ }
1068
+ }]
1069
+ }),
1070
+ filterAction: archiveFilterAction
1071
+ })
1072
+ }, archiveToastEntry);
1073
+ yield ctx.slots.register({
1074
+ name: "workspace.toast",
1075
+ key: "stoppedAndArchived",
1076
+ locale: "workspace",
1077
+ store: viewStore,
1078
+ inject: () => ({
1079
+ display: (notice, t, dismiss) => ({
1080
+ text: t("toast.stoppedAndArchived"),
1081
+ tone: "success",
1082
+ holdMs: 6e3,
1083
+ actions: [{
1084
+ label: t("toast.archivedUndo"),
1085
+ onClick: () => {
1086
+ dismiss();
1087
+ actions.undoArchive(notice.sessionId);
1088
+ }
1089
+ }]
1090
+ }),
1091
+ filterAction: archiveFilterAction
1092
+ })
1093
+ }, archiveToastEntry);
1094
+ });
1095
+ }
1096
+ //#endregion
1097
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-values@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-values/lib/index.js
1098
+ /**
1099
+ * Lazily scanned view of one JSON object's top-level fields, built from text
1100
+ * that may still be streaming or from an already parsed object. Nothing is
1101
+ * scanned until a reader asks; the view remembers every question it answered
1102
+ * and reports changed answers when the owner refreshes for publication.
1103
+ * Used for model tool-call arguments: a row reads the fields it
1104
+ * cares about at whatever granularity it displays, at every stage of the call.
1105
+ * @module @deepseek-ai/dsh-util-values/src/partial-json
1106
+ */
1107
+ const SIMPLE_ESCAPES = {
1108
+ "\"": "\"",
1109
+ "\\": "\\",
1110
+ "/": "/",
1111
+ b: "\b",
1112
+ f: "\f",
1113
+ n: "\n",
1114
+ r: "\r",
1115
+ t: " "
1116
+ };
1117
+ const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
1118
+ function isWhitespace(c) {
1119
+ return c === " " || c === "\n" || c === "\r" || c === " ";
1120
+ }
1121
+ function isHex(c) {
1122
+ return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
1123
+ }
1124
+ (class PartialArguments {
1125
+ /** The view of a call with no arguments available. */
1126
+ static EMPTY = PartialArguments.fromObject({});
1127
+ /**
1128
+ * View finished argument text without scanning it until a reader asks.
1129
+ * @param text - the complete argument JSON text.
1130
+ * @returns a sealed view.
1131
+ */
1132
+ static fromText(text) {
1133
+ const view = new PartialArguments();
1134
+ view.append(text);
1135
+ view.sealed = true;
1136
+ return view;
1137
+ }
1138
+ /**
1139
+ * View an already parsed argument payload, such as a PTC dispatch object.
1140
+ * @param value - the parsed argument value.
1141
+ * @returns a sealed view; a non-object payload has no fields.
1142
+ */
1143
+ static fromObject(value) {
1144
+ const view = new PartialArguments();
1145
+ view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
1146
+ view.sealed = true;
1147
+ return view;
1148
+ }
1149
+ /**
1150
+ * The source: text so far or a parsed object, plus whether it can still grow.
1151
+ * These are the only enumerable fields, so two views over the same source
1152
+ * compare equal structurally however far each has been read.
1153
+ */
1154
+ chunks = [];
1155
+ object;
1156
+ sealed = false;
1157
+ #ends = [];
1158
+ #size = 0;
1159
+ #consumed = 0;
1160
+ #mode = "root";
1161
+ #escape = false;
1162
+ #keyStart = 0;
1163
+ #keyEscaped = false;
1164
+ #key = "";
1165
+ #current = null;
1166
+ #nestedEnds = [];
1167
+ #nestedInString = false;
1168
+ #invalidAt;
1169
+ #invalidValue = false;
1170
+ #entries = /* @__PURE__ */ new Map();
1171
+ #order = [];
1172
+ #reads = /* @__PURE__ */ new Map();
1173
+ /** Whether this view rejects further appends; does not scan text or register reads. */
1174
+ get isSealed() {
1175
+ return this.sealed;
1176
+ }
1177
+ /** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
1178
+ get invalid() {
1179
+ this.scan();
1180
+ return this.#mode === "invalid" || this.#invalidValue;
1181
+ }
1182
+ /**
1183
+ * Retain streamed argument text without scanning or comparing observed answers.
1184
+ * @param fragment - the text following every fragment appended before.
1185
+ */
1186
+ append(fragment) {
1187
+ if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
1188
+ if (fragment.length === 0) return;
1189
+ this.chunks.push(fragment);
1190
+ this.#size += fragment.length;
1191
+ this.#ends.push(this.#size);
1192
+ }
1193
+ /**
1194
+ * Reconcile a streamed prefix with authoritative complete text without joining the fragments.
1195
+ * @param text - the final argument text, which replaces missing or conflicting deltas.
1196
+ * @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
1197
+ */
1198
+ settle(text) {
1199
+ if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
1200
+ let offset = 0;
1201
+ for (const chunk of this.chunks) {
1202
+ if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
1203
+ offset += chunk.length;
1204
+ }
1205
+ this.chunks = text.length === 0 ? [] : [text];
1206
+ this.#ends = text.length === 0 ? [] : [text.length];
1207
+ this.sealed = true;
1208
+ return this;
1209
+ }
1210
+ /**
1211
+ * Compare observed answers and advance their publication baseline. Unread views remain unscanned.
1212
+ * @returns whether any observed answer changed since its first read or the preceding refresh.
1213
+ */
1214
+ refresh() {
1215
+ if (this.#reads.size === 0) return false;
1216
+ this.scan();
1217
+ let changed = false;
1218
+ let completions = false;
1219
+ for (const read of this.#reads.values()) {
1220
+ if (read.completion) {
1221
+ completions = true;
1222
+ continue;
1223
+ }
1224
+ changed = this.refreshRead(read) || changed;
1225
+ }
1226
+ if (completions) {
1227
+ for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
1228
+ }
1229
+ if (this.sealed) this.#reads.clear();
1230
+ return changed;
1231
+ }
1232
+ refreshRead(read) {
1233
+ const now = read.answer();
1234
+ if (Object.is(now, read.last)) return false;
1235
+ read.last = now;
1236
+ return true;
1237
+ }
1238
+ /**
1239
+ * Check whether no further fields can arrive.
1240
+ * @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
1241
+ */
1242
+ closed() {
1243
+ return this.remember("closed", "", () => this.closedNow());
1244
+ }
1245
+ /**
1246
+ * List discovered fields in first-appearance order.
1247
+ * @returns top-level keys seen so far, in first-appearance order.
1248
+ */
1249
+ keys() {
1250
+ return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
1251
+ }
1252
+ /**
1253
+ * Check whether a top-level field has appeared.
1254
+ * @param key - argument name.
1255
+ * @returns whether the field has appeared (a string opened or another value began).
1256
+ */
1257
+ has(key) {
1258
+ return this.remember("has", key, () => this.hasNow(key));
1259
+ }
1260
+ /**
1261
+ * Check whether a field's closing delimiter has arrived, without validating its contents.
1262
+ * @param key - argument name.
1263
+ * @returns whether its delimiter arrived and no content reader has reported an error for this value.
1264
+ */
1265
+ complete(key) {
1266
+ return this.remember("complete", key, () => this.completeNow(key));
1267
+ }
1268
+ /**
1269
+ * Read string length without materializing its text.
1270
+ * @param key - argument name.
1271
+ * @param options - change granularity for a streaming string.
1272
+ * @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
1273
+ */
1274
+ stringLength(key, options) {
1275
+ const step = Math.max(1, Math.floor(options?.step ?? 1));
1276
+ const offset = options?.offset ?? 0;
1277
+ return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
1278
+ }
1279
+ /**
1280
+ * Check a string against a decoded UTF-16 length limit without materializing it.
1281
+ * @param key - argument name.
1282
+ * @param maxLength - decoded UTF-16 limit, floored to at least zero.
1283
+ * @returns whether the string is longer than the limit; false when absent or not a string.
1284
+ */
1285
+ stringExceeds(key, maxLength) {
1286
+ const limit = Math.max(0, Math.floor(maxLength));
1287
+ return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
1288
+ }
1289
+ /**
1290
+ * Read a decoded string, including a streaming prefix.
1291
+ * @param key - argument name.
1292
+ * @returns the string field's decoded text so far; undefined when absent or not a string.
1293
+ */
1294
+ text(key) {
1295
+ return this.remember("text", key, () => this.textNow(key));
1296
+ }
1297
+ /**
1298
+ * Read at most the first decoded UTF-16 units of a string.
1299
+ * @param key - argument name.
1300
+ * @param maxLength - maximum decoded UTF-16 length, floored to at least one.
1301
+ * @returns the bounded string prefix; undefined when absent or not a string.
1302
+ */
1303
+ textPrefix(key, maxLength) {
1304
+ const limit = Math.max(1, Math.floor(maxLength));
1305
+ return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
1306
+ }
1307
+ /**
1308
+ * Read a completed non-string argument.
1309
+ * @param key - argument name.
1310
+ * @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
1311
+ */
1312
+ value(key) {
1313
+ return this.remember("value", key, () => this.valueNow(key));
1314
+ }
1315
+ /** Answer a question and, on a streaming view, remember it for change detection. */
1316
+ remember(kind, key, read, comparison) {
1317
+ this.scan();
1318
+ const result = read();
1319
+ if (!this.sealed) {
1320
+ const id = `${kind}/${key}`;
1321
+ if (!this.#reads.has(id)) this.#reads.set(id, {
1322
+ completion: kind === "complete",
1323
+ answer: comparison === void 0 ? read : () => comparison(read()),
1324
+ last: comparison === void 0 ? result : comparison(result)
1325
+ });
1326
+ }
1327
+ return result;
1328
+ }
1329
+ closedNow() {
1330
+ return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
1331
+ }
1332
+ keysNow() {
1333
+ return this.object === void 0 ? this.#order : Object.keys(this.object);
1334
+ }
1335
+ hasNow(key) {
1336
+ return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
1337
+ }
1338
+ completeNow(key) {
1339
+ if (this.object !== void 0) return Object.hasOwn(this.object, key);
1340
+ const entry = this.#entries.get(key);
1341
+ return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
1342
+ }
1343
+ lengthNow(key, limit = Number.POSITIVE_INFINITY) {
1344
+ if (this.object !== void 0) {
1345
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1346
+ return typeof field === "string" ? field.length : void 0;
1347
+ }
1348
+ const entry = this.#entries.get(key);
1349
+ if (entry?.kind !== "string") return void 0;
1350
+ if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
1351
+ const read = entry.length ??= {
1352
+ at: entry.start,
1353
+ length: 0,
1354
+ text: ""
1355
+ };
1356
+ this.readString(entry, read, limit, false);
1357
+ return read.length;
1358
+ }
1359
+ textNow(key) {
1360
+ if (this.object !== void 0) {
1361
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1362
+ return typeof field === "string" ? field : void 0;
1363
+ }
1364
+ const entry = this.#entries.get(key);
1365
+ if (entry?.kind !== "string") return void 0;
1366
+ if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
1367
+ let text;
1368
+ try {
1369
+ text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
1370
+ } catch (_error) {}
1371
+ if (text !== void 0) entry.text = {
1372
+ at: entry.end,
1373
+ length: text.length,
1374
+ text
1375
+ };
1376
+ }
1377
+ const read = entry.text ??= {
1378
+ at: entry.start,
1379
+ length: 0,
1380
+ text: ""
1381
+ };
1382
+ this.readString(entry, read, Number.POSITIVE_INFINITY, true);
1383
+ return read.text;
1384
+ }
1385
+ textPrefixNow(key, maxLength) {
1386
+ if (this.object !== void 0) {
1387
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
1388
+ return typeof field === "string" ? field.slice(0, maxLength) : void 0;
1389
+ }
1390
+ const entry = this.#entries.get(key);
1391
+ if (entry?.kind !== "string") return void 0;
1392
+ const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
1393
+ let read = prefixes.get(maxLength);
1394
+ if (read === void 0) {
1395
+ read = {
1396
+ at: entry.start,
1397
+ length: 0,
1398
+ text: ""
1399
+ };
1400
+ prefixes.set(maxLength, read);
1401
+ }
1402
+ this.readString(entry, read, maxLength, true);
1403
+ return read.text;
1404
+ }
1405
+ valueNow(key) {
1406
+ if (this.object !== void 0) {
1407
+ if (!Object.hasOwn(this.object, key)) return void 0;
1408
+ const field = this.object[key];
1409
+ return typeof field === "string" ? void 0 : field;
1410
+ }
1411
+ const entry = this.#entries.get(key);
1412
+ if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
1413
+ if (entry.parsed === void 0) try {
1414
+ entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
1415
+ } catch (_error) {
1416
+ entry.invalid = true;
1417
+ this.#invalidValue = true;
1418
+ }
1419
+ return entry.parsed;
1420
+ }
1421
+ chunkAt(at) {
1422
+ let low = 0;
1423
+ let high = this.#ends.length;
1424
+ while (low < high) {
1425
+ const mid = low + high >>> 1;
1426
+ if (this.#ends[mid] <= at) low = mid + 1;
1427
+ else high = mid;
1428
+ }
1429
+ return low;
1430
+ }
1431
+ /** Materialize only a requested range, never the cumulative source. */
1432
+ slice(start, end) {
1433
+ if (start >= end) return "";
1434
+ const first = this.chunkAt(start);
1435
+ const last = this.chunkAt(end - 1);
1436
+ const base = first === 0 ? 0 : this.#ends[first - 1];
1437
+ if (first === last) return this.chunks[first].slice(start - base, end - base);
1438
+ const parts = [this.chunks[first].slice(start - base)];
1439
+ for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
1440
+ parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
1441
+ return parts.join("");
1442
+ }
1443
+ readString(entry, read, limit, materialize) {
1444
+ const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
1445
+ if (!entry.needsDecoding) {
1446
+ const length = Math.min(end - read.at, limit - read.length);
1447
+ if (length <= 0) return;
1448
+ if (materialize) read.text += this.slice(read.at, read.at + length);
1449
+ read.at += length;
1450
+ read.length += length;
1451
+ return;
1452
+ }
1453
+ let chunkIndex = this.chunkAt(read.at);
1454
+ while (read.at < end && read.length < limit) {
1455
+ const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
1456
+ const chunk = this.chunks[chunkIndex];
1457
+ const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
1458
+ const boundary = remaining.search(CONTENT_ESCAPE);
1459
+ const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
1460
+ if (length > 0) {
1461
+ if (materialize) read.text += remaining.slice(0, length);
1462
+ read.at += length;
1463
+ read.length += length;
1464
+ if (read.at === base + chunk.length) chunkIndex++;
1465
+ continue;
1466
+ }
1467
+ const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
1468
+ let decoded;
1469
+ let width = 2;
1470
+ if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
1471
+ if (remaining[0] === "\\" && type === "u") {
1472
+ const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
1473
+ let valid = true;
1474
+ for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
1475
+ if (valid) {
1476
+ if (hex.length < 4 && entry.end < 0) return;
1477
+ if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
1478
+ }
1479
+ width = 6;
1480
+ } else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
1481
+ if (decoded === void 0) {
1482
+ entry.invalidAt = read.at;
1483
+ this.#invalidValue = true;
1484
+ return;
1485
+ }
1486
+ if (materialize) read.text += decoded;
1487
+ read.length++;
1488
+ read.at += width;
1489
+ while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
1490
+ }
1491
+ }
1492
+ /** Locate new field ranges without decoding or parsing their contents. */
1493
+ scan() {
1494
+ if (this.object !== void 0 || this.#consumed === this.#size) return;
1495
+ for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
1496
+ const pending = this.chunks[i];
1497
+ const base = i === 0 ? 0 : this.#ends[i - 1];
1498
+ for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
1499
+ if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
1500
+ const end = this.stringBoundary(pending, index);
1501
+ this.#consumed += end - index;
1502
+ index = end;
1503
+ if (index === pending.length) break;
1504
+ }
1505
+ this.step(pending[index], this.#consumed);
1506
+ this.#consumed++;
1507
+ }
1508
+ }
1509
+ }
1510
+ /** Only raw quotes and their preceding backslash runs can terminate a string. */
1511
+ stringBoundary(fragment, start) {
1512
+ let at = start;
1513
+ while (true) {
1514
+ const quote = fragment.indexOf("\"", at);
1515
+ const end = quote < 0 ? fragment.length : quote;
1516
+ if (this.#mode === "string") {
1517
+ const entry = this.#current;
1518
+ if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
1519
+ }
1520
+ let slashStart = end;
1521
+ while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
1522
+ const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
1523
+ this.#escape = quote < 0 && escaped;
1524
+ if (quote < 0 || !escaped) return end;
1525
+ at = quote + 1;
1526
+ }
1527
+ }
1528
+ step(c, at) {
1529
+ switch (this.#mode) {
1530
+ case "root":
1531
+ if (isWhitespace(c)) return;
1532
+ if (c === "{") {
1533
+ this.#mode = "key-or-end";
1534
+ return;
1535
+ }
1536
+ this.fail();
1537
+ return;
1538
+ case "key-or-end":
1539
+ if (isWhitespace(c)) return;
1540
+ if (c === "}") {
1541
+ this.#mode = "closed";
1542
+ return;
1543
+ }
1544
+ if (c === "\"") {
1545
+ this.beginKey(at);
1546
+ return;
1547
+ }
1548
+ this.fail();
1549
+ return;
1550
+ case "key-only":
1551
+ if (isWhitespace(c)) return;
1552
+ if (c === "\"") {
1553
+ this.beginKey(at);
1554
+ return;
1555
+ }
1556
+ this.fail();
1557
+ return;
1558
+ case "key":
1559
+ this.stepKey(c, at);
1560
+ return;
1561
+ case "colon":
1562
+ if (isWhitespace(c)) return;
1563
+ if (c === ":") {
1564
+ this.#mode = "value";
1565
+ return;
1566
+ }
1567
+ this.fail();
1568
+ return;
1569
+ case "value":
1570
+ this.beginValue(c, at);
1571
+ return;
1572
+ case "string": {
1573
+ const entry = this.#current;
1574
+ entry.end = at;
1575
+ this.#current = null;
1576
+ this.#mode = "comma-or-end";
1577
+ return;
1578
+ }
1579
+ case "scalar":
1580
+ this.stepScalar(c, at);
1581
+ return;
1582
+ case "nested":
1583
+ this.stepNested(c, at);
1584
+ return;
1585
+ case "comma-or-end":
1586
+ if (isWhitespace(c)) return;
1587
+ if (c === ",") {
1588
+ this.#mode = "key-only";
1589
+ return;
1590
+ }
1591
+ if (c === "}") {
1592
+ this.#mode = "closed";
1593
+ return;
1594
+ }
1595
+ this.fail();
1596
+ return;
1597
+ case "closed":
1598
+ if (isWhitespace(c)) return;
1599
+ this.fail();
1600
+ return;
1601
+ /* v8 ignore next 2 -- scan() stops stepping once the view is invalid. */
1602
+ case "invalid": return;
1603
+ /* v8 ignore next 2 -- Every scanner mode has a handler above. */
1604
+ default: assertNever$1(this.#mode);
1605
+ }
1606
+ }
1607
+ fail() {
1608
+ this.#invalidAt = this.#consumed;
1609
+ this.#mode = "invalid";
1610
+ this.#current = null;
1611
+ }
1612
+ beginKey(at) {
1613
+ this.#mode = "key";
1614
+ this.#keyStart = at + 1;
1615
+ this.#keyEscaped = false;
1616
+ this.#escape = false;
1617
+ }
1618
+ stepKey(c, at) {
1619
+ if (c < " ") {
1620
+ this.fail();
1621
+ return;
1622
+ }
1623
+ if (this.#escape) {
1624
+ this.#escape = false;
1625
+ return;
1626
+ }
1627
+ if (c === "\\") {
1628
+ this.#escape = true;
1629
+ this.#keyEscaped = true;
1630
+ return;
1631
+ }
1632
+ if (c !== "\"") return;
1633
+ const raw = this.slice(this.#keyStart, at);
1634
+ if (this.#keyEscaped) try {
1635
+ this.#key = JSON.parse(`"${raw}"`);
1636
+ } catch (_error) {
1637
+ this.fail();
1638
+ return;
1639
+ }
1640
+ else this.#key = raw;
1641
+ this.#mode = "colon";
1642
+ }
1643
+ open(entry) {
1644
+ if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
1645
+ this.#entries.set(this.#key, entry);
1646
+ this.#current = entry;
1647
+ }
1648
+ beginValue(c, at) {
1649
+ if (isWhitespace(c)) return;
1650
+ if (c === "\"") {
1651
+ this.open({
1652
+ kind: "string",
1653
+ start: at + 1,
1654
+ end: -1,
1655
+ needsDecoding: false,
1656
+ invalidAt: void 0,
1657
+ length: void 0,
1658
+ text: void 0,
1659
+ prefixes: void 0
1660
+ });
1661
+ this.#escape = false;
1662
+ this.#mode = "string";
1663
+ return;
1664
+ }
1665
+ if (c === "}" || c === "," || c === ":" || c === "]") {
1666
+ this.fail();
1667
+ return;
1668
+ }
1669
+ this.open({
1670
+ kind: "value",
1671
+ start: at,
1672
+ end: -1,
1673
+ parsed: void 0,
1674
+ invalid: false
1675
+ });
1676
+ if (c === "{" || c === "[") {
1677
+ this.#mode = "nested";
1678
+ this.#nestedEnds = [c === "{" ? "}" : "]"];
1679
+ this.#nestedInString = false;
1680
+ this.#escape = false;
1681
+ return;
1682
+ }
1683
+ this.#mode = "scalar";
1684
+ }
1685
+ stepScalar(c, at) {
1686
+ if (c !== "," && c !== "}" && !isWhitespace(c)) return;
1687
+ this.closeValue(at);
1688
+ this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
1689
+ }
1690
+ stepNested(c, at) {
1691
+ if (this.#nestedInString) {
1692
+ this.#nestedInString = false;
1693
+ return;
1694
+ }
1695
+ if (c === "\"") {
1696
+ this.#nestedInString = true;
1697
+ return;
1698
+ }
1699
+ if (c === "{" || c === "[") {
1700
+ this.#nestedEnds.push(c === "{" ? "}" : "]");
1701
+ return;
1702
+ }
1703
+ if (c === "}" || c === "]") {
1704
+ if (this.#nestedEnds.pop() !== c) {
1705
+ this.fail();
1706
+ return;
1707
+ }
1708
+ if (this.#nestedEnds.length === 0) {
1709
+ this.closeValue(at + 1);
1710
+ this.#mode = "comma-or-end";
1711
+ }
1712
+ }
1713
+ }
1714
+ closeValue(end) {
1715
+ const entry = this.#current;
1716
+ entry.end = end;
1717
+ this.#current = null;
1718
+ }
1719
+ });
906
1720
  /** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
907
1721
  /**
908
1722
  * Mark an unreachable closed-union branch.
@@ -915,7 +1729,7 @@ window.__ModuleLoader__.load({
915
1729
  throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
916
1730
  }
917
1731
  //#endregion
918
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.0-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
1732
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.2.1-rc.2_@deepseek-ai+cordis@4.0.5/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
919
1733
  /**
920
1734
  * Browser-safe Workspace path and display helpers.
921
1735
  * @module @deepseek-ai/dsh-util-workspace-path
@@ -1490,46 +2304,90 @@ window.__ModuleLoader__.load({
1490
2304
  };
1491
2305
  }, "ui-workspace: Workspace navigation policy");
1492
2306
  }
1493
- async connectWorkspace(workspaceId) {
2307
+ connectWorkspace(workspaceId, options = {}) {
2308
+ return this.connectBlank(workspaceId, options);
2309
+ }
2310
+ /**
2311
+ * @param reuse - preset for the reused or created blank; absent creates a fresh Session.
2312
+ */
2313
+ async connectBlank(workspaceId, reuse) {
1494
2314
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
1495
2315
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
2316
+ if (reuse === void 0) return this.sessions.create({ workspaceId });
1496
2317
  const inflight = this.connecting.get(workspaceId);
1497
- if (inflight !== void 0) return inflight;
1498
- const attempt = this.reuseOrCreateBlank(workspace).finally(() => {
1499
- this.connecting.delete(workspaceId);
2318
+ if (inflight !== void 0 && inflight.agentPreset === reuse.agentPreset) return inflight.attempt;
2319
+ const attempt = this.createInWorkspace(workspace, reuse).finally(() => {
2320
+ if (this.connecting.get(workspaceId)?.attempt === attempt) this.connecting.delete(workspaceId);
2321
+ });
2322
+ this.connecting.set(workspaceId, {
2323
+ agentPreset: reuse.agentPreset,
2324
+ attempt
1500
2325
  });
1501
- this.connecting.set(workspaceId, attempt);
1502
2326
  return attempt;
1503
2327
  }
1504
- reuseOrCreateBlank(workspace) {
2328
+ createInWorkspace(workspace, options) {
1505
2329
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
1506
2330
  const sessions = this.sessions.list.getSnapshot();
1507
- for (const id of sessions.ids) {
2331
+ const blank = sessions.ids.find((id) => {
1508
2332
  const summary = sessions.byId[id];
1509
- if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
1510
- return this.reuseBlank(workspace.workspaceId, id);
1511
- }
1512
- return this.sessions.create({ workspaceId: workspace.workspaceId });
2333
+ return summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(id) && !archived.includes(id);
2334
+ });
2335
+ return this.createNamed({
2336
+ workspaceId: workspace.workspaceId,
2337
+ ...blank === void 0 ? {} : { sessionId: blank },
2338
+ ...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
2339
+ });
2340
+ }
2341
+ /**
2342
+ * Create through the Host. A named blank the Host declined may still read as
2343
+ * blank from stale cached list metadata, which hides it from the Session tree;
2344
+ * its projections are reloaded so the row reflects its log.
2345
+ */
2346
+ async createNamed(request) {
2347
+ const sessionId = await this.sessions.create(request);
2348
+ const named = request.sessionId;
2349
+ if (named !== void 0 && named !== sessionId) this.sessions.refreshProjections(named).catch((reason) => {
2350
+ console.warn("declined blank Session projection reload failed:", reason);
2351
+ });
2352
+ return sessionId;
1513
2353
  }
1514
- async reuseBlank(workspaceId, sessionId) {
2354
+ /**
2355
+ * Reopen a saved blank Session under the preset its log recorded, or under
2356
+ * the Host default once that preset no longer activates. Listed values are
2357
+ * cached hints that can lag the log after an unclean Host exit, so the
2358
+ * Session's projections are reloaded before the preset is read; a Session
2359
+ * that still awaits a format migration reloads only those cached hints.
2360
+ */
2361
+ async restoreBlank(workspaceId, sessionId) {
2362
+ await this.sessions.refreshProjections(sessionId);
2363
+ const recorded = this.sessions.list.getSnapshot().byId[sessionId]?.projectionValues?.agentPreset;
2364
+ const request = {
2365
+ workspaceId,
2366
+ sessionId
2367
+ };
2368
+ if (typeof recorded !== "string") return this.createNamed(request);
1515
2369
  try {
1516
- return await this.sessions.create({
1517
- workspaceId,
1518
- sessionId
2370
+ return await this.createNamed({
2371
+ ...request,
2372
+ agentPreset: recorded
1519
2373
  });
1520
2374
  } catch (error) {
1521
- if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
1522
- return this.sessions.create({ workspaceId });
2375
+ const code = sessionCreateErrorOf(error)?.rpcError.code;
2376
+ if (code !== "agent-preset/not-found" && code !== "agent-preset/invalid") throw error;
2377
+ return this.createNamed(request);
1523
2378
  }
1524
2379
  }
1525
2380
  openSession(target) {
1526
2381
  this.replaceMain(target, this.lifetime.signal, "reveal");
1527
2382
  }
1528
- async openWorkspace(workspaceId, beforeOpen) {
2383
+ openWorkspace(workspaceId, beforeOpen, options = {}) {
2384
+ return this.openWorkspaceSession(workspaceId, beforeOpen, options);
2385
+ }
2386
+ async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
1529
2387
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1530
2388
  let sessionId;
1531
2389
  try {
1532
- sessionId = await this.connectWorkspace(workspaceId);
2390
+ sessionId = await this.connectBlank(workspaceId, reuse);
1533
2391
  } catch (error) {
1534
2392
  if (!navigation.aborted) this.notify({
1535
2393
  kind: "createFailed",
@@ -1540,14 +2398,18 @@ window.__ModuleLoader__.load({
1540
2398
  if (navigation.aborted) return;
1541
2399
  this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
1542
2400
  }
1543
- async forkSession(sessionId, onCreated) {
2401
+ forkSession(sessionId, onCreated, options) {
1544
2402
  return this.sessions.fork({
1545
2403
  sessionId,
1546
2404
  increaseTitle: true,
1547
- ...onCreated === void 0 ? {} : { onCreated }
2405
+ ...onCreated === void 0 ? {} : { onCreated },
2406
+ ...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
1548
2407
  });
1549
2408
  }
1550
- startSession(workspaceId) {
2409
+ startSession(workspaceId, options) {
2410
+ const { agentPreset, ...draft } = options ?? {};
2411
+ const draftOptions = options === void 0 ? void 0 : draft;
2412
+ const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
1551
2413
  const workspace = this.workspaces.list.getSnapshot();
1552
2414
  const sessions = this.sessions.list.getSnapshot();
1553
2415
  const current = this.mainReference?.sessionId;
@@ -1555,13 +2417,34 @@ window.__ModuleLoader__.load({
1555
2417
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
1556
2418
  const target = workspaceId ?? currentWorkspaceId ?? recent;
1557
2419
  if (target === void 0) {
2420
+ if (initializeDraft) {
2421
+ this.notify({
2422
+ kind: "createFailed",
2423
+ message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
2424
+ });
2425
+ return;
2426
+ }
1558
2427
  this.clearMain();
1559
2428
  return;
1560
2429
  }
1561
- this.openWorkspace(target).catch((reason) => {
2430
+ (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (sessionId) => {
2431
+ const binding = this.sessions.binding(sessionId) ?? this.draftPreparationFailed();
2432
+ this.prepareDraft(binding, draftOptions);
2433
+ } : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
1562
2434
  console.warn("new session failed:", reason);
1563
2435
  });
1564
2436
  }
2437
+ prepareDraft(binding, options) {
2438
+ if ((this.ctx.get("conversation") ?? this.draftPreparationFailed()).input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
2439
+ }
2440
+ draftPreparationFailed() {
2441
+ const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
2442
+ this.notify({
2443
+ kind: "createFailed",
2444
+ message
2445
+ });
2446
+ throw new Error(message);
2447
+ }
1565
2448
  async archiveSession(sessionId, options = {}) {
1566
2449
  await this.workspaces.archiveSession(sessionId, options);
1567
2450
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
@@ -1640,7 +2523,7 @@ window.__ModuleLoader__.load({
1640
2523
  }
1641
2524
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
1642
2525
  let sessionId;
1643
- if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.reuseBlank(workspace.workspaceId, summary.id);
2526
+ if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.restoreBlank(workspace.workspaceId, summary.id);
1644
2527
  let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
1645
2528
  if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
1646
2529
  const prepared = await this.initializeDefaultWorkspace(navigation);
@@ -1946,6 +2829,51 @@ window.__ModuleLoader__.load({
1946
2829
  liveStatus: aggregated.get(group.key) ?? EMPTY_LIVE_STATUS
1947
2830
  }));
1948
2831
  }
2832
+ /** The blank row plus the first `watermark` non-blank rows, in list order. */
2833
+ function watermarkRows(sessions, watermark) {
2834
+ let count = 0;
2835
+ const rows = sessions.filter((session) => {
2836
+ if (session.blank) return true;
2837
+ count += 1;
2838
+ return count <= watermark;
2839
+ });
2840
+ return {
2841
+ rows,
2842
+ hiddenCount: sessions.length - rows.length
2843
+ };
2844
+ }
2845
+ /**
2846
+ * Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
2847
+ * child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
2848
+ * @param sessions - one group's ordered Session rows.
2849
+ * @param current - Session retained by the main view, if the sidebar is the active surface.
2850
+ * @param revealSessionId - Session selected from search and required to be visible.
2851
+ * @returns the group's baseline non-blank row count.
2852
+ */
2853
+ function requiredWatermark(sessions, current, revealSessionId) {
2854
+ let index = 0;
2855
+ let required = 0;
2856
+ for (const session of sessions) {
2857
+ if (session.blank) continue;
2858
+ index += 1;
2859
+ if (index <= 50 && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required = index;
2860
+ }
2861
+ return required;
2862
+ }
2863
+ /**
2864
+ * Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises.
2865
+ * @param marks - watermarks stored by the current browser tree.
2866
+ * @param required - required baselines, in group order.
2867
+ * @returns either the unchanged input or a new object containing only raised groups.
2868
+ */
2869
+ function raiseWatermarks(marks, required) {
2870
+ let raised = marks;
2871
+ for (const [key, watermark] of required) if (watermark > (raised[key] ?? 5)) raised = {
2872
+ ...raised,
2873
+ [key]: watermark
2874
+ };
2875
+ return raised;
2876
+ }
1949
2877
  //#endregion
1950
2878
  //#region src/client/facet-visibility.ts
1951
2879
  /**
@@ -2476,6 +3404,7 @@ window.__ModuleLoader__.load({
2476
3404
  side: "bottom",
2477
3405
  align: "end",
2478
3406
  delayMs: 500,
3407
+ portal: true,
2479
3408
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2480
3409
  type: "button",
2481
3410
  className: Rows_module_css_default.iconButton,
@@ -2504,6 +3433,7 @@ window.__ModuleLoader__.load({
2504
3433
  t
2505
3434
  }),
2506
3435
  openDelayMs: 800,
3436
+ grouped: true,
2507
3437
  disabled: menuOpen,
2508
3438
  copyText: row.cwd,
2509
3439
  copyLabel: t("copy"),
@@ -2658,6 +3588,7 @@ window.__ModuleLoader__.load({
2658
3588
  side: "bottom",
2659
3589
  align: "end",
2660
3590
  delayMs: 500,
3591
+ portal: true,
2661
3592
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2662
3593
  type: "button",
2663
3594
  className: Rows_module_css_default.iconButton,
@@ -2812,6 +3743,7 @@ window.__ModuleLoader__.load({
2812
3743
  t
2813
3744
  }),
2814
3745
  openDelayMs: 800,
3746
+ grouped: true,
2815
3747
  disabled: menuOpen || drag?.active === true,
2816
3748
  copyText: row.blank || row.title === "" ? void 0 : row.title,
2817
3749
  copyLabel: t("copy"),
@@ -3253,22 +4185,6 @@ window.__ModuleLoader__.load({
3253
4185
  const SEARCH_DEBOUNCE_MS = 250;
3254
4186
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
3255
4187
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
3256
- /** Idle Session rows visible per Workspace before the local overflow control. */
3257
- const COLLAPSED_SESSION_LIMIT = 5;
3258
- /** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
3259
- function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
3260
- let idleCount = 0;
3261
- const rows = sessions.filter((session) => {
3262
- if (session.blank || session.running || session.runningSubagentCount > 0) return true;
3263
- if (idleCount >= limit) return false;
3264
- idleCount += 1;
3265
- return true;
3266
- });
3267
- return {
3268
- rows,
3269
- hiddenCount: sessions.length - rows.length
3270
- };
3271
- }
3272
4188
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
3273
4189
  function sanitizeSearchQuery(value) {
3274
4190
  const withoutNul = value.replaceAll("\0", "");
@@ -3458,7 +4374,8 @@ window.__ModuleLoader__.load({
3458
4374
  const facets = (0, react.useMemo)(() => visibleFacets(facetsStore.list(), badgeSettings), [badgeSettings]);
3459
4375
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
3460
4376
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
3461
- const [sessionLimits, setSessionLimits] = (0, react.useState)({});
4377
+ const [storedWatermarks, setStoredWatermarks] = (0, react.useState)({});
4378
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react.useState)(0);
3462
4379
  const [drag, setDrag] = (0, react.useState)(null);
3463
4380
  const sessionDropCommitted = (0, react.useRef)(false);
3464
4381
  const [workspaceDrag, setWorkspaceDrag] = (0, react.useState)(null);
@@ -3516,20 +4433,24 @@ window.__ModuleLoader__.load({
3516
4433
  revealGroup,
3517
4434
  setGroupExpanded
3518
4435
  ]);
3519
- (0, react.useEffect)(() => {
3520
- if (revealSessionId === void 0 || revealGroup === void 0) return;
3521
- const group = groups.find((candidate) => candidate.key === revealGroup);
3522
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
3523
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
3524
- setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
3525
- ...limits,
3526
- [revealGroup]: Infinity
3527
- });
3528
- }, [
3529
- groups,
3530
- revealGroup,
3531
- revealSessionId
3532
- ]);
4436
+ const baselines = new Map(groups.map((group) => [group.key, Math.max(5, requiredWatermark(group.expanded ? group.sessions : [], current, group.key === revealGroup ? revealSessionId : void 0))]));
4437
+ const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
4438
+ const underFoldedAncestor = (key) => {
4439
+ for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
4440
+ return false;
4441
+ };
4442
+ const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
4443
+ const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
4444
+ if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
4445
+ const dropWatermarks = (drop) => {
4446
+ if (!Object.keys(storedWatermarks).some(drop)) return;
4447
+ setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
4448
+ setWatermarkMotionKey((key) => key + 1);
4449
+ };
4450
+ const withinGroup = (key, root) => {
4451
+ for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
4452
+ return false;
4453
+ };
3533
4454
  const now = Date.now();
3534
4455
  const commitSessionDrag = (activeDrag, over) => {
3535
4456
  if (sessionDropCommitted.current) return;
@@ -3540,7 +4461,7 @@ window.__ModuleLoader__.load({
3540
4461
  if (over.id === activeDrag.sessionId) return;
3541
4462
  const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
3542
4463
  if (accountSessionIds === void 0) return;
3543
- const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
4464
+ const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? 5).rows;
3544
4465
  const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
3545
4466
  if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
3546
4467
  };
@@ -3598,8 +4519,9 @@ window.__ModuleLoader__.load({
3598
4519
  const workspaceId = group.workspaceId;
3599
4520
  const children = childrenByParent.get(group.key) ?? [];
3600
4521
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
3601
- const collapsed = collapsedSessionRows(group.sessions);
3602
- const visible = collapsedSessionRows(group.sessions, sessionLimits[group.key]);
4522
+ const watermark = sessionWatermarks[group.key] ?? 5;
4523
+ const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? 5);
4524
+ const visible = watermarkRows(group.sessions, watermark);
3603
4525
  const sessionsExpanded = visible.hiddenCount === 0;
3604
4526
  rowKeys.push(`workspace:${group.key}`);
3605
4527
  const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
@@ -3673,10 +4595,7 @@ window.__ModuleLoader__.load({
3673
4595
  now,
3674
4596
  t,
3675
4597
  onToggle: () => {
3676
- if (group.expanded) setSessionLimits((limits) => ({
3677
- ...limits,
3678
- [group.key]: COLLAPSED_SESSION_LIMIT
3679
- }));
4598
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
3680
4599
  setGroupExpanded(group.key, !group.expanded);
3681
4600
  },
3682
4601
  onCreate: () => {
@@ -3761,10 +4680,15 @@ window.__ModuleLoader__.load({
3761
4680
  "data-row-key": `overflow:${group.key}`,
3762
4681
  "aria-expanded": sessionsExpanded,
3763
4682
  onClick: () => {
3764
- setSessionLimits((limits) => ({
3765
- ...limits,
3766
- [group.key]: sessionsExpanded ? COLLAPSED_SESSION_LIMIT : visible.hiddenCount <= COLLAPSED_SESSION_LIMIT ? Infinity : (limits[group.key] ?? COLLAPSED_SESSION_LIMIT) + COLLAPSED_SESSION_LIMIT
4683
+ if (sessionsExpanded) {
4684
+ dropWatermarks((key) => key === group.key);
4685
+ return;
4686
+ }
4687
+ setStoredWatermarks((marks) => ({
4688
+ ...marks,
4689
+ [group.key]: visible.hiddenCount <= 5 ? Infinity : watermark + 5
3767
4690
  }));
4691
+ setWatermarkMotionKey((key) => key + 1);
3768
4692
  },
3769
4693
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
3770
4694
  })
@@ -3772,7 +4696,7 @@ window.__ModuleLoader__.load({
3772
4696
  }, group.key);
3773
4697
  };
3774
4698
  const groupRows = rootGroups.map((group) => renderGroup(group, 0));
3775
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4699
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3776
4700
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3777
4701
  children: [
3778
4702
  workspaceDropAtListStart && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3784,7 +4708,7 @@ window.__ModuleLoader__.load({
3784
4708
  label: t("section.sessions"),
3785
4709
  rowKeys,
3786
4710
  ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
3787
- resetKey: animationResetKey,
4711
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
3788
4712
  children: [groupsWithAggregatedStatus.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptySessions, {
3789
4713
  rowState,
3790
4714
  onLeaveArchivedOnly,
@@ -3817,7 +4741,7 @@ window.__ModuleLoader__.load({
3817
4741
  if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
3818
4742
  };
3819
4743
  const now = Date.now();
3820
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4744
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
3821
4745
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
3822
4746
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AnimatedRows, {
3823
4747
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
@@ -3950,7 +4874,7 @@ window.__ModuleLoader__.load({
3950
4874
  * @param props - composed slot props (shell owner share + store + injected actions).
3951
4875
  * @returns the region element tree.
3952
4876
  */
3953
- 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, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, dismissForkError, renderSlot, t }) {
4877
+ 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, useBadgeSettings, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
3954
4878
  const home = useHostInfo((info) => info.home);
3955
4879
  const shortcuts = useShortcuts((rows) => rows);
3956
4880
  const searchShortcut = shortcuts.find((row) => row.id === "session.search");
@@ -4379,7 +5303,7 @@ window.__ModuleLoader__.load({
4379
5303
  side: "right",
4380
5304
  onPick: (workspaceId) => {
4381
5305
  closeAddWorkspace();
4382
- startSession(workspaceId);
5306
+ startSession(workspaceId, { clearPreviousDraft: false });
4383
5307
  },
4384
5308
  onClose: () => {
4385
5309
  closeAddWorkspace();
@@ -4559,11 +5483,7 @@ window.__ModuleLoader__.load({
4559
5483
  role: "alert",
4560
5484
  children: deleteError
4561
5485
  })]
4562
- }),
4563
- shortcutState.forkError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4564
- text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
4565
- onDone: dismissForkError
4566
- }, shortcutState.forkError.seq)
5486
+ })
4567
5487
  ]
4568
5488
  });
4569
5489
  }
@@ -4608,6 +5528,7 @@ window.__ModuleLoader__.load({
4608
5528
  side: "bottom",
4609
5529
  align: "end",
4610
5530
  delayMs: 500,
5531
+ portal: true,
4611
5532
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4612
5533
  type: "button",
4613
5534
  className: Rows_module_css_default.iconButton,
@@ -4753,13 +5674,15 @@ window.__ModuleLoader__.load({
4753
5674
  /** The fork action: one `sidebar.workspaces.session.menu.item` row. */
4754
5675
  /**
4755
5676
  * Menu row (order 300): fork at the Session's last completed turn; the child
4756
- * arrives through the Host list beside its source.
4757
- * @param props - owner share, menu open state, and the fork share.
4758
- * @returns the row.
5677
+ * arrives through the Host list beside its source. Absent when the access
5678
+ * snapshot excludes `session/fork` for this Session.
5679
+ * @param props - owner share, menu open state, access Hook, and the fork share.
5680
+ * @returns the row, or nothing when the caller may not fork this Session.
4759
5681
  */
4760
- function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
5682
+ function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
4761
5683
  const [, setMenuOpen] = useMenuOpenState();
4762
5684
  const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
5685
+ if (!useSessionAccess(sessionId, "session/fork")) return null;
4763
5686
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
4764
5687
  shortcut,
4765
5688
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
@@ -4820,6 +5743,7 @@ window.__ModuleLoader__.load({
4820
5743
  side: "bottom",
4821
5744
  align: "end",
4822
5745
  delayMs: 500,
5746
+ portal: true,
4823
5747
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4824
5748
  type: "button",
4825
5749
  className: Rows_module_css_default.iconButton,
@@ -4949,75 +5873,38 @@ window.__ModuleLoader__.load({
4949
5873
  });
4950
5874
  }
4951
5875
  //#endregion
4952
- //#region src/client/session-actions/RowActionToast.tsx
4953
- /**
4954
- * The `shell.overlay` entry for Workspace and Session notices.
4955
- * One notice is visible at a time; a parent rerender does not extend its hold.
4956
- */
5876
+ //#region src/client/session-actions/SessionId.tsx
5877
+ /** The copy Session ID action: one `sidebar.workspaces.session.menu.item` row. */
4957
5878
  /**
4958
- * Hold for the notices that take longer to read than a one-line warning: the
4959
- * actionable archived notice (two buttons to react to) and a refused Session
4960
- * creation, which quotes the Host's reason.
5879
+ * Copy the complete ID after dismissing the row menu.
5880
+ * @param props - row identity, menu dismissal, clipboard action, and locale.
5881
+ * @returns the final built-in menu item.
4961
5882
  */
4962
- const LONG_TOAST_HOLD_MS = 6e3;
5883
+ function SessionIdMenuItem({ sessionId, useMenuOpenState, copySessionId, t }) {
5884
+ const [, setMenuOpen] = useMenuOpenState();
5885
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
5886
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {}),
5887
+ onSelect: () => {
5888
+ setMenuOpen(false);
5889
+ copySessionId(sessionId);
5890
+ },
5891
+ children: t("menu.sessionId")
5892
+ });
5893
+ }
5894
+ //#endregion
5895
+ //#region src/client/session-actions/RowActionToast.tsx
4963
5896
  /**
4964
- * Render the current notice: the archived and stopped-and-archived notices
4965
- * with their undo action — plus the show-archived action while archived rows
4966
- * are hidden — on a 6 s hold, a refused Session creation with the Host's
4967
- * reason on the same hold, or a plain warning for a failed pin, an archived
4968
- * row that was clicked, or default Workspace creation.
4969
- * @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
4970
- * @returns the notice on display, or null.
5897
+ * Dispatch the current notice without interpreting its business payload.
5898
+ * @param props - Current notice, dismissal, and the declared keyed renderer.
5899
+ * @returns the selected notice presentation, or nothing.
4971
5900
  */
4972
- function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }) {
4973
- const toast = useToast((current) => current);
4974
- const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
4975
- if (toast === null) return null;
4976
- if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
4977
- const { sessionId } = toast;
4978
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
4979
- text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
4980
- tone: "success",
4981
- holdMs: LONG_TOAST_HOLD_MS,
4982
- actions: [{
4983
- label: t("toast.archivedUndo"),
4984
- onClick: () => {
4985
- dismissToast();
4986
- undoArchive(sessionId);
4987
- }
4988
- }, ...archivedRowsVisible ? [] : [{
4989
- prefix: t("toast.archivedOr"),
4990
- label: t("toast.archivedFilter"),
4991
- onClick: () => {
4992
- dismissToast();
4993
- showArchived();
4994
- }
4995
- }]],
4996
- onDone: dismissToast
4997
- }, `toast-${String(toast.seq)}`);
4998
- }
4999
- if (toast.kind === "createFailed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
5000
- text: t("toast.createFailed", { message: toast.message }),
5001
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
5002
- holdMs: LONG_TOAST_HOLD_MS,
5003
- onDone: dismissToast
5004
- }, `toast-${String(toast.seq)}`);
5005
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
5006
- text: plainNoticeText(toast, t),
5007
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
5008
- onDone: dismissToast
5009
- }, `toast-${String(toast.seq)}`);
5010
- }
5011
- /** The copy of one plain warning, keyed by the notice kind the union closes over. */
5012
- function plainNoticeText(toast, t) {
5013
- switch (toast.kind) {
5014
- case "pinFailed": return t("toast.pinFailed");
5015
- case "unpinFailed": return t("toast.unpinFailed");
5016
- case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
5017
- case "archivedNotOpenable": return t("toast.archivedNotOpenable");
5018
- /* v8 ignore next 2 -- closed-union backstop; only reached if a notice kind is forged */
5019
- default: return assertNever$1(toast);
5020
- }
5901
+ function RowActionToast({ useToast, dismissToast, renderSlot }) {
5902
+ const notice = useToast((current) => current);
5903
+ if (notice === null) return null;
5904
+ return renderSlot("workspace.toast", {
5905
+ ...notice,
5906
+ dismissToast
5907
+ }, { entryKey: notice.kind });
5021
5908
  }
5022
5909
  //#endregion
5023
5910
  //#region src/client/locales.ts
@@ -5029,6 +5916,8 @@ window.__ModuleLoader__.load({
5029
5916
  /** Simplified Chinese dictionary (the key-set source of truth). */
5030
5917
  const zh = {
5031
5918
  "defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
5919
+ "draft.workspaceRequired": "请先选择工作区",
5920
+ "draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
5032
5921
  "group.ungrouped": "未分组",
5033
5922
  "session.new": "新会话",
5034
5923
  "session.untitled": "未命名",
@@ -5083,6 +5972,9 @@ window.__ModuleLoader__.load({
5083
5972
  "menu.unarchiveSession": "取消归档",
5084
5973
  "menu.pinSession": "置顶会话",
5085
5974
  "menu.unpinSession": "取消置顶",
5975
+ "menu.sessionId": "复制 Session ID",
5976
+ "toast.sessionIdCopied": "Session ID 已复制",
5977
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
5086
5978
  "row.archived": "已归档",
5087
5979
  "row.pinned": "已置顶",
5088
5980
  "toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
@@ -5109,6 +6001,8 @@ window.__ModuleLoader__.load({
5109
6001
  "toast.pinFailed": "置顶失败,请稍后重试",
5110
6002
  "toast.unpinFailed": "取消置顶失败,请稍后重试",
5111
6003
  "toast.createFailed": "新建会话失败:{message}",
6004
+ "toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
6005
+ "toast.openForkSource": "打开原会话",
5112
6006
  "sessions.count.one": "{n} 个会话",
5113
6007
  "sessions.count.other": "{n} 个会话",
5114
6008
  "actions.workspace.aria": "工作区“{name}”的操作",
@@ -5167,6 +6061,8 @@ window.__ModuleLoader__.load({
5167
6061
  /** English dictionary, checked complete against the zh key set. */
5168
6062
  const en = {
5169
6063
  "defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
6064
+ "draft.workspaceRequired": "Choose a workspace first.",
6065
+ "draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
5170
6066
  "group.ungrouped": "Ungrouped",
5171
6067
  "session.new": "New Session",
5172
6068
  "session.untitled": "Untitled",
@@ -5221,6 +6117,9 @@ window.__ModuleLoader__.load({
5221
6117
  "menu.unarchiveSession": "Unarchive session",
5222
6118
  "menu.pinSession": "Pin session",
5223
6119
  "menu.unpinSession": "Unpin session",
6120
+ "menu.sessionId": "Copy Session ID",
6121
+ "toast.sessionIdCopied": "Session ID copied",
6122
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
5224
6123
  "row.archived": "Archived",
5225
6124
  "row.pinned": "Pinned",
5226
6125
  "toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
@@ -5247,6 +6146,8 @@ window.__ModuleLoader__.load({
5247
6146
  "toast.pinFailed": "Pin failed. Try again later.",
5248
6147
  "toast.unpinFailed": "Unpin failed. Try again later.",
5249
6148
  "toast.createFailed": "New session failed: {message}",
6149
+ "toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
6150
+ "toast.openForkSource": "Open original session",
5250
6151
  "sessions.count.one": "{n} session",
5251
6152
  "sessions.count.other": "{n} sessions",
5252
6153
  "actions.workspace.aria": "Workspace actions for {name}",
@@ -5321,7 +6222,8 @@ window.__ModuleLoader__.load({
5321
6222
  "remote",
5322
6223
  "remote.directoryPicker",
5323
6224
  "layout",
5324
- "shortcuts"
6225
+ "shortcuts",
6226
+ "remoteAccess"
5325
6227
  ];
5326
6228
  /**
5327
6229
  * Register the browser and picker once their slot declarations are on the
@@ -5347,7 +6249,14 @@ window.__ModuleLoader__.load({
5347
6249
  });
5348
6250
  };
5349
6251
  const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
5350
- ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
6252
+ const denyWorkspaceAccess = {
6253
+ getSnapshot: () => false,
6254
+ subscribe: () => () => {}
6255
+ };
6256
+ ctx.slots.provideRoot({
6257
+ hooks: { workspaces: workspaces.list },
6258
+ keyedHooks: { workspaceAccess: (workspaceId, action) => action === void 0 ? denyWorkspaceAccess : ctx.remoteAccess.source("workspace", workspaceId, action) }
6259
+ });
5351
6260
  ctx.effect(() => ctx.locale.register(NS, {
5352
6261
  zh,
5353
6262
  en
@@ -5446,7 +6355,12 @@ window.__ModuleLoader__.load({
5446
6355
  },
5447
6356
  unarchiveSession
5448
6357
  });
5449
- installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession);
6358
+ installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
6359
+ notify({
6360
+ kind: "shortcutForkFailed",
6361
+ reason
6362
+ });
6363
+ });
5450
6364
  const archiveConfirmInjected = () => ({
5451
6365
  hooks: { archiveRequest },
5452
6366
  settleSessionArchive: () => {
@@ -5467,7 +6381,12 @@ window.__ModuleLoader__.load({
5467
6381
  parent_session_id: sessionId,
5468
6382
  click_position: "sidebar"
5469
6383
  });
5470
- }).catch(() => {});
6384
+ }, { allowMigration: false }).catch((reason) => {
6385
+ if ((reason instanceof Error && reason.name === "SessionForkError" ? reason : void 0)?.rpcError?.code === "session/migration-required") notify({
6386
+ kind: "forkRequiresOpen",
6387
+ sessionId
6388
+ });
6389
+ });
5471
6390
  } });
5472
6391
  const renameInjected = () => ({ requestSessionRename });
5473
6392
  const renameDialogInjected = () => ({
@@ -5475,19 +6394,31 @@ window.__ModuleLoader__.load({
5475
6394
  settleSessionRename: shortcutControls.closeRename,
5476
6395
  renameSession
5477
6396
  });
6397
+ const sessionIdInjected = () => ({ copySessionId: async (id) => {
6398
+ notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(id) ? "sessionIdCopied" : "sessionIdCopyFailed" });
6399
+ } });
6400
+ registerToasts(ctx, viewStore, {
6401
+ undoArchive: unarchiveSession,
6402
+ showArchived: () => {
6403
+ viewInstance.actions.setArchivedFilter("show");
6404
+ },
6405
+ openForkSource: (id) => {
6406
+ if (workspaces.list.getSnapshot().archivedSessionIds.includes(id)) {
6407
+ notify({ kind: "archivedNotOpenable" });
6408
+ return;
6409
+ }
6410
+ openSession(id);
6411
+ }
6412
+ });
5478
6413
  const rowToastInjected = () => ({
5479
6414
  hooks: { toast: rowToast },
5480
6415
  dismissToast: () => {
5481
6416
  rowToast.set(null);
5482
- },
5483
- undoArchive: unarchiveSession,
5484
- showArchived: () => {
5485
- viewInstance.actions.setArchivedFilter("show");
5486
6417
  }
5487
6418
  });
5488
6419
  const browserInjected = () => ({
5489
- startSession: (workspaceId) => {
5490
- uiWorkspace.startSession(workspaceId);
6420
+ startSession: (workspaceId, options) => {
6421
+ uiWorkspace.startSession(workspaceId, options);
5491
6422
  },
5492
6423
  open: openSession,
5493
6424
  searchSessions,
@@ -5513,7 +6444,6 @@ window.__ModuleLoader__.load({
5513
6444
  requestAddWorkspace: shortcutControls.add,
5514
6445
  closeAddWorkspace: shortcutControls.closeAdd,
5515
6446
  setDirectoryBusy: shortcutControls.directoryBusy,
5516
- dismissForkError: shortcutControls.dismissForkError,
5517
6447
  hooks: {
5518
6448
  directoryFlow: browserFlowSource,
5519
6449
  hostInfo,
@@ -5586,6 +6516,13 @@ window.__ModuleLoader__.load({
5586
6516
  locale: NS,
5587
6517
  inject: archiveInjected
5588
6518
  }, ArchiveSessionMenuItem);
6519
+ yield ctx.slots.register({
6520
+ name: "sidebar.workspaces.session.menu.item",
6521
+ id: "session-id",
6522
+ order: 450,
6523
+ locale: NS,
6524
+ inject: sessionIdInjected
6525
+ }, SessionIdMenuItem);
5589
6526
  });
5590
6527
  ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
5591
6528
  yield ctx.slots.register({
@@ -5619,9 +6556,11 @@ window.__ModuleLoader__.load({
5619
6556
  yield ctx.slots.register({
5620
6557
  name: "shell.overlay",
5621
6558
  id: "workspace.row-toast",
5622
- locale: NS,
5623
- store: viewStore,
5624
- inject: rowToastInjected
6559
+ inject: rowToastInjected,
6560
+ children: { "workspace.toast": {
6561
+ kind: "keyed",
6562
+ scope: "root"
6563
+ } }
5625
6564
  }, RowActionToast);
5626
6565
  });
5627
6566
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({