dsh-git-worktree 0.10.0 → 0.11.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 (50) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/README.en.md +7 -4
  3. package/README.md +7 -4
  4. package/lib/adapters/registry.js +4 -32
  5. package/lib/adapters/registry.js.map +1 -1
  6. package/lib/client/actions.d.ts +2 -0
  7. package/lib/client/pre-session/controller.js +5 -0
  8. package/lib/client/pre-session/controller.js.map +1 -1
  9. package/lib/client/review-console/PreflightStatus.js +5 -2
  10. package/lib/client/review-console/PreflightStatus.js.map +1 -1
  11. package/lib/client/review-console/ReviewActions.js +47 -46
  12. package/lib/client/review-console/ReviewActions.js.map +1 -1
  13. package/lib/client/review-console/ReviewDetailsModal.js +13 -3
  14. package/lib/client/review-console/ReviewDetailsModal.js.map +1 -1
  15. package/lib/client/review-console/review-console.styles.js +22 -11
  16. package/lib/client/review-console/review-console.styles.js.map +1 -1
  17. package/lib/client/target-console/WorktreeReviewStatus.js +7 -7
  18. package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
  19. package/lib/client/workspace-sidebar/index.js +6 -3
  20. package/lib/client/workspace-sidebar/index.js.map +1 -1
  21. package/lib/client.js +1461 -467
  22. package/lib/commit-message.d.ts +2 -0
  23. package/lib/commit-message.js +5 -0
  24. package/lib/commit-message.js.map +1 -0
  25. package/lib/console-host/control-plane.js +6 -5
  26. package/lib/console-host/control-plane.js.map +1 -1
  27. package/lib/console-remote/schemas.d.ts +1 -1
  28. package/lib/console-remote/schemas.js +2 -1
  29. package/lib/console-remote/schemas.js.map +1 -1
  30. package/lib/host-compat.js +2 -1
  31. package/lib/host-compat.js.map +1 -1
  32. package/lib/i18n/client-messages.d.ts +8 -4
  33. package/lib/i18n/client-messages.js +2 -4
  34. package/lib/i18n/client-messages.js.map +1 -1
  35. package/lib/i18n/host-messages.d.ts +3 -7
  36. package/lib/i18n/host-messages.js +3 -7
  37. package/lib/i18n/host-messages.js.map +1 -1
  38. package/lib/index.d.ts +9 -9
  39. package/lib/ports.d.ts +1 -1
  40. package/lib/session-checkout-apply.js +6 -5
  41. package/lib/session-checkout-apply.js.map +1 -1
  42. package/lib/session-checkout-module.js +10 -7
  43. package/lib/session-checkout-module.js.map +1 -1
  44. package/lib/workspace-provider-lifecycle.js +31 -6
  45. package/lib/workspace-provider-lifecycle.js.map +1 -1
  46. package/package.json +43 -41
  47. package/scripts/check-publish.mjs +2 -0
  48. package/scripts/workspace-sidebar-upstream-alpha.mjs +16 -14
  49. package/scripts/workspace-sidebar-upstream-next.mjs +12 -16
  50. package/scripts/workspace-sidebar-upstream.mjs +18 -4
package/lib/client.js CHANGED
@@ -136,6 +136,14 @@ const clientMessages = {
136
136
  "zh": "未提供验证记录。",
137
137
  "en": "No validation records provided."
138
138
  },
139
+ "detail.expandList": {
140
+ "zh": "展开全部 {count} 项",
141
+ "en": "Show all {count} items"
142
+ },
143
+ "detail.collapseList": {
144
+ "zh": "收起",
145
+ "en": "Show fewer"
146
+ },
139
147
  "detail.version": {
140
148
  "zh": "版本信息",
141
149
  "en": "Version information"
@@ -188,10 +196,6 @@ const clientMessages = {
188
196
  "zh": "当前会话已切换,已停止准备独立工作目录。",
189
197
  "en": "The current session changed. Isolated working directory preparation stopped."
190
198
  },
191
- "500": {
192
- "zh": "/500",
193
- "en": "/500"
194
- },
195
199
  "the.cwd.of.new.harness.session.does.not": {
196
200
  "zh": "Harness 新建 Session {p0} 的 cwd 与 Host 记录不一致。",
197
201
  "en": "The cwd of new Harness Session {p0} does not match the Host record."
@@ -6185,6 +6189,13 @@ function superRefine(fn, params) {
6185
6189
  const describe = describe$1;
6186
6190
  const meta = meta$1;
6187
6191
 
6192
+ //#endregion
6193
+ //#region src/commit-message.ts
6194
+ /** Commit bodies are sent to Git on stdin; 500 characters is not a Git limit. */
6195
+ function isValidCommitMessage(value) {
6196
+ return typeof value === "string" && value.trim().length > 0 && !value.includes("\0");
6197
+ }
6198
+
6188
6199
  //#endregion
6189
6200
  //#region src/types.ts
6190
6201
  const SESSION_CHECKOUT_ERROR_CODES = [
@@ -6414,7 +6425,7 @@ const strict = (shape) => object(shape).strict();
6414
6425
  const sessionIdSchema = string().min(1).max(200).refine((value) => !/[\0\r\n]/u.test(value), "unsafe session id");
6415
6426
  const checkoutIdSchema = string().min(1).max(200).refine((value) => !value.includes("..") && !/[\\/\0\r\n]/u.test(value), "unsafe checkout id");
6416
6427
  const reviewIdSchema = string().min(1).max(200).refine((value) => !/[\0\r\n]/u.test(value), "unsafe review id");
6417
- const commitMessageSchema = string().trim().min(1).max(500);
6428
+ const commitMessageSchema = string().trim().refine(isValidCommitMessage, "invalid commit message");
6418
6429
  const revisionSchema = number().int().nonnegative();
6419
6430
  const gitOidSchema = string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu);
6420
6431
  const oidSchema = union([
@@ -8722,7 +8733,6 @@ const REVIEW_CONSOLE_STYLES = String.raw`
8722
8733
  .dsh-wt-retention-select { display:grid; gap:5px; padding:9px; border-radius:8px; background:var(--wt-panel); font-size:12px; }
8723
8734
  .dsh-wt-retention-select select { min-height:32px; border:1px solid var(--wt-line); border-radius:7px; padding:0 8px; background:Canvas; color:inherit; }
8724
8735
  .dsh-wt-preflight { display:grid; gap:7px; margin-top:7px; padding:8px 9px; border-radius:8px; background:var(--wt-panel); color:var(--wt-muted); font-size:12px; }
8725
- .dsh-wt-preflight[data-preflight="conflict"],
8726
8736
  .dsh-wt-preflight[data-preflight="blocked"],
8727
8737
  .dsh-wt-preflight[data-preflight="error"] { color:var(--wt-amber); }
8728
8738
  .dsh-wt-preflight-head { display:flex; align-items:center; justify-content:space-between; gap:8px; }
@@ -8735,7 +8745,15 @@ const REVIEW_CONSOLE_STYLES = String.raw`
8735
8745
  .dsh-wt-delivery-proof dt { color:var(--wt-muted); }
8736
8746
  .dsh-wt-preflight-facts dd,
8737
8747
  .dsh-wt-delivery-proof dd { min-width:0; margin:0; overflow-wrap:anywhere; }
8738
- .dsh-wt-conflict-list { display:grid; gap:3px; max-height:116px; margin:0; padding-left:18px; overflow:auto; }
8748
+ .dsh-wt-preflight.dsh-wt-conflict { display:flex; align-items:center; flex-wrap:wrap; color:inherit; min-width:0; padding:8px 10px; gap:8px 12px; border:1px solid color-mix(in srgb, var(--wt-amber) 22%, transparent); background:color-mix(in srgb, var(--wt-amber) 5%, transparent); }
8749
+ .dsh-wt-conflict-heading { display:flex; align-items:center; flex:1 1 auto; gap:8px; min-width:0; }
8750
+ .dsh-wt-conflict-heading > div { display:flex; align-items:center; flex-wrap:wrap; gap:2px 10px; min-width:0; }
8751
+ .dsh-wt-conflict-heading > .dsh-wt-icon,
8752
+ .dsh-wt-conflict-heading strong { color:var(--wt-amber); }
8753
+ .dsh-wt-conflict-heading strong { line-height:1.5; overflow-wrap:anywhere; }
8754
+ .dsh-wt-conflict-heading p { margin:0; color:var(--wt-muted); line-height:1.5; }
8755
+ .dsh-wt-conflict-actions { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:8px; margin-left:auto; color:var(--wt-ink); }
8756
+ .dsh-wt-conflict-actions .dsh-wt-button { display:inline-flex; align-items:center; gap:6px; max-width:100%; }
8739
8757
  .dsh-wt-recovery-actions { display:flex; flex-wrap:wrap; gap:6px; }
8740
8758
  .dsh-wt-inline-action { border:0; padding:0; background:transparent; color:inherit; font:inherit; text-decoration:underline; cursor:pointer; }
8741
8759
  .dsh-wt-inline-action:disabled { opacity:.5; cursor:not-allowed; }
@@ -8745,18 +8763,21 @@ const REVIEW_CONSOLE_STYLES = String.raw`
8745
8763
  .dsh-wt-delivery-proof-compact { display:block; margin:1px 0 0; padding:0; border:0; background:transparent; color:var(--wt-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
8746
8764
  .dsh-wt-action-status { min-height:1.35em; margin-top:7px; color:var(--wt-muted); font-size:12px; }
8747
8765
  .dsh-wt-review-dock { box-sizing:border-box; display:flex; flex:none; align-items:center; gap:9px; width:calc(100% - 2 * var(--dsh-composer-side-clearance, 16px) - 2 * var(--dsh-composer-dock-inset, 0px)); max-width:calc(var(--dsh-composer-card-max-width, 780px) - 2 * var(--dsh-composer-dock-inset, 0px)); margin:0 auto 8px; padding:7px 9px; border:1px solid color-mix(in srgb, #3275db 22%, transparent); border-radius:9px; background:color-mix(in srgb, #3275db 6%, transparent); color:var(--wt-ink); font-size:12px; }
8748
- .dsh-wt-review-dock > .dsh-wt-icon { order:-2; }
8749
- .dsh-wt-review-dock-copy { order:-1; order:-1; display:grid; min-width:0; flex:1; gap:1px; }
8766
+ .dsh-wt-review-dock > .dsh-wt-icon { order:-2; align-self:flex-start; margin-top:6px; }
8767
+ .dsh-wt-review-dock-copy { display:grid; min-width:0; flex:1; gap:1px; }
8750
8768
  .dsh-wt-review-dock-copy strong { font-weight:500; overflow-wrap:anywhere; }
8751
8769
  .dsh-wt-review-dock-copy span { color:var(--wt-muted); font-size:12px; }
8752
- .dsh-wt-review-dock .dsh-wt-review-actions { min-width:0; max-width:100%; padding:0; }
8770
+ .dsh-wt-review-dock .dsh-wt-review-actions { flex:1; min-width:0; max-width:100%; padding:0; }
8771
+ .dsh-wt-review-action-head { display:flex; align-items:center; flex-wrap:wrap; gap:6px 10px; min-width:0; }
8772
+ .dsh-wt-review-action-head > .dsh-wt-review-dock-copy { flex:1 1 180px; }
8773
+ .dsh-wt-review-action-head > .dsh-wt-actions { flex:0 0 auto; margin-left:auto; }
8753
8774
  .dsh-wt-action-status:empty { min-height:0; margin-top:0; }
8754
8775
  .dsh-wt-action-status:empty { min-height:0; margin-top:0; }
8755
8776
  .dsh-wt-review-dock .dsh-wt-action-status:empty { display:none; }
8756
8777
  .dsh-wt-review-evidence-status { margin:0 14px 10px 42px; color:var(--wt-muted); font-size:12px; }
8757
8778
  .dsh-wt-review-dock .dsh-wt-actions { justify-content:flex-end; }
8758
8779
  .dsh-wt-review-dock .dsh-wt-preflight-facts { display:none; }
8759
- .dsh-wt-review-dock .dsh-wt-preflight { margin:0; padding:4px 6px; }
8780
+ .dsh-wt-review-dock .dsh-wt-preflight { margin:8px 0 0; padding:8px 10px; }
8760
8781
  .dsh-wt-review-dock .dsh-wt-preflight[data-preflight="loading"],
8761
8782
  .dsh-wt-review-dock .dsh-wt-preflight[data-preflight="ready"],
8762
8783
  .dsh-wt-review-dock .dsh-wt-preflight[data-preflight="local_advanced"],
@@ -8771,16 +8792,13 @@ const REVIEW_CONSOLE_STYLES = String.raw`
8771
8792
  .dsh-wt-review-validation-details,
8772
8793
  .dsh-wt-review-actions { margin-left:0; padding-left:14px; }
8773
8794
  .dsh-wt-review-dock { flex-wrap:wrap; width:calc(100% - 2 * var(--dsh-composer-side-clearance, 12px) - 2 * var(--dsh-composer-dock-inset, 0px)); }
8774
- .dsh-wt-review-dock > .dsh-wt-icon { order:-2; }
8775
- .dsh-wt-review-dock-copy { order:-1; order:-1; flex-basis:calc(100% - 28px); }
8776
-
8777
- .dsh-wt-review-dock .dsh-wt-review-actions { margin-left:auto; }
8795
+ .dsh-wt-review-dock > .dsh-wt-review-dock-copy { flex-basis:calc(100% - 28px); }
8778
8796
  }
8779
8797
 
8780
8798
  .dsh-wt-icon { display:inline-flex; width:18px; height:18px; flex:0 0 18px; vertical-align:middle; }
8781
8799
  .dsh-wt-review-dock { background:var(--dsw-alias-bg-layer-2, #fff); border-color:var(--wt-line); padding:10px 12px; gap:10px; }
8782
8800
  .dsh-wt-review-dock-copy strong { font-size:13px; font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
8783
- .dsh-wt-review-dock-copy .dsh-wt-dock-evidence { display:flex; align-items:center; gap:5px; }
8801
+ .dsh-wt-review-dock-copy .dsh-wt-dock-evidence { display:flex; align-items:center; flex-wrap:wrap; gap:5px; }
8784
8802
  .dsh-wt-dock-evidence .dsh-wt-icon { width:14px; height:14px; flex-basis:14px; }
8785
8803
  .dsh-wt-details-trigger { display:inline-flex; align-items:center; gap:5px; flex-shrink:0; padding:6px; border:0; background:transparent; color:inherit; font:inherit; cursor:pointer; border-radius:6px; }
8786
8804
  .dsh-wt-details-trigger:hover { background:var(--wt-panel); }
@@ -8797,6 +8815,10 @@ const REVIEW_CONSOLE_STYLES = String.raw`
8797
8815
  .dsh-wt-details-notice[data-tone="warning"] { background:color-mix(in srgb, var(--wt-amber) 8%, transparent); }
8798
8816
  .dsh-wt-details-section { border-top:1px solid var(--wt-line); padding:16px 0; }
8799
8817
  .dsh-wt-details-section h3 { font-size:14px; font-weight:600; margin:0 0 12px; }
8818
+ .dsh-wt-details-list-toggle { display:inline-flex; align-items:center; gap:6px; margin-top:8px; padding:4px 0; border:0; background:transparent; color:var(--wt-muted); font:inherit; cursor:pointer; }
8819
+ .dsh-wt-details-list-toggle:hover { color:var(--wt-ink); }
8820
+ .dsh-wt-details-list-toggle:focus-visible { outline:2px solid #5a8dee; outline-offset:3px; }
8821
+ .dsh-wt-details-list-toggle[aria-expanded="true"] .dsh-wt-icon { transform:rotate(90deg); }
8800
8822
  .dsh-wt-details-files, .dsh-wt-details-tests { padding:0; margin:0; list-style:none; }
8801
8823
  .dsh-wt-details-files li { display:flex; align-items:flex-start; gap:10px; padding:8px 0; }
8802
8824
  .dsh-wt-details-files li > span:nth-child(2) { flex:1; min-width:0; overflow-wrap:anywhere; font-family:ui-monospace, monospace; }
@@ -9064,6 +9086,7 @@ var PreSessionWorktreeController = class {
9064
9086
  if (request.input.phase !== "plain") throw new PreSessionWorktreeError(t("the.draft.is.being.submitted.or.parsed.wait"));
9065
9087
  if (request.input.occurrences.length > 0) throw new PreSessionWorktreeError(t("the.draft.contains.unserialized.references.remove.the.reference"));
9066
9088
  if (!this.services.uiWorkspace?.openSession && !this.services.sessions.open) throw new PreSessionWorktreeError(t("harness.session.navigation.unavailable"));
9089
+ const sourcePreset = (this.services.sessions.list.getSnapshot().byId[request.sessionId]?.projectionValues)?.agentPreset;
9067
9090
  const previousBlock = this.services.conversation.blocks.storeFor(request.sessionId).getSnapshot();
9068
9091
  this.services.conversation.blocks.set(request.sessionId, { reason: t("creating.isolated.worktree") });
9069
9092
  let created;
@@ -9082,7 +9105,8 @@ var PreSessionWorktreeController = class {
9082
9105
  workspaceId = workspace.workspaceId;
9083
9106
  actualSessionId = await this.services.sessions.create({
9084
9107
  workspaceId: workspace.workspaceId,
9085
- sessionId: created.targetSessionId
9108
+ sessionId: created.targetSessionId,
9109
+ ...typeof sourcePreset === "string" ? { agentPreset: sourcePreset } : {}
9086
9110
  });
9087
9111
  if (actualSessionId !== created.targetSessionId) throw new PreSessionWorktreeError(t("harness.created.unexpected.session.expected.2", {
9088
9112
  p0: actualSessionId,
@@ -10325,6 +10349,28 @@ function validationText(status, t) {
10325
10349
  function validationIcon(status) {
10326
10350
  return status === "passed" ? "check" : status === "failed" || status === "partial" ? "warning" : "minus";
10327
10351
  }
10352
+ const COLLAPSED_LIST_LIMIT = 5;
10353
+ function ReviewDetailsList({ label, className, items }) {
10354
+ const t = useClientTranslator();
10355
+ const listId = (0, react.useId)();
10356
+ const [expanded, setExpanded] = (0, react.useState)(false);
10357
+ const collapsible = items.length > COLLAPSED_LIST_LIMIT;
10358
+ const toggleText = expanded ? t("detail.collapseList") : t("detail.expandList", { count: items.length });
10359
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10360
+ id: listId,
10361
+ className,
10362
+ "aria-label": label,
10363
+ children: expanded ? items : items.slice(0, COLLAPSED_LIST_LIMIT)
10364
+ }), collapsible ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10365
+ type: "button",
10366
+ className: "dsh-wt-details-list-toggle",
10367
+ "aria-label": `${label}: ${toggleText}`,
10368
+ "aria-expanded": expanded,
10369
+ "aria-controls": listId,
10370
+ onClick: () => setExpanded((value) => !value),
10371
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "chevron" }), toggleText]
10372
+ }) : null] });
10373
+ }
10328
10374
  function ReviewDetailsModal({ open, onClose, target, preflight }) {
10329
10375
  const t = useClientTranslator();
10330
10376
  const content = (0, react.useRef)(null);
@@ -10356,6 +10402,7 @@ function ReviewDetailsModal({ open, onClose, target, preflight }) {
10356
10402
  }, [open]);
10357
10403
  const review = target.review;
10358
10404
  if (!review) return null;
10405
+ const listKey = `${target.checkoutId}:${review.reviewId}:${target.revision}:${open}`;
10359
10406
  const conflict = preflight?.status === "conflict";
10360
10407
  const recovery = target.state === "preview_detached" || target.state === "recovery_required";
10361
10408
  const state = recovery ? "detail.recovery" : conflict || preflight?.status === "blocked" ? "detail.blocked" : target.state === "preview_active" ? "detail.preview" : target.state === "retained" || target.state === "delivered" || target.state === "cleanup_pending" ? "detail.saved" : "detail.ready";
@@ -10403,11 +10450,20 @@ function ReviewDetailsModal({ open, onClose, target, preflight }) {
10403
10450
  "data-tone": recovery || conflict || preflight?.status === "blocked" ? "warning" : "neutral",
10404
10451
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: recovery || conflict ? "warning" : "list" }), note]
10405
10452
  }) : null,
10453
+ conflict ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10454
+ className: "dsh-wt-details-section",
10455
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("conflicting.files") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewDetailsList, {
10456
+ label: t("conflicting.files"),
10457
+ className: "dsh-wt-details-files",
10458
+ items: preflight.conflictingFiles.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "file" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: file })] }, file))
10459
+ }, `conflicts:${listKey}:${JSON.stringify(preflight.conflictingFiles)}`)]
10460
+ }) : null,
10406
10461
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10407
10462
  className: "dsh-wt-details-section",
10408
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("detail.files") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10463
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("detail.files") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewDetailsList, {
10464
+ label: t("detail.files"),
10409
10465
  className: "dsh-wt-details-files",
10410
- children: review.changedFiles.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
10466
+ items: review.changedFiles.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
10411
10467
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "file" }),
10412
10468
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: file }),
10413
10469
  conflict && preflight.conflictingFiles.includes(file) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -10416,7 +10472,7 @@ function ReviewDetailsModal({ open, onClose, target, preflight }) {
10416
10472
  children: t("detail.conflict")
10417
10473
  }) : null
10418
10474
  ] }, file))
10419
- })]
10475
+ }, `files:${listKey}`)]
10420
10476
  }),
10421
10477
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10422
10478
  className: "dsh-wt-details-section",
@@ -10426,16 +10482,17 @@ function ReviewDetailsModal({ open, onClose, target, preflight }) {
10426
10482
  className: "dsh-wt-details-muted",
10427
10483
  children: review.validationSummary
10428
10484
  }) : null,
10429
- review.tests.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10485
+ review.tests.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewDetailsList, {
10486
+ label: t("detail.validation"),
10430
10487
  className: "dsh-wt-details-tests",
10431
- children: review.tests.map((test, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: validationIcon(test.status) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10488
+ items: review.tests.map((test, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: validationIcon(test.status) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10432
10489
  className: "dsh-wt-details-test-title",
10433
10490
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: test.command }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10434
10491
  "data-validation": test.status,
10435
10492
  children: validationText(test.status, t)
10436
10493
  })]
10437
10494
  }), test.summary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: test.summary }) : null] })] }, index))
10438
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10495
+ }, `tests:${listKey}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10439
10496
  className: "dsh-wt-details-muted",
10440
10497
  children: t("detail.noTests")
10441
10498
  })
@@ -10658,6 +10715,29 @@ function PreflightStatus({ snapshot, target, compact = false, onRefresh, onRecov
10658
10715
  })]
10659
10716
  });
10660
10717
  const preflight = snapshot.preflight;
10718
+ if (preflight.status === "conflict") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10719
+ className: "dsh-wt-preflight dsh-wt-conflict",
10720
+ "data-preflight": "conflict",
10721
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10722
+ className: "dsh-wt-conflict-heading",
10723
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "warning" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: statusLabel(preflight, t) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("read.only.check.local.unchanged") })] })]
10724
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10725
+ className: "dsh-wt-conflict-actions",
10726
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10727
+ type: "button",
10728
+ className: "dsh-wt-button",
10729
+ disabled: busy,
10730
+ onClick: onRefresh,
10731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "refresh" }), t("check.again")]
10732
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10733
+ type: "button",
10734
+ className: "dsh-wt-button dsh-wt-primary",
10735
+ disabled: busy || !target?.capabilities.resumeRevision,
10736
+ onClick: () => onRecovery(preflight),
10737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "warning" }), t("ask.agent.to.resolve.conflicts")]
10738
+ })]
10739
+ })]
10740
+ });
10661
10741
  const blocked = preflight.status === "blocked";
