@nanmicoder/dsh-agent-teams 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/assets/agent-teams/action-celebrating.png +0 -0
  4. package/assets/agent-teams/action-reporting.png +0 -0
  5. package/assets/agent-teams/action-sending.png +0 -0
  6. package/assets/agent-teams/action-sleeping.png +0 -0
  7. package/assets/agent-teams/action-thinking.png +0 -0
  8. package/assets/agent-teams/action-working.png +0 -0
  9. package/assets/agent-teams/data-analyst.png +0 -0
  10. package/assets/agent-teams/designer.png +0 -0
  11. package/assets/agent-teams/docs-coordinator.png +0 -0
  12. package/assets/agent-teams/engineer.png +0 -0
  13. package/assets/agent-teams/qa-engineer.png +0 -0
  14. package/assets/agent-teams/researcher.png +0 -0
  15. package/assets/agent-teams/security-reviewer.png +0 -0
  16. package/assets/agent-teams/team-lead.png +0 -0
  17. package/cordis.patch.yml +21 -0
  18. package/lib/client/ActivityPanel.js +340 -0
  19. package/lib/client/AgentTeamsCard.js +74 -0
  20. package/lib/client/activity-model.js +70 -0
  21. package/lib/client/agent-teams-card-definition.js +85 -0
  22. package/lib/client/artwork.js +40 -0
  23. package/lib/client/index.js +33 -0
  24. package/lib/client.js +1235 -0
  25. package/lib/client.js.map +1 -0
  26. package/lib/event-types.js +12 -0
  27. package/lib/events.js +60 -0
  28. package/lib/index.js +172 -0
  29. package/lib/members.js +168 -0
  30. package/lib/snapshot.js +155 -0
  31. package/lib/state.js +461 -0
  32. package/lib/tools.js +749 -0
  33. package/lib/types/client/ActivityPanel.d.ts +64 -0
  34. package/lib/types/client/AgentTeamsCard.d.ts +24 -0
  35. package/lib/types/client/activity-model.d.ts +31 -0
  36. package/lib/types/client/agent-teams-card-definition.d.ts +44 -0
  37. package/lib/types/client/artwork.d.ts +19 -0
  38. package/lib/types/client/index.d.ts +11 -0
  39. package/lib/types/event-types.d.ts +103 -0
  40. package/lib/types/events.d.ts +37 -0
  41. package/lib/types/index.d.ts +42 -0
  42. package/lib/types/members.d.ts +86 -0
  43. package/lib/types/snapshot.d.ts +83 -0
  44. package/lib/types/state.d.ts +144 -0
  45. package/lib/types/tools.d.ts +40 -0
  46. package/lib/types/types.d.ts +73 -0
  47. package/lib/types.js +11 -0
  48. package/package.json +108 -0
