@shendeguize/dsh-agent-sidecar 0.1.0 → 0.1.1

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
@@ -155,11 +155,12 @@ window.__ModuleLoader__.load({
155
155
  dead: "已结束",
156
156
  unknown: "未知"
157
157
  },
158
- /** Badge attention markers; gap outranks stale (per-session > global). */
159
- attention: {
160
- gap: "事件缺口",
161
- stale: "可能滞后"
162
- },
158
+ /**
159
+ * Badge attention markers. Only the per-session gap marker renders at
160
+ * card level; the global stream-health notice lives in the top banner
161
+ * alone (UX-18: repeating it on every card was pure noise).
162
+ */
163
+ attention: { gap: "事件缺口" },
163
164
  /** Daemon supervisor state labels for the top-bar badge. */
164
165
  daemon: {
165
166
  probe: "探测中",
@@ -189,7 +190,7 @@ window.__ModuleLoader__.load({
189
190
  daemonDeferTitle: "等待系统服务拉起 daemon",
190
191
  daemonDeferHint: "检测到 LaunchAgent 托管,插件不会自行启动 daemon;服务拉起后看板会自动出数。",
191
192
  filteredTitle: "当前过滤条件下没有会话",
192
- filteredHint: "试试放宽时间窗,或打开「显示已结束」。",
193
+ filteredHint: "试试放宽时间窗、取消状态过滤,或打开「显示已结束」。",
193
194
  noSessionsTitle: "暂无被观测的会话",
194
195
  noSessionsHint: "本机 agent(claude / codex / cursor / dsh …)开始工作后会自动出现在这里。"
195
196
  },
@@ -197,9 +198,25 @@ window.__ModuleLoader__.load({
197
198
  topbar: {
198
199
  title: "Sidecar 多 agent 看板",
199
200
  refresh: "刷新",
201
+ refreshing: "刷新中…",
200
202
  refreshTitle: "手动拉取最新快照",
203
+ refreshFailed: "手动刷新失败,看板仍显示原有快照",
204
+ dismiss: "知道了",
201
205
  showDead: "显示已结束",
202
- timeWindow: "时间窗"
206
+ timeWindow: "时间窗",
207
+ /** Count badges (UX-01); working/waiting are clickable status filters. */
208
+ countWorking: "{n} 工作中",
209
+ countWaiting: "{n} 等待中",
210
+ countTotal: "共 {n} 个会话",
211
+ filterByStatusTitle: "只看「{label}」的会话",
212
+ clearStatusFilterTitle: "取消状态过滤,显示全部会话"
213
+ },
214
+ /** Group collapse / truncation controls (UX-02). */
215
+ group: {
216
+ collapseTitle: "收起该分组",
217
+ expandTitle: "展开该分组",
218
+ showAll: "展开全部 {n} 个会话",
219
+ showLess: "只看前 {n} 个"
203
220
  },
204
221
  /** Session card texts. */
205
222
  card: {
@@ -209,14 +226,19 @@ window.__ModuleLoader__.load({
209
226
  observedDisclaimer: "状态为从持久化数据推断的观察值,可能滞后",
210
227
  observedValue: "观察值: {status}",
211
228
  lastReconcile: "最近对账: {time}",
212
- neverReconciled: "尚未对账"
229
+ neverReconciled: "尚未对账",
230
+ copyId: "点击复制完整会话 id",
231
+ copied: "已复制"
213
232
  },
214
- /** Relative time templates. */
233
+ /**
234
+ * Relative time templates. Ages ≥ 24h render as an absolute short date
235
+ * (`MM-DD HH:mm`, see formatRelativeTime) instead of a `{n} 天前` bucket
236
+ * (UX-13: whole columns of "3 天前" carried zero information).
237
+ */
215
238
  time: {
216
239
  justNow: "刚刚",
217
240
  minutesAgo: "{n} 分钟前",
218
- hoursAgo: "{n} 小时前",
219
- daysAgo: "{n} 天前"
241
+ hoursAgo: "{n} 小时前"
220
242
  },
221
243
  /** Time-window option templates. */
222
244
  timeWindow: {
@@ -297,6 +319,9 @@ window.__ModuleLoader__.load({
297
319
  };
298
320
  /**
299
321
  * Visibility rules (task spec):
322
+ * - an active `statusFilter` (UX-01) overrides everything: only sessions
323
+ * of that status are visible, regardless of window or showDead — the
324
+ * count badge and the filtered board therefore always agree;
300
325
  * - dead sessions are hidden unless `showDead`;
301
326
  * - working sessions are always visible (even outside the window);
302
327
  * - everything else hides once `updatedAtMs` falls strictly beyond the
@@ -305,6 +330,7 @@ window.__ModuleLoader__.load({
305
330
  */
306
331
  function isSessionVisible(session, filters, nowMs) {
307
332
  const status = normalizeStatus(session.status);
333
+ if (filters.statusFilter !== void 0) return status === filters.statusFilter;
308
334
  if (status === "dead" && !filters.showDead) return false;
309
335
  if (status === "working") return true;
310
336
  const windowMs = filters.timeWindowHours * HOUR_MS$2;
@@ -364,20 +390,19 @@ window.__ModuleLoader__.load({
364
390
  return groups;
365
391
  }
366
392
  /**
367
- * status + gap + streamHealth → badge tone/label/attention.
393
+ * status + gap → badge tone/label/attention.
368
394
  *
369
- * Priority: a per-session `gap` marker (a known data hole for THIS session)
370
- * outranks the global stale marker (stream reconnecting affects everyone
371
- * and is already surfaced by the top banner). Unknown raw statuses keep
395
+ * Card-level attention carries ONLY the per-session `gap` marker (a known
396
+ * data hole for THIS session). The global stream-health state is a
397
+ * board-wide fact and lives in the top banner alone repeating it on
398
+ * every card was noise, not signal (UX-18). Unknown raw statuses keep
372
399
  * their raw text as the label — the board never invents a state.
373
400
  */
374
- function deriveBadge(rawStatus, gap, streamHealth) {
401
+ function deriveBadge(rawStatus, gap) {
375
402
  const status = normalizeStatus(rawStatus);
376
403
  const trimmed = rawStatus.trim();
377
404
  const label = status === "unknown" ? trimmed === "" ? BOARD_STRINGS.status.unknown : trimmed : BOARD_STRINGS.status[status];
378
- let attention = null;
379
- if (gap) attention = "gap";
380
- else if (streamHealth !== "ok") attention = "stale";
405
+ const attention = gap ? "gap" : null;
381
406
  return {
382
407
  status,
383
408
  tone: STATUS_TONE$1[status],
@@ -400,8 +425,19 @@ window.__ModuleLoader__.load({
400
425
  ].join("\n");
401
426
  }
402
427
  /**
403
- * Coarse relative time: <60s (including clock skew into the future) is
404
- * 刚刚, then whole minutes/hours/days. Non-finite input renders empty.
428
+ * Absolute short timestamp in local time: `MM-DD HH:mm`. Used for ages
429
+ * beyond 24h where relative buckets stop discriminating (UX-13).
430
+ */
431
+ function formatAbsoluteShort(thenMs) {
432
+ const date = new Date(thenMs);
433
+ const pad = (n) => String(n).padStart(2, "0");
434
+ return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
435
+ }
436
+ /**
437
+ * Card time: <60s (including clock skew into the future) is 刚刚, then
438
+ * whole minutes/hours; from 24h on the absolute short date takes over —
439
+ * a column of "3 天前" carries no information, "08-22 14:03" does
440
+ * (UX-13). Non-finite input renders empty.
405
441
  */
406
442
  function formatRelativeTime$1(thenMs, nowMs) {
407
443
  if (!Number.isFinite(thenMs)) return "";
@@ -409,7 +445,7 @@ window.__ModuleLoader__.load({
409
445
  if (delta < MINUTE_MS$2) return BOARD_STRINGS.time.justNow;
410
446
  if (delta < HOUR_MS$2) return formatTemplate$2(BOARD_STRINGS.time.minutesAgo, { n: Math.floor(delta / MINUTE_MS$2) });
411
447
  if (delta < DAY_MS$2) return formatTemplate$2(BOARD_STRINGS.time.hoursAgo, { n: Math.floor(delta / HOUR_MS$2) });
412
- return formatTemplate$2(BOARD_STRINGS.time.daysAgo, { n: Math.floor(delta / DAY_MS$2) });
448
+ return formatAbsoluteShort(thenMs);
413
449
  }
414
450
  /** Label for a time-window option: whole days as 天, otherwise 小时. */
415
451
  function timeWindowLabel(hours) {
@@ -504,6 +540,30 @@ window.__ModuleLoader__.load({
504
540
  return `${id.slice(0, 12)}…${id.slice(-6)}`;
505
541
  }
506
542
  /**
543
+ * Slice a status-sorted card list down to `limit` for display, unless
544
+ * `expanded`. The cut never lands inside the leading working/waiting run:
545
+ * attention-worthy sessions are the reason the board exists, so the
546
+ * effective limit grows to cover all of them (an all-active group renders
547
+ * fully — honest, and rare). Non-positive limits disable truncation.
548
+ */
549
+ function sliceCardsForDisplay(cards, limit, expanded) {
550
+ if (expanded || limit <= 0 || cards.length <= limit) return {
551
+ shown: [...cards],
552
+ hiddenCount: 0
553
+ };
554
+ let activeRun = 0;
555
+ while (activeRun < cards.length) {
556
+ const status = normalizeStatus(cards[activeRun].status);
557
+ if (status !== "working" && status !== "waiting") break;
558
+ activeRun += 1;
559
+ }
560
+ const effectiveLimit = Math.max(limit, activeRun);
561
+ return {
562
+ shown: cards.slice(0, effectiveLimit),
563
+ hiddenCount: cards.length - Math.min(cards.length, effectiveLimit)
564
+ };
565
+ }
566
+ /**
507
567
  * Connection dot: green only when the daemon is connected (adopted/hosted)
508
568
  * AND the event stream is healthy; FAILED is the only hard-off state;
509
569
  * every transitional state shows the cautious yellow.
@@ -513,12 +573,16 @@ window.__ModuleLoader__.load({
513
573
  if ((daemonState === "adopted" || daemonState === "hosted") && streamHealth === "ok") return "ok";
514
574
  return "degraded";
515
575
  }
516
- /** Count of sessions currently observed as working. */
517
- function countWorking(sessions) {
576
+ /** Count of sessions observed in one normalized status. */
577
+ function countByStatus(sessions, status) {
518
578
  let count = 0;
519
- for (const session of sessions) if (normalizeStatus(session.status) === "working") count += 1;
579
+ for (const session of sessions) if (normalizeStatus(session.status) === status) count += 1;
520
580
  return count;
521
581
  }
582
+ /** Count of sessions currently observed as working. */
583
+ function countWorking(sessions) {
584
+ return countByStatus(sessions, "working");
585
+ }
522
586
  /** Widget hover/aria text: connection state, plus the count when nonzero. */
523
587
  function widgetTitle(connection, workingCount) {
524
588
  const base = `${BOARD_STRINGS.widget.label}: ${BOARD_STRINGS.widget.connection[connection]}`;
@@ -532,7 +596,7 @@ window.__ModuleLoader__.load({
532
596
  return {
533
597
  groups: groupSessions(visible.map((session) => ({
534
598
  ...session,
535
- badge: deriveBadge(session.status, session.gap, streamHealth),
599
+ badge: deriveBadge(session.status, session.gap),
536
600
  glyph: agentGlyph$1(session.agent),
537
601
  shortId: abbreviateSessionId(session.sessionId),
538
602
  relativeTime: formatRelativeTime$1(session.updatedAtMs, nowMs),
@@ -545,7 +609,8 @@ window.__ModuleLoader__.load({
545
609
  streamTone: streamHealthTone(streamHealth),
546
610
  visibleCount: visible.length,
547
611
  totalCount: sessions.length,
548
- workingCount: countWorking(sessions)
612
+ workingCount: countWorking(sessions),
613
+ waitingCount: countByStatus(sessions, "waiting")
549
614
  };
550
615
  }
551
616
  //#endregion
@@ -571,6 +636,11 @@ window.__ModuleLoader__.load({
571
636
  listenOn: "监听中",
572
637
  listenOff: "监听",
573
638
  listenHint: "开启后新事件将实时追加并高亮",
639
+ refresh: "刷新",
640
+ refreshing: "刷新中…",
641
+ refreshHint: "手动拉取最新时间线窗口",
642
+ copyIdTitle: "点击复制会话 ID",
643
+ copied: "已复制",
574
644
  untitled: "(无标题)",
575
645
  unknownProject: "未知项目",
576
646
  /** Design §5.3 / SKILL.md wording: statuses are inferred observations. */
@@ -607,6 +677,12 @@ window.__ModuleLoader__.load({
607
677
  },
608
678
  /** Seq-discontinuity marker row (design §4.b.3 honest presentation). */
609
679
  gap: { label: "缺口:可能有 {n} 条事件未捕获(256 队列上限或未持久化)" },
680
+ /** Kind filter chips (UX-03): protocol noise hidden by default, honestly counted. */
681
+ filter: {
682
+ conversation: "只看对话",
683
+ all: "全部事件",
684
+ hiddenNotice: "已隐藏 {n} 条协议事件"
685
+ },
610
686
  /** Timeline list chrome. */
611
687
  timeline: {
612
688
  loadMore: "加载更多历史",
@@ -617,7 +693,9 @@ window.__ModuleLoader__.load({
617
693
  newBadge: "新",
618
694
  seq: "seq {n}",
619
695
  hiddenNotice: "为保持流畅,较早的 {n} 条已折叠",
620
- showAll: "全部显示"
696
+ showAll: "全部显示",
697
+ /** Aggregated row for an adjacent run of empty streaming chunks (UX-03). */
698
+ chunkRun: "{n} 个流式分块"
621
699
  },
622
700
  /** Loading / empty / error body states. */
623
701
  states: {
@@ -773,6 +851,9 @@ window.__ModuleLoader__.load({
773
851
  liveChip: "实时",
774
852
  /** Untitled-session fallback. */
775
853
  untitled: "(无标题)",
854
+ /** Lane truncation fold (UX-20; mirrors the board's group fold). */
855
+ showAllSessions: "展开全部 {n} 个会话",
856
+ showLessSessions: "只看前 {n} 个",
776
857
  /** Empty state (no groups at all). */
777
858
  empty: {
778
859
  title: "暂无项目关联",
@@ -835,7 +916,7 @@ window.__ModuleLoader__.load({
835
916
  lastActivityAt: session.lastActivityAt,
836
917
  live,
837
918
  gap,
838
- badge: deriveBadge(session.status, gap, "ok"),
919
+ badge: deriveBadge(session.status, gap),
839
920
  glyph: agentGlyph$1(session.agent),
840
921
  shortId: abbreviateSessionId(session.sessionId),
841
922
  relativeTime: formatRelativeTime$1(session.lastActivityAt, nowMs),
@@ -1010,7 +1091,7 @@ window.__ModuleLoader__.load({
1010
1091
  "settings.streamIdleMsHint": "无会话工作时的 status 快照周期。",
1011
1092
  "settings.sectionInject": "消息注入",
1012
1093
  "settings.injectEnabledLabel": "启用注入",
1013
- "settings.injectEnabledHint": "关闭时看板隐藏全部注入入口,写接口在服务端同步拒绝。",
1094
+ "settings.injectEnabledHint": "关闭后注入面板为只读禁用态,写接口在服务端同步拒绝。",
1014
1095
  "settings.injectDefaultModeLabel": "默认注入模式",
1015
1096
  "settings.injectDefaultModeHint": "注入面板打开时预选的模式。",
1016
1097
  "settings.injectModeQueue": "queue(排队下一轮)",
@@ -1064,6 +1145,7 @@ window.__ModuleLoader__.load({
1064
1145
  "inject.resultUnknown": "结果未知:消息可能已投递。请勿重试;请前往目标会话核对后再决定下一步。",
1065
1146
  "inject.resultReplayed": "幂等重放:返回的是此前同一请求的结果,未发生二次注入。",
1066
1147
  "inject.reprepare": "重新准备",
1148
+ "inject.observeListen": "开启监听观察反应",
1067
1149
  "inject.errInjectDisabled": "注入功能已在服务端关闭;请在设置中开启注入。",
1068
1150
  "inject.errInvalidMessage": "消息未通过服务端校验。",
1069
1151
  "inject.errTargetNotFound": "目标会话不存在或已离开观测范围。",
@@ -1086,6 +1168,11 @@ window.__ModuleLoader__.load({
1086
1168
  "detail.header.listenOn": D.header.listenOn,
1087
1169
  "detail.header.listenOff": D.header.listenOff,
1088
1170
  "detail.header.listenHint": D.header.listenHint,
1171
+ "detail.header.refresh": D.header.refresh,
1172
+ "detail.header.refreshing": D.header.refreshing,
1173
+ "detail.header.refreshHint": D.header.refreshHint,
1174
+ "detail.header.copyIdTitle": D.header.copyIdTitle,
1175
+ "detail.header.copied": D.header.copied,
1089
1176
  "detail.header.untitled": D.header.untitled,
1090
1177
  "detail.header.unknownProject": D.header.unknownProject,
1091
1178
  "detail.header.observedDisclaimer": D.header.observedDisclaimer,
@@ -1110,6 +1197,9 @@ window.__ModuleLoader__.load({
1110
1197
  "detail.kind.error": D.kind.error,
1111
1198
  "detail.kind.other": D.kind.other,
1112
1199
  "detail.gap.label": D.gap.label,
1200
+ "detail.filter.conversation": D.filter.conversation,
1201
+ "detail.filter.all": D.filter.all,
1202
+ "detail.filter.hiddenNotice": D.filter.hiddenNotice,
1113
1203
  "detail.timeline.loadMore": D.timeline.loadMore,
1114
1204
  "detail.timeline.loadingMore": D.timeline.loadingMore,
1115
1205
  "detail.timeline.noMore": D.timeline.noMore,
@@ -1119,6 +1209,7 @@ window.__ModuleLoader__.load({
1119
1209
  "detail.timeline.seq": D.timeline.seq,
1120
1210
  "detail.timeline.hiddenNotice": D.timeline.hiddenNotice,
1121
1211
  "detail.timeline.showAll": D.timeline.showAll,
1212
+ "detail.timeline.chunkRun": D.timeline.chunkRun,
1122
1213
  "detail.states.loadingTitle": D.states.loadingTitle,
1123
1214
  "detail.states.emptyTitle": D.states.emptyTitle,
1124
1215
  "detail.states.emptyHint": D.states.emptyHint,
@@ -1136,6 +1227,9 @@ window.__ModuleLoader__.load({
1136
1227
  "detail.actions.inject": "注入",
1137
1228
  "detail.actions.analyze": "AI 分析",
1138
1229
  "detail.actions.analyzeDisabledHint": "在设置中开启「启用 AI 旁路分析」后可用",
1230
+ "detail.tools.title": "谱系与检索",
1231
+ "detail.tools.show": "展开",
1232
+ "detail.tools.hide": "收起",
1139
1233
  "dshtools.lineage.title": Q.lineage.title,
1140
1234
  "dshtools.lineage.loading": Q.lineage.loading,
1141
1235
  "dshtools.lineage.error": Q.lineage.error,
@@ -1181,6 +1275,8 @@ window.__ModuleLoader__.load({
1181
1275
  "project.lastActive": P.lastActive,
1182
1276
  "project.liveChip": P.liveChip,
1183
1277
  "project.untitled": P.untitled,
1278
+ "project.showAllSessions": P.showAllSessions,
1279
+ "project.showLessSessions": P.showLessSessions,
1184
1280
  "project.empty.title": P.empty.title,
1185
1281
  "project.empty.hint": P.empty.hint,
1186
1282
  "project.loading": P.loading,
@@ -1239,7 +1335,7 @@ window.__ModuleLoader__.load({
1239
1335
  "command.noSessions": "暂无被观测的会话",
1240
1336
  "command.unknownProject": "未知项目",
1241
1337
  "command.untitled": "(无标题)",
1242
- "command.truncated": "还有 {n} 个活跃会话未列出",
1338
+ "command.truncated": "还有 {n} 个会话未列出",
1243
1339
  "command.boardHint": "打开会话视图的「Sidecar」Tab 查看完整看板",
1244
1340
  "command.unreachable": "sidecar 未连接",
1245
1341
  "command.unreachableHint": "无法获取状态快照;请确认 agent-sidecar 插件已启用、daemon 可用后重试。",
@@ -1308,7 +1404,7 @@ window.__ModuleLoader__.load({
1308
1404
  "settings.streamIdleMsHint": "Status snapshot cadence while no session is working.",
1309
1405
  "settings.sectionInject": "Message injection",
1310
1406
  "settings.injectEnabledLabel": "Enable injection",
1311
- "settings.injectEnabledHint": "When off, the board hides every inject affordance and the server rejects write actions.",
1407
+ "settings.injectEnabledHint": "When off, the inject panel renders read-only and disabled, and the server rejects write actions.",
1312
1408
  "settings.injectDefaultModeLabel": "Default injection mode",
1313
1409
  "settings.injectDefaultModeHint": "The mode preselected when the inject panel opens.",
1314
1410
  "settings.injectModeQueue": "queue (next turn)",
@@ -1362,6 +1458,7 @@ window.__ModuleLoader__.load({
1362
1458
  "inject.resultUnknown": "Outcome unknown: the message may have been delivered. Do NOT retry; check the target session before deciding anything.",
1363
1459
  "inject.resultReplayed": "Idempotent replay: this is the earlier result of the same request — no second injection happened.",
1364
1460
  "inject.reprepare": "Prepare again",
1461
+ "inject.observeListen": "Listen for the reaction",
1365
1462
  "inject.errInjectDisabled": "Injection is disabled on the server; enable it in Settings.",
1366
1463
  "inject.errInvalidMessage": "The message failed server-side validation.",
1367
1464
  "inject.errTargetNotFound": "The target session does not exist or left the observation window.",
@@ -1384,6 +1481,11 @@ window.__ModuleLoader__.load({
1384
1481
  "detail.header.listenOn": "Listening",
1385
1482
  "detail.header.listenOff": "Listen",
1386
1483
  "detail.header.listenHint": "New events append live and get highlighted while on",
1484
+ "detail.header.refresh": "Refresh",
1485
+ "detail.header.refreshing": "Refreshing…",
1486
+ "detail.header.refreshHint": "Pull the newest timeline window",
1487
+ "detail.header.copyIdTitle": "Click to copy the session ID",
1488
+ "detail.header.copied": "Copied",
1387
1489
  "detail.header.untitled": "(untitled)",
1388
1490
  "detail.header.unknownProject": "Unknown project",
1389
1491
  "detail.header.observedDisclaimer": "Status is an observed value inferred from persisted data and may lag",
@@ -1408,6 +1510,9 @@ window.__ModuleLoader__.load({
1408
1510
  "detail.kind.error": "Error",
1409
1511
  "detail.kind.other": "Event",
1410
1512
  "detail.gap.label": "Gap: about {n} events may be uncaptured (256-slot queue cap or not persisted)",
1513
+ "detail.filter.conversation": "Conversation only",
1514
+ "detail.filter.all": "All events",
1515
+ "detail.filter.hiddenNotice": "{n} protocol events hidden",
1411
1516
  "detail.timeline.loadMore": "Load older history",
1412
1517
  "detail.timeline.loadingMore": "Loading…",
1413
1518
  "detail.timeline.noMore": "Start of the timeline",
@@ -1417,6 +1522,7 @@ window.__ModuleLoader__.load({
1417
1522
  "detail.timeline.seq": "seq {n}",
1418
1523
  "detail.timeline.hiddenNotice": "{n} earlier entries collapsed to stay smooth",
1419
1524
  "detail.timeline.showAll": "Show all",
1525
+ "detail.timeline.chunkRun": "{n} streaming chunks",
1420
1526
  "detail.states.loadingTitle": "Loading the timeline…",
1421
1527
  "detail.states.emptyTitle": "No events yet",
1422
1528
  "detail.states.emptyHint": "This session has no normalized events to show yet.",
@@ -1434,6 +1540,9 @@ window.__ModuleLoader__.load({
1434
1540
  "detail.actions.inject": "Inject",
1435
1541
  "detail.actions.analyze": "AI analysis",
1436
1542
  "detail.actions.analyzeDisabledHint": "Enable \"AI bypass analysis\" in Settings to use this",
1543
+ "detail.tools.title": "Lineage & search",
1544
+ "detail.tools.show": "Show",
1545
+ "detail.tools.hide": "Hide",
1437
1546
  "dshtools.lineage.title": "Session lineage",
1438
1547
  "dshtools.lineage.loading": "Loading lineage…",
1439
1548
  "dshtools.lineage.error": "Lineage failed to load",
@@ -1479,6 +1588,8 @@ window.__ModuleLoader__.load({
1479
1588
  "project.lastActive": "Last active {time}",
1480
1589
  "project.liveChip": "Live",
1481
1590
  "project.untitled": "(untitled)",
1591
+ "project.showAllSessions": "Show all {n} sessions",
1592
+ "project.showLessSessions": "Show first {n} only",
1482
1593
  "project.empty.title": "No project correlation yet",
1483
1594
  "project.empty.hint": "No cross-agent project activity within the time window; projects appear here once an agent works inside a project directory.",
1484
1595
  "project.loading": "Loading project correlation…",
@@ -1537,7 +1648,7 @@ window.__ModuleLoader__.load({
1537
1648
  "command.noSessions": "No observed sessions yet",
1538
1649
  "command.unknownProject": "Unknown project",
1539
1650
  "command.untitled": "(untitled)",
1540
- "command.truncated": "{n} more active sessions not listed",
1651
+ "command.truncated": "{n} more sessions not listed",
1541
1652
  "command.boardHint": "Open the \"Sidecar\" tab in the conversation view for the full board",
1542
1653
  "command.unreachable": "sidecar is not connected",
1543
1654
  "command.unreachableHint": "The state snapshot could not be fetched; check that the agent-sidecar plugin is enabled and the daemon is available, then retry.",
@@ -2286,7 +2397,11 @@ window.__ModuleLoader__.load({
2286
2397
  return null;
2287
2398
  }
2288
2399
  }
2289
- /** Parse + validate persisted filters; anything malformed reads as absent. */
2400
+ /**
2401
+ * Parse + validate persisted filters; anything malformed reads as absent.
2402
+ * The optional statusFilter (UX-01) survives only as one of its two legal
2403
+ * values — an unrecognized token is dropped, not the whole record.
2404
+ */
2290
2405
  function readStoredFilters(storage) {
2291
2406
  if (storage === null) return null;
2292
2407
  try {
@@ -2296,10 +2411,12 @@ window.__ModuleLoader__.load({
2296
2411
  if (typeof parsed !== "object" || parsed === null) return null;
2297
2412
  const candidate = parsed;
2298
2413
  if (typeof candidate.timeWindowHours !== "number" || !Number.isFinite(candidate.timeWindowHours) || candidate.timeWindowHours <= 0 || typeof candidate.showDead !== "boolean") return null;
2299
- return {
2414
+ const filters = {
2300
2415
  timeWindowHours: candidate.timeWindowHours,
2301
2416
  showDead: candidate.showDead
2302
2417
  };
2418
+ if (candidate.statusFilter === "working" || candidate.statusFilter === "waiting") filters.statusFilter = candidate.statusFilter;
2419
+ return filters;
2303
2420
  } catch {
2304
2421
  return null;
2305
2422
  }
@@ -2394,13 +2511,20 @@ window.__ModuleLoader__.load({
2394
2511
  };
2395
2512
  this.notify();
2396
2513
  }
2397
- /** Manual refresh (board's refresh button): one out-of-band snapshot pull. */
2514
+ /**
2515
+ * Manual refresh (board's refresh button): one out-of-band snapshot
2516
+ * pull. Resolves true when the snapshot applied, false on failure so
2517
+ * the button can surface honest feedback (UX-07) — never rejects; the
2518
+ * stream (and its status surface) remains the health authority.
2519
+ */
2398
2520
  async refresh() {
2399
2521
  try {
2400
2522
  const snapshot = await this.fetchFn({});
2401
2523
  this.applySnapshot(snapshot);
2524
+ return true;
2402
2525
  } catch (err) {
2403
2526
  console.error("agent-sidecar: manual refresh failed", err);
2527
+ return false;
2404
2528
  }
2405
2529
  }
2406
2530
  applySnapshot(snapshot) {
@@ -2522,7 +2646,7 @@ window.__ModuleLoader__.load({
2522
2646
  }
2523
2647
  //#endregion
2524
2648
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/board/board.module.css.mjs
2525
- const css$8 = ".aJ0YNW_root{box-sizing:border-box;height:100%;min-height:420px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:12px;padding:16px 20px;display:flex;overflow-y:auto}.aJ0YNW_topbar{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.aJ0YNW_title{color:var(--dsw-alias-label-primary,#1f2328);margin-right:2px;font-size:15px;font-weight:650}.aJ0YNW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:5px;padding:0 8px;font-size:12px;line-height:20px;display:inline-flex}.aJ0YNW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:8px;height:8px}.aJ0YNW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.aJ0YNW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.aJ0YNW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.aJ0YNW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.aJ0YNW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.aJ0YNW_spacer{flex:1}.aJ0YNW_control{color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;align-items:center;gap:5px;font-size:12px;display:inline-flex}.aJ0YNW_select{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,#0000001f);border-radius:6px;padding:2px 6px;font-family:inherit;font-size:12px}.aJ0YNW_checkbox{accent-color:var(--dsw-alias-brand-primary,#4d6bfe);margin:0}.aJ0YNW_refresh{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:2px 10px;font-family:inherit;font-size:12px}.aJ0YNW_refresh:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_banner{border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.aJ0YNW_banner[data-tone=warn]{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border-color:var(--dsw-alias-state-warn-secondary,#9a67003d)}.aJ0YNW_banner[data-tone=danger]{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border-color:var(--dsw-alias-state-error-secondary,#cf222e3d)}.aJ0YNW_empty{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:180px;padding:24px;display:flex}.aJ0YNW_emptyTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.aJ0YNW_emptyHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.aJ0YNW_group{flex-direction:column;gap:8px;display:flex}.aJ0YNW_groupHead{align-items:baseline;gap:8px;min-width:0;display:flex}.aJ0YNW_groupName{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.aJ0YNW_groupCount{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:18px}.aJ0YNW_grid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px;display:grid}.aJ0YNW_card{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);cursor:pointer;border-radius:8px;flex-direction:column;gap:6px;min-width:0;padding:10px 12px;font-family:inherit;display:flex}.aJ0YNW_card:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_card:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.aJ0YNW_cardHead{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.aJ0YNW_agent{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:12px;font-weight:600;display:inline-flex;overflow:hidden}.aJ0YNW_glyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.aJ0YNW_attention{color:var(--dsw-alias-state-warn-primary,#9a6700);font-size:11px}.aJ0YNW_attention[data-kind=gap]{color:var(--dsw-alias-state-error-primary,#cf222e)}.aJ0YNW_cardTitle{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.aJ0YNW_cardId{color:var(--dsw-alias-label-tertiary,#6e7781);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.aJ0YNW_cardEvent{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.aJ0YNW_cardTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));font-size:11px}";
2649
+ const css$8 = ".aJ0YNW_root{box-sizing:border-box;height:100%;min-height:420px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:12px;padding:16px 20px;display:flex;overflow-y:auto}.aJ0YNW_topbar{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.aJ0YNW_title{color:var(--dsw-alias-label-primary,#1f2328);margin-right:2px;font-size:15px;font-weight:650}.aJ0YNW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:5px;padding:0 8px;font-size:12px;line-height:20px;display:inline-flex}.aJ0YNW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:8px;height:8px}.aJ0YNW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.aJ0YNW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.aJ0YNW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.aJ0YNW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.aJ0YNW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.aJ0YNW_countBadge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;cursor:pointer;border-radius:999px;align-items:center;gap:5px;padding:0 8px;font-family:inherit;font-size:12px;line-height:20px;display:inline-flex}.aJ0YNW_countBadge:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_countBadge[aria-pressed=true]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe)}.aJ0YNW_countTotal{color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;font-size:12px}.aJ0YNW_spacer{flex:1}.aJ0YNW_control{color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;align-items:center;gap:5px;font-size:12px;display:inline-flex}.aJ0YNW_select{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,#0000001f);border-radius:6px;padding:2px 6px;font-family:inherit;font-size:12px}.aJ0YNW_checkbox{accent-color:var(--dsw-alias-brand-primary,#4d6bfe);margin:0}.aJ0YNW_refresh{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:2px 10px;font-family:inherit;font-size:12px}.aJ0YNW_refresh:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_refresh:disabled{cursor:default;opacity:.6}.aJ0YNW_banner{border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.aJ0YNW_banner[data-tone=warn]{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border-color:var(--dsw-alias-state-warn-secondary,#9a67003d)}.aJ0YNW_banner[data-tone=danger]{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border-color:var(--dsw-alias-state-error-secondary,#cf222e3d)}.aJ0YNW_bannerDismiss{color:inherit;cursor:pointer;background:0 0;border:none;margin-left:10px;padding:0;font-family:inherit;font-size:12px;text-decoration:underline}.aJ0YNW_empty{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:180px;padding:24px;display:flex}.aJ0YNW_emptyTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.aJ0YNW_emptyHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.aJ0YNW_group{flex-direction:column;gap:8px;display:flex}.aJ0YNW_groupHead{text-align:left;cursor:pointer;background:0 0;border:none;align-items:baseline;gap:8px;width:100%;min-width:0;padding:0;font-family:inherit;display:flex}.aJ0YNW_chevron{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:10px}.aJ0YNW_groupAttention{color:var(--dsw-alias-state-warn-primary,#9a6700);flex:none;font-size:11px}.aJ0YNW_showMore{color:var(--dsw-alias-label-secondary,#57606a);border:1px dashed var(--dsw-alias-border-l2,#0000001f);cursor:pointer;background:0 0;border-radius:6px;align-self:flex-start;padding:2px 10px;font-family:inherit;font-size:12px}.aJ0YNW_showMore:hover{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_groupName{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.aJ0YNW_groupCount{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:18px}.aJ0YNW_grid{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px;display:grid}.aJ0YNW_card{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);cursor:pointer;border-radius:8px;flex-direction:column;gap:6px;min-width:0;padding:10px 12px;font-family:inherit;display:flex}.aJ0YNW_card:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.aJ0YNW_card:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.aJ0YNW_cardHead{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.aJ0YNW_agent{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:12px;font-weight:600;display:inline-flex;overflow:hidden}.aJ0YNW_glyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.aJ0YNW_attention{color:var(--dsw-alias-state-warn-primary,#9a6700);font-size:11px}.aJ0YNW_attention[data-kind=gap]{color:var(--dsw-alias-state-error-primary,#cf222e)}.aJ0YNW_cardTitle{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.aJ0YNW_cardId{color:var(--dsw-alias-label-tertiary,#6e7781);text-overflow:ellipsis;white-space:nowrap;cursor:copy;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.aJ0YNW_cardId:hover{color:var(--dsw-alias-label-secondary,#57606a)}.aJ0YNW_copied{color:var(--dsw-alias-state-success-primary,#1a7f37);margin-left:6px;font-family:inherit}.aJ0YNW_cardEvent{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.aJ0YNW_cardTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));font-size:11px}";
2526
2650
  const tagId$8 = "@shendeguize/dsh-agent-sidecar/board.module.css";
2527
2651
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$8) + "]") === null) {
2528
2652
  const tag = document.createElement("style");
@@ -2536,6 +2660,7 @@ window.__ModuleLoader__.load({
2536
2660
  "attention": "aJ0YNW_attention",
2537
2661
  "badge": "aJ0YNW_badge",
2538
2662
  "banner": "aJ0YNW_banner",
2663
+ "bannerDismiss": "aJ0YNW_bannerDismiss",
2539
2664
  "card": "aJ0YNW_card",
2540
2665
  "cardEvent": "aJ0YNW_cardEvent",
2541
2666
  "cardHead": "aJ0YNW_cardHead",
@@ -2543,7 +2668,11 @@ window.__ModuleLoader__.load({
2543
2668
  "cardTime": "aJ0YNW_cardTime",
2544
2669
  "cardTitle": "aJ0YNW_cardTitle",
2545
2670
  "checkbox": "aJ0YNW_checkbox",
2671
+ "chevron": "aJ0YNW_chevron",
2546
2672
  "control": "aJ0YNW_control",
2673
+ "copied": "aJ0YNW_copied",
2674
+ "countBadge": "aJ0YNW_countBadge",
2675
+ "countTotal": "aJ0YNW_countTotal",
2547
2676
  "dot": "aJ0YNW_dot",
2548
2677
  "empty": "aJ0YNW_empty",
2549
2678
  "emptyHint": "aJ0YNW_emptyHint",
@@ -2551,18 +2680,39 @@ window.__ModuleLoader__.load({
2551
2680
  "glyph": "aJ0YNW_glyph",
2552
2681
  "grid": "aJ0YNW_grid",
2553
2682
  "group": "aJ0YNW_group",
2683
+ "groupAttention": "aJ0YNW_groupAttention",
2554
2684
  "groupCount": "aJ0YNW_groupCount",
2555
2685
  "groupHead": "aJ0YNW_groupHead",
2556
2686
  "groupName": "aJ0YNW_groupName",
2557
2687
  "refresh": "aJ0YNW_refresh",
2558
2688
  "root": "aJ0YNW_root",
2559
2689
  "select": "aJ0YNW_select",
2690
+ "showMore": "aJ0YNW_showMore",
2560
2691
  "spacer": "aJ0YNW_spacer",
2561
2692
  "title": "aJ0YNW_title",
2562
2693
  "topbar": "aJ0YNW_topbar"
2563
2694
  };
2564
2695
  //#endregion
2565
2696
  //#region src/client/board/Board.tsx
2697
+ /**
2698
+ * Cross-agent session board (design §5.1 view 1).
2699
+ *
2700
+ * Presentation-only: no data fetching, no api/sse imports. Everything
2701
+ * arrives through props already shaped as the view models of `logic.ts`;
2702
+ * the integration layer (T2.4) owns state, transport, and the mapping
2703
+ * from the host wire types (epoch-seconds → epoch-ms conversion included).
2704
+ *
2705
+ * Interaction surface handed back to the owner:
2706
+ * - `onFiltersChange` — time-window select / show-dead checkbox / the
2707
+ * top-bar status-filter badges (controlled, UX-01);
2708
+ * - `onRefresh` — manual snapshot pull button; a Promise<boolean>
2709
+ * return drives the in-flight/failure feedback (UX-07);
2710
+ * - `onSelectSession` — card click, pass-through for the M3 detail view.
2711
+ *
2712
+ * Local UI state (deliberately NOT lifted into the controller stores):
2713
+ * group collapse and per-group truncation (UX-02) are ephemeral view
2714
+ * concerns — useState here, reset on tab remount.
2715
+ */
2566
2716
  /** Time-window choices offered by the top bar (hours). */
2567
2717
  const TIME_WINDOW_OPTIONS = [
2568
2718
  6,
@@ -2573,6 +2723,18 @@ window.__ModuleLoader__.load({
2573
2723
  ];
2574
2724
  function SessionCard(props) {
2575
2725
  const { card, onSelect } = props;
2726
+ const [copied, setCopied] = (0, react.useState)(false);
2727
+ const onCopyId = (ev) => {
2728
+ ev.stopPropagation();
2729
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
2730
+ if (clipboard === void 0) return;
2731
+ clipboard.writeText(card.sessionId).then(() => {
2732
+ setCopied(true);
2733
+ setTimeout(() => {
2734
+ setCopied(false);
2735
+ }, 2e3);
2736
+ }, () => {});
2737
+ };
2576
2738
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2577
2739
  type: "button",
2578
2740
  className: board_module_css_default["card"],
@@ -2611,10 +2773,16 @@ window.__ModuleLoader__.load({
2611
2773
  title: card.title,
2612
2774
  children: card.title.trim() === "" ? BOARD_STRINGS.card.untitled : card.title
2613
2775
  }),
2614
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2615
2777
  className: board_module_css_default["cardId"],
2616
- title: card.sessionId,
2617
- children: card.shortId
2778
+ title: `${card.sessionId}\n${BOARD_STRINGS.card.copyId}`,
2779
+ onClick: onCopyId,
2780
+ "data-testid": "agent-sidecar-card-id",
2781
+ children: [card.shortId, copied && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2782
+ className: board_module_css_default["copied"],
2783
+ role: "status",
2784
+ children: BOARD_STRINGS.card.copied
2785
+ })]
2618
2786
  }),
2619
2787
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2620
2788
  className: board_module_css_default["cardEvent"],
@@ -2629,25 +2797,65 @@ window.__ModuleLoader__.load({
2629
2797
  }
2630
2798
  function ProjectGroup(props) {
2631
2799
  const { group, onSelect } = props;
2800
+ const [collapsed, setCollapsed] = (0, react.useState)(false);
2801
+ const [expanded, setExpanded] = (0, react.useState)(false);
2802
+ const { shown, hiddenCount } = sliceCardsForDisplay(group.cards, 20, expanded);
2803
+ const waitingInGroup = group.cards.filter((card) => card.badge.status === "waiting").length;
2632
2804
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2633
2805
  className: board_module_css_default["group"],
2634
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2806
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2807
+ type: "button",
2635
2808
  className: board_module_css_default["groupHead"],
2636
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2637
- className: board_module_css_default["groupName"],
2638
- title: group.fullPath === "" ? void 0 : group.fullPath,
2639
- children: group.label
2640
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2641
- className: board_module_css_default["groupCount"],
2642
- children: formatTemplate$2(BOARD_STRINGS.groupCount, { n: group.cards.length })
2643
- })]
2644
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2645
- className: board_module_css_default["grid"],
2646
- children: group.cards.map((card) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionCard, {
2647
- card,
2648
- onSelect
2649
- }, `${card.agent}:${card.sessionId}`))
2650
- })]
2809
+ "aria-expanded": !collapsed,
2810
+ title: collapsed ? BOARD_STRINGS.group.expandTitle : BOARD_STRINGS.group.collapseTitle,
2811
+ onClick: () => {
2812
+ setCollapsed(!collapsed);
2813
+ },
2814
+ children: [
2815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2816
+ className: board_module_css_default["chevron"],
2817
+ "aria-hidden": true,
2818
+ children: collapsed ? "" : "▾"
2819
+ }),
2820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2821
+ className: board_module_css_default["groupName"],
2822
+ title: group.fullPath === "" ? void 0 : group.fullPath,
2823
+ children: group.label
2824
+ }),
2825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2826
+ className: board_module_css_default["groupCount"],
2827
+ children: formatTemplate$2(BOARD_STRINGS.groupCount, { n: group.cards.length })
2828
+ }),
2829
+ collapsed && waitingInGroup > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2830
+ className: board_module_css_default["groupAttention"],
2831
+ children: formatTemplate$2(BOARD_STRINGS.topbar.countWaiting, { n: waitingInGroup })
2832
+ })
2833
+ ]
2834
+ }), !collapsed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2836
+ className: board_module_css_default["grid"],
2837
+ children: shown.map((card) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionCard, {
2838
+ card,
2839
+ onSelect
2840
+ }, `${card.agent}:${card.sessionId}`))
2841
+ }),
2842
+ hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2843
+ type: "button",
2844
+ className: board_module_css_default["showMore"],
2845
+ onClick: () => {
2846
+ setExpanded(true);
2847
+ },
2848
+ children: formatTemplate$2(BOARD_STRINGS.group.showAll, { n: group.cards.length })
2849
+ }),
2850
+ expanded && group.cards.length > 20 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2851
+ type: "button",
2852
+ className: board_module_css_default["showMore"],
2853
+ onClick: () => {
2854
+ setExpanded(false);
2855
+ },
2856
+ children: formatTemplate$2(BOARD_STRINGS.group.showLess, { n: 20 })
2857
+ })
2858
+ ] })]
2651
2859
  });
2652
2860
  }
2653
2861
  /** The board view. Pure render of `buildBoardViewModel` over the props. */
@@ -2662,6 +2870,30 @@ window.__ModuleLoader__.load({
2662
2870
  nowMs
2663
2871
  });
2664
2872
  const windowOptions = TIME_WINDOW_OPTIONS.includes(props.filters.timeWindowHours) ? TIME_WINDOW_OPTIONS : [...TIME_WINDOW_OPTIONS, props.filters.timeWindowHours].sort((a, b) => a - b);
2873
+ const [refreshing, setRefreshing] = (0, react.useState)(false);
2874
+ const [refreshFailed, setRefreshFailed] = (0, react.useState)(false);
2875
+ const onRefreshClick = () => {
2876
+ if (refreshing) return;
2877
+ setRefreshFailed(false);
2878
+ const result = props.onRefresh();
2879
+ if (result instanceof Promise) {
2880
+ setRefreshing(true);
2881
+ result.then((ok) => {
2882
+ setRefreshFailed(!ok);
2883
+ }).catch(() => {
2884
+ setRefreshFailed(true);
2885
+ }).finally(() => {
2886
+ setRefreshing(false);
2887
+ });
2888
+ }
2889
+ };
2890
+ const toggleStatusFilter = (status) => {
2891
+ const next = { ...props.filters };
2892
+ if (next.statusFilter === status) delete next.statusFilter;
2893
+ else next.statusFilter = status;
2894
+ props.onFiltersChange(next);
2895
+ };
2896
+ const statusBadgeTitle = (status) => props.filters.statusFilter === status ? BOARD_STRINGS.topbar.clearStatusFilterTitle : formatTemplate$2(BOARD_STRINGS.topbar.filterByStatusTitle, { label: BOARD_STRINGS.status[status] });
2665
2897
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2666
2898
  className: board_module_css_default["root"],
2667
2899
  "data-testid": "agent-sidecar-board",
@@ -2690,6 +2922,39 @@ window.__ModuleLoader__.load({
2690
2922
  "data-tone": vm.streamTone
2691
2923
  }), vm.streamLabel]
2692
2924
  }),
2925
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2926
+ type: "button",
2927
+ className: board_module_css_default["countBadge"],
2928
+ "aria-pressed": props.filters.statusFilter === "working",
2929
+ title: statusBadgeTitle("working"),
2930
+ onClick: () => {
2931
+ toggleStatusFilter("working");
2932
+ },
2933
+ "data-testid": "agent-sidecar-count-working",
2934
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2935
+ className: board_module_css_default["dot"],
2936
+ "data-tone": vm.workingCount > 0 ? "success" : "neutral"
2937
+ }), formatTemplate$2(BOARD_STRINGS.topbar.countWorking, { n: vm.workingCount })]
2938
+ }),
2939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2940
+ type: "button",
2941
+ className: board_module_css_default["countBadge"],
2942
+ "aria-pressed": props.filters.statusFilter === "waiting",
2943
+ title: statusBadgeTitle("waiting"),
2944
+ onClick: () => {
2945
+ toggleStatusFilter("waiting");
2946
+ },
2947
+ "data-testid": "agent-sidecar-count-waiting",
2948
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2949
+ className: board_module_css_default["dot"],
2950
+ "data-tone": vm.waitingCount > 0 ? "warn" : "neutral"
2951
+ }), formatTemplate$2(BOARD_STRINGS.topbar.countWaiting, { n: vm.waitingCount })]
2952
+ }),
2953
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2954
+ className: board_module_css_default["countTotal"],
2955
+ "data-testid": "agent-sidecar-count-total",
2956
+ children: formatTemplate$2(BOARD_STRINGS.topbar.countTotal, { n: vm.totalCount })
2957
+ }),
2693
2958
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: board_module_css_default["spacer"] }),
2694
2959
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2695
2960
  className: board_module_css_default["control"],
@@ -2722,11 +2987,25 @@ window.__ModuleLoader__.load({
2722
2987
  type: "button",
2723
2988
  className: board_module_css_default["refresh"],
2724
2989
  title: BOARD_STRINGS.topbar.refreshTitle,
2725
- onClick: props.onRefresh,
2726
- children: BOARD_STRINGS.topbar.refresh
2990
+ disabled: refreshing,
2991
+ onClick: onRefreshClick,
2992
+ children: refreshing ? BOARD_STRINGS.topbar.refreshing : BOARD_STRINGS.topbar.refresh
2727
2993
  })
2728
2994
  ]
2729
2995
  }),
2996
+ refreshFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2997
+ className: board_module_css_default["banner"],
2998
+ "data-tone": "warn",
2999
+ role: "status",
3000
+ children: [BOARD_STRINGS.topbar.refreshFailed, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3001
+ type: "button",
3002
+ className: board_module_css_default["bannerDismiss"],
3003
+ onClick: () => {
3004
+ setRefreshFailed(false);
3005
+ },
3006
+ children: BOARD_STRINGS.topbar.dismiss
3007
+ })]
3008
+ }),
2730
3009
  vm.banner !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2731
3010
  className: board_module_css_default["banner"],
2732
3011
  "data-tone": vm.banner.tone,
@@ -2752,7 +3031,7 @@ window.__ModuleLoader__.load({
2752
3031
  }
2753
3032
  //#endregion
2754
3033
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/board/project-view.module.css.mjs
2755
- const css$7 = ".i_F0aW_root{box-sizing:border-box;height:100%;min-height:320px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:12px;padding:16px 20px;display:flex;overflow-y:auto}.i_F0aW_topbar{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.i_F0aW_title{color:var(--dsw-alias-label-primary,#1f2328);font-size:15px;font-weight:650}.i_F0aW_summary{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px}.i_F0aW_loadingChip{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;padding:0 8px;font-size:11px;line-height:18px}.i_F0aW_banner{border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.i_F0aW_banner[data-tone=danger]{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border-color:var(--dsw-alias-state-error-secondary,#cf222e3d)}.i_F0aW_empty{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:160px;padding:24px;display:flex}.i_F0aW_emptyTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.i_F0aW_emptyHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.i_F0aW_group{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:10px;flex-direction:column;gap:8px;padding:10px 12px;display:flex}.i_F0aW_groupHead{flex-wrap:wrap;align-items:baseline;gap:8px;min-width:0;display:flex}.i_F0aW_groupName{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.i_F0aW_agentBadges{flex-wrap:wrap;align-items:center;gap:4px;display:inline-flex}.i_F0aW_agentBadge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:4px;padding:0 7px;font-size:11px;line-height:18px;display:inline-flex}.i_F0aW_crossBadge{color:var(--dsw-alias-static-white,#fff);background:var(--dsw-alias-brand-primary,#4d6bfe);white-space:nowrap;border-radius:999px;padding:0 7px;font-size:11px;line-height:18px}.i_F0aW_spacer{flex:1}.i_F0aW_groupMeta{color:var(--dsw-alias-label-tertiary,#6e7781);white-space:nowrap;flex:none;font-size:11px}.i_F0aW_lanes{flex-direction:column;gap:6px;display:flex}.i_F0aW_lane{flex-direction:column;gap:4px;display:flex}.i_F0aW_laneHead{color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:12px;font-weight:600;display:inline-flex}.i_F0aW_glyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.i_F0aW_laneSessions{flex-direction:column;gap:4px;display:flex}.i_F0aW_session{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-base,transparent);min-width:0;color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;border-radius:7px;align-items:center;gap:8px;padding:5px 8px;font-family:inherit;font-size:12px;display:flex}.i_F0aW_session:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.i_F0aW_session:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.i_F0aW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:5px;padding:0 7px;font-size:11px;line-height:18px;display:inline-flex}.i_F0aW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:7px;height:7px}.i_F0aW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.i_F0aW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.i_F0aW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.i_F0aW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.i_F0aW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.i_F0aW_attention{color:var(--dsw-alias-state-warn-primary,#9a6700);font-size:11px}.i_F0aW_attention[data-kind=gap]{color:var(--dsw-alias-state-error-primary,#cf222e)}.i_F0aW_sessionTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.i_F0aW_liveChip{color:var(--dsw-alias-state-success-primary,#1a7f37);border:1px solid var(--dsw-alias-state-success-primary,#1a7f3766);border-radius:999px;flex:none;padding:0 6px;font-size:10px;line-height:16px}.i_F0aW_sessionId{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.i_F0aW_sessionTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));flex:none;margin-left:auto;font-size:11px}";
3034
+ const css$7 = ".i_F0aW_root{box-sizing:border-box;height:100%;min-height:320px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:12px;padding:16px 20px;display:flex;overflow-y:auto}.i_F0aW_topbar{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.i_F0aW_title{color:var(--dsw-alias-label-primary,#1f2328);font-size:15px;font-weight:650}.i_F0aW_summary{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px}.i_F0aW_loadingChip{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;padding:0 8px;font-size:11px;line-height:18px}.i_F0aW_banner{border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.i_F0aW_banner[data-tone=danger]{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border-color:var(--dsw-alias-state-error-secondary,#cf222e3d)}.i_F0aW_empty{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:160px;padding:24px;display:flex}.i_F0aW_emptyTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.i_F0aW_emptyHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.i_F0aW_group{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:10px;flex-direction:column;gap:8px;padding:10px 12px;display:flex}.i_F0aW_groupHead{flex-wrap:wrap;align-items:baseline;gap:8px;min-width:0;display:flex}.i_F0aW_groupName{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;overflow:hidden}.i_F0aW_agentBadges{flex-wrap:wrap;align-items:center;gap:4px;display:inline-flex}.i_F0aW_agentBadge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-2,#0000000a);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:4px;padding:0 7px;font-size:11px;line-height:18px;display:inline-flex}.i_F0aW_crossBadge{color:var(--dsw-alias-static-white,#fff);background:var(--dsw-alias-brand-primary,#4d6bfe);white-space:nowrap;border-radius:999px;padding:0 7px;font-size:11px;line-height:18px}.i_F0aW_spacer{flex:1}.i_F0aW_groupMeta{color:var(--dsw-alias-label-tertiary,#6e7781);white-space:nowrap;flex:none;font-size:11px}.i_F0aW_lanes{flex-direction:column;gap:6px;display:flex}.i_F0aW_lane{flex-direction:column;gap:4px;display:flex}.i_F0aW_laneHead{color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:12px;font-weight:600;display:inline-flex}.i_F0aW_glyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.i_F0aW_laneSessions{flex-direction:column;gap:4px;display:flex}.i_F0aW_showMore{color:var(--dsw-alias-label-secondary,#57606a);border:1px dashed var(--dsw-alias-border-l2,#0000001f);cursor:pointer;background:0 0;border-radius:6px;align-self:flex-start;padding:2px 10px;font-family:inherit;font-size:12px}.i_F0aW_showMore:hover{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.i_F0aW_session{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-base,transparent);min-width:0;color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;border-radius:7px;align-items:center;gap:8px;padding:5px 8px;font-family:inherit;font-size:12px;display:flex}.i_F0aW_session:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.i_F0aW_session:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.i_F0aW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:5px;padding:0 7px;font-size:11px;line-height:18px;display:inline-flex}.i_F0aW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:7px;height:7px}.i_F0aW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.i_F0aW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.i_F0aW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.i_F0aW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.i_F0aW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.i_F0aW_attention{color:var(--dsw-alias-state-warn-primary,#9a6700);font-size:11px}.i_F0aW_attention[data-kind=gap]{color:var(--dsw-alias-state-error-primary,#cf222e)}.i_F0aW_sessionTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.i_F0aW_liveChip{color:var(--dsw-alias-state-success-primary,#1a7f37);border:1px solid var(--dsw-alias-state-success-primary,#1a7f3766);border-radius:999px;flex:none;padding:0 6px;font-size:10px;line-height:16px}.i_F0aW_sessionId{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.i_F0aW_sessionTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));flex:none;margin-left:auto;font-size:11px}";
2756
3035
  const tagId$7 = "@shendeguize/dsh-agent-sidecar/project-view.module.css";
2757
3036
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$7) + "]") === null) {
2758
3037
  const tag = document.createElement("style");
@@ -2788,6 +3067,7 @@ window.__ModuleLoader__.load({
2788
3067
  "sessionId": "i_F0aW_sessionId",
2789
3068
  "sessionTime": "i_F0aW_sessionTime",
2790
3069
  "sessionTitle": "i_F0aW_sessionTitle",
3070
+ "showMore": "i_F0aW_showMore",
2791
3071
  "spacer": "i_F0aW_spacer",
2792
3072
  "summary": "i_F0aW_summary",
2793
3073
  "title": "i_F0aW_title",
@@ -2795,6 +3075,21 @@ window.__ModuleLoader__.load({
2795
3075
  };
2796
3076
  //#endregion
2797
3077
  //#region src/client/board/project-view.tsx
3078
+ /**
3079
+ * Cross-agent project correlation view (design §4.e.2 / §5.1 M3, T5.5).
3080
+ *
3081
+ * Presentation-only and fully controlled: no data fetching, no api/sse
3082
+ * imports. The owner fetches `GET <prefix>/projects`, hands the wire
3083
+ * groups in as props (the wire shape IS the input view model — camelCase,
3084
+ * epoch-ms `lastActivityAt`), and receives session clicks back through
3085
+ * `onSelectSession` (pass-through to the detail view, same as Board).
3086
+ *
3087
+ * Render precedence when there is nothing to show: error > loading >
3088
+ * empty state. When groups ARE available, an error renders as a banner
3089
+ * above the (possibly stale) content instead of replacing it, and
3090
+ * `loading` shows as a quiet header chip — honest degradation without
3091
+ * blanking data the user already has.
3092
+ */
2798
3093
  function SessionRow$1(props) {
2799
3094
  const { session, onSelect } = props;
2800
3095
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -2842,22 +3137,43 @@ window.__ModuleLoader__.load({
2842
3137
  }
2843
3138
  function AgentLane(props) {
2844
3139
  const { lane, onSelect } = props;
3140
+ const [expanded, setExpanded] = (0, react.useState)(false);
3141
+ const { shown, hiddenCount } = sliceCardsForDisplay(lane.sessions, 10, expanded);
2845
3142
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2846
3143
  className: project_view_module_css_default["lane"],
2847
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2848
- className: project_view_module_css_default["laneHead"],
2849
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2850
- className: project_view_module_css_default["glyph"],
2851
- "aria-hidden": true,
2852
- children: lane.glyph
2853
- }), lane.agent]
2854
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2855
- className: project_view_module_css_default["laneSessions"],
2856
- children: lane.sessions.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow$1, {
2857
- session,
2858
- onSelect
2859
- }, `${session.agent}:${session.sessionId}`))
2860
- })]
3144
+ children: [
3145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3146
+ className: project_view_module_css_default["laneHead"],
3147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3148
+ className: project_view_module_css_default["glyph"],
3149
+ "aria-hidden": true,
3150
+ children: lane.glyph
3151
+ }), lane.agent]
3152
+ }),
3153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3154
+ className: project_view_module_css_default["laneSessions"],
3155
+ children: shown.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow$1, {
3156
+ session,
3157
+ onSelect
3158
+ }, `${session.agent}:${session.sessionId}`))
3159
+ }),
3160
+ hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3161
+ type: "button",
3162
+ className: project_view_module_css_default["showMore"],
3163
+ onClick: () => {
3164
+ setExpanded(true);
3165
+ },
3166
+ children: formatTemplate$2(PROJECT_VIEW_STRINGS.showAllSessions, { n: lane.sessions.length })
3167
+ }),
3168
+ expanded && lane.sessions.length > 10 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3169
+ type: "button",
3170
+ className: project_view_module_css_default["showMore"],
3171
+ onClick: () => {
3172
+ setExpanded(false);
3173
+ },
3174
+ children: formatTemplate$2(PROJECT_VIEW_STRINGS.showLessSessions, { n: 10 })
3175
+ })
3176
+ ]
2861
3177
  });
2862
3178
  }
2863
3179
  function ProjectSection(props) {
@@ -2998,7 +3314,6 @@ window.__ModuleLoader__.load({
2998
3314
  border: "none",
2999
3315
  borderRadius: 6,
3000
3316
  background: "transparent",
3001
- cursor: "pointer",
3002
3317
  font: "inherit",
3003
3318
  color: "var(--dsw-alias-label-secondary, #57606a)"
3004
3319
  };
@@ -3008,31 +3323,51 @@ window.__ModuleLoader__.load({
3008
3323
  lineHeight: "14px",
3009
3324
  whiteSpace: "nowrap"
3010
3325
  };
3011
- /** Connection dot + working-session counter (e.g. `▸2`) for the footer. */
3326
+ /**
3327
+ * Connection dot + working-session counter (e.g. `▸2`) for the footer.
3328
+ *
3329
+ * Without a wired `onOpen` the widget renders as an inert status `<span>`
3330
+ * (UX-06): button semantics + pointer cursor on a control that does
3331
+ * nothing would be a lie. The button form returns as soon as the
3332
+ * integration layer supplies the callback.
3333
+ */
3012
3334
  function SidecarWidget(props) {
3013
3335
  const title = widgetTitle(props.connection, props.workingCount);
3014
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3015
- type: "button",
3336
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3337
+ "aria-hidden": true,
3338
+ style: {
3339
+ width: 8,
3340
+ height: 8,
3341
+ borderRadius: "50%",
3342
+ flex: "none",
3343
+ background: DOT_COLORS$1[props.connection]
3344
+ }
3345
+ }), props.workingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3346
+ style: countStyle,
3347
+ "data-testid": "agent-sidecar-widget-count",
3348
+ children: `▸${props.workingCount}`
3349
+ })] });
3350
+ if (props.onOpen === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3016
3351
  style: rootStyle$1,
3017
3352
  title,
3018
3353
  "aria-label": title,
3354
+ role: "status",
3355
+ "data-testid": "agent-sidecar-widget",
3356
+ "data-connection": props.connection,
3357
+ children: body
3358
+ });
3359
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3360
+ type: "button",
3361
+ style: {
3362
+ ...rootStyle$1,
3363
+ cursor: "pointer"
3364
+ },
3365
+ title,
3366
+ "aria-label": title,
3019
3367
  onClick: props.onOpen,
3020
3368
  "data-testid": "agent-sidecar-widget",
3021
3369
  "data-connection": props.connection,
3022
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3023
- "aria-hidden": true,
3024
- style: {
3025
- width: 8,
3026
- height: 8,
3027
- borderRadius: "50%",
3028
- flex: "none",
3029
- background: DOT_COLORS$1[props.connection]
3030
- }
3031
- }), props.workingCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3032
- style: countStyle,
3033
- "data-testid": "agent-sidecar-widget-count",
3034
- children: `▸${props.workingCount}`
3035
- })]
3370
+ children: body
3036
3371
  });
