@nanmicoder/dsh-agent-teams 0.1.11 → 0.1.13

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
@@ -76,7 +78,7 @@ Then ask for a team directly:
76
78
  1. The current session creates a team and becomes its captain.
77
79
  2. The captain adds role-specific members backed by continuable sub-agents.
78
80
  3. The goal becomes tasks with owners and explicit dependencies.
79
- 4. The shared scheduler uses real `running / idle / ready` state to atomically claim one ready task per idle member and wake it. If an idle/ready member still owns an open task after an interrupted turn or process restart, the scheduler retries it with a fresh attempt.
81
+ 4. The shared scheduler uses real `running / idle / ready` state to atomically claim one ready task per idle member and wake it. An interrupted resident attempt stays parked and can resume through a direct message without losing its capability; after a cold process restart, the scheduler retries stranded open work with a fresh attempt.
80
82
  5. Members update with the current `attempt_id`; reassignment or captain takeover revokes the old attempt and waits for the old worker to quiesce before a new attempt starts.
81
83
  6. The captain presents the combined result, then archives the complete team record.
82
84
 
@@ -127,7 +129,7 @@ Defaults work without extra setup. A trusted profile can override member behavio
127
129
  ## Boundaries
128
130
 
129
131
  - One captain leads one active team at a time.
130
- - Idle members are automatically reused for ready work; messages that cannot be delivered live remain durable and are retried at a later status boundary.
132
+ - Idle members with no open task are automatically reused for ready work. An idle member that still owns an open attempt is parked until messaged or explicitly reassigned; messages that cannot be delivered live remain durable and are retried at a later status boundary.
131
133
  - State is file-backed and serialized within one DSH process; concurrent processes editing the same team are not coordinated.
132
134
  - The activity panel reports persisted state as-is. Models may occasionally finish work without performing the expected task-state update.
133
135
 
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
  ### 从源码构建
@@ -76,7 +78,7 @@ dsh web
76
78
  1. 当前会话创建团队并成为队长。
77
79
  2. 队长按角色添加由可续聊子 Agent 驱动的成员。
78
80
  3. 目标被拆成有负责人和显式依赖的任务。
79
- 4. 共享调度器依据真实 `running / idle / ready` 状态,为每个空闲成员原子领取一项就绪任务并唤醒它;成员在中断或进程重启后仍持有开放任务时,会以新 attempt 自动恢复执行。
81
+ 4. 共享调度器依据真实 `running / idle / ready` 状态,为每个空闲成员原子领取一项就绪任务并唤醒它;驻留成员被中断时会停驻当前 attempt,可通过直接消息继续而不丢 capability;只有冷进程重启后的遗留任务才会生成新 attempt 恢复。
80
82
  5. 成员携带当前 `attempt_id` 更新任务;转派或队长接管会先撤销旧 attempt、等待原成员安静,再启动新 attempt。
81
83
  6. 队长汇总结果,随后归档完整团队记录。
82
84
 
@@ -117,7 +119,7 @@ dsh web
117
119
  ## 使用边界
118
120
 
119
121
  - 一个队长同一时间只能带一个活动团队。
120
- - 成员空闲后由共享调度器自动续领就绪任务;中断/冷重启遗留的开放任务会生成新 attempt 并重新唤醒原成员;暂时无法实时投递的消息会持久保存在邮箱中并在后续状态边界重投。
122
+ - 没有开放任务的空闲成员会自动续领就绪任务;仍持有开放 attempt 的空闲成员会停驻,队长可发消息让其沿用原 attempt 继续,或显式转派;冷重启遗留的开放任务才会生成新 attempt。暂时无法实时投递的消息会持久保存在邮箱中并在后续状态边界重投。
121
123
  - 状态使用文件持久化,并在单个 DSH 进程内串行操作;多个进程同时修改同一团队不保证一致。
122
124
  - 活动面板如实展示持久化状态;模型偶尔可能完成工作却没有按协议更新任务状态。
123
125
 
@@ -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))) }))] }));
@@ -99,18 +99,30 @@ export function updateActivitySnapshots(update) {
99
99
  }
100
100
  /** Poll cadence for the live host snapshot route. */
101
101
  export const ACTIVITY_POLL_MS = 1000;
