dsh-sessions-manager 3.6.1 → 3.7.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.
package/lib/client.js CHANGED
@@ -55,9 +55,24 @@ function dotStateFor({ manualUnread = false, dataState = "", isActive = false }
55
55
  return null;
56
56
  }
57
57
  }
58
- function authoritativeTitleForFirstPaint({ firstPaint = false, rendered = "", authoritative = "" } = {}) {
59
- if (!firstPaint || !authoritative || rendered === authoritative) return null;
60
- return authoritative;
58
+ function titleBackfillDecision({ rendered = "", baseline = null, authoritative = "" } = {}) {
59
+ const auth = authoritative || "";
60
+ if (!baseline) {
61
+ const next = auth || rendered;
62
+ return { text: next, changed: !!auth && auth !== rendered, nextBaseline: { text: next, authoritative: auth || null } };
63
+ }
64
+ if (rendered !== baseline.text) {
65
+ return { changed: false, nextBaseline: { text: rendered, authoritative: baseline.authoritative || null } };
66
+ }
67
+ if (!auth || auth === baseline.authoritative) return null;
68
+ return { text: auth, changed: true, nextBaseline: { text: auth, authoritative: auth } };
69
+ }
70
+ function effectiveTitleOf(item, authoritativeTitles) {
71
+ if (!item) return "";
72
+ if (item.title) return String(item.title);
73
+ const id = String(item.sessionId);
74
+ const auth = authoritativeTitles && authoritativeTitles.get(id);
75
+ return auth ? String(auth) : "";
61
76
  }
62
77
  function canDropOnWorkspace(item, target) {
63
78
  if (!item || !target) return false;
@@ -137,6 +152,167 @@ function toastDurationFor(text, kind) {
137
152
  const scaled = kind === "err" ? Math.round(readingMs * 1.25) : readingMs;
138
153
  return Math.min(TOAST_MAX_MS, Math.max(TOAST_MIN_MS, scaled));
139
154
  }
155
+ function pathTail(p) {
156
+ const parts = String(p == null ? "" : p).split(/[\\/]+/).filter(Boolean);
157
+ return parts.length ? parts[parts.length - 1] : "";
158
+ }
159
+ function moveNoticeText(n) {
160
+ const id = shortId(n && n.sessionId);
161
+ if (n && n.kind === "moved") {
162
+ const where = pathTail(n.targetPath);
163
+ return `\u6392\u961F\u4E2D\u7684\u79FB\u52A8\u5DF2\u5B8C\u6210\uFF1A${id}${where ? ` \u2192 \u300C${where}\u300D` : ""}`;
164
+ }
165
+ const reason = String(n && n.reason || "").split("\n")[0].slice(0, 120);
166
+ return `\u6392\u961F\u4E2D\u7684\u79FB\u52A8\u591A\u6B21\u5931\u8D25\u5DF2\u653E\u5F03\uFF1A${id}${reason ? `\uFF08${reason}\uFF09` : ""}\uFF0C\u53EF\u5728 \u8BBE\u7F6E \u2192 \u4F1A\u8BDD\u7BA1\u7406 \u2192 \u5F85\u79FB\u52A8\u961F\u5217 \u91CD\u65B0\u53D1\u8D77\u79FB\u52A8`;
167
+ }
168
+ function noticeToastPlan(raw, seenIds, max = 2) {
169
+ const list = (Array.isArray(raw) ? raw : []).filter((n) => n && typeof n.id === "string" && typeof n.sessionId === "string" && (n.kind === "moved" || n.kind === "abandoned") && !seenIds.has(String(n.id))).sort((a, b) => (a.at || 0) - (b.at || 0));
170
+ if (!list.length) return { text: null, kind: "ok", ackIds: [] };
171
+ const shown = list.slice(Math.max(0, list.length - Math.max(1, max)));
172
+ const parts = shown.map(moveNoticeText);
173
+ if (list.length > shown.length) parts.push(`\u53E6\u6709 ${list.length - shown.length} \u6761\u6392\u961F\u79FB\u52A8\u7684\u7ED3\u679C\uFF0C\u89C1 \u8BBE\u7F6E \u2192 \u4F1A\u8BDD\u7BA1\u7406 \u2192 \u5F85\u79FB\u52A8\u961F\u5217`);
174
+ return {
175
+ text: parts.join("\uFF1B"),
176
+ kind: shown.some((n) => n.kind === "abandoned") ? "err" : "ok",
177
+ ackIds: shown.map((n) => String(n.id))
178
+ };
179
+ }
180
+ function branchTimeKey(item) {
181
+ const c = Number(item && item.createdAt);
182
+ if (Number.isFinite(c) && c > 0) return c;
183
+ const u = Number(item && item.updatedAt);
184
+ if (Number.isFinite(u) && u > 0) return u;
185
+ return 0;
186
+ }
187
+ function ascBranchTime(a, b) {
188
+ const ta = String((a && a.sessionId) ?? "");
189
+ const tb = String((b && b.sessionId) ?? "");
190
+ return branchTimeKey(a) - branchTimeKey(b) || ta.localeCompare(tb);
191
+ }
192
+ function branchParentOf(id, table) {
193
+ const info = table[id];
194
+ if (!info || typeof info !== "object") return null;
195
+ if (info.origin === "subagent") return null;
196
+ const p = info.parentSession ? String(info.parentSession) : null;
197
+ if (!p || p === id) return null;
198
+ return p;
199
+ }
200
+ function foldBranches(items, lineage) {
201
+ const list = Array.isArray(items) ? items : [];
202
+ const table = lineage && typeof lineage === "object" ? lineage : {};
203
+ const byId = /* @__PURE__ */ new Map();
204
+ for (const it of list) {
205
+ if (it == null || it.sessionId == null) continue;
206
+ byId.set(String(it.sessionId), it);
207
+ }
208
+ const maxWalk = Object.keys(table).length + list.length + 2;
209
+ const anchorMemo = /* @__PURE__ */ new Map();
210
+ function resolveAnchor(id) {
211
+ if (anchorMemo.has(id)) return anchorMemo.get(id);
212
+ const seen = /* @__PURE__ */ new Set([id]);
213
+ let cur = id;
214
+ let steps = 0;
215
+ let anchor = null;
216
+ while (true) {
217
+ const p = branchParentOf(cur, table);
218
+ if (!p) {
219
+ anchor = steps === 0 ? null : cur;
220
+ break;
221
+ }
222
+ if (seen.has(p)) break;
223
+ seen.add(p);
224
+ steps++;
225
+ if (byId.has(p)) {
226
+ if (!branchParentOf(p, table)) {
227
+ anchor = p;
228
+ break;
229
+ }
230
+ cur = p;
231
+ if (steps > maxWalk) break;
232
+ continue;
233
+ }
234
+ if (!branchParentOf(p, table)) {
235
+ anchor = p;
236
+ break;
237
+ }
238
+ cur = p;
239
+ if (steps > maxWalk) break;
240
+ }
241
+ anchorMemo.set(id, anchor);
242
+ return anchor;
243
+ }
244
+ const groupsOf = /* @__PURE__ */ new Map();
245
+ const anchorOfRow = /* @__PURE__ */ new Map();
246
+ let foldedCount = 0;
247
+ for (const it of list) {
248
+ if (it == null || it.sessionId == null) continue;
249
+ const id = String(it.sessionId);
250
+ if (!branchParentOf(id, table)) continue;
251
+ const anchor = resolveAnchor(id);
252
+ if (!anchor || anchor === id) continue;
253
+ if (!groupsOf.has(anchor)) groupsOf.set(anchor, []);
254
+ groupsOf.get(anchor).push(it);
255
+ anchorOfRow.set(it, anchor);
256
+ foldedCount++;
257
+ }
258
+ const earliest = /* @__PURE__ */ new Set();
259
+ for (const [anchor, members] of groupsOf) {
260
+ members.sort(ascBranchTime);
261
+ if (!byId.has(anchor)) earliest.add(members[0]);
262
+ }
263
+ const topList = [];
264
+ const emitted = /* @__PURE__ */ new Set();
265
+ for (const it of list) {
266
+ const anchor = it == null ? void 0 : anchorOfRow.get(it);
267
+ if (anchor === void 0) {
268
+ topList.push(it);
269
+ continue;
270
+ }
271
+ if (!byId.has(anchor) && !emitted.has(anchor) && earliest.has(it)) {
272
+ emitted.add(anchor);
273
+ topList.push({ syntheticRoot: anchor, sessionId: "dsm-src:" + anchor });
274
+ }
275
+ }
276
+ for (const [anchor, members] of groupsOf) {
277
+ if (!members.length) groupsOf.delete(anchor);
278
+ }
279
+ return { topList, branchGroupsOf: groupsOf, foldedCount, groupCount: groupsOf.size };
280
+ }
281
+ function applyTagFilter(list, tagId) {
282
+ const items = Array.isArray(list) ? list : [];
283
+ if (!tagId) return items;
284
+ const want = String(tagId);
285
+ return items.filter((item) => {
286
+ const tags = item && item.tags;
287
+ return Array.isArray(tags) && tags.some((t) => String(t) === want);
288
+ });
289
+ }
290
+ function tagDeleteConfirm(name) {
291
+ return `\u5220\u9664\u6807\u7B7E\u300C${String(name == null ? "" : name) || "(\u672A\u547D\u540D)"}\u300D\uFF1F\u53EA\u5220\u6807\u7B7E\uFF0C\u4E0D\u4F1A\u5220\u9664\u4F1A\u8BDD\uFF1B\u6253\u8FC7\u8FD9\u4E2A\u6807\u7B7E\u7684\u4F1A\u8BDD\u53EA\u662F\u5931\u53BB\u5B83\u3002`;
292
+ }
293
+ var SAVED_VIEWS = ["all", "active", "archived", "starred", "empty", "trash"];
294
+ var SAVED_SORTS = ["newest", "oldest", "title"];
295
+ function filterSnapshotOf({ filter = "all", workspaceFilter = "all", sortBy = "newest", tagFilter = "" } = {}) {
296
+ return {
297
+ view: SAVED_VIEWS.includes(filter) ? filter : "all",
298
+ workspace: typeof workspaceFilter === "string" && workspaceFilter ? workspaceFilter : "all",
299
+ sort: SAVED_SORTS.includes(sortBy) ? sortBy : "newest",
300
+ tag: typeof tagFilter === "string" ? tagFilter : ""
301
+ };
302
+ }
303
+ function filterShapeFromSaved(item) {
304
+ const f = item && item.filters && typeof item.filters === "object" && !Array.isArray(item.filters) ? item.filters : {};
305
+ const degraded = [];
306
+ let view = typeof f.view === "string" && SAVED_VIEWS.includes(f.view) ? f.view : "all";
307
+ if (view === "all" && f.view !== "all") degraded.push("view");
308
+ let workspace = typeof f.workspace === "string" && f.workspace ? f.workspace : "all";
309
+ if (workspace === "all" && f.workspace !== "all") degraded.push("workspace");
310
+ let sort = typeof f.sort === "string" && SAVED_SORTS.includes(f.sort) ? f.sort : "newest";
311
+ if (sort === "newest" && f.sort !== "newest") degraded.push("sort");
312
+ let tag = typeof f.tag === "string" ? f.tag : "";
313
+ if (tag === "" && f.tag !== "") degraded.push("tag");
314
+ return { view, workspace, sort, tag, degraded };
315
+ }
140
316
 
141
317
  // src/client/index.jsx
142
318
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -178,10 +354,14 @@ var CSS = `
178
354
  .sess-fbtn-on{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
179
355
  .sess-tools{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin:0 0 8px}
180
356
  .sess-tools-4{grid-template-columns:repeat(auto-fit,minmax(132px,1fr))}
357
+ /* T4 \u8D77\u641C\u7D22\u884C\u6709 5 \u4E2A\u5B57\u6BB5\uFF08\u641C\u7D22/\u5DE5\u4F5C\u533A/\u6807\u7B7E/\u6392\u5E8F/\u5206\u7EC4\uFF09\uFF1A\u6536\u7A84\u4E0B\u9650\u4FDD\u8BC1 800px \u5185\u4E00\u884C\u6392\u6EE1\u3002 */
358
+ .sess-tools-5{grid-template-columns:repeat(auto-fit,minmax(124px,1fr))}
181
359
  .dsm-kids{display:flex;flex-direction:column;gap:6px;margin:10px 0 2px;margin-left:43px;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l3)}
182
360
  .dsm-kid .dsm-kids{margin-left:8px;margin-top:6px}
183
361
  .dsm-kids-toggle{appearance:none;min-height:22px;padding:0 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary);border-radius:var(--dsm-radius-tag);font:inherit;font-size:11px;font-weight:500;cursor:pointer;flex:none;white-space:nowrap}
184
362
  .dsm-kids-toggle:hover{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}
363
+ .dsm-src-head{display:flex;align-items:center;gap:8px;padding:5px 9px;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsm-radius-ctl);font-size:12px;color:var(--dsw-alias-label-secondary)}
364
+ .dsm-src-head .dsm-src-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
185
365
  .dsm-kid{display:flex;flex-direction:column;gap:6px;min-width:0}
186
366
  .dsm-kid-row{display:flex;align-items:center;gap:8px;min-width:0}
187
367
  .dsm-kid-name{flex:1 1 auto;min-width:0;font-size:12px;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -191,7 +371,9 @@ var CSS = `
191
371
  .sess-field{display:flex;flex-direction:column;gap:5px;min-width:0}
192
372
  .sess-field label{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary)}
193
373
  .sess-field input,.sess-field select{box-sizing:border-box;width:100%;min-height:36px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px}
194
- .sess-results{display:flex;flex-direction:column;gap:1px;font-size:11px;color:var(--dsw-alias-label-tertiary);margin:0 0 4px}
374
+ .sess-results{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--dsw-alias-label-tertiary);margin:0 0 4px}
375
+ /* T4 \u540E\u8BA1\u6570\u884C\u53F3\u4FA7\u6302\u4E86\u300C\u4FDD\u5B58\u7B5B\u9009\u300D\u63A7\u4EF6\u7EC4\uFF0C\u6B63\u6587\u72EC\u5360\u5DE6\u4FA7\u53EF\u6536\u7F29\u3002 */
376
+ .sess-results-main{flex:1 1 auto;min-width:0}
195
377
  .archv button:focus-visible,.archv input:focus-visible,.archv select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