3037
3372
  }
3038
3373
  //#endregion
@@ -3614,6 +3949,24 @@ window.__ModuleLoader__.load({
3614
3949
  if (delta < DAY_MS) return formatTemplate$1(DETAIL_STRINGS.time.hoursAgo, { n: Math.floor(delta / HOUR_MS) });
3615
3950
  return formatTemplate$1(DETAIL_STRINGS.time.daysAgo, { n: Math.floor(delta / DAY_MS) });
3616
3951
  }
3952
+ function pad2(n) {
3953
+ return n < 10 ? `0${n}` : String(n);
3954
+ }
3955
+ /**
3956
+ * Absolute short timestamp for the time column (UX-13): the same local
3957
+ * calendar day renders「HH:mm」, anything older (or a different day)
3958
+ * renders「MM-DD HH:mm」— so a column of same-age rows stays tellable
3959
+ * apart, unlike the coarse relative buckets. The format rule is
3960
+ * copy-implemented to match the board column (no cross-surface import);
3961
+ * the full ISO timestamp stays in the hover title.
3962
+ */
3963
+ function formatEventTime(thenMs, nowMs) {
3964
+ if (!Number.isFinite(thenMs) || !Number.isFinite(nowMs)) return "";
3965
+ const then = new Date(thenMs);
3966
+ const now = new Date(nowMs);
3967
+ const hhmm = `${pad2(then.getHours())}:${pad2(then.getMinutes())}`;
3968
+ return then.getFullYear() === now.getFullYear() && then.getMonth() === now.getMonth() && then.getDate() === now.getDate() ? hhmm : `${pad2(then.getMonth() + 1)}-${pad2(then.getDate())} ${hhmm}`;
3969
+ }
3617
3970
  const KIND_GLYPHS = {
3618
3971
  user: "▷",
3619
3972
  assistant: "◁",
@@ -3625,24 +3978,38 @@ window.__ModuleLoader__.load({
3625
3978
  error: "✕",
3626
3979
  other: "•"
3627
3980
  };
3981
+ /** One path segment → family token, or null when it names no family. */
3982
+ function segmentToken(segment) {
3983
+ if (segment === "user") return "user";
3984
+ if (segment === "assistant") return "assistant";
3985
+ if (segment === "thinking" || segment === "reasoning") return "thinking";
3986
+ if (segment === "tool_call" || segment === "tool-call" || segment === "toolcall") return "toolCall";
3987
+ if (segment === "tool_result" || segment === "tool-result" || segment === "toolresult") return "toolResult";
3988
+ if (segment.startsWith("turn_") || segment === "turn") return "turn";
3989
+ if (segment.startsWith("step_") || segment === "step") return "step";
3990
+ if (segment === "error") return "error";
3991
+ return null;
3992
+ }
3628
3993
  /**
3629
- * Map a raw event kind onto the glyph/label vocabulary. Covers both the
3630
- * sidecar normalized kinds — user/assistant/thinking/tool_call/tool_result
3631
- * plus the turn_ and step_ prefixes (sidecar/model.py) — and dsh native
3632
- * slash-path types (`message/user` style); everything else is honestly
3633
- * 'other'.
3994
+ * Map a raw event kind onto the glyph/label vocabulary. Covers the sidecar
3995
+ * normalized kinds — user/assistant/thinking/tool_call/tool_result plus
3996
+ * the turn_ and step_ prefixes (sidecar/model.py) — and dsh native
3997
+ * slash-path types in BOTH orders: `message/user` style (family last) and
3998
+ * the observed `user/message` / `assistant/chunk` / `turn/start` style
3999
+ * (family first; live-data fact, UX-03 — without it the conversation
4000
+ * filter would misfile real dsh user messages as protocol noise). The
4001
+ * last segment stays authoritative; the first is only a fallback.
4002
+ * Everything else is honestly 'other'.
3634
4003
  */
3635
4004
  function classifyKind(kind) {
3636
4005
  const k = kind.trim().toLowerCase();
3637
- const last = k.includes("/") ? k.split("/").pop() ?? k : k;
3638
- if (last === "user") return "user";
3639
- if (last === "assistant") return "assistant";
3640
- if (last === "thinking" || last === "reasoning") return "thinking";
3641
- if (last === "tool_call" || last === "tool-call" || last === "toolcall") return "toolCall";
3642
- if (last === "tool_result" || last === "tool-result" || last === "toolresult") return "toolResult";
3643
- if (last.startsWith("turn_") || last === "turn") return "turn";
3644
- if (last.startsWith("step_") || last === "step") return "step";
3645
- if (last === "error") return "error";
4006
+ const segments = k.split("/");
4007
+ const last = segmentToken(segments[segments.length - 1] ?? k);
4008
+ if (last !== null) return last;
4009
+ if (segments.length > 1) {
4010
+ const first = segmentToken(segments[0] ?? "");
4011
+ if (first !== null) return first;
4012
+ }
3646
4013
  return "other";
3647
4014
  }
3648
4015
  /** Glyph for a kind token. */
@@ -3906,13 +4273,14 @@ window.__ModuleLoader__.load({
3906
4273
  return "";
3907
4274
  }
3908
4275
  }
3909
- function eventHoverTitle(entry) {
4276
+ function eventHoverTitle(entry, nowMs) {
3910
4277
  const parts = [
3911
4278
  isoOrEmpty(entry.ts),
3912
4279
  entry.kindRaw,
3913
4280
  entry.origin
3914
4281
  ];
3915
4282
  if (entry.seq !== null) parts.push(formatTemplate$1(DETAIL_STRINGS.timeline.seq, { n: entry.seq }));
4283
+ parts.push(formatRelativeTime(entry.ts, nowMs));
3916
4284
  return parts.filter((p) => p !== "").join(" · ");
3917
4285
  }
3918
4286
  /** Gap marker text: 「缺口:可能有 N 条事件未捕获(256 队列上限或未持久化)」. */
@@ -3949,13 +4317,108 @@ window.__ModuleLoader__.load({
3949
4317
  type: "event",
3950
4318
  key: entry.key,
3951
4319
  entry,
3952
- relativeTime: formatRelativeTime(entry.ts, nowMs),
3953
- hoverTitle: eventHoverTitle(entry),
4320
+ timeLabel: formatEventTime(entry.ts, nowMs),
4321
+ hoverTitle: eventHoverTitle(entry, nowMs),
3954
4322
  isNew: newKeys.has(entry.key)
3955
4323
  });
3956
4324
  }
3957
4325
  return rows;
3958
4326
  }
4327
+ /** The kinds that count as conversation (review UX-03 vocabulary). */
4328
+ const CONVERSATION_KINDS = /* @__PURE__ */ new Set([
4329
+ "user",
4330
+ "assistant",
4331
+ "error"
4332
+ ]);
4333
+ /**
4334
+ * Kind filter over derived rows: 'conversation' keeps user/assistant/error
4335
+ * events only; 'all' passes everything through. Gap markers ALWAYS stay —
4336
+ * honesty rows are not noise and hiding them could fake a clean timeline.
4337
+ * Runs BEFORE {@link aggregateChunkRows} (gaps are detected on the full
4338
+ * entry list upstream, so filtering can never fabricate a gap).
4339
+ */
4340
+ function filterTimelineRows(rows, mode) {
4341
+ if (mode === "all") return {
4342
+ rows: [...rows],
4343
+ hiddenCount: 0
4344
+ };
4345
+ const out = [];
4346
+ let hiddenCount = 0;
4347
+ for (const row of rows) {
4348
+ if (row.type === "event" && !CONVERSATION_KINDS.has(row.entry.kind)) {
4349
+ hiddenCount += 1;
4350
+ continue;
4351
+ }
4352
+ out.push(row);
4353
+ }
4354
+ return {
4355
+ rows: out,
4356
+ hiddenCount
4357
+ };
4358
+ }
4359
+ /**
4360
+ * True for a protocol streaming-chunk entry: an empty one-line summary and
4361
+ * a chunk-flavored kind (`assistant/chunk` style, matched on the last
4362
+ * slash segment). These are the rows that drowned real conversation in
4363
+ * the walkthrough (18 of 38 rows, review UX-03).
4364
+ */
4365
+ function isStreamChunkEntry(entry) {
4366
+ if (entry.summary !== "") return false;
4367
+ const k = entry.kindRaw.trim().toLowerCase();
4368
+ const last = k.includes("/") ? k.split("/").pop() ?? k : k;
4369
+ return last === "chunk" || last.endsWith("_chunk") || last.endsWith("-chunk");
4370
+ }
4371
+ /**
4372
+ * Collapse each maximal run of ≥2 adjacent same-kind streaming-chunk rows
4373
+ * into one 'chunks' row carrying the members verbatim (lossless — the view
4374
+ * offers 展开). Gap markers and any non-chunk row break a run, so the
4375
+ * aggregation can never paper over a seq discontinuity. Single chunks stay
4376
+ * as plain rows (a 1-run header would add noise, not remove it).
4377
+ */
4378
+ function aggregateChunkRows(rows) {
4379
+ const out = [];
4380
+ let run = [];
4381
+ const flush = () => {
4382
+ if (run.length >= 2) {
4383
+ const first = run[0];
4384
+ const last = run[run.length - 1];
4385
+ out.push({
4386
+ type: "chunks",
4387
+ key: `chunks:${first.key}`,
4388
+ kindRaw: first.entry.kindRaw,
4389
+ count: run.length,
4390
+ label: formatTemplate$1(DETAIL_STRINGS.timeline.chunkRun, { n: run.length }),
4391
+ timeLabel: last.timeLabel,
4392
+ hoverTitle: `${first.entry.kindRaw} ×${run.length}`,
4393
+ isNew: run.some((r) => r.isNew),
4394
+ members: run
4395
+ });
4396
+ } else out.push(...run);
4397
+ run = [];
4398
+ };
4399
+ for (const row of rows) {
4400
+ if (row.type === "event" && isStreamChunkEntry(row.entry)) {
4401
+ if (!(run.length === 0 || run[run.length - 1].entry.kindRaw === row.entry.kindRaw)) flush();
4402
+ run.push(row);
4403
+ continue;
4404
+ }
4405
+ flush();
4406
+ out.push(row);
4407
+ }
4408
+ flush();
4409
+ return out;
4410
+ }
4411
+ /**
4412
+ * Whether the view should pin its scroll position to the newest rows:
4413
+ * on the first non-empty render (initial landing — understanding the
4414
+ * current context needs the latest events, review UX-04), and on every
4415
+ * append while listen mode is on. Loading older history must never yank
4416
+ * the viewport (`positioned` stays true after the first landing).
4417
+ */
4418
+ function shouldStickToLatest(input) {
4419
+ if (input.entryCount === 0) return false;
4420
+ return !input.positioned || input.listening;
4421
+ }
3959
4422
  /**
3960
4423
  * Keep only the newest `max` rows (the tail — listen mode appends there).
3961
4424
  * The data stays in the VM; this is purely a render bound the component
@@ -4091,7 +4554,7 @@ window.__ModuleLoader__.load({
4091
4554
  }
4092
4555
  //#endregion
4093
4556
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/detail/detail.module.css.mjs
4094
- const css$5 = ".V1TWBW_root{box-sizing:border-box;height:100%;min-height:420px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:10px;padding:16px 20px;display:flex}.V1TWBW_header{flex-direction:column;gap:6px;display:flex}.V1TWBW_headerTop{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.V1TWBW_closeButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:2px 10px;font-family:inherit;font-size:12px}.V1TWBW_closeButton:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.V1TWBW_agent{color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:13px;font-weight:650;display:inline-flex}.V1TWBW_agentGlyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.V1TWBW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:5px;padding:0 8px;font-size:12px;line-height:20px;display:inline-flex}.V1TWBW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:8px;height:8px}.V1TWBW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.V1TWBW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.V1TWBW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.V1TWBW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.V1TWBW_spacer{flex:1}.V1TWBW_listenButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:999px;padding:2px 12px;font-family:inherit;font-size:12px}.V1TWBW_listenButton:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f)}.V1TWBW_listenButton[data-active]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:var(--dsw-alias-brand-tertiary,#4d6bfe14)}.V1TWBW_title{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:600;overflow:hidden}.V1TWBW_meta{align-items:baseline;gap:10px;min-width:0;display:flex}.V1TWBW_project{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.V1TWBW_sessionId{color:var(--dsw-alias-label-tertiary,#6e7781);text-overflow:ellipsis;white-space:nowrap;flex:none;max-width:40%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.V1TWBW_metaRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.V1TWBW_disclaimer{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));font-size:11px}.V1TWBW_sourceList{align-items:center;gap:4px;display:inline-flex}.V1TWBW_sourceBadge{border:1px solid var(--dsw-alias-border-l1,#00000014);color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-1,#00000008);white-space:nowrap;border-radius:999px;padding:0 6px;font-size:11px;line-height:16px}.V1TWBW_sourceBadge[data-tone=success]{color:var(--dsw-alias-state-success-primary,#1a7f37);border-color:var(--dsw-alias-state-success-secondary,#1a7f373d)}.V1TWBW_sourceBadge[data-tone=muted]{color:var(--dsw-alias-label-dimmed,#8c959f)}.V1TWBW_banner{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border:1px solid var(--dsw-alias-state-warn-secondary,#9a67003d);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.V1TWBW_bodyState{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:160px;padding:24px;display:flex}.V1TWBW_bodyStateTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.V1TWBW_bodyState[data-kind=error] .V1TWBW_bodyStateTitle{color:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_bodyStateHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.V1TWBW_pager{justify-content:center;display:flex}.V1TWBW_loadMoreButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:3px 14px;font-family:inherit;font-size:12px}.V1TWBW_loadMoreButton:hover:enabled{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.V1TWBW_loadMoreButton:disabled{cursor:default;opacity:.6}.V1TWBW_pagerNote{color:var(--dsw-alias-label-tertiary,#6e7781);font-size:11px}.V1TWBW_hiddenNotice{color:var(--dsw-alias-label-tertiary,#6e7781);justify-content:center;align-items:center;gap:8px;font-size:11px;display:flex}.V1TWBW_showAllButton{color:var(--dsw-alias-brand-primary,#4d6bfe);cursor:pointer;background:0 0;border:none;padding:0;font-family:inherit;font-size:11px}.V1TWBW_timeline{flex-direction:column;flex:1;gap:6px;margin:0;padding:0 2px 8px 0;list-style:none;display:flex;overflow-y:auto}.V1TWBW_event{border:1px solid var(--dsw-alias-border-l1,#0000000f);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:8px;flex-direction:column;gap:4px;padding:6px 10px;display:flex}.V1TWBW_event[data-new]{border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:var(--dsw-alias-brand-tertiary,#4d6bfe0f)}.V1TWBW_eventHead{align-items:center;gap:6px;min-width:0;display:flex}.V1TWBW_eventGlyph{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:12px}.V1TWBW_event[data-kind=user] .V1TWBW_eventGlyph,.V1TWBW_event[data-kind=assistant] .V1TWBW_eventGlyph{color:var(--dsw-alias-brand-primary,#4d6bfe)}.V1TWBW_event[data-kind=error] .V1TWBW_eventGlyph{color:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_eventLabel{color:var(--dsw-alias-label-primary,#1f2328);white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:600;overflow:hidden}.V1TWBW_eventSeq{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px}.V1TWBW_eventNew{color:var(--dsw-alias-brand-primary,#4d6bfe);border:1px solid var(--dsw-alias-brand-primary,#4d6bfe);border-radius:999px;flex:none;padding:0 5px;font-size:10px;line-height:14px}.V1TWBW_eventSpacer{flex:1}.V1TWBW_eventTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));flex:none;font-size:11px}.V1TWBW_eventSummary{color:var(--dsw-alias-label-secondary,#57606a);overflow-wrap:anywhere;font-size:12px;line-height:18px}.V1TWBW_expandButton{color:var(--dsw-alias-brand-primary,#4d6bfe);cursor:pointer;background:0 0;border:none;align-self:flex-start;padding:0;font-family:inherit;font-size:11px}.V1TWBW_eventBody{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:6px;max-height:320px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;overflow-y:auto}.V1TWBW_gap{text-align:center;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a67000f);border:1px dashed var(--dsw-alias-state-warn-secondary,#9a670052);border-radius:6px;padding:4px 10px;font-size:11px;line-height:16px}";
4557
+ const css$5 = ".V1TWBW_root{box-sizing:border-box;height:100%;min-height:420px;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-base,transparent);flex-direction:column;gap:10px;padding:16px 20px;display:flex}.V1TWBW_header{flex-direction:column;gap:6px;display:flex}.V1TWBW_headerTop{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.V1TWBW_closeButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:2px 10px;font-family:inherit;font-size:12px}.V1TWBW_closeButton:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.V1TWBW_agent{color:var(--dsw-alias-label-primary,#1f2328);align-items:center;gap:5px;font-size:13px;font-weight:650;display:inline-flex}.V1TWBW_agentGlyph{color:var(--dsw-alias-brand-primary,#4d6bfe);flex:none}.V1TWBW_badge{border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000008);color:var(--dsw-alias-label-secondary,#57606a);white-space:nowrap;border-radius:999px;align-items:center;gap:5px;padding:0 8px;font-size:12px;line-height:20px;display:inline-flex}.V1TWBW_dot{background:var(--dsw-alias-label-tertiary,#6e7781);border-radius:50%;flex:none;width:8px;height:8px}.V1TWBW_dot[data-tone=success]{background:var(--dsw-alias-state-success-primary,#1a7f37)}.V1TWBW_dot[data-tone=warn]{background:var(--dsw-alias-state-warn-primary,#9a6700)}.V1TWBW_dot[data-tone=danger]{background:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_dot[data-tone=neutral]{background:var(--dsw-alias-label-tertiary,#6e7781)}.V1TWBW_dot[data-tone=muted]{background:var(--dsw-alias-label-dimmed,#8c959f)}.V1TWBW_spacer{flex:1}.V1TWBW_listenButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:999px;padding:2px 12px;font-family:inherit;font-size:12px}.V1TWBW_listenButton:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f)}.V1TWBW_listenButton[data-active]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 8%, transparent)}.V1TWBW_refreshButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:999px;padding:2px 12px;font-family:inherit;font-size:12px}.V1TWBW_refreshButton:hover:enabled{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f)}.V1TWBW_refreshButton:disabled{cursor:default;opacity:.6}.V1TWBW_title{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:600;overflow:hidden}.V1TWBW_meta{align-items:baseline;gap:10px;min-width:0;display:flex}.V1TWBW_project{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.V1TWBW_sessionId{color:var(--dsw-alias-label-tertiary,#6e7781);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;flex:none;max-width:40%;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.V1TWBW_sessionId:hover{color:var(--dsw-alias-label-primary,#1f2328);text-decoration:underline}.V1TWBW_copiedBubble{color:var(--dsw-alias-state-success-primary,#1a7f37);border:1px solid var(--dsw-alias-state-success-secondary,#1a7f373d);border-radius:999px;flex:none;padding:0 6px;font-size:10px;line-height:16px}.V1TWBW_metaRow{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.V1TWBW_disclaimer{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));font-size:11px}.V1TWBW_sourceList{align-items:center;gap:4px;display:inline-flex}.V1TWBW_sourceBadge{border:1px solid var(--dsw-alias-border-l1,#00000014);color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-1,#00000008);white-space:nowrap;border-radius:999px;padding:0 6px;font-size:11px;line-height:16px}.V1TWBW_sourceBadge[data-tone=success]{color:var(--dsw-alias-state-success-primary,#1a7f37);border-color:var(--dsw-alias-state-success-secondary,#1a7f373d)}.V1TWBW_sourceBadge[data-tone=muted]{color:var(--dsw-alias-label-dimmed,#8c959f)}.V1TWBW_banner{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border:1px solid var(--dsw-alias-state-warn-secondary,#9a67003d);border-radius:8px;padding:6px 10px;font-size:12px;line-height:18px}.V1TWBW_bodyState{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:6px;min-height:160px;padding:24px;display:flex}.V1TWBW_bodyStateTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:14px;font-weight:600}.V1TWBW_bodyState[data-kind=error] .V1TWBW_bodyStateTitle{color:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_bodyStateHint{max-width:420px;color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:20px}.V1TWBW_filterRow{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.V1TWBW_filterChip{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:999px;padding:0 10px;font-family:inherit;font-size:11px;line-height:18px}.V1TWBW_filterChip:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f)}.V1TWBW_filterChip[data-active]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 8%, transparent)}.V1TWBW_filterHiddenNote{color:var(--dsw-alias-label-tertiary,#6e7781);font-size:11px}.V1TWBW_pager{justify-content:center;display:flex}.V1TWBW_loadMoreButton{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;padding:3px 14px;font-family:inherit;font-size:12px}.V1TWBW_loadMoreButton:hover:enabled{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.V1TWBW_loadMoreButton:disabled{cursor:default;opacity:.6}.V1TWBW_pagerNote{color:var(--dsw-alias-label-tertiary,#6e7781);font-size:11px}.V1TWBW_hiddenNotice{color:var(--dsw-alias-label-tertiary,#6e7781);justify-content:center;align-items:center;gap:8px;font-size:11px;display:flex}.V1TWBW_showAllButton{color:var(--dsw-alias-brand-primary,#4d6bfe);cursor:pointer;background:0 0;border:none;padding:0;font-family:inherit;font-size:11px}.V1TWBW_timeline{flex-direction:column;flex:1;gap:6px;margin:0;padding:0 2px 8px 0;list-style:none;display:flex;overflow-y:auto}.V1TWBW_event{border:1px solid var(--dsw-alias-border-l1,#0000000f);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:8px;flex-direction:column;gap:4px;padding:6px 10px;display:flex}.V1TWBW_event[data-new]{border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 6%, transparent)}.V1TWBW_chunkRun{color:var(--dsw-alias-label-tertiary,#6e7781);background:var(--dsw-alias-bg-layer-1,#00000005);border:1px dashed var(--dsw-alias-border-l2,#0000001f);border-radius:6px;align-items:center;gap:8px;padding:4px 10px;font-size:11px;line-height:16px;display:flex}.V1TWBW_chunkRun[data-new]{border-color:var(--dsw-alias-brand-primary,#4d6bfe)}.V1TWBW_chunkRunLabel{flex:none}.V1TWBW_eventHead{align-items:center;gap:6px;min-width:0;display:flex}.V1TWBW_eventGlyph{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:12px}.V1TWBW_event[data-kind=user] .V1TWBW_eventGlyph,.V1TWBW_event[data-kind=assistant] .V1TWBW_eventGlyph{color:var(--dsw-alias-brand-primary,#4d6bfe)}.V1TWBW_event[data-kind=error] .V1TWBW_eventGlyph{color:var(--dsw-alias-state-error-primary,#cf222e)}.V1TWBW_eventLabel{color:var(--dsw-alias-label-primary,#1f2328);white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:600;overflow:hidden}.V1TWBW_eventSeq{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px}.V1TWBW_eventNew{color:var(--dsw-alias-brand-primary,#4d6bfe);border:1px solid var(--dsw-alias-brand-primary,#4d6bfe);border-radius:999px;flex:none;padding:0 5px;font-size:10px;line-height:14px}.V1TWBW_eventSpacer{flex:1}.V1TWBW_eventTime{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary,#6e7781));flex:none;font-size:11px}.V1TWBW_eventSummary{color:var(--dsw-alias-label-secondary,#57606a);overflow-wrap:anywhere;font-size:12px;line-height:18px}.V1TWBW_expandButton{color:var(--dsw-alias-brand-primary,#4d6bfe);cursor:pointer;background:0 0;border:none;align-self:flex-start;padding:0;font-family:inherit;font-size:11px}.V1TWBW_eventBody{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:pre-wrap;overflow-wrap:anywhere;border-radius:6px;max-height:320px;margin:0;padding:8px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;overflow-y:auto}.V1TWBW_gap{text-align:center;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a67000f);border:1px dashed var(--dsw-alias-state-warn-secondary,#9a670052);border-radius:6px;padding:4px 10px;font-size:11px;line-height:16px}";
4095
4558
  const tagId$5 = "@shendeguize/dsh-agent-sidecar/detail.module.css";
4096
4559
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
4097
4560
  const tag = document.createElement("style");
@@ -4108,7 +4571,10 @@ window.__ModuleLoader__.load({
4108
4571
  "bodyState": "V1TWBW_bodyState",
4109
4572
  "bodyStateHint": "V1TWBW_bodyStateHint",
4110
4573
  "bodyStateTitle": "V1TWBW_bodyStateTitle",
4574
+ "chunkRun": "V1TWBW_chunkRun",
4575
+ "chunkRunLabel": "V1TWBW_chunkRunLabel",
4111
4576
  "closeButton": "V1TWBW_closeButton",
4577
+ "copiedBubble": "V1TWBW_copiedBubble",
4112
4578
  "disclaimer": "V1TWBW_disclaimer",
4113
4579
  "dot": "V1TWBW_dot",
4114
4580
  "event": "V1TWBW_event",
@@ -4122,6 +4588,9 @@ window.__ModuleLoader__.load({
4122
4588
  "eventSummary": "V1TWBW_eventSummary",
4123
4589
  "eventTime": "V1TWBW_eventTime",
4124
4590
  "expandButton": "V1TWBW_expandButton",
4591
+ "filterChip": "V1TWBW_filterChip",
4592
+ "filterHiddenNote": "V1TWBW_filterHiddenNote",
4593
+ "filterRow": "V1TWBW_filterRow",
4125
4594
  "gap": "V1TWBW_gap",
4126
4595
  "header": "V1TWBW_header",
4127
4596
  "headerTop": "V1TWBW_headerTop",
@@ -4133,6 +4602,7 @@ window.__ModuleLoader__.load({
4133
4602
  "pager": "V1TWBW_pager",
4134
4603
  "pagerNote": "V1TWBW_pagerNote",
4135
4604
  "project": "V1TWBW_project",
4605
+ "refreshButton": "V1TWBW_refreshButton",
4136
4606
  "root": "V1TWBW_root",
4137
4607
  "sessionId": "V1TWBW_sessionId",
4138
4608
  "showAllButton": "V1TWBW_showAllButton",
@@ -4151,13 +4621,19 @@ window.__ModuleLoader__.load({
4151
4621
  * imports. The integration layer (S7) owns transport and accumulation —
4152
4622
  * it feeds the {@link TimelineVM} built via logic.ts (`applyTimelinePage`
4153
4623
  * for history pages, `applyListenPage` for listen-mode refetches) and
4154
- * handles `onLoadMore` / `onToggleListen`.
4624
+ * handles `onLoadMore` / `onToggleListen` / `onRefresh`.
4625
+ *
4626
+ * Row pipeline (all pure, logic.ts): buildTimelineRows (gaps on the FULL
4627
+ * entry list) → filterTimelineRows (UX-03 kind filter, conversation-first
4628
+ * by default with an honest hidden count) → aggregateChunkRows (UX-03
4629
+ * adjacent empty streaming chunks collapse into one expandable run) →
4630
+ * limitTimelineRows (render cap with a 全部显示 escape hatch).
4155
4631
  *
4156
4632
  * Long-list posture (task report): no full virtualization — history only
4157
4633
  * grows page-by-page on explicit 加载更多, and rendering is additionally
4158
- * capped at {@link DEFAULT_MAX_RENDER_ROWS} newest rows behind a collapse
4159
- * notice with a 全部显示 escape hatch. View-local concerns (expanded
4160
- * bodies, the lift-cap flag, auto-scroll) are component state; everything
4634
+ * capped at {@link DEFAULT_MAX_RENDER_ROWS} newest rows. View-local
4635
+ * concerns (expanded bodies/runs, filter mode, the lift-cap flag, the
4636
+ * UX-04 initial landing, copy feedback) are component state; everything
4161
4637
  * else comes through props.
4162
4638
  */
4163
4639
  function EventRow(props) {
@@ -4193,7 +4669,7 @@ window.__ModuleLoader__.load({
4193
4669
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default["eventSpacer"] }),
4194
4670
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4195
4671
  className: detail_module_css_default["eventTime"],
4196
- children: row.relativeTime
4672
+ children: row.timeLabel
4197
4673
  })
4198
4674
  ]
