@nanmicoder/dsh-agent-teams 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +9 -4
  2. package/README_ZH.md +5 -1
  3. package/assets/agent-teams/action-celebrating-v2.png +0 -0
  4. package/assets/agent-teams/action-reporting-v2.png +0 -0
  5. package/assets/agent-teams/action-sending-v2.png +0 -0
  6. package/assets/agent-teams/action-sleeping-v2.png +0 -0
  7. package/assets/agent-teams/action-thinking-v2.png +0 -0
  8. package/assets/agent-teams/action-working-v2.png +0 -0
  9. package/assets/agent-teams/member-data-v2.png +0 -0
  10. package/assets/agent-teams/member-designer-v2.png +0 -0
  11. package/assets/agent-teams/member-docs-v2.png +0 -0
  12. package/assets/agent-teams/member-engineer-v2.png +0 -0
  13. package/assets/agent-teams/member-operator-v2.png +0 -0
  14. package/assets/agent-teams/member-qa-v2.png +0 -0
  15. package/assets/agent-teams/member-researcher-v2.png +0 -0
  16. package/assets/agent-teams/member-security-v2.png +0 -0
  17. package/assets/agent-teams/team-lead-v2.png +0 -0
  18. package/assets/ui.png +0 -0
  19. package/lib/client/ActivityPanel.js +226 -23
  20. package/lib/client/activity-model.js +4 -4
  21. package/lib/client/artwork.js +15 -12
  22. package/lib/client/index.js +21 -14
  23. package/lib/client/panel-geometry.js +143 -0
  24. package/lib/client.js +505 -92
  25. package/lib/client.js.map +1 -1
  26. package/lib/command.js +14 -15
  27. package/lib/index.js +9 -6
  28. package/lib/types/client/ActivityPanel.d.ts +8 -6
  29. package/lib/types/client/activity-model.d.ts +4 -4
  30. package/lib/types/client/artwork.d.ts +1 -1
  31. package/lib/types/client/index.d.ts +3 -4
  32. package/lib/types/client/panel-geometry.d.ts +49 -0
  33. package/lib/types/command.d.ts +14 -15
  34. package/package.json +23 -23
  35. package/release-notes/v0.1.10.md +48 -0
  36. package/assets/agent-teams/action-celebrating.png +0 -0
  37. package/assets/agent-teams/action-reporting.png +0 -0
  38. package/assets/agent-teams/action-sending.png +0 -0
  39. package/assets/agent-teams/action-sleeping.png +0 -0
  40. package/assets/agent-teams/action-thinking.png +0 -0
  41. package/assets/agent-teams/action-working.png +0 -0
  42. package/assets/agent-teams/data-analyst.png +0 -0
  43. package/assets/agent-teams/designer.png +0 -0
  44. package/assets/agent-teams/docs-coordinator.png +0 -0
  45. package/assets/agent-teams/engineer.png +0 -0
  46. package/assets/agent-teams/qa-engineer.png +0 -0
  47. package/assets/agent-teams/researcher.png +0 -0
  48. package/assets/agent-teams/security-reviewer.png +0 -0
  49. package/assets/agent-teams/team-lead.png +0 -0
package/README.md CHANGED
@@ -22,6 +22,10 @@ Ask in natural language. The plugin provides the team protocol, ten coordination
22
22
  <img src="./assets/ui.png" width="100%" alt="DeepSeek Harness conversation with the AgentTeams live activity panel, members, tasks, dependencies, and reports">
23
23
  </p>
24
24
 
25
+ ## Releases
26
+
27
+ Read the [latest release notes](https://github.com/NanmiCoder/dsh-agent-teams/releases/latest) or browse the [complete release history](https://github.com/NanmiCoder/dsh-agent-teams/releases). The same Markdown notes are included in the npm package under `release-notes/`.
28
+
25
29
  ## Why AgentTeams?
26
30
 
27
31
  | Capability | What it changes |
@@ -91,10 +95,11 @@ command), describe the goal, and press Enter.
91
95
  /agent-teams research the pricing pages of three competitors