196
378
  .sess-batch{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 2px 4px;margin-bottom:4px}
197
379
  /* issue #7\uFF1A\u52FE\u9009\u540E\u6279\u91CF\u64CD\u4F5C\u680F\u5438\u9876\u2014\u2014\u5217\u8868\u518D\u957F\uFF0C\u64CD\u4F5C\u6309\u94AE\u4E5F\u4E00\u76F4\u5728\u624B\u8FB9\u3002 */
@@ -212,6 +394,10 @@ var CSS = `
212
394
  .archv-date{font-size:11px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none}
213
395
  .dsm-branch-chip{display:inline-flex;align-items:center;flex:none;min-height:22px;padding:0 9px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary) 45%,transparent);border-radius:var(--dsm-radius-tag);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent);color:var(--dsw-alias-state-success-primary);font-size:11px;font-weight:500;line-height:1;white-space:nowrap}
214
396
  .dsm-empty-chip{display:inline-flex;align-items:center;flex:none;min-height:22px;padding:0 9px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-tag);background:var(--dsw-alias-fill-subtle);color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500;line-height:1;white-space:nowrap}
397
+ /* 3.7.0 T4 \u6807\u7B7E chip \u7CFB\uFF1A\u5361\u7247\u6807\u9898\u884C\u5185\u3001\u5206\u652F/\u7A7A\u767D chip \u4E4B\u540E\u3002\u8B66\u793A\u8272\u7CFB\u533A\u522B\u4E8E
398
+ \u5206\u652F\uFF08\u7EFF\uFF09/\u7A7A\u767D\uFF08\u7070\uFF09\uFF0C\u540D\u5B57\u8D85\u957F\u7701\u7565\u53F7\uFF1B+N \u8BA1\u6570 chip \u4E2D\u6027\u8272\u3002 */
399
+ .dsm-tagchip{display:inline-flex;align-items:center;flex:none;max-width:9em;min-height:22px;padding:0 8px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#EAB308) 45%,transparent);border-radius:var(--dsm-radius-tag);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#EAB308) 10%,transparent);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:500;line-height:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
400
+ .dsm-tagchip-more{max-width:none;color:var(--dsw-alias-label-tertiary);border-color:var(--dsw-alias-border-l2);background:var(--dsw-alias-fill-subtle)}
215
401
  .archv-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10.5px;color:var(--dsw-alias-label-tertiary);flex:none;margin-left:auto;white-space:nowrap}
216
402
  .archv-dot{color:var(--dsw-alias-border-l3);flex:none}
217
403
  .archv-check{width:15px;height:15px;accent-color:var(--dsw-alias-state-business-primary);flex:none;cursor:pointer}
@@ -243,7 +429,7 @@ var CSS = `
243
429
  /* \u5931\u8D25\u63D0\u793A\u7528\u8B66\u793A\u8272 + \u52A0\u7C97\uFF0C\u5E76\u505C\u7559\u66F4\u4E45\uFF08\u9700\u8981\u7528\u6237\u8BFB\u5B8C\u53BB\u505A\u4E0B\u4E00\u6B65\u64CD\u4F5C\uFF09\u3002 */
244
430
  .archv-status-err{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);font-weight:500}
245
431
  @media (prefers-reduced-motion:reduce){.archv-skel-card::after{animation:none}.archv-card,.archv-btn,.archv-star{transition:none}.archv-star:hover,.archv-star:active{transform:none}.archv-status,.archv-spin{animation:none}}
246
- @media (max-width:640px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}.sess-tools,.sess-tools-4{grid-template-columns:1fr}.sess-fbtn,.archv-btn{min-height:40px}.archv-star{width:30px;height:30px}.archv-star svg{width:22px;height:22px}.dsm-kids{margin-left:10px}.archv-titlerow{flex-wrap:wrap}.dsm-kid-row{flex-wrap:wrap}.dsm-kid-acts{margin-left:0}}
432
+ @media (max-width:640px){.archv-card{flex-direction:column;align-items:stretch;gap:10px}.archv-actions{justify-content:flex-end}.sess-tools,.sess-tools-4,.sess-tools-5{grid-template-columns:1fr}.sess-fbtn,.archv-btn{min-height:40px}.archv-star{width:30px;height:30px}.archv-star svg{width:22px;height:22px}.dsm-kids{margin-left:10px}.archv-titlerow{flex-wrap:wrap}.dsm-kid-row{flex-wrap:wrap}.dsm-kid-acts{margin-left:0}}
247
433
  .mv-sheet{width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:12px;margin-top:12px;padding:14px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-sheet);background:var(--dsw-alias-fill-subtle)}
248
434
  .mv-sheet-head{display:flex;align-items:center;justify-content:space-between;gap:10px}
249
435
  .mv-sheet-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}
@@ -292,6 +478,25 @@ var CSS = `
292
478
  .dlg-title{font-size:15px;font-weight:650;color:var(--dsw-alias-label-primary);margin:0}
293
479
  .dlg-text{font-size:13px;line-height:1.6;color:var(--dsw-alias-label-secondary);margin:0;word-break:break-all}
294
480
  .dlg-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}
481
+ /* ---- T4 \u6807\u7B7E\u7F16\u8F91/\u7BA1\u7406 sheet \u4E0E\u7B5B\u9009\u4FDD\u5B58\u63A7\u4EF6\uFF08\u5168\u90E8\u8D70 --dsw token\uFF09 ---- */
482
+ .dsm-taglist{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow:auto}
483
+ .dsm-tagcheck{display:flex;align-items:center;gap:8px;min-width:0;padding:5px 2px;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:7px}
484
+ .dsm-tagcheck:hover{background:var(--dsw-alias-interactive-bg-hover)}
485
+ .dsm-tagcheck input{width:15px;height:15px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer;flex:none}
486
+ .dsm-tagcheck-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
487
+ .dsm-tagrow{display:flex;align-items:center;gap:8px;min-width:0;padding:6px 0;border-bottom:1px solid var(--dsw-alias-border-l2)}
488
+ .dsm-tagrow:last-child{border-bottom:none}
489
+ .dsm-tag-name{flex:0 1 auto;min-width:0;font-size:12.5px;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
490
+ .dsm-tag-input{box-sizing:border-box;flex:1 1 auto;min-width:0;min-height:28px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font-size:12px;font-family:inherit}
491
+ .dsm-tag-input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
492
+ .dsm-tag-acts{display:flex;align-items:center;gap:6px;flex:none;margin-left:auto}
493
+ .dsm-tag-acts .archv-btn{min-height:26px;padding:0 9px;font-size:11px}
494
+ .dsm-tag-acts select{appearance:none;min-height:26px;padding:0 6px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-secondary);font-size:11px;font-family:inherit;max-width:10em}
495
+ .dsm-fbar{display:flex;align-items:center;gap:6px;flex:none;margin-left:auto;flex-wrap:wrap}
496
+ .dsm-fbar .archv-btn{min-height:24px;padding:0 8px;font-size:11px}
497
+ .dsm-fbar select{appearance:none;min-height:24px;padding:0 6px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-secondary);font-size:11px;font-family:inherit;max-width:12em}
498
+ .dsm-fbar input{box-sizing:border-box;min-width:9em;min-height:24px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsm-radius-ctl);background:var(--dsw-alias-fill-elevated);color:var(--dsw-alias-label-primary);font-size:11px;font-family:inherit}
499
+ @media (max-width:640px){.dsm-fbar{margin-left:0;width:100%;justify-content:flex-end}.dsm-tag-acts{margin-left:auto;flex-wrap:wrap;justify-content:flex-end}}
295
500
  `;