102
+ /**
103
+ * Low-frequency probe cadence while a cardless discovery session still owns
104
+ * no team. The probe keeps the panel able to pick up a team created later in
105
+ * that session (e.g. a run_code-wrapped agent_teams_create) without turning
106
+ * every ordinary session into a one-second filesystem scan.
107
+ */
108
+ export const ACTIVITY_PROBE_MS = 5000;
102
109
  /** Host route serving live and archived team snapshots. */
103
110
  export const ACTIVITY_STATE_URL = '/plugins/dsh-agent-teams/state';
104
111
  /**
105
112
  * Start the single polling loop for the current session's requested targets.
106
113
  *
107
114
  * 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.
115
+ * Explicit card targets poll at the live cadence from the start. A discovery
116
+ * session performs an immediate live+archive restore pass, then while it
117
+ * still owns no team probes on a low-frequency cadence, so a team created
118
+ * later in that session (e.g. a run_code-wrapped agent_teams_create) is
119
+ * discovered without a manual reload, without turning every ordinary session
120
+ * into a one-second filesystem scan. The moment a team for the discovery
121
+ * session appears, the controller upgrades to the live one-second cadence for
122
+ * the rest of its lifetime. The caller — the session view, which stops the
123
+ * controller when the session is no longer current — bounds the lifetime, and
124
+ * archive state is refreshed when a target or a previously discovered live
125
+ * team disappears.
114
126
  */
115
127
  export function startActivityPolling(monitorTargets, runtime = {}) {
116
128
  const discoverySessionId = runtime.discoverySessionId?.trim();
@@ -124,16 +136,21 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
124
136
  const settleTargets = runtime.settleTargets ?? settleActivityMonitorTargets;
125
137
  let cancelled = false;
126
138
  let inFlight = false;
139
+ // Explicit card targets are demanded work: start at the live cadence. A
140
+ // discovery session starts probing low-frequency and upgrades on detection.
141
+ let hot = monitorTargets.length > 0;
127
142
  let discoveryComplete = false;
128
143
  let discoveredLiveKeys = new Set();
129
144
  let controller;
145
+ let timer;
146
+ const intervalMs = () => (hot ? ACTIVITY_POLL_MS : ACTIVITY_PROBE_MS);
147
+ const reschedule = () => {
148
+ cancel(timer);
149
+ timer = schedule(() => { void tick(); }, intervalMs());
150
+ };
130
151
  const tick = async () => {
131
152
  if (inFlight || cancelled)
132
153
  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;
137
154
  inFlight = true;
138
155
  controller = new AbortController();
139
156
  try {
@@ -154,6 +171,12 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
154
171
  : liveTeams
155
172
  .filter((team) => team.captainSessionId === discoverySessionId)
156
173
  .map((team) => team.teamId));
174
+ // A discovery session found its first team: upgrade from the low-frequency
175
+ // probe to the live cadence for the rest of the controller lifetime.
176
+ if (!hot && discoveredLiveKeys.size > 0) {
177
+ hot = true;
178
+ reschedule();
179
+ }
157
180
  const discoveredTeamArchived = [...previousDiscoveredKeys]
158
181
  .some((teamId) => !discoveredLiveKeys.has(teamId));
159
182
  const missing = monitorTargets.filter((target) => !liveTeams.some((team) => team.captainSessionId === target.sessionId && team.teamId === target.teamId));
@@ -164,8 +187,9 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
164
187
  return;
165
188
  // Archives are immutable per team generation. A successful fallback
166
189
  // 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.
190
+ // host archive no longer exists; a discovery session that already
191
+ // upgraded keeps polling, and a still-probing one keeps probing, so a
192
+ // team created later in the same session stays discoverable.
169
193
  const archivedResponse = await fetchState(`${ACTIVITY_STATE_URL}?archived=1`, {
170
194
  cache: 'no-store',
171
195
  signal: controller.signal,
@@ -189,7 +213,8 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
189
213
  }
190
214
  };
191
215
  const firstTick = tick();
192
- const timer = schedule(() => { void tick(); }, ACTIVITY_POLL_MS);
216
+ if (timer === undefined)
217
+ timer = schedule(() => { void tick(); }, intervalMs());
193
218
  return {
194
219
  firstTick,
195
220
  stop: () => {
@@ -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
  }),