@nanmicoder/dsh-agent-teams 0.1.10 → 0.1.12

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 CHANGED
@@ -37,6 +37,8 @@ Read the [latest release notes](https://github.com/NanmiCoder/dsh-agent-teams/re
37
37
  | **Direct messaging** | Members send durable mailbox messages directly to teammates or the captain—no relay required. |
38
38
  | **Live activity panel** | The Web UI combines segmented progress, a collapsible roster, and an interactive task DAG; completed archives retain their full member and task history. |
39
39
 
40
+ The conversation card and activity panel use Harness's official locale service. They follow live language changes between English and Simplified Chinese—including status labels, dynamic summaries, controls, archive markers, and accessibility text—without a page reload or a separate plugin setting.
41
+
40
42
  ## Install
41
43
 
42
44
  > [!NOTE]
@@ -45,7 +47,7 @@ Read the [latest release notes](https://github.com/NanmiCoder/dsh-agent-teams/re
45
47
  ### npm
46
48
 
47
49
  ```sh
48
- dsh plugin --profile web add @nanmicoder/dsh-agent-teams
50
+ dsh plugin --profile web add @nanmicoder/dsh-agent-teams@latest
49
51
  ```
50
52
 
51
53
  ### Build from source
package/README_ZH.md CHANGED
@@ -37,6 +37,8 @@
37
37
  | **成员直达消息** | 成员通过持久化邮箱直接联系队友或队长,不需要队长中转。 |
38
38
  | **实时活动面板** | Web UI 用分段进度、可折叠成员树和可交互 DAG 展示实时工作;团队结束后仍保留完整成员与任务历史。 |
39
39
 
40
+ 对话卡片与活动面板接入 Harness 官方多语言服务,会随宿主在简体中文和英文之间实时切换;任务/成员状态、动态摘要、操作按钮、历史归档标识和无障碍文案都会同步更新,无需刷新页面,也不增加插件自己的语言设置。
41
+
40
42
  ## 安装
41
43
 
42
44
  > [!NOTE]
@@ -45,7 +47,7 @@
45
47
  ### npm
46
48
 
47
49
  ```sh
48
- dsh plugin --profile web add @nanmicoder/dsh-agent-teams
50
+ dsh plugin --profile web add @nanmicoder/dsh-agent-teams@latest
49
51
  ```
50
52
 
51
53
  ### 从源码构建
@@ -73,15 +73,19 @@ function accentOf(id) {
73
73
  /** Badge text follows the raw task status (finer than the 4 visual states):
74
74
  * claimed/pending/failed/cancelled keep their own labels and colors. */
75
75
  const TASK_STATUS_LABEL = {
76
- pending: '待领取',
77
- claimed: '已认领',
78
- in_progress: '进行中',
79
- completed: '已完成',
80
- failed: '失败',
81
- cancelled: '已取消',
76
+ pending: 'task.status.pending',
77
+ claimed: 'task.status.claimed',
78
+ in_progress: 'task.status.inProgress',
79
+ completed: 'task.status.completed',
80
+ failed: 'task.status.failed',
81
+ cancelled: 'task.status.cancelled',
82
82
  };
83
- function taskStatusLabel(status) {
84
- return TASK_STATUS_LABEL[status] ?? status;
83
+ function taskStatusLabel(status, t) {
84
+ const key = TASK_STATUS_LABEL[status];
85
+ return key === undefined ? status : t(key);
86
+ }
87
+ function formatTaskIds(ids, t) {
88
+ return ids.join(t('format.listSeparator'));
85
89
  }
86
90
  /** Badge/bar coloring key: visual state, widened for terminal statuses. */
87
91
  function taskTone(state, status) {
@@ -98,78 +102,85 @@ function WorkGlyph({ active }) {
98
102
  return (_jsx("svg", { className: css.workGlyph, "data-active": active, width: "11", height: "11", viewBox: "0 0 11 11", fill: "currentColor", "aria-hidden": true, children: [[0, 0], [4.2, 0], [8.4, 0], [0, 4.2], [4.2, 4.2], [8.4, 4.2]].map(([x, y], index) => (_jsx("rect", { x: x, y: y, width: "2.6", height: "2.6", rx: ".6", style: { animationDelay: `${index * 0.15}s` } }, `${x}:${y}`))) }));
99
103
  }
100
104
  /** Collapsed badge: an always-visible corner pill while any team exists. */
101
- function CollapsedBadge({ count, busy, onClick }) {
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 })] }));
105
+ function CollapsedBadge({ count, busy, onClick, t }) {
106
+ return (_jsxs("button", { type: "button", className: css.badge, "data-agent-teams-collapsed": true, "data-busy": busy, onClick: onClick, "aria-label": t('activity.badgeAria', { count }), children: [_jsx("span", { className: css.badgeDot, "data-busy": busy, "aria-hidden": true }), _jsx("span", { className: css.badgeCount, children: count })] }));
103
107
  }