296
501
  function fmtDate(iso) {
297
502
  if (!iso) return null;
@@ -448,11 +653,22 @@ function SessionPanel({ workspacesSvc }) {
448
653
  const [toast, setToast] = (0, import_react.useState)(null);
449
654
  const [picking, setPicking] = (0, import_react.useState)(false);
450
655
  const [trash, setTrash] = (0, import_react.useState)([]);
656
+ const [pendingQueue, setPendingQueue] = (0, import_react.useState)([]);
657
+ const cancelQueuedMove = async (sid) => {
658
+ try {
659
+ await postJSON("/archived-sessions/pending-moves/cancel", { sessionIds: [sid] });
660
+ showToast("\u5DF2\u53D6\u6D88\u6392\u961F\uFF0C\u4F1A\u8BDD\u7559\u5728\u539F\u5DE5\u4F5C\u533A", "ok");
661
+ setPendingQueue((q) => q.filter((x) => String(x.sessionId) !== String(sid)));
662
+ } catch (e) {
663
+ showToast("\u53D6\u6D88\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
664
+ }
665
+ };
451
666
  const [trashBusy, setTrashBusy] = (0, import_react.useState)(null);
452
667
  const [trashSettings, setTrashSettings] = (0, import_react.useState)({ retentionDays: 0 });
453
668
  const [trashCheck, setTrashCheck] = (0, import_react.useState)(null);
454
669
  const [purgeTarget, setPurgeTarget] = (0, import_react.useState)(null);
455
670
  const [details, setDetails] = (0, import_react.useState)({});
671
+ const detailsAt = (0, import_react.useRef)({});
456
672
  const [openDetails, setOpenDetails] = (0, import_react.useState)(null);
457
673
  const [detailsLoading, setDetailsLoading] = (0, import_react.useState)(null);
458
674
  const [mdBusy, setMdBusy] = (0, import_react.useState)(null);
@@ -472,7 +688,297 @@ function SessionPanel({ workspacesSvc }) {
472
688
  const dialogRef = (0, import_react.useRef)(null);
473
689
  const [lineage, setLineage] = (0, import_react.useState)({});
474
690
  const [openKids, setOpenKids] = (0, import_react.useState)({});
691
+ const [openBranches, setOpenBranches] = (0, import_react.useState)(() => {
692
+ try {
693
+ const v = JSON.parse(localStorage.getItem("dsm-branch-open-v1") || "[]");
694
+ const o = {};
695
+ for (const k of Array.isArray(v) ? v : []) if (typeof k === "string") o[k] = true;
696
+ return o;
697
+ } catch (e) {
698
+ return {};
699
+ }
700
+ });
701
+ const toggleBranch = (rootId) => setOpenBranches((prev) => {
702
+ const next = Object.assign({}, prev);
703
+ if (next[rootId]) delete next[rootId];
704
+ else next[rootId] = true;
705
+ try {
706
+ localStorage.setItem("dsm-branch-open-v1", JSON.stringify(Object.keys(next)));
707
+ } catch (e) {
708
+ }
709
+ return next;
710
+ });
475
711
  const [groupByLineage, setGroupByLineage] = (0, import_react.useState)(() => initialPrefs.groupByLineage !== false);
712
+ const [tagDefs, setTagDefs] = (0, import_react.useState)([]);
713
+ const [assignments, setAssignments] = (0, import_react.useState)({});
714
+ const [tagsState, setTagsState] = (0, import_react.useState)("loading");
715
+ const tagsReady = tagsState === "ready";
716
+ const [tagFilter, setTagFilter] = (0, import_react.useState)("");
717
+ const [savedFilters, setSavedFilters] = (0, import_react.useState)([]);
718
+ const [openTags, setOpenTags] = (0, import_react.useState)(null);
719
+ const [openTagMgr, setOpenTagMgr] = (0, import_react.useState)(false);
720
+ const [tagBusy, setTagBusy] = (0, import_react.useState)(null);
721
+ const [mgrBusy, setMgrBusy] = (0, import_react.useState)(false);
722
+ const [cardTagName, setCardTagName] = (0, import_react.useState)("");
723
+ const [mgrTagName, setMgrTagName] = (0, import_react.useState)("");
724
+ const [renamingTagId, setRenamingTagId] = (0, import_react.useState)(null);
725
+ const [renameTagValue, setRenameTagValue] = (0, import_react.useState)("");
726
+ const [mergeTarget, setMergeTarget] = (0, import_react.useState)({});
727
+ const [saveFilterOpen, setSaveFilterOpen] = (0, import_react.useState)(false);
728
+ const [saveFilterName, setSaveFilterName] = (0, import_react.useState)("");
729
+ const [appliedSavedId, setAppliedSavedId] = (0, import_react.useState)("");
730
+ const tagFilterRef = (0, import_react.useRef)("");
731
+ tagFilterRef.current = tagFilter;
732
+ const tagMap = (0, import_react.useMemo)(() => new Map(tagDefs.map((t) => [String(t.id), String(t.name)])), [tagDefs]);
733
+ const tagUsage = (0, import_react.useMemo)(() => {
734
+ const m = {};
735
+ for (const ids of Object.values(assignments)) for (const id of Array.isArray(ids) ? ids : []) m[String(id)] = (m[String(id)] || 0) + 1;
736
+ return m;
737
+ }, [assignments]);
738
+ const tagsBlockedTitle = tagsState === "failed" ? "\u6807\u7B7E\u6570\u636E\u672A\u80FD\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u4F7F\u7528\u6807\u7B7E\u529F\u80FD\uFF08\u91CD\u65B0\u6253\u5F00\u8BBE\u7F6E\u9762\u677F\u53EF\u91CD\u8BD5\uFF09" : "\u6807\u7B7E\u6570\u636E\u52A0\u8F7D\u4E2D\uFF0C\u7A0D\u5019\u5373\u53EF\u4F7F\u7528";
739
+ const loadTags = () => postJSON("/archived-sessions/tags/list", {}).then((r) => {
740
+ const defs = r && Array.isArray(r.tags) ? r.tags : [];
741
+ setTagDefs(defs);
742
+ setAssignments(r && r.assignments && typeof r.assignments === "object" && !Array.isArray(r.assignments) ? r.assignments : {});
743
+ setTagsState("ready");
744
+ const cur = tagFilterRef.current;
745
+ if (cur && !defs.some((t) => String(t.id) === cur)) setTagFilter("");
746
+ }).catch(() => {
747
+ setTagDefs([]);
748
+ setAssignments({});
749
+ setTagsState("failed");
750
+ if (tagFilterRef.current) setTagFilter("");
751
+ });
752
+ const loadSavedFilters = () => postJSON("/archived-sessions/filters/list", {}).then((r) => setSavedFilters(r && Array.isArray(r.items) ? r.items : [])).catch(() => setSavedFilters([]));
753
+ const setSessionTags = async (sid, nextIds) => {
754
+ const key = String(sid);
755
+ const prev = Array.isArray(assignments[key]) ? assignments[key].slice() : [];
756
+ if (prev.length === nextIds.length && prev.every((t, i) => String(t) === String(nextIds[i]))) return;
757
+ const writeLocal = (ids) => {
758
+ setAssignments((a) => {
759
+ const n = Object.assign({}, a);
760
+ if (ids.length) n[key] = ids;
761
+ else delete n[key];
762
+ return n;
763
+ });
764
+ setSessions((s) => s && s.map((x) => String(x.sessionId) === key ? Object.assign({}, x, { tags: ids }) : x));
765
+ };
766
+ writeLocal(nextIds);
767
+ setTagBusy(key);
768
+ try {
769
+ const r = await postJSON("/archived-sessions/tags/set", { sessionId: sid, tagIds: nextIds });
770
+ const row = r && r.assignments && Array.isArray(r.assignments[key]) ? r.assignments[key].map(String) : nextIds.map(String);
771
+ writeLocal(row);
772
+ } catch (e) {
773
+ writeLocal(prev);
774
+ showToast("\u6807\u7B7E\u8BBE\u7F6E\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
775
+ } finally {
776
+ setTagBusy(null);
777
+ }
778
+ };
779
+ const toggleTagFor = (sid, tagId) => {
780
+ if (tagBusy !== null) return;
781
+ const key = String(sid);
782
+ const cur = Array.isArray(assignments[key]) ? assignments[key] : [];
783
+ const has = cur.some((t) => String(t) === String(tagId));
784
+ const next = has ? cur.filter((t) => String(t) !== String(tagId)) : cur.concat(String(tagId));
785
+ setSessionTags(sid, next);
786
+ };
787
+ const createTagAndAttach = async (sid) => {
788
+ const name = cardTagName.trim();
789
+ if (!name || tagBusy !== null) return;
790
+ setTagBusy(String(sid));
791
+ let fresh = null;
792
+ try {
793
+ const r = await postJSON("/archived-sessions/tags/create", { name });
794
+ if (r && r.tag && r.tag.id != null) fresh = r.tag;
795
+ else throw new Error("\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u65B0\u6807\u7B7E");
796
+ } catch (e) {
797
+ showToast("\u65B0\u5EFA\u6807\u7B7E\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
798
+ }
799
+ if (!fresh) {
800
+ setTagBusy(null);
801
+ return;
802
+ }
803
+ setTagDefs((d) => d.some((t) => String(t.id) === String(fresh.id)) ? d : d.concat(fresh));
804
+ const cur = Array.isArray(assignments[String(sid)]) ? assignments[String(sid)].slice() : [];
805
+ setCardTagName("");
806
+ await setSessionTags(sid, cur.concat(String(fresh.id)));
807
+ };
808
+ const createTag = async () => {
809
+ const name = mgrTagName.trim();
810
+ if (!name || mgrBusy) return;
811
+ setMgrBusy(true);
812
+ try {
813
+ const r = await postJSON("/archived-sessions/tags/create", { name });
814
+ if (r && r.tag && r.tag.id != null) {
815
+ const fresh = r.tag;
816
+ setTagDefs((d) => d.some((t) => String(t.id) === String(fresh.id)) ? d : d.concat(fresh));
817
+ setMgrTagName("");
818
+ }
819
+ } catch (e) {
820
+ showToast("\u65B0\u5EFA\u6807\u7B7E\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
821
+ } finally {
822
+ setMgrBusy(false);
823
+ }
824
+ };
825
+ const commitTagRename = async (tag) => {
826
+ const name = renameTagValue.trim();
827
+ if (!name || mgrBusy) {
828
+ setRenamingTagId(null);
829
+ return;
830
+ }
831
+ if (name === tag.name) {
832
+ setRenamingTagId(null);
833
+ return;
834
+ }
835
+ setMgrBusy(true);
836
+ try {
837
+ await postJSON("/archived-sessions/tags/rename", { id: tag.id, name });
838
+ setTagDefs((d) => d.map((t) => String(t.id) === String(tag.id) ? Object.assign({}, t, { name }) : t));
839
+ setRenamingTagId(null);
840
+ } catch (e) {
841
+ showToast("\u91CD\u547D\u540D\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
842
+ } finally {
843
+ setMgrBusy(false);
844
+ }
845
+ };
846
+ const deleteTag = async (tag) => {
847
+ if (mgrBusy) return;
848
+ if (!window.confirm(tagDeleteConfirm(tag.name))) return;
849
+ setMgrBusy(true);
850
+ try {
851
+ await postJSON("/archived-sessions/tags/delete", { id: tag.id });
852
+ const gone = String(tag.id);
853
+ setTagDefs((d) => d.filter((t) => String(t.id) !== gone));
854
+ setAssignments((a) => {
855
+ const n = {};
856
+ for (const [sid, ids] of Object.entries(a)) {
857
+ const kept = (Array.isArray(ids) ? ids : []).filter((id) => String(id) !== gone);
858
+ if (kept.length) n[sid] = kept;
859
+ }
860
+ return n;
861
+ });
862
+ setSessions((s) => s && s.map((x) => {
863
+ const curT = Array.isArray(x.tags) ? x.tags : [];
864
+ if (!curT.some((id) => String(id) === gone)) return x;
865
+ return Object.assign({}, x, { tags: curT.filter((id) => String(id) !== gone) });
866
+ }));
867
+ if (tagFilterRef.current === gone) setTagFilter("");
868
+ setMergeTarget((m) => {
869
+ const n = Object.assign({}, m);
870
+ delete n[gone];
871
+ return n;
872
+ });
873
+ } catch (e) {
874
+ showToast("\u5220\u9664\u6807\u7B7E\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
875
+ } finally {
876
+ setMgrBusy(false);
877
+ }
878
+ };
879
+ const mergeTag = async (from) => {
880
+ const toId = mergeTarget[String(from.id)];
881
+ if (!toId || mgrBusy) return;
882
+ setMgrBusy(true);
883
+ try {
884
+ await postJSON("/archived-sessions/tags/merge", { fromId: from.id, toId });
885
+ const gone = String(from.id);
886
+ setTagDefs((d) => d.filter((t) => String(t.id) !== gone));
887
+ setAssignments((a) => {
888
+ const n = {};
889
+ for (const [sid, ids] of Object.entries(a)) {
890
+ const kept = [];
891
+ const seen = /* @__PURE__ */ new Set();
892
+ for (const id of Array.isArray(ids) ? ids : []) {
893
+ const v = String(id) === gone ? String(toId) : String(id);
894
+ if (seen.has(v)) continue;
895
+ seen.add(v);
896
+ kept.push(v);
897
+ }
898
+ if (kept.length) n[sid] = kept;
899
+ }
900
+ return n;
901
+ });
902
+ if (tagFilterRef.current === gone) setTagFilter(String(toId));
903
+ setMergeTarget((m) => {
904
+ const n = Object.assign({}, m);
905
+ delete n[gone];
906
+ return n;
907
+ });
908
+ setSessions((s) => s && s.map((x) => {
909
+ const curT = Array.isArray(x.tags) ? x.tags : [];
910
+ if (!curT.some((id) => String(id) === gone)) return x;
911
+ const nextT = [];
912
+ const seenT = /* @__PURE__ */ new Set();
913
+ for (const id of curT) {
914
+ const v = String(id) === gone ? String(toId) : String(id);
915
+ if (seenT.has(v)) continue;
916
+ seenT.add(v);
917
+ nextT.push(v);
918
+ }
919
+ return Object.assign({}, x, { tags: nextT });
920
+ }));
921
+ showToast(`\u5DF2\u5E76\u5165\u300C${tagMap.get(String(toId)) || "\u76EE\u6807\u6807\u7B7E"}\u300D`);
922
+ } catch (e) {
923
+ showToast("\u5408\u5E76\u6807\u7B7E\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
924
+ } finally {
925
+ setMgrBusy(false);
926
+ }
927
+ };
928
+ const saveCurrentFilter = async () => {
929
+ const name = saveFilterName.trim();
930
+ if (!name || mgrBusy) return;
931
+ setMgrBusy(true);
932
+ try {
933
+ const r = await postJSON("/archived-sessions/filters/save", {
934
+ name,
935
+ filters: filterSnapshotOf({ filter, workspaceFilter, sortBy, tagFilter })
936
+ });
937
+ if (r && r.item) setSavedFilters((l) => l.some((x) => String(x.id) === String(r.item.id)) ? l : l.concat(r.item));
938
+ setSaveFilterOpen(false);
939
+ setSaveFilterName("");
940
+ showToast(`\u5DF2\u4FDD\u5B58\u7B5B\u9009\u300C${r && r.item && r.item.name || name}\u300D`);
941
+ } catch (e) {
942
+ showToast("\u4FDD\u5B58\u7B5B\u9009\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
943
+ } finally {
944
+ setMgrBusy(false);
945
+ }
946
+ };
947
+ const applySavedFilter = (id) => {
948
+ const key = String(id);
949
+ setAppliedSavedId(key);
950
+ if (!key) return;
951
+ const item = savedFilters.find((x) => String(x.id) === key);
952
+ if (!item) return;
953
+ const shape = filterShapeFromSaved(item);
954
+ const tagUnavailable = !!shape.tag && (!tagsReady || !tagMap.has(shape.tag));
955
+ const degraded = shape.degraded.slice();
956
+ if (tagUnavailable) degraded.push("tag");
957
+ setFilter(shape.view);
958
+ clearSel();
959
+ setConfirmBatch(false);
960
+ if (shape.view === "starred" || shape.view === "empty" || shape.view === "trash") setMoreOpen(true);
961
+ setWorkspaceFilter(shape.workspace);
962
+ setSortBy(shape.sort);
963
+ setTagFilter(tagUnavailable ? "" : shape.tag);
964
+ if (degraded.length) {
965
+ const labels = { view: "\u89C6\u56FE", workspace: "\u5DE5\u4F5C\u533A", sort: "\u6392\u5E8F", tag: "\u6807\u7B7E" };
966
+ showToast(`\u5DF2\u5E94\u7528\u7B5B\u9009\u300C${item.name}\u300D\uFF0C\u4F46\u5176\u4E2D ${[...new Set(degraded)].map((k) => labels[k] || k).join("\u3001")} \u6761\u4EF6\u5DF2\u5931\u6548\u5E76\u56DE\u843D\u9ED8\u8BA4`, "err");
967
+ }
968
+ };
969
+ const deleteSavedFilter = async (id) => {
970
+ if (mgrBusy) return;
971
+ setMgrBusy(true);
972
+ try {
973
+ await postJSON("/archived-sessions/filters/delete", { ids: [id] });
974
+ setSavedFilters((l) => l.filter((x) => String(x.id) !== String(id)));
975
+ if (String(appliedSavedId) === String(id)) setAppliedSavedId("");
976
+ } catch (e) {
977
+ showToast("\u5220\u9664\u5DF2\u5B58\u7B5B\u9009\u5931\u8D25\uFF1A" + String(e && e.message || e), "err");
978
+ } finally {
979
+ setMgrBusy(false);
980
+ }
981
+ };
476
982
  const loadLineage = () => postJSON("/archived-sessions/sidebar-state", {}).then((r) => setLineage(r && r.lineage || {})).catch(() => {
477
983
  });
478
984
  const showToast = (msg, kind) => {
@@ -499,6 +1005,10 @@ function SessionPanel({ workspacesSvc }) {
499
1005
  loadLineage();
500
1006
  if (!targetWs && works.items && works.items.length) setTargetWs(works.items[0].workspaceId);
501
1007
  loadTrash();
1008
+ loadTags();
1009
+ loadSavedFilters();
1010
+ postJSON("/archived-sessions/pending-moves", {}).then((q) => setPendingQueue(q && q.items || [])).catch(() => {
1011
+ });
502
1012
  if (storageOpen) loadStorage();
503
1013
  else storageDirty.current = true;
504
1014
  }).catch((e) => setError(String(e && e.message || e)));
@@ -509,6 +1019,12 @@ function SessionPanel({ workspacesSvc }) {
509
1019
  if (timer.current) clearTimeout(timer.current);
510
1020
  };
511
1021
  }, []);
1022
+ const refreshRef = (0, import_react.useRef)(null);
1023
+ refreshRef.current = refresh;
1024
+ (0, import_react.useEffect)(() => dsmOnWarmDone(() => {
1025
+ if (refreshRef.current) refreshRef.current();
1026
+ }), []);
1027
+ (0, import_react.useEffect)(() => dsmOnNotice((text, kind) => showToast(text, kind)), []);
512
1028
  (0, import_react.useEffect)(() => {
513
1029
  try {
514
1030
  localStorage.setItem(PANEL_PREFS_KEY, JSON.stringify({ filter, workspaceFilter, sortBy, groupByLineage }));
@@ -610,29 +1126,40 @@ function SessionPanel({ workspacesSvc }) {
610
1126
  const list = (0, import_react.useMemo)(() => {
611
1127
  if (filter === "trash") return [];
612
1128
  const base = filter === "archived" ? archivedList : filter === "active" ? activeList : filter === "starred" ? starredList : filter === "empty" ? emptyList : sessions || [];
1129
+ const tagPassed = applyTagFilter(base, tagsReady ? tagFilter : "");
613
1130
  const needle = query.trim().toLocaleLowerCase();
614
- const filtered = base.filter((item) => {
1131
+ const filtered = tagPassed.filter((item) => {
615
1132
  if (workspaceFilter !== "all" && (item.workspacePath || "") !== workspaceFilter) return false;
616
1133
  if (!needle) return true;
617
- return [item.title, item.sessionId, item.workspaceTitle, item.workspacePath].some((value) => String(value || "").toLocaleLowerCase().includes(needle));
1134
+ return [effectiveTitleOf(item, dsmAuthoritativeTitles), item.sessionId, item.workspaceTitle, item.workspacePath].some((value) => String(value || "").toLocaleLowerCase().includes(needle));
618
1135
  });
619
1136
  return [...filtered].sort((a, b) => {
620
1137
  if (sortBy === "oldest") return Number(a.createdAt || 0) - Number(b.createdAt || 0);
621
- if (sortBy === "title") return String(a.title || "").localeCompare(String(b.title || ""), "zh-CN");
1138
+ if (sortBy === "title") return effectiveTitleOf(a, dsmAuthoritativeTitles).localeCompare(effectiveTitleOf(b, dsmAuthoritativeTitles), "zh-CN");
622
1139
  return Number(b.createdAt || 0) - Number(a.createdAt || 0);
623
1140
  });
624
- }, [sessions, filter, query, workspaceFilter, sortBy, emptyList, starredList]);
1141
+ }, [sessions, filter, query, workspaceFilter, sortBy, emptyList, starredList, tagFilter, tagsReady]);
625
1142
  const selIds = Object.keys(selected).filter((k) => selected[k]);
626
1143
  const showSessionList = filter !== "trash";
627
- const { topList, kidsOf, foldedCount } = (0, import_react.useMemo)(() => {
628
- if (!groupByLineage) return { topList: list, kidsOf: /* @__PURE__ */ new Map(), foldedCount: 0 };
629
- return foldSubagents(list, lineage);
1144
+ const { topList, kidsOf, foldedCount, branchGroupsOf, branchFolded, branchGroupCount } = (0, import_react.useMemo)(() => {
1145
+ if (!groupByLineage) return { topList: list, kidsOf: /* @__PURE__ */ new Map(), foldedCount: 0, branchGroupsOf: /* @__PURE__ */ new Map(), branchFolded: 0, branchGroupCount: 0 };
1146
+ const kids = foldSubagents(list, lineage);
1147
+ const br = foldBranches(kids.topList, lineage);
1148
+ return { topList: br.topList, kidsOf: kids.kidsOf, foldedCount: kids.foldedCount, branchGroupsOf: br.branchGroupsOf, branchFolded: br.foldedCount, branchGroupCount: br.groupCount };
630
1149
  }, [list, lineage, groupByLineage]);
1150
+ const matchIds = (0, import_react.useMemo)(() => {
1151
+ const needle = query.trim().toLocaleLowerCase();
1152
+ if (!needle) return null;
1153
+ return new Set((sessions || []).filter((item) => [effectiveTitleOf(item, dsmAuthoritativeTitles), item.sessionId, item.workspaceTitle, item.workspacePath].some((v) => String(v || "").toLocaleLowerCase().includes(needle))).map((i) => String(i.sessionId)));
1154
+ }, [sessions, query]);
1155
+ const kidsHit = (parentId) => !!(matchIds && (kidsOf.get(String(parentId)) || []).some((k) => matchIds.has(String(k.sessionId))));
1156
+ const branchHit = (rootId) => !!(matchIds && (branchGroupsOf.get(String(rootId)) || []).some((m) => matchIds.has(String(m.sessionId))));
1157
+ const syntheticCount = groupByLineage ? topList.reduce((n, it) => n + (it && it.syntheticRoot ? 1 : 0), 0) : 0;
631
1158
  const kidsBadge = (sessionId) => {
632
1159
  if (!groupByLineage) return null;
633
1160
  const kids = kidsOf.get(String(sessionId)) || [];
634
1161
  if (!kids.length) return null;
635
- const open = !!openKids[sessionId];
1162
+ const open = !!openKids[sessionId] || kidsHit(sessionId);
636
1163
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
637
1164
  "button",
638
1165
  {
@@ -663,9 +1190,28 @@ function SessionPanel({ workspacesSvc }) {
663
1190
  if (!li || !li.empty) return null;
664
1191
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-empty-chip", title: "\u65E0\u5185\u5BB9\u7684\u7A7A\u767D\u4F1A\u8BDD", children: "\u7A7A\u767D" });
665
1192
  };
1193
+ const tagChips = (sessionId) => {
1194
+ if (!tagsReady) return null;
1195
+ const ids = Array.isArray(assignments[String(sessionId)]) ? assignments[String(sessionId)] : [];
1196
+ const names = [];
1197
+ for (const id of ids) {
1198
+ const n = tagMap.get(String(id));
1199
+ if (n) names.push(n);
1200
+ }
1201
+ if (!names.length) return null;
1202
+ const shown = names.slice(0, 3);
1203
+ const rest = names.slice(3);
1204
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1205
+ shown.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-tagchip", title: "\u6807\u7B7E\uFF1A" + n, children: n }, i)),
1206
+ rest.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-tagchip dsm-tagchip-more", title: "\u53E6\u6709\u6807\u7B7E\uFF1A" + rest.join("\u3001"), children: [
1207
+ "+",
1208
+ rest.length
1209
+ ] })
1210
+ ] });
1211
+ };
666
1212
  const renderKids = (parentId, depth) => {
667
1213
  const kids = kidsOf.get(String(parentId)) || [];
668
- if (!kids.length || !openKids[parentId]) return null;
1214
+ if (!kids.length || !(openKids[parentId] || kidsHit(parentId))) return null;
669
1215
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-kids", children: kids.map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-kid", children: [
670
1216
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-kid-row", children: [
671
1217
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-kid-name", title: k.title || k.sessionId, children: k.title || "(\u65E0\u6807\u9898)" }),
@@ -687,6 +1233,56 @@ function SessionPanel({ workspacesSvc }) {
687
1233
  (depth || 0) < 4 && renderKids(k.sessionId, (depth || 0) + 1)
688
1234
  ] }, k.sessionId)) });
689
1235
  };
1236
+ const branchGroupBadge = (sessionId) => {
1237
+ if (!groupByLineage) return null;
1238
+ const members = branchGroupsOf.get(String(sessionId)) || [];
1239
+ if (!members.length) return null;
1240
+ const open = !!openBranches[sessionId] || branchHit(sessionId);
1241
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1242
+ "button",
1243
+ {
1244
+ type: "button",
1245
+ className: "dsm-kids-toggle",
1246
+ "aria-expanded": open,
1247
+ title: (open ? "\u6536\u8D77 " : "\u5C55\u5F00 ") + members.length + " \u4E2A\u5206\u652F\u4F1A\u8BDD",
1248
+ onClick: () => toggleBranch(sessionId),
1249
+ children: [
1250
+ open ? "\u25BE" : "\u25B8",
1251
+ " ",
1252
+ members.length,
1253
+ " \u5206\u652F"
1254
+ ]
1255
+ }
1256
+ );
1257
+ };
1258
+ const renderBranchGroup = (rootId) => {
1259
+ const members = branchGroupsOf.get(String(rootId)) || [];
1260
+ if (!members.length || !(openBranches[rootId] || branchHit(rootId))) return null;
1261
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-kids", "aria-label": "\u5206\u652F\u4F1A\u8BDD", children: members.map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-kid", children: [
1262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-kid-row", children: [
1263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-kid-name", title: k.title || k.sessionId, children: k.title || "(\u65E0\u6807\u9898)" }),
1264
+ branchBadge(k.sessionId),
1265
+ emptyBadge(k.sessionId),
1266
+ kidsBadge(k.sessionId),
1267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-kid-meta", children: [
1268
+ k.archived ? "\u5DF2\u5F52\u6863" : "\u6D3B\u52A8",
1269
+ fmtDate(k.createdAt) ? ` \xB7 ${fmtDate(k.createdAt)}` : ""
1270
+ ] }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-kid-acts", children: [
1272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: busy !== null, title: "\u5207\u6362\u5230\u8FD9\u4E2A\u5206\u652F\u4F1A\u8BDD\uFF08\u8BBE\u7F6E\u9762\u677F\u6321\u7740\u4F1A\u8BDD\u533A\uFF0C\u5173\u6389\u5373\u53EF\u770B\u5230\uFF09", onClick: async () => {
1273
+ const name = k.title || String(k.sessionId).slice(0, 8) + "\u2026";
1274
+ const li = lineage[String(k.sessionId)];
1275
+ const res = await dsmOpenSessionById(k.sessionId, li && li.parentSession || rootId);
1276
+ const msg = openSubagentToast(res, name, "panel");
1277
+ showToast(msg.text, msg.kind);
1278
+ }, children: "\u6253\u5F00" }),
1279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: busy !== null, onClick: () => act(k.archived ? "restore" : "archive", k), children: k.archived ? "\u6062\u590D" : "\u5F52\u6863" }),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: busy !== null, title: "\u79FB\u5165\u56DE\u6536\u7AD9\uFF0C\u53EF\u5728\u56DE\u6536\u7AD9\u6062\u590D", onClick: () => setDelTarget(k), children: "\u5220\u9664" })
1281
+ ] })
1282
+ ] }),
1283
+ renderKids(k.sessionId, 1)
1284
+ ] }, k.sessionId)) });
1285
+ };
690
1286
  const toggle = (id) => setSelected((s) => ({ ...s, [id]: !s[id] }));