package/lib/client.js ADDED
@@ -0,0 +1,1235 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-agent-teams",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react_dom_client = require("react-dom/client");
9
+ let react = require("react");
10
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ //#region lib/client/activity-model.js
12
+ /** Pure relationship projections used by the AgentTeams activity panel. */
13
+ /**
14
+ * Whether an expanded activity panel still belongs to the current session.
15
+ *
16
+ * The panel is mounted through a body portal, so React does not remount it
17
+ * when the conversation route changes. Ownership keeps an expanded panel
18
+ * from leaking onto the new-session screen (or another conversation) while
19
+ * its local open state is being reset.
20
+ */
21
+ function activityPanelExpandedForSession(open, owner, current) {
22
+ return open && owner !== void 0 && owner === current;
23
+ }
24
+ /** Group tasks by their precomputed dependency depth. */
25
+ function taskStages(tasks) {
26
+ const byDepth = /* @__PURE__ */ new Map();
27
+ for (const task of tasks) {
28
+ const depth = Number.isFinite(task.depth) ? Math.max(0, Math.floor(task.depth)) : 0;
29
+ const stage = byDepth.get(depth) ?? [];
30
+ stage.push(task);
31
+ byDepth.set(depth, stage);
32
+ }
33
+ return [...byDepth.entries()].sort(([left], [right]) => left - right).map(([depth, stageTasks]) => ({
34
+ depth,
35
+ tasks: stageTasks.slice().sort((left, right) => left.id.localeCompare(right.id, "en", { numeric: true }))
36
+ }));
37
+ }
38
+ /**
39
+ * Return the complete upstream/downstream chain around one task.
40
+ *
41
+ * Traversal uses both dependency directions and remains cycle-safe, so the UI
42
+ * can highlight every handoff related to the focused task even if malformed
43
+ * durable data contains a cycle.
44
+ */
45
+ function relatedTaskIds(taskId, tasks) {
46
+ const byId = new Map(tasks.map((task) => [task.id, task]));
47
+ if (!byId.has(taskId)) return /* @__PURE__ */ new Set();
48
+ const dependents = /* @__PURE__ */ new Map();
49
+ for (const task of tasks) for (const dependency of task.dependencies) {
50
+ const targets = dependents.get(dependency) ?? [];
51
+ targets.push(task.id);
52
+ dependents.set(dependency, targets);
53
+ }
54
+ const related = /* @__PURE__ */ new Set();
55
+ const upstreamSeen = /* @__PURE__ */ new Set();
56
+ const downstreamSeen = /* @__PURE__ */ new Set();
57
+ const visitUpstream = (id) => {
58
+ if (upstreamSeen.has(id)) return;
59
+ upstreamSeen.add(id);
60
+ related.add(id);
61
+ for (const dependency of byId.get(id)?.dependencies ?? []) visitUpstream(dependency);
62
+ };
63
+ const visitDownstream = (id) => {
64
+ if (downstreamSeen.has(id)) return;
65
+ downstreamSeen.add(id);
66
+ related.add(id);
67
+ for (const dependent of dependents.get(id) ?? []) visitDownstream(dependent);
68
+ };
69
+ visitUpstream(taskId);
70
+ visitDownstream(taskId);
71
+ return related;
72
+ }
73
+ //#endregion
74
+ //#region lib/client/artwork.js
75
+ /**
76
+ * Shared whale artwork lookup for the activity panel and the conversation
77
+ * card: role keywords map to the packaged role images; the captain always
78
+ * uses the lead whale.
79
+ * @module dsh-agent-teams/client/artwork
80
+ */
81
+ /** Artwork route prefix served by the plugin host half. */
82
+ const ART_BASE = "/plugins/dsh-agent-teams/assets/";
83
+ /** Whale role artwork per role keyword. */
84
+ const ROLE_ART = [
85
+ [/resear|analys|investig|explor|data|study|研究|分析|数据|调查|探索|调研/, "researcher.png"],
86
+ [/engineer|dev\b|server|backend|\bapi\b|runtime|watcher|contract|工程|后端|服务|接口|开发|代码|编程/, "engineer.png"],
87
+ [/\bqa\b|test|verif|quality|测试|质量/, "qa-engineer.png"],
88
+ [/design|\bui\b|\bux\b|front|theme|accessib|设计|前端|主题/, "designer.png"],
89
+ [/secur|audit|risk|threat|review|安全|审计|审查|风险/, "security-reviewer.png"],
90
+ [/docs|writer|product|spec|coordin|撰写|文案|写作|文档|协调/, "docs-coordinator.png"],
91
+ [/release|\bbuild\b|deploy|\bops\b|\bci\b|ship|发布|构建|部署/, "engineer.png"]
92
+ ];
93
+ /** Captain artwork (always the lead whale). */
94
+ const LEAD_ART = `${ART_BASE}team-lead.png`;
95
+ /** Status action artwork per member activity. */
96
+ const ACTION_ART = {
97
+ working: `${ART_BASE}action-working.png`,
98
+ idle: `${ART_BASE}action-sleeping.png`,
99
+ unknown: `${ART_BASE}action-thinking.png`
100
+ };
101
+ /**
102
+ * Member artwork URL, or null when no role matches (initial-letter fallback).
103
+ * @param name - the member's display name.
104
+ * @param role - the member's role text.
105
+ * @returns the artwork URL, or null when unmatched.
106
+ */
107
+ function memberArtUrl(name, role) {
108
+ const identity = `${name} ${role}`.toLowerCase();
109
+ for (const [pattern, art] of ROLE_ART) if (pattern.test(identity)) return `${ART_BASE}${art}`;
110
+ return null;
111
+ }
112
+ //#endregion
113
+ //#region \0dsh-css:/Users/nanmi/workspace/myself_code/dsh-agent-teams/src/client/AgentTeamsCard.module.css.mjs
114
+ const css$1 = "._6Ci0pW_root{box-sizing:border-box;border:1px solid var(--dsw-alias-line-normal);background:var(--dsw-alias-bg-module-platform);border-radius:10px;flex-direction:column;gap:8px;width:100%;min-width:0;padding:10px 12px;display:flex}._6Ci0pW_head{align-items:center;gap:8px;min-width:0;display:flex}._6Ci0pW_leadAvatar{border:1px solid var(--dsw-alias-line-strong);object-fit:cover;background:#0b1d33;border-radius:50%;flex:none;width:24px;height:24px}._6Ci0pW_teamName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:0 auto;font-size:13px;font-weight:600;line-height:20px;overflow:hidden}._6Ci0pW_memberCount{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;margin-left:auto;font-size:11px;line-height:16px}._6Ci0pW_panelButton{border:1px solid var(--dsw-alias-line-strong);background:var(--dsw-alias-bg-module);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:999px;flex:none;padding:2px 8px;font-size:10.5px;font-weight:600;line-height:16px;transition:border-color .12s,color .12s}._6Ci0pW_panelButton:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}._6Ci0pW_panelButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._6Ci0pW_members{flex-wrap:wrap;gap:6px;min-width:0;display:flex}._6Ci0pW_member{border:1px solid var(--dsw-alias-line-normal);background:var(--dsw-alias-bg-module);max-width:160px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:999px;align-items:center;gap:5px;padding:3px 8px 3px 3px;font-size:11px;font-weight:500;line-height:16px;transition:border-color .12s,background-color .12s;display:inline-flex}._6Ci0pW_member:hover{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-fill-neutral)}._6Ci0pW_member:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._6Ci0pW_memberArt{border:1px solid var(--dsw-alias-line-strong);object-fit:cover;background:#0b1d33;border-radius:50%;width:20px;height:20px}._6Ci0pW_memberInitial{background:var(--dsw-alias-bg-fill-business);width:20px;height:20px;color:var(--dsw-alias-label-on-fill);border-radius:50%;justify-content:center;align-items:center;font-size:10px;font-weight:600;line-height:20px;display:inline-flex}._6Ci0pW_memberName{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}";
115
+ const tagId$1 = "dsh-agent-teams/AgentTeamsCard.module.css";
116
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
117
+ const tag = document.createElement("style");
118
+ tag.dataset.plugin = "dsh-agent-teams";
119
+ tag.dataset.pluginCss = tagId$1;
120
+ tag.textContent = css$1;
121
+ document.head.appendChild(tag);
122
+ }
123
+ var AgentTeamsCard_module_css_default = {
124
+ "memberArt": "_6Ci0pW_memberArt",
125
+ "memberCount": "_6Ci0pW_memberCount",
126
+ "root": "_6Ci0pW_root",
127
+ "memberInitial": "_6Ci0pW_memberInitial",
128
+ "panelButton": "_6Ci0pW_panelButton",
129
+ "members": "_6Ci0pW_members",
130
+ "teamName": "_6Ci0pW_teamName",
131
+ "head": "_6Ci0pW_head",
132
+ "leadAvatar": "_6Ci0pW_leadAvatar",
133
+ "member": "_6Ci0pW_member",
134
+ "memberName": "_6Ci0pW_memberName"
135
+ };
136
+ //#endregion
137
+ //#region lib/client/AgentTeamsCard.js
138
+ /**
139
+ * AgentTeams conversation card: the lightweight in-conversation summary for
140
+ * one team — the captain's whale avatar and name, the member roster as
141
+ * clickable whale avatars (opening the member's subagent transcript), and
142
+ * an "activity panel" button that re-activates the top-right floater.
143
+ *
144
+ * The floater and this card share the `agent-teams:open-panel` window event
145
+ * so the card can summon the panel even after it was closed (or when an old
146
+ * session is re-opened for review).
147
+ * @module dsh-agent-teams/client/card
148
+ */
149
+ /** Window event name the floater listens for to open itself. */
150
+ const OPEN_PANEL_EVENT = "agent-teams:open-panel";
151
+ /** Re-activate the top-right activity panel, carrying this team's summary
152
+ * so the panel can show it even when the team no longer exists on disk
153
+ * (historical session review). */
154
+ function openActivityPanel(data) {
155
+ window.dispatchEvent(new CustomEvent(OPEN_PANEL_EVENT, { detail: {
156
+ teamId: data.teamId,
157
+ captainSessionId: data.captainSessionId,
158
+ teamName: data.teamName,
159
+ members: data.members
160
+ } }));
161
+ }
162
+ /** Render one durable team as a compact conversation card. */
163
+ function AgentTeamsCard({ node, openSession, currentSessionId }) {
164
+ const data = node.data;
165
+ const owner = data.captainSessionId || currentSessionId() || "";
166
+ const [snapshot, setSnapshot] = (0, react.useState)();
167
+ (0, react.useEffect)(() => {
168
+ let cancelled = false;
169
+ const tick = async () => {
170
+ for (const url of ["/plugins/dsh-agent-teams/state", "/plugins/dsh-agent-teams/state?archived=1"]) try {
171
+ const response = await fetch(url, { cache: "no-store" });
172
+ if (!response.ok) continue;
173
+ const body = await response.json();
174
+ const found = Array.isArray(body.teams) ? body.teams.find((team) => team.teamId === data.teamId && (owner === "" || team.captainSessionId === owner)) : void 0;
175
+ if (found !== void 0) {
176
+ if (!cancelled) setSnapshot(found);
177
+ return;
178
+ }
179
+ } catch {}
180
+ };
181
+ tick();
182
+ const timer = setInterval(() => {
183
+ tick();
184
+ }, 1500);
185
+ return () => {
186
+ cancelled = true;
187
+ clearInterval(timer);
188
+ };
189
+ }, [data.teamId, owner]);
190
+ const resolved = (0, react.useMemo)(() => ({
191
+ ...data,
192
+ captainSessionId: snapshot?.captainSessionId ?? owner,
193
+ teamName: snapshot?.name ?? data.teamName,
194
+ members: snapshot?.members.map((member) => ({
195
+ id: member.id,
196
+ name: member.name,
197
+ role: member.role
198
+ })) ?? data.members
199
+ }), [
200
+ data,
201
+ owner,
202
+ snapshot
203
+ ]);
204
+ return (0, react_jsx_runtime.jsxs)("section", {
205
+ className: AgentTeamsCard_module_css_default.root,
206
+ "data-agent-teams-card": true,
207
+ "data-team-id": resolved.teamId,
208
+ children: [(0, react_jsx_runtime.jsxs)("header", {
209
+ className: AgentTeamsCard_module_css_default.head,
210
+ children: [
211
+ (0, react_jsx_runtime.jsx)("img", {
212
+ className: AgentTeamsCard_module_css_default.leadAvatar,
213
+ src: LEAD_ART,
214
+ alt: "",
215
+ "aria-hidden": true
216
+ }),
217
+ (0, react_jsx_runtime.jsx)("span", {
218
+ className: AgentTeamsCard_module_css_default.teamName,
219
+ title: resolved.teamName,
220
+ children: resolved.teamName
221
+ }),
222
+ (0, react_jsx_runtime.jsxs)("span", {
223
+ className: AgentTeamsCard_module_css_default.memberCount,
224
+ children: [resolved.members.length, " 名成员"]
225
+ }),
226
+ (0, react_jsx_runtime.jsx)("button", {
227
+ type: "button",
228
+ className: AgentTeamsCard_module_css_default.panelButton,
229
+ onClick: () => {
230
+ openActivityPanel(resolved);
231
+ },
232
+ "aria-label": "打开活动面板",
233
+ title: "打开活动面板",
234
+ children: "活动面板"
235
+ })
236
+ ]
237
+ }), resolved.members.length > 0 && (0, react_jsx_runtime.jsx)("div", {
238
+ className: AgentTeamsCard_module_css_default.members,
239
+ children: resolved.members.map((member) => (0, react_jsx_runtime.jsxs)("button", {
240
+ type: "button",
241
+ className: AgentTeamsCard_module_css_default.member,
242
+ onClick: () => {
243
+ if (member.id !== "") openSession(member.id);
244
+ },
245
+ title: member.role === "" ? member.name : `${member.name} · ${member.role}`,
246
+ children: [memberArtUrl(member.name, member.role) !== null ? (0, react_jsx_runtime.jsx)("img", {
247
+ className: AgentTeamsCard_module_css_default.memberArt,
248
+ src: memberArtUrl(member.name, member.role) ?? "",
249
+ alt: "",
250
+ "aria-hidden": true
251
+ }) : (0, react_jsx_runtime.jsx)("span", {
252
+ className: AgentTeamsCard_module_css_default.memberInitial,
253
+ children: member.name.trim().slice(0, 1).toUpperCase() || "?"
254
+ }), (0, react_jsx_runtime.jsx)("span", {
255
+ className: AgentTeamsCard_module_css_default.memberName,
256
+ children: member.name
257
+ })]
258
+ }, member.id))
259
+ })]
260
+ });
261
+ }
262
+ //#endregion
263
+ //#region \0dsh-css:/Users/nanmi/workspace/myself_code/dsh-agent-teams/src/client/ActivityPanel.module.css.mjs
264
+ const css = "html{--agent-teams-panel-width:388px;--agent-teams-panel-right:calc(18px + var(--dsh-sidebar-width,0px));--agent-teams-panel-gap:14px;--agent-teams-panel-shift:calc(var(--agent-teams-panel-width) + 18px + var(--agent-teams-panel-gap))}html[data-agent-teams-panel-open] [data-phase=active]{box-sizing:border-box;padding-right:var(--agent-teams-panel-shift)}[data-phase=active]{will-change:padding-right;transition:padding-right .36s cubic-bezier(.22,1,.36,1)}.qZToFW_badge{top:64px;right:var(--agent-teams-panel-right);z-index:2147483000;box-sizing:border-box;border:1px solid var(--dsw-alias-line-normal);background:color-mix(in srgb, var(--dsw-alias-bg-module-platform) 92%, transparent);backdrop-filter:blur(16px);height:34px;box-shadow:0 8px 28px color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:999px;align-items:center;gap:7px;padding:0 12px;font-size:12px;font-weight:600;line-height:20px;transition:border-color .15s,transform .12s;display:inline-flex;position:fixed}.qZToFW_badge:hover{border-color:var(--dsw-alias-line-strong);transform:translateY(-1px)}.qZToFW_badge:active{transform:translateY(0)scale(.98)}.qZToFW_badge:focus-visible,.qZToFW_closeButton:focus-visible,.qZToFW_memberRow:focus-visible,.qZToFW_taskNode:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.qZToFW_badgeDot,.qZToFW_panelDot{background:var(--dsw-alias-label-tertiary);border-radius:50%;width:7px;height:7px}.qZToFW_badgeDot[data-busy=true],.qZToFW_panelDot[data-busy=true]{background:var(--dsw-alias-state-business-primary);animation:1.25s ease-in-out infinite qZToFW_agentTeamsPulse}.qZToFW_badgeCount,.qZToFW_memberCount,.qZToFW_teamStats,.qZToFW_stageLabel,.qZToFW_taskId{font-variant-numeric:tabular-nums}.qZToFW_panel{top:64px;right:var(--agent-teams-panel-right);z-index:2147483000;width:min(var(--agent-teams-panel-width), calc(100vw - 24px));box-sizing:border-box;border:1px solid color-mix(in srgb, var(--dsw-alias-line-strong) 58%, transparent);background:color-mix(in srgb, var(--dsw-alias-bg-module-platform) 95%, transparent);backdrop-filter:blur(20px)saturate(1.08);max-height:70dvh;box-shadow:0 12px 32px color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent), 0 32px 72px color-mix(in srgb, var(--dsw-alias-label-primary) 16%, transparent);border-radius:16px;flex-direction:column;animation:.18s ease-out qZToFW_agentTeamsPanelIn;display:flex;position:fixed;overflow:hidden}@keyframes qZToFW_agentTeamsPanelIn{0%{opacity:0;transform:translateY(-6px)scale(.99)}to{opacity:1;transform:translateY(0)scale(1)}}@keyframes qZToFW_agentTeamsPulse{0%,to{opacity:.42}50%{opacity:1}}.qZToFW_panelHead{border-bottom:1px solid var(--dsw-alias-line-normal);flex:none;justify-content:space-between;align-items:center;min-height:44px;padding:0 14px 0 16px;display:flex}.qZToFW_panelTitle{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:14px;font-weight:600;line-height:20px;display:inline-flex}.qZToFW_closeButton{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:7px;justify-content:center;align-items:center;padding:0;transition:background-color .12s,color .12s,transform .12s;display:inline-flex}.qZToFW_closeButton:hover{background:var(--dsw-alias-bg-fill-neutral);color:var(--dsw-alias-label-primary)}.qZToFW_closeButton:active{transform:scale(.94)}.qZToFW_teams{overscroll-behavior:contain;flex-direction:column;min-height:0;display:flex;overflow-y:auto}.qZToFW_team{border-bottom:1px solid var(--dsw-alias-line-normal);flex-direction:column;gap:12px;padding:12px 14px 16px;display:flex}.qZToFW_team:last-child{border-bottom:0}.qZToFW_teamHead{align-items:center;gap:10px;min-width:0;display:flex}.qZToFW_teamName{min-width:0;color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:13px;font-weight:600;line-height:18px;overflow:hidden}.qZToFW_teamStats{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;gap:8px;font-size:10.5px;line-height:16px;display:inline-flex}.qZToFW_sectionHead{justify-content:space-between;align-items:center;gap:8px;min-width:0;display:flex}.qZToFW_sectionTitle{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11px;font-weight:600;line-height:16px;display:inline-flex}.qZToFW_sectionHint{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:14px;overflow:hidden}.qZToFW_delegationSection{min-width:0}.qZToFW_captainNode{box-sizing:border-box;border:1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary) 32%, var(--dsw-alias-line-normal));background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 7%, var(--dsw-alias-bg-module));border-radius:10px;grid-template-columns:38px minmax(0,1fr) auto;align-items:center;gap:9px;min-height:48px;padding:8px 10px;display:grid}.qZToFW_captainAvatar,.qZToFW_memberAvatar{flex:none;justify-content:center;align-items:center;display:inline-flex;position:relative}.qZToFW_captainAvatar{width:36px;height:36px}.qZToFW_leadAvatar,.qZToFW_memberArt,.qZToFW_memberInitial{box-sizing:border-box;border:1px solid var(--dsw-alias-line-strong);object-fit:cover;background:#0b1d33;border-radius:50%;width:34px;height:34px}.qZToFW_captainInfo,.qZToFW_memberInfo{flex-direction:column;min-width:0;display:flex}.qZToFW_captainInfo{gap:2px}.qZToFW_captainLine,.qZToFW_memberLine{align-items:center;gap:6px;min-width:0;display:flex}.qZToFW_captainName,.qZToFW_memberName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;font-weight:600;line-height:18px;overflow:hidden}.qZToFW_captainRole,.qZToFW_memberRole{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:14px;overflow:hidden}.qZToFW_captainSummary,.qZToFW_memberStatusLine{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:10.5px;line-height:15px;overflow:hidden}.qZToFW_captainState,.qZToFW_memberState{color:var(--dsw-alias-label-tertiary);white-space:nowrap;flex:none;align-items:center;gap:5px;font-size:10px;font-weight:500;line-height:15px;display:inline-flex}.qZToFW_captainState[data-busy=true],.qZToFW_memberState[data-activity=working]{color:var(--dsw-alias-state-business-primary)}.qZToFW_delegationTree{flex-direction:column;gap:2px;margin-left:18px;padding:9px 0 0 20px;display:flex;position:relative}.qZToFW_delegationTree:before{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 48%, var(--dsw-alias-line-normal));content:\"\";width:1px;position:absolute;top:0;bottom:22px;left:0}.qZToFW_memberBlock{flex-direction:column;min-width:0;padding:3px 0 7px;display:flex;position:relative}.qZToFW_memberBranch{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 48%, var(--dsw-alias-line-normal));width:20px;height:1px;display:block;position:absolute;top:23px;right:100%}.qZToFW_memberBranch:before{background:var(--dsw-alias-state-business-primary);content:\"\";border-radius:50%;width:5px;height:5px;position:absolute;top:-2px;right:-1px}.qZToFW_memberRow{box-sizing:border-box;width:100%;min-width:0;min-height:44px;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:8px;grid-template-columns:38px minmax(0,1fr) auto;align-items:center;gap:8px;padding:4px 6px;transition:background-color .12s,transform .12s;display:grid}.qZToFW_memberRow:hover,.qZToFW_memberRow[data-activity=working]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 6%, var(--dsw-alias-bg-module))}.qZToFW_memberRow:active{transform:scale(.995)}.qZToFW_memberAvatar{width:34px;height:34px}.qZToFW_memberAvatar[data-unread=true]:after{border:1px solid var(--dsw-alias-state-business-primary);content:\"\";border-radius:50%;animation:1.5s ease-out infinite qZToFW_agentTeamsRing;position:absolute;inset:-3px}@keyframes qZToFW_agentTeamsRing{0%{opacity:.82;transform:scale(.94)}75%,to{opacity:0;transform:scale(1.18)}}.qZToFW_memberInitial{color:var(--dsw-alias-label-on-fill);justify-content:center;align-items:center;font-size:14px;font-weight:600;line-height:20px;display:inline-flex}.qZToFW_stateArt{box-sizing:border-box;border:2px solid var(--dsw-alias-bg-module-platform);object-fit:cover;background:#0b1d33;border-radius:50%;width:19px;height:19px;position:absolute;bottom:-4px;right:-4px}.qZToFW_stateArt[data-activity=working]{animation:2.4s ease-in-out infinite qZToFW_agentTeamsFloat}.qZToFW_stateArt[data-activity=idle]{animation:4.2s ease-in-out infinite qZToFW_agentTeamsBreathe}.qZToFW_stateArt[data-activity=unknown]{animation:2.8s ease-in-out infinite qZToFW_agentTeamsThink}@keyframes qZToFW_agentTeamsFloat{0%,to{transform:translateY(0)rotate(-4deg)}50%{transform:translateY(-2px)rotate(4deg)}}@keyframes qZToFW_agentTeamsBreathe{0%,to{opacity:.82;transform:scale(1)}50%{opacity:1;transform:scale(1.06)}}@keyframes qZToFW_agentTeamsThink{0%,to{transform:rotate(-7deg)}50%{transform:rotate(7deg)}}.qZToFW_memberState{margin-left:auto}.qZToFW_memberCount{color:var(--dsw-alias-label-tertiary);font-size:10.5px;line-height:16px}.qZToFW_assignmentLine{align-items:center;gap:7px;min-width:0;padding:0 6px 0 52px;display:flex}.qZToFW_assignmentLabel{color:var(--dsw-alias-label-tertiary);flex:none;font-size:9.5px;line-height:14px}.qZToFW_assignmentTasks{flex-wrap:wrap;flex:1;gap:4px;min-width:0;display:flex}.qZToFW_assignmentChip{background:var(--dsw-alias-bg-fill-neutral);min-height:16px;color:var(--dsw-alias-label-secondary);border-radius:4px;align-items:center;padding:0 5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;font-weight:600;line-height:14px;display:inline-flex}.qZToFW_assignmentChip[data-state=running]{background:var(--dsw-alias-bg-fill-business);color:var(--dsw-alias-label-on-fill)}.qZToFW_assignmentChip[data-state=completed]{background:var(--dsw-alias-bg-fill-success);color:var(--dsw-alias-label-on-fill)}.qZToFW_assignmentChip[data-state=blocked]{background:var(--dsw-alias-bg-fill-warning);color:var(--dsw-alias-label-on-fill)}.qZToFW_assignmentChip[data-state=failed]{background:var(--dsw-alias-bg-fill-danger);color:var(--dsw-alias-label-on-fill)}.qZToFW_assignmentChip[data-state=cancelled]{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}.qZToFW_unreadPill{color:var(--dsw-alias-state-business-primary);white-space:nowrap;flex:none;font-size:9.5px;font-weight:600;line-height:14px}.qZToFW_taskEmpty{color:var(--dsw-alias-label-tertiary);font-size:9.5px;line-height:14px}.qZToFW_dependencySection{border-top:1px solid var(--dsw-alias-line-normal);flex-direction:column;gap:7px;min-width:0;padding-top:10px;display:flex}.qZToFW_stageFlow{scrollbar-width:thin;align-items:stretch;gap:0;min-width:0;padding:1px 1px 5px;display:flex;overflow-x:auto}.qZToFW_stageGroup{flex:1 0 126px;min-width:126px;display:flex;position:relative}.qZToFW_stageConnector{width:22px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;align-items:center;margin-top:0;display:flex}.qZToFW_stageLine{background:var(--dsw-alias-line-strong);flex:1;height:1px;display:block}.qZToFW_stageColumn{flex-direction:column;flex:1;gap:5px;min-width:0;display:flex}.qZToFW_stageLabel{color:var(--dsw-alias-label-tertiary);justify-content:space-between;align-items:center;gap:6px;padding:0 2px;font-size:9.5px;font-weight:600;line-height:14px;display:flex}.qZToFW_stageLabel span{background:var(--dsw-alias-bg-fill-neutral);border-radius:4px;justify-content:center;align-items:center;min-width:14px;height:14px;font-size:8.5px;display:inline-flex}.qZToFW_stageTasks{flex-direction:column;gap:5px;display:flex}.qZToFW_taskNode{box-sizing:border-box;border:1px solid var(--dsw-alias-line-normal);background:var(--dsw-alias-bg-module);min-width:0;min-height:72px;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;cursor:pointer;border-radius:8px;flex-direction:column;gap:4px;padding:7px 8px;transition:border-color .14s,opacity .14s,transform .12s,background-color .14s;display:flex}.qZToFW_taskNode:hover,.qZToFW_taskNode[data-focused=true]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 6%, var(--dsw-alias-bg-module));transform:translateY(-1px)}.qZToFW_taskNode[data-dimmed=true]{opacity:.34}.qZToFW_taskNode[data-state=completed]{border-color:color-mix(in srgb, var(--dsw-alias-state-success) 48%, var(--dsw-alias-line-normal))}.qZToFW_taskNode[data-state=blocked]{border-color:color-mix(in srgb, var(--dsw-alias-state-warning) 52%, var(--dsw-alias-line-normal))}.qZToFW_taskNode[data-state=failed]{border-color:color-mix(in srgb, var(--dsw-alias-state-danger) 56%, var(--dsw-alias-line-normal))}.qZToFW_taskNodeHead,.qZToFW_taskRoute{justify-content:space-between;align-items:center;gap:5px;min-width:0;display:flex}.qZToFW_taskId{color:var(--dsw-alias-label-tertiary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9.5px;font-weight:700}.qZToFW_taskBadge{background:var(--dsw-alias-bg-fill-neutral);min-height:14px;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;align-items:center;padding:0 4px;font-size:8.5px;font-weight:600;line-height:13px;display:inline-flex}.qZToFW_taskBadge[data-state=running]{background:var(--dsw-alias-bg-fill-business);color:var(--dsw-alias-label-on-fill)}.qZToFW_taskBadge[data-state=completed]{background:var(--dsw-alias-bg-fill-success);color:var(--dsw-alias-label-on-fill)}.qZToFW_taskBadge[data-state=blocked]{background:var(--dsw-alias-bg-fill-warning);color:var(--dsw-alias-label-on-fill)}.qZToFW_taskBadge[data-state=failed]{background:var(--dsw-alias-bg-fill-danger);color:var(--dsw-alias-label-on-fill)}.qZToFW_taskBadge[data-state=cancelled]{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}.qZToFW_taskSubject{min-height:30px;color:var(--dsw-alias-label-primary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:10.5px;font-weight:500;line-height:15px;display:-webkit-box;overflow:hidden}.qZToFW_taskRoute{color:var(--dsw-alias-label-tertiary);margin-top:auto;font-size:8.5px;line-height:13px}.qZToFW_taskOwner,.qZToFW_taskDeps{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.qZToFW_taskOwner{max-width:48%;color:var(--dsw-alias-label-secondary);font-weight:600}.qZToFW_taskDeps{text-align:right;flex:1}.qZToFW_taskStart{color:var(--dsw-alias-label-tertiary)}.qZToFW_unclaimed,.qZToFW_inbox{border-top:1px solid var(--dsw-alias-line-normal);flex-direction:column;gap:5px;min-width:0;padding-top:10px;display:flex}.qZToFW_unclaimedTitle{color:var(--dsw-alias-label-secondary);font-size:10.5px;font-weight:600;line-height:15px}.qZToFW_inboxRow{border-radius:6px;grid-template-columns:112px minmax(0,1fr);align-items:center;gap:8px;min-width:0;min-height:24px;padding:2px 5px;display:grid}.qZToFW_inboxRow:hover{background:var(--dsw-alias-bg-module)}.qZToFW_inboxRoute{min-width:0;color:var(--dsw-alias-state-business-primary);text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:3px;font-size:9.5px;font-weight:600;line-height:14px;display:inline-flex;overflow:hidden}.qZToFW_inboxContent{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:10px;line-height:14px;overflow:hidden}.qZToFW_emptyHint{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:11px;line-height:16px}.qZToFW_team[data-historic],.qZToFW_archivedWrap{opacity:.82}.qZToFW_historicPill{background:var(--dsw-alias-bg-fill-neutral);color:var(--dsw-alias-label-tertiary);border-radius:4px;flex:none;margin-left:auto;padding:1px 7px;font-size:9.5px;font-weight:600;line-height:15px}.qZToFW_members{flex-direction:column;gap:3px;display:flex}.qZToFW_archivedWrap:before{color:var(--dsw-alias-label-tertiary);content:\"已结束 · 历史归档\";padding:5px 14px 0;font-size:9.5px;font-weight:600;line-height:14px;display:block}@media (prefers-reduced-motion:reduce){[data-phase=active],.qZToFW_panel,.qZToFW_badge,.qZToFW_badgeDot,.qZToFW_panelDot,.qZToFW_stateArt,.qZToFW_memberAvatar[data-unread=true]:after{transition:none;animation:none}}@media (width<=960px){html{--agent-teams-main-shift:0px}html[data-agent-teams-panel-open] [data-phase=active]{padding-right:0}}@media (width<=640px){html{--agent-teams-panel-right:calc(10px + var(--dsh-sidebar-width,0px))}.qZToFW_panel{width:auto;max-height:calc(100dvh - 68px);top:56px;left:10px}.qZToFW_badge{top:56px}.qZToFW_teamStats span[data-stat=messages]{display:none}.qZToFW_captainNode{grid-template-columns:38px minmax(0,1fr)}.qZToFW_captainState{display:none}.qZToFW_delegationTree{margin-left:12px;padding-left:15px}.qZToFW_memberBranch{width:15px}.qZToFW_assignmentLine{padding-left:45px}}";
265
+ const tagId = "dsh-agent-teams/ActivityPanel.module.css";
266
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
267
+ const tag = document.createElement("style");
268
+ tag.dataset.plugin = "dsh-agent-teams";
269
+ tag.dataset.pluginCss = tagId;
270
+ tag.textContent = css;
271
+ document.head.appendChild(tag);
272
+ }
273
+ var ActivityPanel_module_css_default = {
274
+ "inbox": "qZToFW_inbox",
275
+ "unclaimedTitle": "qZToFW_unclaimedTitle",
276
+ "panelHead": "qZToFW_panelHead",
277
+ "panelDot": "qZToFW_panelDot",
278
+ "agentTeamsBreathe": "qZToFW_agentTeamsBreathe",
279
+ "team": "qZToFW_team",
280
+ "assignmentTasks": "qZToFW_assignmentTasks",
281
+ "archivedWrap": "qZToFW_archivedWrap",
282
+ "stageGroup": "qZToFW_stageGroup",
283
+ "memberAvatar": "qZToFW_memberAvatar",
284
+ "dependencySection": "qZToFW_dependencySection",
285
+ "sectionHint": "qZToFW_sectionHint",
286
+ "memberState": "qZToFW_memberState",
287
+ "taskNodeHead": "qZToFW_taskNodeHead",
288
+ "inboxContent": "qZToFW_inboxContent",
289
+ "captainRole": "qZToFW_captainRole",
290
+ "taskId": "qZToFW_taskId",
291
+ "captainSummary": "qZToFW_captainSummary",
292
+ "badgeCount": "qZToFW_badgeCount",
293
+ "taskSubject": "qZToFW_taskSubject",
294
+ "captainState": "qZToFW_captainState",
295
+ "teamStats": "qZToFW_teamStats",
296
+ "taskRoute": "qZToFW_taskRoute",
297
+ "sectionTitle": "qZToFW_sectionTitle",
298
+ "taskEmpty": "qZToFW_taskEmpty",
299
+ "agentTeamsRing": "qZToFW_agentTeamsRing",
300
+ "stageLine": "qZToFW_stageLine",
301
+ "assignmentLine": "qZToFW_assignmentLine",
302
+ "inboxRow": "qZToFW_inboxRow",
303
+ "emptyHint": "qZToFW_emptyHint",
304
+ "members": "qZToFW_members",
305
+ "captainAvatar": "qZToFW_captainAvatar",
306
+ "stateArt": "qZToFW_stateArt",
307
+ "agentTeamsThink": "qZToFW_agentTeamsThink",
308
+ "memberRow": "qZToFW_memberRow",
309
+ "memberCount": "qZToFW_memberCount",
310
+ "agentTeamsPanelIn": "qZToFW_agentTeamsPanelIn",
311
+ "captainNode": "qZToFW_captainNode",
312
+ "delegationTree": "qZToFW_delegationTree",
313
+ "taskOwner": "qZToFW_taskOwner",
314
+ "memberArt": "qZToFW_memberArt",
315
+ "stageColumn": "qZToFW_stageColumn",
316
+ "taskDeps": "qZToFW_taskDeps",
317
+ "assignmentChip": "qZToFW_assignmentChip",
318
+ "panelTitle": "qZToFW_panelTitle",
319
+ "captainLine": "qZToFW_captainLine",
320
+ "stageTasks": "qZToFW_stageTasks",
321
+ "delegationSection": "qZToFW_delegationSection",
322
+ "teams": "qZToFW_teams",
323
+ "taskStart": "qZToFW_taskStart",
324
+ "badgeDot": "qZToFW_badgeDot",
325
+ "panel": "qZToFW_panel",
326
+ "teamHead": "qZToFW_teamHead",
327
+ "memberBlock": "qZToFW_memberBlock",
328
+ "leadAvatar": "qZToFW_leadAvatar",
329
+ "agentTeamsFloat": "qZToFW_agentTeamsFloat",
330
+ "memberInfo": "qZToFW_memberInfo",
331
+ "memberStatusLine": "qZToFW_memberStatusLine",
332
+ "captainName": "qZToFW_captainName",
333
+ "historicPill": "qZToFW_historicPill",
334
+ "memberLine": "qZToFW_memberLine",
335
+ "stageFlow": "qZToFW_stageFlow",
336
+ "unreadPill": "qZToFW_unreadPill",
337
+ "stageLabel": "qZToFW_stageLabel",
338
+ "inboxRoute": "qZToFW_inboxRoute",
339
+ "stageConnector": "qZToFW_stageConnector",
340
+ "sectionHead": "qZToFW_sectionHead",
341
+ "badge": "qZToFW_badge",
342
+ "teamName": "qZToFW_teamName",
343
+ "memberInitial": "qZToFW_memberInitial",
344
+ "taskNode": "qZToFW_taskNode",
345
+ "closeButton": "qZToFW_closeButton",
346
+ "unclaimed": "qZToFW_unclaimed",
347
+ "memberName": "qZToFW_memberName",
348
+ "captainInfo": "qZToFW_captainInfo",
349
+ "taskBadge": "qZToFW_taskBadge",
350
+ "assignmentLabel": "qZToFW_assignmentLabel",
351
+ "agentTeamsPulse": "qZToFW_agentTeamsPulse",
352
+ "memberRole": "qZToFW_memberRole",
353
+ "memberBranch": "qZToFW_memberBranch"
354
+ };
355
+ //#endregion
356
+ //#region lib/client/ActivityPanel.js
357
+ /**
358
+ * AgentTeams activity panel: the top-right floater monitoring every team.
359
+ *
360
+ * Modeled on the Claude Code desktop SessionActivityPanel: a fixed glass
361
+ * panel at the top-right corner. On wide viewports it cooperatively makes the
362
+ * conversation column yield space; narrow viewports keep overlay mode. It
363
+ * polls the host `/plugins/dsh-agent-teams/state` route for
364
+ * server-side snapshots (durable files + live subagent activity), with a
365
+ * collapsed badge that auto-expands once when activity appears. Archived
366
+ * teams stay available for the owning conversation after live work ends.
367
+ *
368
+ * The floater mounts through a body portal (no top-right slot exists in the
369
+ * web shell); it is not a conversation node — the in-conversation panel was
370
+ * removed in favor of this always-available monitor.
371
+ * @module dsh-agent-teams/client/activity
372
+ */
373
+ /** Poll cadence for the host snapshot route. */
374
+ const POLL_MS = 1e3;
375
+ /** Grace before the panel collapses once no team remains. */
376
+ const AUTOCLOSE_GRACE_MS = 2e3;
377
+ /**
378
+ * Page-settle window after mount: activity restored on page load only shows
379
+ * the collapsed badge, so the panel never yanks the conversation column
380
+ * right after load. New activity after this window auto-expands as usual.
381
+ */
382
+ const AUTO_OPEN_SETTLE_MS = 4e3;
383
+ /** Host route serving team snapshots. */
384
+ const STATE_URL = "/plugins/dsh-agent-teams/state";
385
+ /** Root marker shared with the panel CSS while the portal is expanded. */
386
+ const PANEL_OPEN_ATTRIBUTE = "data-agent-teams-panel-open";
387
+ /** Initial-letter fallback for unmatched roles. */
388
+ function memberInitial(name) {
389
+ return name.trim().slice(0, 1).toUpperCase() || "?";
390
+ }
391
+ function stableHash(value) {
392
+ let hash = 0;
393
+ for (let index = 0; index < value.length; index += 1) hash = (hash << 5) - hash + value.charCodeAt(index) | 0;
394
+ return Math.abs(hash);
395
+ }
396
+ const ACCENTS = [
397
+ "var(--dsw-alias-state-business-primary)",
398
+ "var(--dsw-alias-state-success)",
399
+ "var(--dsw-alias-state-danger)",
400
+ "var(--dsw-alias-state-warning)",
401
+ "var(--dsw-alias-label-tertiary)"
402
+ ];
403
+ function accentOf(id) {
404
+ return ACCENTS[stableHash(id) % ACCENTS.length] ?? ACCENTS[0];
405
+ }
406
+ /** Badge text follows the raw task status (finer than the 4 visual states):
407
+ * claimed/pending/failed/cancelled keep their own labels and colors. */
408
+ const TASK_STATUS_LABEL = {
409
+ pending: "待领取",
410
+ claimed: "已认领",
411
+ in_progress: "进行中",
412
+ completed: "已完成",
413
+ failed: "失败",
414
+ cancelled: "已取消"
415
+ };
416
+ function taskStatusLabel(status) {
417
+ return TASK_STATUS_LABEL[status] ?? status;
418
+ }
419
+ /** Badge/bar coloring key: visual state, widened for terminal statuses. */
420
+ function taskTone(state, status) {
421
+ if (status === "failed") return "failed";
422
+ if (status === "cancelled") return "cancelled";
423
+ return state;
424
+ }
425
+ /** Collapsed badge: an always-visible corner pill while any team exists. */
426
+ function CollapsedBadge({ count, busy, onClick }) {
427
+ return (0, react_jsx_runtime.jsxs)("button", {
428
+ type: "button",
429
+ className: ActivityPanel_module_css_default.badge,
430
+ "data-busy": busy,
431
+ onClick,
432
+ "aria-label": `AgentTeams 活动,${count} 个团队`,
433
+ children: [(0, react_jsx_runtime.jsx)("span", {
434
+ className: ActivityPanel_module_css_default.badgeDot,
435
+ "data-busy": busy,
436
+ "aria-hidden": true
437
+ }), (0, react_jsx_runtime.jsx)("span", {
438
+ className: ActivityPanel_module_css_default.badgeCount,
439
+ children: count
440
+ })]
441
+ });
442
+ }
443
+ function memberDotState(member, tasks) {
444
+ const owned = tasks.filter((task) => task.assignee === member.name);
445
+ if (member.activity === "working") return "ongoing";
446
+ if (owned.some((task) => task.status === "failed")) return "error";
447
+ if (owned.length > 0 && owned.every((task) => task.status === "completed")) return "done";
448
+ return "warning";
449
+ }
450
+ function memberStateLabel(member, tasks) {
451
+ const owned = tasks.filter((task) => task.assignee === member.name);
452
+ if (member.activity === "working") return "工作中";
453
+ if (owned.some((task) => task.status === "failed")) return "有失败";
454
+ if (owned.some((task) => task.state === "blocked")) return "等待";
455
+ if (owned.length > 0 && owned.every((task) => task.status === "completed")) return "已交付";
456
+ if (owned.length > 0) return "待执行";
457
+ return "待派工";
458
+ }
459
+ function memberStatusText(member, tasks) {
460
+ const owned = tasks.filter((task) => task.assignee === member.name);
461
+ const current = owned.find((task) => task.id === member.currentTask);
462
+ const blocked = owned.find((task) => task.state === "blocked");
463
+ if (member.activity === "working" && current !== void 0) return `正在执行 ${current.id}`;
464
+ if (member.activity === "working") return "正在处理已派任务";
465
+ if (blocked !== void 0) {
466
+ const dependency = tasks.find((task) => blocked.dependencies.includes(task.id) && task.state !== "completed");
467
+ if (dependency !== void 0) return `等待 ${dependency.id} · ${dependency.assignee || "待认领"}`;
468
+ return "等待前置任务";
469
+ }
470
+ if (member.total === 0) return "等待队长派工";
471
+ if (member.done === member.total) return "任务已交付";
472
+ return member.activity === "idle" ? "待继续执行" : "状态未知";
473
+ }
474
+ function dependencyLabel(task, tasks) {
475
+ return task.dependencies.map((id) => {
476
+ const dependency = tasks.find((candidate) => candidate.id === id);
477
+ return dependency?.assignee ? `${id}·${dependency.assignee}` : id;
478
+ }).join("、");
479
+ }
480
+ function TaskNode({ task, tasks, focused, dimmed, pinned, onPin, onPreview }) {
481
+ const tone = taskTone(task.state, task.status);
482
+ return (0, react_jsx_runtime.jsxs)("button", {
483
+ type: "button",
484
+ className: ActivityPanel_module_css_default.taskNode,
485
+ "data-task-id": task.id,
486
+ "data-state": tone,
487
+ "data-focused": focused,
488
+ "data-dimmed": dimmed,
489
+ "aria-pressed": pinned,
490
+ title: `${task.id} · ${task.subject}(点击固定依赖链)`,
491
+ onClick: () => {
492
+ onPin(task.id);
493
+ },
494
+ onMouseEnter: () => {
495
+ onPreview(task.id);
496
+ },
497
+ onMouseLeave: () => {
498
+ onPreview(null);
499
+ },
500
+ onFocus: () => {
501
+ onPreview(task.id);
502
+ },
503
+ onBlur: () => {
504
+ onPreview(null);
505
+ },
506
+ children: [
507
+ (0, react_jsx_runtime.jsxs)("span", {
508
+ className: ActivityPanel_module_css_default.taskNodeHead,
509
+ children: [(0, react_jsx_runtime.jsx)("span", {
510
+ className: ActivityPanel_module_css_default.taskId,
511
+ children: task.id
512
+ }), (0, react_jsx_runtime.jsx)("span", {
513
+ className: ActivityPanel_module_css_default.taskBadge,
514
+ "data-state": tone,
515
+ children: taskStatusLabel(task.status)
516
+ })]
517
+ }),
518
+ (0, react_jsx_runtime.jsx)("span", {
519
+ className: ActivityPanel_module_css_default.taskSubject,
520
+ children: task.subject
521
+ }),
522
+ (0, react_jsx_runtime.jsxs)("span", {
523
+ className: ActivityPanel_module_css_default.taskRoute,
524
+ children: [(0, react_jsx_runtime.jsx)("span", {
525
+ className: ActivityPanel_module_css_default.taskOwner,
526
+ children: task.assignee || "待认领"
527
+ }), task.dependencies.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
528
+ className: ActivityPanel_module_css_default.taskStart,
529
+ children: "起点"
530
+ }) : (0, react_jsx_runtime.jsxs)("span", {
531
+ className: ActivityPanel_module_css_default.taskDeps,
532
+ children: ["依赖 ", dependencyLabel(task, tasks)]
533
+ })]
534
+ })
535
+ ]
536
+ });
537
+ }
538
+ function DependencyMap({ tasks }) {
539
+ const [previewTaskId, setPreviewTaskId] = (0, react.useState)(null);
540
+ const [pinnedTaskId, setPinnedTaskId] = (0, react.useState)(null);
541
+ const focusedTaskId = pinnedTaskId ?? previewTaskId;
542
+ const stages = (0, react.useMemo)(() => taskStages(tasks), [tasks]);
543
+ const related = (0, react.useMemo)(() => focusedTaskId === null ? null : relatedTaskIds(focusedTaskId, tasks), [focusedTaskId, tasks]);
544
+ (0, react.useEffect)(() => {
545
+ const onKeyDown = (event) => {
546
+ if (event.key === "Escape") setPinnedTaskId(null);
547
+ };
548
+ window.addEventListener("keydown", onKeyDown);
549
+ return () => {
550
+ window.removeEventListener("keydown", onKeyDown);
551
+ };
552
+ }, []);
553
+ if (tasks.length === 0) return null;
554
+ return (0, react_jsx_runtime.jsxs)("section", {
555
+ className: ActivityPanel_module_css_default.dependencySection,
556
+ "aria-label": "任务依赖链",
557
+ "data-dependency-map": true,
558
+ children: [(0, react_jsx_runtime.jsxs)("header", {
559
+ className: ActivityPanel_module_css_default.sectionHead,
560
+ children: [(0, react_jsx_runtime.jsxs)("span", {
561
+ className: ActivityPanel_module_css_default.sectionTitle,
562
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {}), " 任务依赖"]
563
+ }), (0, react_jsx_runtime.jsx)("span", {
564
+ className: ActivityPanel_module_css_default.sectionHint,
565
+ children: pinnedTaskId === null ? "悬停预览 · 点击固定" : `${pinnedTaskId} 已固定 · Esc 取消`
566
+ })]
567
+ }), (0, react_jsx_runtime.jsx)("div", {
568
+ className: ActivityPanel_module_css_default.stageFlow,
569
+ children: stages.map((stage, index) => (0, react_jsx_runtime.jsxs)("div", {
570
+ className: ActivityPanel_module_css_default.stageGroup,
571
+ "data-depth": stage.depth,
572
+ children: [index > 0 && (0, react_jsx_runtime.jsxs)("span", {
573
+ className: ActivityPanel_module_css_default.stageConnector,
574
+ "aria-hidden": true,
575
+ children: [(0, react_jsx_runtime.jsx)("span", { className: ActivityPanel_module_css_default.stageLine }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})]
576
+ }), (0, react_jsx_runtime.jsxs)("div", {
577
+ className: ActivityPanel_module_css_default.stageColumn,
578
+ children: [(0, react_jsx_runtime.jsxs)("span", {
579
+ className: ActivityPanel_module_css_default.stageLabel,
580
+ children: [stage.depth === 0 ? "起点" : `依赖层 ${stage.depth}`, (0, react_jsx_runtime.jsx)("span", { children: stage.tasks.length })]
581
+ }), (0, react_jsx_runtime.jsx)("div", {
582
+ className: ActivityPanel_module_css_default.stageTasks,
583
+ children: stage.tasks.map((task) => (0, react_jsx_runtime.jsx)(TaskNode, {
584
+ task,
585
+ tasks,
586
+ focused: related?.has(task.id) ?? false,
587
+ dimmed: related !== null && !related.has(task.id),
588
+ pinned: pinnedTaskId === task.id,
589
+ onPin: (id) => {
590
+ setPinnedTaskId((current) => current === id ? null : id);
591
+ },
592
+ onPreview: setPreviewTaskId
593
+ }, task.id))
594
+ })]
595
+ })]
596
+ }, stage.depth))
597
+ })]
598
+ });
599
+ }
600
+ function TeamSection({ team, onNavigate, historic = false }) {
601
+ const busyCount = team.members.filter((member) => member.activity === "working").length;
602
+ const assignedCount = team.tasks.filter((task) => task.assignee !== "").length;
603
+ const completedCount = team.tasks.filter((task) => task.status === "completed").length;
604
+ const allCompleted = team.tasks.length > 0 && completedCount === team.tasks.length;
605
+ const unclaimed = team.tasks.filter((task) => {
606
+ if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") return false;
607
+ if (task.assignee === "") return true;
608
+ return !team.members.some((member) => member.name === task.assignee);
609
+ });
610
+ return (0, react_jsx_runtime.jsxs)("section", {
611
+ className: ActivityPanel_module_css_default.team,
612
+ "data-team-id": team.teamId,
613
+ children: [
614
+ (0, react_jsx_runtime.jsxs)("header", {
615
+ className: ActivityPanel_module_css_default.teamHead,
616
+ children: [
617
+ (0, react_jsx_runtime.jsx)("span", {
618
+ className: ActivityPanel_module_css_default.teamName,
619
+ title: team.name,
620
+ children: team.name
621
+ }),
622
+ historic && (0, react_jsx_runtime.jsx)("span", {
623
+ className: ActivityPanel_module_css_default.historicPill,
624
+ children: "已结束"
625
+ }),
626
+ (0, react_jsx_runtime.jsxs)("span", {
627
+ className: ActivityPanel_module_css_default.teamStats,
628
+ children: [
629
+ (0, react_jsx_runtime.jsxs)("span", {
630
+ "data-stat": "members",
631
+ children: [team.members.length, " 成员"]
632
+ }),
633
+ (0, react_jsx_runtime.jsxs)("span", {
634
+ "data-stat": "tasks",
635
+ children: [
636
+ completedCount,
637
+ "/",
638
+ team.tasks.length,
639
+ " 完成"
640
+ ]
641
+ }),
642
+ (0, react_jsx_runtime.jsxs)("span", {
643
+ "data-stat": "messages",
644
+ children: [team.messageCount, " 消息"]
645
+ })
646
+ ]
647
+ })
648
+ ]
649
+ }),
650
+ (0, react_jsx_runtime.jsxs)("section", {
651
+ className: ActivityPanel_module_css_default.delegationSection,
652
+ "aria-label": "队长派工关系",
653
+ "data-delegation-map": true,
654
+ children: [(0, react_jsx_runtime.jsxs)("div", {
655
+ className: ActivityPanel_module_css_default.captainNode,
656
+ children: [
657
+ (0, react_jsx_runtime.jsx)("span", {
658
+ className: ActivityPanel_module_css_default.captainAvatar,
659
+ children: (0, react_jsx_runtime.jsx)("img", {
660
+ className: ActivityPanel_module_css_default.leadAvatar,
661
+ src: LEAD_ART,
662
+ alt: "",
663
+ "aria-hidden": true
664
+ })
665
+ }),
666
+ (0, react_jsx_runtime.jsxs)("span", {
667
+ className: ActivityPanel_module_css_default.captainInfo,
668
+ children: [(0, react_jsx_runtime.jsxs)("span", {
669
+ className: ActivityPanel_module_css_default.captainLine,
670
+ children: [(0, react_jsx_runtime.jsx)("span", {
671
+ className: ActivityPanel_module_css_default.captainName,
672
+ children: "队长"
673
+ }), (0, react_jsx_runtime.jsx)("span", {
674
+ className: ActivityPanel_module_css_default.captainRole,
675
+ children: "拆解 · 派发 · 汇总"
676
+ })]
677
+ }), (0, react_jsx_runtime.jsxs)("span", {
678
+ className: ActivityPanel_module_css_default.captainSummary,
679
+ children: [
680
+ "已派发 ",
681
+ assignedCount,
682
+ " 项任务给 ",
683
+ team.members.length,
684
+ " 名成员"
685
+ ]
686
+ })]
687
+ }),
688
+ (0, react_jsx_runtime.jsxs)("span", {
689
+ className: ActivityPanel_module_css_default.captainState,
690
+ "data-busy": busyCount > 0,
691
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: busyCount > 0 ? "ongoing" : allCompleted ? "done" : "warning" }), busyCount > 0 ? `${busyCount} 人执行中` : allCompleted ? "已收齐" : "等待回报"]
692
+ })
693
+ ]
694
+ }), (0, react_jsx_runtime.jsxs)("div", {
695
+ className: ActivityPanel_module_css_default.delegationTree,
696
+ children: [team.members.length === 0 && (0, react_jsx_runtime.jsx)("span", {
697
+ className: ActivityPanel_module_css_default.emptyHint,
698
+ children: "暂无成员,等待队长组建团队"
699
+ }), team.members.map((member) => {
700
+ const owned = team.tasks.filter((task) => task.assignee === member.name);
701
+ return (0, react_jsx_runtime.jsxs)("div", {
702
+ className: ActivityPanel_module_css_default.memberBlock,
703
+ "data-activity": member.activity,
704
+ children: [
705
+ (0, react_jsx_runtime.jsx)("span", {
706
+ className: ActivityPanel_module_css_default.memberBranch,
707
+ "aria-hidden": true,
708
+ children: (0, react_jsx_runtime.jsx)("span", {})
709
+ }),
710
+ (0, react_jsx_runtime.jsxs)("button", {
711
+ type: "button",
712
+ className: ActivityPanel_module_css_default.memberRow,
713
+ "data-activity": member.activity,
714
+ onClick: () => {
715
+ if (member.id !== "") onNavigate(member.id);
716
+ },
717
+ children: [
718
+ (0, react_jsx_runtime.jsxs)("span", {
719
+ className: ActivityPanel_module_css_default.memberAvatar,
720
+ "data-unread": member.unread > 0,
721
+ children: [memberArtUrl(member.name, member.role) !== null ? (0, react_jsx_runtime.jsx)("img", {
722
+ className: ActivityPanel_module_css_default.memberArt,
723
+ src: memberArtUrl(member.name, member.role) ?? "",
724
+ alt: "",
725
+ "aria-hidden": true
726
+ }) : (0, react_jsx_runtime.jsx)("span", {
727
+ className: ActivityPanel_module_css_default.memberInitial,
728
+ style: { background: accentOf(member.id) },
729
+ children: memberInitial(member.name)
730
+ }), (0, react_jsx_runtime.jsx)("img", {
731
+ className: ActivityPanel_module_css_default.stateArt,
732
+ "data-activity": member.activity,
733
+ src: ACTION_ART[member.activity],
734
+ alt: "",
735
+ "aria-hidden": true
736
+ })]
737
+ }),
738
+ (0, react_jsx_runtime.jsxs)("span", {
739
+ className: ActivityPanel_module_css_default.memberInfo,
740
+ children: [(0, react_jsx_runtime.jsxs)("span", {
741
+ className: ActivityPanel_module_css_default.memberLine,
742
+ children: [
743
+ (0, react_jsx_runtime.jsx)("span", {
744
+ className: ActivityPanel_module_css_default.memberName,
745
+ children: member.name
746
+ }),
747
+ member.role !== "" && (0, react_jsx_runtime.jsx)("span", {
748
+ className: ActivityPanel_module_css_default.memberRole,
749
+ children: member.role
750
+ }),
751
+ (0, react_jsx_runtime.jsxs)("span", {
752
+ className: ActivityPanel_module_css_default.memberState,
753
+ "data-activity": member.activity,
754
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: memberDotState(member, team.tasks) }), memberStateLabel(member, team.tasks)]
755
+ })
756
+ ]
757
+ }), (0, react_jsx_runtime.jsx)("span", {
758
+ className: ActivityPanel_module_css_default.memberStatusLine,
759
+ children: memberStatusText(member, team.tasks)
760
+ })]
761
+ }),
762
+ (0, react_jsx_runtime.jsxs)("span", {
763
+ className: ActivityPanel_module_css_default.memberCount,
764
+ children: [
765
+ member.done,
766
+ "/",
767
+ member.total
768
+ ]
769
+ })
770
+ ]
771
+ }),
772
+ (0, react_jsx_runtime.jsxs)("div", {
773
+ className: ActivityPanel_module_css_default.assignmentLine,
774
+ children: [
775
+ (0, react_jsx_runtime.jsx)("span", {
776
+ className: ActivityPanel_module_css_default.assignmentLabel,
777
+ children: "队长派发"
778
+ }),
779
+ (0, react_jsx_runtime.jsx)("span", {
780
+ className: ActivityPanel_module_css_default.assignmentTasks,
781
+ children: owned.length === 0 ? (0, react_jsx_runtime.jsx)("span", {
782
+ className: ActivityPanel_module_css_default.taskEmpty,
783
+ children: "暂无任务"
784
+ }) : owned.map((task) => (0, react_jsx_runtime.jsx)("span", {
785
+ className: ActivityPanel_module_css_default.assignmentChip,
786
+ "data-state": taskTone(task.state, task.status),
787
+ title: task.subject,
788
+ children: task.id
789
+ }, task.id))
790
+ }),
791
+ member.unread > 0 && (0, react_jsx_runtime.jsxs)("span", {
792
+ className: ActivityPanel_module_css_default.unreadPill,
793
+ children: [member.unread, " 条消息"]
794
+ })
795
+ ]
796
+ })
797
+ ]
798
+ }, member.id);
799
+ })]
800
+ })]
801
+ }),
802
+ (0, react_jsx_runtime.jsx)(DependencyMap, { tasks: team.tasks }),
803
+ unclaimed.length > 0 && (0, react_jsx_runtime.jsxs)("section", {
804
+ className: ActivityPanel_module_css_default.unclaimed,
805
+ "aria-label": "待认领任务",
806
+ children: [(0, react_jsx_runtime.jsx)("span", {
807
+ className: ActivityPanel_module_css_default.unclaimedTitle,
808
+ children: "待队长认领或改派"
809
+ }), (0, react_jsx_runtime.jsx)("span", {
810
+ className: ActivityPanel_module_css_default.assignmentTasks,
811
+ children: unclaimed.map((task) => (0, react_jsx_runtime.jsxs)("span", {
812
+ className: ActivityPanel_module_css_default.assignmentChip,
813
+ "data-state": taskTone(task.state, task.status),
814
+ title: task.subject,
815
+ children: [
816
+ task.id,
817
+ " · ",
818
+ task.assignee || "未分配"
819
+ ]
820
+ }, task.id))
821
+ })]
822
+ }),
823
+ team.captainInbox.length > 0 && (0, react_jsx_runtime.jsxs)("section", {
824
+ className: ActivityPanel_module_css_default.inbox,
825
+ "aria-label": "成员回报队长",
826
+ children: [(0, react_jsx_runtime.jsxs)("header", {
827
+ className: ActivityPanel_module_css_default.sectionHead,
828
+ children: [(0, react_jsx_runtime.jsx)("span", {
829
+ className: ActivityPanel_module_css_default.sectionTitle,
830
+ children: "成员回报"
831
+ }), (0, react_jsx_runtime.jsx)("span", {
832
+ className: ActivityPanel_module_css_default.sectionHint,
833
+ children: "流向队长"
834
+ })]
835
+ }), team.captainInbox.slice(-2).map((message, index) => (0, react_jsx_runtime.jsxs)("div", {
836
+ className: ActivityPanel_module_css_default.inboxRow,
837
+ children: [(0, react_jsx_runtime.jsxs)("span", {
838
+ className: ActivityPanel_module_css_default.inboxRoute,
839
+ children: [
840
+ message.from,
841
+ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {}),
842
+ "队长"
843
+ ]
844
+ }), (0, react_jsx_runtime.jsx)("span", {
845
+ className: ActivityPanel_module_css_default.inboxContent,
846
+ title: message.content,
847
+ children: message.content
848
+ })]
849
+ }, index))]
850
+ })
851
+ ]
852
+ });
853
+ }
854
+ /** The top-right activity floater. Teams follow the current session: live
855
+ * snapshots and historic card summaries are only shown while their captain
856
+ * session is the one currently open. */
857
+ function ActivityPanel({ sessionsList, openSession }) {
858
+ const navigateToSession = (id) => {
859
+ setOpen(false);
860
+ setWasActive(false);
861
+ openSession(id);
862
+ };
863
+ const [teams, setTeams] = (0, react.useState)([]);
864
+ const [archivedTeams, setArchivedTeams] = (0, react.useState)([]);
865
+ const [open, setOpen] = (0, react.useState)(false);
866
+ const [openOwner, setOpenOwner] = (0, react.useState)();
867
+ const [autoOpened, setAutoOpened] = (0, react.useState)(false);
868
+ const [wasActive, setWasActive] = (0, react.useState)(false);
869
+ const [historic, setHistoric] = (0, react.useState)(/* @__PURE__ */ new Map());
870
+ const current = (0, react.useSyncExternalStore)(sessionsList.subscribe, sessionsList.getSnapshot).current;
871
+ const currentRef = (0, react.useRef)(current);
872
+ (0, react.useEffect)(() => {
873
+ currentRef.current = current;
874
+ }, [current]);
875
+ const mountedAtRef = (0, react.useRef)(performance.now());
876
+ const expanded = activityPanelExpandedForSession(open, openOwner, current);
877
+ (0, react.useLayoutEffect)(() => {
878
+ if (openOwner === void 0 || openOwner === current) return;
879
+ setOpen(false);
880
+ setOpenOwner(void 0);
881
+ setWasActive(false);
882
+ setAutoOpened(false);
883
+ }, [current, openOwner]);
884
+ (0, react.useLayoutEffect)(() => {
885
+ const root = document.documentElement;
886
+ if (expanded) root.setAttribute(PANEL_OPEN_ATTRIBUTE, "");
887
+ else root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
888
+ return () => {
889
+ root.removeAttribute(PANEL_OPEN_ATTRIBUTE);
890
+ };
891
+ }, [expanded]);
892
+ (0, react.useEffect)(() => {
893
+ let cancelled = false;
894
+ let inFlight = false;
895
+ const tick = async () => {
896
+ if (inFlight || cancelled) return;
897
+ inFlight = true;
898
+ try {
899
+ const [liveResponse, archivedResponse] = await Promise.all([fetch(STATE_URL, { cache: "no-store" }), fetch(`${STATE_URL}?archived=1`, { cache: "no-store" })]);
900
+ if (liveResponse.ok) {
901
+ const body = await liveResponse.json();
902
+ if (!cancelled && Array.isArray(body.teams)) setTeams(body.teams);
903
+ }
904
+ if (archivedResponse.ok) {
905
+ const body = await archivedResponse.json();
906
+ if (!cancelled && Array.isArray(body.teams)) setArchivedTeams(body.teams);
907
+ }
908
+ } catch {} finally {
909
+ inFlight = false;
910
+ }
911
+ };
912
+ tick();
913
+ const timer = setInterval(() => {
914
+ tick();
915
+ }, POLL_MS);
916
+ return () => {
917
+ cancelled = true;
918
+ clearInterval(timer);
919
+ };
920
+ }, []);
921
+ (0, react.useEffect)(() => {
922
+ const onOpenPanel = (event) => {
923
+ const activeSession = currentRef.current;
924
+ if (activeSession === void 0) return;
925
+ setOpenOwner(activeSession);
926
+ setOpen(true);
927
+ const detail = event.detail;
928
+ if (detail?.teamId !== void 0) {
929
+ const owner = detail.captainSessionId !== "" ? detail.captainSessionId : currentRef.current ?? "";
930
+ const teamKey = `${owner}:${detail.teamId}`;
931
+ setHistoric((previous) => {
932
+ const next = new Map(previous);
933
+ next.set(teamKey, {
934
+ data: detail,
935
+ owner
936
+ });
937
+ return next;
938
+ });
939
+ }
940
+ };
941
+ window.addEventListener(OPEN_PANEL_EVENT, onOpenPanel);
942
+ return () => {
943
+ window.removeEventListener(OPEN_PANEL_EVENT, onOpenPanel);
944
+ };
945
+ }, []);
946
+ const visibleTeams = (0, react.useMemo)(() => current === void 0 ? [] : teams.filter((team) => team.captainSessionId === current), [teams, current]);
947
+ const visibleHistoric = (0, react.useMemo)(() => current === void 0 ? [] : [...historic.values()].filter(({ data, owner }) => owner === current && !teams.some((live) => live.captainSessionId === current && live.teamId === data.teamId) && !archivedTeams.some((archived) => archived.captainSessionId === current && archived.teamId === data.teamId)), [
948
+ historic,
949
+ current,
950
+ teams,
951
+ archivedTeams
952
+ ]);
953
+ const visibleArchived = (0, react.useMemo)(() => current === void 0 ? [] : archivedTeams.filter((team) => team.captainSessionId === current && !teams.some((live) => live.captainSessionId === current && live.teamId === team.teamId)), [
954
+ archivedTeams,
955
+ current,
956
+ teams
957
+ ]);
958
+ const visibleCount = visibleTeams.length + visibleArchived.length + visibleHistoric.length;
959
+ (0, react.useEffect)(() => {
960
+ if (visibleCount > 0) {
961
+ setWasActive(true);
962
+ const settled = performance.now() - mountedAtRef.current >= AUTO_OPEN_SETTLE_MS;
963
+ if (!autoOpened && settled) {
964
+ setOpenOwner(current);
965
+ setOpen(true);
966
+ setAutoOpened(true);
967
+ }
968
+ return;
969
+ }
970
+ if (!wasActive) return;
971
+ const timer = setTimeout(() => {
972
+ setOpen(false);
973
+ setOpenOwner(void 0);
974
+ setWasActive(false);
975
+ setAutoOpened(false);
976
+ }, AUTOCLOSE_GRACE_MS);
977
+ return () => {
978
+ clearTimeout(timer);
979
+ };
980
+ }, [
981
+ visibleCount,
982
+ autoOpened,
983
+ wasActive
984
+ ]);
985
+ const busy = (0, react.useMemo)(() => visibleTeams.some((team) => team.members.some((member) => member.activity === "working")), [visibleTeams]);
986
+ if (!(visibleCount > 0) && !expanded) return null;
987
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!expanded && (0, react_jsx_runtime.jsx)(CollapsedBadge, {
988
+ count: visibleCount,
989
+ busy,
990
+ onClick: () => {
991
+ if (current === void 0) return;
992
+ setOpenOwner(current);
993
+ setOpen(true);
994
+ }
995
+ }), expanded && (0, react_jsx_runtime.jsxs)("aside", {
996
+ className: ActivityPanel_module_css_default.panel,
997
+ "data-agent-teams-activity": true,
998
+ children: [(0, react_jsx_runtime.jsxs)("header", {
999
+ className: ActivityPanel_module_css_default.panelHead,
1000
+ children: [(0, react_jsx_runtime.jsxs)("span", {
1001
+ className: ActivityPanel_module_css_default.panelTitle,
1002
+ children: ["AgentTeams 活动", (0, react_jsx_runtime.jsx)("span", {
1003
+ className: ActivityPanel_module_css_default.panelDot,
1004
+ "data-busy": busy,
1005
+ "aria-hidden": true
1006
+ })]
1007
+ }), (0, react_jsx_runtime.jsx)("button", {
1008
+ type: "button",
1009
+ className: ActivityPanel_module_css_default.closeButton,
1010
+ onClick: () => {
1011
+ setOpen(false);
1012
+ setOpenOwner(void 0);
1013
+ },
1014
+ "aria-label": "关闭",
1015
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
1016
+ })]
1017
+ }), (0, react_jsx_runtime.jsx)("div", {
1018
+ className: ActivityPanel_module_css_default.teams,
1019
+ children: visibleCount === 0 ? (0, react_jsx_runtime.jsx)("span", {
1020
+ className: ActivityPanel_module_css_default.emptyHint,
1021
+ children: "暂无团队活动"
1022
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1023
+ visibleTeams.map((team) => (0, react_jsx_runtime.jsx)(TeamSection, {
1024
+ team,
1025
+ onNavigate: navigateToSession
1026
+ }, team.teamId)),
1027
+ visibleArchived.map((team) => (0, react_jsx_runtime.jsx)("div", {
1028
+ "data-team-id": team.teamId,
1029
+ "data-historic": true,
1030
+ className: ActivityPanel_module_css_default.archivedWrap,
1031
+ children: (0, react_jsx_runtime.jsx)(TeamSection, {
1032
+ team,
1033
+ onNavigate: navigateToSession,
1034
+ historic: true
1035
+ })
1036
+ }, `${team.captainSessionId}:${team.teamId}`)),
1037
+ visibleHistoric.map(({ data: team, owner }) => {
1038
+ const teamKey = `${owner}:${team.teamId}`;
1039
+ return (0, react_jsx_runtime.jsxs)("section", {
1040
+ className: ActivityPanel_module_css_default.team,
1041
+ "data-team-id": team.teamId,
1042
+ "data-historic": true,
1043
+ children: [(0, react_jsx_runtime.jsxs)("header", {
1044
+ className: ActivityPanel_module_css_default.teamHead,
1045
+ children: [(0, react_jsx_runtime.jsxs)("span", {
1046
+ className: ActivityPanel_module_css_default.teamName,
1047
+ title: team.teamName,
1048
+ children: [
1049
+ (0, react_jsx_runtime.jsx)("img", {
1050
+ className: ActivityPanel_module_css_default.leadAvatar,
1051
+ src: LEAD_ART,
1052
+ alt: "",
1053
+ "aria-hidden": true
1054
+ }),
1055
+ " ",
1056
+ team.teamName
1057
+ ]
1058
+ }), (0, react_jsx_runtime.jsx)("span", {
1059
+ className: ActivityPanel_module_css_default.historicPill,
1060
+ children: "已结束"
1061
+ })]
1062
+ }), (0, react_jsx_runtime.jsx)("div", {
1063
+ className: ActivityPanel_module_css_default.members,
1064
+ children: team.members.map((member) => (0, react_jsx_runtime.jsxs)("button", {
1065
+ type: "button",
1066
+ className: ActivityPanel_module_css_default.memberRow,
1067
+ "data-activity": "idle",
1068
+ onClick: () => {
1069
+ if (member.id !== "") navigateToSession(member.id);
1070
+ },
1071
+ children: [(0, react_jsx_runtime.jsx)("span", {
1072
+ className: ActivityPanel_module_css_default.memberAvatar,
1073
+ children: memberArtUrl(member.name, member.role) !== null ? (0, react_jsx_runtime.jsx)("img", {
1074
+ className: ActivityPanel_module_css_default.memberArt,
1075
+ src: memberArtUrl(member.name, member.role) ?? "",
1076
+ alt: "",
1077
+ "aria-hidden": true
1078
+ }) : (0, react_jsx_runtime.jsx)("span", {
1079
+ className: ActivityPanel_module_css_default.memberInitial,
1080
+ style: { background: accentOf(member.id) },
1081
+ children: memberInitial(member.name)
1082
+ })
1083
+ }), (0, react_jsx_runtime.jsx)("span", {
1084
+ className: ActivityPanel_module_css_default.memberInfo,
1085
+ children: (0, react_jsx_runtime.jsxs)("span", {
1086
+ className: ActivityPanel_module_css_default.memberLine,
1087
+ children: [(0, react_jsx_runtime.jsx)("span", {
1088
+ className: ActivityPanel_module_css_default.memberName,
1089
+ children: member.name
1090
+ }), member.role !== "" && (0, react_jsx_runtime.jsx)("span", {
1091
+ className: ActivityPanel_module_css_default.memberRole,
1092
+ children: member.role
1093
+ })]
1094
+ })
1095
+ })]
1096
+ }, member.id))
1097
+ })]
1098
+ }, teamKey);
1099
+ })
1100
+ ] })
1101
+ })]
1102
+ })] });
1103
+ }
1104
+ //#endregion
1105
+ //#region lib/client/agent-teams-card-definition.js
1106
+ /**
1107
+ * AgentTeams conversation card: a lightweight in-conversation summary shown
1108
+ * when a team is created — the captain's name, the member roster with whale
1109
+ * avatars, and an entry point that re-activates the top-right activity
1110
+ * panel (useful after the floater was closed, or when re-opening an old
1111
+ * session for review).
1112
+ *
1113
+ * The fold anchors to the Harness's durable `tool/call` + `tool/result`
1114
+ * records for `agent_teams_create`. Those are first-party session events, so
1115
+ * the card survives restarts without writing an out-of-repo event type.
1116
+ * @module dsh-agent-teams/client/card
1117
+ */
1118
+ /** Parse the only create-call fields the historic card owns. */
1119
+ function parseAgentTeamsCreateArgs(value) {
1120
+ try {
1121
+ const parsed = JSON.parse(value);
1122
+ if (typeof parsed !== "object" || parsed === null || !("name" in parsed) || typeof parsed.name !== "string") return;
1123
+ const name = parsed.name.trim();
1124
+ if (name === "") return void 0;
1125
+ const cleaned = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1126
+ return {
1127
+ teamId: cleaned === "" ? "team" : cleaned,
1128
+ name
1129
+ };
1130
+ } catch {
1131
+ return;
1132
+ }
1133
+ }
1134
+ /** Durable first-party tool events folded into one keyed Chat node. */
1135
+ const agentTeamsCardDefinition = {
1136
+ kind: "agent-teams",
1137
+ target: "chat",
1138
+ match: (event) => {
1139
+ if (event.type === "tool/call" && event.data.name === "agent_teams_create") return parseAgentTeamsCreateArgs(event.data.arguments) === void 0 ? null : {
1140
+ id: String(event.data.callId),
1141
+ role: "start"
1142
+ };
1143
+ if (event.type === "tool/result" && event.data.message.source.kind === "tool") return {
1144
+ id: String(event.data.message.source.callId),
1145
+ role: "update"
1146
+ };
1147
+ return null;
1148
+ },
1149
+ start: (_context, match) => {
1150
+ if (match.event.type !== "tool/call") throw new Error("agent-teams card start requires agent_teams_create tool/call");
1151
+ const parsed = parseAgentTeamsCreateArgs(match.event.data.arguments);
1152
+ if (parsed === void 0) throw new Error("agent-teams card start requires valid create arguments");
1153
+ return {
1154
+ ...parsed,
1155
+ accepted: false
1156
+ };
1157
+ },
1158
+ update: (context, match) => {
1159
+ if (match.event.type !== "tool/result") return context.state;
1160
+ if (match.event.data.error !== void 0 || match.event.data.message.content.some((block) => block.type === "tool-result" && block.isError === true)) return context.state;
1161
+ return {
1162
+ ...context.state,
1163
+ accepted: true
1164
+ };
1165
+ },
1166
+ buildViewNode: (context) => {
1167
+ if (context.start === void 0) return null;
1168
+ const state = context.state;
1169
+ if (!state.accepted) return null;
1170
+ return {
1171
+ key: context.key,
1172
+ kind: "agent-teams",
1173
+ id: context.id,
1174
+ target: "chat",
1175
+ anchorSeq: context.start.event.seq,
1176
+ location: context.start.location,
1177
+ visibility: "visible",
1178
+ data: {
1179
+ teamId: state.teamId,
1180
+ captainSessionId: "",
1181
+ teamName: state.name,
1182
+ members: []
1183
+ }
1184
+ };
1185
+ }
1186
+ };
1187
+ //#endregion
1188
+ //#region lib/client/index.js
1189
+ /** Required services: conversation nodes, slots, and sessions navigation. */
1190
+ const inject = [
1191
+ "conversationEvents",
1192
+ "slots",
1193
+ "sessions"
1194
+ ];
1195
+ /**
1196
+ * Mount the floater through a body portal (the web shell has no top-right
1197
+ * slot) and register the in-conversation team card, whose "activity panel"
1198
+ * button re-activates the floater via a window event — the recovery path
1199
+ * for a closed floater or a re-opened session.
1200
+ */
1201
+ function apply(ctx) {
1202
+ const host = document.createElement("div");
1203
+ host.dataset.agentTeamsHost = "";
1204
+ document.body.appendChild(host);
1205
+ const root = (0, react_dom_client.createRoot)(host);
1206
+ root.render((0, react_jsx_runtime.jsx)(ActivityPanel, {
1207
+ sessionsList: ctx.sessions.list,
1208
+ openSession: (id) => {
1209
+ ctx.sessions.open(id);
1210
+ }
1211
+ }));
1212
+ ctx.effect(() => () => {
1213
+ root.unmount();
1214
+ host.remove();
1215
+ }, "agent-teams: activity panel");
1216
+ ctx.conversationEvents.register(agentTeamsCardDefinition);
1217
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1218
+ name: "conversation.chat.node",
1219
+ key: "agent-teams",
1220
+ inject: () => ({
1221
+ openSession: (id) => {
1222
+ ctx.sessions.open(id);
1223
+ },
1224
+ currentSessionId: () => ctx.sessions.list.getSnapshot().current
1225
+ })
1226
+ }, AgentTeamsCard));
1227
+ }
1228
+ //#endregion
1229
+ exports.apply = apply;
1230
+ exports.inject = inject;
1231
+ return module.exports;
1232
+ }
1233
+ });
1234
+
1235
+ //# sourceMappingURL=client.js.map