10662
10742
  const staleIsolated = blocked && preflight.reason === "stale_isolated" && preflight.reviewId !== null;
10663
10743
  const holder = blocked && preflight.reason === "project_acceptance_busy" ? preflight.blocker ?? target?.reviewSlotHolder : void 0;
@@ -10682,11 +10762,6 @@ function PreflightStatus({ snapshot, target, compact = false, onRefresh, onRecov
10682
10762
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("changes") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: t("count.files", { count: preflight.changedFiles.length }) })] })
10683
10763
  ]
10684
10764
  }) : null,
10685
- !compact && preflight.status === "conflict" && preflight.conflictingFiles.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10686
- className: "dsh-wt-conflict-list",
10687
- "aria-label": t("conflicting.files"),
10688
- children: preflight.conflictingFiles.map((path) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: path }) }, path))
10689
- }) : null,
10690
10765
  holder ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
10691
10766
  t("holding.task"),
10692
10767
  holder.checkoutId.slice(0, 8),
@@ -10705,7 +10780,7 @@ function PreflightStatus({ snapshot, target, compact = false, onRefresh, onRecov
10705
10780
  delivered: t("delivered")
10706
10781
  }[holder.state]
10707
10782
  ] }) : null,
10708
- preflight.status === "conflict" || blocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10783
+ blocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10709
10784
  className: "dsh-wt-recovery-actions",
10710
10785
  children: [
10711
10786
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -10715,17 +10790,7 @@ function PreflightStatus({ snapshot, target, compact = false, onRefresh, onRecov
10715
10790
  onClick: onRefresh,
10716
10791
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "refresh" }), t("check.again")]
10717
10792
  }),
10718
- preflight.status === "conflict" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10719
- type: "button",
10720
- className: "dsh-wt-inline-action",
10721
- disabled: busy || !target?.capabilities.resumeRevision,
10722
- onClick: () => onRecovery(preflight),
10723
- children: [
10724
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "warning" }),
10725
- t("ask.agent.to.resolve.conflicts"),
10726
- " "
10727
- ]
10728
- }) : staleIsolated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10793
+ staleIsolated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10729
10794
  type: "button",
10730
10795
  className: "dsh-wt-inline-action",
10731
10796
  disabled: busy,
@@ -11673,7 +11738,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
11673
11738
  const checkpoint = async () => {
11674
11739
  const value = checkpointMessage.trim();
11675
11740
  const requestId = checkpointRequestId.current;
11676
- if (!value || value.length > 500 || !requestId || !begin("checkpoint") || !adapter || !identity || !target?.checkpointGeneration) return;
11741
+ if (!isValidCommitMessage(value) || !requestId || !begin("checkpoint") || !adapter || !identity || !target?.checkpointGeneration) return;
11677
11742
  const expectedCount = target.checkpoints?.length ?? 0;
11678
11743
  const outcome = await adapter.checkpoint({
11679
11744
  ...identity,
@@ -11787,7 +11852,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
11787
11852
  const submitCommit = async () => {
11788
11853
  const value = commitMessage.trim();
11789
11854
  const mode = commitMode;
11790
- if (!mode || !value || value.length > 500 || !begin(mode) || !adapter || !identity) return;
11855
+ if (!mode || !isValidCommitMessage(value) || !begin(mode) || !adapter || !identity) return;
11791
11856
  const selectedRetention = retainEnvironment ? retention : "cleanup";
11792
11857
  const request = {
11793
11858
  ...identity,
@@ -12124,7 +12189,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12124
12189
  },
12125
12190
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "refresh" }), submitting === "retry_cleanup" ? t("cleaning.up") : t("retry.environment.cleanup")]
12126
12191
  }) : null;
12127
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [renderStatus?.(submitting), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
12192
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
12128
12193
  className: "dsh-wt-review-actions",
12129
12194
  "aria-label": t("review.actions"),
12130
12195
  children: [
@@ -12132,123 +12197,126 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12132
12197
  className: "dsh-wt-status",
12133
12198
  children: unavailableMessage
12134
12199
  }) : null,
12135
- terminal ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
12136
- className: "dsh-wt-status",
12137
- children: target.state === "retained" ? t("this.iteration.was.committed.the.environment.is.temporarily", { p0: target.commitOid ? ` · ${target.commitOid.slice(0, 8)}` : "" }) : t("this.iteration.was.delivered", { p0: target.commitOid ? ` · ${target.commitOid.slice(0, 8)}` : "" })
12138
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12139
- className: "dsh-wt-actions",
12140
- children: [primary, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
12141
- ref: moreMenuRef,
12142
- className: "dsh-wt-more-menu",
12143
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
12144
- className: "dsh-wt-more-trigger",
12145
- "aria-label": t("more.delivery.actions"),
12146
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "more" })
12147
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12148
- className: "dsh-wt-more-content",
12149
- role: "menu",
12150
- children: [
12151
- focusReview && !renderStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12152
- type: "button",
12153
- role: "menuitem",
12154
- className: "dsh-wt-more-item",
12155
- onClick: () => {
12156
- focusReview();
12157
- closeMoreMenu();
12158
- },
12159
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "list" }), t("view.review")]
12160
- }) : null,
12161
- ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12162
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12201
+ className: renderStatus ? "dsh-wt-review-action-head" : void 0,
12202
+ children: [renderStatus?.(submitting), terminal ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
12203
+ className: "dsh-wt-status",
12204
+ children: target.state === "retained" ? t("this.iteration.was.committed.the.environment.is.temporarily", { p0: target.commitOid ? ` · ${target.commitOid.slice(0, 8)}` : "" }) : t("this.iteration.was.delivered", { p0: target.commitOid ? ` · ${target.commitOid.slice(0, 8)}` : "" })
12205
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12206
+ className: "dsh-wt-actions",
12207
+ children: [ready && preflightSnapshot.status === "success" && preflightSnapshot.preflight.status === "conflict" ? null : primary, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
12208
+ ref: moreMenuRef,
12209
+ className: "dsh-wt-more-menu",
12210
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
12211
+ className: "dsh-wt-more-trigger",
12212
+ "aria-label": t("more.delivery.actions"),
12213
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "more" })
12214
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12215
+ className: "dsh-wt-more-content",
12216
+ role: "menu",
12217
+ children: [
12218
+ focusReview && !renderStatus ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12219
+ type: "button",
12220
+ role: "menuitem",
12221
+ className: "dsh-wt-more-item",
12222
+ onClick: () => {
12223
+ focusReview();
12224
+ closeMoreMenu();
12225
+ },
12226
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "list" }), t("view.review")]
12227
+ }) : null,
12228
+ ready ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
12229
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12230
+ type: "button",
12231
+ role: "menuitem",
12232
+ className: "dsh-wt-more-item",
12233
+ disabled: allDisabled || !target.capabilities.checkpoint || !target.checkpointGeneration,
12234
+ onClick: () => {
12235
+ openCheckpoint();
12236
+ closeMoreMenu();
12237
+ },
12238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "save" }), t("save.stage.and.continue")]
12239
+ }),
12240
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12241
+ type: "button",
12242
+ role: "menuitem",
12243
+ className: "dsh-wt-more-item",
12244
+ disabled: allDisabled || !target.capabilities.resumeRevision,
12245
+ onClick: () => {
12246
+ resumeRevision();
12247
+ closeMoreMenu();
12248
+ },
12249
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "edit" }), submitting === "resume_revision" ? t("recovering") : t("continue.editing")]
12250
+ }),
12251
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12252
+ type: "button",
12253
+ role: "menuitem",
12254
+ className: "dsh-wt-more-item",
12255
+ disabled: allDisabled || !target.capabilities.finalize || !safeReadyPreflight,
12256
+ onClick: () => {
12257
+ setCommitMode("finish");
12258
+ closeMoreMenu();
12259
+ },
12260
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "confirm" }), t("skip.preview.and.save")]
12261
+ })
12262
+ ] }) : null,
12263
+ previewActive && target.capabilities.checkpoint && target.checkpointGeneration ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12163
12264
  type: "button",
12164
12265
  role: "menuitem",
12165
12266
  className: "dsh-wt-more-item",
12166
- disabled: allDisabled || !target.capabilities.checkpoint || !target.checkpointGeneration,
12267
+ disabled: allDisabled,
12167
12268
  onClick: () => {
12168
12269
  openCheckpoint();
12169
12270
  closeMoreMenu();
12170
12271
  },
12171
12272
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "save" }), t("save.stage.and.continue")]
12172
- }),
12173
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12273
+ }) : null,
12274
+ previewActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12174
12275
  type: "button",
12175
12276
  role: "menuitem",
12176
12277
  className: "dsh-wt-more-item",
12177
- disabled: allDisabled || !target.capabilities.resumeRevision,
12278
+ disabled: allDisabled || !target.capabilities.rollbackPreview,
12178
12279
  onClick: () => {
12179
- resumeRevision();
12280
+ rollbackPreview(true);
12180
12281
  closeMoreMenu();
12181
12282
  },
12182
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "edit" }), submitting === "resume_revision" ? t("recovering") : t("continue.editing")]
12183
- }),
12184
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12283
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "edit" }), t("continue.editing")]
12284
+ }) : null,
12285
+ previewActive || rollbackRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12185
12286
  type: "button",
12186
12287
  role: "menuitem",
12187
12288
  className: "dsh-wt-more-item",
12188
- disabled: allDisabled || !target.capabilities.finalize || !safeReadyPreflight,
12289
+ disabled: allDisabled || !target.capabilities.rollbackPreview,
12189
12290
  onClick: () => {
12190
- setCommitMode("finish");
12291
+ rollbackPreview();
12191
12292
  closeMoreMenu();
12192
12293
  },
12193
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "confirm" }), t("skip.preview.and.save")]
12194
- })
12195
- ] }) : null,
12196
- previewActive && target.capabilities.checkpoint && target.checkpointGeneration ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12197
- type: "button",
12198
- role: "menuitem",
12199
- className: "dsh-wt-more-item",
12200
- disabled: allDisabled,
12201
- onClick: () => {
12202
- openCheckpoint();
12203
- closeMoreMenu();
12204
- },
12205
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "save" }), t("save.stage.and.continue")]
12206
- }) : null,
12207
- previewActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12208
- type: "button",
12209
- role: "menuitem",
12210
- className: "dsh-wt-more-item",
12211
- disabled: allDisabled || !target.capabilities.rollbackPreview,
12212
- onClick: () => {
12213
- rollbackPreview(true);
12214
- closeMoreMenu();
12215
- },
12216
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "edit" }), t("continue.editing")]
12217
- }) : null,
12218
- previewActive || rollbackRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12219
- type: "button",
12220
- role: "menuitem",
12221
- className: "dsh-wt-more-item",
12222
- disabled: allDisabled || !target.capabilities.rollbackPreview,
12223
- onClick: () => {
12224
- rollbackPreview();
12225
- closeMoreMenu();
12226
- },
12227
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "rollback" }), previewActive ? t("roll.back.this.preview") : t("retry.rollback")]
12228
- }) : null,
12229
- finalizeRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12230
- type: "button",
12231
- role: "menuitem",
12232
- className: "dsh-wt-more-item",
12233
- disabled: allDisabled || !target.capabilities.finalizePreview,
12234
- onClick: () => {
12235
- setCommitMode("finalize_preview");
12236
- closeMoreMenu();
12237
- },
12238
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "confirm" }), t("save.changes")]
12239
- }) : null,
12240
- canDiscard ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12241
- type: "button",
12242
- role: "menuitem",
12243
- className: "dsh-wt-more-item dsh-wt-danger-text",
12244
- disabled: allDisabled,
12245
- onClick: () => {
12246
- setDiscardOpen(true);
12247
- closeMoreMenu();
12248
- },
12249
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "discard" }), t("discard.task")]
12250
- }) : null
12251
- ]
12294
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "rollback" }), previewActive ? t("roll.back.this.preview") : t("retry.rollback")]
12295
+ }) : null,
12296
+ finalizeRecoverySafe ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12297
+ type: "button",
12298
+ role: "menuitem",
12299
+ className: "dsh-wt-more-item",
12300
+ disabled: allDisabled || !target.capabilities.finalizePreview,
12301
+ onClick: () => {
12302
+ setCommitMode("finalize_preview");
12303
+ closeMoreMenu();
12304
+ },
12305
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "confirm" }), t("save.changes")]
12306
+ }) : null,
12307
+ canDiscard ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12308
+ type: "button",
12309
+ role: "menuitem",
12310
+ className: "dsh-wt-more-item dsh-wt-danger-text",
12311
+ disabled: allDisabled,
12312
+ onClick: () => {
12313
+ setDiscardOpen(true);
12314
+ closeMoreMenu();
12315
+ },
12316
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "discard" }), t("discard.task")]
12317
+ }) : null
12318
+ ]
12319
+ })]
12252
12320
  })]
12253
12321
  })]
12254
12322
  }),
@@ -12269,7 +12337,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12269
12337
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12270
12338
  type: "button",
12271
12339
  className: "dsh-wt-button dsh-wt-primary",
12272
- disabled: submitting !== null || !checkpointMessage.trim() || checkpointMessage.trim().length > 500,
12340
+ disabled: submitting !== null || !isValidCommitMessage(checkpointMessage),
12273
12341
  onClick: () => {
12274
12342
  checkpoint();
12275
12343
  },
@@ -12291,13 +12359,12 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12291
12359
  id: `${formId}-checkpoint-message`,
12292
12360
  "aria-label": t("checkpoint.commit.message"),
12293
12361
  rows: 6,
12294
- maxLength: 500,
12295
12362
  value: checkpointMessage,
12296
12363
  onChange: (event) => setCheckpointMessage(event.target.value)
12297
12364
  }),
12298
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12299
12366
  className: "dsh-wt-character-count",
12300
- children: [checkpointMessage.length, t("500")]
12367
+ children: checkpointMessage.length
12301
12368
  })
12302
12369
  ]
12303
12370
  })
@@ -12319,7 +12386,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12319
12386
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12320
12387
  type: "button",
12321
12388
  className: "dsh-wt-button dsh-wt-primary",
12322
- disabled: submitting !== null || !commitMessage.trim() || commitMessage.trim().length > 500,
12389
+ disabled: submitting !== null || !isValidCommitMessage(commitMessage),
12323
12390
  onClick: () => {
12324
12391
  submitCommit();
12325
12392
  },
@@ -12337,13 +12404,12 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12337
12404
  id: `${formId}-message`,
12338
12405
  "aria-label": t("commit.message"),
12339
12406
  rows: 6,
12340
- maxLength: 500,
12341
12407
  value: commitMessage,
12342
12408
  onChange: (event) => setCommitMessage(event.target.value)
12343
12409
  }),
12344
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12345
12411
  className: "dsh-wt-character-count",
12346
- children: [commitMessage.length, t("500")]
12412
+ children: commitMessage.length
12347
12413
  }),
12348
12414
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
12349
12415
  className: "dsh-wt-retention-check",
@@ -12559,7 +12625,7 @@ function ReviewActions({ review, adapter, services, identity, target, disabled,
12559
12625
  }), " "]