691
1287
  const clearSel = () => setSelected({});
692
1288
  const selectAll = () => {
@@ -960,6 +1556,7 @@ function SessionPanel({ workspacesSvc }) {
960
1556
  setOpenMove(null);
961
1557
  return;
962
1558
  }
1559
+ setOpenTags(null);
963
1560
  setTargetWs(workspaces.length ? targetWs || workspaces[0].workspaceId : "");
964
1561
  setMoveMode("existing");
965
1562
  setNewPath("");
@@ -987,10 +1584,19 @@ function SessionPanel({ workspacesSvc }) {
987
1584
  }
988
1585
  if (details[it.sessionId]) {
989
1586
  setOpenDetails(it.sessionId);
1587
+ const at = detailsAt.current[it.sessionId] || 0;
1588
+ if (Date.now() - at > 3e4) {
1589
+ postJSON("/archived-sessions/details", { sessionId: it.sessionId }).then((d) => {
1590
+ detailsAt.current[it.sessionId] = Date.now();
1591
+ setDetails((m) => ({ ...m, [it.sessionId]: d }));
1592
+ }).catch(() => {
1593
+ });
1594
+ }
990
1595
  return;
991
1596
  }
992
1597
  setDetailsLoading(it.sessionId);
993
1598
  postJSON("/archived-sessions/details", { sessionId: it.sessionId }).then((d) => {
1599
+ detailsAt.current[it.sessionId] = Date.now();
994
1600
  setDetails((m) => ({ ...m, [it.sessionId]: d }));
995
1601
  setDetailsLoading(null);
996
1602
  setOpenDetails(it.sessionId);
@@ -1010,21 +1616,36 @@ function SessionPanel({ workspacesSvc }) {
1010
1616
  if (wName) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag", title: it.workspacePath || "", children: wName });
1011
1617
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-wtag", children: "\u672A\u5206\u7EC4" });
1012
1618
  };
1619
+ const openTagsFor = (it) => {
1620
+ if (!tagsReady) {
1621
+ showToast(tagsState === "failed" ? "\u6807\u7B7E\u6570\u636E\u672A\u80FD\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u7F16\u8F91\u6807\u7B7E\uFF1B\u91CD\u65B0\u6253\u5F00\u8BBE\u7F6E\u9762\u677F\u53EF\u91CD\u8BD5" : "\u6807\u7B7E\u6570\u636E\u8FD8\u5728\u52A0\u8F7D\u4E2D\uFF0C\u7A0D\u5019\u518D\u8BD5", "err");
1622
+ return;
1623
+ }
1624
+ if (openTags === it.sessionId) {
1625
+ setOpenTags(null);
1626
+ return;
1627
+ }
1628
+ setOpenMove(null);
1629
+ setCardTagName("");
1630
+ setOpenTags(it.sessionId);
1631
+ };
1013
1632
  const runMenu = (id, it) => {
1014
1633
  setOpenMenu(null);
1015
1634
  if (id === "restore") act("restore", it);
1016
1635
  else if (id === "archive") act("archive", it);
1017
1636
  else if (id === "delete") setDelTarget(it);
1018
1637
  else if (id === "move") openMoveFor(it);
1638
+ else if (id === "tags") openTagsFor(it);
1019
1639
  else if (id === "details") toggleDetails(it);
1020
1640
  };
1021
1641
  const rowMenu = (it) => {
1022
1642
  const items = it.archived ? [
1023
1643
  ["restore", "\u6062\u590D"],
1024
1644
  ["move", openMove === it.sessionId ? "\u6536\u8D77\u79FB\u52A8" : "\u79FB\u52A8"],
1645
+ ["tags", "\u6807\u7B7E"],
1025
1646
  ["details", openDetails === it.sessionId ? "\u6536\u8D77\u8BE6\u60C5" : "\u8BE6\u60C5"],
1026
1647
  ["delete", "\u5220\u9664"]
1027
- ] : [["archive", "\u5F52\u6863"], ["move", "\u79FB\u52A8"], ["details", "\u8BE6\u60C5"], ["delete", "\u5220\u9664"]];
1648
+ ] : [["archive", "\u5F52\u6863"], ["move", "\u79FB\u52A8"], ["tags", openTags === it.sessionId ? "\u6536\u8D77\u6807\u7B7E" : "\u6807\u7B7E"], ["details", "\u8BE6\u60C5"], ["delete", "\u5220\u9664"]];
1028
1649
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: openMenu === it.sessionId ? menuRef : null, className: "more-wrap", children: [
1029
1650
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1030
1651
  "button",
@@ -1042,7 +1663,8 @@ function SessionPanel({ workspacesSvc }) {
1042
1663
  }
1043
1664
  ),
1044
1665
  openMenu === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "more-menu", role: "menu", children: items.map(([id, label]) => {
1045
- const blocked = id === "move" && !canMove.available;
1666
+ const blocked = id === "move" && !canMove.available || id === "tags" && !tagsReady;
1667
+ const blockedTitle = id === "move" ? canMove.reason : id === "tags" ? tagsBlockedTitle : void 0;
1046
1668
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1047
1669
  "button",
1048
1670
  {
@@ -1050,7 +1672,7 @@ function SessionPanel({ workspacesSvc }) {
1050
1672
  role: "menuitem",
1051
1673
  className: "more-item" + (id === "delete" ? " more-item-danger" : ""),
1052
1674
  disabled: blocked,
1053
- title: blocked ? canMove.reason : void 0,
1675
+ title: blocked ? blockedTitle : void 0,
1054
1676
  onClick: () => runMenu(id, it),
1055
1677
  children: label
1056
1678
  },
@@ -1144,8 +1766,122 @@ function SessionPanel({ workspacesSvc }) {
1144
1766
  ")"
1145
1767
  ] })
1146
1768
  ] }),
1147
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "sess-farrow", "aria-expanded": moreOpen, "aria-label": moreOpen ? "\u6536\u8D77\u66F4\u591A\u7B5B\u9009" : "\u5C55\u5F00\u66F4\u591A\u7B5B\u9009", title: moreOpen ? "\u6536\u8D77\u66F4\u591A\u7B5B\u9009" : "\u5C55\u5F00\u66F4\u591A\u7B5B\u9009\uFF08\u5DF2\u6536\u85CF / \u7A7A\u767D / \u56DE\u6536\u7AD9\uFF09", onClick: () => setMoreOpen(!moreOpen), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 2l4 4-4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
1769
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "sess-farrow", "aria-expanded": moreOpen, "aria-label": moreOpen ? "\u6536\u8D77\u66F4\u591A\u7B5B\u9009" : "\u5C55\u5F00\u66F4\u591A\u7B5B\u9009", title: moreOpen ? "\u6536\u8D77\u66F4\u591A\u7B5B\u9009" : "\u5C55\u5F00\u66F4\u591A\u7B5B\u9009\uFF08\u5DF2\u6536\u85CF / \u7A7A\u767D / \u56DE\u6536\u7AD9\uFF09", onClick: () => setMoreOpen(!moreOpen), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 2l4 4-4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1771
+ "button",
1772
+ {
1773
+ type: "button",
1774
+ className: "archv-btn",
1775
+ "aria-expanded": openTagMgr,
1776
+ disabled: !tagsReady && !openTagMgr,
1777
+ title: tagsReady ? "\u65B0\u5EFA / \u91CD\u547D\u540D / \u5408\u5E76 / \u5220\u9664\u6807\u7B7E" : tagsBlockedTitle,
1778
+ onClick: () => {
1779
+ setOpenTagMgr(!openTagMgr);
1780
+ setRenamingTagId(null);
1781
+ },
1782
+ children: [
1783
+ "\u6807\u7B7E\u7BA1\u7406",
1784
+ tagsReady && tagDefs.length ? ` (${tagDefs.length})` : ""
1785
+ ]
1786
+ }
1787
+ )
1148
1788
  ] }) }),