104
- function memberStateLabel(member, tasks, historic) {
108
+ function memberStateLabel(member, tasks, historic, t) {
105
109
  const owned = tasks.filter((task) => task.assignee === member.name);
106
110
  if (member.activity === 'working')
107
- return '工作中';
111
+ return t('member.state.working');
108
112
  if (owned.some((task) => task.status === 'failed'))
109
- return '有失败';
113
+ return t('member.state.failed');
110
114
  if (owned.some((task) => task.state === 'blocked'))
111
- return '等待';
115
+ return t('member.state.waiting');
112
116
  if (owned.length > 0 && owned.every((task) => task.status === 'completed'))
113
- return '已交付';
117
+ return t('member.state.delivered');
114
118
  if (member.status === 'removed')
115
- return historic ? '已离队' : '已移除';
119
+ return t(historic ? 'member.state.left' : 'member.state.removed');
116
120
  if (owned.length > 0)
117
- return '待执行';
118
- return '待派工';
121
+ return t('member.state.pending');
122
+ return t('member.state.unassigned');
119
123
  }
120
- function memberStatusText(member, tasks) {
124
+ function memberStatusText(member, tasks, t) {
121
125
  const owned = tasks.filter((task) => task.assignee === member.name);
122
126
  const current = owned.find((task) => task.id === member.currentTask);
123
127
  const blocked = owned.find((task) => task.state === 'blocked');
124
128
  if (member.activity === 'working' && current !== undefined)
125
- return `正在执行 ${current.id}`;
129
+ return t('member.status.executing', { taskId: current.id });
126
130
  if (member.activity === 'working')
127
- return '正在处理已派任务';
131
+ return t('member.status.working');
128
132
  if (blocked !== undefined) {
129
133
  const dependency = tasks.find((task) => blocked.dependencies.includes(task.id) && task.state !== 'completed');
130
- if (dependency !== undefined)
131
- return `等待 ${dependency.id} · ${dependency.assignee || '待认领'}`;
132
- return '等待前置任务';
134
+ if (dependency !== undefined) {
135
+ return t('member.status.waitingOn', {
136
+ taskId: dependency.id,
137
+ assignee: dependency.assignee || t('task.assignee.unclaimed'),
138
+ });
139
+ }
140
+ return t('member.status.waitingPrerequisite');
133
141
  }
134
142
  if (member.total === 0)
135
- return '等待队长派工';
143
+ return t('member.status.waitingAssignment');
136
144
  if (member.done === member.total)
137
- return '任务已交付';
138
- return member.activity === 'idle' ? '待继续执行' : '状态未知';
145
+ return t('member.status.delivered');
146
+ return t(member.activity === 'idle' ? 'member.status.idle' : 'member.status.unknown');
139
147
  }
140
148
  function compactTaskLabel(subject) {
141
149
  const withoutVerb = subject.replace(/^开发\s*/u, '').replace(/^\d+[-_.、\s]*/u, '');
142
150
  const head = withoutVerb.split(/[((·::]/u)[0]?.trim() ?? withoutVerb;
143
151
  return head.length > 18 ? `${head.slice(0, 17)}…` : head;
144
152
  }
145
- function taskSummary(team) {
153
+ function taskSummary(team, t) {
146
154
  const completed = team.tasks.filter((task) => task.status === 'completed');
147
155
  const running = team.tasks.filter((task) => task.state === 'running');
148
156
  const blocked = team.tasks.filter((task) => task.state === 'blocked');
149
157
  const ready = team.tasks.filter((task) => task.state === 'open' && task.status !== 'completed');
150
158
  if (team.tasks.length === 0)
151
- return '等待队长拆解任务';
159
+ return t('task.summary.waitingBreakdown');
152
160
  if (completed.length === team.tasks.length)
153
- return `全部 ${completed.length} 项任务已交付`;
161
+ return t('task.summary.allDelivered', { count: completed.length });
154
162
  if (blocked.length > 0 && running.length > 0) {
155
- return `${blocked.slice(0, 3).map((task) => task.id).join('、')}${blocked.length > 3 ? ` 等 ${blocked.length} 项` : ''} 等待前置,其余已开工`;
163
+ return t('task.summary.blockedAndRunning', {
164
+ tasks: formatTaskIds(blocked.slice(0, 3).map((task) => task.id), t),
165
+ more: blocked.length > 3 ? t('task.summary.more', { count: blocked.length - 3 }) : '',
166
+ });
156
167
  }
157
168
  if (running.length > 0)
158
- return `${running.map((task) => task.id).join('、')} 正在执行`;
169
+ return t('task.summary.running', { tasks: formatTaskIds(running.map((task) => task.id), t) });
159
170
  if (ready.length > 0)
160
- return `${ready.map((task) => task.id).join('、')} 已就绪待开工`;
171
+ return t('task.summary.ready', { tasks: formatTaskIds(ready.map((task) => task.id), t) });
161
172
  if (blocked.length > 0)
162
- return `${blocked.map((task) => task.id).join('、')} 等待前置`;
163
- return '等待下一轮调度';
173
+ return t('task.summary.blocked', { tasks: formatTaskIds(blocked.map((task) => task.id), t) });
174
+ return t('task.summary.waitingSchedule');
164
175
  }
165
- function ProgressOverview({ team }) {
176
+ function ProgressOverview({ team, t }) {
166
177
  const running = team.tasks.filter((task) => task.state === 'running').length;
167
178
  const blocked = team.tasks.filter((task) => task.state === 'blocked').length;
168
179
  const completed = team.tasks.filter((task) => task.status === 'completed').length;
169
180
  const summaryTone = blocked > 0 ? 'warning' : completed === team.tasks.length && team.tasks.length > 0 ? 'completed' : 'running';
170
- return (_jsxs("section", { className: css.progressOverview, "aria-label": "\u56E2\u961F\u603B\u8FDB\u5EA6", "data-progress-summary": true, children: [_jsx("span", { className: css.progressTitle, children: "\u603B\u8FDB\u5EA6" }), team.tasks.length > 0 ? (_jsx("span", { className: css.progressSegments, "aria-hidden": true, children: team.tasks.map((task) => _jsx("span", { "data-state": taskTone(task.state, task.status) }, task.id)) })) : _jsx("span", { className: css.progressEmpty }), _jsxs("span", { className: css.progressLegend, children: [_jsxs("span", { "data-state": "running", children: ["\u25A0 \u8FDB\u884C\u4E2D ", running] }), _jsxs("span", { "data-state": "blocked", children: ["\u25A0 \u7B49\u5F85\u4F9D\u8D56 ", blocked] }), _jsxs("span", { "data-state": "completed", children: ["\u25A0 \u5DF2\u4EA4\u4ED8 ", completed] })] }), _jsxs("span", { className: css.progressSummary, "data-state": summaryTone, children: [_jsx("span", { className: css.progressSummaryDot }), _jsx("span", { children: taskSummary(team) })] })] }));
181
+ return (_jsxs("section", { className: css.progressOverview, "aria-label": t('progress.aria'), "data-progress-summary": true, children: [_jsx("span", { className: css.progressTitle, children: t('progress.title') }), team.tasks.length > 0 ? (_jsx("span", { className: css.progressSegments, "aria-hidden": true, children: team.tasks.map((task) => _jsx("span", { "data-state": taskTone(task.state, task.status) }, task.id)) })) : _jsx("span", { className: css.progressEmpty }), _jsxs("span", { className: css.progressLegend, children: [_jsx("span", { "data-state": "running", children: t('progress.running', { count: running }) }), _jsx("span", { "data-state": "blocked", children: t('progress.blocked', { count: blocked }) }), _jsx("span", { "data-state": "completed", children: t('progress.delivered', { count: completed }) })] }), _jsxs("span", { className: css.progressSummary, "data-state": summaryTone, children: [_jsx("span", { className: css.progressSummaryDot }), _jsx("span", { children: taskSummary(team, t) })] })] }));
171
182
  }
172
- function DependencyMap({ tasks }) {
183
+ function DependencyMap({ tasks, t }) {
173
184
  const [open, setOpen] = useState(true);
174
185
  const [hoverTaskId, setHoverTaskId] = useState(null);
175
186
  const [keyboardTaskId, setKeyboardTaskId] = useState(null);
@@ -213,34 +224,44 @@ function DependencyMap({ tasks }) {
213
224
  const detailTask = tasks.find((task) => task.id === focusedTaskId) ?? fallbackTask;
214
225
  const waitingOn = detailTask.dependencies.filter((dependency) => (tasks.find((task) => task.id === dependency)?.status !== 'completed'));
215
226
  const dependents = tasks.filter((task) => task.dependencies.includes(detailTask.id));
216
- return (_jsxs("section", { className: css.dependencySection, "aria-label": "\u4EFB\u52A1\u4F9D\u8D56\u94FE", "data-dependency-map": true, children: [_jsxs("header", { className: css.sectionHead, children: [_jsxs("button", { type: "button", className: css.sectionToggleTitle, onClick: () => { setOpen((current) => !current); }, "aria-expanded": open, children: [_jsx(Chevron, { open: open }), _jsx(IconBranchOutline16, {}), " ", parallel ? '并行任务' : '任务依赖'] }), _jsx("span", { className: css.sectionHint, children: pinnedTaskId === null
217
- ? parallel ? '无前后依赖 · 点击查看详情' : '悬停高亮依赖链 · 点击固定'
218
- : `${pinnedTaskId} 已固定 · Esc 取消` })] }), open && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.dagViewport, children: _jsxs("div", { className: css.dagCanvas, "data-layout": parallel ? 'parallel' : 'dependency', style: parallel ? undefined : { width: layout.width, height: layout.height }, children: [!parallel && _jsx("svg", { className: css.dagEdges, width: layout.width, height: layout.height, "aria-hidden": true, children: layout.edges.map((edge) => {
227
+ return (_jsxs("section", { className: css.dependencySection, "aria-label": t('dependency.aria'), "data-dependency-map": true, children: [_jsxs("header", { className: css.sectionHead, children: [_jsxs("button", { type: "button", className: css.sectionToggleTitle, onClick: () => { setOpen((current) => !current); }, "aria-expanded": open, children: [_jsx(Chevron, { open: open }), _jsx(IconBranchOutline16, {}), " ", t(parallel ? 'dependency.parallel' : 'dependency.title')] }), _jsx("span", { className: css.sectionHint, children: pinnedTaskId === null
228
+ ? t(parallel ? 'dependency.hint.parallel' : 'dependency.hint.chain')
229
+ : t('dependency.hint.pinned', { taskId: pinnedTaskId }) })] }), open && (_jsxs(_Fragment, { children: [_jsx("div", { className: css.dagViewport, children: _jsxs("div", { className: css.dagCanvas, "data-layout": parallel ? 'parallel' : 'dependency', style: parallel ? undefined : { width: layout.width, height: layout.height }, children: [!parallel && _jsx("svg", { className: css.dagEdges, width: layout.width, height: layout.height, "aria-hidden": true, children: layout.edges.map((edge) => {
219
230
  const active = related !== null && related.has(edge.from) && related.has(edge.to);
220
231
  return _jsx("path", { d: edge.path, "data-active": active, "data-dimmed": related !== null && !active }, `${edge.from}:${edge.to}`);
221
232
  }) }), layout.nodes.map(({ task, x, y }) => (_jsxs("button", { type: "button", className: css.dagNode, style: parallel
222
233
  ? { height: COMPACT_DAG_NODE_HEIGHT }
223
- : { left: x, top: y, width: COMPACT_DAG_NODE_WIDTH, height: COMPACT_DAG_NODE_HEIGHT }, "data-task-id": task.id, "data-state": taskTone(task.state, task.status), "data-focused": related?.has(task.id) ?? false, "data-dimmed": related !== null && !related.has(task.id), "aria-pressed": pinnedTaskId === task.id, title: `${task.id} · ${task.subject}`, onClick: () => { setPinnedTaskId((current) => current === task.id ? null : task.id); }, onMouseEnter: () => { scheduleHover(task.id); }, onMouseLeave: () => { scheduleHover(null); }, onFocus: () => { setKeyboardTaskId(task.id); }, onBlur: () => { setKeyboardTaskId(null); }, children: [_jsxs("span", { className: css.dagNodeHead, children: [_jsx("span", { className: css.dagNodeDot }), task.id] }), _jsx("span", { className: css.dagNodeLabel, children: compactTaskLabel(task.subject) }), task.state === 'running' && (_jsx("span", { className: css.dagRunningState, "aria-label": "\u8FD0\u884C\u4E2D", children: _jsx(WorkGlyph, { active: true }) }))] }, task.id)))] }) }), _jsxs("section", { className: css.taskDetail, "data-task-detail": detailTask.id, children: [_jsxs("span", { className: css.taskDetailHead, children: [_jsx("span", { className: css.taskDetailId, children: detailTask.id }), _jsx("span", { className: css.taskDetailSubject, title: detailTask.subject, children: detailTask.subject.replace(/^开发\s*/u, '') }), _jsx("span", { className: css.taskDetailBadge, "data-state": taskTone(detailTask.state, detailTask.status), children: taskStatusLabel(detailTask.status) })] }), _jsxs("span", { className: css.taskDetailLine, children: [detailTask.assignee || '待认领', " \u00B7 ", detailTask.status === 'completed'
224
- ? '已完成并交付'
234
+ : { left: x, top: y, width: COMPACT_DAG_NODE_WIDTH, height: COMPACT_DAG_NODE_HEIGHT }, "data-task-id": task.id, "data-state": taskTone(task.state, task.status), "data-focused": related?.has(task.id) ?? false, "data-dimmed": related !== null && !related.has(task.id), "aria-pressed": pinnedTaskId === task.id, title: `${task.id} · ${task.subject}`, onClick: () => { setPinnedTaskId((current) => current === task.id ? null : task.id); }, onMouseEnter: () => { scheduleHover(task.id); }, onMouseLeave: () => { scheduleHover(null); }, onFocus: () => { setKeyboardTaskId(task.id); }, onBlur: () => { setKeyboardTaskId(null); }, children: [_jsxs("span", { className: css.dagNodeHead, children: [_jsx("span", { className: css.dagNodeDot }), task.id] }), _jsx("span", { className: css.dagNodeLabel, children: compactTaskLabel(task.subject) }), task.state === 'running' && (_jsx("span", { className: css.dagRunningState, "aria-label": t('task.runningAria'), children: _jsx(WorkGlyph, { active: true }) }))] }, task.id)))] }) }), _jsxs("section", { className: css.taskDetail, "data-task-detail": detailTask.id, children: [_jsxs("span", { className: css.taskDetailHead, children: [_jsx("span", { className: css.taskDetailId, children: detailTask.id }), _jsx("span", { className: css.taskDetailSubject, title: detailTask.subject, children: detailTask.subject.replace(/^开发\s*/u, '') }), _jsx("span", { className: css.taskDetailBadge, "data-state": taskTone(detailTask.state, detailTask.status), children: taskStatusLabel(detailTask.status, t) })] }), _jsxs("span", { className: css.taskDetailLine, children: [detailTask.assignee || t('task.assignee.unclaimed'), " \u00B7 ", detailTask.status === 'completed'
235
+ ? t('task.detail.completed')
225
236
  : detailTask.dependencies.length === 0
226
- ? '无前置,可立即开工'
237
+ ? t('task.detail.noPrerequisite')
227
238
  : waitingOn.length === 0
228
- ? '前置已就绪,可开工'
229
- : `等待 ${waitingOn.join('、')}`] }), _jsx("span", { className: css.taskDetailMeta, children: dependents.length === 0 ? '无下游任务' : `完成后解锁 ${dependents.map((task) => task.id).join('、')}` })] })] }))] }));
239
+ ? t('task.detail.ready')
240
+ : t('task.detail.waitingOn', { tasks: formatTaskIds(waitingOn, t) })] }), _jsx("span", { className: css.taskDetailMeta, children: dependents.length === 0
241
+ ? t('task.detail.noDownstream')
242
+ : t('task.detail.unlocks', { tasks: formatTaskIds(dependents.map((task) => task.id), t) }) })] })] }))] }));
230
243
  }
231
- function TeamSection({ team, onNavigate, historic = false }) {
244
+ function TeamSection({ team, onNavigate, t, historic = false }) {
232
245
  const [membersOpen, setMembersOpen] = useState(true);
233
246
  const busyCount = team.members.filter((member) => member.activity === 'working').length;
234
247
  const assignedCount = team.tasks.filter((task) => task.assignee !== '').length;
235
248
  const completedCount = team.tasks.filter((task) => task.status === 'completed').length;
236
249
  const allCompleted = team.tasks.length > 0 && completedCount === team.tasks.length;
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) => {
250
+ 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: t('team.ended') }), _jsxs("span", { className: css.teamStats, children: [_jsx("span", { "data-stat": "members", children: t('team.stats.members', { count: team.members.length }) }), _jsx("span", { "data-stat": "tasks", children: t('team.stats.completed', { completed: completedCount, total: team.tasks.length }) }), _jsx("span", { "data-stat": "messages", children: t('team.stats.messages', { count: team.messageCount }) })] })] }), _jsxs("section", { className: css.delegationSection, "aria-label": t('delegation.aria'), "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: t('captain.name') }), _jsx("span", { className: css.captainRole, children: t('captain.role') })] }), _jsx("span", { className: css.captainSummary, children: t('captain.summary', {
251
+ tasks: assignedCount,
252
+ members: team.members.length,
253
+ }) })] }), _jsxs("span", { className: css.captainState, "data-busy": busyCount > 0, children: [_jsx(WorkGlyph, { active: busyCount > 0 }), busyCount > 0
254
+ ? t('captain.state.working', { count: busyCount })
255
+ : t(allCompleted ? 'captain.state.collected' : 'captain.state.waiting')] })] }), _jsx(ProgressOverview, { team: team, t: t }), _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 }), t('members.toggle', { count: team.members.length })] }), _jsx("span", { children: t(membersOpen ? 'members.collapse' : 'members.expand') })] }), membersOpen && _jsxs("div", { className: css.delegationTree, children: [team.members.length === 0 && _jsx("span", { className: css.emptyHint, children: t('members.empty') }), team.members.map((member) => {
238
256
  const owned = team.tasks.filter((task) => task.assignee === member.name);
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: () => { if (member.id !== '')
240
- onNavigate(member.id); }, 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
241
- ? _jsx("span", { className: css.taskEmpty, children: "\u6682\u65E0\u4EFB\u52A1" })
257
+ 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: () => {
258
+ if (member.id !== '') {
259
+ onNavigate(team.captainSessionId, member.id);
260
+ }
261
+ }, 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, t)] })] }), _jsx("span", { className: css.memberStatusLine, children: memberStatusText(member, team.tasks, t) })] }), _jsxs("span", { className: css.memberCount, children: [member.done, "/", member.total] })] }), _jsxs("div", { className: css.assignmentLine, children: [_jsx("span", { className: css.assignmentLabel, children: t('assignment.label') }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
262
+ ? _jsx("span", { className: css.taskEmpty, children: t('assignment.empty') })
242
263
  : 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));
243
- })] })] }), _jsx(DependencyMap, { tasks: team.tasks })] }));
264
+ })] })] }), _jsx(DependencyMap, { tasks: team.tasks, t: t })] }));
244
265
  }
