@nanmicoder/dsh-agent-teams 0.1.10 → 0.1.11

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.
@@ -236,8 +236,11 @@ function TeamSection({ team, onNavigate, historic = false }) {
236
236
  const allCompleted = team.tasks.length > 0 && completedCount === team.tasks.length;
237
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) => {
238
238
  const owned = team.tasks.filter((task) => task.assignee === member.name);
239
- return (_jsxs("div", { className: css.memberBlock, "data-activity": member.activity, children: [_jsx("span", { className: css.memberBranch, "aria-hidden": true, children: _jsx("span", {}) }), _jsxs("button", { type: "button", className: css.memberRow, "data-activity": member.activity, onClick: () => { if (member.id !== '')
240
- onNavigate(member.id); }, children: [_jsxs("span", { className: css.memberAvatar, "data-unread": member.unread > 0, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, style: { background: accentOf(member.id) }, children: memberInitial(member.name) })), _jsx("img", { className: css.stateArt, "data-activity": member.activity, src: ACTION_ART[member.activity], alt: "", "aria-hidden": true })] }), _jsxs("span", { className: css.memberInfo, children: [_jsxs("span", { className: css.memberLine, children: [_jsx("span", { className: css.memberName, children: member.name }), member.role !== '' && _jsx("span", { className: css.memberRole, children: member.role }), _jsxs("span", { className: css.memberState, "data-activity": member.activity, children: [_jsx(WorkGlyph, { active: member.activity === 'working' }), memberStateLabel(member, team.tasks, historic)] })] }), _jsx("span", { className: css.memberStatusLine, children: memberStatusText(member, team.tasks) })] }), _jsxs("span", { className: css.memberCount, children: [member.done, "/", member.total] })] }), _jsxs("div", { className: css.assignmentLine, children: [_jsx("span", { className: css.assignmentLabel, children: "\u961F\u957F\u6D3E\u53D1" }), _jsx("span", { className: css.assignmentTasks, children: owned.length === 0
239
+ return (_jsxs("div", { className: css.memberBlock, "data-activity": member.activity, children: [_jsx("span", { className: css.memberBranch, "aria-hidden": true, children: _jsx("span", {}) }), _jsxs("button", { type: "button", className: css.memberRow, "data-activity": member.activity, onClick: () => {
240
+ if (member.id !== '') {
241
+ onNavigate(team.captainSessionId, member.id);
242
+ }
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
241
244
  ? _jsx("span", { className: css.taskEmpty, children: "\u6682\u65E0\u4EFB\u52A1" })
242
245
  : owned.map((task) => (_jsx("span", { className: css.assignmentChip, "data-state": taskTone(task.state, task.status), title: task.subject, children: task.id }, task.id))) })] })] }, member.id));
243
246
  })] })] }), _jsx(DependencyMap, { tasks: team.tasks })] }));
@@ -268,14 +271,14 @@ function historicCardTeam(data, owner) {
268
271
  /** The top-right activity floater. Teams follow the current session: live
269
272
  * snapshots and historic card summaries are only shown while their captain
270
273
  * session is the one currently open. */
271
- export function ActivityPanel({ sessionsList, openSession }) {
274
+ export function ActivityPanel({ sessionsList, openMember }) {
272
275
  // Navigating to a member's subagent transcript is an explicit departure:
273
276
  // hide the floater immediately instead of waiting out the autocollapse
274
277
  // grace, so the panel never lingers over the member session.
275
- const navigateToSession = (id) => {
278
+ const navigateToSession = (parentId, childId) => {
276
279
  setOpen(false);
277
280
  setWasActive(false);
278
- openSession(id);
281
+ openMember(parentId, childId);
279
282
  };
280
283
  const [open, setOpen] = useState(false);
281
284
  const [openOwner, setOpenOwner] = useState();
@@ -383,13 +386,14 @@ export function ActivityPanel({ sessionsList, openSession }) {
383
386
  };
384
387
  }, [compact, expanded, geometry.mode, geometry.width]);
385
388
  useEffect(() => {
386
- // Installing the plugin alone must not touch the state route. A successful
387
- // AgentTeams conversation card registers the demand that reaches here.
388
- if (currentTargets.length === 0)
389
+ if (current === undefined)
389
390
  return;
390
- const controller = startActivityPolling(currentTargets);
391
+ // Cards keep live teams on the normal cadence. The current-session scope
392
+ // also performs one cold-start discovery pass so archived/cardless teams
393
+ // survive a browser or `dsh web` restart.
394
+ const controller = startActivityPolling(currentTargets, { discoverySessionId: current });
391
395
  return () => { controller.stop(); };
392
- }, [currentTargets]);
396
+ }, [current, currentTargets]);
393
397
  useEffect(() => {
394
398
  const onOpenPanel = (event) => {
395
399
  const activeSession = currentRef.current;
@@ -30,7 +30,7 @@ function openActivityPanel(data) {
30
30
  }));
31
31
  }
