@nanmicoder/dsh-agent-teams 0.1.5 → 0.1.7

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.
@@ -10,6 +10,32 @@ export interface RelationshipStage<T extends RelationshipTask> {
10
10
  readonly depth: number;
11
11
  readonly tasks: readonly T[];
12
12
  }
13
+ /** Geometry used by the compact task DAG in the activity panel. */
14
+ export interface CompactDagNode<T extends RelationshipTask> {
15
+ readonly task: T;
16
+ readonly x: number;
17
+ readonly y: number;
18
+ }
19
+ /** One dependency edge routed between two compact DAG nodes. */
20
+ export interface CompactDagEdge {
21
+ readonly from: string;
22
+ readonly to: string;
23
+ readonly path: string;
24
+ }
25
+ /** Complete, scrollable compact DAG projection. */
26
+ export interface CompactDagLayout<T extends RelationshipTask> {
27
+ readonly width: number;
28
+ readonly height: number;
29
+ readonly nodes: readonly CompactDagNode<T>[];
30
+ readonly edges: readonly CompactDagEdge[];
31
+ }
32
+ /** Reference-panel geometry: narrow nodes with enough room for curved edges. */
33
+ export declare const COMPACT_DAG_NODE_WIDTH = 92;
34
+ export declare const COMPACT_DAG_NODE_HEIGHT = 30;
35
+ export declare const COMPACT_DAG_COLUMN_GAP = 26;
36
+ export declare const COMPACT_DAG_ROW_GAP = 8;
37
+ /** Use a fill-width grid when the task graph has no real dependency edges. */
38
+ export declare function usesParallelTaskGrid<T extends RelationshipTask>(tasks: readonly T[]): boolean;
13
39
  /**
14
40
  * Whether an expanded activity panel still belongs to the current session.
15
41
  *
@@ -29,6 +55,14 @@ export declare function activityPanelExpandedForSession(open: boolean, owner: st
29
55
  export declare function dependencyFocusTaskId(pinnedTaskId: string | null, keyboardTaskId: string | null, hoverTaskId: string | null): string | null;
30
56
  /** Group tasks by their precomputed dependency depth. */
31
57
  export declare function taskStages<T extends RelationshipTask>(tasks: readonly T[]): readonly RelationshipStage<T>[];
58
+ /**
59
+ * Lay tasks out as the reference panel's compact left-to-right DAG.
60
+ *
61
+ * Columns are dependency-depth stages. Rows are stable task-id order within
62
+ * each stage. Edges use cubic curves so fan-in remains readable without
63
+ * turning every task into a large card.
64
+ */
65
+ export declare function compactDagLayout<T extends RelationshipTask>(tasks: readonly T[]): CompactDagLayout<T>;
32
66
  /**
33
67
  * Return the complete upstream/downstream chain around one task.
34
68
  *
@@ -44,6 +44,8 @@ export interface AgentTeamsTaskUpdatedData {
44
44
  readonly status: string;
45
45
  readonly assignee?: string;
46
46
  readonly output?: string;
47
+ readonly attempt?: number;
48
+ readonly attemptId?: string;
47
49
  }
48
50
  /** Closes one team record: the team was deleted. */