92
96
  ```
93
97
 
94
- The line is claimed by the command pipeline and never reaches the model as
95
- plain text the handler queues one deterministic activation message as an
96
- ordinary follow-up turn, so the captain protocol starts immediately. The
97
- invocation is durably logged (`command/run` / `command/done`).
98
+ The command pipeline claims the line, then preserves that exact input as an
99
+ ordinary user follow-up so it remains visible in the main chat. The gesture
100
+ boundary adds the deterministic activation directive at pre-step, so the
101
+ captain protocol still starts immediately. The invocation is also durably
102
+ logged (`command/run` / `command/done`).
98
103
 
99
104
  Surfaces without command adjudication (for example the headless CLI) get the
100
105
  same deterministic activation through a gesture boundary: any genuine user
package/README_ZH.md CHANGED
@@ -22,6 +22,10 @@
22
22
  <img src="./assets/ui.png" width="100%" alt="DeepSeek Harness 对话与 AgentTeams 实时活动面板,展示成员、任务依赖和回报">
23
23
  </p>
24
24
 
25
+ ## 版本更新
26
+
27
+ 查看[最新版本说明](https://github.com/NanmiCoder/dsh-agent-teams/releases/latest),或浏览[完整发布历史](https://github.com/NanmiCoder/dsh-agent-teams/releases)。同一份 Markdown 说明也会随 npm 包发布到 `release-notes/` 目录。
28
+
25
29
  ## 为什么需要 AgentTeams?
26
30
 
27
31
  | 能力 | 带来的变化 |
@@ -88,7 +92,7 @@ dsh web
88
92
  /agent-teams 调研三家竞品的定价页
89
93
  ```
90
94
 
91
- 这一行会被命令管线认领,**不会**以纯文本形式发给模型——处理器会以一条确定性激活消息排队一个普通跟进回合,队长协议立即启动。调用会持久化记录(`command/run` / `command/done`)。
95
+ 这一行被命令管线认领后,会按用户提交的原文作为普通用户消息送入主会话,因此聊天记录中仍能看到完整的 `/agent-teams …`。手势边界会在 pre-step 注入确定性激活指令,队长协议仍会立即启动。调用也会持久化记录(`command/run` / `command/done`)。
92
96
 
93
97
  没有命令裁决的表面(例如 headless CLI)也享有同等的确定性激活:任何以 `/agent-teams` 开头的真实用户消息,都会为其余文本激活该协议;句子中间出现的字样仍是普通文本。
94
98
 
package/assets/ui.png ADDED
Binary file
@@ -2,25 +2,28 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  /**
3
3
  * AgentTeams activity panel: the top-right floater monitoring every team.
4
4
  *
5
- * Modeled on the Claude Code desktop SessionActivityPanel: a fixed glass
6
- * panel at the top-right corner. On wide viewports it cooperatively makes the
7
- * conversation column yield space; narrow viewports keep overlay mode. It
5
+ * Modeled on the Claude Code desktop SessionActivityPanel: a shell-overlay
6
+ * panel that docks at the conversation's top-right edge by default, can be
7
+ * dragged into a floating window, resized, and folded into an activity badge.
8
+ * On wide viewports the docked panel makes the conversation column yield
9
+ * space; narrow viewports keep a simple inset overlay. It
8
10
  * polls the host `/plugins/dsh-agent-teams/state` route for
9
11
  * server-side snapshots (durable files + live subagent activity), with a
10
12
  * collapsed badge that auto-expands once when activity appears. Archived
11
13
  * teams stay available for the owning conversation after live work ends.
12
14
  *
13
- * The floater mounts through a body portal (no top-right slot exists in the
14
- * web shell); it is not a conversation node — the in-conversation panel was
15
- * removed in favor of this always-available monitor.
15
+ * The floater mounts in ui-layout's additive `shell.overlay`; it is not a
16
+ * conversation node — the in-conversation panel was removed in favor of this
17
+ * always-available monitor.
16
18
  * @module dsh-agent-teams/client/activity
17
19
  */
