@nanmicoder/dsh-agent-teams 0.1.19 → 0.1.21
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 +19 -12
- package/README_ZH.md +19 -12
- package/assets/readme/workspace.png +0 -0
- package/compatibility.json +14 -2
- package/lib/capabilities.js +3 -1
- package/lib/client/ActivityPanel.js +80 -51
- package/lib/client/AgentTeamsCard.js +14 -5
- package/lib/client/TeamChatEntry.js +22 -0
- package/lib/client/WorkspaceActivity.js +100 -0
- package/lib/client/activity-model.js +73 -8
- package/lib/client/activity-monitor.js +11 -4
- package/lib/client/agent-teams-card-definition.js +11 -1
- package/lib/client/icons.js +18 -0
- package/lib/client/index.js +35 -4
- package/lib/client/locales.js +44 -0
- package/lib/client/session-navigation.js +13 -1
- package/lib/client/workspace-state.js +43 -0
- package/lib/client.js +832 -210
- package/lib/client.js.map +1 -1
- package/lib/harness-compat.js +20 -3
- package/lib/index.js +1 -1
- package/lib/mailbox.js +23 -3
- package/lib/members.js +3 -3
- package/lib/quality-gates.js +54 -0
- package/lib/scheduler.js +8 -2
- package/lib/snapshot.js +1 -0
- package/lib/state.js +15 -3
- package/lib/tools.js +71 -22
- package/lib/types/capabilities.d.ts +0 -1
- package/lib/types/client/ActivityPanel.d.ts +18 -1
- package/lib/types/client/AgentTeamsCard.d.ts +14 -2
- package/lib/types/client/TeamChatEntry.d.ts +4 -0
- package/lib/types/client/WorkspaceActivity.d.ts +22 -0
- package/lib/types/client/activity-model.d.ts +37 -1
- package/lib/types/client/activity-monitor.d.ts +4 -0
- package/lib/types/client/agent-teams-card-definition.d.ts +8 -0
- package/lib/types/client/icons.d.ts +5 -0
- package/lib/types/client/locales.d.ts +44 -0
- package/lib/types/client/session-navigation.d.ts +13 -2
- package/lib/types/client/workspace-state.d.ts +19 -0
- package/lib/types/harness-compat.d.ts +14 -1
- package/lib/types/mailbox.d.ts +4 -0
- package/lib/types/quality-gates.d.ts +7 -1
- package/lib/types/scheduler.d.ts +1 -0
- package/lib/types/snapshot.d.ts +2 -0
- package/lib/types/types.d.ts +16 -0
- package/package.json +53 -50
- package/release-notes/v0.1.20.md +20 -0
- package/release-notes/v0.1.21-rc.1.md +14 -0
- package/release-notes/v0.1.21.md +23 -0
package/README.md
CHANGED
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
Ask in natural language. The plugin provides the team protocol, 14 coordination tools, persistent state, an automatic shared-task scheduler, and a live Web UI—without requiring a separate workflow engine.
|
|
26
26
|
|
|
27
27
|
<p align="center">
|
|
28
|
-
<img src="./assets/
|
|
28
|
+
<img src="./assets/readme/workspace.png" width="100%" alt="AgentTeams native workspace with members, task dependencies, and progress">
|
|
29
29
|
</p>
|
|
30
30
|
|
|
31
31
|
## Releases
|
|
32
32
|
|
|
33
|
-
[v0.1.
|
|
33
|
+
[v0.1.21](./release-notes/v0.1.21.md) brings teams into the native Harness workspace, restores **View team** in chat, and fixes narrow layouts. This is the npm `latest` release; the recommended host is Harness `0.1.7-rc.2` (its `next` channel). See the exact [support matrix](./compatibility.json).
|
|
34
34
|
|
|
35
35
|
## Why AgentTeams?
|
|
36
36
|
|
|
@@ -49,19 +49,20 @@ The conversation card and activity panel use Harness's official locale service.
|
|
|
49
49
|
|
|
50
50
|
## Install and choose versions
|
|
51
51
|
|
|
52
|
-
**Recommended pair: DeepSeek Harness `0.1.
|
|
52
|
+
**Recommended pair: DeepSeek Harness `0.1.7-rc.2` + AgentTeams `0.1.21`. Harness remains a prerelease.**
|
|
53
53
|
|
|
54
54
|
| Use case | DeepSeek Harness | AgentTeams plugin |
|
|
55
55
|
| --- | --- | --- |
|
|
56
|
-
| **Recommended
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
56
|
+
| **Recommended** | **`0.1.7-rc.2`** | **`0.1.21`** |
|
|
57
|
+
| Legacy RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.21` |
|
|
58
|
+
| Retaining an older RC | `0.1.2-rc.1` | `0.1.21` |
|
|
59
|
+
| Developer Alpha testing | `0.1.2-alpha.5` | `0.1.21` |
|
|
60
|
+
| Retaining an older Alpha | `0.1.2-alpha.2` | `0.1.21` |
|
|
60
61
|
|
|
61
62
|
### 1. Install DeepSeek Harness
|
|
62
63
|
|
|
63
64
|
```sh
|
|
64
|
-
npm install --global @deepseek-ai/dsh@0.1.
|
|
65
|
+
npm install --global @deepseek-ai/dsh@0.1.7-rc.2
|
|
65
66
|
dsh --version
|
|
66
67
|
```
|
|
67
68
|
|
|
@@ -69,15 +70,15 @@ Skip this if you already run this version. Alpha is opt-in: select an exact Alph
|
|
|
69
70
|
|
|
70
71
|
### 2. Install the AgentTeams plugin
|
|
71
72
|
|
|
72
|
-
Install
|
|
73
|
+
Install or upgrade from npm (pinned to this `latest` release):
|
|
73
74
|
|
|
74
75
|
```sh
|
|
75
|
-
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.
|
|
76
|
+
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.21
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
Replace `web` with your active profile. **Stop and restart that profile's Harness process, then refresh the browser.** Installing the plugin does not upgrade Harness; the host and plugin have independent `latest` channels.
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
For source installations, run `pnpm install --frozen-lockfile` and `pnpm build` after pulling, then restart the corresponding Harness process. Updating Git alone does not update local build output. See the [maintenance guide](./docs/maintenance-workflow.md).
|
|
81
82
|
|
|
82
83
|
> Desktop users must check the app's embedded Harness core; upgrading the global CLI does not upgrade it. For older `0.1.0-*` / `0.1.1-*` or unlisted hosts, keep a working pair and follow the [older-version and diagnostic guide](./docs/maintenance-workflow.md).
|
|
83
84
|
|
|
@@ -87,6 +88,12 @@ Then ask for a team directly:
|
|
|
87
88
|
|
|
88
89
|
> Use AgentTeams to review the commits after v0.5.3 from performance, security, and product perspectives. Return one consolidated report.
|
|
89
90
|
|
|
91
|
+
## View teams in the workspace
|
|
92
|
+
|
|
93
|
+
Click **View team** in the current chat header or the team card below a reply to open the native **Team collaboration** tab. The entry belongs to its chat, rather than the general workspace start page.
|
|
94
|
+
|
|
95
|
+
Team and task content starts immediately: wide panes use columns and narrow panes stack them. Select a task to locate its owner; member icons open their conversations. Closing the tab does not stop the team, and its chat can reopen it. Completed teams retain their archives. Older hosts keep the activity panel.
|
|
96
|
+
|
|
90
97
|
## How it works
|
|
91
98
|
|
|
92
99
|
1. For a request to use AgentTeams, the captain follows the core protocol already in its system instructions. It continues an existing team and uses `agent_teams_status` when current state needs checking. When no team exists, the goal becomes a staged plan for review.
|
package/README_ZH.md
CHANGED
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
你只需用自然语言提出目标。插件会提供精简的固定团队协议、14 个业务工具、持久化状态、自动共享任务调度和实时 Web UI,不需要额外的 Workflow 引擎。
|
|
26
26
|
|
|
27
27
|
<p align="center">
|
|
28
|
-
<img src="./assets/
|
|
28
|
+
<img src="./assets/readme/workspace.png" width="100%" alt="DeepSeek Harness 对话与 AgentTeams 实时活动面板,展示成员、任务依赖和回报">
|
|
29
29
|
</p>
|
|
30
30
|
|
|
31
31
|
## 版本更新
|
|
32
32
|
|
|
33
|
-
[v0.1.
|
|
33
|
+
[v0.1.21](./release-notes/v0.1.21.md) 将团队协作移入 Harness 原生工作区,恢复聊天中的「查看团队」入口,并修复窄宽度布局。npm `latest` 对应此版本;推荐搭配 Harness `0.1.7-rc.2`(宿主的 `next` 渠道)。精确支持范围见 [compatibility.json](./compatibility.json)。
|
|
34
34
|
|
|
35
35
|
## 为什么需要 AgentTeams?
|
|
36
36
|
|
|
@@ -48,19 +48,20 @@
|
|
|
48
48
|
|
|
49
49
|
## 安装与版本选择
|
|
50
50
|
|
|
51
|
-
**推荐组合:DeepSeek Harness `0.1.
|
|
51
|
+
**推荐组合:DeepSeek Harness `0.1.7-rc.2` + AgentTeams `0.1.21`。Harness 仍为预发布版本。**
|
|
52
52
|
|
|
53
53
|
| 使用场景 | DeepSeek Harness | AgentTeams 插件 |
|
|
54
54
|
| --- | --- | --- |
|
|
55
|
-
|
|
|
56
|
-
| 保留旧 RC | `0.1.
|
|
57
|
-
|
|
|
58
|
-
|
|
|
55
|
+
| **推荐版本** | **`0.1.7-rc.2`** | **`0.1.21`** |
|
|
56
|
+
| 保留旧 RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.21` |
|
|
57
|
+
| 保留旧 RC | `0.1.2-rc.1` | `0.1.21` |
|
|
58
|
+
| 开发者测试 Alpha | `0.1.2-alpha.5` | `0.1.21` |
|
|
59
|
+
| 保留旧 Alpha | `0.1.2-alpha.2` | `0.1.21` |
|
|
59
60
|
|
|
60
61
|
### 1. 安装 DeepSeek Harness
|
|
61
62
|
|
|
62
63
|
```sh
|
|
63
|
-
npm install --global @deepseek-ai/dsh@0.1.
|
|
64
|
+
npm install --global @deepseek-ai/dsh@0.1.7-rc.2
|
|
64
65
|
dsh --version
|
|
65
66
|
```
|
|
66
67
|
|
|
@@ -68,15 +69,15 @@ dsh --version
|
|
|
68
69
|
|
|
69
70
|
### 2. 安装 AgentTeams 插件
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
从 npm 安装或升级插件(命令锁定本次 `latest` 的具体版本):
|
|
72
73
|
|
|
73
74
|
```sh
|
|
74
|
-
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.
|
|
75
|
+
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.21
|
|
75
76
|
```
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
将 `web` 换成实际使用的 profile。**安装后,停止并重新启动该 profile 的 Harness 进程,再刷新浏览器。** 安装插件不会自动升级宿主;Harness 的 `latest` 与插件的 `latest` 是两个独立渠道。
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
源码用户拉取代码后需重新运行 `pnpm install --frozen-lockfile` 和 `pnpm build`,再重启对应的 Harness;只更新 Git 不会更新本地构建产物。详见[维护指南](./docs/maintenance-workflow.md)。
|
|
80
81
|
|
|
81
82
|
> Desktop 用户需核对应用内置的 Harness 核心;全局 CLI 升级不会升级桌面内核。旧 `0.1.0-*` / `0.1.1-*` 或其他未列出的宿主,请先保留已工作的组合,参考[旧版本与诊断指引](./docs/maintenance-workflow.md)。
|
|
82
83
|
|
|
@@ -88,6 +89,12 @@ npm 默认标签 `latest` 现指向 `0.1.17`,因此新 profile 使用 `dsh plu
|
|
|
88
89
|
|
|
89
90
|
队长会话从第一轮起保留精简的固定核心协议和原有 14 个业务工具,直接调用,无需额外的加载工具或激活调用。已配置模板的精简目录固定保留。创建、批准、继续或结束团队都不会改写系统提示词和工具 schema,会话压缩或代码模式丢弃工具结果也不会丢失核心规则。成员保留四个团队工具、固定成员说明及普通编程/研究工具。Web 批准后会通知并唤醒队长,后续成员报告会再次唤醒它,无需忙轮询。详见[固定协议与 benchmark 标准](./docs/progressive-loading.md)。
|
|
90
91
|
|
|
92
|
+
## 在工作区查看团队
|
|
93
|
+
|
|
94
|
+
从当前聊天标题栏或回复下方的团队卡片点击 **查看团队**,在原生「团队协作」标签页中查看成员分工、任务依赖和执行进度。入口跟随所属聊天,不放在通用工作区开始页。
|
|
95
|
+
|
|
96
|
+
页面直接呈现团队和任务内容;宽屏并排展示,窄分栏自动堆叠。点击任务可定位负责成员,成员图标可打开对应会话。关闭标签页不会停止团队;回到所属聊天可以再次打开。已完成团队保留归档,旧宿主继续使用活动面板。
|
|
97
|
+
|
|
91
98
|
## 工作方式
|
|
92
99
|
|
|
93
100
|
1. 当前会话创建团队并成为队长。
|
|
Binary file
|
package/compatibility.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"recommendedHost": "0.1.
|
|
3
|
+
"recommendedHost": "0.1.7-rc.2",
|
|
4
4
|
"previewTag": "next",
|
|
5
5
|
"supportedHosts": [
|
|
6
6
|
{
|
|
7
|
-
"version": "0.1.
|
|
7
|
+
"version": "0.1.7-rc.2",
|
|
8
8
|
"track": "recommended"
|
|
9
9
|
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.5-rc.3",
|
|
12
|
+
"track": "legacy"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.1.5-rc.2",
|
|
16
|
+
"track": "legacy"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"version": "0.1.5-rc.1",
|
|
20
|
+
"track": "legacy"
|
|
21
|
+
},
|
|
10
22
|
{
|
|
11
23
|
"version": "0.1.2-rc.1",
|
|
12
24
|
"track": "legacy"
|
package/lib/capabilities.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Stable, agent-scoped presentation. Business authority stays in the tools. */
|
|
2
|
+
import { onAgentReady } from "./harness-compat.js";
|
|
1
3
|
import { readdirSync } from 'node:fs';
|
|
2
4
|
import { join } from 'node:path';
|
|
3
5
|
import { readTeamSync, readRetiredMemberIdsSync } from "./state.js";
|
|
@@ -95,7 +97,7 @@ export function installTeamCapabilities(ctx, config) {
|
|
|
95
97
|
return agent !== undefined && states.get(agent)?.member ? TEAM_MEMBER_PROMPT : captainPrompt;
|
|
96
98
|
},
|
|
97
99
|
});
|
|
98
|
-
ctx
|
|
100
|
+
onAgentReady(ctx, agent => { attach(agent); });
|
|
99
101
|
ctx.effect(() => () => {
|
|
100
102
|
mounted = false;
|
|
101
103
|
for (const state of [...active])
|
|
@@ -18,8 +18,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
18
18
|
* @module dsh-agent-teams/client/activity
|
|
19
19
|
*/
|
|
20
20
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
|
|
21
|
-
import { IconBranchOutline16, IconChevronDownOutline14, IconPanelLeftOutline16, IconStopFill16, IconWarningOutline16,
|
|
22
|
-
import {
|
|
21
|
+
import { IconBranchOutline16, IconChevronDownOutline14, IconPanelLeftOutline16, IconStopFill16, IconWarningOutline16, } from "./icons.js";
|
|
22
|
+
import { Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
23
|
+
import { currentSessionId } from "./session-navigation.js";
|
|
24
|
+
import { activityPanelExpandedForSession, activityPanelShouldAutoExpand, compactDagLayout, compactModelLabel, COMPACT_DAG_NODE_HEIGHT, COMPACT_DAG_NODE_WIDTH, dependencyFocusTaskId, memberRouteLabel, orderDelegationMembers, relatedTaskIds, taskModelLabel, teamIsActive, usesParallelTaskGrid, } from "./activity-model.js";
|
|
23
25
|
import { ACTIVITY_HALT_URL, getActivityMonitorTargetsSnapshot, getActivitySnapshotsSnapshot, startActivityPolling, subscribeActivityMonitorTargets, subscribeActivitySnapshots, } from "./activity-monitor.js";
|
|
24
26
|
import { ACTION_ART, LEAD_ART, memberArtUrl } from "./artwork.js";
|
|
25
27
|
import { OPEN_PANEL_EVENT } from "./AgentTeamsCard.js";
|
|
@@ -158,6 +160,10 @@ function memberStatusText(member, tasks, t) {
|
|
|
158
160
|
return t('member.status.waitingAssignment');
|
|
159
161
|
if (member.done === member.total)
|
|
160
162
|
return t('member.status.delivered');
|
|
163
|
+
if (owned.some((task) => task.status === 'failed'))
|
|
164
|
+
return t('task.detail.failed');
|
|
165
|
+
if (owned.length > 0 && owned.every((task) => task.status === 'completed' || task.status === 'cancelled'))
|
|
166
|
+
return t('member.status.settled');
|
|
161
167
|
return t(member.activity === 'idle' ? 'member.status.idle' : 'member.status.unknown');
|
|
162
168
|
}
|
|
163
169
|
function compactTaskLabel(subject) {
|
|
@@ -209,17 +215,20 @@ function ProgressOverview({ team, t, discarded = false }) {
|
|
|
209
215
|
const blocked = discarded ? 0 : team.tasks.filter((task) => task.state === 'blocked').length;
|
|
210
216
|
const completed = discarded ? 0 : team.tasks.filter((task) => task.status === 'completed').length;
|
|
211
217
|
const settled = !discarded && team.tasks.length > 0 && team.tasks.every((task) => (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled'));
|
|
212
|
-
const summaryTone = discarded ? 'discarded' : blocked > 0 ? 'warning' : settled ? 'completed' : 'running';
|
|
218
|
+
const summaryTone = discarded ? 'discarded' : blocked > 0 || team.tasks.some(task => task.status === 'failed' || task.status === 'cancelled') ? 'warning' : settled ? 'completed' : 'running';
|
|
213
219
|
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": discarded ? 'cancelled' : 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, discarded) })] })] }));
|
|
214
220
|
}
|
|
215
|
-
function DependencyMap({ tasks, members, t, discarded = false }) {
|
|
221
|
+
function DependencyMap({ tasks, members, t, discarded = false, workspace = false, onSelectTask }) {
|
|
216
222
|
const [open, setOpen] = useState(true);
|
|
217
223
|
const [hoverTaskId, setHoverTaskId] = useState(null);
|
|
218
224
|
const [keyboardTaskId, setKeyboardTaskId] = useState(null);
|
|
219
225
|
const [pinnedTaskId, setPinnedTaskId] = useState(null);
|
|
220
226
|
const hoverTimer = useRef(null);
|
|
221
227
|
const focusedTaskId = dependencyFocusTaskId(pinnedTaskId, keyboardTaskId, hoverTaskId);
|
|
222
|
-
const
|
|
228
|
+
const nodeWidth = workspace ? 164 : COMPACT_DAG_NODE_WIDTH;
|
|
229
|
+
const nodeHeight = workspace ? 76 : COMPACT_DAG_NODE_HEIGHT;
|
|
230
|
+
const layout = useMemo(() => compactDagLayout(tasks, workspace
|
|
231
|
+
? { nodeWidth: 164, nodeHeight: 76, columnGap: 36, rowGap: 16 } : undefined), [tasks, workspace]);
|
|
223
232
|
const parallel = useMemo(() => usesParallelTaskGrid(tasks), [tasks]);
|
|
224
233
|
const related = useMemo(() => focusedTaskId === null ? null : relatedTaskIds(focusedTaskId, tasks), [focusedTaskId, tasks]);
|
|
225
234
|
const scheduleHover = (id) => {
|
|
@@ -242,12 +251,14 @@ function DependencyMap({ tasks, members, t, discarded = false }) {
|
|
|
242
251
|
}, []);
|
|
243
252
|
useEffect(() => {
|
|
244
253
|
const onKeyDown = (event) => {
|
|
245
|
-
if (event.key === 'Escape')
|
|
254
|
+
if (event.key === 'Escape') {
|
|
246
255
|
setPinnedTaskId(null);
|
|
256
|
+
onSelectTask?.(null);
|
|
257
|
+
}
|
|
247
258
|
};
|
|
248
259
|
window.addEventListener('keydown', onKeyDown);
|
|
249
260
|
return () => { window.removeEventListener('keydown', onKeyDown); };
|
|
250
|
-
}, []);
|
|
261
|
+
}, [onSelectTask]);
|
|
251
262
|
if (tasks.length === 0)
|
|
252
263
|
return null;
|
|
253
264
|
const fallbackTask = tasks.find((task) => task.state === 'blocked')
|
|
@@ -258,30 +269,35 @@ function DependencyMap({ tasks, members, t, discarded = false }) {
|
|
|
258
269
|
const waitingOn = detailTask.dependencies.filter((dependency) => (tasks.find((task) => task.id === dependency)?.status !== 'completed'));
|
|
259
270
|
const dependents = tasks.filter((task) => task.dependencies.includes(detailTask.id));
|
|
260
271
|
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
|
|
261
|
-
? t(parallel ? 'dependency.hint.parallel' : 'dependency.hint.chain')
|
|
272
|
+
? t(parallel ? 'dependency.hint.parallel' : workspace ? 'workspace.dependencies' : 'dependency.hint.chain')
|
|
262
273
|
: 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) => {
|
|
263
274
|
const active = related !== null && related.has(edge.from) && related.has(edge.to);
|
|
264
275
|
return _jsx("path", { d: edge.path, "data-active": active, "data-dimmed": related !== null && !active }, `${edge.from}:${edge.to}`);
|
|
265
276
|
}) }), layout.nodes.map(({ task, x, y }) => {
|
|
266
277
|
const model = taskModelLabel(task, members);
|
|
267
|
-
const shortModel = compactModelLabel(model);
|
|
268
278
|
return (_jsxs("button", { type: "button", className: css.dagNode, style: parallel
|
|
269
|
-
? { height:
|
|
270
|
-
: { left: x, top: y, width:
|
|
279
|
+
? { height: nodeHeight }
|
|
280
|
+
: { left: x, top: y, width: nodeWidth, height: nodeHeight }, "data-task-id": task.id, "data-state": discarded ? 'cancelled' : taskTone(task.state, task.status), "data-task-model": model || undefined, "data-focused": related?.has(task.id) ?? false, "data-dimmed": related !== null && !related.has(task.id), "aria-pressed": pinnedTaskId === task.id, title: taskTitle(task, model), onClick: () => { const next = pinnedTaskId === task.id ? null : task.id; setPinnedTaskId(next); onSelectTask?.(next); }, 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, workspace && _jsx("span", { className: css.dagOwner, children: task.assignee || t('task.assignee.unclaimed') })] }), _jsx("span", { className: css.dagNodeLabel, children: workspace ? task.subject : compactTaskLabel(task.subject) }), task.state === 'running' && (_jsx("span", { className: css.dagRunningState, "aria-label": t('task.runningAria'), children: _jsx(WorkGlyph, { active: true }) }))] }, task.id));
|
|
271
281
|
})] }) }), _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": discarded ? 'cancelled' : taskTone(detailTask.state, detailTask.status), children: discarded ? t('task.status.notRun') : taskStatusLabel(detailTask.status, t) })] }), _jsxs("span", { className: css.taskDetailLine, children: [detailTask.assignee || t('task.assignee.unclaimed'), " \u00B7 ", discarded
|
|
272
282
|
? t('task.detail.notRun')
|
|
273
283
|
: detailTask.status === 'completed'
|
|
274
284
|
? t('task.detail.completed')
|
|
275
|
-
: detailTask.
|
|
276
|
-
? t('task.detail.
|
|
277
|
-
:
|
|
278
|
-
? t('task.detail.
|
|
279
|
-
:
|
|
285
|
+
: detailTask.status === 'cancelled'
|
|
286
|
+
? t('task.detail.cancelled')
|
|
287
|
+
: detailTask.status === 'failed'
|
|
288
|
+
? t('task.detail.failed')
|
|
289
|
+
: detailTask.dependencies.length === 0
|
|
290
|
+
? t('task.detail.noPrerequisite')
|
|
291
|
+
: waitingOn.length === 0
|
|
292
|
+
? t('task.detail.ready')
|
|
293
|
+
: t('task.detail.waitingOn', { tasks: formatTaskIds(waitingOn, t) })] }), detailModel !== '' && (_jsx("span", { className: css.taskDetailModel, "data-task-model": detailModel, children: t('task.model', { model: detailModel }) })), _jsx("span", { className: css.taskDetailMeta, children: dependents.length === 0
|
|
280
294
|
? t('task.detail.noDownstream')
|
|
281
295
|
: t('task.detail.unlocks', { tasks: formatTaskIds(dependents.map((task) => task.id), t) }) })] })] }))] }));
|
|
282
296
|
}
|
|
283
|
-
function TeamSection({ team, modelDirectory, onContinuePlanning, onDiscarded, onNavigate, t, historic = false }) {
|
|
284
|
-
const [
|
|
297
|
+
export function TeamSection({ team, modelDirectory, onContinuePlanning, onDiscarded, onNavigate, t, historic = false, workspace = false }) {
|
|
298
|
+
const [selectedTaskId, setSelectedTaskId] = useState(null);
|
|
299
|
+
const selectedAssignee = team.tasks.find(task => task.id === selectedTaskId)?.assignee;
|
|
300
|
+
const [membersOpen, setMembersOpen] = useState(workspace);
|
|
285
301
|
const [stopOpen, setStopOpen] = useState(false);
|
|
286
302
|
const [stopping, setStopping] = useState(false);
|
|
287
303
|
const [stopError, setStopError] = useState('');
|
|
@@ -329,7 +345,7 @@ function TeamSection({ team, modelDirectory, onContinuePlanning, onDiscarded, on
|
|
|
329
345
|
setStopping(false);
|
|
330
346
|
}
|
|
331
347
|
};
|
|
332
|
-
return (_jsxs(_Fragment, { children: [_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(discarded ? 'team.discarded' : 'team.ended') }), stopped && _jsx("span", { className: css.historicPill, children: t('team.stopped') }), _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 }) })] }), canStop && (_jsx("button", { type: "button", className: css.teamStopButton, "aria-label": t('team.stop'), title: t('team.stop'), onClick: () => { setStopError(''); setStopOpen(true); }, children: _jsx(IconStopFill16, {}) }))] }), team.phase === 'staged' && !historic && modelDirectory !== undefined && onContinuePlanning !== undefined && onDiscarded !== undefined && (_jsx(StagingPlanEditor, { team: team, modelDirectory: modelDirectory, onContinuePlanning: onContinuePlanning, onDiscarded: onDiscarded, t: t })), _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: discarded
|
|
348
|
+
return (_jsxs(_Fragment, { children: [_jsxs("section", { className: css.team, "data-team-id": team.teamId, "data-workspace-team": workspace || undefined, 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(discarded ? 'team.discarded' : 'team.ended') }), stopped && _jsx("span", { className: css.historicPill, children: t('team.stopped') }), _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 }) })] }), canStop && (_jsx("button", { type: "button", className: css.teamStopButton, "aria-label": t('team.stop'), title: t('team.stop'), onClick: () => { setStopError(''); setStopOpen(true); }, children: _jsx(IconStopFill16, {}) }))] }), team.phase === 'staged' && !historic && modelDirectory !== undefined && onContinuePlanning !== undefined && onDiscarded !== undefined && (_jsx(StagingPlanEditor, { team: team, modelDirectory: modelDirectory, onContinuePlanning: onContinuePlanning, onDiscarded: onDiscarded, t: t })), _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: discarded
|
|
333
349
|
? t('captain.summary.discarded', { tasks: team.tasks.length, members: team.members.length })
|
|
334
350
|
: captainBusy
|
|
335
351
|
? t('captain.summary.withTakeover', { tasks: assignedCount, captainTasks: captainTaskIds })
|
|
@@ -351,44 +367,57 @@ function TeamSection({ team, modelDirectory, onContinuePlanning, onDiscarded, on
|
|
|
351
367
|
? 'captain.state.collected'
|
|
352
368
|
: allSettled
|
|
353
369
|
? 'captain.state.settled'
|
|
354
|
-
: 'captain.state.waiting')] })] }), _jsx(ProgressOverview, { team: team, t: t, discarded: discarded }),
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
370
|
+
: 'captain.state.waiting')] })] }), _jsx(ProgressOverview, { team: team, t: t, discarded: discarded }), (() => {
|
|
371
|
+
const orderedMembers = orderDelegationMembers(team.members, team.tasks);
|
|
372
|
+
const runningMembers = orderedMembers.filter((member) => (member.activity === 'working' || member.status === 'working'
|
|
373
|
+
|| team.tasks.some((task) => task.assignee === member.name
|
|
374
|
+
&& (task.status === 'pending' || task.status === 'claimed' || task.status === 'in_progress'))));
|
|
375
|
+
const visibleMembers = membersOpen ? orderedMembers : runningMembers;
|
|
376
|
+
const hiddenFinishedCount = orderedMembers.length - visibleMembers.length;
|
|
377
|
+
const expandLabel = membersOpen
|
|
378
|
+
? t('members.collapse')
|
|
379
|
+
: hiddenFinishedCount > 0
|
|
380
|
+
? t('members.expandFinished', { count: hiddenFinishedCount })
|
|
381
|
+
: t('members.expand');
|
|
382
|
+
return (_jsxs(_Fragment, { children: [_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: expandLabel })] }), (membersOpen || visibleMembers.length > 0) && _jsxs("div", { className: css.delegationTree, children: [team.members.length === 0 && _jsx("span", { className: css.emptyHint, children: t('members.empty') }), visibleMembers.map((member) => {
|
|
383
|
+
const owned = team.tasks.filter((task) => task.assignee === member.name);
|
|
384
|
+
const memberModel = memberRouteLabel(member);
|
|
385
|
+
return (_jsxs("div", { className: css.memberBlock, "data-activity": member.activity, "data-selected-member": workspace && selectedAssignee === member.name || undefined, children: [_jsx("span", { className: css.memberBranch, "aria-hidden": true, children: _jsx("span", {}) }), _jsxs("button", { type: "button", className: css.memberRow, "data-activity": member.activity, onClick: () => {
|
|
386
|
+
if (member.id !== '') {
|
|
387
|
+
onNavigate(team.captainSessionId, member.id);
|
|
388
|
+
}
|
|
389
|
+
}, 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 }), memberModel !== '' && (_jsx("span", { className: css.memberModel, role: "img", "data-member-model": memberModel, title: memberModel, "aria-label": memberModel, children: compactModelLabel(memberModel) })), _jsxs("span", { className: css.memberState, "data-activity": member.activity, children: [_jsx(WorkGlyph, { active: member.activity === 'working' }), discarded
|
|
390
|
+
? t('member.state.notCreated')
|
|
391
|
+
: stopped
|
|
392
|
+
? t('member.state.stopped')
|
|
393
|
+
: team.phase === 'staged'
|
|
394
|
+
? t('member.state.staged')
|
|
395
|
+
: memberStateLabel(member, team.tasks, historic, t)] })] }), _jsx("span", { className: css.memberStatusLine, children: discarded
|
|
396
|
+
? t('member.status.discarded')
|
|
363
397
|
: stopped
|
|
364
|
-
? t('member.
|
|
398
|
+
? t('member.status.stopped')
|
|
365
399
|
: team.phase === 'staged'
|
|
366
|
-
? t('member.
|
|
367
|
-
:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
?
|
|
400
|
+
? t('member.status.staged')
|
|
401
|
+
: historic && owned.length > 0 && owned.every((task) => (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled'))
|
|
402
|
+
? t('member.status.settled')
|
|
403
|
+
: 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(discarded
|
|
404
|
+
? 'assignment.discarded'
|
|
371
405
|
: team.phase === 'staged'
|
|
372
|
-
?
|
|
373
|
-
:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const model = taskModelLabel(task, team.members);
|
|
383
|
-
const shortModel = compactModelLabel(model);
|
|
384
|
-
return (_jsx("span", { className: css.assignmentChip, "data-state": discarded ? 'cancelled' : taskTone(task.state, task.status), "data-task-model": model || undefined, title: taskTitle(task, model), children: task.state === 'running' && shortModel !== '' ? `${task.id} · ${shortModel}` : task.id }, task.id));
|
|
385
|
-
}) })] })] }, member.id || member.name));
|
|
386
|
-
})] })] }), _jsx(DependencyMap, { tasks: team.tasks, members: team.members, t: t, discarded: discarded })] }), _jsx(Modal, { open: stopOpen, onClose: () => { if (!stopping)
|
|
406
|
+
? 'assignment.staged'
|
|
407
|
+
: 'assignment.label') }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
|
|
408
|
+
? _jsx("span", { className: css.taskEmpty, children: t('assignment.empty') })
|
|
409
|
+
: owned.map((task) => {
|
|
410
|
+
const model = taskModelLabel(task, team.members);
|
|
411
|
+
const shortModel = compactModelLabel(model);
|
|
412
|
+
return (_jsx("span", { className: css.assignmentChip, "data-state": discarded ? 'cancelled' : taskTone(task.state, task.status), "data-task-model": model || undefined, title: taskTitle(task, model), children: task.state === 'running' && shortModel !== '' ? `${task.id} · ${shortModel}` : task.id }, task.id));
|
|
413
|
+
}) })] })] }, member.id || member.name));
|
|
414
|
+
})] })] }));
|
|
415
|
+
})()] }), _jsx(DependencyMap, { tasks: team.tasks, members: team.members, t: t, discarded: discarded, workspace: workspace, onSelectTask: setSelectedTaskId })] }), _jsx(Modal, { open: stopOpen, onClose: () => { if (!stopping)
|
|
387
416
|
setStopOpen(false); }, title: t('team.stopTitle', { team: team.name }), closeLabel: t('plan.cancel'), description: t('team.stopDescription', { tasks: unfinishedCount, members: busyCount }), footer: (_jsxs("span", { className: css.stopModalActions, children: [_jsx("button", { type: "button", disabled: stopping, onClick: () => { setStopOpen(false); }, children: t('team.stopCancel') }), _jsxs("button", { type: "button", "data-danger": true, disabled: stopping, onClick: () => { void stopTeam(); }, children: [_jsx(IconStopFill16, {}), stopping ? t('team.stopping') : t('team.stopConfirm')] })] })), children: stopError !== '' && _jsxs("p", { className: css.stopModalError, role: "alert", children: [_jsx(IconWarningOutline16, {}), stopError] }) })] }));
|
|
388
417
|
}
|
|
389
418
|
/** Legacy conversation cards may outlive their host archive. Project their
|
|
390
419
|
* durable roster through the same rebuilt panel instead of a second UI. */
|
|
391
|
-
function historicCardTeam(data, owner) {
|
|
420
|
+
export function historicCardTeam(data, owner) {
|
|
392
421
|
return {
|
|
393
422
|
workspace: '',
|
|
394
423
|
teamId: data.teamId,
|
|
@@ -432,7 +461,7 @@ export function ActivityPanel({ sessionsList, modelDirectories, openMember, t, c
|
|
|
432
461
|
const gestureRef = useRef(null);
|
|
433
462
|
const frameRef = useRef(null);
|
|
434
463
|
const pendingLayoutRef = useRef(null);
|
|
435
|
-
const current = useSyncExternalStore(sessionsList.subscribe, sessionsList.getSnapshot)
|
|
464
|
+
const current = currentSessionId(useSyncExternalStore(sessionsList.subscribe, sessionsList.getSnapshot));
|
|
436
465
|
const autoOpenTrackerRef = useRef({ sessionId: current, restoreComplete: false, liveTeamIds: new Set() });
|
|
437
466
|
const monitorTargets = useSyncExternalStore(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
|
|
438
467
|
const returnToComposer = () => {
|
|
@@ -16,10 +16,10 @@ import { LEAD_ART, memberArtUrl } from "./artwork.js";
|
|
|
16
16
|
import css from './AgentTeamsCard.module.css';
|
|
17
17
|
/** Window event name the floater listens for to open itself. */
|
|
18
18
|
export const OPEN_PANEL_EVENT = 'agent-teams:open-panel';
|
|
19
|
-
/**
|
|
19
|
+
/** Open the matching team view, carrying this team's summary
|
|
20
20
|
* so the panel can show it even when the team no longer exists on disk
|
|
21
21
|
* (historical session review). */
|
|
22
|
-
function openActivityPanel(data) {
|
|
22
|
+
export function openActivityPanel(data) {
|
|
23
23
|
window.dispatchEvent(new CustomEvent(OPEN_PANEL_EVENT, {
|
|
24
24
|
detail: {
|
|
25
25
|
teamId: data.teamId,
|
|
@@ -30,8 +30,17 @@ function openActivityPanel(data) {
|
|
|
30
30
|
}));
|
|
31
31
|
}
|
|
32
32
|
/** Render one durable team as a compact conversation card. */
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const noWorkspace = () => undefined;
|
|
34
|
+
const noSubscription = () => () => { };
|
|
35
|
+
export function AgentTeamsCard({ node, openMember, sessionId, t, workspaceBridge }) {
|
|
36
|
+
const native = useSyncExternalStore(workspaceBridge?.subscribe ?? noSubscription, workspaceBridge?.getSnapshot ?? noWorkspace);
|
|
37
|
+
const location = node.location;
|
|
38
|
+
// Closed turns render their card through the official, non-folding turn tail.
|
|
39
|
+
if (native && (location.kind === 'turn' || location.kind === 'step') && location.turn.status === 'closed')
|
|
40
|
+
return null;
|
|
41
|
+
return _jsx(AgentTeamsSummary, { data: node.data, openMember: openMember, sessionId: sessionId, t: t });
|
|
42
|
+
}
|
|
43
|
+
export function AgentTeamsSummary({ data, openMember, sessionId, t }) {
|
|
35
44
|
// `conversation.chat.node` is session-scoped, so its framework-owned id is
|
|
36
45
|
// a stable owner even while another conversation becomes current.
|
|
37
46
|
const owner = data.captainSessionId || sessionId;
|
|
@@ -47,7 +56,7 @@ export function AgentTeamsCard({ node, openMember, sessionId, t }) {
|
|
|
47
56
|
teamName: snapshot?.name ?? data.teamName,
|
|
48
57
|
members: snapshot?.members.map((member) => ({ id: member.id, name: member.name, role: member.role })) ?? data.members,
|
|
49
58
|
}), [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 }), _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('
|
|
59
|
+
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('workspace.focus'), title: t('workspace.focus'), children: t('workspace.focus') })] }), resolved.members.length > 0 && (_jsx("div", { className: css.members, children: resolved.members.map((member) => (_jsxs("button", { type: "button", className: css.member, onClick: () => {
|
|
51
60
|
if (member.id !== '')
|
|
52
61
|
openMember(owner, member.id);
|
|
53
62
|
}, 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 || member.name))) }))] }));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** Session-owned entry points: a persistent title action and a durable turn card. */
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
4
|
+
import { AgentTeamsSummary, openActivityPanel } from "./AgentTeamsCard.js";
|
|
5
|
+
import { getActivitySnapshotsSnapshot, subscribeActivitySnapshots } from "./activity-monitor.js";
|
|
6
|
+
import { teamCardsForTurn } from "./agent-teams-card-definition.js";
|
|
7
|
+
import { LEAD_ART } from "./artwork.js";
|
|
8
|
+
import css from './AgentTeamsCard.module.css';
|
|
9
|
+
export function TeamChatEntry({ sessionId, t }) {
|
|
10
|
+
const { teams, archivedTeams } = useSyncExternalStore(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
11
|
+
const team = [...teams, ...archivedTeams].find(team => team.captainSessionId === sessionId);
|
|
12
|
+
if (!team)
|
|
13
|
+
return null;
|
|
14
|
+
return _jsxs("button", { className: css.chatEntry, "data-team-chat-entry": true, type: "button", title: t('workspace.focus'), onClick: () => openActivityPanel({ teamId: team.teamId, captainSessionId: sessionId, teamName: team.name, members: team.members }), children: [_jsx("img", { src: LEAD_ART, alt: "", "aria-hidden": true }), t('workspace.focus')] });
|
|
15
|
+
}
|
|
16
|
+
export function TeamTurnCard({ sessionId, turn, useChat, t, openMember }) {
|
|
17
|
+
const nodes = useChat(snapshot => snapshot.nodes);
|
|
18
|
+
const cards = teamCardsForTurn(nodes.values(), turn.turn);
|
|
19
|
+
if (turn.status !== 'closed' || cards.length === 0)
|
|
20
|
+
return null;
|
|
21
|
+
return _jsx("div", { className: css.turnCards, "data-team-turn-cards": true, children: cards.map(node => _jsx(AgentTeamsSummary, { data: node.data, sessionId: sessionId, t: t, openMember: openMember }, node.key)) });
|
|
22
|
+
}
|