@nanmicoder/dsh-agent-teams 0.1.9 → 0.1.11
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/README.md +9 -4
- package/README_ZH.md +5 -1
- package/assets/ui.png +0 -0
- package/lib/client/ActivityPanel.js +240 -33
- package/lib/client/AgentTeamsCard.js +5 -3
- package/lib/client/activity-model.js +4 -4
- package/lib/client/activity-monitor.js +32 -7
- package/lib/client/index.js +28 -15
- package/lib/client/panel-geometry.js +143 -0
- package/lib/client/session-navigation.js +21 -0
- package/lib/client.js +551 -100
- package/lib/client.js.map +1 -1
- package/lib/command.js +14 -15
- package/lib/members.js +21 -47
- package/lib/snapshot.js +4 -15
- package/lib/tools.js +1 -1
- package/lib/types/client/ActivityPanel.d.ts +10 -8
- package/lib/types/client/AgentTeamsCard.d.ts +2 -2
- package/lib/types/client/activity-model.d.ts +4 -4
- package/lib/types/client/activity-monitor.d.ts +13 -3
- package/lib/types/client/index.d.ts +3 -4
- package/lib/types/client/panel-geometry.d.ts +49 -0
- package/lib/types/client/session-navigation.d.ts +22 -0
- package/lib/types/command.d.ts +14 -15
- package/lib/types/members.d.ts +17 -13
- package/package.json +23 -23
- package/release-notes/v0.1.10.md +48 -0
- package/release-notes/v0.1.11.md +52 -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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
|
6
|
-
* panel at the top-right
|
|
7
|
-
*
|
|
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
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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,
|
|
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
|
|
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);
|
|
@@ -219,8 +236,11 @@ function TeamSection({ team, onNavigate, historic = false }) {
|
|
|
219
236
|
const allCompleted = team.tasks.length > 0 && completedCount === team.tasks.length;
|
|
220
237
|
return (_jsxs("section", { className: css.team, "data-team-id": team.teamId, children: [_jsxs("header", { className: css.teamHead, children: [_jsx("span", { className: css.teamName, title: team.name, children: team.name }), historic && _jsx("span", { className: css.historicPill, children: "\u5DF2\u7ED3\u675F" }), _jsxs("span", { className: css.teamStats, children: [_jsxs("span", { "data-stat": "members", children: [team.members.length, " \u6210\u5458"] }), _jsxs("span", { "data-stat": "tasks", children: [completedCount, "/", team.tasks.length, " \u5B8C\u6210"] }), _jsxs("span", { "data-stat": "messages", children: [team.messageCount, " \u6D88\u606F"] })] })] }), _jsxs("section", { className: css.delegationSection, "aria-label": "\u961F\u957F\u6D3E\u5DE5\u5173\u7CFB", "data-delegation-map": true, children: [_jsxs("div", { className: css.captainNode, children: [_jsx("span", { className: css.captainAvatar, children: _jsx("img", { className: css.leadAvatar, src: LEAD_ART, alt: "", "aria-hidden": true }) }), _jsxs("span", { className: css.captainInfo, children: [_jsxs("span", { className: css.captainLine, children: [_jsx("span", { className: css.captainName, children: "\u961F\u957F" }), _jsx("span", { className: css.captainRole, children: "\u62C6\u89E3 \u00B7 \u6D3E\u53D1 \u00B7 \u6C47\u603B" })] }), _jsxs("span", { className: css.captainSummary, children: ["\u5DF2\u6D3E\u53D1 ", assignedCount, " \u9879\u4EFB\u52A1\u7ED9 ", team.members.length, " \u540D\u6210\u5458"] })] }), _jsxs("span", { className: css.captainState, "data-busy": busyCount > 0, children: [_jsx(WorkGlyph, { active: busyCount > 0 }), busyCount > 0 ? `${busyCount} 人执行中` : allCompleted ? '已收齐' : '等待回报'] })] }), _jsx(ProgressOverview, { team: team }), _jsxs("button", { type: "button", className: css.membersToggle, onClick: () => { setMembersOpen((current) => !current); }, "aria-expanded": membersOpen, "data-members-toggle": true, children: [_jsxs("span", { children: [_jsx(Chevron, { open: membersOpen }), "\u6210\u5458 ", team.members.length] }), _jsx("span", { children: membersOpen ? '收起' : '展开' })] }), membersOpen && _jsxs("div", { className: css.delegationTree, children: [team.members.length === 0 && _jsx("span", { className: css.emptyHint, children: "\u6682\u65E0\u6210\u5458\uFF0C\u7B49\u5F85\u961F\u957F\u7EC4\u5EFA\u56E2\u961F" }), team.members.map((member) => {
|
|
221
238
|
const owned = team.tasks.filter((task) => task.assignee === member.name);
|
|
222
|
-
return (_jsxs("div", { className: css.memberBlock, "data-activity": member.activity, children: [_jsx("span", { className: css.memberBranch, "aria-hidden": true, children: _jsx("span", {}) }), _jsxs("button", { type: "button", className: css.memberRow, "data-activity": member.activity, onClick: () => {
|
|
223
|
-
|
|
239
|
+
return (_jsxs("div", { className: css.memberBlock, "data-activity": member.activity, children: [_jsx("span", { className: css.memberBranch, "aria-hidden": true, children: _jsx("span", {}) }), _jsxs("button", { type: "button", className: css.memberRow, "data-activity": member.activity, onClick: () => {
|
|
240
|
+
if (member.id !== '') {
|
|
241
|
+
onNavigate(team.captainSessionId, member.id);
|
|
242
|
+
}
|
|
243
|
+
}, children: [_jsxs("span", { className: css.memberAvatar, "data-unread": member.unread > 0, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, style: { background: accentOf(member.id) }, children: memberInitial(member.name) })), _jsx("img", { className: css.stateArt, "data-activity": member.activity, src: ACTION_ART[member.activity], alt: "", "aria-hidden": true })] }), _jsxs("span", { className: css.memberInfo, children: [_jsxs("span", { className: css.memberLine, children: [_jsx("span", { className: css.memberName, children: member.name }), member.role !== '' && _jsx("span", { className: css.memberRole, children: member.role }), _jsxs("span", { className: css.memberState, "data-activity": member.activity, children: [_jsx(WorkGlyph, { active: member.activity === 'working' }), memberStateLabel(member, team.tasks, historic)] })] }), _jsx("span", { className: css.memberStatusLine, children: memberStatusText(member, team.tasks) })] }), _jsxs("span", { className: css.memberCount, children: [member.done, "/", member.total] })] }), _jsxs("div", { className: css.assignmentLine, children: [_jsx("span", { className: css.assignmentLabel, children: "\u961F\u957F\u6D3E\u53D1" }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
|
|
224
244
|
? _jsx("span", { className: css.taskEmpty, children: "\u6682\u65E0\u4EFB\u52A1" })
|
|
225
245
|
: owned.map((task) => (_jsx("span", { className: css.assignmentChip, "data-state": taskTone(task.state, task.status), title: task.subject, children: task.id }, task.id))) })] })] }, member.id));
|
|
226
246
|
})] })] }), _jsx(DependencyMap, { tasks: team.tasks })] }));
|
|
@@ -251,20 +271,28 @@ function historicCardTeam(data, owner) {
|
|
|
251
271
|
/** The top-right activity floater. Teams follow the current session: live
|
|
252
272
|
* snapshots and historic card summaries are only shown while their captain
|
|
253
273
|
* session is the one currently open. */
|
|
254
|
-
export function ActivityPanel({ sessionsList,
|
|
274
|
+
export function ActivityPanel({ sessionsList, openMember }) {
|
|
255
275
|
// Navigating to a member's subagent transcript is an explicit departure:
|
|
256
276
|
// hide the floater immediately instead of waiting out the autocollapse
|
|
257
277
|
// grace, so the panel never lingers over the member session.
|
|
258
|
-
const navigateToSession = (
|
|
278
|
+
const navigateToSession = (parentId, childId) => {
|
|
259
279
|
setOpen(false);
|
|
260
280
|
setWasActive(false);
|
|
261
|
-
|
|
281
|
+
openMember(parentId, childId);
|
|
262
282
|
};
|
|
263
283
|
const [open, setOpen] = useState(false);
|
|
264
284
|
const [openOwner, setOpenOwner] = useState();
|
|
265
285
|
const [autoOpened, setAutoOpened] = useState(false);
|
|
266
286
|
const [wasActive, setWasActive] = useState(false);
|
|
267
287
|
const [historic, setHistoric] = useState(new Map());
|
|
288
|
+
const [layout, setLayout] = useState(initialPanelLayout);
|
|
289
|
+
const [bounds, setBounds] = useState(initialPanelBounds);
|
|
290
|
+
const [interaction, setInteraction] = useState(null);
|
|
291
|
+
const panelRef = useRef(null);
|
|
292
|
+
const boundsRef = useRef(bounds);
|
|
293
|
+
const gestureRef = useRef(null);
|
|
294
|
+
const frameRef = useRef(null);
|
|
295
|
+
const pendingLayoutRef = useRef(null);
|
|
268
296
|
const current = useSyncExternalStore(sessionsList.subscribe, sessionsList.getSnapshot).current;
|
|
269
297
|
const monitorTargets = useSyncExternalStore(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
|
|
270
298
|
const { teams, archivedTeams } = useSyncExternalStore(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
@@ -273,7 +301,60 @@ export function ActivityPanel({ sessionsList, openSession }) {
|
|
|
273
301
|
useEffect(() => { currentRef.current = current; }, [current]);
|
|
274
302
|
const mountedAtRef = useRef(performance.now());
|
|
275
303
|
const expanded = activityPanelExpandedForSession(open, openOwner, current);
|
|
276
|
-
|
|
304
|
+
const geometry = useMemo(() => resolvePanelGeometry(layout, bounds), [layout, bounds]);
|
|
305
|
+
const compact = compactPanelForBounds(bounds);
|
|
306
|
+
const commitLayout = useCallback((next) => {
|
|
307
|
+
setLayout(next);
|
|
308
|
+
}, []);
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
window.localStorage.setItem(PANEL_LAYOUT_STORAGE_KEY, JSON.stringify(layout));
|
|
311
|
+
}, [layout]);
|
|
312
|
+
// The slot sits inside AppFrame, so all geometry is measured against the
|
|
313
|
+
// shell overlay rather than the browser viewport. The conversation's real
|
|
314
|
+
// right edge is the dock anchor and naturally follows sidebar/details
|
|
315
|
+
// concessions without importing their hashed implementation classes.
|
|
316
|
+
useLayoutEffect(() => {
|
|
317
|
+
const overlay = document.querySelector('[data-shell-overlay]');
|
|
318
|
+
if (overlay === null)
|
|
319
|
+
return;
|
|
320
|
+
const conversation = document.querySelector("[data-phase='active']");
|
|
321
|
+
let frame = null;
|
|
322
|
+
const measure = () => {
|
|
323
|
+
frame = null;
|
|
324
|
+
const overlayRect = overlay.getBoundingClientRect();
|
|
325
|
+
const conversationRect = conversation?.getBoundingClientRect();
|
|
326
|
+
const next = {
|
|
327
|
+
width: overlayRect.width,
|
|
328
|
+
height: overlayRect.height,
|
|
329
|
+
anchorRight: conversationRect === undefined
|
|
330
|
+
? overlayRect.width
|
|
331
|
+
: Math.min(Math.max(conversationRect.right - overlayRect.left, 0), overlayRect.width),
|
|
332
|
+
};
|
|
333
|
+
const previous = boundsRef.current;
|
|
334
|
+
if (previous.width === next.width
|
|
335
|
+
&& previous.height === next.height
|
|
336
|
+
&& previous.anchorRight === next.anchorRight)
|
|
337
|
+
return;
|
|
338
|
+
boundsRef.current = next;
|
|
339
|
+
setBounds(next);
|
|
340
|
+
};
|
|
341
|
+
const scheduleMeasure = () => {
|
|
342
|
+
frame ??= requestAnimationFrame(measure);
|
|
343
|
+
};
|
|
344
|
+
measure();
|
|
345
|
+
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(scheduleMeasure);
|
|
346
|
+
observer?.observe(overlay);
|
|
347
|
+
if (conversation !== null)
|
|
348
|
+
observer?.observe(conversation);
|
|
349
|
+
window.addEventListener('resize', scheduleMeasure);
|
|
350
|
+
return () => {
|
|
351
|
+
if (frame !== null)
|
|
352
|
+
cancelAnimationFrame(frame);
|
|
353
|
+
observer?.disconnect();
|
|
354
|
+
window.removeEventListener('resize', scheduleMeasure);
|
|
355
|
+
};
|
|
356
|
+
}, [current]);
|
|
357
|
+
// This shell overlay survives conversation route changes. Gate expansion by its
|
|
277
358
|
// owning session during render, then clear stale state before paint. This
|
|
278
359
|
// removes the old panel immediately instead of waiting for the no-team
|
|
279
360
|
// autoclose grace period on the destination page.
|
|
@@ -285,25 +366,34 @@ export function ActivityPanel({ sessionsList, openSession }) {
|
|
|
285
366
|
setWasActive(false);
|
|
286
367
|
setAutoOpened(false);
|
|
287
368
|
}, [current, openOwner]);
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
369
|
+
// Only the wide docked mode asks the conversation column to yield. Floating
|
|
370
|
+
// and compact modes are intentionally true overlays. The width is written as
|
|
371
|
+
// one shared variable so the panel and the concession cannot drift apart.
|
|
291
372
|
useLayoutEffect(() => {
|
|
292
373
|
const root = document.documentElement;
|
|
293
|
-
|
|
374
|
+
const shouldYield = expanded && geometry.mode === 'docked' && !compact;
|
|
375
|
+
if (shouldYield) {
|
|
294
376
|
root.setAttribute(PANEL_OPEN_ATTRIBUTE, '');
|
|
295
|
-
|
|
377
|
+
root.style.setProperty(PANEL_SHIFT_PROPERTY, `${geometry.width + PANEL_CONVERSATION_GAP + 18}px`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
296
380
|
root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
|
|
297
|
-
|
|
298
|
-
|
|
381
|
+
root.style.removeProperty(PANEL_SHIFT_PROPERTY);
|
|
382
|
+
}
|
|
383
|
+
return () => {
|
|
384
|
+
root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
|
|
385
|
+
root.style.removeProperty(PANEL_SHIFT_PROPERTY);
|
|
386
|
+
};
|
|
387
|
+
}, [compact, expanded, geometry.mode, geometry.width]);
|
|
299
388
|
useEffect(() => {
|
|
300
|
-
|
|
301
|
-
// AgentTeams conversation card registers the demand that reaches here.
|
|
302
|
-
if (currentTargets.length === 0)
|
|
389
|
+
if (current === undefined)
|
|
303
390
|
return;
|
|
304
|
-
|
|
391
|
+
// Cards keep live teams on the normal cadence. The current-session scope
|
|
392
|
+
// also performs one cold-start discovery pass so archived/cardless teams
|
|
393
|
+
// survive a browser or `dsh web` restart.
|
|
394
|
+
const controller = startActivityPolling(currentTargets, { discoverySessionId: current });
|
|
305
395
|
return () => { controller.stop(); };
|
|
306
|
-
}, [currentTargets]);
|
|
396
|
+
}, [current, currentTargets]);
|
|
307
397
|
useEffect(() => {
|
|
308
398
|
const onOpenPanel = (event) => {
|
|
309
399
|
const activeSession = currentRef.current;
|
|
@@ -365,6 +455,123 @@ export function ActivityPanel({ sessionsList, openSession }) {
|
|
|
365
455
|
}, [visibleCount, autoOpened, wasActive]);
|
|
366
456
|
const busy = useMemo(() => visibleTeams.some((team) => team.members.some((member) => member.activity === 'working')), [visibleTeams]);
|
|
367
457
|
const hasTeams = visibleCount > 0;
|
|
458
|
+
// Auto-height panels do not store their live content height. Capture the
|
|
459
|
+
// rendered box when a pointer gesture starts so movement and a first manual
|
|
460
|
+
// resize clamp against what the user actually sees.
|
|
461
|
+
const panelGeometryForGesture = useCallback(() => {
|
|
462
|
+
const measuredHeight = panelRef.current?.getBoundingClientRect().height;
|
|
463
|
+
if (measuredHeight === undefined || measuredHeight <= 0)
|
|
464
|
+
return geometry;
|
|
465
|
+
return { ...geometry, height: measuredHeight };
|
|
466
|
+
}, [geometry]);
|
|
467
|
+
const flushScheduledLayout = useCallback(() => {
|
|
468
|
+
if (frameRef.current !== null) {
|
|
469
|
+
cancelAnimationFrame(frameRef.current);
|
|
470
|
+
frameRef.current = null;
|
|
471
|
+
}
|
|
472
|
+
const pending = pendingLayoutRef.current;
|
|
473
|
+
pendingLayoutRef.current = null;
|
|
474
|
+
if (pending !== null)
|
|
475
|
+
commitLayout(pending);
|
|
476
|
+
}, [commitLayout]);
|
|
477
|
+
const scheduleLayout = useCallback((next) => {
|
|
478
|
+
pendingLayoutRef.current = next;
|
|
479
|
+
frameRef.current ??= requestAnimationFrame(() => {
|
|
480
|
+
frameRef.current = null;
|
|
481
|
+
const pending = pendingLayoutRef.current;
|
|
482
|
+
pendingLayoutRef.current = null;
|
|
483
|
+
if (pending !== null)
|
|
484
|
+
commitLayout(pending);
|
|
485
|
+
});
|
|
486
|
+
}, [commitLayout]);
|
|
487
|
+
useEffect(() => () => {
|
|
488
|
+
if (frameRef.current !== null)
|
|
489
|
+
cancelAnimationFrame(frameRef.current);
|
|
490
|
+
}, []);
|
|
491
|
+
const beginMove = useCallback((event) => {
|
|
492
|
+
if (compact || event.button !== 0 || event.target.closest('button') !== null)
|
|
493
|
+
return;
|
|
494
|
+
event.preventDefault();
|
|
495
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
496
|
+
gestureRef.current = {
|
|
497
|
+
kind: 'move',
|
|
498
|
+
pointerId: event.pointerId,
|
|
499
|
+
originX: event.clientX,
|
|
500
|
+
originY: event.clientY,
|
|
501
|
+
start: panelGeometryForGesture(),
|
|
502
|
+
activated: false,
|
|
503
|
+
};
|
|
504
|
+
}, [compact, panelGeometryForGesture]);
|
|
505
|
+
const beginResize = useCallback((edge, event) => {
|
|
506
|
+
if (compact || event.button !== 0 || (geometry.mode === 'docked' && edge !== 'left'))
|
|
507
|
+
return;
|
|
508
|
+
event.preventDefault();
|
|
509
|
+
event.stopPropagation();
|
|
510
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
511
|
+
gestureRef.current = {
|
|
512
|
+
kind: 'resize',
|
|
513
|
+
edge,
|
|
514
|
+
pointerId: event.pointerId,
|
|
515
|
+
originX: event.clientX,
|
|
516
|
+
originY: event.clientY,
|
|
517
|
+
start: panelGeometryForGesture(),
|
|
518
|
+
activated: true,
|
|
519
|
+
};
|
|
520
|
+
setInteraction('resizing');
|
|
521
|
+
}, [compact, geometry.mode, panelGeometryForGesture]);
|
|
522
|
+
const updateGesture = useCallback((event) => {
|
|
523
|
+
const gesture = gestureRef.current;
|
|
524
|
+
if (gesture === null || gesture.pointerId !== event.pointerId
|
|
525
|
+
|| !event.currentTarget.hasPointerCapture(event.pointerId))
|
|
526
|
+
return;
|
|
527
|
+
const dx = event.clientX - gesture.originX;
|
|
528
|
+
const dy = event.clientY - gesture.originY;
|
|
529
|
+
const activeBounds = boundsRef.current;
|
|
530
|
+
if (gesture.kind === 'move') {
|
|
531
|
+
if (!gesture.activated && Math.hypot(dx, dy) < MOVE_THRESHOLD)
|
|
532
|
+
return;
|
|
533
|
+
if (!gesture.activated) {
|
|
534
|
+
gesture.activated = true;
|
|
535
|
+
setInteraction('dragging');
|
|
536
|
+
}
|
|
537
|
+
scheduleLayout(movePanelLayout(floatPanelLayout(gesture.start, activeBounds), dx, dy, activeBounds));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
scheduleLayout(resizePanelLayout(gesture.start, gesture.edge ?? 'left', dx, dy, activeBounds));
|
|
541
|
+
}, [scheduleLayout]);
|
|
542
|
+
const endGesture = useCallback((event) => {
|
|
543
|
+
const gesture = gestureRef.current;
|
|
544
|
+
if (gesture === null || gesture.pointerId !== event.pointerId)
|
|
545
|
+
return;
|
|
546
|
+
updateGesture(event);
|
|
547
|
+
flushScheduledLayout();
|
|
548
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
549
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
550
|
+
}
|
|
551
|
+
gestureRef.current = null;
|
|
552
|
+
setInteraction(null);
|
|
553
|
+
}, [flushScheduledLayout, updateGesture]);
|
|
554
|
+
const cancelGesture = useCallback((event) => {
|
|
555
|
+
const gesture = gestureRef.current;
|
|
556
|
+
if (gesture === null || gesture.pointerId !== event.pointerId)
|
|
557
|
+
return;
|
|
558
|
+
flushScheduledLayout();
|
|
559
|
+
gestureRef.current = null;
|
|
560
|
+
setInteraction(null);
|
|
561
|
+
}, [flushScheduledLayout]);
|
|
562
|
+
const toggleDock = useCallback(() => {
|
|
563
|
+
const liveGeometry = panelGeometryForGesture();
|
|
564
|
+
commitLayout(liveGeometry.mode === 'docked'
|
|
565
|
+
? floatPanelLayout(liveGeometry, boundsRef.current)
|
|
566
|
+
: dockPanelLayout(liveGeometry, boundsRef.current));
|
|
567
|
+
}, [commitLayout, panelGeometryForGesture]);
|
|
568
|
+
const autoHeight = panelUsesAutoHeight(geometry, bounds);
|
|
569
|
+
const panelStyle = {
|
|
570
|
+
width: geometry.width,
|
|
571
|
+
height: autoHeight ? 'auto' : geometry.height,
|
|
572
|
+
maxHeight: panelMaximumHeight(geometry, bounds),
|
|
573
|
+
transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,
|
|
574
|
+
};
|
|
368
575
|
if (!hasTeams && !expanded)
|
|
369
576
|
return null;
|
|
370
577
|
return (_jsxs(_Fragment, { children: [!expanded && (_jsx(CollapsedBadge, { count: visibleCount, busy: busy, onClick: () => {
|
|
@@ -372,13 +579,13 @@ export function ActivityPanel({ sessionsList, openSession }) {
|
|
|
372
579
|
return;
|
|
373
580
|
setOpenOwner(current);
|
|
374
581
|
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.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
582
|
+
} })), 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: () => {
|
|
583
|
+
setOpen(false);
|
|
584
|
+
setOpenOwner(undefined);
|
|
585
|
+
}, "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
586
|
? _jsx("span", { className: css.emptyHint, children: "\u6682\u65E0\u56E2\u961F\u6D3B\u52A8" })
|
|
380
587
|
: (_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
588
|
const teamKey = `${owner}:${team.teamId}`;
|
|
382
589
|
return (_jsx(TeamSection, { team: historicCardTeam(team, owner), onNavigate: navigateToSession, historic: true }, teamKey));
|
|
383
|
-
})] })) })] }))] }));
|
|
590
|
+
})] })) }), !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
591
|
}
|
|
@@ -30,7 +30,7 @@ function openActivityPanel(data) {
|
|
|
30
30
|
}));
|
|
31
31
|
}
|
|
32
32
|
/** Render one durable team as a compact conversation card. */
|
|
33
|
-
export function AgentTeamsCard({ node,
|
|
33
|
+
export function AgentTeamsCard({ node, openMember, sessionId }) {
|
|
34
34
|
const data = node.data;
|
|
35
35
|
// `conversation.chat.node` is session-scoped, so its framework-owned id is
|
|
36
36
|
// a stable owner even while another conversation becomes current.
|
|
@@ -47,6 +47,8 @@ export function AgentTeamsCard({ node, openSession, sessionId }) {
|
|
|
47
47
|
teamName: snapshot?.name ?? data.teamName,
|
|
48
48
|
members: snapshot?.members.map((member) => ({ id: member.id, name: member.name, role: member.role })) ?? data.members,
|
|
49
49
|
}), [data, owner, snapshot]);
|
|
50
|
-
return (_jsxs("section", { className: css.root, "data-agent-teams-card": true, "data-team-id": resolved.teamId, children: [_jsxs("header", { className: css.head, children: [_jsx("img", { className: css.leadAvatar, src: LEAD_ART, alt: "", "aria-hidden": true }), _jsx("span", { className: css.teamName, title: resolved.teamName, children: resolved.teamName }), _jsxs("span", { className: css.memberCount, children: [resolved.members.length, " \u540D\u6210\u5458"] }), _jsx("button", { type: "button", className: css.panelButton, onClick: () => { openActivityPanel(resolved); }, "aria-label": "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", title: "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", children: "\u6D3B\u52A8\u9762\u677F" })] }), resolved.members.length > 0 && (_jsx("div", { className: css.members, children: resolved.members.map((member) => (_jsxs("button", { type: "button", className: css.member, onClick: () => {
|
|
51
|
-
|
|
50
|
+
return (_jsxs("section", { className: css.root, "data-agent-teams-card": true, "data-team-id": resolved.teamId, children: [_jsxs("header", { className: css.head, children: [_jsx("img", { className: css.leadAvatar, src: LEAD_ART, alt: "", "aria-hidden": true }), _jsx("span", { className: css.teamName, title: resolved.teamName, children: resolved.teamName }), _jsxs("span", { className: css.memberCount, children: [resolved.members.length, " \u540D\u6210\u5458"] }), _jsx("button", { type: "button", className: css.panelButton, onClick: () => { openActivityPanel(resolved); }, "aria-label": "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", title: "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", children: "\u6D3B\u52A8\u9762\u677F" })] }), resolved.members.length > 0 && (_jsx("div", { className: css.members, children: resolved.members.map((member) => (_jsxs("button", { type: "button", className: css.member, onClick: () => {
|
|
51
|
+
if (member.id !== '')
|
|
52
|
+
openMember(owner, member.id);
|
|
53
|
+
}, title: member.role === '' ? member.name : `${member.name} · ${member.role}`, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, children: member.name.trim().slice(0, 1).toUpperCase() || '?' })), _jsx("span", { className: css.memberName, children: member.name })] }, member.id))) }))] }));
|
|
52
54
|
}
|
|
@@ -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
|
|
18
|
-
* when the conversation route changes. Ownership keeps an expanded
|
|
19
|
-
* from leaking onto the new-session screen (or another conversation)
|
|
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;
|
|
@@ -104,12 +104,17 @@ export const ACTIVITY_STATE_URL = '/plugins/dsh-agent-teams/state';
|
|
|
104
104
|
/**
|
|
105
105
|
* Start the single polling loop for the current session's requested targets.
|
|
106
106
|
*
|
|
107
|
-
* With
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
* With neither targets nor a discovery session this is deliberately inert.
|
|
108
|
+
* A discovery session performs one live+archive pass after selection/restart;
|
|
109
|
+
* it keeps polling only while that captain still owns a live team. This
|
|
110
|
+
* restores legacy/cardless history without turning every ordinary session
|
|
111
|
+
* into a permanent one-second filesystem scan. Explicit card targets retain
|
|
112
|
+
* the normal cadence, and archive state is refreshed when a target or a
|
|
113
|
+
* previously discovered live team disappears.
|
|
110
114
|
*/
|
|
111
115
|
export function startActivityPolling(monitorTargets, runtime = {}) {
|
|
112
|
-
|
|
116
|
+
const discoverySessionId = runtime.discoverySessionId?.trim();
|
|
117
|
+
if (monitorTargets.length === 0 && (discoverySessionId === undefined || discoverySessionId === '')) {
|
|
113
118
|
return { firstTick: Promise.resolve(), stop: () => { } };
|
|
114
119
|
}
|
|
115
120
|
const fetchState = runtime.fetchState ?? ((url, init) => fetch(url, init));
|
|
@@ -119,10 +124,16 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
|
|
|
119
124
|
const settleTargets = runtime.settleTargets ?? settleActivityMonitorTargets;
|
|
120
125
|
let cancelled = false;
|
|
121
126
|
let inFlight = false;
|
|
127
|
+
let discoveryComplete = false;
|
|
128
|
+
let discoveredLiveKeys = new Set();
|
|
122
129
|
let controller;
|
|
123
130
|
const tick = async () => {
|
|
124
131
|
if (inFlight || cancelled)
|
|
125
132
|
return;
|
|
133
|
+
// A cardless ordinary or archive-only session needs one recovery pass,
|
|
134
|
+
// then stays dormant until the component is recreated for another session.
|
|
135
|
+
if (discoveryComplete && monitorTargets.length === 0 && discoveredLiveKeys.size === 0)
|
|
136
|
+
return;
|
|
126
137
|
inFlight = true;
|
|
127
138
|
controller = new AbortController();
|
|
128
139
|
try {
|
|
@@ -137,11 +148,24 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
|
|
|
137
148
|
return;
|
|
138
149
|
const liveTeams = body.teams;
|
|
139
150
|
publishSnapshots({ teams: liveTeams });
|
|
151
|
+
const previousDiscoveredKeys = discoveredLiveKeys;
|
|
152
|
+
discoveredLiveKeys = new Set(discoverySessionId === undefined || discoverySessionId === ''
|
|
153
|
+
? []
|
|
154
|
+
: liveTeams
|
|
155
|
+
.filter((team) => team.captainSessionId === discoverySessionId)
|
|
156
|
+
.map((team) => team.teamId));
|
|
157
|
+
const discoveredTeamArchived = [...previousDiscoveredKeys]
|
|
158
|
+
.some((teamId) => !discoveredLiveKeys.has(teamId));
|
|
140
159
|
const missing = monitorTargets.filter((target) => !liveTeams.some((team) => team.captainSessionId === target.sessionId && team.teamId === target.teamId));
|
|
141
|
-
|
|
160
|
+
const needsDiscoveryArchive = discoverySessionId !== undefined
|
|
161
|
+
&& discoverySessionId !== ''
|
|
162
|
+
&& !discoveryComplete;
|
|
163
|
+
if (missing.length === 0 && !needsDiscoveryArchive && !discoveredTeamArchived)
|
|
142
164
|
return;
|
|
143
|
-
// Archives are immutable. A successful fallback
|
|
144
|
-
// target, including legacy cards whose
|
|
165
|
+
// Archives are immutable per team generation. A successful fallback
|
|
166
|
+
// retires every missing explicit target, including legacy cards whose
|
|
167
|
+
// host archive no longer exists; discovery remains available from the
|
|
168
|
+
// shared snapshot after this controller becomes dormant.
|
|
145
169
|
const archivedResponse = await fetchState(`${ACTIVITY_STATE_URL}?archived=1`, {
|
|
146
170
|
cache: 'no-store',
|
|
147
171
|
signal: controller.signal,
|
|
@@ -152,6 +176,7 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
|
|
|
152
176
|
if (cancelled || !Array.isArray(archivedBody.teams))
|
|
153
177
|
return;
|
|
154
178
|
publishSnapshots({ archivedTeams: archivedBody.teams });
|
|
179
|
+
discoveryComplete = true;
|
|
155
180
|
settleTargets(new Set(missing.map((target) => target.key)));
|
|
156
181
|
}
|
|
157
182
|
catch (error) {
|
package/lib/client/index.js
CHANGED
|
@@ -1,32 +1,45 @@
|
|
|
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";
|
|
5
|
+
import { openAgentTeamMember } from "./session-navigation.js";
|
|
6
6
|
/** Required services: conversation nodes, slots, and sessions navigation. */
|
|
7
7
|
export const inject = ['conversationEvents', 'slots', 'sessions'];
|
|
8
|
+
/** The replayed user message is the canonical transcript entry. */
|
|
9
|
+
function HiddenAgentTeamsCommand() {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* for a closed floater or a re-opened session.
|
|
13
|
+
* Register the activity monitor in the shell's additive overlay and the
|
|
14
|
+
* in-conversation team card. The card's activity button re-opens a folded
|
|
15
|
+
* monitor via a window event — the recovery path for an old session.
|
|
13
16
|
*/
|
|
14
17
|
export function apply(ctx) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const openMember = (parentId, childId) => {
|
|
19
|
+
void openAgentTeamMember(ctx.sessions, parentId, childId).catch((error) => {
|
|
20
|
+
console.warn(`agent-teams: failed to open member transcript ${childId}: ${String(error)}`);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openMember: openMember });
|
|
24
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
25
|
+
name: 'shell.overlay',
|
|
26
|
+
id: 'agent-teams-activity',
|
|
27
|
+
order: 80,
|
|
28
|
+
label: 'AgentTeams activity',
|
|
29
|
+
}, Panel));
|
|
30
|
+
// The host command is only the slash-menu/admission surface. Its input is
|
|
31
|
+
// replayed as the visible user message, so the generic result row would be
|
|
32
|
+
// a duplicate placed before that message by command lifecycle ordering.
|
|
33
|
+
ctx.slots.inject('conversation.chat.commandview', () => ctx.slots.register({
|
|
34
|
+
name: 'conversation.chat.commandview',
|
|
35
|
+
key: 'agent-teams',
|
|
36
|
+
}, HiddenAgentTeamsCommand));
|
|
24
37
|
ctx.conversationEvents.register(agentTeamsCardDefinition);
|
|
25
38
|
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
26
39
|
name: 'conversation.chat.node',
|
|
27
40
|
key: 'agent-teams',
|
|
28
41
|
inject: () => ({
|
|
29
|
-
|
|
42
|
+
openMember,
|
|
30
43
|
}),
|
|
31
44
|
}, AgentTeamsCard));
|
|
32
45
|
}
|