@nanmicoder/dsh-agent-teams 0.1.11 → 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,37 +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
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: () => {
240
258
  if (member.id !== '') {
241
259
  onNavigate(team.captainSessionId, member.id);
242
260
  }
243
- }, children: [_jsxs("span", { className: css.memberAvatar, "data-unread": member.unread > 0, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, style: { background: accentOf(member.id) }, children: memberInitial(member.name) })), _jsx("img", { className: css.stateArt, "data-activity": member.activity, src: ACTION_ART[member.activity], alt: "", "aria-hidden": true })] }), _jsxs("span", { className: css.memberInfo, children: [_jsxs("span", { className: css.memberLine, children: [_jsx("span", { className: css.memberName, children: member.name }), member.role !== '' && _jsx("span", { className: css.memberRole, children: member.role }), _jsxs("span", { className: css.memberState, "data-activity": member.activity, children: [_jsx(WorkGlyph, { active: member.activity === 'working' }), memberStateLabel(member, team.tasks, historic)] })] }), _jsx("span", { className: css.memberStatusLine, children: memberStatusText(member, team.tasks) })] }), _jsxs("span", { className: css.memberCount, children: [member.done, "/", member.total] })] }), _jsxs("div", { className: css.assignmentLine, children: [_jsx("span", { className: css.assignmentLabel, children: "\u961F\u957F\u6D3E\u53D1" }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
244
- ? _jsx("span", { className: css.taskEmpty, children: "\u6682\u65E0\u4EFB\u52A1" })
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') })
245
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));
246
- })] })] }), _jsx(DependencyMap, { tasks: team.tasks })] }));
264
+ })] })] }), _jsx(DependencyMap, { tasks: team.tasks, t: t })] }));
247
265
  }
248
266
  /** Legacy conversation cards may outlive their host archive. Project their
249
267
  * durable roster through the same rebuilt panel instead of a second UI. */
@@ -268,10 +286,7 @@ function historicCardTeam(data, owner) {
268
286
  captainInbox: [],
269
287
  };
270
288
  }
271
- /** The top-right activity floater. Teams follow the current session: live
272
- * snapshots and historic card summaries are only shown while their captain
273
- * session is the one currently open. */
274
- export function ActivityPanel({ sessionsList, openMember }) {
289
+ export function ActivityPanel({ sessionsList, openMember, t }) {
275
290
  // Navigating to a member's subagent transcript is an explicit departure:
276
291
  // hide the floater immediately instead of waiting out the autocollapse
277
292
  // grace, so the panel never lingers over the member session.
@@ -574,18 +589,18 @@ export function ActivityPanel({ sessionsList, openMember }) {
574
589
  };
575
590
  if (!hasTeams && !expanded)
576
591
  return null;
577
- 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: () => {
578
593
  if (current === undefined)
579
594
  return;
580
595
  setOpenOwner(current);
581
596
  setOpen(true);
582
- } })), expanded && (_jsxs("aside", { ref: panelRef, className: css.panel, style: panelStyle, "data-agent-teams-activity": true, "data-panel-mode": geometry.mode, "data-height-mode": autoHeight ? 'auto' : 'manual', "data-compact": compact || undefined, "data-dragging": interaction === 'dragging' || undefined, "data-resizing": interaction === 'resizing' || undefined, "aria-label": "AgentTeams \u6D3B\u52A8\u9762\u677F", children: [_jsxs("header", { className: css.panelHead, onPointerDown: beginMove, onPointerMove: updateGesture, onPointerUp: endGesture, onPointerCancel: cancelGesture, "data-drag-handle": !compact || undefined, children: [_jsxs("span", { className: css.panelTitle, children: ["AgentTeams \u6D3B\u52A8", _jsx("span", { className: css.panelDot, "data-busy": busy, "aria-hidden": true })] }), _jsxs("span", { className: css.panelControls, children: [!compact && (_jsx("button", { type: "button", className: css.iconButton, "data-control": "dock", "data-mode": geometry.mode, onClick: toggleDock, "aria-label": geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', title: geometry.mode === 'docked' ? '切换为浮动面板' : '停靠到右侧', children: _jsx(IconPanelLeftOutline16, {}) })), _jsx("button", { type: "button", className: css.iconButton, "data-control": "collapse", onClick: () => {
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: () => {
583
598
  setOpen(false);
584
599
  setOpenOwner(undefined);
585
- }, "aria-label": "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", title: "\u6536\u8D77\u6D3B\u52A8\u9762\u677F", children: _jsx(IconChevronDownOutline14, {}) })] })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
586
- ? _jsx("span", { className: css.emptyHint, children: "\u6682\u65E0\u56E2\u961F\u6D3B\u52A8" })
587
- : (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team, onNavigate: navigateToSession }, team.teamId))), visibleArchived.map((team) => (_jsx("div", { "data-team-id": team.teamId, "data-historic": true, className: css.archivedWrap, children: _jsx(TeamSection, { team: team, onNavigate: navigateToSession, historic: true }) }, `${team.captainSessionId}:${team.teamId}`))), visibleHistoric.map(({ data: team, owner }) => {
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 }) => {
588
603
  const teamKey = `${owner}:${team.teamId}`;
589
- 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));
590
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 })] }))] }))] }));
591
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, openMember, 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,7 +47,7 @@ export function AgentTeamsCard({ node, openMember, 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: () => {
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
51
  if (member.id !== '')
52
52
  openMember(owner, member.id);
53
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))) }))] }));
@@ -2,9 +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
+ import { AGENT_TEAMS_LOCALE_NAMESPACE, en, zh, } from "./locales.js";
5
6
  import { openAgentTeamMember } from "./session-navigation.js";