18
- import { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
19
- import { IconBranchOutline16, IconCloseOutline16, } from '@deepseek-ai/dsh-client-ui-primitives';
20
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
21
+ import { IconBranchOutline16, IconChevronDownOutline14, IconPanelLeftOutline16, } from '@deepseek-ai/dsh-client-ui-primitives';
20
22
  import { activityPanelExpandedForSession, compactDagLayout, COMPACT_DAG_NODE_HEIGHT, COMPACT_DAG_NODE_WIDTH, dependencyFocusTaskId, relatedTaskIds, usesParallelTaskGrid, } from "./activity-model.js";
21
23
  import { getActivityMonitorTargetsSnapshot, getActivitySnapshotsSnapshot, startActivityPolling, subscribeActivityMonitorTargets, subscribeActivitySnapshots, } from "./activity-monitor.js";
22
24
  import { ACTION_ART, LEAD_ART, memberArtUrl } from "./artwork.js";
23
25
  import { OPEN_PANEL_EVENT } from "./AgentTeamsCard.js";
26
+ import { DEFAULT_PANEL_LAYOUT, PANEL_LAYOUT_STORAGE_KEY, compactPanelForBounds, dockPanelLayout, floatPanelLayout, movePanelLayout, panelMaximumHeight, panelUsesAutoHeight, parsePanelLayout, resizePanelLayout, resolvePanelGeometry, } from "./panel-geometry.js";
24
27
  import css from './ActivityPanel.module.css';
25
28
  /** Grace before the panel collapses once no team remains. */
26
29
  const AUTOCLOSE_GRACE_MS = 2000;
@@ -30,8 +33,22 @@ const AUTOCLOSE_GRACE_MS = 2000;
30
33
  * right after load. New activity after this window auto-expands as usual.
31
34
  */
32
35
  const AUTO_OPEN_SETTLE_MS = 4000;
33
- /** Root marker shared with the panel CSS while the portal is expanded. */
36
+ /** Root marker shared with the panel CSS while the shell overlay is expanded. */
34
37
  const PANEL_OPEN_ATTRIBUTE = 'data-agent-teams-panel-open';
38
+ /** Shared width concession consumed by the conversation root CSS. */
39
+ const PANEL_SHIFT_PROPERTY = '--agent-teams-panel-shift';
40
+ const PANEL_CONVERSATION_GAP = 14;
41
+ const MOVE_THRESHOLD = 4;
42
+ function initialPanelLayout() {
43
+ if (typeof window === 'undefined')
44
+ return DEFAULT_PANEL_LAYOUT;
45
+ return parsePanelLayout(window.localStorage.getItem(PANEL_LAYOUT_STORAGE_KEY));
46
+ }
47
+ function initialPanelBounds() {
48
+ if (typeof window === 'undefined')
49
+ return { width: 1440, height: 900, anchorRight: 1440 };
50
+ return { width: window.innerWidth, height: window.innerHeight, anchorRight: window.innerWidth };
51
+ }
35
52
  /** Initial-letter fallback for unmatched roles. */
36
53
  function memberInitial(name) {
37
54
  return name.trim().slice(0, 1).toUpperCase() || '?';
@@ -82,7 +99,7 @@ function WorkGlyph({ active }) {
82
99
  }
83
100
  /** Collapsed badge: an always-visible corner pill while any team exists. */
84
101
  function CollapsedBadge({ count, busy, onClick }) {
85
- return (_jsxs("button", { type: "button", className: css.badge, "data-busy": busy, onClick: onClick, "aria-label": `AgentTeams 活动,${count} 个团队`, children: [_jsx("span", { className: css.badgeDot, "data-busy": busy, "aria-hidden": true }), _jsx("span", { className: css.badgeCount, children: count })] }));
102
+ return (_jsxs("button", { type: "button", className: css.badge, "data-agent-teams-collapsed": true, "data-busy": busy, onClick: onClick, "aria-label": `AgentTeams 活动,${count} 个团队`, children: [_jsx("span", { className: css.badgeDot, "data-busy": busy, "aria-hidden": true }), _jsx("span", { className: css.badgeCount, children: count })] }));
86
103
  }
87
104
  function memberStateLabel(member, tasks, historic) {
88
105
  const owned = tasks.filter((task) => task.assignee === member.name);
@@ -265,6 +282,14 @@ export function ActivityPanel({ sessionsList, openSession }) {
265
282
  const [autoOpened, setAutoOpened] = useState(false);
266
283
  const [wasActive, setWasActive] = useState(false);
267
284
  const [historic, setHistoric] = useState(new Map());
285
+ const [layout, setLayout] = useState(initialPanelLayout);
286
+ const [bounds, setBounds] = useState(initialPanelBounds);
287
+ const [interaction, setInteraction] = useState(null);
288
+ const panelRef = useRef(null);
289
+ const boundsRef = useRef(bounds);
290
+ const gestureRef = useRef(null);
291
+ const frameRef = useRef(null);
292
+ const pendingLayoutRef = useRef(null);
268
293
  const current = useSyncExternalStore(sessionsList.subscribe, sessionsList.getSnapshot).current;
269
294
  const monitorTargets = useSyncExternalStore(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
270
295
  const { teams, archivedTeams } = useSyncExternalStore(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
@@ -273,7 +298,60 @@ export function ActivityPanel({ sessionsList, openSession }) {
273
298
  useEffect(() => { currentRef.current = current; }, [current]);
274
299
  const mountedAtRef = useRef(performance.now());
275
300
  const expanded = activityPanelExpandedForSession(open, openOwner, current);
276
- // This portal survives conversation route changes. Gate expansion by its
301
+ const geometry = useMemo(() => resolvePanelGeometry(layout, bounds), [layout, bounds]);
302
+ const compact = compactPanelForBounds(bounds);
303
+ const commitLayout = useCallback((next) => {
304
+ setLayout(next);
305
+ }, []);
306
+ useEffect(() => {
307
+ window.localStorage.setItem(PANEL_LAYOUT_STORAGE_KEY, JSON.stringify(layout));
308
+ }, [layout]);
309
+ // The slot sits inside AppFrame, so all geometry is measured against the
310
+ // shell overlay rather than the browser viewport. The conversation's real
311
+ // right edge is the dock anchor and naturally follows sidebar/details
312
+ // concessions without importing their hashed implementation classes.
313
+ useLayoutEffect(() => {
314
+ const overlay = document.querySelector('[data-shell-overlay]');
315
+ if (overlay === null)
316
+ return;
317
+ const conversation = document.querySelector("[data-phase='active']");
318
+ let frame = null;
319
+ const measure = () => {
320
+ frame = null;
321
+ const overlayRect = overlay.getBoundingClientRect();
322
+ const conversationRect = conversation?.getBoundingClientRect();
323
+ const next = {
324
+ width: overlayRect.width,
325
+ height: overlayRect.height,
326
+ anchorRight: conversationRect === undefined
327
+ ? overlayRect.width
328
+ : Math.min(Math.max(conversationRect.right - overlayRect.left, 0), overlayRect.width),
329
+ };
330
+ const previous = boundsRef.current;
331
+ if (previous.width === next.width
332
+ && previous.height === next.height
333
+ && previous.anchorRight === next.anchorRight)
334
+ return;
335
+ boundsRef.current = next;
336
+ setBounds(next);
337
+ };
338
+ const scheduleMeasure = () => {
339
+ frame ??= requestAnimationFrame(measure);
340
+ };
341
+ measure();
342
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(scheduleMeasure);
343
+ observer?.observe(overlay);
344
+ if (conversation !== null)
345
+ observer?.observe(conversation);
346
+ window.addEventListener('resize', scheduleMeasure);
347
+ return () => {
348
+ if (frame !== null)
349
+ cancelAnimationFrame(frame);
350
+ observer?.disconnect();
351
+ window.removeEventListener('resize', scheduleMeasure);
352
+ };
353
+ }, [current]);
354
+ // This shell overlay survives conversation route changes. Gate expansion by its
277
355
  // owning session during render, then clear stale state before paint. This
278
356
  // removes the old panel immediately instead of waiting for the no-team
279
357
  // autoclose grace period on the destination page.
@@ -285,17 +363,25 @@ export function ActivityPanel({ sessionsList, openSession }) {
285
363
  setWasActive(false);
286
364
  setAutoOpened(false);
287
365
  }, [current, openOwner]);
288
- // The activity panel is a body portal, so announce its open state on body.
289
- // CSS can then make the conversation column yield space without knowing the
290
- // host shell's hashed module class names. Narrow viewports keep overlay mode.
366
+ // Only the wide docked mode asks the conversation column to yield. Floating
367
+ // and compact modes are intentionally true overlays. The width is written as
368
+ // one shared variable so the panel and the concession cannot drift apart.
291
369
  useLayoutEffect(() => {
292
370
  const root = document.documentElement;
293
- if (expanded)
371
+ const shouldYield = expanded && geometry.mode === 'docked' && !compact;
372
+ if (shouldYield) {
294
373
  root.setAttribute(PANEL_OPEN_ATTRIBUTE, '');
295
- else
374
+ root.style.setProperty(PANEL_SHIFT_PROPERTY, `${geometry.width + PANEL_CONVERSATION_GAP + 18}px`);
375
+ }
376
+ else {
296
377
  root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
297
- return () => { root.removeAttribute(PANEL_OPEN_ATTRIBUTE); };
298
- }, [expanded]);
378
+ root.style.removeProperty(PANEL_SHIFT_PROPERTY);
379
+ }
380
+ return () => {
381
+ root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
382
+ root.style.removeProperty(PANEL_SHIFT_PROPERTY);
383
+ };
384
+ }, [compact, expanded, geometry.mode, geometry.width]);
299
385
  useEffect(() => {
300
386
  // Installing the plugin alone must not touch the state route. A successful
301
387
  // AgentTeams conversation card registers the demand that reaches here.
@@ -365,6 +451,123 @@ export function ActivityPanel({ sessionsList, openSession }) {
365
451
  }, [visibleCount, autoOpened, wasActive]);
366
452
  const busy = useMemo(() => visibleTeams.some((team) => team.members.some((member) => member.activity === 'working')), [visibleTeams]);
367
453
  const hasTeams = visibleCount > 0;
454
+ // Auto-height panels do not store their live content height. Capture the
455
+ // rendered box when a pointer gesture starts so movement and a first manual
456
+ // resize clamp against what the user actually sees.
457
+ const panelGeometryForGesture = useCallback(() => {
458
+ const measuredHeight = panelRef.current?.getBoundingClientRect().height;
459
+ if (measuredHeight === undefined || measuredHeight <= 0)
460
+ return geometry;
461
+ return { ...geometry, height: measuredHeight };
462
+ }, [geometry]);
463
+ const flushScheduledLayout = useCallback(() => {
464
+ if (frameRef.current !== null) {
465
+ cancelAnimationFrame(frameRef.current);
466
+ frameRef.current = null;
467
+ }
468
+ const pending = pendingLayoutRef.current;
469
+ pendingLayoutRef.current = null;
470
+ if (pending !== null)
471
+ commitLayout(pending);
472
+ }, [commitLayout]);
473
+ const scheduleLayout = useCallback((next) => {
474
+ pendingLayoutRef.current = next;
475
+ frameRef.current ??= requestAnimationFrame(() => {
476
+ frameRef.current = null;
477
+ const pending = pendingLayoutRef.current;
478
+ pendingLayoutRef.current = null;
479
+ if (pending !== null)
480
+ commitLayout(pending);
481
+ });
482
+ }, [commitLayout]);
483
+ useEffect(() => () => {
484
+ if (frameRef.current !== null)
485
+ cancelAnimationFrame(frameRef.current);
486
+ }, []);
487
+ const beginMove = useCallback((event) => {
488
+ if (compact || event.button !== 0 || event.target.closest('button') !== null)
489
+ return;
490
+ event.preventDefault();
491
+ event.currentTarget.setPointerCapture(event.pointerId);
492
+ gestureRef.current = {
493
+ kind: 'move',
494
+ pointerId: event.pointerId,
495
+ originX: event.clientX,
496
+ originY: event.clientY,
497
+ start: panelGeometryForGesture(),
498
+ activated: false,
499
+ };
500
+ }, [compact, panelGeometryForGesture]);
501
+ const beginResize = useCallback((edge, event) => {
502
+ if (compact || event.button !== 0 || (geometry.mode === 'docked' && edge !== 'left'))
503
+ return;
504
+ event.preventDefault();
505
+ event.stopPropagation();
506
+ event.currentTarget.setPointerCapture(event.pointerId);
507
+ gestureRef.current = {
508
+ kind: 'resize',
509
+ edge,
510
+ pointerId: event.pointerId,
511
+ originX: event.clientX,
512
+ originY: event.clientY,
513
+ start: panelGeometryForGesture(),
514
+ activated: true,
515
+ };
516
+ setInteraction('resizing');
517
+ }, [compact, geometry.mode, panelGeometryForGesture]);
518
+ const updateGesture = useCallback((event) => {
519
+ const gesture = gestureRef.current;
520
+ if (gesture === null || gesture.pointerId !== event.pointerId
521
+ || !event.currentTarget.hasPointerCapture(event.pointerId))
522
+ return;
523
+ const dx = event.clientX - gesture.originX;
524
+ const dy = event.clientY - gesture.originY;
525
+ const activeBounds = boundsRef.current;
526
+ if (gesture.kind === 'move') {
527
+ if (!gesture.activated && Math.hypot(dx, dy) < MOVE_THRESHOLD)
528
+ return;
529
+ if (!gesture.activated) {
530
+ gesture.activated = true;
531
+ setInteraction('dragging');
532
+ }
533
+ scheduleLayout(movePanelLayout(floatPanelLayout(gesture.start, activeBounds), dx, dy, activeBounds));
534
+ return;
535
+ }
536
+ scheduleLayout(resizePanelLayout(gesture.start, gesture.edge ?? 'left', dx, dy, activeBounds));
537
+ }, [scheduleLayout]);
538
+ const endGesture = useCallback((event) => {
539
+ const gesture = gestureRef.current;
540
+ if (gesture === null || gesture.pointerId !== event.pointerId)
541
+ return;
542
+ updateGesture(event);
543
+ flushScheduledLayout();
544
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
545
+ event.currentTarget.releasePointerCapture(event.pointerId);
546
+ }
547
+ gestureRef.current = null;
548
+ setInteraction(null);
549
+ }, [flushScheduledLayout, updateGesture]);
550
+ const cancelGesture = useCallback((event) => {
551
+ const gesture = gestureRef.current;
552
+ if (gesture === null || gesture.pointerId !== event.pointerId)
553
+ return;
554
+ flushScheduledLayout();
555
+ gestureRef.current = null;
556
+ setInteraction(null);
557
+ }, [flushScheduledLayout]);
558
+ const toggleDock = useCallback(() => {
559
+ const liveGeometry = panelGeometryForGesture();
560
+ commitLayout(liveGeometry.mode === 'docked'
561
+ ? floatPanelLayout(liveGeometry, boundsRef.current)
562
+ : dockPanelLayout(liveGeometry, boundsRef.current));
563
+ }, [commitLayout, panelGeometryForGesture]);
564
+ const autoHeight = panelUsesAutoHeight(geometry, bounds);
565
+ const panelStyle = {
566
+ width: geometry.width,
567
+ height: autoHeight ? 'auto' : geometry.height,
568
+ maxHeight: panelMaximumHeight(geometry, bounds),
569
+ transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,
570
+ };
368
571
  if (!hasTeams && !expanded)
369
572
  return null;
370
573
  return (_jsxs(_Fragment, { children: [!expanded && (_jsx(CollapsedBadge, { count: visibleCount, busy: busy, onClick: () => {
@@ -372,13 +575,13 @@ export function ActivityPanel({ sessionsList, openSession }) {
372
575
  return;
373
576
  setOpenOwner(current);
374
577
  setOpen(true);
375
- } })), expanded && (_jsxs("aside", { className: css.panel, "data-agent-teams-activity": true, children: [_jsxs("header", { className: css.panelHead, children: [_jsxs("span", { className: css.panelTitle, children: ["AgentTeams \u6D3B\u52A8", _jsx("span", { className: css.panelDot, "data-busy": busy, "aria-hidden": true })] }), _jsx("button", { type: "button", className: css.closeButton, onClick: () => {
376
- setOpen(false);
377
- setOpenOwner(undefined);
378
- }, "aria-label": "\u5173\u95ED", children: _jsx(IconCloseOutline16, {}) })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
578
+ } })), expanded && (_jsxs("aside", { ref: panelRef, className: css.panel, style: panelStyle, "data-agent-teams-activity": true, "data-panel-mode": geometry.mode, "data-height-mode": autoHeight ? 'auto' : 'manual', "data-compact": compact || undefined, "data-dragging": interaction === 'dragging' || undefined, "data-resizing": interaction === 'resizing' || undefined, "aria-label": "AgentTeams \u6D3B\u52A8\u9762\u677F", children: [_jsxs("header", { className: css.panelHead, onPointerDown: beginMove, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "data-drag-handle": !compact || undefined, children: [_jsxs("span", { className: css.panelTitle, children: ["AgentTeams \u6D3B\u52A8", _jsx("span", { className: css.panelDot, "data-busy": busy, "aria-hidden": true })] }), _jsxs("span", { className: css.panelControls, children: [!compact && (_jsx("button", { type: "button", className: css.iconButton, "data-control": "dock", "data-mode": geometry.mode, onClick: toggleDock, "aria-label": geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', title: geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', children: _jsx(IconPanelLeftOutline16, {}) })), _jsx("button", { type: "button", className: css.iconButton, "data-control": "collapse", onClick: () => {
579
+ setOpen(false);
580
+ setOpenOwner(undefined);
581
+ }, "aria-label": "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", title: "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", children: _jsx(IconChevronDownOutline14, {}) })] })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
379
582
  ? _jsx("span", { className: css.emptyHint, children: "\u6682\u65E0\u56E2\u961F\u6D3B\u52A8" })
380
583
  : (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team, onNavigate: navigateToSession }, team.teamId))), visibleArchived.map((team) => (_jsx("div", { "data-team-id": team.teamId, "data-historic": true, className: css.archivedWrap, children: _jsx(TeamSection, { team: team, onNavigate: navigateToSession, historic: true }) }, `${team.captainSessionId}:${team.teamId}`))), visibleHistoric.map(({ data: team, owner }) => {
381
584
  const teamKey = `${owner}:${team.teamId}`;
382
585
  return (_jsx(TeamSection, { team: historicCardTeam(team, owner), onNavigate: navigateToSession, historic: true }, teamKey));
383
- })] })) })] }))] }));
586
+ })] })) }), !compact && (_jsx("div", { className: css.resizeHandle, "data-resize-edge": "left", onPointerDown: (event) => { beginResize('left', event); }, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "aria-hidden": true })), !compact && geometry.mode === 'floating' && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.resizeHandle, "data-resize-edge": "bottom", onPointerDown: (event) => { beginResize('bottom', event); }, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "aria-hidden": true }), _jsx("div", { className: css.resizeHandle, "data-resize-edge": "corner", onPointerDown: (event) => { beginResize('corner', event); }, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "aria-hidden": true })] }))] }))] }));
384
587
  }