12560
12626
  }) : null
12561
12627
  ]
12562
- })] });
12628
+ }) });
12563
12629
  }
12564
12630
 
12565
12631
  //#endregion
@@ -12699,12 +12765,6 @@ function WorktreeReviewStatus({ session, adapter, services }) {
12699
12765
  className: "dsh-wt-error",
12700
12766
  children: error
12701
12767
  }) : null,
12702
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12703
- type: "button",
12704
- className: "dsh-wt-details-trigger",
12705
- onClick: () => setDetailsOpen(true),
12706
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "list" }), t("detail.view")]
12707
- }),
12708
12768
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewDetailsModal, {
12709
12769
  open: detailsOpen,
12710
12770
  onClose: () => setDetailsOpen(false),
@@ -12720,7 +12780,7 @@ function WorktreeReviewStatus({ session, adapter, services }) {
12720
12780
  disabled: false,
12721
12781
  unavailableMessage: t("live.worktree.console.is.disconnected"),
12722
12782
  focusReview: () => setDetailsOpen(true),
12723
- renderStatus: (operation) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
12783
+ renderStatus: (operation) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
12724
12784
  className: "dsh-wt-review-dock-copy",
12725
12785
  role: "status",
12726
12786
  "aria-live": "polite",
@@ -12741,7 +12801,12 @@ function WorktreeReviewStatus({ session, adapter, services }) {
12741
12801
  }),
12742
12802
  target.state === "preview_detached" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: detail }) : null
12743
12803
  ]
12744
- }),
12804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
12805
+ type: "button",
12806
+ className: "dsh-wt-details-trigger",
12807
+ onClick: () => setDetailsOpen(true),
12808
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewIcon, { name: "list" }), t("detail.view")]
12809
+ })] }),
12745
12810
  isActive: () => mounted.current && currentSessionId.current === sessionId && sessionGeneration.current === actionGeneration,