6
- /** Required services: conversation nodes, slots, and sessions navigation. */
7
- export const inject = ['conversationEvents', 'slots', 'sessions'];
7
+ /** Required services: conversation nodes, slots, sessions navigation, and locale. */
8
+ export const inject = ['conversationEvents', 'slots', 'sessions', 'locale'];
8
9
  /** The replayed user message is the canonical transcript entry. */
9
10
  function HiddenAgentTeamsCommand() {
10
11
  return null;
@@ -15,17 +16,19 @@ function HiddenAgentTeamsCommand() {
15
16
  * monitor via a window event — the recovery path for an old session.
16
17
  */
17
18
  export function apply(ctx) {
19
+ ctx.effect(() => ctx.locale.register(AGENT_TEAMS_LOCALE_NAMESPACE, { zh, en }), 'agent-teams: dictionaries');
18
20
  const openMember = (parentId, childId) => {
19
21
  void openAgentTeamMember(ctx.sessions, parentId, childId).catch((error) => {
20
22
  console.warn(`agent-teams: failed to open member transcript ${childId}: ${String(error)}`);
21
23
  });
22
24
  };
23
- const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openMember: openMember });
25
+ const Panel = ({ t }) => (_jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openMember: openMember, t: t }));
24
26
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
25
27
  name: 'shell.overlay',
26
28
  id: 'agent-teams-activity',
27
29
  order: 80,
28
30
  label: 'AgentTeams activity',
31
+ locale: AGENT_TEAMS_LOCALE_NAMESPACE,
29
32
  }, Panel));
30
33
  // The host command is only the slash-menu/admission surface. Its input is
31
34
  // replayed as the visible user message, so the generic result row would be