4199
4675
  }),
@@ -4214,12 +4690,48 @@ window.__ModuleLoader__.load({
4214
4690
  ]
4215
4691
  });
4216
4692
  }
4693
+ /** Collapsed run of adjacent streaming chunks (UX-03); expandable lossless. */
4694
+ function ChunkRunRow(props) {
4695
+ const { row } = props;
4696
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
4697
+ className: detail_module_css_default["chunkRun"],
4698
+ "data-new": row.isNew || void 0,
4699
+ title: row.hoverTitle,
4700
+ "data-testid": "agent-sidecar-detail-chunks",
4701
+ children: [
4702
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4703
+ className: detail_module_css_default["chunkRunLabel"],
4704
+ children: row.label
4705
+ }),
4706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4707
+ type: "button",
4708
+ className: detail_module_css_default["expandButton"],
4709
+ onClick: () => props.onToggleRun(row.key),
4710
+ children: props.expanded ? DETAIL_STRINGS.timeline.collapse : DETAIL_STRINGS.timeline.expand
4711
+ }),
4712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default["eventSpacer"] }),
4713
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4714
+ className: detail_module_css_default["eventTime"],
4715
+ children: row.timeLabel
4716
+ })
4717
+ ]
4718
+ }), props.expanded && row.members.map((member) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventRow, {
4719
+ row: member,
4720
+ expanded: props.expandedKeys.has(member.key),
4721
+ onToggleExpand: props.onToggleExpand
4722
+ }, member.key))] });
4723
+ }
4217
4724
  /** The session-detail view. Pure render of the logic.ts pipelines over props. */