12746
12811
  onStale: () => {
12747
12812
  refresh();
@@ -13018,10 +13083,10 @@ const official$2 = ((require$1) => {
13018
13083
  var exports$1 = module$1.exports;
13019
13084
  Object.defineProperty(exports$1, Symbol.toStringTag, { value: "Module" });
13020
13085
  let _deepseek_ai_dsh_client_store = require$1("@deepseek-ai/dsh-client-store");
13086
+ let _deepseek_ai_dsh_client_ui_primitives = require$1("@deepseek-ai/dsh-client-ui-primitives");
13021
13087
  let _deepseek_ai_cordis = require$1("@deepseek-ai/cordis");
13022
13088
  let react_jsx_runtime$1 = require$1("react/jsx-runtime");
13023
13089
  let react$1 = require$1("react");
13024
- let _deepseek_ai_dsh_client_ui_primitives = require$1("@deepseek-ai/dsh-client-ui-primitives");
13025
13090
  /**
13026
13091
  * Bind the row's render occurrence into the entries' `useMenuOpenState` hook:
13027
13092
  * the owner supplies its open-state pair as the occurrence's `hookContext`,
@@ -13040,10 +13105,8 @@ const official$2 = ((require$1) => {
13040
13105
  searchRequest: 0,
13041
13106
  addRequested: false,
13042
13107
  directoryBusy: false,
13043
- renameTarget: null,
13044
- forkError: null
13108
+ renameTarget: null
13045
13109
  });
13046
- let forkErrorSeq = 0;
13047
13110
  return {
13048
13111
  state,
13049
13112
  search: () => {
@@ -13084,22 +13147,6 @@ const official$2 = ((require$1) => {
13084
13147
  ...state.getSnapshot(),
13085
13148
  renameTarget: null
13086
13149
  });
13087
- },
13088
- forkFailed: (reason) => {
13089
- forkErrorSeq += 1;
13090
- state.set({
13091
- ...state.getSnapshot(),
13092
- forkError: {
13093
- reason,
13094
- seq: forkErrorSeq
13095
- }
13096
- });
13097
- },
13098
- dismissForkError: () => {
13099
- state.set({
13100
- ...state.getSnapshot(),
13101
- forkError: null
13102
- });
13103
13150
  }
13104
13151
  };
13105
13152
  }
@@ -13110,8 +13157,9 @@ const official$2 = ((require$1) => {
13110
13157
  * @param navigation - session creation and forking from the pointer controls' navigation service.
13111
13158
  * @param controls - browser-owned opening requests.
13112
13159
  * @param archiveSession - shared archive action, including running-work confirmation and notices.
13160
+ * @param reportForkFailure - publish a classified failure through the frame-wide notice channel.
13113
13161
  */
13114
- function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession) {
13162
+ function installWorkspaceShortcuts(ctx, navigation, controls, archiveSession, reportForkFailure) {
13115
13163
  const t = ctx.locale.bind("workspace");
13116
13164
  const current = () => Object.values(ctx.sessions.list.getSnapshot().byId).find((row) => (row.retainedBy.mainView ?? 0) > 0);
13117
13165
  const addReason = () => ctx.slots.entries("sidebar.workspaces.directoryFlow").length === 0 ? t("shortcut.noPicker") : controls.state.getSnapshot().directoryBusy ? t("shortcut.directoryBusy") : null;
@@ -13194,7 +13242,7 @@ const official$2 = ((require$1) => {
13194
13242
  run: () => {
13195
13243
  navigation.forkSession(target.id).catch((error) => {
13196
13244
  const unavailable = error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/fork-unavailable";
13197
- controls.forkFailed(unavailable ? "unavailable" : "failed");
13245
+ reportForkFailure(unavailable ? "unavailable" : "failed");
13198
13246
  if (!unavailable) console.warn("session fork rejected:", error);
13199
13247
  });
13200
13248
  }
@@ -13214,137 +13262,757 @@ const official$2 = ((require$1) => {
13214
13262
  });
13215
13263
  }
13216
13264
  /**
13217
- * Mark an unreachable closed-union branch.
13218
- * @param value - impossible value; an unhandled typed variant fails at the call site.
13219
- * @param context - optional switch-site label included in the failure message.
13220
- * @returns never; a runtime value that escaped its type always throws.
13221
- */
13222
- function assertNever$1(value, context) {
13223
- const rendered = JSON.stringify(value) ?? String(value);
13224
- throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
13225
- }
13226
- /** Whether a path uses a Windows drive or UNC prefix. */
13227
- function isWindowsStylePath(value) {
13228
- return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
13229
- }
13230
- /**
13231
- * Abbreviate a POSIX home directory for display.
13232
- * @param path - Absolute or already-short display path.
13233
- * @param home - Host account home; absent skips abbreviation.
13234
- * @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
13235
- */
13236
- function abbreviateHomePath(path, home) {
13237
- if (home === void 0 || home === "") return path;
13238
- if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
13239
- const root = home.replace(/\/+$/, "");
13240
- if (root === "" || root === "/") return path;
13241
- if (path.replace(/\/+$/, "") === root) return "~";
13242
- if (path.startsWith(`${root}/`)) return `~${path.slice(root.length)}`;
13243
- return path;
13244
- }
13245
- /**
13246
- * Read the final non-empty segment of a Workspace path for display.
13247
- * Workspace-label surfaces use this helper instead of deriving another basename.
13248
- * @param path - Workspace directory path using POSIX or Windows separators.
13249
- * @returns the final segment, or an empty string for a separator-only path.
13250
- */
13251
- function workspaceTitleOf(path) {
13252
- const trimmed = path.replace(/[/\\]+$/, "");
13253
- const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
13254
- return trimmed.slice(separator + 1);
13255
- }
13256
- /**
13257
- * Resolve the Workspace browser group that owns one Session.
13258
- * @param workspaces - authoritative Workspace membership.
13259
- * @param sessionId - Session whose browser group is required.
13260
- * @returns owning Workspace id, or {@link UNGROUPED_KEY} when no Workspace accounts for it.
13261
- */
13262
- function owningGroupKey(workspaces, sessionId) {
13263
- return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
13264
- }
13265
- function mainSessionId(list) {
13266
- return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
13267
- }
13268
- /**
13269
- * Directory display label: basename of the path (both separators accepted).
13270
- * Ungrouped-bucket fallback for surfaces without a workspace title.
13271
- * @param cwd - directory path, or undefined for the ungrouped bucket.
13272
- * @returns basename, the raw cwd when it has no basename, or an empty ungrouped marker.
13265
+ * Lazily scanned view of one JSON object's top-level fields, built from text
13266
+ * that may still be streaming or from an already parsed object. Nothing is
13267
+ * scanned until a reader asks; the view remembers every question it answered
13268
+ * and reports changed answers when the owner refreshes for publication.
13269
+ * Used for model tool-call arguments: a row reads the fields it
13270
+ * cares about at whatever granularity it displays, at every stage of the call.
13271
+ * @module @deepseek-ai/dsh-util-values/src/partial-json
13273
13272
  */
13274
- function workspaceLabel(cwd) {
13275
- if (cwd === void 0 || cwd === "") return "";
13276
- const base = workspaceTitleOf(cwd);
13277
- return base !== "" ? base : cwd;
13273
+ const SIMPLE_ESCAPES = {
13274
+ "\"": "\"",
13275
+ "\\": "\\",
13276
+ "/": "/",
13277
+ b: "\b",
13278
+ f: "\f",
13279
+ n: "\n",
13280
+ r: "\r",
13281
+ t: " "
13282
+ };
13283
+ const CONTENT_ESCAPE = /[\\\u0000-\u001f]/u;
13284
+ function isWhitespace(c) {
13285
+ return c === " " || c === "\n" || c === "\r" || c === " ";
13278
13286
  }
13279
- /**
13280
- * Project known account members by current Session recency.
13281
- * @param sessionIds - authoritative account membership.
13282
- * @param summaries - current Session summaries; members without a summary are omitted until it arrives.
13283
- * @returns known members newest first, with Session identity as the deterministic tie-break.
13284
- */
13285
- function orderByRecency(sessionIds, summaries) {
13286
- return sessionIds.flatMap((id) => {
13287
- const summary = summaries[id];
13288
- if (summary === void 0) return [];
13289
- return [{
13290
- id,
13291
- rank: summary.updatedAt
13292
- }];
13293
- }).sort((a, b) => {
13294
- if (a.rank !== b.rank) return b.rank - a.rank;
13295
- return a.id < b.id ? -1 : 1;
13296
- }).map((member) => member.id);
13287
+ function isHex(c) {
13288
+ return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
13297
13289
  }
13298
- /**
13299
- * Reconcile a browser-local manual order with current account membership.
13300
- * New ordinary forks precede their sources without changing saved entries' relative order.
13301
- * @param memberIds - authoritative account membership.
13302
- * @param savedOrder - previously saved browser-local order.
13303
- * @param summaries - current Session metadata; unknown new members wait for their summaries.
13304
- * @param rowState - global pin and archive membership; only account members can supplement the order.
13305
- * @returns saved relative positions plus missing members ordered by pin, fork source, recency, and archive status.
13306
- */
13307
- function reconcileManualOrder(memberIds, savedOrder, summaries, rowState) {
13308
- const members = new Map(memberIds.map((id) => [id, id]));
13309
- const included = /* @__PURE__ */ new Set();
13310
- const ordered = [];
13311
- for (const key of savedOrder ?? []) {
13312
- const id = members.get(key);
13313
- if (id === void 0 || included.has(key)) continue;
13314
- ordered.push(id);
13315
- included.add(key);
13290
+ (class PartialArguments {
13291
+ /** The view of a call with no arguments available. */
13292
+ static EMPTY = PartialArguments.fromObject({});
13293
+ /**
13294
+ * View finished argument text without scanning it until a reader asks.
13295
+ * @param text - the complete argument JSON text.
13296
+ * @returns a sealed view.
13297
+ */
13298
+ static fromText(text) {
13299
+ const view = new PartialArguments();
13300
+ view.append(text);
13301
+ view.sealed = true;
13302
+ return view;
13316
13303
  }
13317
- const archived = new Set(rowState?.archivedSessionIds);
13318
- const pins = [];
13319
- for (const sessionId of rowState?.pinnedSessionIds ?? []) {
13320
- const id = members.get(sessionId);
13321
- if (id === void 0 || included.has(id) || archived.has(id) || summaries[id] === void 0) continue;
13322
- pins.push(id);
13323
- included.add(id);
13304
+ /**
13305
+ * View an already parsed argument payload, such as a PTC dispatch object.
13306
+ * @param value - the parsed argument value.
13307
+ * @returns a sealed view; a non-object payload has no fields.
13308
+ */
13309
+ static fromObject(value) {
13310
+ const view = new PartialArguments();
13311
+ view.object = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
13312
+ view.sealed = true;
13313
+ return view;
13324
13314
  }
13325
- const ordinary = [];
13326
- const archives = [];
13327
- for (const id of orderByRecency([...members.values()].filter((id$1) => !included.has(id$1)), summaries)) if (archived.has(id)) archives.push(id);
13328
- else ordinary.push(id);
13329
- const result = [
13330
- ...pins,
13331
- ...ordered,
13332
- ...ordinary,
13333
- ...archives
13334
- ];
13335
- const pending = new Set(ordinary);
13336
- const placeFork = (id) => {
13337
- if (!pending.delete(id)) return;
13338
- const parentId = summaries[id]?.parentId;
13339
- if (parentId === void 0 || parentId === id || !result.includes(parentId)) return;
13340
- placeFork(parentId);
13341
- result.splice(result.indexOf(id), 1);
13342
- result.splice(result.indexOf(parentId), 0, id);
13343
- };
13344
- for (const id of [...ordinary].reverse()) placeFork(id);
13345
- return result;
13346
- }
13347
- /**
13315
+ /**
13316
+ * The source: text so far or a parsed object, plus whether it can still grow.
13317
+ * These are the only enumerable fields, so two views over the same source
13318
+ * compare equal structurally however far each has been read.
13319
+ */
13320
+ chunks = [];
13321
+ object;
13322
+ sealed = false;
13323
+ #ends = [];
13324
+ #size = 0;
13325
+ #consumed = 0;
13326
+ #mode = "root";
13327
+ #escape = false;
13328
+ #keyStart = 0;
13329
+ #keyEscaped = false;
13330
+ #key = "";
13331
+ #current = null;
13332
+ #nestedEnds = [];
13333
+ #nestedInString = false;
13334
+ #invalidAt;
13335
+ #invalidValue = false;
13336
+ #entries = /* @__PURE__ */ new Map();
13337
+ #order = [];
13338
+ #reads = /* @__PURE__ */ new Map();
13339
+ /** Whether this view rejects further appends; does not scan text or register reads. */
13340
+ get isSealed() {
13341
+ return this.sealed;
13342
+ }
13343
+ /** Whether indexing or a content read found invalid JSON; unread value contents are not validated. */
13344
+ get invalid() {
13345
+ this.scan();
13346
+ return this.#mode === "invalid" || this.#invalidValue;
13347
+ }
13348
+ /**
13349
+ * Retain streamed argument text without scanning or comparing observed answers.
13350
+ * @param fragment - the text following every fragment appended before.
13351
+ */
13352
+ append(fragment) {
13353
+ if (this.sealed) throw new Error("PartialArguments: cannot append to a sealed view");
13354
+ if (fragment.length === 0) return;
13355
+ this.chunks.push(fragment);
13356
+ this.#size += fragment.length;
13357
+ this.#ends.push(this.#size);
13358
+ }
13359
+ /**
13360
+ * Reconcile a streamed prefix with authoritative complete text without joining the fragments.
13361
+ * @param text - the final argument text, which replaces missing or conflicting deltas.
13362
+ * @returns this view sealed with its caches retained when every character matches; otherwise a new sealed view.
13363
+ */
13364
+ settle(text) {
13365
+ if (this.object !== void 0 || text.length !== this.#size) return PartialArguments.fromText(text);
13366
+ let offset = 0;
13367
+ for (const chunk of this.chunks) {
13368
+ if (!text.startsWith(chunk, offset)) return PartialArguments.fromText(text);
13369
+ offset += chunk.length;
13370
+ }
13371
+ this.chunks = text.length === 0 ? [] : [text];
13372
+ this.#ends = text.length === 0 ? [] : [text.length];
13373
+ this.sealed = true;
13374
+ return this;
13375
+ }
13376
+ /**
13377
+ * Compare observed answers and advance their publication baseline. Unread views remain unscanned.
13378
+ * @returns whether any observed answer changed since its first read or the preceding refresh.
13379
+ */
13380
+ refresh() {
13381
+ if (this.#reads.size === 0) return false;
13382
+ this.scan();
13383
+ let changed = false;
13384
+ let completions = false;
13385
+ for (const read of this.#reads.values()) {
13386
+ if (read.completion) {
13387
+ completions = true;
13388
+ continue;
13389
+ }
13390
+ changed = this.refreshRead(read) || changed;
13391
+ }
13392
+ if (completions) {
13393
+ for (const read of this.#reads.values()) if (read.completion) changed = this.refreshRead(read) || changed;
13394
+ }
13395
+ if (this.sealed) this.#reads.clear();
13396
+ return changed;
13397
+ }
13398
+ refreshRead(read) {
13399
+ const now = read.answer();
13400
+ if (Object.is(now, read.last)) return false;
13401
+ read.last = now;
13402
+ return true;
13403
+ }
13404
+ /**
13405
+ * Check whether no further fields can arrive.
13406
+ * @returns whether the outer object closed, indexing failed, or the view is sealed; unread values are not validated.
13407
+ */
13408
+ closed() {
13409
+ return this.remember("closed", "", () => this.closedNow());
13410
+ }
13411
+ /**
13412
+ * List discovered fields in first-appearance order.
13413
+ * @returns top-level keys seen so far, in first-appearance order.
13414
+ */
13415
+ keys() {
13416
+ return this.remember("keys", "", () => this.keysNow(), (keys) => keys.length);
13417
+ }
13418
+ /**
13419
+ * Check whether a top-level field has appeared.
13420
+ * @param key - argument name.
13421
+ * @returns whether the field has appeared (a string opened or another value began).
13422
+ */
13423
+ has(key) {
13424
+ return this.remember("has", key, () => this.hasNow(key));
13425
+ }
13426
+ /**
13427
+ * Check whether a field's closing delimiter has arrived, without validating its contents.
13428
+ * @param key - argument name.
13429
+ * @returns whether its delimiter arrived and no content reader has reported an error for this value.
13430
+ */
13431
+ complete(key) {
13432
+ return this.remember("complete", key, () => this.completeNow(key));
13433
+ }
13434
+ /**
13435
+ * Read string length without materializing its text.
13436
+ * @param key - argument name.
13437
+ * @param options - change granularity for a streaming string.
13438
+ * @returns decoded UTF-16 length of the string field so far; undefined when absent or not a string.
13439
+ */
13440
+ stringLength(key, options) {
13441
+ const step = Math.max(1, Math.floor(options?.step ?? 1));
13442
+ const offset = options?.offset ?? 0;
13443
+ return this.remember(`length:${step}:${offset}`, key, () => this.lengthNow(key), (length) => length === void 0 ? void 0 : Math.ceil((length + offset) / step));
13444
+ }
13445
+ /**
13446
+ * Check a string against a decoded UTF-16 length limit without materializing it.
13447
+ * @param key - argument name.
13448
+ * @param maxLength - decoded UTF-16 limit, floored to at least zero.
13449
+ * @returns whether the string is longer than the limit; false when absent or not a string.
13450
+ */
13451
+ stringExceeds(key, maxLength) {
13452
+ const limit = Math.max(0, Math.floor(maxLength));
13453
+ return this.remember(`exceeds:${limit}`, key, () => (this.lengthNow(key, limit + 1) ?? 0) > limit);
13454
+ }
13455
+ /**
13456
+ * Read a decoded string, including a streaming prefix.
13457
+ * @param key - argument name.
13458
+ * @returns the string field's decoded text so far; undefined when absent or not a string.
13459
+ */
13460
+ text(key) {
13461
+ return this.remember("text", key, () => this.textNow(key));
13462
+ }
13463
+ /**
13464
+ * Read at most the first decoded UTF-16 units of a string.
13465
+ * @param key - argument name.
13466
+ * @param maxLength - maximum decoded UTF-16 length, floored to at least one.
13467
+ * @returns the bounded string prefix; undefined when absent or not a string.
13468
+ */
13469
+ textPrefix(key, maxLength) {
13470
+ const limit = Math.max(1, Math.floor(maxLength));
13471
+ return this.remember(`prefix:${limit}`, key, () => this.textPrefixNow(key, limit));
13472
+ }
13473
+ /**
13474
+ * Read a completed non-string argument.
13475
+ * @param key - argument name.
13476
+ * @returns the parsed non-string value once it closed; undefined while open, absent, or a string.
13477
+ */
13478
+ value(key) {
13479
+ return this.remember("value", key, () => this.valueNow(key));
13480
+ }
13481
+ /** Answer a question and, on a streaming view, remember it for change detection. */
13482
+ remember(kind, key, read, comparison) {
13483
+ this.scan();
13484
+ const result = read();
13485
+ if (!this.sealed) {
13486
+ const id = `${kind}/${key}`;
13487
+ if (!this.#reads.has(id)) this.#reads.set(id, {
13488
+ completion: kind === "complete",
13489
+ answer: comparison === void 0 ? read : () => comparison(read()),
13490
+ last: comparison === void 0 ? result : comparison(result)
13491
+ });
13492
+ }
13493
+ return result;
13494
+ }
13495
+ closedNow() {
13496
+ return this.sealed || this.#mode === "closed" || this.#mode === "invalid";
13497
+ }
13498
+ keysNow() {
13499
+ return this.object === void 0 ? this.#order : Object.keys(this.object);
13500
+ }
13501
+ hasNow(key) {
13502
+ return this.object === void 0 ? this.#entries.has(key) : Object.hasOwn(this.object, key);
13503
+ }
13504
+ completeNow(key) {
13505
+ if (this.object !== void 0) return Object.hasOwn(this.object, key);
13506
+ const entry = this.#entries.get(key);
13507
+ return entry !== void 0 && entry.end >= 0 && (entry.kind === "string" ? entry.invalidAt === void 0 : !entry.invalid);
13508
+ }
13509
+ lengthNow(key, limit = Number.POSITIVE_INFINITY) {
13510
+ if (this.object !== void 0) {
13511
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
13512
+ return typeof field === "string" ? field.length : void 0;
13513
+ }
13514
+ const entry = this.#entries.get(key);
13515
+ if (entry?.kind !== "string") return void 0;
13516
+ if (entry.text !== void 0 && entry.text.at === entry.end) return entry.text.length;
13517
+ const read = entry.length ??= {
13518
+ at: entry.start,
13519
+ length: 0,
13520
+ text: ""
13521
+ };
13522
+ this.readString(entry, read, limit, false);
13523
+ return read.length;
13524
+ }
13525
+ textNow(key) {
13526
+ if (this.object !== void 0) {
13527
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
13528
+ return typeof field === "string" ? field : void 0;
13529
+ }
13530
+ const entry = this.#entries.get(key);
13531
+ if (entry?.kind !== "string") return void 0;
13532
+ if (entry.text === void 0 && entry.end >= 0 && entry.needsDecoding && entry.invalidAt === void 0) {
13533
+ let text;
13534
+ try {
13535
+ text = JSON.parse(`"${this.slice(entry.start, entry.end)}"`);
13536
+ } catch (_error) {}
13537
+ if (text !== void 0) entry.text = {
13538
+ at: entry.end,
13539
+ length: text.length,
13540
+ text
13541
+ };
13542
+ }
13543
+ const read = entry.text ??= {
13544
+ at: entry.start,
13545
+ length: 0,
13546
+ text: ""
13547
+ };
13548
+ this.readString(entry, read, Number.POSITIVE_INFINITY, true);
13549
+ return read.text;
13550
+ }
13551
+ textPrefixNow(key, maxLength) {
13552
+ if (this.object !== void 0) {
13553
+ const field = Object.hasOwn(this.object, key) ? this.object[key] : void 0;
13554
+ return typeof field === "string" ? field.slice(0, maxLength) : void 0;
13555
+ }
13556
+ const entry = this.#entries.get(key);
13557
+ if (entry?.kind !== "string") return void 0;
13558
+ const prefixes = entry.prefixes ??= /* @__PURE__ */ new Map();
13559
+ let read = prefixes.get(maxLength);
13560
+ if (read === void 0) {
13561
+ read = {
13562
+ at: entry.start,
13563
+ length: 0,
13564
+ text: ""
13565
+ };
13566
+ prefixes.set(maxLength, read);
13567
+ }
13568
+ this.readString(entry, read, maxLength, true);
13569
+ return read.text;
13570
+ }
13571
+ valueNow(key) {
13572
+ if (this.object !== void 0) {
13573
+ if (!Object.hasOwn(this.object, key)) return void 0;
13574
+ const field = this.object[key];
13575
+ return typeof field === "string" ? void 0 : field;
13576
+ }
13577
+ const entry = this.#entries.get(key);
13578
+ if (entry?.kind !== "value" || entry.end < 0 || entry.invalid) return void 0;
13579
+ if (entry.parsed === void 0) try {
13580
+ entry.parsed = JSON.parse(this.slice(entry.start, entry.end));
13581
+ } catch (_error) {
13582
+ entry.invalid = true;
13583
+ this.#invalidValue = true;
13584
+ }
13585
+ return entry.parsed;
13586
+ }
13587
+ chunkAt(at) {
13588
+ let low = 0;
13589
+ let high = this.#ends.length;
13590
+ while (low < high) {
13591
+ const mid = low + high >>> 1;
13592
+ if (this.#ends[mid] <= at) low = mid + 1;
13593
+ else high = mid;
13594
+ }
13595
+ return low;
13596
+ }
13597
+ /** Materialize only a requested range, never the cumulative source. */
13598
+ slice(start, end) {
13599
+ if (start >= end) return "";
13600
+ const first = this.chunkAt(start);
13601
+ const last = this.chunkAt(end - 1);
13602
+ const base = first === 0 ? 0 : this.#ends[first - 1];
13603
+ if (first === last) return this.chunks[first].slice(start - base, end - base);
13604
+ const parts = [this.chunks[first].slice(start - base)];
13605
+ for (let i = first + 1; i < last; i++) parts.push(this.chunks[i]);
13606
+ parts.push(this.chunks[last].slice(0, end - this.#ends[last - 1]));
13607
+ return parts.join("");
13608
+ }
13609
+ readString(entry, read, limit, materialize) {
13610
+ const end = Math.min(entry.end < 0 ? this.#consumed : entry.end, entry.invalidAt ?? Number.POSITIVE_INFINITY, this.#invalidAt ?? Number.POSITIVE_INFINITY);
13611
+ if (!entry.needsDecoding) {
13612
+ const length = Math.min(end - read.at, limit - read.length);
13613
+ if (length <= 0) return;
13614
+ if (materialize) read.text += this.slice(read.at, read.at + length);
13615
+ read.at += length;
13616
+ read.length += length;
13617
+ return;
13618
+ }
13619
+ let chunkIndex = this.chunkAt(read.at);
13620
+ while (read.at < end && read.length < limit) {
13621
+ const base = chunkIndex === 0 ? 0 : this.#ends[chunkIndex - 1];
13622
+ const chunk = this.chunks[chunkIndex];
13623
+ const remaining = chunk.slice(read.at - base, Math.min(chunk.length, end - base));
13624
+ const boundary = remaining.search(CONTENT_ESCAPE);
13625
+ const length = Math.min(boundary < 0 ? remaining.length : boundary, limit - read.length);
13626
+ if (length > 0) {
13627
+ if (materialize) read.text += remaining.slice(0, length);
13628
+ read.at += length;
13629
+ read.length += length;
13630
+ if (read.at === base + chunk.length) chunkIndex++;
13631
+ continue;
13632
+ }
13633
+ const type = remaining.length > 1 ? remaining[1] : read.at + 1 < end ? this.chunks[chunkIndex + 1][0] : void 0;
13634
+ let decoded;
13635
+ let width = 2;
13636
+ if (remaining[0] === "\\" && type === void 0 && entry.end < 0) return;
13637
+ if (remaining[0] === "\\" && type === "u") {
13638
+ const hex = this.slice(read.at + 2, Math.min(end, read.at + 6));
13639
+ let valid = true;
13640
+ for (let i = 0; i < hex.length; i++) if (!isHex(hex[i])) valid = false;
13641
+ if (valid) {
13642
+ if (hex.length < 4 && entry.end < 0) return;
13643
+ if (hex.length === 4) decoded = String.fromCharCode(Number.parseInt(hex, 16));
13644
+ }
13645
+ width = 6;
13646
+ } else if (remaining[0] === "\\" && type !== void 0) decoded = SIMPLE_ESCAPES[type];
13647
+ if (decoded === void 0) {
13648
+ entry.invalidAt = read.at;
13649
+ this.#invalidValue = true;
13650
+ return;
13651
+ }
13652
+ if (materialize) read.text += decoded;
13653
+ read.length++;
13654
+ read.at += width;
13655
+ while (chunkIndex < this.chunks.length && read.at >= this.#ends[chunkIndex]) chunkIndex++;
13656
+ }
13657
+ }
13658
+ /** Locate new field ranges without decoding or parsing their contents. */
13659
+ scan() {
13660
+ if (this.object !== void 0 || this.#consumed === this.#size) return;
13661
+ for (let i = this.chunkAt(this.#consumed); i < this.chunks.length && this.#invalidAt === void 0; i++) {
13662
+ const pending = this.chunks[i];
13663
+ const base = i === 0 ? 0 : this.#ends[i - 1];
13664
+ for (let index = this.#consumed - base; index < pending.length && this.#mode !== "invalid"; index++) {
13665
+ if (this.#mode === "string" || this.#mode === "nested" && this.#nestedInString) {
13666
+ const end = this.stringBoundary(pending, index);
13667
+ this.#consumed += end - index;
13668
+ index = end;
13669
+ if (index === pending.length) break;
13670
+ }
13671
+ this.step(pending[index], this.#consumed);
13672
+ this.#consumed++;
13673
+ }
13674
+ }
13675
+ }
13676
+ /** Only raw quotes and their preceding backslash runs can terminate a string. */
13677
+ stringBoundary(fragment, start) {
13678
+ let at = start;
13679
+ while (true) {
13680
+ const quote = fragment.indexOf("\"", at);
13681
+ const end = quote < 0 ? fragment.length : quote;
13682
+ if (this.#mode === "string") {
13683
+ const entry = this.#current;
13684
+ if (!entry.needsDecoding && CONTENT_ESCAPE.test(fragment.slice(at, end))) entry.needsDecoding = true;
13685
+ }
13686
+ let slashStart = end;
13687
+ while (slashStart > at && fragment[slashStart - 1] === "\\") slashStart--;
13688
+ const escaped = (end - slashStart) % 2 === 1 !== (slashStart === at && this.#escape);
13689
+ this.#escape = quote < 0 && escaped;
13690
+ if (quote < 0 || !escaped) return end;
13691
+ at = quote + 1;
13692
+ }
13693
+ }
13694
+ step(c, at) {
13695
+ switch (this.#mode) {
13696
+ case "root":
13697
+ if (isWhitespace(c)) return;
13698
+ if (c === "{") {
13699
+ this.#mode = "key-or-end";
13700
+ return;
13701
+ }
13702
+ this.fail();
13703
+ return;
13704
+ case "key-or-end":
13705
+ if (isWhitespace(c)) return;
13706
+ if (c === "}") {
13707
+ this.#mode = "closed";
13708
+ return;
13709
+ }
13710
+ if (c === "\"") {
13711
+ this.beginKey(at);
13712
+ return;
13713
+ }
13714
+ this.fail();
13715
+ return;
13716
+ case "key-only":
13717
+ if (isWhitespace(c)) return;
13718
+ if (c === "\"") {
13719
+ this.beginKey(at);
13720
+ return;
13721
+ }
13722
+ this.fail();
13723
+ return;
13724
+ case "key":
13725
+ this.stepKey(c, at);
13726
+ return;
13727
+ case "colon":
13728
+ if (isWhitespace(c)) return;
13729
+ if (c === ":") {
13730
+ this.#mode = "value";
13731
+ return;
13732
+ }
13733
+ this.fail();
13734
+ return;
13735
+ case "value":
13736
+ this.beginValue(c, at);
13737
+ return;
13738
+ case "string": {
13739
+ const entry = this.#current;
13740
+ entry.end = at;
13741
+ this.#current = null;
13742
+ this.#mode = "comma-or-end";
13743
+ return;
13744
+ }
13745
+ case "scalar":
13746
+ this.stepScalar(c, at);
13747
+ return;
13748
+ case "nested":
13749
+ this.stepNested(c, at);
13750
+ return;
13751
+ case "comma-or-end":
13752
+ if (isWhitespace(c)) return;
13753
+ if (c === ",") {
13754
+ this.#mode = "key-only";
13755
+ return;
13756
+ }
13757
+ if (c === "}") {
13758
+ this.#mode = "closed";
13759
+ return;
13760
+ }
13761
+ this.fail();
13762
+ return;
13763
+ case "closed":
13764
+ if (isWhitespace(c)) return;
13765
+ this.fail();
13766
+ return;
13767
+ case "invalid": return;
13768
+ default: assertNever$1(this.#mode);
13769
+ }
13770
+ }
13771
+ fail() {
13772
+ this.#invalidAt = this.#consumed;
13773
+ this.#mode = "invalid";
13774
+ this.#current = null;
13775
+ }
13776
+ beginKey(at) {
13777
+ this.#mode = "key";
13778
+ this.#keyStart = at + 1;
13779
+ this.#keyEscaped = false;
13780
+ this.#escape = false;
13781
+ }
13782
+ stepKey(c, at) {
13783
+ if (c < " ") {
13784
+ this.fail();
13785
+ return;
13786
+ }
13787
+ if (this.#escape) {
13788
+ this.#escape = false;
13789
+ return;
13790
+ }
13791
+ if (c === "\\") {
13792
+ this.#escape = true;
13793
+ this.#keyEscaped = true;
13794
+ return;
13795
+ }
13796
+ if (c !== "\"") return;
13797
+ const raw = this.slice(this.#keyStart, at);
13798
+ if (this.#keyEscaped) try {
13799
+ this.#key = JSON.parse(`"${raw}"`);
13800
+ } catch (_error) {
13801
+ this.fail();
13802
+ return;
13803
+ }
13804
+ else this.#key = raw;
13805
+ this.#mode = "colon";
13806
+ }
13807
+ open(entry) {
13808
+ if (!this.#entries.has(this.#key)) this.#order.push(this.#key);
13809
+ this.#entries.set(this.#key, entry);
13810
+ this.#current = entry;
13811
+ }
13812
+ beginValue(c, at) {
13813
+ if (isWhitespace(c)) return;
13814
+ if (c === "\"") {
13815
+ this.open({
13816
+ kind: "string",
13817
+ start: at + 1,
13818
+ end: -1,
13819
+ needsDecoding: false,
13820
+ invalidAt: void 0,
13821
+ length: void 0,
13822
+ text: void 0,
13823
+ prefixes: void 0
13824
+ });
13825
+ this.#escape = false;
13826
+ this.#mode = "string";
13827
+ return;
13828
+ }
13829
+ if (c === "}" || c === "," || c === ":" || c === "]") {
13830
+ this.fail();
13831
+ return;
13832
+ }
13833
+ this.open({
13834
+ kind: "value",
13835
+ start: at,
13836
+ end: -1,
13837
+ parsed: void 0,
13838
+ invalid: false
13839
+ });
13840
+ if (c === "{" || c === "[") {
13841
+ this.#mode = "nested";
13842
+ this.#nestedEnds = [c === "{" ? "}" : "]"];
13843
+ this.#nestedInString = false;
13844
+ this.#escape = false;
13845
+ return;
13846
+ }
13847
+ this.#mode = "scalar";
13848
+ }
13849
+ stepScalar(c, at) {
13850
+ if (c !== "," && c !== "}" && !isWhitespace(c)) return;
13851
+ this.closeValue(at);
13852
+ this.#mode = c === "," ? "key-only" : c === "}" ? "closed" : "comma-or-end";
13853
+ }
13854
+ stepNested(c, at) {
13855
+ if (this.#nestedInString) {
13856
+ this.#nestedInString = false;
13857
+ return;
13858
+ }
13859
+ if (c === "\"") {
13860
+ this.#nestedInString = true;
13861
+ return;
13862
+ }
13863
+ if (c === "{" || c === "[") {
13864
+ this.#nestedEnds.push(c === "{" ? "}" : "]");
13865
+ return;
13866
+ }
13867
+ if (c === "}" || c === "]") {
13868
+ if (this.#nestedEnds.pop() !== c) {
13869
+ this.fail();
13870
+ return;
13871
+ }
13872
+ if (this.#nestedEnds.length === 0) {
13873
+ this.closeValue(at + 1);
13874
+ this.#mode = "comma-or-end";
13875
+ }
13876
+ }
13877
+ }
13878
+ closeValue(end) {
13879
+ const entry = this.#current;
13880
+ entry.end = end;
13881
+ this.#current = null;
13882
+ }
13883
+ });
13884
+ /**
13885
+ * Mark an unreachable closed-union branch.
13886
+ * @param value - impossible value; an unhandled typed variant fails at the call site.
13887
+ * @param context - optional switch-site label included in the failure message.
13888
+ * @returns never; a runtime value that escaped its type always throws.
13889
+ */
13890
+ function assertNever$1(value, context) {
13891
+ const rendered = JSON.stringify(value) ?? String(value);
13892
+ throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
13893
+ }
13894
+ /** Whether a path uses a Windows drive or UNC prefix. */
13895
+ function isWindowsStylePath(value) {
13896
+ return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
13897
+ }
13898
+ /**
13899
+ * Abbreviate a POSIX home directory for display.
13900
+ * @param path - Absolute or already-short display path.
13901
+ * @param home - Host account home; absent skips abbreviation.
13902
+ * @returns `~` or `~/…` for the POSIX home and its descendants, otherwise `path`.
13903
+ */
13904
+ function abbreviateHomePath(path, home) {
13905
+ if (home === void 0 || home === "") return path;
13906
+ if (isWindowsStylePath(path) || isWindowsStylePath(home)) return path;
13907
+ const root = home.replace(/\/+$/, "");
13908
+ if (root === "" || root === "/") return path;
13909
+ if (path.replace(/\/+$/, "") === root) return "~";
13910
+ if (path.startsWith(`${root}/`)) return `~${path.slice(root.length)}`;
13911
+ return path;
13912
+ }
13913
+ /**
13914
+ * Read the final non-empty segment of a Workspace path for display.
13915
+ * Workspace-label surfaces use this helper instead of deriving another basename.
13916
+ * @param path - Workspace directory path using POSIX or Windows separators.
13917
+ * @returns the final segment, or an empty string for a separator-only path.
13918
+ */
13919
+ function workspaceTitleOf(path) {
13920
+ const trimmed = path.replace(/[/\\]+$/, "");
13921
+ const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
13922
+ return trimmed.slice(separator + 1);
13923
+ }
13924
+ /**
13925
+ * Resolve the Workspace browser group that owns one Session.
13926
+ * @param workspaces - authoritative Workspace membership.
13927
+ * @param sessionId - Session whose browser group is required.
13928
+ * @returns owning Workspace id, or {@link UNGROUPED_KEY} when no Workspace accounts for it.
13929
+ */
13930
+ function owningGroupKey(workspaces, sessionId) {
13931
+ return workspaces.find((workspace) => workspace.sessionIds.includes(sessionId))?.workspaceId ?? "";
13932
+ }
13933
+ function mainSessionId(list) {
13934
+ return Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
13935
+ }
13936
+ /**
13937
+ * Directory display label: basename of the path (both separators accepted).
13938
+ * Ungrouped-bucket fallback for surfaces without a workspace title.
13939
+ * @param cwd - directory path, or undefined for the ungrouped bucket.
13940
+ * @returns basename, the raw cwd when it has no basename, or an empty ungrouped marker.
13941
+ */
13942
+ function workspaceLabel(cwd) {
13943
+ if (cwd === void 0 || cwd === "") return "";
13944
+ const base = workspaceTitleOf(cwd);
13945
+ return base !== "" ? base : cwd;
13946
+ }
13947
+ /**
13948
+ * Project known account members by current Session recency.
13949
+ * @param sessionIds - authoritative account membership.
13950
+ * @param summaries - current Session summaries; members without a summary are omitted until it arrives.
13951
+ * @returns known members newest first, with Session identity as the deterministic tie-break.
13952
+ */
13953
+ function orderByRecency(sessionIds, summaries) {
13954
+ return sessionIds.flatMap((id) => {
13955
+ const summary = summaries[id];
13956
+ if (summary === void 0) return [];
13957
+ return [{
13958
+ id,
13959
+ rank: summary.updatedAt
13960
+ }];
13961
+ }).sort((a, b) => {
13962
+ if (a.rank !== b.rank) return b.rank - a.rank;
13963
+ return a.id < b.id ? -1 : 1;
13964
+ }).map((member) => member.id);
13965
+ }
13966
+ /**
13967
+ * Reconcile a browser-local manual order with current account membership.
13968
+ * New ordinary forks precede their sources without changing saved entries' relative order.
13969
+ * @param memberIds - authoritative account membership.
13970
+ * @param savedOrder - previously saved browser-local order.
13971
+ * @param summaries - current Session metadata; unknown new members wait for their summaries.
13972
+ * @param rowState - global pin and archive membership; only account members can supplement the order.
13973
+ * @returns saved relative positions plus missing members ordered by pin, fork source, recency, and archive status.
13974
+ */
13975
+ function reconcileManualOrder(memberIds, savedOrder, summaries, rowState) {
13976
+ const members = new Map(memberIds.map((id) => [id, id]));
13977
+ const included = /* @__PURE__ */ new Set();
13978
+ const ordered = [];
13979
+ for (const key of savedOrder ?? []) {
13980
+ const id = members.get(key);
13981
+ if (id === void 0 || included.has(key)) continue;
13982
+ ordered.push(id);
13983
+ included.add(key);
13984
+ }
13985
+ const archived = new Set(rowState?.archivedSessionIds);
13986
+ const pins = [];
13987
+ for (const sessionId of rowState?.pinnedSessionIds ?? []) {
13988
+ const id = members.get(sessionId);
13989
+ if (id === void 0 || included.has(id) || archived.has(id) || summaries[id] === void 0) continue;
13990
+ pins.push(id);
13991
+ included.add(id);
13992
+ }
13993
+ const ordinary = [];
13994
+ const archives = [];
13995
+ for (const id of orderByRecency([...members.values()].filter((id$1) => !included.has(id$1)), summaries)) if (archived.has(id)) archives.push(id);
13996
+ else ordinary.push(id);
13997
+ const result = [
13998
+ ...pins,
13999
+ ...ordered,
14000
+ ...ordinary,
14001
+ ...archives
14002
+ ];
14003
+ const pending = new Set(ordinary);
14004
+ const placeFork = (id) => {
14005
+ if (!pending.delete(id)) return;
14006
+ const parentId = summaries[id]?.parentId;
14007
+ if (parentId === void 0 || parentId === id || !result.includes(parentId)) return;
14008
+ placeFork(parentId);
14009
+ result.splice(result.indexOf(id), 1);
14010
+ result.splice(result.indexOf(parentId), 0, id);
14011
+ };
14012
+ for (const id of [...ordinary].reverse()) placeFork(id);
14013
+ return result;
14014
+ }
14015
+ /**
13348
14016
  * Keep the selected provisional New Session ahead of either base order.
13349
14017
  * @param order - recency or reconciled manual order.
13350
14018
  * @param currentBlank - selected blank Session in this account, when present.
@@ -13760,6 +14428,7 @@ const official$2 = ((require$1) => {
13760
14428
  sessions;
13761
14429
  view;
13762
14430
  notify;
14431
+ /** The latest in-flight connection per Workspace; a request joins it only when both name the same preset. */
13763
14432
  connecting = /* @__PURE__ */ new Map();
13764
14433
  lifetime = new AbortController();
13765
14434
  selection = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({}, { persist: { name: "dsh.sessions.current" } });
@@ -13774,6 +14443,7 @@ const official$2 = ((require$1) => {
13774
14443
  */
13775
14444
  constructor(ctx, directoryPicker, workspaces, sessions, view, notify) {
13776
14445
  super(ctx, "uiWorkspace");
14446
+ this.managedWorktreeGuard = ctx.get("__dshGitWorktreeManagedGuard");
13777
14447
  this.directoryPicker = directoryPicker;
13778
14448
  this.workspaces = workspaces;
13779
14449
  this.sessions = sessions;
@@ -13790,46 +14460,90 @@ const official$2 = ((require$1) => {
13790
14460
  };
13791
14461
  }, "ui-workspace: Workspace navigation policy");
13792
14462
  }
13793
- async connectWorkspace(workspaceId) {
14463
+ connectWorkspace(workspaceId, options = {}) {
14464
+ return this.connectBlank(workspaceId, options);
14465
+ }
14466
+ /**
14467
+ * @param reuse - preset for the reused or created blank; absent creates a fresh Session.
14468
+ */
14469
+ async connectBlank(workspaceId, reuse) {
13794
14470
  const workspace = this.workspaces.list.getSnapshot().items.find((item) => item.workspaceId === workspaceId);
13795
14471
  if (workspace === void 0) throw new Error(`uiWorkspace.connectWorkspace: unknown workspace ${workspaceId}`);
14472
+ if (reuse === void 0) return this.sessions.create({ workspaceId });
13796
14473
  const inflight = this.connecting.get(workspaceId);
13797
- if (inflight !== void 0) return inflight;
13798
- const attempt$1 = this.reuseOrCreateBlank(workspace).finally(() => {
13799
- this.connecting.delete(workspaceId);
14474
+ if (inflight !== void 0 && inflight.agentPreset === reuse.agentPreset) return inflight.attempt;
14475
+ const attempt$1 = this.createInWorkspace(workspace, reuse).finally(() => {
14476
+ if (this.connecting.get(workspaceId)?.attempt === attempt$1) this.connecting.delete(workspaceId);
14477
+ });
14478
+ this.connecting.set(workspaceId, {
14479
+ agentPreset: reuse.agentPreset,
14480
+ attempt: attempt$1
13800
14481
  });
13801
- this.connecting.set(workspaceId, attempt$1);
13802
14482
  return attempt$1;
13803
14483
  }
13804
- reuseOrCreateBlank(workspace) {
14484
+ createInWorkspace(workspace, options) {
13805
14485
  const archived = this.workspaces.list.getSnapshot().archivedSessionIds;
13806
14486
  const sessions = this.sessions.list.getSnapshot();
13807
- for (const id of sessions.ids) {
14487
+ const blank = sessions.ids.find((id) => {
13808
14488
  const summary = sessions.byId[id];
13809
- if (summary === void 0 || !summary.blank || summary.cwd !== workspace.path || !workspace.sessionIds.includes(id) || archived.includes(id)) continue;
13810
- return this.reuseBlank(workspace.workspaceId, id);
13811
- }
13812
- return this.sessions.create({ workspaceId: workspace.workspaceId });
14489
+ return summary !== void 0 && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(id) && !archived.includes(id);
14490
+ });
14491
+ return this.createNamed({
14492
+ workspaceId: workspace.workspaceId,
14493
+ ...blank === void 0 ? {} : { sessionId: blank },
14494
+ ...options.agentPreset === void 0 ? {} : { agentPreset: options.agentPreset }
14495
+ });
13813
14496
  }
13814
- async reuseBlank(workspaceId, sessionId) {
14497
+ /**
14498
+ * Create through the Host. A named blank the Host declined may still read as
14499
+ * blank from stale cached list metadata, which hides it from the Session tree;
14500
+ * its projections are reloaded so the row reflects its log.
14501
+ */
14502
+ async createNamed(request) {
14503
+ const sessionId = await this.sessions.create(request);
14504
+ const named = request.sessionId;
14505
+ if (named !== void 0 && named !== sessionId) this.sessions.refreshProjections(named).catch((reason) => {
14506
+ console.warn("declined blank Session projection reload failed:", reason);
14507
+ });
14508
+ return sessionId;
14509
+ }
14510
+ /**
14511
+ * Reopen a saved blank Session under the preset its log recorded, or under
14512
+ * the Host default once that preset no longer activates. Listed values are
14513
+ * cached hints that can lag the log after an unclean Host exit, so the
14514
+ * Session's projections are reloaded before the preset is read; a Session
14515
+ * that still awaits a format migration reloads only those cached hints.
14516
+ */
14517
+ async restoreBlank(workspaceId, sessionId) {
14518
+ await this.sessions.refreshProjections(sessionId);
14519
+ const recorded = this.sessions.list.getSnapshot().byId[sessionId]?.projectionValues?.agentPreset;
14520
+ const request = {
14521
+ workspaceId,
14522
+ sessionId
14523
+ };
14524
+ if (typeof recorded !== "string") return this.createNamed(request);
13815
14525
  try {
13816
- return await this.sessions.create({
13817
- workspaceId,
13818
- sessionId
14526
+ return await this.createNamed({
14527
+ ...request,
14528
+ agentPreset: recorded
13819
14529
  });
13820
14530
  } catch (error) {
13821
- if (sessionCreateErrorOf(error)?.rpcError.code !== "session/writer-held") throw error;
13822
- return this.sessions.create({ workspaceId });
14531
+ const code = sessionCreateErrorOf(error)?.rpcError.code;
14532
+ if (code !== "agent-preset/not-found" && code !== "agent-preset/invalid") throw error;
14533
+ return this.createNamed(request);
13823
14534
  }
13824
14535
  }
13825
14536
  openSession(target) {
13826
14537
  this.replaceMain(target, this.lifetime.signal, "reveal");
13827
14538
  }
13828
- async openWorkspace(workspaceId, beforeOpen) {
14539
+ openWorkspace(workspaceId, beforeOpen, options = {}) {
14540
+ return this.openWorkspaceSession(workspaceId, beforeOpen, options);
14541
+ }
14542
+ async openWorkspaceSession(workspaceId, beforeOpen, reuse) {
13829
14543
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
13830
14544
  let sessionId;
13831
14545
  try {
13832
- sessionId = await this.connectWorkspace(workspaceId);
14546
+ sessionId = await this.connectBlank(workspaceId, reuse);
13833
14547
  } catch (error) {
13834
14548
  if (!navigation.aborted) this.notify({
13835
14549
  kind: "createFailed",
@@ -13840,28 +14554,58 @@ const official$2 = ((require$1) => {
13840
14554
  if (navigation.aborted) return;
13841
14555
  this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
13842
14556
  }
13843
- async forkSession(sessionId, onCreated) {
14557
+ async forkSession(sessionId, onCreated, options) {
14558
+ if (this.managedWorktreeGuard.blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
13844
14559
  return this.sessions.fork({
13845
14560
  sessionId,
13846
14561
  increaseTitle: true,
13847
- ...onCreated === void 0 ? {} : { onCreated }
14562
+ ...onCreated === void 0 ? {} : { onCreated },
14563
+ ...options?.allowMigration === void 0 ? {} : { allowMigration: options.allowMigration }
13848
14564
  });
13849
14565
  }
13850
- startSession(workspaceId) {
14566
+ startSession(workspaceId, options) {
14567
+ const { agentPreset, ...draft } = options ?? {};
14568
+ const draftOptions = options === void 0 ? void 0 : draft;
14569
+ const initializeDraft = draftOptions !== void 0 && (draftOptions.prompt !== void 0 || draftOptions.clearPreviousDraft === true);
13851
14570
  const workspace = this.workspaces.list.getSnapshot();
13852
14571
  const sessions = this.sessions.list.getSnapshot();
13853
14572
  const current = this.mainReference?.sessionId;
13854
14573
  const currentWorkspaceId = current === void 0 ? void 0 : workspace.items.find((item) => item.sessionIds.includes(current))?.workspaceId;
13855
14574
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
13856
14575
  const target = workspaceId ?? currentWorkspaceId ?? recent;
14576
+ if (target !== void 0 && this.managedWorktreeGuard.blockWorkspace(target)) return;
13857
14577
  if (target === void 0) {
14578
+ if (initializeDraft) {
14579
+ this.notify({
14580
+ kind: "createFailed",
14581
+ message: this.ctx.locale.bind("workspace")("draft.workspaceRequired")
14582
+ });
14583
+ return;
14584
+ }
13858
14585
  this.clearMain();
13859
14586
  return;
13860
14587
  }
13861
- this.openWorkspace(target).catch((reason) => {
14588
+ (draftOptions === void 0 ? this.openWorkspaceSession(target, void 0, void 0) : this.openWorkspace(target, initializeDraft ? (id) => {
14589
+ const binding = this.sessions.binding(id);
14590
+ if (binding === void 0) this.draftPreparationFailed();
14591
+ this.prepareDraft(binding, draftOptions);
14592
+ } : void 0, agentPreset === void 0 ? {} : { agentPreset })).catch((reason) => {
13862
14593
  console.warn("new session failed:", reason);
13863
14594
  });
13864
14595
  }
14596
+ prepareDraft(binding, options) {
14597
+ const conversation = this.ctx.get("conversation");
14598
+ if (conversation === void 0) this.draftPreparationFailed();
14599
+ if (conversation.input.requestDraftInitialization(binding, options) === "blocked") this.draftPreparationFailed();
14600
+ }
14601
+ draftPreparationFailed() {
14602
+ const message = this.ctx.locale.bind("workspace")("draft.initializationFailed");
14603
+ this.notify({
14604
+ kind: "createFailed",
14605
+ message
14606
+ });
14607
+ throw new Error(message);
14608
+ }
13865
14609
  async archiveSession(sessionId, options = {}) {
13866
14610
  await this.workspaces.archiveSession(sessionId, options);
13867
14611
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
@@ -13940,7 +14684,7 @@ const official$2 = ((require$1) => {
13940
14684
  }
13941
14685
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
13942
14686
  let sessionId;
13943
- if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.reuseBlank(workspace.workspaceId, summary.id);
14687
+ if (summary !== void 0 && workspace !== void 0 && summary.cwd === workspace.path && !workspaces.archivedSessionIds.includes(summary.id)) sessionId = await this.restoreBlank(workspace.workspaceId, summary.id);
13944
14688
  let target = workspace?.workspaceId ?? recentWorkspace(workspaces.items, sessions.byId);
13945
14689
  if (target === void 0 && workspaces.items.length === 0 && sessions.ids.length === 0) {
13946
14690
  const prepared = await this.initializeDefaultWorkspace(navigation);
@@ -14058,7 +14802,7 @@ const official$2 = ((require$1) => {
14058
14802
  function workspaceDisplayTitle(title, localizedDefault) {
14059
14803
  return title === "default-workspace" ? localizedDefault : title;
14060
14804
  }
14061
- const css$3 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover,.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.YDXeBa_searchResultRow:hover,.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.YDXeBa_searchResultWorkspace:only-child{max-width:100%}.YDXeBa_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.YDXeBa_projectRow{box-sizing:border-box;align-items:center;height:34px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{gap:0;height:32px}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}.YDXeBa_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.YDXeBa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.YDXeBa_folderActive{color:var(--dsw-alias-state-business-primary)}.YDXeBa_projectRow .YDXeBa_chevron{display:none}.YDXeBa_projectRow:hover .YDXeBa_chevron{display:inline-flex}.YDXeBa_projectRow:hover .YDXeBa_folder{display:none}.YDXeBa_arrow{transition:transform .15s var(--ds-ease-in-out)}.YDXeBa_arrowOpen{transform:rotate(90deg)}.YDXeBa_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.YDXeBa_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}.YDXeBa_sessionRow .YDXeBa_title{flex:1}.YDXeBa_sessionRow .YDXeBa_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}.YDXeBa_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.YDXeBa_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}.YDXeBa_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.YDXeBa_sessionRow.YDXeBa_archived .YDXeBa_title,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultTitle,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultWorkspace,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultSnippet{color:var(--dsw-alias-label-caption)}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:10px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_searchResultRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_searchResultHeading .YDXeBa_rowActions{margin-left:auto}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time,.YDXeBa_sessionRow:hover .YDXeBa_pinIndicator,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_pinIndicator{display:none}@media (hover:hover){.YDXeBa_sessionRow:hover .YDXeBa_title,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_title{text-overflow:clip}}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore,.YDXeBa_sessionRow.YDXeBa_dropAfter{position:relative}.YDXeBa_sessionRow.YDXeBa_dropBefore:before,.YDXeBa_sessionRow.YDXeBa_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.YDXeBa_sessionRow.YDXeBa_dropBefore:before{top:-7px}.YDXeBa_sessionRow.YDXeBa_dropAfter:after{bottom:-7px}.YDXeBa_hoverContent{flex-direction:column;gap:8px;display:flex}.YDXeBa_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.YDXeBa_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.YDXeBa_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.YDXeBa_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.YDXeBa_hoverArchived svg{flex-shrink:0;margin:0 -4px}.YDXeBa_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.YDXeBa_iconButton:hover{color:var(--dsw-alias-label-primary)}.YDXeBa_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.YDXeBa_arrow{transition:none;animation:none}}";
14805
+ const css$3 = ".YDXeBa_projectRow,.YDXeBa_sessionRow{border-radius:var(--dsw-radius-md);padding:0 8px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-primary);align-items:center;gap:6px;padding-inline-start:calc(8px + var(--dsh-workspace-indent,0px));display:flex}.YDXeBa_projectRow:hover,.YDXeBa_sessionRow:hover,.YDXeBa_sessionRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultRow{box-sizing:border-box;border-radius:var(--dsw-radius-lg);cursor:pointer;text-align:left;width:100%;min-height:48px;color:var(--dsw-alias-label-primary);background:0 0;border:none;flex-direction:column;align-items:stretch;padding:4px 8px;display:flex}.YDXeBa_searchResultRow:hover,.YDXeBa_searchResultRow.YDXeBa_selected{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_searchResultHeading{align-items:center;min-width:0;display:flex}.YDXeBa_searchResultTitle{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;margin-left:4px;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_searchResultMeta{align-items:center;gap:6px;min-width:0;margin-left:20px;display:flex}.YDXeBa_searchResultWorkspace,.YDXeBa_searchResultSnippet{text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:17px;overflow:hidden}.YDXeBa_searchResultWorkspace{max-width:40%;color:var(--dsw-alias-label-tertiary);flex:none}.YDXeBa_searchResultWorkspace:only-child{max-width:100%}.YDXeBa_searchResultSnippet{min-width:0;color:var(--dsw-alias-label-secondary);flex:1}.YDXeBa_projectRow{box-sizing:border-box;align-items:center;height:34px}.YDXeBa_projectRow .YDXeBa_rowActions{height:20px}.YDXeBa_sessionRow{gap:0;height:32px}.YDXeBa_sessionRow .YDXeBa_title{margin:0 6px 0 4px}.YDXeBa_slot{width:16px;height:20px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.YDXeBa_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.YDXeBa_folderActive{color:var(--dsw-alias-state-business-primary)}.YDXeBa_projectRow .YDXeBa_chevron{display:none}.YDXeBa_projectRow:hover .YDXeBa_chevron{display:inline-flex}.YDXeBa_projectRow:hover .YDXeBa_folder{display:none}.YDXeBa_arrow{transition:transform .15s var(--ds-ease-in-out)}.YDXeBa_arrowOpen{transform:rotate(90deg)}.YDXeBa_projectText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.YDXeBa_title{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:14px;line-height:20px;overflow:hidden}.YDXeBa_renameInput{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-button-elevated-fill);min-width:0;color:inherit;outline:none;padding:0 2px;font-size:14px;line-height:20px}.YDXeBa_sessionRow .YDXeBa_title{flex:1}.YDXeBa_sessionRow .YDXeBa_title[data-scrolled]{mask-image:linear-gradient(90deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-clipped]{mask-image:linear-gradient(270deg,#0000,#000 12px)}.YDXeBa_sessionRow .YDXeBa_title[data-scrolled][data-clipped]{mask-image:linear-gradient(90deg,#0000,#000 12px calc(100% - 12px),#0000)}.YDXeBa_meta{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden}.YDXeBa_time{color:var(--dsw-alias-label-tertiary);flex:none;font-size:10px;line-height:16px}.YDXeBa_pinIndicator{width:16px;height:20px;color:var(--dsw-alias-label-caption);flex:none;justify-content:center;align-items:center;margin-left:6px;display:inline-flex}.YDXeBa_sessionRow.YDXeBa_archived .YDXeBa_title,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultTitle,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultWorkspace,.YDXeBa_searchResultRow.YDXeBa_archived .YDXeBa_searchResultSnippet{color:var(--dsw-alias-label-caption)}.YDXeBa_dot{flex:none}.YDXeBa_rowActions{flex:none;align-items:center;gap:15px;display:none}.YDXeBa_projectRow:hover .YDXeBa_rowActions,.YDXeBa_sessionRow:hover .YDXeBa_rowActions,.YDXeBa_searchResultRow:hover .YDXeBa_rowActions,.YDXeBa_projectRow.YDXeBa_menuOpen .YDXeBa_rowActions,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_rowActions{display:inline-flex}.YDXeBa_searchResultHeading .YDXeBa_rowActions{margin-left:auto}.YDXeBa_sessionRow:hover .YDXeBa_time,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_time,.YDXeBa_sessionRow:hover .YDXeBa_pinIndicator,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_pinIndicator{display:none}@media (hover:hover){.YDXeBa_sessionRow:hover .YDXeBa_title,.YDXeBa_sessionRow.YDXeBa_menuOpen .YDXeBa_title{text-overflow:clip}}.YDXeBa_projectRow.YDXeBa_menuOpen,.YDXeBa_sessionRow.YDXeBa_menuOpen{background:var(--dsw-alias-interactive-bg-hover)}.YDXeBa_sessionRow.YDXeBa_dropBefore,.YDXeBa_sessionRow.YDXeBa_dropAfter{position:relative}.YDXeBa_sessionRow.YDXeBa_dropBefore:before,.YDXeBa_sessionRow.YDXeBa_dropAfter:after{content:\"\";z-index:1;background:linear-gradient(55deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 0 / 5px 7px no-repeat, linear-gradient(125deg, transparent calc(50% - 1px), var(--dsw-alias-state-business-primary) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) 0 5px / 5px 7px no-repeat, linear-gradient(var(--dsw-alias-state-business-primary) 0 0) 4px 5px / calc(100% - 4px) 2px no-repeat;pointer-events:none;height:12px;position:absolute;left:0;right:4px}.YDXeBa_sessionRow.YDXeBa_dropBefore:before{top:-7px}.YDXeBa_sessionRow.YDXeBa_dropAfter:after{bottom:-7px}.YDXeBa_hoverContent{flex-direction:column;gap:8px;display:flex}.YDXeBa_hoverTitle{color:#fff;overflow-wrap:break-word;font-size:14px;line-height:20px}.YDXeBa_hoverPath{color:#cfd3d6;word-break:break-all;font-size:12px;line-height:16px}.YDXeBa_hoverTime{color:#cfd3d6;font-size:12px;line-height:16px}.YDXeBa_hoverStatus{color:#adb2b8;align-items:center;gap:8px;font-size:12px;line-height:20px;display:flex}.YDXeBa_hoverArchived svg{flex-shrink:0;margin:0 -4px}.YDXeBa_iconButton{border-radius:var(--dsw-radius-xs);cursor:pointer;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.YDXeBa_iconButton:hover{color:var(--dsw-alias-label-primary)}.YDXeBa_chevron{color:var(--dsw-alias-label-caption)}@media (prefers-reduced-motion:reduce){.YDXeBa_arrow{transition:none;animation:none}}";
14062
14806
  const tagId$3 = "@deepseek-ai/dsh-client-ui-workspace/Rows.module.css";
14063
14807
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
14064
14808
  const tag = document.createElement("style");
@@ -14343,6 +15087,7 @@ const official$2 = ((require$1) => {
14343
15087
  side: "bottom",
14344
15088
  align: "end",
14345
15089
  delayMs: 500,
15090
+ portal: true,
14346
15091
  children: !protectedManagedWorkspace && (0, react_jsx_runtime$1.jsx)("button", {
14347
15092
  type: "button",
14348
15093
  className: Rows_module_css_default.iconButton,
@@ -14368,6 +15113,7 @@ const official$2 = ((require$1) => {
14368
15113
  t
14369
15114
  }),
14370
15115
  openDelayMs: 800,
15116
+ grouped: true,
14371
15117
  disabled: menuOpen,
14372
15118
  copyText: row.cwd,
14373
15119
  copyLabel: t("copy"),
@@ -14470,7 +15216,7 @@ const official$2 = ((require$1) => {
14470
15216
  "data-worktree-state": decoration.state,
14471
15217
  "aria-hidden": "true",
14472
15218
  children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {})
14473
- }), (0, react_jsx_runtime$1.jsx)("span", {
15219
+ }), decoration.state !== "working" && (0, react_jsx_runtime$1.jsx)("span", {
14474
15220
  className: "dsh-git-worktree-sidebar-badge",
14475
15221
  "data-worktree-state": decoration.state,
14476
15222
  "aria-hidden": "true",
@@ -14550,6 +15296,7 @@ const official$2 = ((require$1) => {
14550
15296
  side: "bottom",
14551
15297
  align: "end",
14552
15298
  delayMs: 500,
15299
+ portal: true,
14553
15300
  children: (0, react_jsx_runtime$1.jsx)("button", {
14554
15301
  type: "button",
14555
15302
  className: Rows_module_css_default.iconButton,
@@ -14709,6 +15456,7 @@ const official$2 = ((require$1) => {
14709
15456
  t
14710
15457
  }),
14711
15458
  openDelayMs: 800,
15459
+ grouped: true,
14712
15460
  disabled: menuOpen || drag?.active === true,
14713
15461
  copyText: row.blank || row.title === "" ? void 0 : row.title,
14714
15462
  copyLabel: t("copy"),
@@ -15137,22 +15885,46 @@ const official$2 = ((require$1) => {
15137
15885
  const SEARCH_DEBOUNCE_MS = 250;
15138
15886
  /** `session.search` wire bound, measured in JavaScript UTF-16 code units. */
15139
15887
  const SEARCH_QUERY_MAX_CODE_UNITS = 500;
15140
- /** Idle Session rows visible per Workspace before the local overflow control. */
15141
- const COLLAPSED_SESSION_LIMIT = 5;
15142
- /** Keep provisional and running rows outside the idle-session quota, including parents with running children. */
15143
- function collapsedSessionRows(sessions, limit = COLLAPSED_SESSION_LIMIT) {
15144
- let idleCount = 0;
15888
+ /** Non-blank Session rows visible per Workspace while no Session raises its watermark. */
15889
+ const SESSION_WATERMARK_BASE = 5;
15890
+ /** Highest watermark that attention-bearing Sessions raise automatically; Show more and search reveals may exceed it. */
15891
+ const SESSION_WATERMARK_AUTO_MAX = 50;
15892
+ /** Show the blank row plus the first `watermark` non-blank rows, in list order. */
15893
+ function watermarkRows(sessions, watermark) {
15894
+ let count = 0;
15145
15895
  const rows = sessions.filter((session) => {
15146
- if (session.blank || session.running || session.runningSubagentCount > 0) return true;
15147
- if (idleCount >= limit) return false;
15148
- idleCount += 1;
15149
- return true;
15896
+ if (session.blank) return true;
15897
+ count += 1;
15898
+ return count <= watermark;
15150
15899
  });
15151
15900
  return {
15152
15901
  rows,
15153
15902
  hiddenCount: sessions.length - rows.length
15154
15903
  };
15155
15904
  }
15905
+ /**
15906
+ * Smallest watermark that covers every non-blank row within the automatic maximum that is running, has a running
15907
+ * child, awaits the user, carries an unread completion, or is current, and the reveal target at any position.
15908
+ */
15909
+ function requiredWatermark(sessions, current, revealSessionId) {
15910
+ let index = 0;
15911
+ let required$1 = 0;
15912
+ for (const session of sessions) {
15913
+ if (session.blank) continue;
15914
+ index += 1;
15915
+ if (index <= SESSION_WATERMARK_AUTO_MAX && (session.running || session.runningSubagentCount > 0 || session.pendingInteraction !== void 0 || session.completed || session.id === current) || session.id === revealSessionId) required$1 = index;
15916
+ }
15917
+ return required$1;
15918
+ }
15919
+ /** Raise each listed group's watermark to its requirement; returns `marks` itself when nothing rises. */
15920
+ function raiseWatermarks(marks, required$1) {
15921
+ let raised = marks;
15922
+ for (const [key, watermark] of required$1) if (watermark > (raised[key] ?? SESSION_WATERMARK_BASE)) raised = {
15923
+ ...raised,
15924
+ [key]: watermark
15925
+ };
15926
+ return raised;
15927
+ }
15156
15928
  /** Keep controlled input and RPC payload inside the session.search wire contract. */
15157
15929
  function sanitizeSearchQuery(value) {
15158
15930
  const withoutNul = value.replaceAll("\0", "");
@@ -15333,13 +16105,14 @@ const official$2 = ((require$1) => {
15333
16105
  ]
15334
16106
  });
15335
16107
  }
15336
- /** The scrolling session tree; unmounting drops the sessions subscription and local row limits. */
16108
+ /** The scrolling session tree; unmounting drops the sessions subscription and the session watermarks. */
15337
16109
  function SessionTree({ list, useSessionStatus, startSession, open, workspaces, ungroupedSessionIds, rowState, onLeaveArchivedOnly, workspaceReady, animationResetKey, usePanelInfo, onRenameRequest, onDeleteRequest, onSessionRenameRequest, renderSlot, insertWorkspaceBefore, nestWorkspaces, groupExpansion, setGroupExpanded, setSessionOrder, home, t, revealSessionId, onSessionRevealed, shortcuts }) {
15338
16110
  const panelActive = usePanelInfo((info) => info.activePanelId !== null);
15339
16111
  const statuses = useSessionStatus((s) => s);
15340
16112
  const current = panelActive ? void 0 : Object.values(list.byId).find((session) => (session.retainedBy.mainView ?? 0) > 0)?.id;
15341
16113
  const revealGroup = revealSessionId === void 0 || !workspaceReady ? void 0 : owningGroupKey(workspaces, revealSessionId);
15342
- const [sessionLimits, setSessionLimits] = (0, react$1.useState)({});
16114
+ const [storedWatermarks, setStoredWatermarks] = (0, react$1.useState)({});
16115
+ const [watermarkMotionKey, setWatermarkMotionKey] = (0, react$1.useState)(0);
15343
16116
  const [drag, setDrag] = (0, react$1.useState)(null);
15344
16117
  const sessionDropCommitted = (0, react$1.useRef)(false);
15345
16118
  const [workspaceDrag, setWorkspaceDrag] = (0, react$1.useState)(null);
@@ -15397,20 +16170,24 @@ const official$2 = ((require$1) => {
15397
16170
  revealGroup,
15398
16171
  setGroupExpanded
15399
16172
  ]);
15400
- (0, react$1.useEffect)(() => {
15401
- if (revealSessionId === void 0 || revealGroup === void 0) return;
15402
- const group = groups.find((candidate) => candidate.key === revealGroup);
15403
- if (group === void 0 || !group.expanded || !group.sessions.some((row) => row.id === revealSessionId)) return;
15404
- if (collapsedSessionRows(group.sessions).rows.some((row) => row.id === revealSessionId)) return;
15405
- setSessionLimits((limits) => limits[revealGroup] === Infinity ? limits : {
15406
- ...limits,
15407
- [revealGroup]: Infinity
15408
- });
15409
- }, [
15410
- groups,
15411
- revealGroup,
15412
- revealSessionId
15413
- ]);
16173
+ const baselines = new Map(groups.map((group) => [group.key, Math.max(SESSION_WATERMARK_BASE, requiredWatermark(group.sessions, current, group.key === revealGroup ? revealSessionId : void 0))]));
16174
+ const foldedKeys = new Set(groups.filter((group) => !group.expanded).map((group) => group.key));
16175
+ const underFoldedAncestor = (key) => {
16176
+ for (let owner = parents.get(key); owner !== void 0; owner = parents.get(owner)) if (foldedKeys.has(owner)) return true;
16177
+ return false;
16178
+ };
16179
+ const requiredWatermarks = new Map([...baselines].filter(([key]) => !underFoldedAncestor(key)));
16180
+ const sessionWatermarks = raiseWatermarks(storedWatermarks, requiredWatermarks);
16181
+ if (sessionWatermarks !== storedWatermarks) setStoredWatermarks((marks) => raiseWatermarks(marks, requiredWatermarks));
16182
+ const dropWatermarks = (drop) => {
16183
+ if (!Object.keys(storedWatermarks).some(drop)) return;
16184
+ setStoredWatermarks((marks) => Object.fromEntries(Object.entries(marks).filter(([key]) => !drop(key))));
16185
+ setWatermarkMotionKey((key) => key + 1);
16186
+ };
16187
+ const withinGroup = (key, root) => {
16188
+ for (let owner = key; owner !== void 0; owner = parents.get(owner)) if (owner === root) return true;
16189
+ return false;
16190
+ };
15414
16191
  const now = Date.now();
15415
16192
  const commitSessionDrag = (activeDrag, over) => {
15416
16193
  if (sessionDropCommitted.current) return;
@@ -15421,7 +16198,7 @@ const official$2 = ((require$1) => {
15421
16198
  if (over.id === activeDrag.sessionId) return;
15422
16199
  const accountSessionIds = activeDrag.accountKey === "" ? ungroupedSessionIds : workspaces.find((workspace) => workspace.workspaceId === activeDrag.accountKey)?.sessionIds;
15423
16200
  if (accountSessionIds === void 0) return;
15424
- const renderedSessions = collapsedSessionRows(group.sessions, sessionLimits[group.key]).rows;
16201
+ const renderedSessions = watermarkRows(group.sessions, sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE).rows;
15425
16202
  const nextOrder = sessionDragOrder(accountSessionIds, renderedSessions, activeDrag, over);
15426
16203
  if (nextOrder !== void 0) setSessionOrder(activeDrag.accountKey, nextOrder);
15427
16204
  };
@@ -15461,8 +16238,9 @@ const official$2 = ((require$1) => {
15461
16238
  const workspaceId = group.workspaceId;
15462
16239
  const children = childrenByParent.get(group.key) ?? [];
15463
16240
  const compatibleDrag = workspaceDrag !== null && parents.get(workspaceDrag.workspaceId) === parents.get(group.key);
15464
- const collapsed = collapsedSessionRows(group.sessions);
15465
- const visible = collapsedSessionRows(group.sessions, sessionLimits[group.key]);
16241
+ const watermark = sessionWatermarks[group.key] ?? SESSION_WATERMARK_BASE;
16242
+ const collapsed = watermarkRows(group.sessions, baselines.get(group.key) ?? SESSION_WATERMARK_BASE);
16243
+ const visible = watermarkRows(group.sessions, watermark);
15466
16244
  const sessionsExpanded = visible.hiddenCount === 0;
15467
16245
  rowKeys.push(`workspace:${group.key}`);
15468
16246
  const childRows = group.expanded ? children.map((child) => renderGroup(child, depth + 1)) : [];
@@ -15534,10 +16312,7 @@ const official$2 = ((require$1) => {
15534
16312
  home,
15535
16313
  t,
15536
16314
  onToggle: () => {
15537
- if (group.expanded) setSessionLimits((limits) => ({
15538
- ...limits,
15539
- [group.key]: COLLAPSED_SESSION_LIMIT
15540
- }));
16315
+ if (group.expanded) dropWatermarks((key) => withinGroup(key, group.key));
15541
16316
  setGroupExpanded(group.key, !group.expanded);
15542
16317
  },
15543
16318
  onCreate: () => {
@@ -15622,10 +16397,15 @@ const official$2 = ((require$1) => {
15622
16397
  "data-row-key": `overflow:${group.key}`,
15623
16398
  "aria-expanded": sessionsExpanded,
15624
16399
  onClick: () => {
15625
- setSessionLimits((limits) => ({
15626
- ...limits,
15627
- [group.key]: sessionsExpanded ? COLLAPSED_SESSION_LIMIT : visible.hiddenCount <= COLLAPSED_SESSION_LIMIT ? Infinity : (limits[group.key] ?? COLLAPSED_SESSION_LIMIT) + COLLAPSED_SESSION_LIMIT
16400
+ if (sessionsExpanded) {
16401
+ dropWatermarks((key) => key === group.key);
16402
+ return;
16403
+ }
16404
+ setStoredWatermarks((marks) => ({
16405
+ ...marks,
16406
+ [group.key]: visible.hiddenCount <= SESSION_WATERMARK_BASE ? Infinity : watermark + SESSION_WATERMARK_BASE
15628
16407
  }));
16408
+ setWatermarkMotionKey((key) => key + 1);
15629
16409
  },
15630
16410
  children: sessionsExpanded ? t("sessions.collapse") : t("sessions.expand", { n: visible.hiddenCount })
15631
16411
  })
@@ -15633,7 +16413,7 @@ const official$2 = ((require$1) => {
15633
16413
  }, group.key);
15634
16414
  };
15635
16415
  const groupRows = rootGroups.map((group) => renderGroup(group, 0));
15636
- return (0, react_jsx_runtime$1.jsxs)("div", {
16416
+ return (0, react_jsx_runtime$1.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
15637
16417
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
15638
16418
  children: [
15639
16419
  workspaceDropAtListStart && (0, react_jsx_runtime$1.jsx)("span", {
@@ -15645,7 +16425,7 @@ const official$2 = ((require$1) => {
15645
16425
  label: t("section.sessions"),
15646
16426
  rowKeys,
15647
16427
  ready: list.phase === "ready" && workspaceReady && !nativeDragActive,
15648
- resetKey: JSON.stringify([animationResetKey, sessionLimits]),
16428
+ resetKey: JSON.stringify([animationResetKey, watermarkMotionKey]),
15649
16429
  children: [groups.length === 0 && (0, react_jsx_runtime$1.jsx)(EmptySessions, {
15650
16430
  rowState,
15651
16431
  onLeaveArchivedOnly,
@@ -15678,7 +16458,7 @@ const official$2 = ((require$1) => {
15678
16458
  if (nextOrder !== void 0) setSessionOrder(FLAT_SESSION_ORDER_KEY, nextOrder);
15679
16459
  };
15680
16460
  const now = Date.now();
15681
- return (0, react_jsx_runtime$1.jsxs)("div", {
16461
+ return (0, react_jsx_runtime$1.jsxs)(_deepseek_ai_dsh_client_ui_primitives.HoverCardGroup, {
15682
16462
  className: clsx(WorkspaceBrowser_module_css_default.treeBody, WorkspaceBrowser_module_css_default.wide),
15683
16463
  children: [(0, react_jsx_runtime$1.jsxs)(AnimatedRows, {
15684
16464
  className: clsx(WorkspaceBrowser_module_css_default.list, WorkspaceBrowser_module_css_default.flatList),
@@ -15811,7 +16591,7 @@ const official$2 = ((require$1) => {
15811
16591
  * @param props - composed slot props (shell owner share + store + injected actions).
15812
16592
  * @returns the region element tree.
15813
16593
  */
15814
- function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, dismissForkError, renderSlot, t }) {
16594
+ function WorkspaceBrowser({ wide, usePanelInfo, expandSidebar, useSessions, useSessionStatus, useWorkspaces, useStore, actions, startSession, open, requestSessionRename, notifyArchivedNotOpenable, renameWorkspace, deleteWorkspace, insertWorkspaceBefore, unarchiveSession, createWorkspace, searchSessions, searchResultLimit, useDirectoryFlow, useHostInfo, useShortcuts, useWorkspaceShortcuts, requestSearch, requestAddWorkspace, closeAddWorkspace, setDirectoryBusy, renderSlot, t }) {
15815
16595
  const home = useHostInfo((info) => info.home);
15816
16596
  const shortcuts = useShortcuts((rows) => rows);
15817
16597
  const searchShortcut = shortcuts.find((row) => row.id === "session.search");
@@ -16240,7 +17020,7 @@ const official$2 = ((require$1) => {
16240
17020
  side: "right",
16241
17021
  onPick: (workspaceId) => {
16242
17022
  closeAddWorkspace();
16243
- startSession(workspaceId);
17023
+ startSession(workspaceId, { clearPreviousDraft: false });
16244
17024
  },
16245
17025
  onClose: () => {
16246
17026
  closeAddWorkspace();
@@ -16419,11 +17199,7 @@ const official$2 = ((require$1) => {
16419
17199
  role: "alert",
16420
17200
  children: deleteError
16421
17201
  })]
16422
- }),
16423
- shortcutState.forkError !== null && (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
16424
- text: t(shortcutState.forkError.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
16425
- onDone: dismissForkError
16426
- }, shortcutState.forkError.seq)
17202
+ })
16427
17203
  ]
16428
17204
  });
16429
17205
  }
@@ -16466,6 +17242,7 @@ const official$2 = ((require$1) => {
16466
17242
  side: "bottom",
16467
17243
  align: "end",
16468
17244
  delayMs: 500,
17245
+ portal: true,
16469
17246
  children: (0, react_jsx_runtime$1.jsx)("button", {
16470
17247
  type: "button",
16471
17248
  className: Rows_module_css_default.iconButton,
@@ -16607,14 +17384,17 @@ const official$2 = ((require$1) => {
16607
17384
  /** The fork action: one `sidebar.workspaces.session.menu.item` row. */
16608
17385
  /**
16609
17386
  * Menu row (order 300): fork at the Session's last completed turn; the child
16610
- * arrives through the Host list beside its source.
16611
- * @param props - owner share, menu open state, and the fork share.
16612
- * @returns the row.
17387
+ * arrives through the Host list beside its source. Absent when the access
17388
+ * snapshot excludes `session/fork` for this Session.
17389
+ * @param props - owner share, menu open state, access Hook, and the fork share.
17390
+ * @returns the row, or nothing when the caller may not fork this Session.
16613
17391
  */
16614
- function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, forkSession, t }) {
17392
+ function ForkSessionMenuItem({ sessionId, useMenuOpenState, useShortcuts, useSessionAccess, forkSession, t }) {
16615
17393
  const [, setMenuOpen] = useMenuOpenState();
17394
+ const shortcut = useShortcuts((rows) => rows.find((row) => row.id === "session.fork"));
17395
+ if (!useSessionAccess(sessionId, "session/fork")) return null;
16616
17396
  return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
16617
- shortcut: useShortcuts((rows) => rows.find((row) => row.id === "session.fork")),
17397
+ shortcut,
16618
17398
  icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutlineRegular, {}),
16619
17399
  onSelect: () => {
16620
17400
  setMenuOpen(false);
@@ -16671,6 +17451,7 @@ const official$2 = ((require$1) => {
16671
17451
  side: "bottom",
16672
17452
  align: "end",
16673
17453
  delayMs: 500,
17454
+ portal: true,
16674
17455
  children: (0, react_jsx_runtime$1.jsx)("button", {
16675
17456
  type: "button",
16676
17457
  className: Rows_module_css_default.iconButton,
@@ -16797,72 +17578,232 @@ const official$2 = ((require$1) => {
16797
17578
  });
16798
17579
  }
16799
17580
  /**
16800
- * The `shell.overlay` entry for Workspace and Session notices.
16801
- * One notice is visible at a time; a parent rerender does not extend its hold.
17581
+ * Dispatch the current notice without interpreting its business payload.
17582
+ * @param props - Current notice, dismissal, and the declared keyed renderer.
17583
+ * @returns The selected notice presentation, or nothing.
16802
17584
  */
17585
+ function RowActionToast({ useToast, dismissToast, renderSlot }) {
17586
+ const notice = useToast((current) => current);
17587
+ if (notice === null) return null;
17588
+ return renderSlot("workspace.toast", {
17589
+ ...notice,
17590
+ dismissToast
17591
+ }, { entryKey: notice.kind });
17592
+ }
17593
+ /** One presentation Factory and the adapters for keyed Workspace notices. */
16803
17594
  /**
16804
- * Hold for the notices that take longer to read than a one-line warning: the
16805
- * actionable archived notice (two buttons to react to) and a refused Session
16806
- * creation, which quotes the Host's reason.
17595
+ * Render one notice using the shared Factory.
17596
+ * @param props - Keyed payload, injected display mapping, locale, and Factory renderer.
17597
+ * @returns The shared Toast occurrence.
16807
17598
  */
16808
- const LONG_TOAST_HOLD_MS = 6e3;
17599
+ function ToastEntry(props) {
17600
+ const { dismissToast, display, t, renderFactorySlot, seq } = props;
17601
+ return renderFactorySlot("workspace.toast.content", {
17602
+ ...display(props, t, dismissToast),
17603
+ seq,
17604
+ onDone: dismissToast
17605
+ });
17606
+ }
16809
17607
  /**
16810
- * Render the current notice: the archived and stopped-and-archived notices
16811
- * with their undo action — plus the show-archived action while archived rows
16812
- * are hidden — on a 6 s hold, a refused Session creation with the Host's
16813
- * reason on the same hold, or a plain warning for a failed pin, an archived
16814
- * row that was clicked, or default Workspace creation.
16815
- * @param props - the notice hook, the shared viewing store, the notice dismissal, the two archived-notice actions, and the locale seat.
16816
- * @returns the notice on display, or null.
17608
+ * Keep the archive filter action reactive without subscribing unrelated notices.
17609
+ * @param props - Archive notice, display mapping, and the existing viewing store.
17610
+ * @returns The shared Toast occurrence with its current actions.
16817
17611
  */
16818
- function RowActionToast({ useToast, useStore, dismissToast, undoArchive, showArchived, t }) {
16819
- const toast = useToast((current) => current);
16820
- const archivedRowsVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
16821
- if (toast === null) return null;
16822
- if (toast.kind === "archived" || toast.kind === "stoppedAndArchived") {
16823
- const { sessionId } = toast;
16824
- return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
16825
- text: t(toast.kind === "archived" ? "toast.archived" : "toast.stoppedAndArchived"),
16826
- tone: "success",
16827
- holdMs: LONG_TOAST_HOLD_MS,
16828
- actions: [{
16829
- label: t("toast.archivedUndo"),
16830
- onClick: () => {
16831
- dismissToast();
16832
- undoArchive(sessionId);
16833
- }
16834
- }, ...archivedRowsVisible ? [] : [{
16835
- prefix: t("toast.archivedOr"),
16836
- label: t("toast.archivedFilter"),
16837
- onClick: () => {
16838
- dismissToast();
16839
- showArchived();
16840
- }
16841
- }]],
16842
- onDone: dismissToast
16843
- }, `toast-${String(toast.seq)}`);
16844
- }
16845
- if (toast.kind === "createFailed") return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
16846
- text: t("toast.createFailed", { message: toast.message }),
16847
- icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
16848
- holdMs: LONG_TOAST_HOLD_MS,
17612
+ function ArchiveToastEntry(props) {
17613
+ const { dismissToast, display, filterAction, useStore, t, renderFactorySlot, seq } = props;
17614
+ const archivedVisible = useStore((state) => (state.archivedFilter ?? "default") !== "default");
17615
+ const fields = display(props, t, dismissToast);
17616
+ return renderFactorySlot("workspace.toast.content", {
17617
+ ...fields,
17618
+ actions: [...fields.actions ?? [], ...archivedVisible ? [] : [filterAction(t, dismissToast)]],
17619
+ seq,
16849
17620
  onDone: dismissToast
16850
- }, `toast-${String(toast.seq)}`);
17621
+ });
17622
+ }
17623
+ /**
17624
+ * Present plain display data with the shared primitive's timing and portal.
17625
+ * @param props - Localized display fields, display identity, and dismissal.
17626
+ * @returns One Toast, remounted only for a new display identity.
17627
+ */
17628
+ function WorkspaceToast({ seq, text, tone, holdMs, actions, onDone }) {
16851
17629
  return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
16852
- text: plainNoticeText(toast, t),
16853
- icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}),
16854
- onDone: dismissToast
16855
- }, `toast-${String(toast.seq)}`);
17630
+ text,
17631
+ onDone,
17632
+ ...tone === "success" ? { tone: "success" } : {},
17633
+ ...tone === "warning" ? { icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutlineRegular, {}) } : {},
17634
+ ...holdMs === void 0 ? {} : { holdMs },
17635
+ ...actions === void 0 ? {} : { actions }
17636
+ }, seq);
17637
+ }
17638
+ /** Register notice presentations with their business-owned display fields and actions.
17639
+ * @param ctx - Workspace plugin context.
17640
+ * @param viewStore - Viewing store, read only by archive notices.
17641
+ * @param actions - Archive and source-opening callbacks.
17642
+ */
17643
+ function registerToasts(ctx, viewStore, actions) {
17644
+ const archiveFilterAction = (t, dismiss) => ({
17645
+ prefix: t("toast.archivedOr"),
17646
+ label: t("toast.archivedFilter"),
17647
+ onClick: () => {
17648
+ dismiss();
17649
+ actions.showArchived();
17650
+ }
17651
+ });
17652
+ ctx.slots.registerFactory({
17653
+ name: "workspace.toast.content",
17654
+ scope: "root"
17655
+ }, WorkspaceToast);
17656
+ ctx.slots.inject("workspace.toast", function* () {
17657
+ yield ctx.slots.register({
17658
+ name: "workspace.toast",
17659
+ key: "sessionIdCopied",
17660
+ locale: "workspace",
17661
+ inject: () => ({ display: (_notice, t) => ({
17662
+ text: t("toast.sessionIdCopied"),
17663
+ tone: "success"
17664
+ }) })
17665
+ }, ToastEntry);
17666
+ yield ctx.slots.register({
17667
+ name: "workspace.toast",
17668
+ key: "sessionIdCopyFailed",
17669
+ locale: "workspace",
17670
+ inject: () => ({ display: (_notice, t) => ({
17671
+ text: t("toast.sessionIdCopyFailed"),
17672
+ tone: "warning"
17673
+ }) })
17674
+ }, ToastEntry);
17675
+ yield ctx.slots.register({
17676
+ name: "workspace.toast",
17677
+ key: "pinFailed",
17678
+ locale: "workspace",
17679
+ inject: () => ({ display: (_notice, t) => ({
17680
+ text: t("toast.pinFailed"),
17681
+ tone: "warning"
17682
+ }) })
17683
+ }, ToastEntry);
17684
+ yield ctx.slots.register({
17685
+ name: "workspace.toast",
17686
+ key: "unpinFailed",
17687
+ locale: "workspace",
17688
+ inject: () => ({ display: (_notice, t) => ({
17689
+ text: t("toast.unpinFailed"),
17690
+ tone: "warning"
17691
+ }) })
17692
+ }, ToastEntry);
17693
+ yield ctx.slots.register({
17694
+ name: "workspace.toast",
17695
+ key: "archivedNotOpenable",
17696
+ locale: "workspace",
17697
+ inject: () => ({ display: (_notice, t) => ({
17698
+ text: t("toast.archivedNotOpenable"),
17699
+ tone: "warning"
17700
+ }) })
17701
+ }, ToastEntry);
17702
+ yield ctx.slots.register({
17703
+ name: "workspace.toast",
17704
+ key: "defaultWorkspaceFailed",
17705
+ locale: "workspace",
17706
+ inject: () => ({ display: (_notice, t) => ({
17707
+ text: t("defaultWorkspace.failed"),
17708
+ tone: "warning"
17709
+ }) })
17710
+ }, ToastEntry);
17711
+ yield ctx.slots.register({
17712
+ name: "workspace.toast",
17713
+ key: "shortcutForkFailed",
17714
+ locale: "workspace",
17715
+ inject: () => ({ display: (notice, t) => ({
17716
+ text: t(notice.reason === "unavailable" ? "shortcut.noCompletedTurn" : "shortcut.forkFailed"),
17717
+ tone: "plain"
17718
+ }) })
17719
+ }, ToastEntry);
17720
+ yield ctx.slots.register({
17721
+ name: "workspace.toast",
17722
+ key: "createFailed",
17723
+ locale: "workspace",
17724
+ inject: () => ({ display: (notice, t) => ({
17725
+ text: t("toast.createFailed", { message: notice.message }),
17726
+ tone: "warning",
17727
+ holdMs: 6e3
17728
+ }) })
17729
+ }, ToastEntry);
17730
+ yield ctx.slots.register({
17731
+ name: "workspace.toast",
17732
+ key: "forkRequiresOpen",
17733
+ locale: "workspace",
17734
+ inject: () => ({ display: (notice, t, dismiss) => ({
17735
+ text: t("toast.forkRequiresOpen"),
17736
+ tone: "warning",
17737
+ holdMs: 6e3,
17738
+ actions: [{
17739
+ label: t("toast.openForkSource"),
17740
+ onClick: () => {
17741
+ dismiss();
17742
+ actions.openForkSource(notice.sessionId);
17743
+ }
17744
+ }]
17745
+ }) })
17746
+ }, ToastEntry);
17747
+ yield ctx.slots.register({
17748
+ name: "workspace.toast",
17749
+ key: "archived",
17750
+ locale: "workspace",
17751
+ store: viewStore,
17752
+ inject: () => ({
17753
+ display: (notice, t, dismiss) => ({
17754
+ text: t("toast.archived"),
17755
+ tone: "success",
17756
+ holdMs: 6e3,
17757
+ actions: [{
17758
+ label: t("toast.archivedUndo"),
17759
+ onClick: () => {
17760
+ dismiss();
17761
+ actions.undoArchive(notice.sessionId);
17762
+ }
17763
+ }]
17764
+ }),
17765
+ filterAction: archiveFilterAction
17766
+ })
17767
+ }, ArchiveToastEntry);
17768
+ yield ctx.slots.register({
17769
+ name: "workspace.toast",
17770
+ key: "stoppedAndArchived",
17771
+ locale: "workspace",
17772
+ store: viewStore,
17773
+ inject: () => ({
17774
+ display: (notice, t, dismiss) => ({
17775
+ text: t("toast.stoppedAndArchived"),
17776
+ tone: "success",
17777
+ holdMs: 6e3,
17778
+ actions: [{
17779
+ label: t("toast.archivedUndo"),
17780
+ onClick: () => {
17781
+ dismiss();
17782
+ actions.undoArchive(notice.sessionId);
17783
+ }
17784
+ }]
17785
+ }),
17786
+ filterAction: archiveFilterAction
17787
+ })
17788
+ }, ArchiveToastEntry);
17789
+ });
16856
17790
  }
16857
- /** The copy of one plain warning, keyed by the notice kind the union closes over. */
16858
- function plainNoticeText(toast, t) {
16859
- switch (toast.kind) {
16860
- case "pinFailed": return t("toast.pinFailed");
16861
- case "unpinFailed": return t("toast.unpinFailed");
16862
- case "defaultWorkspaceFailed": return t("defaultWorkspace.failed");
16863
- case "archivedNotOpenable": return t("toast.archivedNotOpenable");
16864
- default: return assertNever$1(toast);
16865
- }
17791
+ /** Copy the row's Session ID without opening its history. */
17792
+ /**
17793
+ * Copy the complete ID after dismissing the row menu.
17794
+ * @param props - Row identity, menu dismissal, clipboard action, and locale.
17795
+ * @returns The final built-in menu item.
17796
+ */
17797
+ function SessionIdMenuItem({ sessionId, useMenuOpenState, copySessionId, t }) {
17798
+ const [, setMenuOpen] = useMenuOpenState();
17799
+ return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.MenuItemButton, {
17800
+ icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular, {}),
17801
+ onSelect: () => {
17802
+ setMenuOpen(false);
17803
+ copySessionId(sessionId);
17804
+ },
17805
+ children: t("menu.sessionId")
17806
+ });
16866
17807
  }
16867
17808
  /**
16868
17809
  * `workspace` namespace dictionaries: the browsing region (section header,
@@ -16880,6 +17821,8 @@ const official$2 = ((require$1) => {
16880
17821
  "dshGitWorktree.state.finalized": "已完成",
16881
17822
  "dshGitWorktree.state.discarded": "已放弃",
16882
17823
  "defaultWorkspace.failed": "无法创建默认工作区,请通过“选择工作区”选择文件夹",
17824
+ "draft.workspaceRequired": "请先选择工作区",
17825
+ "draft.initializationFailed": "暂时无法填入草稿,请稍后重试",
16883
17826
  "group.ungrouped": "未分组",
16884
17827
  "session.new": "新会话",
16885
17828
  "session.untitled": "未命名",
@@ -16934,6 +17877,9 @@ const official$2 = ((require$1) => {
16934
17877
  "menu.unarchiveSession": "取消归档",
16935
17878
  "menu.pinSession": "置顶会话",
16936
17879
  "menu.unpinSession": "取消置顶",
17880
+ "menu.sessionId": "复制 Session ID",
17881
+ "toast.sessionIdCopied": "Session ID 已复制",
17882
+ "toast.sessionIdCopyFailed": "复制失败,请重试",
16937
17883
  "row.archived": "已归档",
16938
17884
  "row.pinned": "已置顶",
16939
17885
  "toast.archivedNotOpenable": "已归档对话暂时无法查看,请取消归档后查看",
@@ -16960,6 +17906,8 @@ const official$2 = ((require$1) => {
16960
17906
  "toast.pinFailed": "置顶失败,请稍后重试",
16961
17907
  "toast.unpinFailed": "取消置顶失败,请稍后重试",
16962
17908
  "toast.createFailed": "新建会话失败:{message}",
17909
+ "toast.forkRequiresOpen": "请先打开原会话完成迁移,再创建分支",
17910
+ "toast.openForkSource": "打开原会话",
16963
17911
  "sessions.count.one": "{n} 个会话",
16964
17912
  "sessions.count.other": "{n} 个会话",
16965
17913
  "actions.workspace.aria": "工作区“{name}”的操作",
@@ -17003,6 +17951,8 @@ const official$2 = ((require$1) => {
17003
17951
  "dshGitWorktree.state.finalized": "Done",
17004
17952
  "dshGitWorktree.state.discarded": "Discarded",
17005
17953
  "defaultWorkspace.failed": "Unable to create default workspace. Use Choose workspace to select a folder.",
17954
+ "draft.workspaceRequired": "Choose a workspace first.",
17955
+ "draft.initializationFailed": "Could not fill the draft. Please try again shortly.",
17006
17956
  "group.ungrouped": "Ungrouped",
17007
17957
  "session.new": "New Session",
17008
17958
  "session.untitled": "Untitled",
@@ -17057,6 +18007,9 @@ const official$2 = ((require$1) => {
17057
18007
  "menu.unarchiveSession": "Unarchive session",
17058
18008
  "menu.pinSession": "Pin session",
17059
18009
  "menu.unpinSession": "Unpin session",
18010
+ "menu.sessionId": "Copy Session ID",
18011
+ "toast.sessionIdCopied": "Session ID copied",
18012
+ "toast.sessionIdCopyFailed": "Copy failed. Try again.",
17060
18013
  "row.archived": "Archived",
17061
18014
  "row.pinned": "Pinned",
17062
18015
  "toast.archivedNotOpenable": "Archived sessions cannot be opened. Unarchive it to view.",
@@ -17083,6 +18036,8 @@ const official$2 = ((require$1) => {
17083
18036
  "toast.pinFailed": "Pin failed. Try again later.",
17084
18037
  "toast.unpinFailed": "Unpin failed. Try again later.",
17085
18038
  "toast.createFailed": "New session failed: {message}",
18039
+ "toast.forkRequiresOpen": "Open the original session to complete migration before creating a branch.",
18040
+ "toast.openForkSource": "Open original session",
17086
18041
  "sessions.count.one": "{n} session",
17087
18042
  "sessions.count.other": "{n} sessions",
17088
18043
  "actions.workspace.aria": "Workspace actions for {name}",
@@ -17133,7 +18088,8 @@ const official$2 = ((require$1) => {
17133
18088
  "remote",
17134
18089
  "remote.directoryPicker",
17135
18090
  "layout",
17136
- "shortcuts"
18091
+ "shortcuts",
18092
+ "remoteAccess"
17137
18093
  ];
17138
18094
  /**
17139
18095
  * Register the browser and picker once their slot declarations are on the
@@ -17159,7 +18115,10 @@ const official$2 = ((require$1) => {
17159
18115
  });
17160
18116
  };
17161
18117
  const uiWorkspace = new UiWorkspaceService(ctx, ctx.remote.directoryPicker, workspaces, sessions, viewInstance.actions, notify);
17162
- ctx.slots.provideRoot({ hooks: { workspaces: workspaces.list } });
18118
+ ctx.slots.provideRoot({
18119
+ hooks: { workspaces: workspaces.list },
18120
+ keyedHooks: { workspaceAccess: (workspaceId, action) => action === void 0 ? void 0 : ctx.remoteAccess.source("workspace", workspaceId, action) }
18121
+ });
17163
18122
  ctx.effect(() => ctx.locale.register(NS, {
17164
18123
  zh,
17165
18124
  en
@@ -17237,7 +18196,12 @@ const official$2 = ((require$1) => {
17237
18196
  },
17238
18197
  unarchiveSession
17239
18198
  });
17240
- installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession);
18199
+ installWorkspaceShortcuts(ctx, uiWorkspace, shortcutControls, archiveInjected().archiveSession, (reason) => {
18200
+ notify({
18201
+ kind: "shortcutForkFailed",
18202
+ reason
18203
+ });
18204
+ });
17241
18205
  const archiveConfirmInjected = () => ({
17242
18206
  hooks: { archiveRequest },
17243
18207
  settleSessionArchive: () => {
@@ -17258,7 +18222,15 @@ const official$2 = ((require$1) => {
17258
18222
  parent_session_id: sessionId,
17259
18223
  click_position: "sidebar"
17260
18224
  });
17261
- }).catch(() => {});
18225
+ }, { allowMigration: false }).catch((error) => {
18226
+ if (error instanceof Error && error.name === "SessionForkError" && error.rpcError.code === "session/migration-required") notify({
18227
+ kind: "forkRequiresOpen",
18228
+ sessionId
18229
+ });
18230
+ });
18231
+ } });
18232
+ const sessionIdInjected = () => ({ copySessionId: async (sessionId) => {
18233
+ notify({ kind: await (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(sessionId) ? "sessionIdCopied" : "sessionIdCopyFailed" });
17262
18234
  } });
17263
18235
  const renameInjected = () => ({ requestSessionRename });
17264
18236
  const renameDialogInjected = () => ({
@@ -17266,19 +18238,28 @@ const official$2 = ((require$1) => {
17266
18238
  settleSessionRename: shortcutControls.closeRename,
17267
18239
  renameSession
17268
18240
  });
18241
+ registerToasts(ctx, viewStore, {
18242
+ undoArchive: unarchiveSession,
18243
+ showArchived: () => {
18244
+ viewInstance.actions.setArchivedFilter("show");
18245
+ },
18246
+ openForkSource: (sessionId) => {
18247
+ if (workspaces.list.getSnapshot().archivedSessionIds.includes(sessionId)) {
18248
+ notify({ kind: "archivedNotOpenable" });
18249
+ return;
18250
+ }
18251
+ openSession(sessionId);
18252
+ }
18253
+ });
17269
18254
  const rowToastInjected = () => ({
17270
18255
  hooks: { toast: rowToast },
17271
18256
  dismissToast: () => {
17272
18257
  rowToast.set(null);
17273
- },
17274
- undoArchive: unarchiveSession,
17275
- showArchived: () => {
17276
- viewInstance.actions.setArchivedFilter("show");
17277
18258
  }
17278
18259
  });
17279
18260
  const browserInjected = () => ({
17280
- startSession: (workspaceId) => {
17281
- uiWorkspace.startSession(workspaceId);
18261
+ startSession: (workspaceId, options) => {
18262
+ uiWorkspace.startSession(workspaceId, options);
17282
18263
  },
17283
18264
  open: openSession,
17284
18265
  searchSessions,
@@ -17304,7 +18285,6 @@ const official$2 = ((require$1) => {
17304
18285
  requestAddWorkspace: shortcutControls.add,
17305
18286
  closeAddWorkspace: shortcutControls.closeAdd,
17306
18287
  setDirectoryBusy: shortcutControls.directoryBusy,
17307
- dismissForkError: shortcutControls.dismissForkError,
17308
18288
  hooks: {
17309
18289
  directoryFlow: browserFlowSource,
17310
18290
  hostInfo,
@@ -17377,6 +18357,13 @@ const official$2 = ((require$1) => {
17377
18357
  locale: NS,
17378
18358
  inject: archiveInjected
17379
18359
  }, ArchiveSessionMenuItem);
18360
+ yield ctx.slots.register({
18361
+ name: "sidebar.workspaces.session.menu.item",
18362
+ id: "session-id",
18363
+ order: 450,
18364
+ locale: NS,
18365
+ inject: sessionIdInjected
18366
+ }, SessionIdMenuItem);
17380
18367
  });
17381
18368
  ctx.slots.inject("sidebar.workspaces.session.row.action", function* () {
17382
18369
  yield ctx.slots.register({
@@ -17410,9 +18397,11 @@ const official$2 = ((require$1) => {
17410
18397
  yield ctx.slots.register({
17411
18398
  name: "shell.overlay",
17412
18399
  id: "workspace.row-toast",
17413
- locale: NS,
17414
- store: viewStore,
17415
- inject: rowToastInjected
18400
+ inject: rowToastInjected,
18401
+ children: { "workspace.toast": {
18402
+ kind: "keyed",
18403
+ scope: "root"
18404
+ } }
17416
18405
  }, RowActionToast);
17417
18406
  });
17418
18407
  ctx.slots.inject("conversation.hero.workspace", () => ctx.slots.register({
@@ -17573,6 +18562,12 @@ function projectManagedWorkspaceSidebar(input, t = defaultClientTranslator) {
17573
18562
  //#endregion
17574
18563
  //#region src/client/workspace-sidebar/index.tsx
17575
18564
  const EMPTY_TOPOLOGY = { projects: [] };
18565
+ const OWNER_SAFE_SESSION_ACTIONS = new Set([
18566
+ "pin",
18567
+ "rename",
18568
+ "archive",
18569
+ "session-id"
18570
+ ]);
17576
18571
  /**
17577
18572
  * Preserve the complete official Workspace Browser and change only its data
17578
18573
  * projection plus mutations that are unsafe for one-to-one Managed owners.
@@ -17732,7 +18727,7 @@ function officialContextProxy(ctx, adapter) {
17732
18727
  };
17733
18728
  const proxySlots = new Proxy(ctx.slots, { get(target, key, receiver) {
17734
18729
  if (key === "register") return (descriptor$1, component) => {
17735
- if ((descriptor$1.name === "sidebar.workspaces.session.menu.item" || descriptor$1.name === "sidebar.workspaces.session.row.action") && descriptor$1.id !== "pin") {
18730
+ if ((descriptor$1.name === "sidebar.workspaces.session.menu.item" || descriptor$1.name === "sidebar.workspaces.session.row.action") && !OWNER_SAFE_SESSION_ACTIONS.has(descriptor$1.id)) {
17736
18731
  const Action = component;
17737
18732
  const GuardedAction = (props) => typeof props.sessionId === "string" && !guard.blockSession(props.sessionId) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Action, { ...props }) : null;
17738
18733
  return target.register(descriptor$1, GuardedAction);
@@ -18541,6 +19536,7 @@ const official$1 = ((require$1) => {
18541
19536
  */
18542
19537
  constructor(ctx, directoryPicker, workspaces, sessions, view, notify) {
18543
19538
  super(ctx, "uiWorkspace");
19539
+ this.managedWorktreeGuard = ctx.get("__dshGitWorktreeManagedGuard");
18544
19540
  this.directoryPicker = directoryPicker;
18545
19541
  this.workspaces = workspaces;
18546
19542
  this.sessions = sessions;
@@ -18608,7 +19604,7 @@ const official$1 = ((require$1) => {
18608
19604
  this.replaceMain(sessionId, navigation, "reveal", beforeOpen);
18609
19605
  }
18610
19606
  async forkSession(sessionId) {
18611
- if (this.ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
19607
+ if (this.managedWorktreeGuard.blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
18612
19608
  await this.sessions.fork({
18613
19609
  sessionId,
18614
19610
  increaseTitle: true
@@ -18621,7 +19617,7 @@ const official$1 = ((require$1) => {
18621
19617
  const currentWorkspaceId = current === void 0 ? void 0 : workspace.items.find((item) => item.sessionIds.includes(current))?.workspaceId;
18622
19618
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
18623
19619
  const target = workspaceId ?? currentWorkspaceId ?? recent;
18624
- if (target !== void 0 && this.ctx.get("__dshGitWorktreeManagedGuard").blockWorkspace(target)) return;
19620
+ if (target !== void 0 && this.managedWorktreeGuard.blockWorkspace(target)) return;
18625
19621
  if (target === void 0) {
18626
19622
  this.clearMain();
18627
19623
  return;
@@ -18631,7 +19627,6 @@ const official$1 = ((require$1) => {
18631
19627
  });
18632
19628
  }
18633
19629
  async archiveSession(sessionId, options = {}) {
18634
- if (this.ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
18635
19630
  await this.workspaces.archiveSession(sessionId, options);
18636
19631
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
18637
19632
  }
@@ -19402,13 +20397,13 @@ const official$1 = ((require$1) => {
19402
20397
  (0, react_jsx_runtime$1.jsx)("span", {
19403
20398
  ref: titleRef,
19404
20399
  className: Rows_module_css_default.title,
19405
- onDoubleClick: row.blank || protectedManagedSession ? void 0 : (e) => {
20400
+ onDoubleClick: row.blank ? void 0 : (e) => {
19406
20401
  e.stopPropagation();
19407
20402
  onRenameRequest(node.id, row.title);
19408
20403
  },
19409
20404
  children: title
19410
20405
  }),
19411
- protectedManagedSession && (0, react_jsx_runtime$1.jsx)("span", {
20406
+ protectedManagedSession && node.__dshGitWorktree.state !== "working" && (0, react_jsx_runtime$1.jsx)("span", {
19412
20407
  className: "dsh-git-worktree-sidebar-badge",
19413
20408
  "data-worktree-state": node.__dshGitWorktree.state,
19414
20409
  children: node.__dshGitWorktree.label
@@ -21925,7 +22920,6 @@ const official$1 = ((require$1) => {
21925
22920
  });
21926
22921
  };
21927
22922
  const renameSession = async (sessionId, title) => {
21928
- if (ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
21929
22923
  const result = await sessions.using(sessionId, { source: "workspaceOperation" }, (reference) => reference.binding.session.rename(title));
21930
22924
  if (!result.ok) throw new Error(result.error.message);
21931
22925
  };
@@ -22218,6 +23212,7 @@ const official = ((require$1) => {
22218
23212
  */
22219
23213
  constructor(ctx, directoryPicker, workspaces, sessions) {
22220
23214
  super(ctx, "uiWorkspace");
23215
+ this.managedWorktreeGuard = ctx.get("__dshGitWorktreeManagedGuard");
22221
23216
  this.directoryPicker = directoryPicker;
22222
23217
  this.workspaces = workspaces;
22223
23218
  this.sessions = sessions;
@@ -22259,7 +23254,7 @@ const official = ((require$1) => {
22259
23254
  this.replaceMain(sessionId, navigation, beforeOpen);
22260
23255
  }
22261
23256
  async forkSession(sessionId) {
22262
- if (this.ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
23257
+ if (this.managedWorktreeGuard.blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
22263
23258
  const navigation = AbortSignal.any([this.ctx.layout.beginNavigation(), this.lifetime.signal]);
22264
23259
  const childId = await this.sessions.fork({
22265
23260
  sessionId,
@@ -22274,7 +23269,7 @@ const official = ((require$1) => {
22274
23269
  const currentWorkspaceId = current === void 0 ? void 0 : workspace.items.find((item) => item.sessionIds.includes(current))?.workspaceId;
22275
23270
  const recent = workspace.phase === "ready" && sessions.phase === "ready" ? recentWorkspace(workspace.items, sessions.byId) : void 0;
22276
23271
  const target = workspaceId ?? currentWorkspaceId ?? recent;
22277
- if (target !== void 0 && this.ctx.get("__dshGitWorktreeManagedGuard").blockWorkspace(target)) return;
23272
+ if (target !== void 0 && this.managedWorktreeGuard.blockWorkspace(target)) return;
22278
23273
  if (target === void 0) {
22279
23274
  this.clearMain();
22280
23275
  return;
@@ -22284,7 +23279,6 @@ const official = ((require$1) => {
22284
23279
  });
22285
23280
  }
22286
23281
  async archiveSession(sessionId) {
22287
- if (this.ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
22288
23282
  await this.workspaces.archiveSession(sessionId);
22289
23283
  if (this.mainReference?.sessionId === sessionId) this.clearMain();
22290
23284
  }
@@ -23303,6 +24297,7 @@ const official = ((require$1) => {
23303
24297
  icon: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 })
23304
24298
  }
23305
24299
  ];
24300
+ const visibleSessionMenuItems = protectedManagedSession ? sessionMenuItems.filter((item) => item.id !== "fork") : sessionMenuItems;
23306
24301
  return (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.HoverCard, {
23307
24302
  anchor: (0, react_jsx_runtime$1.jsxs)("div", {
23308
24303
  ref: rowRef,
@@ -23355,7 +24350,7 @@ const official = ((require$1) => {
23355
24350
  className: Rows_module_css_default.title,
23356
24351
  children: title
23357
24352
  }),
23358
- protectedManagedSession && (0, react_jsx_runtime$1.jsx)("span", {
24353
+ protectedManagedSession && node.__dshGitWorktree.state !== "working" && (0, react_jsx_runtime$1.jsx)("span", {
23359
24354
  className: "dsh-git-worktree-sidebar-badge",
23360
24355
  "data-worktree-state": node.__dshGitWorktree.state,
23361
24356
  children: node.__dshGitWorktree.label
@@ -23365,14 +24360,14 @@ const official = ((require$1) => {
23365
24360
  className: Rows_module_css_default.time,
23366
24361
  children: timeLabel(row.updatedAt, now, t)
23367
24362
  }),
23368
- !row.blank && !protectedManagedSession && (0, react_jsx_runtime$1.jsx)("span", {
24363
+ !row.blank && (0, react_jsx_runtime$1.jsx)("span", {
23369
24364
  className: Rows_module_css_default.rowActions,
23370
24365
  children: (0, react_jsx_runtime$1.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
23371
24366
  open: menuOpen,
23372
24367
  onClose: () => {
23373
24368
  setMenuOpen(false);
23374
24369
  },
23375
- items: sessionMenuItems,
24370
+ items: visibleSessionMenuItems,
23376
24371
  onSelect: (id) => {
23377
24372
  setMenuOpen(false);
23378
24373
  if (id === "rename") onRename(node.id, row.title);
@@ -25088,7 +26083,6 @@ const official = ((require$1) => {
25088
26083
  searchSessions,
25089
26084
  searchResultLimit: sessions.searchResultLimit,
25090
26085
  renameSession: async (sessionId, title) => {
25091
- if (ctx.get("__dshGitWorktreeManagedGuard").blockSession(sessionId)) throw new Error("managed Session must be changed through its Worktree owner");
25092
26086
  const result = await sessions.using(sessionId, { source: "workspaceOperation" }, (reference) => reference.binding.session.rename(title));
25093
26087
  if (!result.ok) throw new Error(result.error.message);
25094
26088
  },