1789
+ openTagMgr && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", "aria-label": "\u6807\u7B7E\u7BA1\u7406", children: [
1790
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet-head", children: [
1791
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { className: "mv-sheet-title", children: [
1792
+ "\u6807\u7B7E\u7BA1\u7406 \xB7 ",
1793
+ tagDefs.length
1794
+ ] }),
1795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "mv-sheet-close", "aria-label": "\u5173\u95ED", onClick: () => {
1796
+ setOpenTagMgr(false);
1797
+ setRenamingTagId(null);
1798
+ }, children: "\xD7" })
1799
+ ] }),
1800
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-field", children: [
1801
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mv-field-label", htmlFor: "dsm-tag-new", children: "\u65B0\u5EFA\u6807\u7B7E" }),
1802
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-browse-row", children: [
1803
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1804
+ "input",
1805
+ {
1806
+ id: "dsm-tag-new",
1807
+ type: "text",
1808
+ value: mgrTagName,
1809
+ disabled: mgrBusy,
1810
+ maxLength: 24,
1811
+ placeholder: "\u6807\u7B7E\u540D\uFF08\u4E0D\u80FD\u542B\u659C\u6760\uFF0C\u6700\u957F 24 \u5B57\uFF09",
1812
+ onChange: (e) => setMgrTagName(e.target.value),
1813
+ onKeyDown: (e) => {
1814
+ if (e.key === "Enter") {
1815
+ e.preventDefault();
1816
+ createTag();
1817
+ }
1818
+ }
1819
+ }
1820
+ ),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: mgrBusy || !mgrTagName.trim(), onClick: createTag, children: "\u65B0\u5EFA" })
1822
+ ] })
1823
+ ] }),
1824
+ tagDefs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u8FD8\u6CA1\u6709\u6807\u7B7E\u3002\u5728\u4E0A\u65B9\u8F93\u5165\u540D\u5B57\u5373\u53EF\u521B\u5EFA\uFF1B\u7ED9\u4F1A\u8BDD\u8D34\u6807\u7B7E\u4E5F\u53EF\u4EE5\u5728\u4F1A\u8BDD\u5361\u7247\u7684\u300C\u22EF \u2192 \u6807\u7B7E\u300D\u91CC\u8FDB\u884C\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: tagDefs.map((t) => {
1825
+ const others = tagDefs.filter((o) => String(o.id) !== String(t.id));
1826
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-tagrow", children: renamingTagId === String(t.id) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1827
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1828
+ "input",
1829
+ {
1830
+ className: "dsm-tag-input",
1831
+ type: "text",
1832
+ value: renameTagValue,
1833
+ disabled: mgrBusy,
1834
+ maxLength: 24,
1835
+ "aria-label": "\u91CD\u547D\u540D\u6807\u7B7E " + t.name,
1836
+ autoFocus: true,
1837
+ onChange: (e) => setRenameTagValue(e.target.value),
1838
+ onKeyDown: (e) => {
1839
+ if (e.key === "Enter") {
1840
+ e.preventDefault();
1841
+ commitTagRename(t);
1842
+ }
1843
+ if (e.key === "Escape") setRenamingTagId(null);
1844
+ }
1845
+ }
1846
+ ),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-tag-acts", children: [
1848
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-go", disabled: mgrBusy || !renameTagValue.trim(), onClick: () => commitTagRename(t), children: "\u4FDD\u5B58" }),
1849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: mgrBusy, onClick: () => setRenamingTagId(null), children: "\u53D6\u6D88" })
1850
+ ] })
1851
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1852
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-tag-name", title: t.name, children: t.name }),
1853
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "maint-note", children: [
1854
+ tagUsage[String(t.id)] || 0,
1855
+ " \u4E2A\u4F1A\u8BDD"
1856
+ ] }),
1857
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-tag-acts", children: [
1858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: mgrBusy || renamingTagId !== null, title: "\u91CD\u547D\u540D\u8FD9\u4E2A\u6807\u7B7E", onClick: () => {
1859
+ setRenamingTagId(String(t.id));
1860
+ setRenameTagValue(t.name);
1861
+ }, children: "\u91CD\u547D\u540D" }),
1862
+ others.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1864
+ "select",
1865
+ {
1866
+ "aria-label": "\u628A\u6807\u7B7E\u300C" + t.name + "\u300D\u5E76\u5165",
1867
+ value: mergeTarget[String(t.id)] || "",
1868
+ disabled: mgrBusy,
1869
+ title: "\u628A\u8FD9\u4E2A\u6807\u7B7E\u7684\u6240\u6709\u4F1A\u8BDD\u5E76\u5165\u53E6\u4E00\u4E2A\u6807\u7B7E\uFF08\u5E76\u5165\u540E\u672C\u6807\u7B7E\u5220\u9664\uFF09",
1870
+ onChange: (e) => setMergeTarget((m) => Object.assign({}, m, { [String(t.id)]: e.target.value })),
1871
+ children: [
1872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5E76\u5165\u2026" }),
1873
+ others.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: String(o.id), children: o.name }, String(o.id)))
1874
+ ]
1875
+ }
1876
+ ),
1877
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: mgrBusy || !mergeTarget[String(t.id)], title: mergeTarget[String(t.id)] ? "\u786E\u8BA4\u5E76\u5165\u6240\u9009\u6807\u7B7E" : "\u5148\u5728\u5DE6\u4FA7\u9009\u62E9\u5E76\u5165\u7684\u76EE\u6807\u6807\u7B7E", onClick: () => mergeTag(t), children: "\u786E\u8BA4" })
1878
+ ] }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-del", disabled: mgrBusy, title: "\u5220\u9664\u6807\u7B7E\uFF08\u53EA\u5220\u6807\u7B7E\uFF0C\u4E0D\u4F1A\u5220\u9664\u4F1A\u8BDD\uFF09", onClick: () => deleteTag(t), children: "\u5220\u9664" })
1880
+ ] })
1881
+ ] }) }, String(t.id));
1882
+ }) }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u6807\u7B7E\u662F\u4F1A\u8BDD\u7684\u81EA\u5B9A\u4E49\u6807\u8BB0\uFF1A\u65B0\u5EFA\u3001\u91CD\u547D\u540D\u3001\u5408\u5E76\u3001\u5220\u9664\u90FD\u53EA\u6539\u6807\u8BB0\u672C\u8EAB\uFF0C\u4E0D\u4F1A\u5220\u9664\u6216\u79FB\u52A8\u4EFB\u4F55\u4F1A\u8BDD\u3002\u5355\u4E2A\u4F1A\u8BDD\u7684\u6807\u7B7E\u6570\u91CF\u4E0E\u6807\u7B7E\u603B\u6570\u90FD\u6709\u670D\u52A1\u7AEF\u4E0A\u9650\uFF0C\u8D85\u9650\u65F6\u4F1A\u63D0\u793A\u5E76\u81EA\u52A8\u56DE\u9000\u672C\u5730\u6539\u52A8\u3002" })
1884
+ ] }),
1149
1885
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "maint-bar", children: [
1150
1886
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "archv-btn", "aria-expanded": storageOpen, onClick: () => {
1151
1887
  const next = !storageOpen;
@@ -1167,6 +1903,28 @@ function SessionPanel({ workspacesSvc }) {
1167
1903
  " \u4E2A"
1168
1904
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "maint-note", children: "\u5C1A\u672A\u68C0\u67E5" })
1169
1905
  ] }),