4218
4725
  function SessionDetail(props) {
4219
4726
  const nowMs = props.nowMs ?? Date.now();
4220
4727
  const [expandedKeys, setExpandedKeys] = (0, react.useState)(/* @__PURE__ */ new Set());
4728
+ const [expandedRuns, setExpandedRuns] = (0, react.useState)(/* @__PURE__ */ new Set());
4729
+ const [filterMode, setFilterMode] = (0, react.useState)("conversation");
4221
4730
  const [renderAll, setRenderAll] = (0, react.useState)(false);
4731
+ const [copied, setCopied] = (0, react.useState)(false);
4222
4732
  const listRef = (0, react.useRef)(null);
4733
+ const positionedRef = (0, react.useRef)(false);
4734
+ const copyTimerRef = (0, react.useRef)(null);
4223
4735
  const status = deriveDetailStatus(props.header.status);
4224
4736
  const sourceBadges = deriveSourceBadges(props.timeline.sources);
4225
4737
  const bodyState = deriveDetailBodyState({
@@ -4227,19 +4739,30 @@ window.__ModuleLoader__.load({
4227
4739
  error: props.error,
4228
4740
  entryCount: props.timeline.entries.length
4229
4741
  });
4230
- const allRows = buildTimelineRows(props.timeline, nowMs);
4742
+ const filtered = filterTimelineRows(buildTimelineRows(props.timeline, nowMs), filterMode);
4743
+ const aggregated = aggregateChunkRows(filtered.rows);
4231
4744
  const limited = renderAll ? {
4232
- rows: allRows,
4745
+ rows: aggregated,
4233
4746
  hiddenCount: 0,
4234
4747
  notice: null
4235
- } : limitTimelineRows(allRows, props.maxRenderRows ?? 400);
4748
+ } : limitTimelineRows(aggregated, props.maxRenderRows ?? 400);
4236
4749
  const entryCount = props.timeline.entries.length;
4237
4750
  const listening = props.listening;
4238
4751
  (0, react.useEffect)(() => {
4239
- if (!listening) return;
4240
4752
  const list = listRef.current;
4241
- if (list !== null) list.scrollTop = list.scrollHeight;
4753
+ if (list === null) return;
4754
+ if (shouldStickToLatest({
4755
+ entryCount,
4756
+ positioned: positionedRef.current,
4757
+ listening
4758
+ })) {
4759
+ list.scrollTop = list.scrollHeight;
4760
+ positionedRef.current = true;
4761
+ }
4242
4762
  }, [listening, entryCount]);
4763
+ (0, react.useEffect)(() => () => {
4764
+ if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current);
4765
+ }, []);
4243
4766
  const toggleExpand = (key) => {
4244
4767
  setExpandedKeys((prev) => {
4245
4768
  const next = new Set(prev);
@@ -4248,6 +4771,25 @@ window.__ModuleLoader__.load({
4248
4771
  return next;
4249
4772
  });
4250
4773
  };
4774
+ const toggleRun = (key) => {
4775
+ setExpandedRuns((prev) => {
4776
+ const next = new Set(prev);
4777
+ if (next.has(key)) next.delete(key);
4778
+ else next.add(key);
4779
+ return next;
4780
+ });
4781
+ };
4782
+ const copySessionId = () => {
4783
+ const clipboard = typeof navigator === "undefined" ? void 0 : navigator.clipboard;
4784
+ if (clipboard === void 0) return;
4785
+ clipboard.writeText(props.sessionId).then(() => {
4786
+ setCopied(true);
4787
+ if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current);
4788
+ copyTimerRef.current = setTimeout(() => {
4789
+ setCopied(false);
4790
+ }, 2e3);
4791
+ }, () => {});
4792
+ };
4251
4793
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4252
4794
  className: detail_module_css_default["root"],
4253
4795
  "data-testid": "agent-sidecar-detail",
@@ -4282,6 +4824,15 @@ window.__ModuleLoader__.load({
4282
4824
  }), status.label]
4283
4825
  }),
