@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.
package/lib/members.js CHANGED
@@ -318,44 +318,22 @@ export function interruptMember(ctx, captain, childId) {
318
318
  ctx.logger.warn(`agent-teams: interrupt of member ${childId} failed: ${String(error)}`);
319
319
  }
320
320
  }
321
- /** Resolve one live parent's workspace-scoped retirement index. */
322
- async function retiredForParent(ctx, parentId, stateDir) {
323
- const parent = ctx.agents.get(parentId);
324
- return parent === undefined
325
- ? new Set()
326
- : readRetiredMemberIds(join(parent.session.header.cwd ?? process.cwd(), stateDir));
327
- }
328
321
  /**
329
322
  * Install the missing per-child retirement boundary above Harness rc.6.
330
323
  *
331
324
  * Upstream `interrupt()` deliberately preserves continuable sessions and the
332
325
  * upstream seam exposes no targeted forget/retire method. The durable
333
- * AgentTeams index therefore guards all three public continuation boundaries:
334
- * retired rows disappear from `list_agents` (children and descendants), and a
335
- * direct `followup()` is rejected before it can cold-resume the member. Exact
336
- * ids keep unrelated subagents untouched; transcripts remain in persistence
337
- * for archived-team review.
326
+ * AgentTeams index therefore rejects `followup()` before it can cold-resume a
327
+ * retired member. Catalog rows deliberately remain discoverable: Harness rc.8
328
+ * uses the direct-child catalog to authorize historical transcript reads and
329
+ * `openSubagent()`, so filtering those rows would make an archived member's
330
+ * persisted conversation inaccessible. Exact ids keep unrelated subagents
331
+ * untouched while the followup boundary still prevents further model turns.
338
332
  */