32
32
  /** Render one durable team as a compact conversation card. */
33
- export function AgentTeamsCard({ node, openSession, sessionId }) {
33
+ export function AgentTeamsCard({ node, openMember, sessionId }) {
34
34
  const data = node.data;
35
35
  // `conversation.chat.node` is session-scoped, so its framework-owned id is
36
36
  // a stable owner even while another conversation becomes current.
@@ -47,6 +47,8 @@ export function AgentTeamsCard({ node, openSession, sessionId }) {
47
47
  teamName: snapshot?.name ?? data.teamName,
48
48
  members: snapshot?.members.map((member) => ({ id: member.id, name: member.name, role: member.role })) ?? data.members,
49
49
  }), [data, owner, snapshot]);
50
- return (_jsxs("section", { className: css.root, "data-agent-teams-card": true, "data-team-id": resolved.teamId, children: [_jsxs("header", { className: css.head, children: [_jsx("img", { className: css.leadAvatar, src: LEAD_ART, alt: "", "aria-hidden": true }), _jsx("span", { className: css.teamName, title: resolved.teamName, children: resolved.teamName }), _jsxs("span", { className: css.memberCount, children: [resolved.members.length, " \u540D\u6210\u5458"] }), _jsx("button", { type: "button", className: css.panelButton, onClick: () => { openActivityPanel(resolved); }, "aria-label": "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", title: "\u6253\u5F00\u6D3B\u52A8\u9762\u677F", children: "\u6D3B\u52A8\u9762\u677F" })] }), resolved.members.length > 0 && (_jsx("div", { className: css.members, children: resolved.members.map((member) => (_jsxs("button", { type: "button", className: css.member, onClick: () => { if (member.id !== '')
51
- openSession(member.id); }, title: member.role === '' ? member.name : `${member.name} · ${member.role}`, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, children: member.name.trim().slice(0, 1).toUpperCase() || '?' })), _jsx("span", { className: css.memberName, children: member.name })] }, member.id))) }))] }));
50
+ return (_jsxs("section", { className: css.root, "data-agent-teams-card": true, "data-team-id": resolved.teamId, children: [_jsxs("header", { className: css.head, children: [_jsx("img", { className: css.leadAvatar, src: LEAD_ART, alt: "", "aria-hidden": true }), _jsx("span", { className: css.teamName, title: resolved.teamName, children: resolved.teamName }), _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: () => {
51
+ if (member.id !== '')
52
+ openMember(owner, member.id);
53
+ }, title: member.role === '' ? member.name : `${member.name} · ${member.role}`, children: [memberArtUrl(member.name, member.role) !== null ? (_jsx("img", { className: css.memberArt, src: memberArtUrl(member.name, member.role) ?? '', alt: "", "aria-hidden": true })) : (_jsx("span", { className: css.memberInitial, children: member.name.trim().slice(0, 1).toUpperCase() || '?' })), _jsx("span", { className: css.memberName, children: member.name })] }, member.id))) }))] }));
52
54
  }