245
266
  /** Legacy conversation cards may outlive their host archive. Project their
246
267
  * durable roster through the same rebuilt panel instead of a second UI. */
@@ -265,17 +286,14 @@ function historicCardTeam(data, owner) {
265
286
  captainInbox: [],
266
287
  };
267
288
  }
268
- /** The top-right activity floater. Teams follow the current session: live
269
- * snapshots and historic card summaries are only shown while their captain
270
- * session is the one currently open. */
271
- export function ActivityPanel({ sessionsList, openSession }) {
289
+ export function ActivityPanel({ sessionsList, openMember, t }) {
272
290
  // Navigating to a member's subagent transcript is an explicit departure:
273
291
  // hide the floater immediately instead of waiting out the autocollapse
274
292
  // grace, so the panel never lingers over the member session.
275
- const navigateToSession = (id) => {
293
+ const navigateToSession = (parentId, childId) => {
276
294
  setOpen(false);
277
295
  setWasActive(false);
278
- openSession(id);
296
+ openMember(parentId, childId);
279
297
  };
280
298
  const [open, setOpen] = useState(false);
281
299
  const [openOwner, setOpenOwner] = useState();
@@ -383,13 +401,14 @@ export function ActivityPanel({ sessionsList, openSession }) {
383
401
  };
384
402
  }, [compact, expanded, geometry.mode, geometry.width]);
385
403
  useEffect(() => {
386
- // Installing the plugin alone must not touch the state route. A successful
387
- // AgentTeams conversation card registers the demand that reaches here.
388
- if (currentTargets.length === 0)
404
+ if (current === undefined)
389
405
  return;
390
- const controller = startActivityPolling(currentTargets);
406
+ // Cards keep live teams on the normal cadence. The current-session scope
407
+ // also performs one cold-start discovery pass so archived/cardless teams
408
+ // survive a browser or `dsh web` restart.
409
+ const controller = startActivityPolling(currentTargets, { discoverySessionId: current });
391
410
  return () => { controller.stop(); };
392
- }, [currentTargets]);
411
+ }, [current, currentTargets]);
393
412
  useEffect(() => {
394
413
  const onOpenPanel = (event) => {
395
414
  const activeSession = currentRef.current;
@@ -570,18 +589,18 @@ export function ActivityPanel({ sessionsList, openSession }) {
570
589
  };
571
590
  if (!hasTeams && !expanded)
572
591
  return null;
573
- return (_jsxs(_Fragment, { children: [!expanded && (_jsx(CollapsedBadge, { count: visibleCount, busy: busy, onClick: () => {
592
+ return (_jsxs(_Fragment, { children: [!expanded && (_jsx(CollapsedBadge, { count: visibleCount, busy: busy, t: t, onClick: () => {
574
593
  if (current === undefined)
575
594
  return;
576
595
  setOpenOwner(current);
577
596
  setOpen(true);
578
- } })), expanded && (_jsxs("aside", { ref: panelRef, className: css.panel, style: panelStyle, "data-agent-teams-activity": true, "data-panel-mode": geometry.mode, "data-height-mode": autoHeight ? 'auto' : 'manual', "data-compact": compact || undefined, "data-dragging": interaction === 'dragging' || undefined, "data-resizing": interaction === 'resizing' || undefined, "aria-label": "AgentTeams \u6D3B\u52A8\u9762\u677F", children: [_jsxs("header", { className: css.panelHead, onPointerDown: beginMove, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "data-drag-handle": !compact || undefined, children: [_jsxs("span", { className: css.panelTitle, children: ["AgentTeams \u6D3B\u52A8", _jsx("span", { className: css.panelDot, "data-busy": busy, "aria-hidden": true })] }), _jsxs("span", { className: css.panelControls, children: [!compact && (_jsx("button", { type: "button", className: css.iconButton, "data-control": "dock", "data-mode": geometry.mode, onClick: toggleDock, "aria-label": geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', title: geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', children: _jsx(IconPanelLeftOutline16, {}) })), _jsx("button", { type: "button", className: css.iconButton, "data-control": "collapse", onClick: () => {
597
+ } })), 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": t('activity.panelAria'), 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: [t('activity.title'), _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": t(geometry.mode === 'docked' ? 'activity.float' : 'activity.dockRight'), title: t(geometry.mode === 'docked' ? 'activity.float' : 'activity.dockRight'), children: _jsx(IconPanelLeftOutline16, {}) })), _jsx("button", { type: "button", className: css.iconButton, "data-control": "collapse", onClick: () => {
579
598
  setOpen(false);
580
599
  setOpenOwner(undefined);
581
- }, "aria-label": "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", title: "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", children: _jsx(IconChevronDownOutline14, {}) })] })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
582
- ? _jsx("span", { className: css.emptyHint, children: "\u6682\u65E0\u56E2\u961F\u6D3B\u52A8" })
583
- : (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team, onNavigate: navigateToSession }, team.teamId))), visibleArchived.map((team) => (_jsx("div", { "data-team-id": team.teamId, "data-historic": true, className: css.archivedWrap, children: _jsx(TeamSection, { team: team, onNavigate: navigateToSession, historic: true }) }, `${team.captainSessionId}:${team.teamId}`))), visibleHistoric.map(({ data: team, owner }) => {
600
+ }, "aria-label": t('activity.collapse'), title: t('activity.collapse'), children: _jsx(IconChevronDownOutline14, {}) })] })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
601
+ ? _jsx("span", { className: css.emptyHint, children: t('activity.empty') })
602
+ : (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team, onNavigate: navigateToSession, t: t }, team.teamId))), visibleArchived.map((team) => (_jsxs("div", { "data-team-id": team.teamId, "data-historic": true, className: css.archivedWrap, children: [_jsx("span", { className: css.archiveLabel, children: t('archive.label') }), _jsx(TeamSection, { team: team, onNavigate: navigateToSession, t: t, historic: true })] }, `${team.captainSessionId}:${team.teamId}`))), visibleHistoric.map(({ data: team, owner }) => {
584
603
  const teamKey = `${owner}:${team.teamId}`;
585
- return (_jsx(TeamSection, { team: historicCardTeam(team, owner), onNavigate: navigateToSession, historic: true }, teamKey));
604
+ return (_jsx(TeamSection, { team: historicCardTeam(team, owner), onNavigate: navigateToSession, t: t, historic: true }, teamKey));
586
605
  })] })) }), !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 })] }))] }))] }));