1906
+ pendingQueue.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", "aria-label": "\u5F85\u79FB\u52A8\u961F\u5217", children: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mv-sheet-head", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { className: "mv-sheet-title", children: [
1908
+ "\u5F85\u79FB\u52A8\u961F\u5217 \xB7 ",
1909
+ pendingQueue.length
1910
+ ] }) }),
1911
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u8FD9\u4E9B\u4F1A\u8BDD\u6B63\u88AB DSH \u6253\u5F00\u7740\uFF0C\u5B98\u65B9\u53EA\u5728\u8FDB\u7A0B\u9000\u51FA\u65F6\u91CA\u653E\u5199\u6743\u9650\uFF1B\u91CA\u653E\u540E\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B8C\u6210\u79FB\u52A8\uFF0C\u4E5F\u53EF\u91CD\u542F DSH \u8BA9\u5B83\u5728\u542F\u52A8\u5934\u51E0\u79D2\u62A2\u5148\u8865\u8DD1\u3002" }),
1912
+ pendingQueue.map((q) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-kid-row", children: [
1913
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-kid-name", title: `${q.sessionId} \u2192 ${q.targetPath || ""}`, children: [
1914
+ shortId(q.sessionId),
1915
+ "\u2026 \u2192 ",
1916
+ pathTail(q.targetPath) || "\u76EE\u6807\u5DE5\u4F5C\u533A"
1917
+ ] }),
1918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-kid-acts", children: [
1919
+ Number.isSafeInteger(q.attempts) && q.attempts > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "maint-note", children: [
1920
+ "\u5DF2\u5931\u8D25 ",
1921
+ q.attempts,
1922
+ " \u6B21"
1923
+ ] }) : null,
1924
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", onClick: () => cancelQueuedMove(q.sessionId), children: "\u53D6\u6D88" })
1925
+ ] })
1926
+ ] }, String(q.sessionId)))
1927
+ ] }),
1170
1928
  aaOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", "aria-label": "\u81EA\u52A8\u5F52\u6863\u8BBE\u7F6E", children: [
1171
1929
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet-head", children: [
1172
1930
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "mv-sheet-title", children: "\u81EA\u52A8\u5F52\u6863" }),
@@ -1229,7 +1987,7 @@ function SessionPanel({ workspacesSvc }) {
1229
1987
  ] })
1230
1988
  ] }),
1231
1989
  showSessionList && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1232
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-tools sess-tools-4", "aria-label": "\u67E5\u627E\u548C\u6574\u7406\u4F1A\u8BDD", children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-tools sess-tools-5", "aria-label": "\u67E5\u627E\u548C\u6574\u7406\u4F1A\u8BDD", children: [
1233
1991
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
1234
1992
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dsm-search", children: "\u641C\u7D22\u4F1A\u8BDD" }),
1235
1993
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { id: "dsm-search", type: "search", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "\u6807\u9898\u3001\u4F1A\u8BDD ID \u6216\u5DE5\u4F5C\u533A" })
@@ -1238,9 +1996,31 @@ function SessionPanel({ workspacesSvc }) {
1238
1996
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dsm-workspace-filter", children: "\u5DE5\u4F5C\u533A" }),
1239
1997
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: "dsm-workspace-filter", value: workspaceFilter, onChange: (e) => setWorkspaceFilter(e.target.value), children: [
1240
1998
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "\u5168\u90E8\u5DE5\u4F5C\u533A" }),
1241
- workspaces.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: w.path, children: w.title }, w.workspaceId))
1999
+ workspaces.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: w.path, children: w.title }, w.workspaceId)),
2000
+ workspaceFilter !== "all" && !workspaces.some((w) => w.path === workspaceFilter) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: workspaceFilter, children: [
2001
+ "\u5DF2\u5220\u5DE5\u4F5C\u533A \xB7 ",
2002
+ pathName(workspaceFilter) || "?"
2003
+ ] })
1242
2004
  ] })
1243
2005
  ] }),
2006
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
2007
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dsm-tag-filter", children: "\u6807\u7B7E" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2009
+ "select",
2010
+ {
2011
+ id: "dsm-tag-filter",
2012
+ value: tagFilter,
2013
+ disabled: !tagsReady,
2014
+ title: !tagsReady ? tagsBlockedTitle : tagFilter ? "\u5F53\u524D\u6309\u300C" + (tagMap.get(tagFilter) || "\u6240\u9009\u6807\u7B7E") + "\u300D\u7B5B\u9009\uFF1B\u591A\u9009\u7EC4\u5408\u53EF\u7528\u300C\u4FDD\u5B58\u5F53\u524D\u7B5B\u9009\u300D\u56FA\u5316" : "\u6309\u6807\u7B7E\u7B5B\u9009\uFF08\u5355\u9009\uFF09",
2015
+ onChange: (e) => setTagFilter(e.target.value),
2016
+ children: [
2017
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "\u5168\u90E8\u6807\u7B7E" }),
2018
+ tagDefs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: t.id, children: t.name }, t.id)),
2019
+ tagFilter && !tagMap.has(tagFilter) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: tagFilter, children: "\uFF08\u5DF2\u5220\u6807\u7B7E\uFF09" })
2020
+ ]
2021
+ }
2022
+ )
2023
+ ] }),
1244
2024
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
1245
2025
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dsm-sort", children: "\u6392\u5E8F" }),
1246
2026
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: "dsm-sort", value: sortBy, onChange: (e) => setSortBy(e.target.value), children: [
@@ -1251,18 +2031,89 @@ function SessionPanel({ workspacesSvc }) {
1251
2031
  ] }),
1252
2032
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-field", children: [
1253
2033
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dsm-group", children: "\u5206\u7EC4" }),
1254
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: "dsm-group", value: groupByLineage ? "lineage" : "flat", onChange: (e) => setGroupByLineage(e.target.value === "lineage"), title: "\u8840\u7F18\u5206\u7EC4\uFF1A\u5B50\u4EE3\u7406\u6298\u53E0\u5728\u7236\u4F1A\u8BDD\u4E0B\uFF1B\u5E73\u94FA\uFF1A\u4E0E DSH \u539F\u751F\u4E00\u81F4\uFF0C\u5168\u90E8\u5E76\u5217", children: [
1255
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "lineage", children: "\u8840\u7F18\uFF08\u5B50\u4EE3\u7406\u6298\u53E0\uFF09" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: "dsm-group", value: groupByLineage ? "lineage" : "flat", onChange: (e) => setGroupByLineage(e.target.value === "lineage"), title: "\u8840\u7F18\u5206\u7EC4\uFF1A\u5B50\u4EE3\u7406\u6298\u53E0\u3001\u5206\u652F\u805A\u62E2\u6210\u7EC4\uFF1B\u5E73\u94FA\uFF1A\u4E0E DSH \u539F\u751F\u4E00\u81F4\uFF0C\u5168\u90E8\u5E76\u5217", children: [
2035
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "lineage", children: "\u8840\u7F18\uFF08\u6298\u53E0\u5206\u7EC4\uFF09" }),
1256
2036
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "flat", children: "\u5E73\u94FA\uFF08\u5168\u90E8\u5E76\u5217\uFF09" })
1257
2037
  ] })
1258
2038
  ] })
1259
2039
  ] }),
1260
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sess-results", role: "status", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "sess-results-main", children: [
1261
- "\u663E\u793A ",
1262
- topList.length,
1263
- " \u4E2A\u4F1A\u8BDD",
1264
- foldedCount ? `\uFF0C\u53E6\u6709 ${foldedCount} \u4E2A\u5B50\u4EE3\u7406\u6298\u53E0\u5728\u7236\u4F1A\u8BDD\u4E0B` : query || workspaceFilter !== "all" ? `\uFF0C\u5171 ${filter === "archived" ? archivedList.length : filter === "active" ? activeList.length : filter === "starred" ? starredList.length : filter === "empty" ? emptyList.length : sessions.length} \u4E2A` : topList.length !== list.length ? `\uFF0C\u5171 ${list.length} \u4E2A` : ""
1265
- ] }) })
2040
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-results", children: [
2041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "sess-results-main", role: "status", children: [
2042
+ "\u663E\u793A ",
2043
+ topList.length - syntheticCount,
2044
+ " \u4E2A\u4F1A\u8BDD",
2045
+ foldedCount || branchFolded ? `\uFF0C\u53E6\u6709 ${[foldedCount ? `${foldedCount} \u4E2A\u5B50\u4EE3\u7406\u6298\u53E0\u5728\u7236\u4F1A\u8BDD\u4E0B` : "", branchFolded ? `${branchFolded} \u4E2A\u5206\u652F\u805A\u6210 ${branchGroupCount} \u7EC4` : ""].filter(Boolean).join("\u3001")}` : query || workspaceFilter !== "all" || tagFilter ? `\uFF0C\u5171 ${filter === "archived" ? archivedList.length : filter === "active" ? activeList.length : filter === "starred" ? starredList.length : filter === "empty" ? emptyList.length : sessions.length} \u4E2A` : topList.length !== list.length ? `\uFF0C\u5171 ${list.length} \u4E2A` : ""
2046
+ ] }),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsm-fbar", children: [
2048
+ saveFilterOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2049
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2050
+ "input",
2051
+ {
2052
+ type: "text",
2053
+ value: saveFilterName,
2054
+ maxLength: 40,
2055
+ placeholder: "\u7B5B\u9009\u540D\u79F0",
2056
+ "aria-label": "\u4E3A\u5F53\u524D\u7B5B\u9009\u547D\u540D",
2057
+ autoFocus: true,
2058
+ disabled: mgrBusy,
2059
+ onChange: (e) => setSaveFilterName(e.target.value),
2060
+ onKeyDown: (e) => {
2061
+ if (e.key === "Enter") {
2062
+ e.preventDefault();
2063
+ saveCurrentFilter();
2064
+ }
2065
+ if (e.key === "Escape") {
2066
+ setSaveFilterOpen(false);
2067
+ setSaveFilterName("");
2068
+ }
2069
+ }
2070
+ }
2071
+ ),
2072
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn archv-go", disabled: mgrBusy || !saveFilterName.trim(), onClick: saveCurrentFilter, children: "\u4FDD\u5B58" }),
2073
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: mgrBusy, onClick: () => {
2074
+ setSaveFilterOpen(false);
2075
+ setSaveFilterName("");
2076
+ }, children: "\u53D6\u6D88" })
2077
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2078
+ "button",
2079
+ {
2080
+ type: "button",
2081
+ className: "archv-btn",
2082
+ title: "\u628A\u5F53\u524D\u89C6\u56FE / \u5DE5\u4F5C\u533A / \u6807\u7B7E / \u6392\u5E8F\u5B58\u4E3A\u4E00\u4E2A\u53EF\u590D\u7528\u7684\u7B5B\u9009",
2083
+ onClick: () => {
2084
+ setSaveFilterOpen(true);
2085
+ setSaveFilterName("");
2086
+ },
2087
+ children: "\u4FDD\u5B58\u5F53\u524D\u7B5B\u9009"
2088
+ }
2089
+ ),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2091
+ "select",
2092
+ {
2093
+ "aria-label": "\u5DF2\u5B58\u7B5B\u9009",
2094
+ value: savedFilters.some((f) => String(f.id) === appliedSavedId) ? appliedSavedId : "",
2095
+ disabled: !savedFilters.length || mgrBusy,
2096
+ title: !savedFilters.length ? "\u8FD8\u6CA1\u6709\u5DF2\u5B58\u7B5B\u9009\uFF1B\u70B9\u5DE6\u4FA7\u300C\u4FDD\u5B58\u5F53\u524D\u7B5B\u9009\u300D\u521B\u5EFA" : "\u9009\u62E9\u4E00\u4E2A\u5DF2\u5B58\u7B5B\u9009\u5E76\u7ACB\u5373\u5E94\u7528\uFF1B\u9009\u4E2D\u540E\u53F3\u4FA7\u53EF\u5220\u9664",
2097
+ onChange: (e) => applySavedFilter(e.target.value),
2098
+ children: [
2099
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: savedFilters.length ? "\u5DF2\u5B58\u7B5B\u9009\u2026" : "\u65E0\u5DF2\u5B58\u7B5B\u9009" }),
2100
+ savedFilters.map((f) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: f.id, children: f.name }, f.id))
2101
+ ]
2102
+ }
2103
+ ),
2104
+ appliedSavedId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2105
+ "button",
2106
+ {
2107
+ type: "button",
2108
+ className: "archv-btn archv-del",
2109
+ disabled: mgrBusy,
2110
+ title: "\u5220\u9664\u5DF2\u5B58\u7B5B\u9009\u300C" + ((savedFilters.find((f) => String(f.id) === appliedSavedId) || {}).name || appliedSavedId) + "\u300D\uFF08\u53EA\u5220\u8FD9\u6761\u4FDD\u5B58\u7684\u7B5B\u9009\uFF0C\u4E0D\u52A8\u4F1A\u8BDD\uFF09",
2111
+ onClick: () => deleteSavedFilter(appliedSavedId),
2112
+ children: "\u5220\u9664"
2113
+ }
2114
+ )
2115
+ ] })
2116
+ ] })
1266
2117
  ] }),
1267
2118
  showSessionList && list.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sess-batch" + (selIds.length > 0 ? " sess-batch-pin" : ""), children: [
1268
2119
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sess-btntext", children: selIds.length ? `\u5DF2\u9009 ${selIds.length} \u9879` : filter === "archived" ? `\u5171 ${archivedList.length} \u4E2A\u5F52\u6863\u4F1A\u8BDD` : filter === "starred" ? `\u5171 ${starredList.length} \u4E2A\u6536\u85CF\u4F1A\u8BDD` : `\u5171 ${sessions.length} \u4E2A\u4F1A\u8BDD\uFF08\u6D3B\u52A8 ${activeList.length} / \u5DF2\u5F52\u6863 ${archivedList.length}\uFF09` }),
@@ -1344,9 +2195,34 @@ function SessionPanel({ workspacesSvc }) {
1344
2195
  ] })
