/** * Member subagent lifecycle: spawn a continuable child per member, deliver * task turns and step-boundary messages, and observe its activity. * * Members are durable continuable subagents of the captain, so a member keeps * its conversation across turns and across harness restarts: the captain * queues its next turn through {@link deliverToMember}, it works through its turn * (updating team state through the `agent_teams_*` tools), and becomes idle * again. Its final assistant message is not readable programmatically, so the * member persists its report into the captain's mailbox and the task records, * which the captain reads through `agent_teams_status`. * @module dsh-agent-teams/members */ import type { Context } from '@deepseek-ai/cordis'; import { type Agent } from '@deepseek-ai/dsh-agent'; import type { Session } from '@deepseek-ai/dsh-session'; import { type TeamMember, type TeamState, type TeamTask } from './types.ts'; /** Persona snapshot of a profile protocol; the full text lives on team.json. */ export declare const PERSONA_PROTOCOL_MAX_CHARS = 400; /** Runtime knobs for member spawning, resolved from plugin config. */ export interface MemberRuntimeConfig { /** Registered `ctx.subagents` provider name (must support continuable + persona). */ provider: string; /** Child delegation depth cap (0 forbids delegation entirely). */ maxDepth?: number; /** Plugin-wide execution prompt. */ executionPrompt?: string; /** Plugin-wide fallback route. */ fallback?: { provider: string; model: string; }; } /** Durable provider/model/reasoning snapshot for one member. */ export interface MemberLlmSelection { /** Registered LLM provider route. */ provider: string; /** Provider-owned model id. */ model: string; /** Adapter-owned reasoning effort, absent when the target has no explicit/default effort. */ reasoningEffort?: string; /** Configured second-choice route. */ fallback?: { provider: string; model: string; }; } /** Optional member-level route requested by the captain. */ export interface MemberLlmSelectionRequest { /** Explicit LLM provider route; requires an explicit model. */ provider?: string; /** Explicit model id; otherwise the plugin default or captain model is used. */ model?: string; /** Plugin-level member model default. */ defaultModel?: string; /** Explicit reasoning effort; "default" selects the target model's default effort. */ reasoningEffort?: string; /** Configured fallback route. */ fallback?: { provider: string; model: string; }; } /** Process-local bridge between spawn admission and synchronous child setup. */ export interface MemberSelectionRuntime { /** Trusted fresh-child admission, before the durable member id is written. */ isPendingMember(agent: Agent): boolean; /** Make one selection visible while Harness materializes the fresh child. */ withPending(parentSessionId: string, label: string, selection: MemberLlmSelection, operation: () => Promise): Promise; } /** * Validate a resolved roster against every provider catalog before any child * session is created. Catalogs are advisory when empty (some adapters accept * dynamic model ids), but a non-empty catalog is authoritative enough to * catch a typo that would otherwise boot a child and fail on its first turn. */ export declare function validateMemberLlmSelections(ctx: Context, selections: readonly MemberLlmSelection[], signal?: AbortSignal): Promise; export declare function isFallbackFailureCode(code: string): boolean; /** Pure state transition used by the request-error handler and TDD tests. */ export declare function selectFallbackRoute(current: { provider: string; model: string; }, fallback: { provider: string; model: string; } | undefined, failureCode: string, alreadySwitched: boolean): { retry: boolean; switched: boolean; selection: { provider: string; model: string; }; }; /** Deliver a durable member report to the live captain at its next model step. */ export declare function steerCaptainReport(captain: Pick, from: string, content: string, receipt?: string): boolean; interface FailedMemberAttempt { readonly captainSessionId: string; readonly memberId: string; readonly task?: Pick; } /** Record a final turn failure, never an intermediate request retry. */ export declare function failMemberOpenAttempt(ctx: Context, stateRoot: string, teamId: string, memberName: string, failure: { readonly code: string; readonly message: string; }, fallbackSession: Session, observed: FailedMemberAttempt): Promise; /** * Resolve one member's complete model selection. Ordinary members snapshot the * captain's current request route and reasoning effort. When provider or model * changes, effort is intentionally omitted so the target model materializes * its own default instead of receiving an adapter-owned id from another route. * An explicit effort overrides either policy; the sentinel "default" also * selects the target model's default. The final effort is validated against * the target model before a child is created. */ export declare function resolveMemberLlmSelection(ctx: Context, captain: Agent, request: MemberLlmSelectionRequest, signal?: AbortSignal): Promise; /** * Install the member selection bridge for every fresh or cold-resumed * continuable child. Fresh creation reads the pending in-memory selection; * cold resume restores the same selection from the owning team's durable * record. Legacy members without a complete saved route retain Harness's * descriptor provider/model behavior. */ export declare function installMemberSelectionRuntime(ctx: Context, stateDir: string, onFailureSettled?: (workspace: string, teamId: string, memberName: string) => Promise): MemberSelectionRuntime; /** * The member's system prompt (persona), shadowing the deployment persona for * that child. Self-contained: it replaces the whole persona section. * Frozen at spawn: draft must already carry the Team goal and profile protocol. * @param team - the team the member joined. * @param member - the member record (name/role are read before spawning). * @param stateDir - configured state directory, so the member can locate the * team files with its own file tools. */ export declare function memberPersona(team: TeamState, member: TeamMember, stateDir: string, executionPrompt?: string): string; /** * The initial user message delivered when the member is created. * Counts non-terminal tasks already assigned to this member on the in-memory draft. * @param team - the team the member joined. * @param memberName - canonical member name used to count assigned pending work. */ export declare function memberWelcome(team: TeamState, memberName: string): string; /** * Spawn one member as a durable continuable subagent of the captain and fill * `member.id` with its child session id. On failure nothing is persisted. * @param ctx - the plugin context (injects `subagents`). * @param config - member runtime knobs. * @param selections - fresh/cold child model-selection bridge. * @param llmSelection - resolved provider/model/reasoning snapshot. * @param captain - the exact live captain agent (the calling agent). * @param team - the team record (read-only here). * @param member - the member draft whose `id` is filled on success. * @param stateDir - configured state directory (for the persona). * @param signal - caller cancellation, forwarded to the start. */ export declare function spawnMember(ctx: Context, config: MemberRuntimeConfig, selections: MemberSelectionRuntime, llmSelection: MemberLlmSelection, captain: Agent, team: TeamState, member: TeamMember, stateDir: string, signal: AbortSignal, initialPrompt?: string): Promise; /** * Deliver one message to a member as its next FIFO turn. Best effort: a * failure (member gone or not continuable) is logged and reported as `false` * so the caller can decide (mailbox delivery still happened). * * Any team sender can route through this helper: the captain is the direct * parent of every member, and the caller passes the captain's live Agent * (its own when the captain calls, the registry-resolved one when a member * sends) — mirroring the Claude Code mailbox model where the writer writes * the target's inbox and the target picks it up on its own. * @param ctx - the plugin context (injects `subagents`). * @param captain - the exact live captain agent (the member's direct parent). * @param childId - the member's durable child session id. * @param text - the message content. * @param signal - caller cancellation, forwarded to the delivery. * @returns whether the member inbox accepted the message. */ export declare function deliverToMember(ctx: Context, captain: Agent, childId: string, text: string, signal: AbortSignal, mode?: 'queue' | 'steer'): Promise; /** * Request cancellation of one live member's current turn. Best effort, fire * and return; the target may keep running until it observes the signal. * @param ctx - the plugin context (injects `subagents`). * @param captain - the exact live captain agent (the member's parent). * @param childId - the member's durable child session id. */ export declare function interruptMember(ctx: Context, captain: Agent, childId: string): void; /** * Install the missing per-child retirement boundary above Harness rc.6. * * Upstream `interrupt()` deliberately preserves continuable sessions and the * upstream seam exposes no targeted forget/retire method. The durable * AgentTeams index therefore rejects every inbox delivery before it can cold-resume a * retired member. Catalog rows deliberately remain discoverable: Harness rc.8 * uses the direct-child catalog to authorize historical transcript reads and * `openSubagent()`, so filtering those rows would make an archived member's * persisted conversation inaccessible. Exact ids keep unrelated subagents * untouched while the delivery boundary still prevents further model turns. */ export declare function installRetiredMemberGuard(ctx: Context, stateDir: string): void; /** Bound all descendant creation, including renamed tools and code-runtime calls. */ export declare function installMemberDelegationGuard(ctx: Context, stateDir: string, maxDepth: number): void; /** * Snapshot the real driver activity for durable member ids. * * The team record is the membership authority, so this path intentionally no * longer depends on `listChildren()`'s versioned projection shape. Harness * rc.8 changed those rows to branded `SessionId` values plus residency-only * `activity`; neither is needed to answer whether the live Agent driver is * running, idle, or absent/ready. * @param ctx - the plugin context (injects `agents`). * @param memberIds - child ids restored from the durable team record. * @returns child id → live activity. */ export declare function memberActivity(ctx: Context, memberIds: readonly string[]): Map; export {};