@@ -104,12 +104,17 @@ export const ACTIVITY_STATE_URL = '/plugins/dsh-agent-teams/state';
104
104
  /**
105
105
  * Start the single polling loop for the current session's requested targets.
106
106
  *
107
- * With no targets this is deliberately inert: installing the plugin must not
108
- * touch the state route. Live state is polled at the normal cadence; archive
109
- * state is fetched only as a one-time fallback for targets no longer live.
107
+ * With neither targets nor a discovery session this is deliberately inert.
108
+ * A discovery session performs one live+archive pass after selection/restart;
109
+ * it keeps polling only while that captain still owns a live team. This
110
+ * restores legacy/cardless history without turning every ordinary session
111
+ * into a permanent one-second filesystem scan. Explicit card targets retain
112
+ * the normal cadence, and archive state is refreshed when a target or a
113
+ * previously discovered live team disappears.
110
114
  */
111
115
  export function startActivityPolling(monitorTargets, runtime = {}) {
112
- if (monitorTargets.length === 0) {
116
+ const discoverySessionId = runtime.discoverySessionId?.trim();
117
+ if (monitorTargets.length === 0 && (discoverySessionId === undefined || discoverySessionId === '')) {
113
118
  return { firstTick: Promise.resolve(), stop: () => { } };
114
119
  }
115
120
  const fetchState = runtime.fetchState ?? ((url, init) => fetch(url, init));
@@ -119,10 +124,16 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
119
124
  const settleTargets = runtime.settleTargets ?? settleActivityMonitorTargets;
120
125
  let cancelled = false;
121
126
  let inFlight = false;
127
+ let discoveryComplete = false;
128
+ let discoveredLiveKeys = new Set();
122
129
  let controller;
123
130
  const tick = async () => {
124
131
  if (inFlight || cancelled)
125
132
  return;
133
+ // A cardless ordinary or archive-only session needs one recovery pass,
134
+ // then stays dormant until the component is recreated for another session.
135
+ if (discoveryComplete && monitorTargets.length === 0 && discoveredLiveKeys.size === 0)
136
+ return;
126
137
  inFlight = true;
127
138
  controller = new AbortController();
128
139
  try {
@@ -137,11 +148,24 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
137
148
  return;
138
149
  const liveTeams = body.teams;
139
150
  publishSnapshots({ teams: liveTeams });
151
+ const previousDiscoveredKeys = discoveredLiveKeys;
152
+ discoveredLiveKeys = new Set(discoverySessionId === undefined || discoverySessionId === ''
153
+ ? []
154
+ : liveTeams
155
+ .filter((team) => team.captainSessionId === discoverySessionId)
156
+ .map((team) => team.teamId));
157
+ const discoveredTeamArchived = [...previousDiscoveredKeys]
158
+ .some((teamId) => !discoveredLiveKeys.has(teamId));
140
159
  const missing = monitorTargets.filter((target) => !liveTeams.some((team) => team.captainSessionId === target.sessionId && team.teamId === target.teamId));
141
- if (missing.length === 0)
160
+ const needsDiscoveryArchive = discoverySessionId !== undefined
161
+ && discoverySessionId !== ''
162
+ && !discoveryComplete;
163
+ if (missing.length === 0 && !needsDiscoveryArchive && !discoveredTeamArchived)
142
164
  return;
143
- // Archives are immutable. A successful fallback retires every missing
144
- // target, including legacy cards whose host archive no longer exists.
165
+ // Archives are immutable per team generation. A successful fallback
166
+ // retires every missing explicit target, including legacy cards whose
167
+ // host archive no longer exists; discovery remains available from the
168
+ // shared snapshot after this controller becomes dormant.
145
169
  const archivedResponse = await fetchState(`${ACTIVITY_STATE_URL}?archived=1`, {
146
170
  cache: 'no-store',
147
171
  signal: controller.signal,
@@ -152,6 +176,7 @@ export function startActivityPolling(monitorTargets, runtime = {}) {
152
176
  if (cancelled || !Array.isArray(archivedBody.teams))
153
177
  return;
154
178
  publishSnapshots({ archivedTeams: archivedBody.teams });
179
+ discoveryComplete = true;
155
180
  settleTargets(new Set(missing.map((target) => target.key)));
156
181
  }
157
182
  catch (error) {
@@ -2,6 +2,7 @@ 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 { openAgentTeamMember } from "./session-navigation.js";
5
6
  /** Required services: conversation nodes, slots, and sessions navigation. */
6
7
  export const inject = ['conversationEvents', 'slots', 'sessions'];
7
8
  /** The replayed user message is the canonical transcript entry. */
@@ -14,7 +15,12 @@ function HiddenAgentTeamsCommand() {
14
15
  * monitor via a window event — the recovery path for an old session.
15
16
  */
16
17
  export function apply(ctx) {
17
- const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openSession: (id) => { ctx.sessions.open(id); } });
18
+ const openMember = (parentId, childId) => {
19
+ void openAgentTeamMember(ctx.sessions, parentId, childId).catch((error) => {
20
+ console.warn(`agent-teams: failed to open member transcript ${childId}: ${String(error)}`);
21
+ });
22
+ };
23
+ const Panel = () => _jsx(ActivityPanel, { sessionsList: ctx.sessions.list, openMember: openMember });
18
24
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
19
25
  name: 'shell.overlay',
20
26
  id: 'agent-teams-activity',
@@ -33,7 +39,7 @@ export function apply(ctx) {
33
39
  name: 'conversation.chat.node',
34
40
  key: 'agent-teams',
35
41
  inject: () => ({
36
- openSession: (id) => { ctx.sessions.open(id); },
42
+ openMember,
37
43
  }),
38
44
  }, AgentTeamsCard));
39
45
  }
@@ -0,0 +1,21 @@
1
+ /** Version-tolerant navigation into durable AgentTeams member transcripts. */
2
+ /**
3
+ * Open one member's persisted transcript.
4
+ *
5
+ * Harness rc.8 intentionally removed cold subagents from the ordinary session
6
+ * list. They must first be rediscovered in their parent's catalog, then opened
7
+ * with the exact parent/child/mode address. Older runtimes have only `open()`;
8
+ * the fallback preserves the plugin's rc.6 peer range.
9
+ */
10
+ export async function openAgentTeamMember(sessions, parentSessionId, childSessionId) {
11
+ if (sessions.openSubagent === undefined || sessions.refreshSubagents === undefined) {
12
+ sessions.open(childSessionId);
13
+ return 'session';
14
+ }
15
+ await sessions.refreshSubagents(parentSessionId);
16
+ const retained = sessions.subagentAddress?.(childSessionId);
17
+ sessions.openSubagent(retained?.parentSessionId === parentSessionId
18
+ ? retained
19
+ : { parentSessionId, childSessionId, mode: 'continuable' });
20
+ return 'subagent';
21
+ }
package/lib/client.js CHANGED
@@ -246,12 +246,17 @@ window.__ModuleLoader__.load({
246
246
  /**
247
247
  * Start the single polling loop for the current session's requested targets.
248
248
  *
249
- * With no targets this is deliberately inert: installing the plugin must not
250
- * touch the state route. Live state is polled at the normal cadence; archive
251
- * state is fetched only as a one-time fallback for targets no longer live.
249
+ * With neither targets nor a discovery session this is deliberately inert.
250
+ * A discovery session performs one live+archive pass after selection/restart;
251
+ * it keeps polling only while that captain still owns a live team. This
252
+ * restores legacy/cardless history without turning every ordinary session
253
+ * into a permanent one-second filesystem scan. Explicit card targets retain
254
+ * the normal cadence, and archive state is refreshed when a target or a
255
+ * previously discovered live team disappears.
252
256
  */
253
257
  function startActivityPolling(monitorTargets, runtime = {}) {
254
- if (monitorTargets.length === 0) return {
258
+ const discoverySessionId = runtime.discoverySessionId?.trim();
259
+ if (monitorTargets.length === 0 && (discoverySessionId === void 0 || discoverySessionId === "")) return {
255
260
  firstTick: Promise.resolve(),
256
261
  stop: () => {}
257
262
  };
@@ -264,9 +269,12 @@ window.__ModuleLoader__.load({
264
269
  const settleTargets = runtime.settleTargets ?? settleActivityMonitorTargets;
265
270
  let cancelled = false;
266
271
  let inFlight = false;
272
+ let discoveryComplete = false;
273
+ let discoveredLiveKeys = /* @__PURE__ */ new Set();
267
274
  let controller;
268
275
  const tick = async () => {
269
276
  if (inFlight || cancelled) return;
277
+ if (discoveryComplete && monitorTargets.length === 0 && discoveredLiveKeys.size === 0) return;
270
278
  inFlight = true;
271
279
  controller = new AbortController();
272
280
  try {
@@ -279,8 +287,12 @@ window.__ModuleLoader__.load({
279
287
  if (cancelled || !Array.isArray(body.teams)) return;
280
288
  const liveTeams = body.teams;
281
289
  publishSnapshots({ teams: liveTeams });
290
+ const previousDiscoveredKeys = discoveredLiveKeys;
291
+ discoveredLiveKeys = new Set(discoverySessionId === void 0 || discoverySessionId === "" ? [] : liveTeams.filter((team) => team.captainSessionId === discoverySessionId).map((team) => team.teamId));
292
+ const discoveredTeamArchived = [...previousDiscoveredKeys].some((teamId) => !discoveredLiveKeys.has(teamId));
282
293
  const missing = monitorTargets.filter((target) => !liveTeams.some((team) => team.captainSessionId === target.sessionId && team.teamId === target.teamId));
283
- if (missing.length === 0) return;
294
+ const needsDiscoveryArchive = discoverySessionId !== void 0 && discoverySessionId !== "" && !discoveryComplete;
295
+ if (missing.length === 0 && !needsDiscoveryArchive && !discoveredTeamArchived) return;
284
296
  const archivedResponse = await fetchState(`${ACTIVITY_STATE_URL}?archived=1`, {
285
297
  cache: "no-store",
286
298
  signal: controller.signal
@@ -289,6 +301,7 @@ window.__ModuleLoader__.load({
289
301
  const archivedBody = await archivedResponse.json();
290
302
  if (cancelled || !Array.isArray(archivedBody.teams)) return;
291
303
  publishSnapshots({ archivedTeams: archivedBody.teams });
304
+ discoveryComplete = true;
292
305
  settleTargets(new Set(missing.map((target) => target.key)));
293
306
  } catch (error) {
294
307
  if (error?.name === "AbortError") return;
@@ -401,7 +414,7 @@ window.__ModuleLoader__.load({
401
414
  } }));
402
415
  }
403
416
  /** Render one durable team as a compact conversation card. */
404
- function AgentTeamsCard({ node, openSession, sessionId }) {
417
+ function AgentTeamsCard({ node, openMember, sessionId }) {
405
418
  const data = node.data;
406
419
  const owner = data.captainSessionId || sessionId;
407
420
  const { teams, archivedTeams } = (0, react.useSyncExternalStore)(subscribeActivitySnapshots, getActivitySnapshotsSnapshot);
@@ -462,7 +475,7 @@ window.__ModuleLoader__.load({
462
475
  type: "button",
463
476
  className: AgentTeamsCard_module_css_default.member,
464
477
  onClick: () => {
465
- if (member.id !== "") openSession(member.id);
478
+ if (member.id !== "") openMember(owner, member.id);
466
479
  },
467
480
  title: member.role === "" ? member.name : `${member.name} · ${member.role}`,
468
481
  children: [memberArtUrl(member.name, member.role) !== null ? (0, react_jsx_runtime.jsx)("img", {
@@ -1272,7 +1285,7 @@ window.__ModuleLoader__.load({
1272
1285
  className: ActivityPanel_module_css_default.memberRow,
1273
1286
  "data-activity": member.activity,
1274
1287
  onClick: () => {
1275
- if (member.id !== "") onNavigate(member.id);
1288
+ if (member.id !== "") onNavigate(team.captainSessionId, member.id);
1276
1289
  },
1277
1290
  children: [
1278
1291
  (0, react_jsx_runtime.jsxs)("span", {
@@ -1383,11 +1396,11 @@ window.__ModuleLoader__.load({
1383
1396
  /** The top-right activity floater. Teams follow the current session: live
1384
1397
  * snapshots and historic card summaries are only shown while their captain
1385
1398
  * session is the one currently open. */
1386
- function ActivityPanel({ sessionsList, openSession }) {
1387
- const navigateToSession = (id) => {
1399
+ function ActivityPanel({ sessionsList, openMember }) {
1400
+ const navigateToSession = (parentId, childId) => {
1388
1401
  setOpen(false);
1389
1402
  setWasActive(false);
1390
- openSession(id);
1403
+ openMember(parentId, childId);
1391
1404
  };
1392
1405
  const [open, setOpen] = (0, react.useState)(false);
1393
1406
  const [openOwner, setOpenOwner] = (0, react.useState)();
@@ -1480,12 +1493,12 @@ window.__ModuleLoader__.load({
1480
1493
  geometry.width
1481
1494
  ]);
1482
1495
  (0, react.useEffect)(() => {
1483
- if (currentTargets.length === 0) return;
1484
- const controller = startActivityPolling(currentTargets);
1496
+ if (current === void 0) return;
1497
+ const controller = startActivityPolling(currentTargets, { discoverySessionId: current });
1485
1498
  return () => {
1486
1499
  controller.stop();
1487
1500
  };
1488
- }, [currentTargets]);
1501
+ }, [current, currentTargets]);
1489
1502
  (0, react.useEffect)(() => {
1490
1503
  const onOpenPanel = (event) => {
1491
1504
  const activeSession = currentRef.current;
@@ -1867,6 +1880,31 @@ window.__ModuleLoader__.load({
1867
1880
  }
1868
1881
  };
1869
1882
  //#endregion
1883
+ //#region lib/client/session-navigation.js
1884
+ /** Version-tolerant navigation into durable AgentTeams member transcripts. */
1885
+ /**
1886
+ * Open one member's persisted transcript.
1887
+ *
1888
+ * Harness rc.8 intentionally removed cold subagents from the ordinary session
1889
+ * list. They must first be rediscovered in their parent's catalog, then opened
1890
+ * with the exact parent/child/mode address. Older runtimes have only `open()`;
1891
+ * the fallback preserves the plugin's rc.6 peer range.
1892
+ */
1893
+ async function openAgentTeamMember(sessions, parentSessionId, childSessionId) {
1894
+ if (sessions.openSubagent === void 0 || sessions.refreshSubagents === void 0) {
1895
+ sessions.open(childSessionId);
1896
+ return "session";
1897
+ }
1898
+ await sessions.refreshSubagents(parentSessionId);
1899
+ const retained = sessions.subagentAddress?.(childSessionId);
1900
+ sessions.openSubagent(retained?.parentSessionId === parentSessionId ? retained : {
1901
+ parentSessionId,
1902
+ childSessionId,
1903
+ mode: "continuable"
1904
+ });
1905
+ return "subagent";
1906
+ }
1907
+ //#endregion
1870
1908
  //#region lib/client/index.js
1871
1909
  /** Required services: conversation nodes, slots, and sessions navigation. */
1872
1910
  const inject = [
@@ -1884,11 +1922,14 @@ window.__ModuleLoader__.load({
1884
1922
  * monitor via a window event — the recovery path for an old session.
1885
1923
  */
1886
1924
  function apply(ctx) {
1925
+ const openMember = (parentId, childId) => {
1926
+ openAgentTeamMember(ctx.sessions, parentId, childId).catch((error) => {
1927
+ console.warn(`agent-teams: failed to open member transcript ${childId}: ${String(error)}`);
1928
+ });
1929
+ };
1887
1930
  const Panel = () => (0, react_jsx_runtime.jsx)(ActivityPanel, {
1888
1931
  sessionsList: ctx.sessions.list,
1889
- openSession: (id) => {
1890
- ctx.sessions.open(id);
1891
- }
1932
+ openMember
1892
1933
  });
1893
1934
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
1894
1935
  name: "shell.overlay",
@@ -1904,9 +1945,7 @@ window.__ModuleLoader__.load({
1904
1945
  ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1905
1946
  name: "conversation.chat.node",
1906
1947
  key: "agent-teams",
1907
- inject: () => ({ openSession: (id) => {
1908
- ctx.sessions.open(id);
1909
- } })
1948
+ inject: () => ({ openMember })
1910
1949
  }, AgentTeamsCard));
1911
1950
  }
1912
1951
  //#endregion