4284
4826
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default["spacer"] }),
4827
+ props.onRefresh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4828
+ type: "button",
4829
+ className: detail_module_css_default["refreshButton"],
4830
+ disabled: props.refreshing === true,
4831
+ title: DETAIL_STRINGS.header.refreshHint,
4832
+ onClick: props.onRefresh,
4833
+ "data-testid": "agent-sidecar-detail-refresh",
4834
+ children: props.refreshing === true ? DETAIL_STRINGS.header.refreshing : DETAIL_STRINGS.header.refresh
4835
+ }),
4285
4836
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4286
4837
  type: "button",
4287
4838
  className: detail_module_css_default["listenButton"],
@@ -4300,15 +4851,26 @@ window.__ModuleLoader__.load({
4300
4851
  }),
4301
4852
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4302
4853
  className: detail_module_css_default["meta"],
4303
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4304
- className: detail_module_css_default["project"],
4305
- title: props.header.project,
4306
- children: props.header.project.trim() === "" ? DETAIL_STRINGS.header.unknownProject : props.header.project
4307
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4308
- className: detail_module_css_default["sessionId"],
4309
- title: props.sessionId,
4310
- children: props.sessionId
4311
- })]
4854
+ children: [
4855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4856
+ className: detail_module_css_default["project"],
4857
+ title: props.header.project,
4858
+ children: props.header.project.trim() === "" ? DETAIL_STRINGS.header.unknownProject : props.header.project
4859
+ }),
4860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4861
+ type: "button",
4862
+ className: detail_module_css_default["sessionId"],
4863
+ title: `${props.sessionId} · ${DETAIL_STRINGS.header.copyIdTitle}`,
4864
+ onClick: copySessionId,
4865
+ "data-testid": "agent-sidecar-detail-copy-id",
4866
+ children: props.sessionId
4867
+ }),
4868
+ copied && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4869
+ className: detail_module_css_default["copiedBubble"],
4870
+ role: "status",
4871
+ children: DETAIL_STRINGS.header.copied
4872
+ })
4873
+ ]
4312
4874
  }),