@@ -38,6 +41,7 @@ export function apply(ctx) {
38
41
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
39
42
  name: 'conversation.chat.node',
40
43
  key: 'agent-teams',
44
+ locale: AGENT_TEAMS_LOCALE_NAMESPACE,
41
45
  inject: () => ({
42
46
  openMember,
43
47
  }),
@@ -0,0 +1,165 @@
1
+ /** `agentTeams` namespace dictionaries for every plugin-owned Web surface. */
2
+ /** Dictionary namespace owned by the AgentTeams client plugin. */
3
+ export const AGENT_TEAMS_LOCALE_NAMESPACE = 'agentTeams';
4
+ /** Simplified Chinese dictionary (the key-set source of truth). */
5
+ export const zh = {
6
+ 'card.memberCount': '{count} 名成员',
7
+ 'action.openActivityPanel': '打开活动面板',
8
+ 'activity.panelButton': '活动面板',
9
+ 'activity.badgeAria': 'AgentTeams 活动,{count} 个团队',
10
+ 'activity.panelAria': 'AgentTeams 活动面板',
11
+ 'activity.title': 'AgentTeams 活动',
12
+ 'activity.float': '切换为浮动面板',
13
+ 'activity.dockRight': '停靠到右侧',
14
+ 'activity.collapse': '收起活动面板',
15
+ 'activity.empty': '暂无团队活动',
16
+ 'format.listSeparator': '、',
17
+ 'task.status.pending': '待领取',
18
+ 'task.status.claimed': '已认领',
19
+ 'task.status.inProgress': '进行中',
20
+ 'task.status.completed': '已完成',
21
+ 'task.status.failed': '失败',
22
+ 'task.status.cancelled': '已取消',
23
+ 'member.state.working': '工作中',
24
+ 'member.state.failed': '有失败',
25
+ 'member.state.waiting': '等待',
26
+ 'member.state.delivered': '已交付',
27
+ 'member.state.left': '已离队',
28
+ 'member.state.removed': '已移除',
29
+ 'member.state.pending': '待执行',
30
+ 'member.state.unassigned': '待派工',
31
+ 'member.status.executing': '正在执行 {taskId}',
32
+ 'member.status.working': '正在处理已派任务',
33
+ 'member.status.waitingOn': '等待 {taskId} · {assignee}',
34
+ 'member.status.waitingPrerequisite': '等待前置任务',
35
+ 'member.status.waitingAssignment': '等待队长派工',
36
+ 'member.status.delivered': '任务已交付',
37
+ 'member.status.idle': '待继续执行',
38
+ 'member.status.unknown': '状态未知',
39
+ 'task.assignee.unclaimed': '待认领',
40
+ 'task.summary.waitingBreakdown': '等待队长拆解任务',
41
+ 'task.summary.allDelivered': '全部 {count} 项任务已交付',
42
+ 'task.summary.blockedAndRunning': '{tasks}{more} 等待前置,其余已开工',
43
+ 'task.summary.more': ' 等 {count} 项',
44
+ 'task.summary.running': '{tasks} 正在执行',
45
+ 'task.summary.ready': '{tasks} 已就绪待开工',
46
+ 'task.summary.blocked': '{tasks} 等待前置',
47
+ 'task.summary.waitingSchedule': '等待下一轮调度',
48
+ 'progress.aria': '团队总进度',
49
+ 'progress.title': '总进度',
50
+ 'progress.running': '■ 进行中 {count}',
51
+ 'progress.blocked': '■ 等待依赖 {count}',
52
+ 'progress.delivered': '■ 已交付 {count}',
53
+ 'dependency.aria': '任务依赖链',
54
+ 'dependency.parallel': '并行任务',
55
+ 'dependency.title': '任务依赖',
56
+ 'dependency.hint.parallel': '无前后依赖 · 点击查看详情',
57
+ 'dependency.hint.chain': '悬停高亮依赖链 · 点击固定',
58
+ 'dependency.hint.pinned': '{taskId} 已固定 · Esc 取消',
59
+ 'task.runningAria': '运行中',
60
+ 'task.detail.completed': '已完成并交付',
61
+ 'task.detail.noPrerequisite': '无前置,可立即开工',
62
+ 'task.detail.ready': '前置已就绪,可开工',
63
+ 'task.detail.waitingOn': '等待 {tasks}',
64
+ 'task.detail.noDownstream': '无下游任务',
65
+ 'task.detail.unlocks': '完成后解锁 {tasks}',
66
+ 'team.ended': '已结束',
67
+ 'team.stats.members': '{count} 名成员',
68
+ 'team.stats.completed': '{completed}/{total} 完成',
69
+ 'team.stats.messages': '{count} 条消息',
70
+ 'delegation.aria': '队长派工关系',
71
+ 'captain.name': '队长',
72
+ 'captain.role': '拆解 · 派发 · 汇总',
73
+ 'captain.summary': '已派发 {tasks} 项任务给 {members} 名成员',
74
+ 'captain.state.working': '{count} 人执行中',
75
+ 'captain.state.collected': '已收齐',
76
+ 'captain.state.waiting': '等待回报',
77
+ 'members.toggle': '{count} 名成员',
78
+ 'members.collapse': '收起',
79
+ 'members.expand': '展开',
80
+ 'members.empty': '暂无成员,等待队长组建团队',
81
+ 'assignment.label': '队长派发',
82
+ 'assignment.empty': '暂无任务',
83
+ 'archive.label': '已结束 · 历史归档',
84
+ };
85
+ /** English dictionary, checked complete against the Chinese source key set. */
86
+ export const en = {
87
+ 'card.memberCount': '{count} members',
88
+ 'action.openActivityPanel': 'Open activity panel',
89
+ 'activity.panelButton': 'Activity panel',
90
+ 'activity.badgeAria': 'AgentTeams activity, {count} teams',
91
+ 'activity.panelAria': 'AgentTeams activity panel',
92
+ 'activity.title': 'AgentTeams activity',
93
+ 'activity.float': 'Switch to floating panel',
94
+ 'activity.dockRight': 'Dock to the right',
95
+ 'activity.collapse': 'Collapse activity panel',
96
+ 'activity.empty': 'No team activity',
97
+ 'format.listSeparator': ', ',
98
+ 'task.status.pending': 'Unclaimed',
99
+ 'task.status.claimed': 'Claimed',
100
+ 'task.status.inProgress': 'In progress',
101
+ 'task.status.completed': 'Completed',
102
+ 'task.status.failed': 'Failed',
103
+ 'task.status.cancelled': 'Cancelled',
104
+ 'member.state.working': 'Working',
105
+ 'member.state.failed': 'Has failures',
106
+ 'member.state.waiting': 'Waiting',
107
+ 'member.state.delivered': 'Delivered',
108
+ 'member.state.left': 'Left team',
109
+ 'member.state.removed': 'Removed',
110
+ 'member.state.pending': 'Pending',
111
+ 'member.state.unassigned': 'Awaiting assignment',
112
+ 'member.status.executing': 'Working on {taskId}',
113
+ 'member.status.working': 'Working on assigned tasks',
114
+ 'member.status.waitingOn': 'Waiting for {taskId} · {assignee}',
115
+ 'member.status.waitingPrerequisite': 'Waiting for prerequisites',
116
+ 'member.status.waitingAssignment': 'Waiting for the captain to assign work',
117
+ 'member.status.delivered': 'Tasks delivered',
118
+ 'member.status.idle': 'Ready to continue',
119
+ 'member.status.unknown': 'Status unknown',
120
+ 'task.assignee.unclaimed': 'Unclaimed',
121
+ 'task.summary.waitingBreakdown': 'Waiting for the captain to break down the work',
122
+ 'task.summary.allDelivered': 'All {count} tasks delivered',
123
+ 'task.summary.blockedAndRunning': '{tasks}{more} waiting on prerequisites; other work has started',
124
+ 'task.summary.more': ' and {count} more',
125
+ 'task.summary.running': '{tasks} in progress',
126
+ 'task.summary.ready': '{tasks} ready to start',
127
+ 'task.summary.blocked': '{tasks} waiting on prerequisites',
128
+ 'task.summary.waitingSchedule': 'Waiting for the next scheduling round',
129
+ 'progress.aria': 'Overall team progress',
130
+ 'progress.title': 'Overall progress',
131
+ 'progress.running': '■ In progress {count}',
132
+ 'progress.blocked': '■ Waiting {count}',
133
+ 'progress.delivered': '■ Delivered {count}',
134
+ 'dependency.aria': 'Task dependency chain',
135
+ 'dependency.parallel': 'Parallel tasks',
136
+ 'dependency.title': 'Task dependencies',
137
+ 'dependency.hint.parallel': 'No dependencies · Click for details',
138
+ 'dependency.hint.chain': 'Hover to highlight dependencies · Click to pin',
139
+ 'dependency.hint.pinned': '{taskId} pinned · Esc to clear',
140
+ 'task.runningAria': 'Running',
141
+ 'task.detail.completed': 'Completed and delivered',
142
+ 'task.detail.noPrerequisite': 'No prerequisites; ready to start',
143
+ 'task.detail.ready': 'Prerequisites ready; can start',
144
+ 'task.detail.waitingOn': 'Waiting for {tasks}',
145
+ 'task.detail.noDownstream': 'No downstream tasks',
146
+ 'task.detail.unlocks': 'Unlocks {tasks} when complete',
147
+ 'team.ended': 'Ended',
148
+ 'team.stats.members': '{count} members',
149
+ 'team.stats.completed': '{completed}/{total} completed',
150
+ 'team.stats.messages': '{count} messages',
151
+ 'delegation.aria': 'Captain delegation map',
152
+ 'captain.name': 'Captain',
153
+ 'captain.role': 'Break down · Delegate · Synthesize',
154
+ 'captain.summary': 'Assigned {tasks} tasks to {members} members',
155
+ 'captain.state.working': '{count} active',
156
+ 'captain.state.collected': 'All reports received',
157
+ 'captain.state.waiting': 'Waiting for reports',
158
+ 'members.toggle': 'Members {count}',
159
+ 'members.collapse': 'Collapse',
160
+ 'members.expand': 'Expand',
161
+ 'members.empty': 'No members yet; waiting for the captain to assemble the team',
162
+ 'assignment.label': 'Captain assigned',
163
+ 'assignment.empty': 'No tasks',
164
+ 'archive.label': 'Ended · Archived history',
165
+ };