/**
 * The globally named `list_agents` tool: a thin model-facing adapter over
 * the continuable projection of `ctx.subagents.listChildren()` and, for the
 * `descendants` scope, `ctx.subagents.listDescendants()`. It stays separately
 * loadable from the root `send_message` plugin so a deployment can register
 * continuation delivery without exposing discovery.
 * @module @deepseek-ai/dsh-tool-subagent-control/list-agents
 */
import { defineTool } from '@deepseek-ai/dsh-tools';
import { assertNever } from '@deepseek-ai/dsh-llm';
export const name = 'tool-subagent-list-agents';
export const inject = ['tools', 'subagents', 'agents'];
/** Resolve the optional model request into an internal required-scope spec. */
function resolveListAgentsRequest(request) {
    return { scope: request.scope ?? 'children' };
}
/**
 * Refine one candidate's status through the live Agent registry: `running`
 * for an active driver, `idle` for a resident Agent between turns (possibly
 * waiting on agents it started), and `ready` when no live Agent remains.
 * `ready` preserves resumability without presenting an inactive conversation
 * as a terminal result to collect.
 */
function statusOf(agents, id) {
    const agent = agents.get(id);
    if (agent === undefined)
        return 'ready';
    return agent.status === 'running' ? 'running' : 'idle';
}
/** Project one service row into the model-facing entry, or omit a one-shot child. */
function project(agents, entry, position) {
    const at = position === undefined ? {} : { parent: position.parentId, depth: position.depth };
    if (entry.kind === 'diagnostic') {
        return { kind: 'diagnostic', id: entry.id, reason: entry.reason, ...at };
    }
    // One-shot children cannot be continued by send_message, so the model
    // never selects them; discovery still traversed them for descendants.
    if (entry.mode !== 'continuable')
        return undefined;
    return {
        kind: 'child',
        id: entry.id,
        label: entry.label,
        status: statusOf(agents, entry.id),
        ...at,
    };
}
/**
 * Register the `list_agents` tool.
 * @param ctx - context carrying the tool registry, subagent service, and live Agent registry.
 */
export function apply(ctx) {
    ctx.tools.register(defineTool({
        name: 'list_agents',
        description: 'List your continuable background subagents by durable id and label. Use it to recall which ones '
            + 'you started, not to poll for completion — you are told when one finishes. Status comes from the live '
            + 'registry: running means the agent is working right now, idle means it is loaded but between turns '
            + '(it may be waiting on agents it started), and ready means it exists only in storage — resumable, not '
            + 'terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same '
            + 'conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery '
            + 'promise — `send_message` performs the authoritative check and may still fail. Children that could '
            + 'not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` '
            + 'walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent '
            + 'session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are '
            + 'candidates for `interrupt_agent` only.',
        parameters: {
            scope: {
                type: 'string',
                enum: ['children', 'descendants'],
                description: 'children (default) lists direct children only; descendants walks the complete tree below you.',
            },
        },
        output: {
            schema: {
                type: 'array',
                items: {
                    oneOf: [
                        {
                            type: 'object',
                            additionalProperties: false,
                            properties: {
                                kind: { type: 'string', required: true, enum: ['child'] },
                                id: { type: 'string', required: true },
                                label: { type: 'string', required: true },
                                status: { type: 'string', required: true, enum: ['running', 'idle', 'ready'] },
                                parent: { type: 'string' },
                                depth: { type: 'number' },
                            },
                        },
                        {
                            type: 'object',
                            additionalProperties: false,
                            properties: {
                                kind: { type: 'string', required: true, enum: ['diagnostic'] },
                                id: { type: 'string', required: true },
                                reason: { type: 'string', required: true, enum: ['corrupt', 'unsupported', 'unavailable'] },
                                parent: { type: 'string' },
                                depth: { type: 'number' },
                            },
                        },
                    ],
                },
            },
            render: (args, entries) => {
                const request = resolveListAgentsRequest(args);
                return [{
                        type: 'text',
                        text: entries.length === 0
                            ? '(no subagents)'
                            : entries.map((entry) => {
                                // A descendants row always carries its position; children rows
                                // never render it. String() spans the schema-optional shape
                                // without a dead fallback branch.
                                const at = request.scope === 'descendants'
                                    ? ` parent=${String(entry.parent)} depth=${String(entry.depth)}`
                                    : '';
                                return entry.kind === 'child'
                                    ? `${entry.id} [${entry.status}]${at} — ${entry.label}`
                                    : `${entry.id} [diagnostic: ${entry.reason}]${at}`;
                            }).join('\n'),
                    }];
            },
        },
        async execute(args, exec) {
            const parent = exec.agent;
            if (!parent) {
                // Non-agent callers have no session whose children could be listed.
                throw new Error('list_agents requires a calling agent (exec.agent was undefined)');
            }
            const request = resolveListAgentsRequest(args);
            // The registry drains started tool bodies, so the scan must observe the
            // call's signal rather than finish a slow catalog after cancellation.
            switch (request.scope) {
                case 'children': {
                    const entries = await ctx.subagents.listChildren(parent.id, exec.signal);
                    return entries
                        .map(entry => project(ctx.agents, entry))
                        .filter(entry => entry !== undefined);
                }
                case 'descendants': {
                    const entries = await ctx.subagents.listDescendants(parent.id, exec.signal);
                    return entries
                        .map(entry => project(ctx.agents, entry, entry))
                        .filter(entry => entry !== undefined);
                }
                /* v8 ignore next 2 -- the resolver normalizes the schema-validated closed scope before dispatch. */
                default:
                    return assertNever(request.scope, 'list_agents scope');
            }
        },
    }));
}
//# sourceMappingURL=list-agents.js.map