4313
4875
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4314
4876
  className: detail_module_css_default["metaRow"],
@@ -4343,6 +4905,23 @@ window.__ModuleLoader__.load({
4343
4905
  children: bodyState.hint
4344
4906
  })]
4345
4907
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4909
+ className: detail_module_css_default["filterRow"],
4910
+ "data-testid": "agent-sidecar-detail-filter",
4911
+ children: [["conversation", "all"].map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4912
+ type: "button",
4913
+ className: detail_module_css_default["filterChip"],
4914
+ "data-active": filterMode === mode || void 0,
4915
+ "aria-pressed": filterMode === mode,
4916
+ onClick: () => {
4917
+ setFilterMode(mode);
4918
+ },
4919
+ children: DETAIL_STRINGS.filter[mode]
4920
+ }, mode)), filtered.hiddenCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4921
+ className: detail_module_css_default["filterHiddenNote"],
4922
+ children: formatTemplate$1(DETAIL_STRINGS.filter.hiddenNotice, { n: filtered.hiddenCount })
4923
+ })]
4924
+ }),
4346
4925
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4347
4926
  className: detail_module_css_default["pager"],
4348
4927
  children: props.hasMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4373,6 +4952,12 @@ window.__ModuleLoader__.load({
4373
4952
  role: "note",
4374
4953
  "data-testid": "agent-sidecar-detail-gap",
4375
4954
  children: row.label
4955
+ }, row.key) : row.type === "chunks" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChunkRunRow, {
4956
+ row,
4957
+ expanded: expandedRuns.has(row.key),
4958
+ onToggleRun: toggleRun,
4959
+ expandedKeys,
4960
+ onToggleExpand: toggleExpand
4376
4961
  }, row.key) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventRow, {
4377
4962
  row,
4378
4963
  expanded: expandedKeys.has(row.key),
@@ -4385,7 +4970,7 @@ window.__ModuleLoader__.load({
4385
4970
  }
4386
4971
  //#endregion
4387
4972
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/dsh-tools/dsh-tools.module.css.mjs
4388
- const css$4 = ".f29_TW_panel{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;gap:8px;display:flex}.f29_TW_panelHead{align-items:baseline;gap:8px;min-width:0;display:flex}.f29_TW_panelTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;font-weight:600}.f29_TW_panelCount{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:18px}.f29_TW_mutedLine{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px}.f29_TW_errorCard{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border:1px solid var(--dsw-alias-state-error-secondary,#cf222e3d);border-radius:8px;padding:8px 10px;font-size:12px;line-height:18px}.f29_TW_errorDetail{color:var(--dsw-alias-label-secondary,#57606a);word-break:break-all;margin-top:2px;font-size:11px;display:block}.f29_TW_degradeCard{border:1px dashed var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:8px;flex-direction:column;gap:4px;padding:10px 12px;display:flex}.f29_TW_degradeTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:12px;font-weight:600}.f29_TW_degradeBody{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:18px}.f29_TW_degradeDetail{color:var(--dsw-alias-label-tertiary,#6e7781);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.f29_TW_noticeBar{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border:1px solid var(--dsw-alias-state-warn-secondary,#9a67003d);border-radius:8px;padding:5px 10px;font-size:12px;line-height:18px}.f29_TW_tree{flex-direction:column;gap:2px;min-width:0;display:flex}.f29_TW_treeRow{align-items:center;gap:4px;min-width:0;display:flex}.f29_TW_toggle{width:18px;height:18px;color:var(--dsw-alias-label-tertiary,#6e7781);cursor:pointer;background:0 0;border:none;border-radius:4px;flex:none;padding:0;font-size:10px;line-height:1}.f29_TW_toggle:hover{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_toggleSpacer{flex:none;width:18px}.f29_TW_node{text-align:left;min-width:0;color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;padding:2px 8px;font-family:inherit;font-size:12px;display:inline-flex}.f29_TW_node:hover{border-color:var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_node:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.f29_TW_node[data-current=true]{border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:var(--dsw-alias-brand-tertiary,#4d6bfe14);cursor:default}.f29_TW_nodeId{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.f29_TW_nodeRole{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:11px}.f29_TW_nodeBadge{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;flex:none;padding:0 6px;font-size:11px;line-height:16px}.f29_TW_nodeBadge[data-kind=current]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:0 0}.f29_TW_nodeBadge[data-kind=live]{color:var(--dsw-alias-state-success-primary,#1a7f37);border-color:var(--dsw-alias-state-success-secondary,#1a7f3752);background:0 0}.f29_TW_searchForm{align-items:center;gap:8px;display:flex}.f29_TW_searchInput{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,#0000001f);border-radius:6px;flex:1;padding:4px 8px;font-family:inherit;font-size:12px}.f29_TW_searchInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:0}.f29_TW_searchSubmit{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:3px 10px;font-family:inherit;font-size:12px}.f29_TW_searchSubmit:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_projectChip{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;align-self:flex-start;padding:0 8px;font-size:11px;line-height:18px}.f29_TW_resultList{flex-direction:column;gap:4px;min-width:0;display:flex}.f29_TW_resultItem{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);cursor:pointer;border-radius:8px;flex-direction:column;gap:3px;min-width:0;padding:6px 10px;font-family:inherit;display:flex}.f29_TW_resultItem:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_resultItem:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.f29_TW_resultHead{align-items:center;gap:6px;min-width:0;display:flex}.f29_TW_resultAgent{color:var(--dsw-alias-label-secondary,#57606a);flex:none;font-size:11px;font-weight:600}.f29_TW_resultTitle{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.f29_TW_matchTag{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;flex:none;margin-left:auto;padding:0 6px;font-size:11px;line-height:16px}.f29_TW_matchTag[data-kind=full-text]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-secondary,#4d6bfe52);background:0 0}.f29_TW_resultMeta{min-width:0;color:var(--dsw-alias-label-tertiary,#6e7781);align-items:center;gap:8px;font-size:11px;display:flex}.f29_TW_resultProject{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.f29_TW_resultId{flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.f29_TW_snippet{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.f29_TW_snippetMark{color:var(--dsw-alias-brand-primary,#4d6bfe);background:var(--dsw-alias-brand-tertiary,#4d6bfe1f);border-radius:2px;font-weight:600}";
4973
+ const css$4 = ".f29_TW_panel{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;gap:8px;display:flex}.f29_TW_panelHead{align-items:baseline;gap:8px;min-width:0;display:flex}.f29_TW_panelTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;font-weight:600}.f29_TW_panelCount{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:18px}.f29_TW_mutedLine{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px}.f29_TW_errorCard{color:var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-state-error-secondary,#cf222e14);border:1px solid var(--dsw-alias-state-error-secondary,#cf222e3d);border-radius:8px;padding:8px 10px;font-size:12px;line-height:18px}.f29_TW_errorDetail{color:var(--dsw-alias-label-secondary,#57606a);word-break:break-all;margin-top:2px;font-size:11px;display:block}.f29_TW_degradeCard{border:1px dashed var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-1,#00000005);border-radius:8px;flex-direction:column;gap:4px;padding:10px 12px;display:flex}.f29_TW_degradeTitle{color:var(--dsw-alias-label-primary,#1f2328);font-size:12px;font-weight:600}.f29_TW_degradeBody{color:var(--dsw-alias-label-secondary,#57606a);font-size:12px;line-height:18px}.f29_TW_degradeDetail{color:var(--dsw-alias-label-tertiary,#6e7781);word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.f29_TW_noticeBar{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#9a6700));background:var(--dsw-alias-state-warn-tertiary,#9a670014);border:1px solid var(--dsw-alias-state-warn-secondary,#9a67003d);border-radius:8px;padding:5px 10px;font-size:12px;line-height:18px}.f29_TW_tree{flex-direction:column;gap:2px;min-width:0;display:flex}.f29_TW_treeRow{align-items:center;gap:4px;min-width:0;display:flex}.f29_TW_toggle{width:18px;height:18px;color:var(--dsw-alias-label-tertiary,#6e7781);cursor:pointer;background:0 0;border:none;border-radius:4px;flex:none;padding:0;font-size:10px;line-height:1}.f29_TW_toggle:hover{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_toggleSpacer{flex:none;width:18px}.f29_TW_node{text-align:left;min-width:0;color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:6px;padding:2px 8px;font-family:inherit;font-size:12px;display:inline-flex}.f29_TW_node:hover{border-color:var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_node:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.f29_TW_node[data-current=true]{border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 8%, transparent);cursor:default}.f29_TW_nodeId{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.f29_TW_nodeRole{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:11px}.f29_TW_nodeBadge{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;flex:none;padding:0 6px;font-size:11px;line-height:16px}.f29_TW_nodeBadge[data-kind=current]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:var(--dsw-alias-brand-primary,#4d6bfe);background:0 0}.f29_TW_nodeBadge[data-kind=live]{color:var(--dsw-alias-state-success-primary,#1a7f37);border-color:var(--dsw-alias-state-success-secondary,#1a7f3752);background:0 0}.f29_TW_searchForm{align-items:center;gap:8px;display:flex}.f29_TW_searchInput{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,#0000001f);border-radius:6px;flex:1;padding:4px 8px;font-family:inherit;font-size:12px}.f29_TW_searchInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:0}.f29_TW_searchSubmit{color:var(--dsw-alias-label-secondary,#57606a);border:1px solid var(--dsw-alias-border-l1,#00000014);cursor:pointer;background:0 0;border-radius:6px;flex:none;padding:3px 10px;font-family:inherit;font-size:12px}.f29_TW_searchSubmit:hover{color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_projectChip{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;align-self:flex-start;padding:0 8px;font-size:11px;line-height:18px}.f29_TW_resultList{flex-direction:column;gap:4px;min-width:0;display:flex}.f29_TW_resultItem{text-align:left;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#00000005);cursor:pointer;border-radius:8px;flex-direction:column;gap:3px;min-width:0;padding:6px 10px;font-family:inherit;display:flex}.f29_TW_resultItem:hover{border-color:var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.f29_TW_resultItem:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}.f29_TW_resultHead{align-items:center;gap:6px;min-width:0;display:flex}.f29_TW_resultAgent{color:var(--dsw-alias-label-secondary,#57606a);flex:none;font-size:11px;font-weight:600}.f29_TW_resultTitle{color:var(--dsw-alias-label-primary,#1f2328);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.f29_TW_matchTag{color:var(--dsw-alias-label-secondary,#57606a);background:var(--dsw-alias-bg-layer-2,#0000000a);border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:999px;flex:none;margin-left:auto;padding:0 6px;font-size:11px;line-height:16px}.f29_TW_matchTag[data-kind=full-text]{color:var(--dsw-alias-brand-primary,#4d6bfe);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 32%, transparent);background:0 0}.f29_TW_resultMeta{min-width:0;color:var(--dsw-alias-label-tertiary,#6e7781);align-items:center;gap:8px;font-size:11px;display:flex}.f29_TW_resultProject{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.f29_TW_resultId{flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.f29_TW_snippet{color:var(--dsw-alias-label-secondary,#57606a);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}.f29_TW_snippetMark{color:var(--dsw-alias-brand-primary,#4d6bfe);background:color-mix(in srgb, var(--dsw-alias-brand-primary,#4d6bfe) 12%, transparent);border-radius:2px;font-weight:600}";
4389
4974
  const tagId$4 = "@shendeguize/dsh-agent-sidecar/dsh-tools.module.css";
4390
4975
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
4391
4976
  const tag = document.createElement("style");
@@ -5273,7 +5858,7 @@ window.__ModuleLoader__.load({
5273
5858
  }
5274
5859
  //#endregion
5275
5860
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/inject/inject.module.css.mjs
5276
- const css$2 = ".KlwADW_panel{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:12px;flex-direction:column;max-width:560px;display:flex}.KlwADW_header{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:12px;padding:14px 16px 10px;display:flex}.KlwADW_title{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:15px;font-weight:600;line-height:1.4}.KlwADW_body{flex-direction:column;gap:10px;padding:12px 16px 14px;display:flex}.KlwADW_capabilityOff{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:8px;margin:0;padding:10px 12px;font-size:13px;line-height:1.6}.KlwADW_noticeWarn,.KlwADW_noticeError{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_noticeError{border:1px solid var(--dsw-alias-label-error);color:var(--dsw-alias-label-error)}.KlwADW_noticeDetail{color:var(--dsw-alias-label-tertiary)}.KlwADW_targetRow{align-items:baseline;gap:10px;min-width:0;display:flex}.KlwADW_noTarget{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.KlwADW_agentTag{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.KlwADW_planTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;overflow:hidden}.KlwADW_field{flex-direction:column;gap:6px;display:flex}.KlwADW_label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.KlwADW_textarea{appearance:none;resize:vertical;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);min-height:96px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:8px 10px;font-size:13px;line-height:1.6}.KlwADW_textarea:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.KlwADW_textarea::placeholder{color:var(--dsw-alias-label-tertiary)}.KlwADW_textarea:disabled{opacity:.5;cursor:default}.KlwADW_byteRow{align-items:center;gap:10px;display:flex}.KlwADW_byteBar{background:var(--dsw-alias-bg-module-platform);border-radius:2px;flex:1;height:3px;overflow:hidden}.KlwADW_byteFill{background:var(--dsw-alias-brand-primary);border-radius:2px;height:100%;transition:width .12s}.KlwADW_byteFillOver{background:var(--dsw-alias-label-error)}.KlwADW_byteText,.KlwADW_byteTextOver{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary);flex:none;font-size:11px;line-height:1.5}.KlwADW_byteTextOver{color:var(--dsw-alias-label-error)}.KlwADW_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.KlwADW_modes{border:0;flex-direction:column;gap:8px;margin:0;padding:0;display:flex}.KlwADW_modeOption{cursor:pointer;align-items:flex-start;gap:10px;display:flex}.KlwADW_modeOption input{accent-color:var(--dsw-alias-brand-primary);cursor:pointer;width:14px;height:14px;margin:3px 0 0}.KlwADW_modeOption input:disabled{cursor:default}.KlwADW_modeText{flex-direction:column;gap:2px;min-width:0;display:flex}.KlwADW_modeLabel{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5}.KlwADW_modeHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.KlwADW_warnBar{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-error);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_auditNote{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_planBox{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.KlwADW_planRow{align-items:baseline;gap:10px;min-width:0;display:flex}.KlwADW_planKey{color:var(--dsw-alias-label-tertiary);flex:none;font-size:12px;line-height:1.5}.KlwADW_planValue{min-width:0;color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:13px;line-height:1.5;display:flex}.KlwADW_observedNote{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}.KlwADW_planPreview{flex-direction:column;gap:4px;min-width:0;display:flex}.KlwADW_preview{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);white-space:pre-wrap;word-break:break-word;max-height:120px;color:var(--dsw-alias-label-secondary);border-radius:6px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6;overflow:auto}.KlwADW_countdown{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}.KlwADW_resultOk,.KlwADW_resultFail,.KlwADW_resultUnknown{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px 12px;font-size:13px;line-height:1.6}.KlwADW_resultOk{border:1px solid var(--dsw-alias-brand-primary)}.KlwADW_resultFail{border:1px solid var(--dsw-alias-label-error);color:var(--dsw-alias-label-error)}.KlwADW_resultUnknown{border:1px solid var(--dsw-alias-label-dimmed);color:var(--dsw-alias-label-secondary)}.KlwADW_resultDetail{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.6}.KlwADW_footer{justify-content:flex-end;align-items:center;gap:8px;padding-top:4px;display:flex}.KlwADW_btn,.KlwADW_btnPrimary,.KlwADW_btnDanger{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.KlwADW_btn{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.KlwADW_btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.KlwADW_btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.KlwADW_btnDanger{border-color:var(--dsw-alias-label-error);color:var(--dsw-alias-label-error);background:0 0;font-weight:600}.KlwADW_btnDanger:hover:not(:disabled){background:var(--dsw-alias-label-error);color:var(--dsw-alias-bg-layer-3)}.KlwADW_btn:disabled,.KlwADW_btnPrimary:disabled,.KlwADW_btnDanger:disabled{opacity:.4;cursor:default}.KlwADW_btn:focus-visible,.KlwADW_btnPrimary:focus-visible,.KlwADW_btnDanger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
5861
+ const css$2 = ".KlwADW_panel{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-2,#0000000a);border-radius:12px;flex-direction:column;max-width:560px;display:flex}.KlwADW_header{border-bottom:1px solid var(--dsw-alias-border-l2,#0000001f);align-items:center;gap:12px;padding:14px 16px 10px;display:flex}.KlwADW_title{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);flex:1;margin:0;font-size:15px;font-weight:600;line-height:1.4}.KlwADW_body{flex-direction:column;gap:10px;padding:12px 16px 14px;display:flex}.KlwADW_capabilityOff{background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-label-secondary,#57606a);border-radius:8px;margin:0;padding:10px 12px;font-size:13px;line-height:1.6}.KlwADW_noticeWarn,.KlwADW_noticeError{background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-label-secondary,#57606a);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_noticeError{border:1px solid var(--dsw-alias-state-error-primary,#cf222e);color:var(--dsw-alias-state-error-primary,#cf222e)}.KlwADW_noticeDetail{color:var(--dsw-alias-label-tertiary,#6e7781)}.KlwADW_targetRow{align-items:baseline;gap:10px;min-width:0;display:flex}.KlwADW_noTarget{color:var(--dsw-alias-label-tertiary,#6e7781);font-size:12px;line-height:1.5}.KlwADW_agentTag{background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-label-secondary,#57606a);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.KlwADW_planTitle{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:1.5;overflow:hidden}.KlwADW_field{flex-direction:column;gap:6px;display:flex}.KlwADW_label{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:1.5}.KlwADW_textarea{appearance:none;resize:vertical;border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-3,#fff);min-height:96px;font:inherit;color:var(--dsw-alias-label-primary,#1f2328);border-radius:8px;padding:8px 10px;font-size:13px;line-height:1.6}.KlwADW_textarea:focus-visible{border-color:var(--dsw-alias-brand-primary,#4d6bfe);outline:none}.KlwADW_textarea::placeholder{color:var(--dsw-alias-label-tertiary,#6e7781)}.KlwADW_textarea:disabled{opacity:.5;cursor:default}.KlwADW_byteRow{align-items:center;gap:10px;display:flex}.KlwADW_byteBar{background:var(--dsw-alias-bg-module-platform,#00000008);border-radius:2px;flex:1;height:3px;overflow:hidden}.KlwADW_byteFill{background:var(--dsw-alias-brand-primary,#4d6bfe);border-radius:2px;height:100%;transition:width .12s}.KlwADW_byteFillOver{background:var(--dsw-alias-state-error-primary,#cf222e)}.KlwADW_byteText,.KlwADW_byteTextOver{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:11px;line-height:1.5}.KlwADW_byteTextOver{color:var(--dsw-alias-state-error-primary,#cf222e)}.KlwADW_invalid{color:var(--dsw-alias-state-error-primary,#cf222e);margin:0;font-size:12px;line-height:1.5}.KlwADW_modes{border:0;flex-direction:column;gap:8px;margin:0;padding:0;display:flex}.KlwADW_modeOption{cursor:pointer;align-items:flex-start;gap:10px;display:flex}.KlwADW_modeOption input{accent-color:var(--dsw-alias-brand-primary,#4d6bfe);cursor:pointer;width:14px;height:14px;margin:3px 0 0}.KlwADW_modeOption input:disabled{cursor:default}.KlwADW_modeText{flex-direction:column;gap:2px;min-width:0;display:flex}.KlwADW_modeLabel{color:var(--dsw-alias-label-primary,#1f2328);font-size:13px;line-height:1.5}.KlwADW_modeHint{color:var(--dsw-alias-label-tertiary,#6e7781);font-size:12px;line-height:1.5}.KlwADW_warnBar{border:1px solid var(--dsw-alias-state-error-primary,#cf222e);background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-state-error-primary,#cf222e);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_auditNote{background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-label-secondary,#57606a);border-radius:8px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6}.KlwADW_planBox{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-3,#fff);border-radius:8px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.KlwADW_planRow{align-items:baseline;gap:10px;min-width:0;display:flex}.KlwADW_planKey{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:12px;line-height:1.5}.KlwADW_planValue{min-width:0;color:var(--dsw-alias-label-primary,#1f2328);align-items:baseline;gap:8px;font-size:13px;line-height:1.5;display:flex}.KlwADW_observedNote{color:var(--dsw-alias-label-tertiary,#6e7781);margin:0;font-size:11px;line-height:1.5}.KlwADW_planPreview{flex-direction:column;gap:4px;min-width:0;display:flex}.KlwADW_preview{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-2,#0000000a);white-space:pre-wrap;word-break:break-word;max-height:120px;color:var(--dsw-alias-label-secondary,#57606a);border-radius:6px;margin:0;padding:8px 10px;font-size:12px;line-height:1.6;overflow:auto}.KlwADW_countdown{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#57606a);margin:0;font-size:12px;line-height:1.5}.KlwADW_resultOk,.KlwADW_resultFail,.KlwADW_resultUnknown{background:var(--dsw-alias-bg-module-platform,#00000008);color:var(--dsw-alias-label-primary,#1f2328);border-radius:8px;margin:0;padding:10px 12px;font-size:13px;line-height:1.6}.KlwADW_resultOk{border:1px solid var(--dsw-alias-brand-primary,#4d6bfe)}.KlwADW_resultFail{border:1px solid var(--dsw-alias-state-error-primary,#cf222e);color:var(--dsw-alias-state-error-primary,#cf222e)}.KlwADW_resultUnknown{border:1px solid var(--dsw-alias-label-dimmed,#8c959f);color:var(--dsw-alias-label-secondary,#57606a)}.KlwADW_resultDetail{color:var(--dsw-alias-label-tertiary,#6e7781);margin:0;font-size:12px;line-height:1.6}.KlwADW_footer{justify-content:flex-end;align-items:center;gap:8px;padding-top:4px;display:flex}.KlwADW_btn,.KlwADW_btnPrimary,.KlwADW_btnDanger{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.KlwADW_btn{border-color:var(--dsw-alias-border-l2,#0000001f);color:var(--dsw-alias-label-secondary,#57606a);background:0 0}.KlwADW_btn:hover:not(:disabled){color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-label-dimmed,#8c959f)}.KlwADW_btnPrimary{background:var(--dsw-alias-label-primary,#1f2328);color:var(--dsw-alias-bg-layer-3,#fff)}.KlwADW_btnDanger{border-color:var(--dsw-alias-state-error-primary,#cf222e);color:var(--dsw-alias-state-error-primary,#cf222e);background:0 0;font-weight:600}.KlwADW_btnDanger:hover:not(:disabled){background:var(--dsw-alias-state-error-primary,#cf222e);color:var(--dsw-alias-bg-layer-3,#fff)}.KlwADW_btn:disabled,.KlwADW_btnPrimary:disabled,.KlwADW_btnDanger:disabled{opacity:.4;cursor:default}.KlwADW_btn:focus-visible,.KlwADW_btnPrimary:focus-visible,.KlwADW_btnDanger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:1px}";
5277
5862
  const tagId$2 = "@shendeguize/dsh-agent-sidecar/inject.module.css";
5278
5863
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
5279
5864
  const tag = document.createElement("style");
@@ -5571,6 +6156,18 @@ window.__ModuleLoader__.load({
5571
6156
  block: null
5572
6157
  };
5573
6158
  }
6159
+ /**
6160
+ * Map a keydown to a panel intent: Escape closes in every phase;
6161
+ * Cmd/Ctrl+Enter submits the PREPARE step only (editor phase with a valid
6162
+ * message). The confirm phase deliberately binds nothing — executing the
6163
+ * injection stays an explicit click (two-phase discipline, §5.3), so no
6164
+ * keyboard path can shortcut the confirmation.
6165
+ */
6166
+ function classifyPanelKey(input) {
6167
+ if (input.key === "Escape") return "close";
6168
+ if (input.key === "Enter" && (input.metaKey || input.ctrlKey)) return input.phase === "idle" && input.canPrepare ? "prepare" : null;
6169
+ return null;
6170
+ }
5574
6171
  /** Which follow-up affordances a terminal outcome earns. */
5575
6172
  function resultActions(outcome) {
5576
6173
  return {
@@ -5578,6 +6175,16 @@ window.__ModuleLoader__.load({
5578
6175
  showCheckSessionHint: outcome === "unknown"
5579
6176
  };
5580
6177
  }
6178
+ /**
6179
+ * True when an execute response reports the message actually reached the
6180
+ * target (UX-05 observation loop): a delivered outcome — including an
6181
+ * idempotent replay of one — never an error envelope. failed/unknown must
6182
+ * NOT trigger observation aids: unknown is a locked terminal state (S6)
6183
+ * and any follow-up activity could read as "retrying is fine".
6184
+ */
6185
+ function isDeliveredResult(value) {
6186
+ return !isApiErrorLike(value) && value.outcome === "delivered";
6187
+ }
5581
6188
  /** Mode radio copy (label + semantics hint) per injection mode. */
5582
6189
  const MODE_COPY = {
5583
6190
  queue: {
@@ -5833,6 +6440,22 @@ window.__ModuleLoader__.load({
5833
6440
  }
5834
6441
  dispatch(event);
5835
6442
  };
6443
+ const handleKeyDown = (event) => {
6444
+ const intent = classifyPanelKey({
6445
+ key: event.key,
6446
+ metaKey: event.metaKey,
6447
+ ctrlKey: event.ctrlKey,
6448
+ phase: state.phase,
6449
+ canPrepare: gate.canPrepare
6450
+ });
6451
+ if (intent === "close" && props.onClose !== void 0) {
6452
+ event.stopPropagation();
6453
+ props.onClose();
6454
+ } else if (intent === "prepare") {
6455
+ event.preventDefault();
6456
+ handlePrepare();
6457
+ }
6458
+ };
5836
6459
  const closeButton = props.onClose !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5837
6460
  type: "button",
5838
6461
  className: inject_module_css_default["btn"],
@@ -5842,6 +6465,7 @@ window.__ModuleLoader__.load({
5842
6465
  if (!props.capability.inject) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5843
6466
  className: inject_module_css_default["panel"],
5844
6467
  "aria-label": t$1("inject.title"),
6468
+ onKeyDown: handleKeyDown,
5845
6469
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5846
6470
  className: inject_module_css_default["header"],
5847
6471
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -5867,6 +6491,7 @@ window.__ModuleLoader__.load({
5867
6491
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5868
6492
  className: inject_module_css_default["panel"],
5869
6493
  "aria-label": t$1("inject.title"),
6494
+ onKeyDown: handleKeyDown,
5870
6495
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5871
6496
  className: inject_module_css_default["header"],
5872
6497
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -5893,6 +6518,13 @@ window.__ModuleLoader__.load({
5893
6518
  className: inject_module_css_default["footer"],
5894
6519
  children: [
5895
6520
  closeButton,
6521
+ isDeliveredResult(result) && props.onObserve !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6522
+ type: "button",
6523
+ className: inject_module_css_default["btnPrimary"],
6524
+ onClick: props.onObserve,
6525
+ "data-testid": "agent-sidecar-inject-observe",
6526
+ children: t$1("inject.observeListen")
6527
+ }) : null,
5896
6528
  actions.canReprepare ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5897
6529
  type: "button",
5898
6530
  className: inject_module_css_default["btnPrimary"],
@@ -5921,6 +6553,7 @@ window.__ModuleLoader__.load({
5921
6553
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5922
6554
  className: inject_module_css_default["panel"],
5923
6555
  "aria-label": t$1("inject.confirmTitle"),
6556
+ onKeyDown: handleKeyDown,
5924
6557
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5925
6558
  className: inject_module_css_default["header"],
5926
6559
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -5975,6 +6608,7 @@ window.__ModuleLoader__.load({
5975
6608
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5976
6609
  className: inject_module_css_default["panel"],
5977
6610
  "aria-label": t$1("inject.title"),
6611
+ onKeyDown: handleKeyDown,
5978
6612
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
5979
6613
  className: inject_module_css_default["header"],
5980
6614
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -6027,6 +6661,7 @@ window.__ModuleLoader__.load({
6027
6661
  placeholder: t$1("inject.messagePlaceholder"),
6028
6662
  disabled: !canEdit,
6029
6663
  rows: 5,
6664
+ autoFocus: true,
6030
6665
  onChange: (event) => {
6031
6666
  setDraft(event.target.value);
6032
6667
  }
@@ -6361,6 +6996,7 @@ window.__ModuleLoader__.load({
6361
6996
  paging = false;
6362
6997
  listenInFlight = false;
6363
6998
  listenQueued = false;
6999
+ refreshInFlight = false;
6364
7000
  lineageStarted = false;
6365
7001
  constructor(sessionId, options = {}) {
6366
7002
  this.fetchDetailFn = options.fetchDetailFn ?? fetchSessionDetail;
@@ -6375,6 +7011,7 @@ window.__ModuleLoader__.load({
6375
7011
  error: null,
6376
7012
  hasMore: false,
6377
7013
  listening: false,
7014
+ refreshing: false,
6378
7015
  ready: false,
6379
7016
  lineage: INITIAL_LINEAGE
6380
7017
  };
@@ -6468,6 +7105,38 @@ window.__ModuleLoader__.load({
6468
7105
  if (listening) this.scheduleListenRefetch();
6469
7106
  }
6470
7107
  /**
7108
+ * Manual newest-window refetch with visible feedback (UX-07), also fired
7109
+ * once after a delivered injection (UX-05 observation loop). Unlike the
7110
+ * silent best-effort listen refetch, it reports in-flight state and
7111
+ * surfaces a failure reason (rendered as the inline banner). Appended
7112
+ * entries get the listen-merge highlight. Coalesced: at most one manual
7113
+ * refresh in flight, extra calls are dropped.
7114
+ */
7115
+ async refreshNewest() {
7116
+ if (this.disposed || !this.state.ready || this.refreshInFlight) return;
7117
+ this.refreshInFlight = true;
7118
+ this.setState({
7119
+ refreshing: true,
7120
+ error: null
7121
+ });
7122
+ try {
7123
+ const page = await this.fetchPageFn(this.state.sessionId, { ...this.listenLimit !== void 0 ? { limit: this.listenLimit } : {} });
7124
+ if (this.disposed) return;
7125
+ this.setState({
7126
+ timeline: applyListenPage(this.state.timeline, page),
7127
+ refreshing: false
7128
+ });
7129
+ } catch (err) {
7130
+ if (this.disposed) return;
7131
+ this.setState({
7132
+ refreshing: false,
7133
+ error: reasonOf(err)
7134
+ });
7135
+ } finally {
7136
+ this.refreshInFlight = false;
7137
+ }
7138
+ }
7139
+ /**
6471
7140
  * SSE `state` frame hook (one call per controller notification). Refreshes
6472
7141
  * the header from the live board card when given, and in listen mode
6473
7142
  * triggers a coalesced newest-window refetch.
@@ -7003,7 +7672,7 @@ window.__ModuleLoader__.load({
7003
7672
  }
7004
7673
  //#endregion
7005
7674
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/detail-view.module.css.mjs
7006
- const css$1 = ".p-2YEW_switcherBar{align-items:center;gap:4px;padding:8px 12px 0;display:flex}.p-2YEW_switcherButton{color:var(--dsw-alias-label-secondary,#57606a);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:999px;padding:3px 10px;font-size:12px}.p-2YEW_switcherButton:hover{background:var(--dsw-alias-bg-layer-1,#00000005)}.p-2YEW_switcherButton[data-active=true]{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-2,#0000000a);border-color:var(--dsw-alias-border-l2,#0000001f);font-weight:600}.p-2YEW_detailRoot{flex-direction:column;gap:12px;min-width:0;padding:12px;display:flex}.p-2YEW_actionsRow{align-items:center;gap:8px;display:flex}.p-2YEW_actionButton{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-1,#00000005);color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;border-radius:6px;padding:3px 12px;font-size:12px}.p-2YEW_actionButton:hover{background:var(--dsw-alias-bg-layer-2,#0000000a)}.p-2YEW_actionButton:disabled{opacity:.5;cursor:not-allowed}.p-2YEW_toolsSection{border-top:1px solid var(--dsw-alias-border-l2,#00000014);flex-direction:column;gap:16px;padding-top:12px;display:flex}";
7675
+ const css$1 = ".p-2YEW_switcherBar{align-items:center;gap:4px;padding:8px 12px 0;display:flex}.p-2YEW_switcherButton{color:var(--dsw-alias-label-secondary,#57606a);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:999px;padding:3px 10px;font-size:12px}.p-2YEW_switcherButton:hover{background:var(--dsw-alias-bg-layer-1,#00000005)}.p-2YEW_switcherButton[data-active=true]{color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-2,#0000000a);border-color:var(--dsw-alias-border-l2,#0000001f);font-weight:600}.p-2YEW_detailRoot{box-sizing:border-box;flex-direction:column;gap:12px;min-width:0;height:100%;padding:12px;display:flex;overflow-y:auto}.p-2YEW_actionsRow{align-items:center;gap:8px;display:flex}.p-2YEW_actionButton{border:1px solid var(--dsw-alias-border-l2,#0000001f);background:var(--dsw-alias-bg-layer-1,#00000005);color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;border-radius:6px;padding:3px 12px;font-size:12px}.p-2YEW_actionButton:hover{background:var(--dsw-alias-bg-layer-2,#0000000a)}.p-2YEW_actionButton:disabled{opacity:.5;cursor:not-allowed}.p-2YEW_toolsSection{border:1px solid var(--dsw-alias-border-l1,#00000014);border-radius:8px;flex-direction:column;gap:16px;padding:8px 12px;display:flex}.p-2YEW_toolsToggle{color:var(--dsw-alias-label-secondary,#57606a);cursor:pointer;background:0 0;border:none;align-self:flex-start;align-items:center;gap:6px;padding:0;font-family:inherit;font-size:12px;display:inline-flex}.p-2YEW_toolsToggle:hover{color:var(--dsw-alias-label-primary,#1f2328)}.p-2YEW_toolsToggleGlyph{color:var(--dsw-alias-label-tertiary,#6e7781);flex:none;font-size:10px}";
7007
7676
  const tagId$1 = "@shendeguize/dsh-agent-sidecar/detail-view.module.css";
7008
7677
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
7009
7678
  const tag = document.createElement("style");
@@ -7018,11 +7687,13 @@ window.__ModuleLoader__.load({
7018
7687
  "detailRoot": "p-2YEW_detailRoot",
7019
7688
  "switcherBar": "p-2YEW_switcherBar",
7020
7689
  "switcherButton": "p-2YEW_switcherButton",
7021
- "toolsSection": "p-2YEW_toolsSection"
7690
+ "toolsSection": "p-2YEW_toolsSection",
7691
+ "toolsToggle": "p-2YEW_toolsToggle",
7692
+ "toolsToggleGlyph": "p-2YEW_toolsToggleGlyph"
7022
7693
  };
7023
7694
  //#endregion
7024
7695
  //#region \0dsh-css:/Users/jingyu/Workspace/Projects/agent_sidecar/plugin/src/client/inject/overlay.module.css.mjs
7025
- const css = "._2WYemW_backdrop{z-index:1000;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._2WYemW_dialog{border-radius:12px;width:min(560px,100%);max-height:min(85vh,720px);overflow:auto}";
7696
+ const css = "._2WYemW_backdrop{z-index:1000;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}._2WYemW_dialog{border-radius:12px;width:min(560px,100%);max-height:min(85vh,720px);overflow:auto}";
7026
7697
  const tagId = "@shendeguize/dsh-agent-sidecar/overlay.module.css";
7027
7698
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
7028
7699
  const tag = document.createElement("style");
@@ -7081,6 +7752,7 @@ window.__ModuleLoader__.load({
7081
7752
  const [analysisStore] = (0, react.useState)(() => integration.createAnalysisStore());
7082
7753
  const [injectOpen, setInjectOpen] = (0, react.useState)(false);
7083
7754
  const [analysisOpen, setAnalysisOpen] = (0, react.useState)(false);
7755
+ const [toolsOpen, setToolsOpen] = (0, react.useState)(false);
7084
7756
  (0, react.useEffect)(() => {
7085
7757
  detailStore.open();
7086
7758
  const unsubscribe = controller.subscribe(() => {
@@ -7109,6 +7781,18 @@ window.__ModuleLoader__.load({
7109
7781
  setInjectOpen(false);
7110
7782
  };
7111
7783
  const title = detail.header.title.trim();
7784
+ const injectActions = injectIntegration === void 0 ? void 0 : {
7785
+ onPrepare: injectIntegration.actions.onPrepare,
7786
+ onExecute: async (req) => {
7787
+ const result = await injectIntegration.actions.onExecute(req);
7788
+ if (isDeliveredResult(result)) detailStore.refreshNewest();
7789
+ return result;
7790
+ }
7791
+ };
7792
+ const observeReaction = () => {
7793
+ if (!detailStore.getState().listening) detailStore.toggleListen();
7794
+ closeInject();
7795
+ };
7112
7796
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7113
7797
  className: detail_view_module_css_default["detailRoot"],
7114
7798
  "data-testid": "agent-sidecar-detail-view",
@@ -7135,6 +7819,53 @@ window.__ModuleLoader__.load({
7135
7819
  children: t("detail.actions.analyze")
7136
7820
  })]
7137
7821
  }),
7822
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7823
+ className: detail_view_module_css_default["toolsSection"],
7824
+ "data-testid": "agent-sidecar-detail-tools",
7825
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7826
+ type: "button",
7827
+ className: detail_view_module_css_default["toolsToggle"],
7828
+ "aria-expanded": toolsOpen,
7829
+ onClick: () => {
7830
+ setToolsOpen((open) => !open);
7831
+ },
7832
+ children: [
7833
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7834
+ className: detail_view_module_css_default["toolsToggleGlyph"],
7835
+ "aria-hidden": true,
7836
+ children: toolsOpen ? "▾" : "▸"
7837
+ }),
7838
+ t("detail.tools.title"),
7839
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7840
+ className: detail_view_module_css_default["toolsToggleGlyph"],
7841
+ children: toolsOpen ? t("detail.tools.hide") : t("detail.tools.show")
7842
+ })
7843
+ ]
7844
+ }), toolsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineageTree, {
7845
+ trace: detail.lineage.trace,
7846
+ available: detail.lineage.available,
7847
+ reason: detail.lineage.reason,
7848
+ detail: detail.lineage.detail,
7849
+ currentSessionId: sessionId,
7850
+ onSelectSession: props.onSelectSession,
7851
+ loading: detail.lineage.loading,
7852
+ error: detail.lineage.error
7853
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {
7854
+ query: search.query,
7855
+ project: search.project,
7856
+ mode: search.mode,
7857
+ items: search.items,
7858
+ loading: search.loading,
7859
+ error: search.error,
7860
+ onQueryChange: (query) => {
7861
+ searchStore.setQuery(query);
7862
+ },
7863
+ onSubmit: () => {
7864
+ searchStore.submit();
7865
+ },
7866
+ onSelectSession: props.onSelectSession
7867
+ })] })]
7868
+ }),
7138
7869
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionDetail, {
7139
7870
  sessionId,
7140
7871
  header: detail.header,
@@ -7143,12 +7874,16 @@ window.__ModuleLoader__.load({
7143
7874
  error: detail.error,
7144
7875
  hasMore: detail.hasMore,
7145
7876
  listening: detail.listening,
7877
+ refreshing: detail.refreshing,
7146
7878
  onLoadMore: () => {
7147
7879
  detailStore.loadMore();
7148
7880
  },
7149
7881
  onToggleListen: () => {
7150
7882
  detailStore.toggleListen();
7151
7883
  },
7884
+ onRefresh: () => {
7885
+ detailStore.refreshNewest();
7886
+ },
7152
7887
  onClose: props.onClose
7153
7888
  }),
7154
7889
  analysisOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnalysisPanel, {
@@ -7170,35 +7905,7 @@ window.__ModuleLoader__.load({
7170
7905
  setAnalysisOpen(false);
7171
7906
  }
7172
7907
  }),
7173
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7174
- className: detail_view_module_css_default["toolsSection"],
7175
- "data-testid": "agent-sidecar-detail-tools",
7176
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LineageTree, {
7177
- trace: detail.lineage.trace,
7178
- available: detail.lineage.available,
7179
- reason: detail.lineage.reason,
7180
- detail: detail.lineage.detail,
7181
- currentSessionId: sessionId,
7182
- onSelectSession: props.onSelectSession,
7183
- loading: detail.lineage.loading,
7184
- error: detail.lineage.error
7185
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchPanel, {
7186
- query: search.query,
7187
- project: search.project,
7188
- mode: search.mode,
7189
- items: search.items,
7190
- loading: search.loading,
7191
- error: search.error,
7192
- onQueryChange: (query) => {
7193
- searchStore.setQuery(query);
7194
- },
7195
- onSubmit: () => {
7196
- searchStore.submit();
7197
- },
7198
- onSelectSession: props.onSelectSession
7199
- })]
7200
- }),
7201
- injectIntegration !== void 0 && injectOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7908
+ injectIntegration !== void 0 && injectActions !== void 0 && injectOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7202
7909
  className: overlay_module_css_default["backdrop"],
7203
7910
  role: "presentation",
7204
7911
  onClick: closeInject,
@@ -7217,9 +7924,10 @@ window.__ModuleLoader__.load({
7217
7924
  ...title !== "" ? { title } : {}
7218
7925
  },
7219
7926
  defaultMode: injectIntegration.getDefaultMode(),
7220
- onPrepare: injectIntegration.actions.onPrepare,
7221
- onExecute: injectIntegration.actions.onExecute,
7222
- onClose: closeInject
7927
+ onPrepare: injectActions.onPrepare,
7928
+ onExecute: injectActions.onExecute,
7929
+ onClose: closeInject,
7930
+ onObserve: observeReaction
7223
7931
  })
7224
7932
  })
7225
7933
  })
@@ -7450,9 +8158,7 @@ window.__ModuleLoader__.load({
7450
8158
  onFiltersChange: (next) => {
7451
8159
  controller.setFilters(next);
7452
8160
  },
7453
- onRefresh: () => {
7454
- controller.refresh();
7455
- },
8161
+ onRefresh: () => controller.refresh(),
7456
8162
  onSelectSession: openDetail
7457
8163
  })] });
7458
8164
  };