/**
 * Shared in-process child composition: the delegation-depth budget, the
 * durable session metadata, the resolved child `AgentOptions`, the delegated
 * policy seed, and the scoped setup a child agent needs. Both the one-shot
 * provider driver and the continuation manager compose children this way, so
 * depth accounting, lineage stamping, and delegation policy have one home.
 *
 * @module @deepseek-ai/dsh-subagent/child-agent
 */
import { delegationDepthOf } from "./depth.js";
/** Thrown when starting a child would exceed the requested depth cap. */
export class SubagentDepthError extends Error {
    attemptedDepth;
    maxDepth;
    constructor(attemptedDepth, maxDepth) {
        super(`subagent depth ${attemptedDepth} exceeds maxDepth ${maxDepth}`);
        this.attemptedDepth = attemptedDepth;
        this.maxDepth = maxDepth;
        this.name = 'SubagentDepthError';
    }
}
/**
 * Resolve the child's delegation depth from its parent and enforce an optional
 * cap. The persisted parent header is the monotone floor, so a resumed parent
 * cannot delegate as if it were top-level.
 * @param parent - the delegating parent agent.
 * @param maxDepth - optional absolute cap the resolved depth must not exceed.
 * @returns the child's non-negative safe-integer depth.
 * @throws {SubagentDepthError} when the resolved depth exceeds `maxDepth`.
 * @throws {RangeError} when the resolved depth leaves the safe-integer range.
 */
export function resolveChildDepth(parent, maxDepth) {
    const childDepth = delegationDepthOf(parent) + 1;
    if (!Number.isSafeInteger(childDepth)) {
        throw new RangeError('subagent child depth exceeds the safe-integer range');
    }
    if (maxDepth !== undefined && childDepth > maxDepth) {
        throw new SubagentDepthError(childDepth, maxDepth);
    }
    return childDepth;
}
/**
 * Resolve the child's `AgentOptions`: the parent's provider/model/maxTokens
 * route unless the request overrides it, stamped with the child's own
 * delegation depth.
 * @param parent - the delegating parent whose route the child inherits.
 * @param requested - per-child overrides, if any.
 * @param childDepth - the resolved delegation depth to stamp.
 * @returns the resolved options for `ctx.agents.create()`.
 */
export function resolveChildAgentOptions(parent, requested, childDepth) {
    const parentProvider = parent.options.provider;
    const parentModel = parent.options.model;
    const parentMaxTokens = parent.options.maxTokens;
    return {
        ...parentProvider !== undefined ? { provider: parentProvider } : {},
        ...parentModel !== undefined ? { model: parentModel } : {},
        ...parentMaxTokens !== undefined ? { maxTokens: parentMaxTokens } : {},
        ...requested,
        subagentDepth: childDepth,
    };
}
/**
 * Build the child session's durable creation metadata: the parent's workspace,
 * its direct lineage, coarse product origin, the recursion budget that must
 * survive persistence, the seed boundary that separates inherited parent
 * history from child work, and the composition the child runs under.
 *
 * The preset is read from the parent's LIVE scope chain rather than from its
 * header, because a parent that switched preset while blank runs on the newer
 * composition and its header still names the older one. Recording it is what
 * makes a child's history reconstructable: without it a cold read of the child
 * resolves the deployment default and rebuilds turns under a tool set the
 * child never had.
 * @param parent - the delegating parent agent.
 * @param childDepth - the resolved delegation depth to persist.
 * @param lineageSeedLength - how many leading events came from the parent's log.
 * @returns the `meta` for `ctx.agents.create()`.
 */
export function childSessionMeta(parent, childDepth, lineageSeedLength) {
    const parentHeader = parent.session.header;
    const agentPreset = parent.ctx.get('agentPresets')?.composedPreset(parent.ctx);
    return {
        ...parentHeader.cwd !== undefined ? { cwd: parentHeader.cwd } : {},
        ...agentPreset === undefined ? {} : { agentPreset },
        parentSession: parentHeader.id,
        // Navigation classification only; the descriptor remains the authority
        // for mode and continuation capability.
        origin: 'subagent',
        // Durable: the recursion budget must survive persistence and resume.
        delegationDepth: childDepth,
        ...lineageSeedLength > 0 ? { seedLength: lineageSeedLength } : {},
    };
}
/**
 * Model-facing delegation-scope statement for every in-process child. A
 * runtime-context contribution rather than a system-prompt section, so the
 * deployment's system prompt stays uniform across parents and children.
 */