1345
2196
  ] })
1346
2197
  ] }),
1347
- showSessionList && list.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", children: query || workspaceFilter !== "all" ? "\u6CA1\u6709\u5339\u914D\u7684\u4F1A\u8BDD\u3002\u8BF7\u8C03\u6574\u641C\u7D22\u8BCD\u6216\u5DE5\u4F5C\u533A\u7B5B\u9009\u3002" : filter === "archived" ? "\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u201C\u5168\u90E8\u201D\u91CC\u9009\u4E2D\u4F1A\u8BDD\u70B9\u201C\u5F52\u6863\u201D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002" : filter === "active" ? "\u76EE\u524D\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD\u3002" : filter === "starred" ? "\u8FD8\u6CA1\u6709\u6536\u85CF\u7684\u4F1A\u8BDD\u3002\u70B9\u51FB\u4F1A\u8BDD\u5DE6\u4FA7\u7684\u661F\u6807\u5373\u53EF\u6536\u85CF\u3002" : filter === "empty" ? "\u6CA1\u6709\u7A7A\u767D\u4F1A\u8BDD\u3002\u65B0\u5F00\u4F1A\u8BDD\u8FD8\u6CA1\u4EA7\u751F\u5185\u5BB9\u65F6\u4F1A\u5F52\u5230\u8FD9\u91CC\uFF0C\u53EF\u5728\u4FA7\u680F\u7528\u300C\u7EAF\u51C0\u89C6\u56FE\u300D\u628A\u5B83\u4EEC\u4E00\u5E76\u6298\u53E0\u3002" : "\u6682\u65E0\u53EF\u7BA1\u7406\u7684\u4F1A\u8BDD\u3002" }) : showSessionList ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-list", role: "list", children: topList.map((it) => {
2198
+ showSessionList && list.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-empty", children: query || workspaceFilter !== "all" || tagFilter ? "\u6CA1\u6709\u5339\u914D\u7684\u4F1A\u8BDD\u3002\u8BF7\u8C03\u6574\u641C\u7D22\u8BCD\u3001\u6807\u7B7E\u6216\u5DE5\u4F5C\u533A\u7B5B\u9009\u3002" : filter === "archived" ? "\u76EE\u524D\u6CA1\u6709\u5F52\u6863\u4F1A\u8BDD\u3002\u5728\u201C\u5168\u90E8\u201D\u91CC\u9009\u4E2D\u4F1A\u8BDD\u70B9\u201C\u5F52\u6863\u201D\u5373\u53EF\u6536\u7EB3\u8FDB\u6765\u3002" : filter === "active" ? "\u76EE\u524D\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD\u3002" : filter === "starred" ? "\u8FD8\u6CA1\u6709\u6536\u85CF\u7684\u4F1A\u8BDD\u3002\u70B9\u51FB\u4F1A\u8BDD\u5DE6\u4FA7\u7684\u661F\u6807\u5373\u53EF\u6536\u85CF\u3002" : filter === "empty" ? "\u6CA1\u6709\u7A7A\u767D\u4F1A\u8BDD\u3002\u65B0\u5F00\u4F1A\u8BDD\u8FD8\u6CA1\u4EA7\u751F\u5185\u5BB9\u65F6\u4F1A\u5F52\u5230\u8FD9\u91CC\uFF0C\u4FA7\u680F\u4F1A\u81EA\u52A8\u9690\u85CF\u5B83\u4EEC\u3002" : "\u6682\u65E0\u53EF\u7BA1\u7406\u7684\u4F1A\u8BDD\u3002" }) : showSessionList ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "archv-list", role: "list", children: topList.map((it) => {
2199
+ if (it && it.syntheticRoot) {
2200
+ const root = String(it.syntheticRoot);
2201
+ const members = branchGroupsOf.get(root) || [];
2202
+ const open = !!openBranches[root] || branchHit(root);
2203
+ const srcTitle = dsmAuthoritativeTitles.get(root);
2204
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsm-src-head", role: "listitem", children: [
2205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2206
+ "button",
2207
+ {
2208
+ type: "button",
2209
+ className: "dsm-kids-toggle",
2210
+ "aria-expanded": open,
2211
+ title: (open ? "\u6536\u8D77 " : "\u5C55\u5F00 ") + members.length + " \u4E2A\u5206\u652F\u4F1A\u8BDD\uFF08\u6765\u6E90\u4F1A\u8BDD\u4E0D\u5728\u5F53\u524D\u5217\u8868\uFF09",
2212
+ onClick: () => toggleBranch(root),
2213
+ children: [
2214
+ open ? "\u25BE" : "\u25B8",
2215
+ " ",
2216
+ members.length,
2217
+ " \u5206\u652F"
2218
+ ]
2219
+ }
2220
+ ),
2221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-src-name", title: root, children: "\u6765\u6E90\uFF1A" + (srcTitle || shortId(root)) })
2222
+ ] }, it.sessionId);
2223
+ }
1348
2224
  const date = fmtDate(it.createdAt);
1349
- const expanded = openMove === it.sessionId;
2225
+ const expanded = openMove === it.sessionId || openTags === it.sessionId;
1350
2226
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-card" + (expanded ? " archv-card-exp" : ""), role: "listitem", children: [
1351
2227
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-row", children: [
1352
2228
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1380,7 +2256,9 @@ function SessionPanel({ workspacesSvc }) {
1380
2256
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-name", title: it.title || "", children: it.title || "(\u65E0\u6807\u9898)" }),
1381
2257
  branchBadge(it.sessionId),
1382
2258
  emptyBadge(it.sessionId),
2259
+ tagChips(it.sessionId),
1383
2260
  kidsBadge(it.sessionId),
2261
+ branchGroupBadge(it.sessionId),
1384
2262
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "archv-id", title: it.sessionId, children: shortId(it.sessionId) })
1385
2263
  ] }),
1386
2264
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "archv-meta", children: [
@@ -1396,6 +2274,7 @@ function SessionPanel({ workspacesSvc }) {
1396
2274
  ] })
1397
2275
  ] }),
1398
2276
  groupByLineage && renderKids(it.sessionId, 0),
2277
+ groupByLineage && renderBranchGroup(it.sessionId),
1399
2278
  expanded && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", role: "region", "aria-label": "\u79FB\u52A8\u5230\u5DE5\u4F5C\u533A", children: [
1400
2279
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet-head", children: [
1401
2280
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "mv-sheet-title", children: "\u79FB\u52A8\u5230\u5DE5\u4F5C\u533A" }),
@@ -1449,6 +2328,51 @@ function SessionPanel({ workspacesSvc }) {
1449
2328
  ] })
1450
2329
  ] })
1451
2330
  ] }),
2331
+ openTags === it.sessionId && (() => {
2332
+ const cur = Array.isArray(assignments[String(it.sessionId)]) ? assignments[String(it.sessionId)] : [];
2333
+ const locked = tagBusy !== null || !tagsReady;
2334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet", role: "region", "aria-label": "\u4F1A\u8BDD\u6807\u7B7E", children: [
2335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-sheet-head", children: [
2336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { className: "mv-sheet-title", children: [
2337
+ "\u4F1A\u8BDD\u6807\u7B7E \xB7 ",
2338
+ it.title || shortId(it.sessionId)
2339
+ ] }),
2340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "mv-sheet-close", "aria-label": "\u5173\u95ED", onClick: () => setOpenTags(null), children: "\xD7" })
2341
+ ] }),
2342
+ tagDefs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u8FD8\u6CA1\u6709\u6807\u7B7E\uFF0C\u5728\u4E0B\u65B9\u65B0\u5EFA\u7B2C\u4E00\u4E2A\u3002" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsm-taglist", children: tagDefs.map((t) => {
2343
+ const checked = cur.some((id) => String(id) === String(t.id));
2344
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsm-tagcheck", children: [
2345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked, disabled: locked, onChange: () => toggleTagFor(it.sessionId, t.id) }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsm-tagcheck-name", title: t.name, children: t.name })
2347
+ ] }, String(t.id));
2348
+ }) }),
2349
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-field", children: [
2350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mv-field-label", htmlFor: "dsm-tag-attach-" + String(it.sessionId), children: "\u65B0\u5EFA\u6807\u7B7E\u5E76\u8D34\u4E0A" }),
2351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mv-browse-row", children: [
2352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2353
+ "input",
2354
+ {
2355
+ id: "dsm-tag-attach-" + String(it.sessionId),
2356
+ type: "text",
2357
+ value: cardTagName,
2358
+ disabled: locked,
2359
+ maxLength: 24,
2360
+ placeholder: "\u6807\u7B7E\u540D\uFF08\u4E0D\u80FD\u542B\u659C\u6760\uFF0C\u6700\u957F 24 \u5B57\uFF09",
2361
+ onChange: (e) => setCardTagName(e.target.value),
2362
+ onKeyDown: (e) => {
2363
+ if (e.key === "Enter") {
2364
+ e.preventDefault();
2365
+ createTagAndAttach(it.sessionId);
2366
+ }
2367
+ }
2368
+ }
2369
+ ),
2370
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "archv-btn", disabled: locked || !cardTagName.trim(), title: locked && !tagsReady ? "\u6807\u7B7E\u6570\u636E\u672A\u80FD\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u65B0\u5EFA\u6807\u7B7E" : "\u65B0\u5EFA\u8FD9\u4E2A\u6807\u7B7E\u5E76\u7ACB\u5373\u8D34\u5230\u5F53\u524D\u4F1A\u8BDD", onClick: () => createTagAndAttach(it.sessionId), children: "\u8D34\u4E0A" })
2371
+ ] })
2372
+ ] }),
2373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-note", children: "\u52FE\u9009\u5373\u65F6\u751F\u6548\uFF1B\u5355\u4E2A\u4F1A\u8BDD\u7684\u6807\u7B7E\u6570\u91CF\u4EE5\u670D\u52A1\u7AEF\u4E0A\u9650\u4E3A\u51C6\uFF0C\u8D85\u65F6\u4F1A\u63D0\u793A\u5E76\u56DE\u9000\u3002" })
2374
+ ] });
2375
+ })(),
1452
2376
  openDetails === it.sessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtl-sheet", role: "region", "aria-label": "\u4F1A\u8BDD\u8BE6\u60C5", children: [
1453
2377
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtl-sheet-head", children: [
1454
2378
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "dtl-sheet-title", children: "\u4F1A\u8BDD\u8BE6\u60C5" }),
@@ -1530,7 +2454,7 @@ function SessionPanel({ workspacesSvc }) {
1530
2454
  ] }, i)) })
1531
2455
  ] }),