339
333
  export function installRetiredMemberGuard(ctx, stateDir) {
340
334
  const runtime = ctx.subagents;
341
335
  ctx.effect(() => {
342
- const listChildren = runtime.listChildren;
343
- const listDescendants = runtime.listDescendants;
344
336
  const followup = runtime.followup;
345
- const guardedChildren = async (parentId, signal) => {
346
- const [entries, retired] = await Promise.all([
347
- listChildren.call(runtime, parentId, signal),
348
- retiredForParent(ctx, parentId, stateDir),
349
- ]);
350
- return entries.filter(entry => !retired.has(entry.id));
351
- };
352
- const guardedDescendants = async (rootId, signal) => {
353
- const [entries, retired] = await Promise.all([
354
- listDescendants.call(runtime, rootId, signal),
355
- retiredForParent(ctx, rootId, stateDir),
356
- ]);
357
- return entries.filter(entry => !retired.has(entry.id));
358
- };
359
337
  const guardedFollowup = async (parent, childId, content, options) => {
360
338
  const retired = await readRetiredMemberIds(join(parent.session.header.cwd ?? process.cwd(), stateDir));
361
339
  if (retired.has(childId)) {
@@ -363,36 +341,32 @@ export function installRetiredMemberGuard(ctx, stateDir) {
363
341
  }
364
342
  return followup.call(runtime, parent, childId, content, options);
365
343
  };
366
- runtime.listChildren = guardedChildren;
367
- runtime.listDescendants = guardedDescendants;
368
344
  runtime.followup = guardedFollowup;
369
345
  return () => {
370
- if (runtime.listChildren === guardedChildren)
371
- runtime.listChildren = listChildren;
372
- if (runtime.listDescendants === guardedDescendants)
373
- runtime.listDescendants = listDescendants;
374
346
  if (runtime.followup === guardedFollowup)
375
347
  runtime.followup = followup;
376
348
  };
377
349
  }, 'agent-teams: retired member guard');
378
350
  }
379
351
  /**
380
- * Snapshot each direct continuable child's real driver activity under the
381
- * captain's session. `listChildren().activity` is only session residency, so
382
- * live children are refined through the Agent registry exactly like Harness's
383
- * shipped `list_agents` tool.
384
- * @param ctx - the plugin context (injects `subagents`).
385
- * @param captainSessionId - the captain's session id.
386
- * @returns child id → activity, missing entries are unknown children.
352
+ * Snapshot the real driver activity for durable member ids.
353
+ *
354
+ * The team record is the membership authority, so this path intentionally no
355
+ * longer depends on `listChildren()`'s versioned projection shape. Harness
356
+ * rc.8 changed those rows to branded `SessionId` values plus residency-only
357
+ * `activity`; neither is needed to answer whether the live Agent driver is
358
+ * running, idle, or absent/ready.
359
+ * @param ctx - the plugin context (injects `agents`).
360
+ * @param memberIds - child ids restored from the durable team record.
361
+ * @returns child id → live activity.
387
362
  */
388
- export async function memberActivity(ctx, captainSessionId) {
389
- const entries = await ctx.subagents.listChildren(brandedSessionId(captainSessionId));
363
+ export function memberActivity(ctx, memberIds) {
390
364
  const activity = new Map();
391
- for (const entry of entries) {
392
- if (entry.kind !== 'child')
365
+ for (const id of memberIds) {
366
+ if (id === '')
393
367
  continue;
394
- const live = ctx.agents.get(entry.id);
395
- activity.set(entry.id, live === undefined ? 'ready' : live.status);
368
+ const live = ctx.agents.get(brandedSessionId(id));
369
+ activity.set(id, live === undefined ? 'ready' : live.status);
396
370
  }
397
371
  return activity;
398
372
  }
package/lib/snapshot.js CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { readdir } from 'node:fs/promises';
11
11
  import { join } from 'node:path';
12
+ import { memberActivity } from "./members.js";
12
13
  import { CAPTAIN_KEY, listArchivedTeamIds, readArchivedTeam, readUnreadMailbox, readTeam, taskDepthsById, taskVisualState, } from "./state.js";
13
14
  /** The current task of a member: its first unfinished owned task. */
14
15
  function currentTaskOf(memberName, tasks) {
@@ -32,21 +33,9 @@ export async function assembleTeamSnapshot(ctx, stateRoot, workspace, state, opt
32
33
  const roster = options.includeRemoved === true
33
34
  ? state.members
34
35
  : state.members.filter((member) => member.status !== 'removed');
35
- const activity = new Map();
36
- if (options.historic !== true) {
37
- try {
38
- const children = await ctx.subagents.listChildren(state.captainSessionId);
39
- for (const entry of children) {
40
- if (entry.kind === 'child') {
41
- const live = ctx.agents.get(entry.id);
42
- activity.set(entry.id, live === undefined ? 'ready' : live.status);
43
- }
44
- }
45
- }
46
- catch (error) {
47
- ctx.logger.warn(`agent-teams: activity listing failed for ${state.name}: ${String(error)}`);
48
- }
49
- }
36
+ const activity = options.historic === true
37
+ ? new Map()
38
+ : memberActivity(ctx, roster.map((member) => member.id));
50
39
  const unreadByMember = new Map();
51
40
  for (const member of roster) {
52
41
  try {
package/lib/tools.js CHANGED
@@ -871,7 +871,7 @@ export function registerAgentTeamsTools(ctx, config) {
871
871
  await scheduler.kickTeam(workspace, located.id, caller);
872
872
  }
873
873
  const { team, identity } = await withTeamLock(teamLockKey(stateRoot, located.id), () => requireFreshParticipant(stateRoot, located.id, caller.id));
874
- const activity = await memberActivity(ctx, team.captainSessionId);
874
+ const activity = memberActivity(ctx, team.members.map((member) => member.id));
875
875
  const members = team.members
876
876
  .filter((member) => member.status !== 'removed')
877
877
  .map((member) => ({
@@ -21,7 +21,7 @@ import type { ObservableSnapshot, SessionListState } from '@deepseek-ai/dsh-clie
21
21
  /** The top-right activity floater. Teams follow the current session: live
22
22
  * snapshots and historic card summaries are only shown while their captain
23
23
  * session is the one currently open. */
24
- export declare function ActivityPanel({ sessionsList, openSession }: {
24
+ export declare function ActivityPanel({ sessionsList, openMember }: {
25
25
  readonly sessionsList: ObservableSnapshot<SessionListState>;
26
- readonly openSession: (id: SessionId) => void;
26
+ readonly openMember: (parentId: SessionId, childId: SessionId) => void;
27
27
  }): import("react").JSX.Element | null;
@@ -15,9 +15,9 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types';
15
15
  export declare const OPEN_PANEL_EVENT = "agent-teams:open-panel";
16
16
  /** Navigation action injected from the plugin's own SessionsService access. */
17
17
  export interface AgentTeamsCardInjected {
18
- readonly openSession: (id: SessionId) => void;
18
+ readonly openMember: (parentId: SessionId, childId: SessionId) => void;
19
19
  }
20
20
  /** Complete keyed Chat renderer props. */
21
21
  export type AgentTeamsCardProps = PropsRuntime<'conversation.chat.node', 'agent-teams'> & PropsLocale<'agentTeams'> & AgentTeamsCardInjected;
22
22
  /** Render one durable team as a compact conversation card. */
23
- export declare function AgentTeamsCard({ node, openSession, sessionId }: AgentTeamsCardProps): import("react").JSX.Element;
23
+ export declare function AgentTeamsCard({ node, openMember, sessionId }: AgentTeamsCardProps): import("react").JSX.Element;
@@ -79,6 +79,12 @@ interface ActivityFetchResponse {
79
79
  }
80
80
  /** Injectable browser primitives used by the poll controller and its tests. */
81
81
  export interface ActivityPollingRuntime {
82
+ /**
83
+ * Current captain session to discover after a cold client/host restart.
84
+ * This one-time scope restores teams whose older conversation log has no
85
+ * AgentTeams card capable of registering an explicit monitor target.
86
+ */
87
+ readonly discoverySessionId?: string;
82
88
  readonly fetchState?: (url: string, init: {
83
89
  readonly cache: 'no-store';
84
90
  readonly signal: AbortSignal;
@@ -98,9 +104,13 @@ export interface ActivityPollingController {
98
104
  /**
99
105
  * Start the single polling loop for the current session's requested targets.
100
106
  *
101
- * With no targets this is deliberately inert: installing the plugin must not
102
- * touch the state route. Live state is polled at the normal cadence; archive
103
- * 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.
104
114
  */
105
115
  export declare function startActivityPolling(monitorTargets: readonly ActivityMonitorTarget[], runtime?: ActivityPollingRuntime): ActivityPollingController;
106
116
  export {};
@@ -0,0 +1,22 @@
1
+ /** Version-tolerant navigation into durable AgentTeams member transcripts. */
2
+ import type { SessionId, SubagentAddress } from '@deepseek-ai/dsh-client-runtime/client';
3
+ /** Narrow sessions-service face used by the activity panel and team card. */
4
+ export interface AgentTeamsSessionNavigator {
5
+ /** Legacy/ordinary session navigation. */
6
+ open(id: SessionId): void;
7
+ /** rc.8 addressed subagent navigation. */
8
+ openSubagent?(address: SubagentAddress): void;
9
+ /** Refresh the exact parent's durable direct-child catalog. */
10
+ refreshSubagents?(parentSessionId: SessionId): Promise<void>;
11
+ /** Reuse an address already retained by the client runtime when available. */
12
+ subagentAddress?(id: SessionId): SubagentAddress | undefined;
13
+ }
14
+ /**
15
+ * Open one member's persisted transcript.
16
+ *
17
+ * Harness rc.8 intentionally removed cold subagents from the ordinary session
18
+ * list. They must first be rediscovered in their parent's catalog, then opened
19
+ * with the exact parent/child/mode address. Older runtimes have only `open()`;
20
+ * the fallback preserves the plugin's rc.6 peer range.
21
+ */
22
+ export declare function openAgentTeamMember(sessions: AgentTeamsSessionNavigator, parentSessionId: SessionId, childSessionId: SessionId): Promise<'subagent' | 'session'>;
@@ -123,20 +123,24 @@ export declare function interruptMember(ctx: Context, captain: Agent, childId: s
123
123
  *
124
124
  * Upstream `interrupt()` deliberately preserves continuable sessions and the
125
125
  * upstream seam exposes no targeted forget/retire method. The durable
126
- * AgentTeams index therefore guards all three public continuation boundaries:
127
- * retired rows disappear from `list_agents` (children and descendants), and a
128
- * direct `followup()` is rejected before it can cold-resume the member. Exact
129
- * ids keep unrelated subagents untouched; transcripts remain in persistence
130
- * for archived-team review.
126
+ * AgentTeams index therefore rejects `followup()` before it can cold-resume a
127
+ * retired member. Catalog rows deliberately remain discoverable: Harness rc.8
128
+ * uses the direct-child catalog to authorize historical transcript reads and
129
+ * `openSubagent()`, so filtering those rows would make an archived member's
130
+ * persisted conversation inaccessible. Exact ids keep unrelated subagents
131
+ * untouched while the followup boundary still prevents further model turns.
131
132
  */
132
133
  export declare function installRetiredMemberGuard(ctx: Context, stateDir: string): void;
133
134
  /**
134
- * Snapshot each direct continuable child's real driver activity under the
135
- * captain's session. `listChildren().activity` is only session residency, so
136
- * live children are refined through the Agent registry exactly like Harness's
137
- * shipped `list_agents` tool.
138
- * @param ctx - the plugin context (injects `subagents`).
139
- * @param captainSessionId - the captain's session id.
140
- * @returns child id → activity, missing entries are unknown children.
135
+ * Snapshot the real driver activity for durable member ids.
136
+ *
137
+ * The team record is the membership authority, so this path intentionally no
138
+ * longer depends on `listChildren()`'s versioned projection shape. Harness
139
+ * rc.8 changed those rows to branded `SessionId` values plus residency-only
140
+ * `activity`; neither is needed to answer whether the live Agent driver is
141
+ * running, idle, or absent/ready.
142
+ * @param ctx - the plugin context (injects `agents`).
143
+ * @param memberIds - child ids restored from the durable team record.
144
+ * @returns child id → live activity.
141
145
  */
142
- export declare function memberActivity(ctx: Context, captainSessionId: string): Promise<Map<string, 'running' | 'idle' | 'ready'>>;
146
+ export declare function memberActivity(ctx: Context, memberIds: readonly string[]): Map<string, 'running' | 'idle' | 'ready'>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanmicoder/dsh-agent-teams",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "AgentTeams for DeepSeek Harness: multi-agent team collaboration (captain, members, tasks with dependencies, messaging) driven by natural language, with a tree monitor in the web GUI",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -0,0 +1,52 @@
1
+ # AgentTeams v0.1.11
2
+
3
+ This release restores AgentTeams activity and member history after a DeepSeek Harness client or host restart. It fixes the rc.8 compatibility regressions reported in [#60](https://github.com/NanmiCoder/dsh-agent-teams/issues/60) and [#61](https://github.com/NanmiCoder/dsh-agent-teams/issues/61).
4
+
5
+ ## Fixes
6
+
7
+ - **Cold-start activity recovery**: selecting a captain session now performs a lightweight live and archived team discovery pass, so older sessions without an AgentTeams conversation card restore their activity panel after restart.
8
+ - **Reliable member activity**: live `running / idle / ready` state is resolved from the durable team roster and Agent registry instead of depending on the versioned `listChildren()` projection shape.
9
+ - **Persistent member history**: retired AgentTeams members remain discoverable in the Harness subagent catalog for transcript review while direct follow-up remains blocked.
10
+ - **rc.8 transcript navigation**: activity-panel and conversation-card member links refresh the parent catalog and use addressed `openSubagent()` navigation, with a fallback for older Harness runtimes.
11
+
12
+ ## Verification
13
+
14
+ - Reproduced the full captain/member/task/archive lifecycle in a real `/tmp` workspace on the Harness web client.
15
+ - Verified cold restart recovery without opening the built-in subagent catalog first.
16
+ - Verified archived member navigation and complete persisted task history.
17
+ - Passed type checking, production build, offline verification, lifecycle verification, and the eight-member complex stress suite.
18
+
19
+ ## Installation
20
+
21
+ ```sh
22
+ dsh plugin --profile web add @nanmicoder/dsh-agent-teams
23
+ ```
24
+
25
+ <details>
26
+ <summary><b>中文版本 / Chinese Version</b></summary>
27
+
28
+ # AgentTeams v0.1.11
29
+
30
+ 本次更新修复 DeepSeek Harness 客户端或 host 重启后 AgentTeams 活动面板与成员历史消失的问题,对应 [#60](https://github.com/NanmiCoder/dsh-agent-teams/issues/60) 和 [#61](https://github.com/NanmiCoder/dsh-agent-teams/issues/61) 中报告的 rc.8 兼容性回归。
31
+
32
+ ## 修复
33
+
34
+ - **冷启动活动恢复**:选择队长会话时会执行一次轻量的活动团队与历史归档发现;即使旧会话没有 AgentTeams 对话卡片,重启后也能恢复活动面板。
35
+ - **可靠的成员活动状态**:从持久化团队成员表和 Agent registry 解析真实的 `running / idle / ready`,不再依赖容易变化的 `listChildren()` 投影结构。
36
+ - **成员历史持续可见**:已退休的 AgentTeams 成员继续保留在 Harness 子代理目录中供历史记录查看,但直接 follow-up 仍会被拒绝。
37
+ - **rc.8 历史会话导航**:活动面板和对话卡片会先刷新父级目录,再通过带父子地址的 `openSubagent()` 打开成员记录;旧版 Harness 继续使用兼容回退。
38
+
39
+ ## 验证
40
+
41
+ - 在 Harness Web 客户端的真实 `/tmp` workspace 中完成队长、成员、任务和归档全流程复现。
42
+ - 验证未预先打开内置子代理目录时的冷重启恢复。
43
+ - 验证归档成员导航及完整持久化任务历史。
44
+ - 通过类型检查、生产构建、离线验证、生命周期验证及八成员复杂压力测试。
45
+
46
+ ## 安装
47
+
48
+ ```sh
49
+ dsh plugin --profile web add @nanmicoder/dsh-agent-teams
50
+ ```
51
+
52
+ </details>