export const SUBAGENT_DELEGATION_CONTEXT = 'You are a delegated subagent: your permission scope was fixed when you were started and cannot be '
    + 'widened from inside this session — operations that require approval are rejected automatically. '
    + 'When the task needs access beyond that scope, do not retry the denied operation; state the '
    + 'limitation in your reply so the delegating agent can handle it.';
/**
 * Compose one child inside its creation window: join its parent's preset,
 * register the fixed delegation-scope statement, then apply the child's own
 * shadowing persona section and tool restriction, all owned by the child's
 * scope and therefore invisible to its parent and siblings. Creation and cold
 * resume both pass through here.
 *
 * The join comes first and the child's own registrations second, which is the
 * order the layering already implies — the nearest scope wins a name, and a
 * per-child restriction intersects with everything its chain admits — but
 * stating it here keeps the two steps from being read as independent.
 *
 * The join and the per-child registrations live in ONE call because a child
 * composed without the join is exactly the defect this function exists to
 * prevent: with every model-facing row on the agent plane, a child that joins
 * no preset sees an empty tool registry and none of its parent's prompt
 * sections. Taking the parent as a parameter is what makes that omission
 * unrepresentable at the call sites.
 * @param childCtx - the child agent's scoped creation context.
 * @param parent - the delegating parent whose composition the child joins.
 * @param composition - the per-child persona and tool filter to install.
 */
export function applyChildComposition(childCtx, parent, composition) {
    childCtx.get('agentPresets')?.composeFrom(childCtx, parent.ctx);
    // Order 120: after the sandbox:policy (110) and approval:policy (115) sentences.
    childCtx.systemPrompt.context({ name: 'subagent:delegation', order: 120, text: SUBAGENT_DELEGATION_CONTEXT });
    if (composition.persona !== undefined) {
        childCtx.systemPrompt.section({ name: 'deployment:persona', order: 0, text: composition.persona });
    }
    if (composition.toolFilter !== undefined)
        childCtx.tools.restrict(composition.toolFilter);
}
/**
 * Capture the policy to seed into one delegation. Call synchronously before
 * the child start's first await: a later parent switch belongs to the
 * parent's future, not to this child. Only the parent session's explicit
 * sandbox override is captured — never deployment defaults or one-shot
 * grants — and the approval policy is pinned to `'never'` regardless of the
 * parent's own policy.
 * @param parent - the delegating parent agent.
 * @returns the sandbox override (or `undefined` without one) and the approval pin.
 */
export function captureDelegatedPolicyOverrides(parent) {
    return {
        sandboxMode: parent.ctx.get('sandboxPolicy')?.overrideOf(parent.session),
        approvalPolicy: parent.ctx.get('approval') === undefined ? undefined : 'never',
    };
}
/**
 * Append the captured delegation policy onto the child's own log as
 * `source: 'delegation'` events inside the unpublished creation window, so the
 * child's effective policy is reconstructable from its log alone. Appends land
 * after any fork seed, so fresh policy wins stale seed state; later child
 * switches still win over these events.
 * @param childSession - the unpublished child's session.
 * @param overrides - the policy captured at delegation.
 */
export function appendDelegatedPolicyOverrides(childSession, overrides) {
    if (overrides.sandboxMode !== undefined) {
        childSession.append('sandbox/mode', { mode: overrides.sandboxMode, source: 'delegation' });
    }
    if (overrides.approvalPolicy !== undefined) {
        childSession.append('approval/policy', { policy: overrides.approvalPolicy, source: 'delegation' });
    }
}
//# sourceMappingURL=child-agent.js.map