49
51
  export interface AgentTeamsTeamDeletedData {
@@ -38,6 +38,8 @@ export interface MemberLlmSelectionRequest {
38
38
  model?: string;
39
39
  /** Plugin-level member model default. */
40
40
  defaultModel?: string;
41
+ /** Explicit reasoning effort; "default" selects the target model's default effort. */
42
+ reasoningEffort?: string;
41
43
  }
42
44
  /** Process-local bridge between spawn admission and synchronous child setup. */
43
45
  export interface MemberSelectionRuntime {
@@ -46,10 +48,12 @@ export interface MemberSelectionRuntime {
46
48
  }
47
49
  /**
48
50
  * Resolve one member's complete model selection. Ordinary members snapshot the
49
- * captain's current request route and reasoning effort. An explicit member
50
- * provider/model or plugin-level model replaces only that route; the current
51
- * captain effort remains the inherited policy and is validated against the
52
- * target model before a child is created.
51
+ * captain's current request route and reasoning effort. When provider or model
52
+ * changes, effort is intentionally omitted so the target model materializes
53
+ * its own default instead of receiving an adapter-owned id from another route.
54
+ * An explicit effort overrides either policy; the sentinel "default" also
55
+ * selects the target model's default. The final effort is validated against
56
+ * the target model before a child is created.
53
57
  */
54
58
  export declare function resolveMemberLlmSelection(ctx: Context, captain: Agent, request: MemberLlmSelectionRequest, signal?: AbortSignal): Promise<MemberLlmSelection>;
55
59
  /**
@@ -115,11 +119,24 @@ export declare function deliverToMember(ctx: Context, captain: Agent, childId: s
115
119
  */
116
120
  export declare function interruptMember(ctx: Context, captain: Agent, childId: string): void;
117
121
  /**
118
- * Snapshot each direct continuable child's activity under the captain's
119
- * session, keyed by child session id. A member that is currently running its
120
- * turn reports `running`; an idle member reports `inactive`.
122
+ * Install the missing per-child retirement boundary above Harness rc.6.
123
+ *
124
+ * Upstream `interrupt()` deliberately preserves continuable sessions and the
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.
131
+ */
132
+ export declare function installRetiredMemberGuard(ctx: Context, stateDir: string): void;
133
+ /**
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.
121
138
  * @param ctx - the plugin context (injects `subagents`).
122
139
  * @param captainSessionId - the captain's session id.
123
140
  * @returns child id → activity, missing entries are unknown children.
124
141
  */
125
- export declare function memberActivity(ctx: Context, captainSessionId: string): Promise<Map<string, 'running' | 'inactive'>>;
142
+ export declare function memberActivity(ctx: Context, captainSessionId: string): Promise<Map<string, 'running' | 'idle' | 'ready'>>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Event-driven shared task scheduler.
3
+ *
4
+ * Claude Code teammates keep polling the shared task list after a turn. DSH
5
+ * continuable agents instead expose explicit idle/running edges, so this
6
+ * scheduler closes the same loop without keeping a polling turn alive: every
7
+ * idle edge and every task-graph mutation attempts one atomic claim and wakes
8
+ * the selected durable member.
9
+ * @module dsh-agent-teams/scheduler
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { Agent } from '@deepseek-ai/dsh-agent';
13
+ export interface SchedulerConfig {
14
+ readonly stateDir: string;
15
+ }
16
+ export interface TeamScheduler {
17
+ /** Try to give every genuinely idle/ready member one unit of ready work. */
18
+ kickTeam(workspace: string, teamId: string, captain?: Agent): Promise<void>;
19
+ /** Try to flush fallback mail or give one member one ready task. */
20
+ kickMember(workspace: string, teamId: string, memberName: string, captain?: Agent): Promise<void>;
21
+ }
22
+ /** Install one scheduler and its member activity observer. */
23
+ export declare function installTeamScheduler(ctx: Context, config: SchedulerConfig): TeamScheduler;
@@ -8,7 +8,7 @@
8
8
  * @module dsh-agent-teams/snapshot
9
9
  */
10
10
  import type { Context } from '@deepseek-ai/cordis';
11
- import type { TeamState } from './types.ts';
11
+ import type { MemberStatus, TeamState } from './types.ts';
12
12
  /** Visual task state for the activity panel. */
13
13
  export type VisualTaskState = 'blocked' | 'open' | 'running' | 'completed';
14
14
  /** One member row of the activity snapshot. */
@@ -16,6 +16,7 @@ export interface TeamActivityMember {
16
16
  readonly id: string;
17
17
  readonly name: string;
18
18
  readonly role: string;
19
+ readonly status: MemberStatus;
19
20
  readonly activity: 'working' | 'idle' | 'unknown';
20
21
  readonly progress: number;
21
22
  readonly done: number;
@@ -50,6 +51,13 @@ export interface TeamActivitySnapshot {
50
51
  readonly messageCount: number;
51
52
  readonly captainInbox: readonly TeamActivityMessage[];
52
53
  }
54
+ /** Snapshot projection switches for live and archived teams. */
55
+ export interface TeamSnapshotOptions {
56
+ /** Historic review must retain members that were marked removed at shutdown. */
57
+ readonly includeRemoved?: boolean;
58
+ /** Archived teams have no meaningful live activity after their sessions stop. */
59
+ readonly historic?: boolean;
60
+ }
53
61
  /**
54
62
  * Assemble one team snapshot from its durable files plus live activity.
55
63
  * @param ctx - the plugin context (injects `subagents`, used for activity).
@@ -58,7 +66,7 @@ export interface TeamActivitySnapshot {
58
66
  * @param state - the durable team record.
59
67
  * @returns the panel snapshot.
60
68
  */
61
- export declare function assembleTeamSnapshot(ctx: Context, stateRoot: string, workspace: string, state: TeamState): Promise<TeamActivitySnapshot>;
69
+ export declare function assembleTeamSnapshot(ctx: Context, stateRoot: string, workspace: string, state: TeamState, options?: TeamSnapshotOptions): Promise<TeamActivitySnapshot>;
62
70
  /**
63
71
  * Collect every team under the given workspace state roots.
64
72
  * @param ctx - the plugin context.
@@ -61,6 +61,15 @@ export declare const TASK_TRANSITIONS: Readonly<Record<TaskStatus, readonly Task
61
61
  * @returns the transition error, or undefined when allowed.
62
62
  */
63
63
  export declare function transitionError(current: TaskStatus, next: TaskStatus): string | undefined;
64
+ /** Activate the task's current generation for one owner and return its capability id. */
65
+ export declare function activateTaskAttempt(task: TeamTask, assignee: string): string;
66
+ /** Start a fresh task generation for one owner. */
67
+ export declare function beginTaskAttempt(task: TeamTask, assignee: string): string;
68
+ /**
69
+ * Revoke the current worker immediately. Clearing its capability makes old
70
+ * updates stale; a separate handoff generation serializes async quiescence.
71
+ */
72
+ export declare function invalidateTaskAttempt(task: TeamTask, nextAssignee?: string, reassigning?: boolean): void;
64
73
  /**
65
74
  * Create the team directory structure and the initial team record.
66
75
  * @param stateRoot - resolved absolute state root directory.
@@ -89,6 +98,10 @@ export declare function readTeamSync(stateRoot: string, teamId: string): TeamSta
89
98
  * @param state - the record to persist.
90
99
  */
91
100
  export declare function writeTeam(stateRoot: string, state: TeamState): Promise<void>;
101
+ /** Read the durable set of member session ids retired by remove/delete. */
102
+ export declare function readRetiredMemberIds(stateRoot: string): Promise<Set<string>>;
103
+ /** Atomically add session ids to the durable retired-member deny-list. */
104
+ export declare function recordRetiredMemberIds(stateRoot: string, memberIds: readonly string[]): Promise<void>;
92
105
  /**
93
106
  * Find the team owned by one captain session (at most one per captain).
94
107
  * @param stateRoot - resolved absolute state root directory.
@@ -125,6 +138,46 @@ export declare function appendMailbox(stateRoot: string, teamId: string, agentKe
125
138
  * @returns the messages, empty when the mailbox does not exist yet.
126
139
  */
127
140
  export declare function readMailbox(stateRoot: string, teamId: string, agentKey: string, onMalformedLine?: (lineNumber: number, error: unknown) => void): Promise<TeamMessage[]>;
141
+ /** Read only messages that have not been acknowledged by their recipient. */
142
+ export declare function readUnreadMailbox(stateRoot: string, teamId: string, agentKey: string, onMalformedLine?: (lineNumber: number, error: unknown) => void): Promise<TeamMessage[]>;
143
+ /** Lease selected fallback messages to one delivery path. */
144
+ export declare function claimMailboxDelivery(stateRoot: string, teamId: string, agentKey: string, messageIds: readonly string[]): Promise<void>;
145
+ /** Release a failed delivery lease so the scheduler can retry it later. */
146
+ export declare function releaseMailboxDelivery(stateRoot: string, teamId: string, agentKey: string, messageIds: readonly string[]): Promise<void>;
147
+ /**
148
+ * Mark selected durable mailbox records delivered/read while preserving
149
+ * malformed lines for diagnostics. Callers serialize this with the team lock.
150
+ */
151
+ export declare function acknowledgeMailbox(stateRoot: string, teamId: string, agentKey: string, messageIds: readonly string[]): Promise<void>;
152
+ /** Filesystem primitives used by {@link replaceFileAtomicOrDirect}; injectable for tests. */
153
+ export interface AtomicReplacePrimitives {
154
+ rename: (from: string, to: string) => Promise<void>;
155
+ writeFile: (file: string, content: string) => Promise<void>;
156
+ remove: (file: string) => Promise<void>;
157
+ }
158
+ /** Tuning knobs for {@link replaceFileAtomicOrDirect} (defaults match production). */
159
+ export interface AtomicReplaceOptions {
160
+ /** Rename attempts before the direct-write fallback (default 3). */
161
+ retries?: number;
162
+ /** Delay between rename attempts in ms (default 50). */
163
+ retryDelayMs?: number;
164
+ }
165
+ /**
166
+ * Replace `file` with `content`, preferring an atomic same-directory rename of
167
+ * an already-written temp file.
168
+ *
169
+ * On Windows, `rename(tmp, file)` over an existing target throws EPERM while
170
+ * any other process keeps the target open without FILE_SHARE_DELETE (editors,
171
+ * indexers, antivirus scans, preview panes). By that point the payload has
172
+ * already been fully written to the temp file, so a direct overwrite of the
173
+ * target is a content-equivalent degraded path: retry the rename a few times
174
+ * (transient locks clear quickly), then write the target in place. Every path
175
+ * removes the temp file; when both the atomic rename and the direct write
176
+ * fail, the combined error surfaces as an {@link AggregateError}.
177
+ *
178
+ * @returns nothing once the file has been replaced by one of the two paths.
179
+ */
180
+ export declare function replaceFileAtomicOrDirect(temporary: string, file: string, content: string, primitives: AtomicReplacePrimitives, options?: AtomicReplaceOptions): Promise<void>;
128
181
  /**
129
182
  * Remove a team's whole directory (members should be interrupted first).
130
183
  * @param stateRoot - resolved absolute state root directory.
@@ -20,12 +20,20 @@ export interface TeamTask {
20
20
  /** What needs to be done. */
21
21
  description?: string;
22
22
  status: TaskStatus;
23
- /** Member name the task is assigned to; unassigned tasks await a claim. */
23
+ /** Member name (or `captain`) the task is assigned to; unassigned tasks await a claim. */
24
24
  assignee?: string;
25
25
  /** Task ids that must reach `completed` before this task can be claimed. */
26
26
  dependencies: string[];
27
27
  /** The worker's written result, set when the task completes or fails. */
28
28
  output?: string;
29
+ /** Monotonic execution generation. Reassignment/retry invalidates every older attempt. */
30
+ attempt?: number;
31
+ /** Capability for the current claimed/in-progress attempt. Members must present it when updating. */
32
+ attemptId?: string;
33
+ /** Opaque generation for a revocation/handoff that has not started its next attempt yet. */
34
+ handoffId?: string;
35
+ /** A handoff is quiescing the old owner; the scheduler must not dispatch it yet. */
36
+ reassigning?: boolean;
29
37
  createdAt: number;
30
38
  updatedAt: number;
31
39
  }
@@ -43,7 +51,7 @@ export interface TeamMember {
43
51
  provider?: string;
44
52
  /** Resolved model captured when this member was created. */
45
53
  model?: string;
46
- /** Resolved reasoning effort captured from the captain's current session. */
54
+ /** Resolved reasoning effort captured from the captain or target model default. */
47
55
  reasoningEffort?: string;
48
56
  joinedAt: number;
49
57
  status: MemberStatus;
@@ -57,6 +65,12 @@ export interface TeamMessage {
57
65
  to: string;
58
66
  content: string;
59
67
  ts: number;
68
+ /** Process-local delivery lease; prevents fallback and direct delivery racing. */
69
+ deliveryClaimedAt?: number;
70
+ /** Set after the durable message was accepted by the recipient's live Harness inbox. */
71
+ deliveredAt?: number;
72
+ /** Set once the recipient has consumed or been shown the durable fallback. */
73
+ readAt?: number;
60
74
  }
61
75
  /** The full durable team record. */
62
76
  export interface TeamState {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanmicoder/dsh-agent-teams",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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",
@@ -82,7 +82,7 @@
82
82
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
83
83
  "sync:skill": "node scripts/sync-skill.mjs",
84
84
  "verify:skill": "node scripts/sync-skill.mjs --check",
85
- "verify": "node scripts/verify.mjs && pnpm verify:skill",
85
+ "verify": "node scripts/verify.mjs && node scripts/lifecycle-verify.mjs && node scripts/stress-verify.mjs && pnpm verify:skill",
86
86
  "prepublishOnly": "pnpm build && pnpm verify"
87
87
  },
88
88
  "peerDependencies": {