@@ -14,10 +14,10 @@ export function usesParallelTaskGrid(tasks) {
14
14
  /**
15
15
  * Whether an expanded activity panel still belongs to the current session.
16
16
  *
17
- * The panel is mounted through a body portal, so React does not remount it
18
- * when the conversation route changes. Ownership keeps an expanded panel
19
- * from leaking onto the new-session screen (or another conversation) while
20
- * its local open state is being reset.
17
+ * The panel is mounted in the root-scoped shell overlay, so React does not
18
+ * remount it when the conversation route changes. Ownership keeps an expanded
19
+ * panel from leaking onto the new-session screen (or another conversation)
20
+ * while its local open state is being reset.
21
21
  */
22
22
  export function activityPanelExpandedForSession(open, owner, current) {
23
23
  return open && owner !== undefined && owner === current;
@@ -6,23 +6,26 @@
6
6
  */
7
7
  /** Artwork route prefix served by the plugin host half. */
8
8
  export const ART_BASE = '/plugins/dsh-agent-teams/assets/';
9
- /** Whale role artwork per role keyword. */
9
+ /** V2 whale role artwork per role keyword. */
10
10
  const ROLE_ART = [
11
- [/resear|analys|investig|explor|data|study|研究|分析|数据|调查|探索|调研/, 'researcher.png'],
12
- [/engineer|dev\b|server|backend|\bapi\b|runtime|watcher|contract|工程|后端|服务|接口|开发|代码|编程/, 'engineer.png'],
13
- [/\bqa\b|test|verif|quality|测试|质量/, 'qa-engineer.png'],
14
- [/design|\bui\b|\bux\b|front|theme|accessib|设计|前端|主题/, 'designer.png'],
15
- [/secur|audit|risk|threat|review|安全|审计|审查|风险/, 'security-reviewer.png'],
16
- [/docs|writer|product|spec|coordin|撰写|文案|写作|文档|协调/, 'docs-coordinator.png'],
17
- [/release|\bbuild\b|deploy|\bops\b|\bci\b|ship|发布|构建|部署/, 'engineer.png'],
11
+ [/data|analys|metric|performance|数据|分析|指标|性能/, 'member-data-v2.png'],
12
+ [/resear|investig|explor|study|研究|调查|探索|调研/, 'member-researcher-v2.png'],
13
+ // Match compound QA titles (for example "QA Engineer") before the broad
14
+ // engineer bucket, otherwise an eight-role roster repeats the engineer art.
15
+ [/\bqa\b|test|verif|quality|测试|质量|验证/, 'member-qa-v2.png'],
16
+ [/engineer|dev\b|server|backend|\bapi\b|runtime|watcher|contract|工程|后端|服务|接口|开发|代码|编程/, 'member-engineer-v2.png'],
17
+ [/design|\bui\b|\bux\b|front|theme|accessib|设计|前端|主题|无障碍/, 'member-designer-v2.png'],
18
+ [/secur|audit|risk|threat|review|安全|审计|审查|风险/, 'member-security-v2.png'],
19
+ [/docs|writer|product|spec|撰写|文案|写作|文档|规范/, 'member-docs-v2.png'],
20
+ [/release|\bbuild\b|deploy|\bops\b|\bci\b|ship|coordin|发布|构建|部署|运维|协调/, 'member-operator-v2.png'],
18
21
  ];
19
22
  /** Captain artwork (always the lead whale). */
20
- export const LEAD_ART = `${ART_BASE}team-lead.png`;
23
+ export const LEAD_ART = `${ART_BASE}team-lead-v2.png`;
21
24
  /** Status action artwork per member activity. */
22
25
  export const ACTION_ART = {
23
- working: `${ART_BASE}action-working.png`,
24
- idle: `${ART_BASE}action-sleeping.png`,
25
- unknown: `${ART_BASE}action-thinking.png`,
26
+ working: `${ART_BASE}action-working-v2.png`,
27
+ idle: `${ART_BASE}action-sleeping-v2.png`,
28
+ unknown: `${ART_BASE}action-thinking-v2.png`,
26
29
  };
27
30
  /**
28
31
  * Member artwork URL, or null when no role matches (initial-letter fallback).
@@ -1,26 +1,33 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createRoot } from 'react-dom/client';
3
2
  import { ActivityPanel } from "./ActivityPanel.js";
4
3
  import { AgentTeamsCard } from "./AgentTeamsCard.js";
5
4
  import { agentTeamsCardDefinition } from "./agent-teams-card-definition.js";
6
5
  /** Required services: conversation nodes, slots, and sessions navigation. */
7
6
  export const inject = ['conversationEvents', 'slots', 'sessions'];
7
+ /** The replayed user message is the canonical transcript entry. */
8
+ function HiddenAgentTeamsCommand() {
9
+ return null;
10
+ }
8
11
  /**
9
- * Mount the floater through a body portal (the web shell has no top-right
10
- * slot) and register the in-conversation team card, whose "activity panel"
11
- * button re-activates the floater via a window event — the recovery path
12
- * for a closed floater or a re-opened session.
12
+ * Register the activity monitor in the shell's additive overlay and the
13
+ * in-conversation team card. The card's activity button re-opens a folded
14
+ * monitor via a window event — the recovery path for an old session.
13
15
  */
14
16
  export function apply(ctx) {
15
- const host = document.createElement('div');
16
- host.dataset.agentTeamsHost = '';
17
- document.body.appendChild(host);
18
- const root = createRoot(host);
19
- root.render(_jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openSession: (id) => { ctx.sessions.open(id); } }));
20
- ctx.effect(() => () => {
21
- root.unmount();
22
- host.remove();
23
- }, 'agent-teams: activity panel');
17
+ const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openSession: (id) => { ctx.sessions.open(id); } });
18
+ ctx.slots.inject('shell.overlay', () => ctx.slots.register({
19
+ name: 'shell.overlay',
20
+ id: 'agent-teams-activity',
21
+ order: 80,
22
+ label: 'AgentTeams activity',
23
+ }, Panel));
24
+ // The host command is only the slash-menu/admission surface. Its input is
25
+ // replayed as the visible user message, so the generic result row would be
26
+ // a duplicate placed before that message by command lifecycle ordering.
27
+ ctx.slots.inject('conversation.chat.commandview', () => ctx.slots.register({
28
+ name: 'conversation.chat.commandview',
29
+ key: 'agent-teams',
30
+ }, HiddenAgentTeamsCommand));
24
31
  ctx.conversationEvents.register(agentTeamsCardDefinition);
25
32
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
26
33
  name: 'conversation.chat.node',