@nanmicoder/dsh-agent-teams 0.1.12 → 0.1.14
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 +43 -7
- package/README_ZH.md +20 -7
- package/lib/client/ActivityPanel.js +219 -50
- package/lib/client/StagingPlanEditor.js +493 -0
- package/lib/client/activity-model.js +71 -0
- package/lib/client/activity-monitor.js +39 -13
- package/lib/client/index.js +2 -2
- package/lib/client/locales.js +224 -2
- package/lib/client.js +1808 -250
- package/lib/client.js.map +1 -1
- package/lib/command.js +116 -99
- package/lib/index.js +286 -14
- package/lib/members.js +137 -16
- package/lib/profiles.js +572 -0
- package/lib/quality-gates.js +777 -0
- package/lib/scheduler.js +215 -18
- package/lib/snapshot.js +25 -1
- package/lib/state.js +116 -10
- package/lib/tools.js +1230 -38
- package/lib/types/client/ActivityPanel.d.ts +3 -1
- package/lib/types/client/StagingPlanEditor.d.ts +17 -0
- package/lib/types/client/activity-model.d.ts +67 -0
- package/lib/types/client/activity-monitor.d.ts +32 -7
- package/lib/types/client/locales.d.ts +222 -0
- package/lib/types/command.d.ts +11 -56
- package/lib/types/event-types.d.ts +35 -1
- package/lib/types/index.d.ts +9 -0
- package/lib/types/members.d.ts +48 -3
- package/lib/types/profiles.d.ts +124 -0
- package/lib/types/quality-gates.d.ts +148 -0
- package/lib/types/scheduler.d.ts +48 -1
- package/lib/types/snapshot.d.ts +18 -1
- package/lib/types/state.d.ts +8 -3
- package/lib/types/tools.d.ts +73 -9
- package/lib/types/types.d.ts +118 -0
- package/lib/types.js +11 -0
- package/package.json +10 -4
- package/release-notes/v0.1.13.md +60 -0
- package/release-notes/v0.1.14.md +68 -0
|
@@ -18,11 +18,12 @@ 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, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
22
|
-
import { activityPanelExpandedForSession, compactDagLayout, COMPACT_DAG_NODE_HEIGHT, COMPACT_DAG_NODE_WIDTH, dependencyFocusTaskId, relatedTaskIds, usesParallelTaskGrid, } from "./activity-model.js";
|
|
23
|
-
import { getActivityMonitorTargetsSnapshot, getActivitySnapshotsSnapshot, startActivityPolling, subscribeActivityMonitorTargets, subscribeActivitySnapshots, } from "./activity-monitor.js";
|
|
21
|
+
import { IconBranchOutline16, IconChevronDownOutline14, IconPanelLeftOutline16, IconStopFill16, IconWarningOutline16, Modal, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
22
|
+
import { activityPanelExpandedForSession, activityPanelShouldAutoExpand, compactDagLayout, compactModelLabel, COMPACT_DAG_NODE_HEIGHT, COMPACT_DAG_NODE_WIDTH, dependencyFocusTaskId, memberRouteLabel, relatedTaskIds, taskModelLabel, teamIsActive, usesParallelTaskGrid, } from "./activity-model.js";
|
|
23
|
+
import { ACTIVITY_HALT_URL, getActivityMonitorTargetsSnapshot, getActivitySnapshotsSnapshot, startActivityPolling, subscribeActivityMonitorTargets, subscribeActivitySnapshots, } from "./activity-monitor.js";
|
|
24
24
|
import { ACTION_ART, LEAD_ART, memberArtUrl } from "./artwork.js";
|
|
25
25
|
import { OPEN_PANEL_EVENT } from "./AgentTeamsCard.js";
|
|
26
|
+
import { StagingPlanEditor } from "./StagingPlanEditor.js";
|
|
26
27
|
import { DEFAULT_PANEL_LAYOUT, PANEL_LAYOUT_STORAGE_KEY, compactPanelForBounds, dockPanelLayout, floatPanelLayout, movePanelLayout, panelMaximumHeight, panelUsesAutoHeight, parsePanelLayout, resizePanelLayout, resolvePanelGeometry, } from "./panel-geometry.js";
|
|
27
28
|
import css from './ActivityPanel.module.css';
|
|
28
29
|
/** Grace before the panel collapses once no team remains. */
|
|
@@ -39,6 +40,7 @@ const PANEL_OPEN_ATTRIBUTE = 'data-agent-teams-panel-open';
|
|
|
39
40
|
const PANEL_SHIFT_PROPERTY = '--agent-teams-panel-shift';
|
|
40
41
|
const PANEL_CONVERSATION_GAP = 14;
|
|
41
42
|
const MOVE_THRESHOLD = 4;
|
|
43
|
+
const CAPTAIN_ASSIGNEE = 'captain';
|
|
42
44
|
function initialPanelLayout() {
|
|
43
45
|
if (typeof window === 'undefined')
|
|
44
46
|
return DEFAULT_PANEL_LAYOUT;
|
|
@@ -87,6 +89,15 @@ function taskStatusLabel(status, t) {
|
|
|
87
89
|
function formatTaskIds(ids, t) {
|
|
88
90
|
return ids.join(t('format.listSeparator'));
|
|
89
91
|
}
|
|
92
|
+
function taskTitle(task, model) {
|
|
93
|
+
const extras = [
|
|
94
|
+
task.kind,
|
|
95
|
+
task.round === undefined ? undefined : `r${task.round}`,
|
|
96
|
+
task.verdict,
|
|
97
|
+
model === '' ? undefined : model,
|
|
98
|
+
].filter((item) => item !== undefined);
|
|
99
|
+
return extras.length === 0 ? `${task.id} · ${task.subject}` : `${task.id} · ${task.subject} · ${extras.join(' · ')}`;
|
|
100
|
+
}
|
|
90
101
|
/** Badge/bar coloring key: visual state, widened for terminal statuses. */
|
|
91
102
|
function taskTone(state, status) {
|
|
92
103
|
if (status === 'failed')
|
|
@@ -125,8 +136,12 @@ function memberStatusText(member, tasks, t) {
|
|
|
125
136
|
const owned = tasks.filter((task) => task.assignee === member.name);
|
|
126
137
|
const current = owned.find((task) => task.id === member.currentTask);
|
|
127
138
|
const blocked = owned.find((task) => task.state === 'blocked');
|
|
128
|
-
if (member.activity === 'working' && current !== undefined)
|
|
129
|
-
|
|
139
|
+
if (member.activity === 'working' && current !== undefined) {
|
|
140
|
+
const model = taskModelLabel(current, [member]);
|
|
141
|
+
return model === ''
|
|
142
|
+
? t('member.status.executing', { taskId: current.id })
|
|
143
|
+
: t('member.status.executingModel', { taskId: current.id, model });
|
|
144
|
+
}
|
|
130
145
|
if (member.activity === 'working')
|
|
131
146
|
return t('member.status.working');
|
|
132
147
|
if (blocked !== undefined) {
|
|
@@ -150,15 +165,31 @@ function compactTaskLabel(subject) {
|
|
|
150
165
|
const head = withoutVerb.split(/[((·::]/u)[0]?.trim() ?? withoutVerb;
|
|
151
166
|
return head.length > 18 ? `${head.slice(0, 17)}…` : head;
|
|
152
167
|
}
|
|
153
|
-
function taskSummary(team, t) {
|
|
168
|
+
function taskSummary(team, t, discarded = false) {
|
|
154
169
|
const completed = team.tasks.filter((task) => task.status === 'completed');
|
|
170
|
+
const cancelled = team.tasks.filter((task) => task.status === 'cancelled');
|
|
155
171
|
const running = team.tasks.filter((task) => task.state === 'running');
|
|
156
172
|
const blocked = team.tasks.filter((task) => task.state === 'blocked');
|
|
157
|
-
const ready = team.tasks.filter((task) => task.state === 'open' && task.status !== 'completed');
|
|
173
|
+
const ready = team.tasks.filter((task) => task.state === 'open' && task.status !== 'completed' && task.status !== 'failed' && task.status !== 'cancelled');
|
|
174
|
+
const failed = team.tasks.filter((task) => task.status === 'failed');
|
|
175
|
+
if (discarded)
|
|
176
|
+
return t('task.summary.discarded', { count: team.tasks.length });
|
|
158
177
|
if (team.tasks.length === 0)
|
|
159
178
|
return t('task.summary.waitingBreakdown');
|
|
179
|
+
if (team.phase === 'staged')
|
|
180
|
+
return t('task.summary.staged', { count: team.tasks.length });
|
|
160
181
|
if (completed.length === team.tasks.length)
|
|
161
182
|
return t('task.summary.allDelivered', { count: completed.length });
|
|
183
|
+
if (completed.length + cancelled.length + failed.length === team.tasks.length) {
|
|
184
|
+
return t('task.summary.ended', {
|
|
185
|
+
completed: completed.length,
|
|
186
|
+
cancelled: cancelled.length,
|
|
187
|
+
failed: failed.length,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (failed.length > 0 && running.length === 0 && ready.length === 0 && blocked.length === 0) {
|
|
191
|
+
return t('task.summary.failedSettled', { count: failed.length });
|
|
192
|
+
}
|
|
162
193
|
if (blocked.length > 0 && running.length > 0) {
|
|
163
194
|
return t('task.summary.blockedAndRunning', {
|
|
164
195
|
tasks: formatTaskIds(blocked.slice(0, 3).map((task) => task.id), t),
|
|
@@ -173,14 +204,15 @@ function taskSummary(team, t) {
|
|
|
173
204
|
return t('task.summary.blocked', { tasks: formatTaskIds(blocked.map((task) => task.id), t) });
|
|
174
205
|
return t('task.summary.waitingSchedule');
|
|
175
206
|
}
|
|
176
|
-
function ProgressOverview({ team, t }) {
|
|
177
|
-
const running = team.tasks.filter((task) => task.state === 'running').length;
|
|
178
|
-
const blocked = team.tasks.filter((task) => task.state === 'blocked').length;
|
|
179
|
-
const completed = team.tasks.filter((task) => task.status === 'completed').length;
|
|
180
|
-
const
|
|
181
|
-
|
|
207
|
+
function ProgressOverview({ team, t, discarded = false }) {
|
|
208
|
+
const running = discarded ? 0 : team.tasks.filter((task) => task.state === 'running').length;
|
|
209
|
+
const blocked = discarded ? 0 : team.tasks.filter((task) => task.state === 'blocked').length;
|
|
210
|
+
const completed = discarded ? 0 : team.tasks.filter((task) => task.status === 'completed').length;
|
|
211
|
+
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';
|
|
213
|
+
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) })] })] }));
|
|
182
214
|
}
|
|
183
|
-
function DependencyMap({ tasks, t }) {
|
|
215
|
+
function DependencyMap({ tasks, members, t, discarded = false }) {
|
|
184
216
|
const [open, setOpen] = useState(true);
|
|
185
217
|
const [hoverTaskId, setHoverTaskId] = useState(null);
|
|
186
218
|
const [keyboardTaskId, setKeyboardTaskId] = useState(null);
|
|
@@ -222,6 +254,7 @@ function DependencyMap({ tasks, t }) {
|
|
|
222
254
|
?? tasks.find((task) => task.state === 'running')
|
|
223
255
|
?? tasks[0];
|
|
224
256
|
const detailTask = tasks.find((task) => task.id === focusedTaskId) ?? fallbackTask;
|
|
257
|
+
const detailModel = taskModelLabel(detailTask, members);
|
|
225
258
|
const waitingOn = detailTask.dependencies.filter((dependency) => (tasks.find((task) => task.id === dependency)?.status !== 'completed'));
|
|
226
259
|
const dependents = tasks.filter((task) => task.dependencies.includes(detailTask.id));
|
|
227
260
|
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
|
|
@@ -229,39 +262,129 @@ function DependencyMap({ tasks, t }) {
|
|
|
229
262
|
: 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) => {
|
|
230
263
|
const active = related !== null && related.has(edge.from) && related.has(edge.to);
|
|
231
264
|
return _jsx("path", { d: edge.path, "data-active": active, "data-dimmed": related !== null && !active }, `${edge.from}:${edge.to}`);
|
|
232
|
-
}) }), layout.nodes.map(({ task, x, y }) =>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
?
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
265
|
+
}) }), layout.nodes.map(({ task, x, y }) => {
|
|
266
|
+
const model = taskModelLabel(task, members);
|
|
267
|
+
const shortModel = compactModelLabel(model);
|
|
268
|
+
return (_jsxs("button", { type: "button", className: css.dagNode, style: parallel
|
|
269
|
+
? { height: COMPACT_DAG_NODE_HEIGHT }
|
|
270
|
+
: { left: x, top: y, width: COMPACT_DAG_NODE_WIDTH, height: COMPACT_DAG_NODE_HEIGHT }, "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: () => { 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: task.state === 'running' && shortModel !== '' ? shortModel : compactTaskLabel(task.subject) }), task.state === 'running' && (_jsx("span", { className: css.dagRunningState, "aria-label": t('task.runningAria'), children: _jsx(WorkGlyph, { active: true }) }))] }, task.id));
|
|
271
|
+
})] }) }), _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
|
+
? t('task.detail.notRun')
|
|
273
|
+
: detailTask.status === 'completed'
|
|
274
|
+
? t('task.detail.completed')
|
|
275
|
+
: detailTask.dependencies.length === 0
|
|
276
|
+
? t('task.detail.noPrerequisite')
|
|
277
|
+
: waitingOn.length === 0
|
|
278
|
+
? t('task.detail.ready')
|
|
279
|
+
: 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
|
|
241
280
|
? t('task.detail.noDownstream')
|
|
242
281
|
: t('task.detail.unlocks', { tasks: formatTaskIds(dependents.map((task) => task.id), t) }) })] })] }))] }));
|
|
243
282
|
}
|
|
244
|
-
function TeamSection({ team, onNavigate, t, historic = false }) {
|
|
283
|
+
function TeamSection({ team, modelDirectory, onContinuePlanning, onDiscarded, onNavigate, t, historic = false }) {
|
|
245
284
|
const [membersOpen, setMembersOpen] = useState(true);
|
|
285
|
+
const [stopOpen, setStopOpen] = useState(false);
|
|
286
|
+
const [stopping, setStopping] = useState(false);
|
|
287
|
+
const [stopError, setStopError] = useState('');
|
|
288
|
+
const discarded = historic && team.phase === 'staged';
|
|
289
|
+
const stopped = !historic && team.halted === true;
|
|
246
290
|
const busyCount = team.members.filter((member) => member.activity === 'working').length;
|
|
247
|
-
const assignedCount = team.tasks.filter((task) => task.assignee !== '').length;
|
|
291
|
+
const assignedCount = team.tasks.filter((task) => task.assignee !== '' && task.assignee !== CAPTAIN_ASSIGNEE).length;
|
|
292
|
+
const captainOwned = team.tasks.filter((task) => task.assignee === CAPTAIN_ASSIGNEE
|
|
293
|
+
&& task.status !== 'completed' && task.status !== 'failed' && task.status !== 'cancelled');
|
|
294
|
+
const captainBusy = captainOwned.length > 0;
|
|
295
|
+
const captainTaskIds = formatTaskIds(captainOwned.map((task) => task.id), t);
|
|
248
296
|
const completedCount = team.tasks.filter((task) => task.status === 'completed').length;
|
|
249
297
|
const allCompleted = team.tasks.length > 0 && completedCount === team.tasks.length;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
298
|
+
const allSettled = team.tasks.length > 0 && team.tasks.every((task) => (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled'));
|
|
299
|
+
const unfinishedCount = team.tasks.filter((task) => (task.status !== 'completed' && task.status !== 'failed' && task.status !== 'cancelled')).length;
|
|
300
|
+
const canStop = !historic && team.phase === 'running' && team.halted !== true && teamIsActive(team);
|
|
301
|
+
const stopTeam = async () => {
|
|
302
|
+
if (stopping)
|
|
303
|
+
return;
|
|
304
|
+
setStopping(true);
|
|
305
|
+
setStopError('');
|
|
306
|
+
try {
|
|
307
|
+
const response = await fetch(ACTIVITY_HALT_URL, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
cache: 'no-store',
|
|
310
|
+
headers: { 'content-type': 'application/json' },
|
|
311
|
+
body: JSON.stringify({ sessionId: team.captainSessionId, teamId: team.teamId }),
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
let message = t('team.stopRequestFailed');
|
|
315
|
+
try {
|
|
316
|
+
const body = await response.json();
|
|
317
|
+
if (typeof body.error === 'string' && body.error.trim() !== '')
|
|
318
|
+
message = body.error;
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
throw new Error(message);
|
|
322
|
+
}
|
|
323
|
+
setStopOpen(false);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
setStopError(t('team.stopFailed', { message: error instanceof Error ? error.message : String(error) }));
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
setStopping(false);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
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
|
|
333
|
+
? t('captain.summary.discarded', { tasks: team.tasks.length, members: team.members.length })
|
|
334
|
+
: captainBusy
|
|
335
|
+
? t('captain.summary.withTakeover', { tasks: assignedCount, captainTasks: captainTaskIds })
|
|
336
|
+
: team.phase === 'staged'
|
|
337
|
+
? t(team.planReviewState === 'awaiting_feedback'
|
|
338
|
+
? 'captain.summary.awaitingFeedback'
|
|
339
|
+
: 'captain.summary.staged', { tasks: team.tasks.length, members: team.members.length })
|
|
340
|
+
: t('captain.summary', { tasks: assignedCount, members: team.members.length }) })] }), _jsxs("span", { className: css.captainState, "data-busy": captainBusy || busyCount > 0, children: [_jsx(WorkGlyph, { active: captainBusy || busyCount > 0 }), discarded
|
|
341
|
+
? t('captain.state.discarded')
|
|
342
|
+
: captainBusy
|
|
343
|
+
? t('captain.state.takeover', { tasks: captainTaskIds })
|
|
344
|
+
: team.phase === 'staged'
|
|
345
|
+
? t(team.planReviewState === 'awaiting_feedback'
|
|
346
|
+
? 'captain.state.awaitingFeedback'
|
|
347
|
+
: 'captain.state.staged')
|
|
348
|
+
: busyCount > 0
|
|
349
|
+
? t('captain.state.working', { count: busyCount })
|
|
350
|
+
: t(allCompleted
|
|
351
|
+
? 'captain.state.collected'
|
|
352
|
+
: allSettled
|
|
353
|
+
? 'captain.state.settled'
|
|
354
|
+
: 'captain.state.waiting')] })] }), _jsx(ProgressOverview, { team: team, t: t, discarded: discarded }), _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) => {
|
|
355
|
+
const owned = team.tasks.filter((task) => task.assignee === member.name);
|
|
356
|
+
const memberModel = memberRouteLabel(member);
|
|
357
|
+
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: () => {
|
|
358
|
+
if (member.id !== '') {
|
|
359
|
+
onNavigate(team.captainSessionId, member.id);
|
|
360
|
+
}
|
|
361
|
+
}, 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' }), discarded
|
|
362
|
+
? t('member.state.notCreated')
|
|
363
|
+
: stopped
|
|
364
|
+
? t('member.state.stopped')
|
|
365
|
+
: team.phase === 'staged'
|
|
366
|
+
? t('member.state.staged')
|
|
367
|
+
: memberStateLabel(member, team.tasks, historic, t)] })] }), _jsx("span", { className: css.memberStatusLine, children: discarded
|
|
368
|
+
? t('member.status.discarded')
|
|
369
|
+
: stopped
|
|
370
|
+
? t('member.status.stopped')
|
|
371
|
+
: team.phase === 'staged'
|
|
372
|
+
? t('member.status.staged')
|
|
373
|
+
: historic && owned.length > 0 && owned.every((task) => (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled'))
|
|
374
|
+
? t('member.status.settled')
|
|
375
|
+
: memberStatusText(member, team.tasks, t) }), memberModel !== '' && (_jsx("span", { className: css.memberModel, "data-member-model": memberModel, children: t('member.model', { model: memberModel }) }))] }), _jsxs("span", { className: css.memberCount, children: [member.done, "/", member.total] })] }), _jsxs("div", { className: css.assignmentLine, children: [_jsx("span", { className: css.assignmentLabel, children: t(discarded
|
|
376
|
+
? 'assignment.discarded'
|
|
377
|
+
: team.phase === 'staged'
|
|
378
|
+
? 'assignment.staged'
|
|
379
|
+
: 'assignment.label') }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
|
|
380
|
+
? _jsx("span", { className: css.taskEmpty, children: t('assignment.empty') })
|
|
381
|
+
: owned.map((task) => {
|
|
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)
|
|
387
|
+
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] }) })] }));
|
|
265
388
|
}
|
|
266
389
|
/** Legacy conversation cards may outlive their host archive. Project their
|
|
267
390
|
* durable roster through the same rebuilt panel instead of a second UI. */
|
|
@@ -271,6 +394,7 @@ function historicCardTeam(data, owner) {
|
|
|
271
394
|
teamId: data.teamId,
|
|
272
395
|
name: data.teamName,
|
|
273
396
|
captainSessionId: data.captainSessionId || owner,
|
|
397
|
+
phase: 'running',
|
|
274
398
|
members: data.members.map((member) => ({
|
|
275
399
|
...member,
|
|
276
400
|
status: 'removed',
|
|
@@ -286,7 +410,7 @@ function historicCardTeam(data, owner) {
|
|
|
286
410
|
captainInbox: [],
|
|
287
411
|
};
|
|
288
412
|
}
|
|
289
|
-
export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
413
|
+
export function ActivityPanel({ sessionsList, modelDirectories, openMember, t }) {
|
|
290
414
|
// Navigating to a member's subagent transcript is an explicit departure:
|
|
291
415
|
// hide the floater immediately instead of waiting out the autocollapse
|
|
292
416
|
// grace, so the panel never lingers over the member session.
|
|
@@ -309,7 +433,15 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
309
433
|
const frameRef = useRef(null);
|
|
310
434
|
const pendingLayoutRef = useRef(null);
|
|
311
435
|
const current = useSyncExternalStore(sessionsList.subscribe, sessionsList.getSnapshot).current;
|
|
436
|
+
const autoOpenTrackerRef = useRef({ sessionId: current, restoreComplete: false, liveTeamIds: new Set() });
|
|
312
437
|
const monitorTargets = useSyncExternalStore(subscribeActivityMonitorTargets, getActivityMonitorTargetsSnapshot);
|
|
438
|
+
const returnToComposer = () => {
|
|
439
|
+
setOpen(false);
|
|
440
|
+
setOpenOwner(undefined);
|
|
441
|
+
window.requestAnimationFrame(() => {
|
|
442
|
+
document.querySelector('[data-composer-card] textarea')?.focus();
|
|
443
|
+
});
|
|
444
|
+
};
|
|
313
445
|
const { teams, archivedTeams } = useSyncExternalStore(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
|
|
314
446
|
const currentTargets = useMemo(() => current === undefined ? [] : monitorTargets.filter((target) => target.sessionId === current), [current, monitorTargets]);
|
|
315
447
|
const currentRef = useRef(current);
|
|
@@ -374,12 +506,18 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
374
506
|
// removes the old panel immediately instead of waiting for the no-team
|
|
375
507
|
// autoclose grace period on the destination page.
|
|
376
508
|
useLayoutEffect(() => {
|
|
509
|
+
const tracker = autoOpenTrackerRef.current;
|
|
510
|
+
if (tracker.sessionId !== current) {
|
|
511
|
+
tracker.sessionId = current;
|
|
512
|
+
tracker.restoreComplete = false;
|
|
513
|
+
tracker.liveTeamIds = new Set();
|
|
514
|
+
setWasActive(false);
|
|
515
|
+
setAutoOpened(false);
|
|
516
|
+
}
|
|
377
517
|
if (openOwner === undefined || openOwner === current)
|
|
378
518
|
return;
|
|
379
519
|
setOpen(false);
|
|
380
520
|
setOpenOwner(undefined);
|
|
381
|
-
setWasActive(false);
|
|
382
|
-
setAutoOpened(false);
|
|
383
521
|
}, [current, openOwner]);
|
|
384
522
|
// Only the wide docked mode asks the conversation column to yield. Floating
|
|
385
523
|
// and compact modes are intentionally true overlays. The width is written as
|
|
@@ -407,7 +545,23 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
407
545
|
// also performs one cold-start discovery pass so archived/cardless teams
|
|
408
546
|
// survive a browser or `dsh web` restart.
|
|
409
547
|
const controller = startActivityPolling(currentTargets, { discoverySessionId: current });
|
|
410
|
-
|
|
548
|
+
let active = true;
|
|
549
|
+
const tracker = autoOpenTrackerRef.current;
|
|
550
|
+
if (tracker.sessionId === current && !tracker.restoreComplete) {
|
|
551
|
+
void controller.firstTick.then(() => {
|
|
552
|
+
const latest = autoOpenTrackerRef.current;
|
|
553
|
+
if (!active || latest.sessionId !== current || latest.restoreComplete)
|
|
554
|
+
return;
|
|
555
|
+
latest.liveTeamIds = new Set(getActivitySnapshotsSnapshot().teams
|
|
556
|
+
.filter((team) => team.captainSessionId === current)
|
|
557
|
+
.map((team) => team.teamId));
|
|
558
|
+
latest.restoreComplete = true;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
return () => {
|
|
562
|
+
active = false;
|
|
563
|
+
controller.stop();
|
|
564
|
+
};
|
|
411
565
|
}, [current, currentTargets]);
|
|
412
566
|
useEffect(() => {
|
|
413
567
|
const onOpenPanel = (event) => {
|
|
@@ -443,13 +597,26 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
443
597
|
const visibleHistoric = useMemo(() => (current === undefined ? [] : [...historic.values()].filter(({ data, owner }) => owner === current && !teams.some((live) => live.captainSessionId === current && live.teamId === data.teamId) && !archivedTeams.some((archived) => archived.captainSessionId === current && archived.teamId === data.teamId))), [historic, current, teams, archivedTeams]);
|
|
444
598
|
const visibleArchived = useMemo(() => (current === undefined ? [] : archivedTeams.filter((team) => team.captainSessionId === current && !teams.some((live) => live.captainSessionId === current && live.teamId === team.teamId))), [archivedTeams, current, teams]);
|
|
445
599
|
const visibleCount = visibleTeams.length + visibleArchived.length + visibleHistoric.length;
|
|
600
|
+
const visibleLiveTeamIds = useMemo(() => visibleTeams.map((team) => team.teamId).sort(), [visibleTeams]);
|
|
601
|
+
const visibleLiveTeamKey = visibleLiveTeamIds.join('\u0000');
|
|
446
602
|
useEffect(() => {
|
|
603
|
+
const tracker = autoOpenTrackerRef.current;
|
|
604
|
+
const settled = performance.now() - mountedAtRef.current >= AUTO_OPEN_SETTLE_MS;
|
|
605
|
+
const shouldAutoExpand = tracker.sessionId === current && activityPanelShouldAutoExpand({
|
|
606
|
+
alreadyAutoOpened: autoOpened,
|
|
607
|
+
pageSettled: settled,
|
|
608
|
+
restoreComplete: tracker.restoreComplete,
|
|
609
|
+
previousLiveTeamIds: tracker.liveTeamIds,
|
|
610
|
+
currentLiveTeamIds: visibleLiveTeamIds,
|
|
611
|
+
});
|
|
612
|
+
if (tracker.sessionId === current && tracker.restoreComplete) {
|
|
613
|
+
tracker.liveTeamIds = new Set(visibleLiveTeamIds);
|
|
614
|
+
}
|
|
447
615
|
if (visibleCount > 0) {
|
|
448
616
|
setWasActive(true);
|
|
449
|
-
//
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
if (!autoOpened && settled) {
|
|
617
|
+
// Existing state restored for a reopened conversation stays collapsed.
|
|
618
|
+
// Only a live team that appears after the restore pass may auto-expand.
|
|
619
|
+
if (shouldAutoExpand) {
|
|
453
620
|
setOpenOwner(current);
|
|
454
621
|
setOpen(true);
|
|
455
622
|
setAutoOpened(true);
|
|
@@ -467,7 +634,7 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
467
634
|
setAutoOpened(false);
|
|
468
635
|
}, AUTOCLOSE_GRACE_MS);
|
|
469
636
|
return () => { clearTimeout(timer); };
|
|
470
|
-
}, [visibleCount, autoOpened, wasActive]);
|
|
637
|
+
}, [visibleCount, visibleLiveTeamKey, autoOpened, wasActive, current]);
|
|
471
638
|
const busy = useMemo(() => visibleTeams.some((team) => team.members.some((member) => member.activity === 'working')), [visibleTeams]);
|
|
472
639
|
const hasTeams = visibleCount > 0;
|
|
473
640
|
// Auto-height panels do not store their live content height. Capture the
|
|
@@ -599,7 +766,9 @@ export function ActivityPanel({ sessionsList, openMember, t }) {
|
|
|
599
766
|
setOpenOwner(undefined);
|
|
600
767
|
}, "aria-label": t('activity.collapse'), title: t('activity.collapse'), children: _jsx(IconChevronDownOutline14, {}) })] })] }), _jsx("div", { className: css.teams, children: visibleCount === 0
|
|
601
768
|
? _jsx("span", { className: css.emptyHint, children: t('activity.empty') })
|
|
602
|
-
: (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team,
|
|
769
|
+
: (_jsxs(_Fragment, { children: [visibleTeams.map((team) => (_jsx(TeamSection, { team: team, modelDirectory: team.phase === 'staged'
|
|
770
|
+
? modelDirectories.directoryFor(team.captainSessionId)
|
|
771
|
+
: undefined, onContinuePlanning: returnToComposer, onDiscarded: returnToComposer, 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(team.phase === 'staged' ? 'archive.discardedLabel' : 'archive.label') }), _jsx(TeamSection, { team: team, onNavigate: navigateToSession, t: t, historic: true })] }, `${team.captainSessionId}:${team.teamId}`))), visibleHistoric.map(({ data: team, owner }) => {
|
|
603
772
|
const teamKey = `${owner}:${team.teamId}`;
|
|
604
773
|
return (_jsx(TeamSection, { team: historicCardTeam(team, owner), onNavigate: navigateToSession, t: t, historic: true }, teamKey));
|
|
605
774
|
})] })) }), !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 })] }))] }))] }));
|