1532
2456
  d.lineage && (d.lineage.parentSessionId || d.lineage.children && d.lineage.children.length > 0 || d.lineage.subagents && d.lineage.subagents.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtl-sec", children: [
1533
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-sec-t", children: "\u8840\u7EDF" }),
2457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dtl-sec-t", children: "\u8840\u7F18" }),
1534
2458
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dtl-paths", children: [
1535
2459
  d.lineage.parentSessionId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1536
2460
  "\u7236\u4F1A\u8BDD: ",
@@ -1792,6 +2716,61 @@ var dsmAuthoritativeTitles = /* @__PURE__ */ new Map();
1792
2716
  var dsmTrashTick = 0;
1793
2717
  var dsmCapabilities = null;
1794
2718
  var dsmLineage = /* @__PURE__ */ new Map();
2719
+ var dsmWarmPending = false;
2720
+ var dsmRefinePending = false;
2721
+ var dsmTrashRetryTimer = null;
2722
+ var dsmWarmDoneHooks = /* @__PURE__ */ new Set();
2723
+ function dsmOnWarmDone(fn) {
2724
+ dsmWarmDoneHooks.add(fn);
2725
+ return () => dsmWarmDoneHooks.delete(fn);
2726
+ }
2727
+ var DSM_KEY_SEEN_NOTICES = "dsm-move-notices-seen-v1";
2728
+ var dsmSeenNotices = null;
2729
+ function dsmLoadSeenNotices() {
2730
+ if (dsmSeenNotices) return dsmSeenNotices;
2731
+ try {
2732
+ dsmSeenNotices = new Set(JSON.parse(localStorage.getItem(DSM_KEY_SEEN_NOTICES) || "[]").map(String));
2733
+ } catch (e) {
2734
+ dsmSeenNotices = /* @__PURE__ */ new Set();
2735
+ }
2736
+ return dsmSeenNotices;
2737
+ }
2738
+ function dsmSaveSeenNotices(set) {
2739
+ try {
2740
+ localStorage.setItem(DSM_KEY_SEEN_NOTICES, JSON.stringify([...set].slice(-200)));
2741
+ } catch (e) {
2742
+ }
2743
+ }
2744
+ var dsmNotifySink = null;
2745
+ var dsmNoticeHooks = /* @__PURE__ */ new Set();
2746
+ function dsmNotify(text, kind) {
2747
+ if (!text) return;
2748
+ if (dsmNotifySink && !sidebarAdapter.disabled) {
2749
+ try {
2750
+ dsmNotifySink(text, kind);
2751
+ return;
2752
+ } catch (e) {
2753
+ }
2754
+ }
2755
+ let done = false;
2756
+ try {
2757
+ for (const fn of dsmNoticeHooks) {
2758
+ fn(text, kind);
2759
+ done = true;
2760
+ }
2761
+ } catch (e) {
2762
+ }
2763
+ if (!done) {
2764
+ try {
2765
+ console.warn("[dsh-sessions-manager] " + text);
2766
+ } catch (e) {
2767
+ }
2768
+ }
2769
+ }
2770
+ function dsmOnNotice(fn) {
2771
+ dsmNoticeHooks.add(fn);
2772
+ return () => dsmNoticeHooks.delete(fn);
2773
+ }
1795
2774
  async function dsmLoadCapabilities() {
1796
2775
  try {
1797
2776
  dsmCapabilities = await postJSON("/archived-sessions/capabilities", {});
@@ -1816,8 +2795,35 @@ async function dsmLoadTrashIds() {
1816
2795
  for (const [id, info] of Object.entries(r && r.lineage || {})) {
1817
2796
  if (info && typeof info === "object") dsmLineage.set(String(id), info);
1818
2797
  }
2798
+ const wasBusy = dsmWarmPending || dsmRefinePending;
2799
+ dsmWarmPending = !!(r && r.warmPending);
2800
+ dsmRefinePending = !!(r && r.refinePending);
2801
+ if (wasBusy && !(dsmWarmPending || dsmRefinePending)) {
2802
+ try {
2803
+ for (const fn of dsmWarmDoneHooks) fn();
2804
+ } catch (e) {
2805
+ }
2806
+ }
2807
+ {
2808
+ const plan = noticeToastPlan(r && r.moveNotices, dsmLoadSeenNotices(), 2);
2809
+ if (plan.ackIds.length) {
2810
+ const seen = dsmLoadSeenNotices();
2811
+ for (const id of plan.ackIds) seen.add(id);
2812
+ dsmSaveSeenNotices(seen);
2813
+ dsmNotify(plan.text, plan.kind);
2814
+ postJSON("/archived-sessions/pending-moves/notices/ack", { ids: plan.ackIds }).catch(() => {
2815
+ });
2816
+ }
2817
+ }
1819
2818
  if (dsmRepaintDots) dsmRepaintDots();
1820
2819
  } catch (e) {
2820
+ if (dsmTrashRetryTimer == null) {
2821
+ dsmTrashRetryTimer = setTimeout(() => {
2822
+ dsmTrashRetryTimer = null;
2823
+ dsmLoadTrashIds();
2824
+ }, 2e3);
2825
+ if (typeof dsmTrashRetryTimer.unref === "function") dsmTrashRetryTimer.unref();
2826
+ }
1821
2827
  }
1822
2828
  return dsmTrashIds;
1823
2829
  }
@@ -2193,7 +3199,7 @@ function installSidebarStatusDots() {
2193
3199
  // 红 出错/需关注 (DSH error)
2194
3200
  };
2195
3201
  const manualUnread = dsmLoadManual();
2196
- const titleInitializedRows = /* @__PURE__ */ new WeakSet();
3202
+ const titleBaselines = /* @__PURE__ */ new WeakMap();
2197
3203
  function dsmSyncEmptyGroups() {
2198
3204
  let groups = null;
2199
3205
  try {
@@ -2283,21 +3289,22 @@ function installSidebarStatusDots() {
2283
3289
  }
2284
3290
  if (row.style.display === "none") row.style.display = "";
2285
3291
  if (!isEmpty && li && li.parentSession && li.origin !== "subagent") {
2286
- if (!row.querySelector('[data-dsm-tag="fork"]')) {
2287
- const fork = document.createElement("button");
3292
+ const parentTitle = dsmAuthoritativeTitles.get(li.parentSession);
3293
+ const tip = parentTitle ? "\u5206\u652F\u4E8E\uFF1A" + parentTitle : "\u5206\u652F\u4F1A\u8BDD\uFF08\u70B9\u51FB\u67E5\u770B\u6765\u6E90\u4F1A\u8BDD\uFF09";
3294
+ let fork = row.querySelector('[data-dsm-tag="fork"]');
3295
+ if (!fork) {
3296
+ fork = document.createElement("button");
2288
3297
  fork.type = "button";
2289
3298
  fork.dataset.dsmTag = "fork";
2290
3299
  fork.className = "dsm-tag dsm-tag-fork";
2291
- const parentTitle = dsmAuthoritativeTitles.get(li.parentSession);
2292
3300
  fork.textContent = "\u2442 \u5206\u652F";
2293
- fork.title = parentTitle ? "\u5206\u652F\u4E8E\uFF1A" + parentTitle : "\u5206\u652F\u4F1A\u8BDD\uFF08\u70B9\u51FB\u67E5\u770B\u6765\u6E90\u4F1A\u8BDD\uFF09";
2294
- fork.setAttribute("aria-label", fork.title);
2295
3301
  fork.addEventListener("click", (e) => {
2296
3302
  e.stopPropagation();
2297
3303
  e.preventDefault();
3304
+ const target = e.currentTarget.dataset.dsmForkParent || "";
2298
3305
  const rows2 = document.querySelectorAll('[role="treeitem"]');
2299
3306
  for (const other of rows2) {
2300
- if (rowId(other) === li.parentSession) {
3307
+ if (rowId(other) === target) {
2301
3308
  other.scrollIntoView({ block: "center", behavior: "smooth" });
2302
3309
  other.classList.add("dsm-row-flash");
2303
3310
  setTimeout(() => other.classList.remove("dsm-row-flash"), 1200);
@@ -2307,6 +3314,11 @@ function installSidebarStatusDots() {
2307
3314
  });
2308
3315
  row.appendChild(fork);
2309
3316
  }
3317
+ if (fork.dataset.dsmForkParent !== li.parentSession) fork.dataset.dsmForkParent = String(li.parentSession);
3318
+ if (fork.title !== tip) {
3319
+ fork.title = tip;
3320
+ fork.setAttribute("aria-label", tip);
3321
+ }
2310
3322
  } else {
2311
3323
  const staleFork = row.querySelector('[data-dsm-tag="fork"]');
2312
3324
  if (staleFork) staleFork.remove();
@@ -2351,19 +3363,29 @@ function installSidebarStatusDots() {
2351
3363
  if (staleKids) staleKids.remove();
2352
3364
  }
2353
3365
  const authoritativeTitle = dsmAuthoritativeTitles.get(id);
2354
- if (!titleInitializedRows.has(row)) {
2355
- const node = rowNode(row) || {};
2356
- const expected = new Set([node.title, node.displayTitle, node.name].filter((value) => typeof value === "string"));
2357
- const spans = [...row.children].filter((el) => el.tagName === "SPAN" && !el.querySelector("[data-state]") && (el.textContent || "").trim());
2358
- const titleEl = spans.find((el) => expected.has((el.textContent || "").trim())) || spans[0];
2359
- if (titleEl) {
2360
- const correction = authoritativeTitleForFirstPaint({
2361
- firstPaint: true,
2362
- rendered: titleEl.textContent || "",
2363
- authoritative: authoritativeTitle || ""
2364
- });
2365
- if (correction) titleEl.textContent = correction;
2366
- titleInitializedRows.add(row);
3366
+ {
3367
+ const base = titleBaselines.get(row);
3368
+ if (!base || base.id !== id || authoritativeTitle && authoritativeTitle !== base.authoritative) {
3369
+ const node = rowNode(row) || {};
3370
+ const expected = new Set([node.title, node.displayTitle, node.name].filter((value) => typeof value === "string"));
3371
+ const spans = [...row.children].filter((el) => el.tagName === "SPAN" && !el.querySelector("[data-state]") && (el.textContent || "").trim());
3372
+ let titleEl = null;
3373
+ if (base && base.id === id) {
3374
+ titleEl = spans.find((el) => (el.textContent || "") === base.text) || spans.find((el) => expected.has((el.textContent || "").trim())) || null;
3375
+ } else {
3376
+ titleEl = spans.find((el) => expected.has((el.textContent || "").trim())) || spans[0] || null;
3377
+ }
3378
+ if (titleEl) {
3379
+ const d = titleBackfillDecision({
3380
+ rendered: titleEl.textContent || "",
3381
+ baseline: base && base.id === id ? { text: base.text, authoritative: base.authoritative } : null,
3382
+ authoritative: authoritativeTitle || ""
3383
+ });
3384
+ if (d) {
3385
+ if (d.changed && titleEl.textContent !== d.text) titleEl.textContent = d.text;
3386
+ titleBaselines.set(row, { id, text: d.nextBaseline.text, authoritative: d.nextBaseline.authoritative });
3387
+ }
3388
+ }
2367
3389
  }
2368
3390
  }
2369
3391
  const sd = row.querySelector("[data-state]");
@@ -2399,10 +3421,13 @@ function installSidebarStatusDots() {
2399
3421
  const t = document.createElement("div");
2400
3422
  t.className = "dsm-toast" + (kind === "err" ? " dsm-toast-err" : "");
2401
3423
  t.textContent = msg;
2402
- t.style.cssText = "position:fixed;left:50%;bottom:24px;transform:translateX(-50%);max-width:80%;padding:8px 14px;border-radius:8px;background:#2C2C2A;color:#F1EFE8;font-size:12px;line-height:1.5;z-index:9999";
3424
+ t.setAttribute("role", "status");
3425
+ t.style.cssText = "position:fixed;left:50%;bottom:24px;transform:translateX(-50%);max-width:80%;padding:8px 14px;border-radius:8px;background:#2C2C2A;color:#F1EFE8;font-size:12px;line-height:1.5;z-index:9999;cursor:pointer";
3426
+ t.addEventListener("click", () => t.remove());
2403
3427
  document.body.appendChild(t);
2404
- setTimeout(() => t.remove(), 4200);
3428
+ setTimeout(() => t.remove(), toastDurationFor(msg, kind));
2405
3429
  };
3430
+ dsmNotifySink = paintToast;
2406
3431
  const DSM_KEY_SUBS = "dsm-subs-open-v1";
2407
3432
  const dsmSubsOpen = /* @__PURE__ */ new Set();
2408
3433
  try {
@@ -2518,7 +3543,8 @@ function installSidebarStatusDots() {
2518
3543
  }
2519
3544
  };
2520
3545
  walk(rec.nodes, 0);
2521
- const sig = rec.status + "|" + flat.map(([n, d]) => [n.sessionId, n.title, n.live ? 1 : 0, n.sizeBytes, d].join(":")).join(";");
3546
+ const effTitle = (n) => n.title || dsmAuthoritativeTitles.get(String(n.sessionId)) || "";
3547
+ const sig = rec.status + "|" + flat.map(([n, d]) => [n.sessionId, effTitle(n), n.live ? 1 : 0, n.sizeBytes, d].join(":")).join(";");
2522
3548
  if (box.dataset.dsmSig === sig) return;
2523
3549
  box.dataset.dsmSig = sig;
2524
3550
  box.textContent = "";
@@ -2557,7 +3583,7 @@ function installSidebarStatusDots() {
2557
3583
  });
2558
3584
  const name = document.createElement("span");
2559
3585
  name.className = "dsm-sub-name";
2560
- name.textContent = n.title || String(n.sessionId).slice(0, 8) + "\u2026";
3586
+ name.textContent = effTitle(n) || String(n.sessionId).slice(0, 8) + "\u2026";
2561
3587
  name.title = n.sessionId;
2562
3588
  r.appendChild(name);
2563
3589
  const meta = document.createElement("span");
@@ -2592,11 +3618,13 @@ function installSidebarStatusDots() {
2592
3618
  const FALLBACK_TICK_MS = 4e3;
2593
3619
  const tick = () => {
2594
3620
  if (typeof document !== "undefined" && document.hidden) return;
2595
- if (++dsmTrashTick % 8 === 0) dsmLoadTrashIds();
3621
+ const cadence = dsmWarmPending || dsmRefinePending ? 1 : 8;
3622
+ if (++dsmTrashTick % cadence === 0) dsmLoadTrashIds();
2596
3623
  paint();
2597
3624
  };
2598
3625
  tick();
2599
3626
  dsmLoadTrashIds();
3627
+ const coldSecondBeat = setTimeout(() => dsmLoadTrashIds(), 1500);
2600
3628
  paint();
2601
3629
  const obs = new MutationObserver(schedulePaint);
2602
3630
  obs.observe(document.body, {
@@ -2609,7 +3637,9 @@ function installSidebarStatusDots() {
2609
3637
  const tickTimer = setInterval(tick, FALLBACK_TICK_MS);
2610
3638
  return () => {
2611
3639
  clearInterval(tickTimer);
3640
+ clearTimeout(coldSecondBeat);
2612
3641
  obs.disconnect();
3642
+ dsmNotifySink = null;
2613
3643
  dsmTeardownSidebarAug();
2614
3644
  };
2615
3645
  }
@@ -2642,7 +3672,7 @@ function installSidebarWorkspaceDrag() {
2642
3672
  el.setAttribute("role", "status");
2643
3673
  el.textContent = message;
2644
3674
  document.body.appendChild(el);
2645
- setTimeout(() => el.remove(), 2600);
3675
+ setTimeout(() => el.remove(), toastDurationFor(message));
2646
3676
  };
2647
3677
  const clearVisuals = () => {
2648
3678
  document.querySelectorAll(".dsm-drag-source,.dsm-drop-target").forEach((el) => el.classList.remove("dsm-drag-source", "dsm-drop-target"));