587
606
  }
@@ -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, openSession, sessionId }) {
33
+ export function AgentTeamsCard({ node, openMember, sessionId, t }) {
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: () => { if (member.id !== '')
51
- openSession(member.id); }, 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))) }))] }));
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 }), _jsx("span", { className: css.memberCount, children: t('card.memberCount', { count: resolved.members.length }) }), _jsx("button", { type: "button", className: css.panelButton, onClick: () => { openActivityPanel(resolved); }, "aria-label": t('action.openActivityPanel'), title: t('action.openActivityPanel'), children: t('activity.panelButton') })] }), 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
  }
@@ -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 no targets this is deliberately inert: installing the plugin must not
108
- * touch the state route. Live state is polled at the normal cadence; archive
109
- * state is fetched only as a one-time fallback for targets no longer live.
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
- if (monitorTargets.length === 0) {
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
- if (missing.length === 0)
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 retires every missing
144
- // target, including legacy cards whose host archive no longer exists.
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) {
@@ -2,8 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ActivityPanel } from "./ActivityPanel.js";
3
3
  import { AgentTeamsCard } from "./AgentTeamsCard.js";
4
4
  import { agentTeamsCardDefinition } from "./agent-teams-card-definition.js";
5
- /** Required services: conversation nodes, slots, and sessions navigation. */
6
- export const inject = ['conversationEvents', 'slots', 'sessions'];
5
+ import { AGENT_TEAMS_LOCALE_NAMESPACE, en, zh, } from "./locales.js";
6
+ import { openAgentTeamMember } from "./session-navigation.js";
7
+ /** Required services: conversation nodes, slots, sessions navigation, and locale. */
8
+ export const inject = ['conversationEvents', 'slots', 'sessions', 'locale'];
7
9
  /** The replayed user message is the canonical transcript entry. */
8
10
  function HiddenAgentTeamsCommand() {
9
11
  return null;
@@ -14,12 +16,19 @@ function HiddenAgentTeamsCommand() {
14
16
  * monitor via a window event — the recovery path for an old session.
15
17
  */
16
18
  export function apply(ctx) {
17
- const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openSession: (id) => { ctx.sessions.open(id); } });
19
+ ctx.effect(() => ctx.locale.register(AGENT_TEAMS_LOCALE_NAMESPACE, { zh, en }), 'agent-teams: dictionaries');
20
+ const openMember = (parentId, childId) => {
21
+ void openAgentTeamMember(ctx.sessions, parentId, childId).catch((error) => {
22
+ console.warn(`agent-teams: failed to open member transcript ${childId}: ${String(error)}`);
23
+ });
24
+ };
25
+ const Panel = ({ t }) => (_jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openMember: openMember, t: t }));
18
26
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
19
27
  name: 'shell.overlay',
20
28
  id: 'agent-teams-activity',
21
29
  order: 80,
22
30
  label: 'AgentTeams activity',
31
+ locale: AGENT_TEAMS_LOCALE_NAMESPACE,
23
32
  }, Panel));
24
33
  // The host command is only the slash-menu/admission surface. Its input is
25
34
  // replayed as the visible user message, so the generic result row would be
@@ -32,8 +41,9 @@ export function apply(ctx) {
32
41
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
33
42
  name: 'conversation.chat.node',
34
43
  key: 'agent-teams',
44
+ locale: AGENT_TEAMS_LOCALE_NAMESPACE,
35
45
  inject: () => ({
36
- openSession: (id) => { ctx.sessions.open(id); },
46
+ openMember,
37
47
  }),
38
48
  }, AgentTeamsCard));
39
49
  }