|
|
@@ -28,7 +28,6 @@ import type {
|
|
|
Agent,
|
|
|
AgentHandle,
|
|
|
AgentOptions,
|
|
|
- AgentSetupCommit,
|
|
|
CreateAgentOptions,
|
|
|
} from '@deepseek-ai/dsh-agent'
|
|
|
import { ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm'
|
|
|
@@ -53,30 +52,21 @@ import { seedDescriptorTurn } from './descriptor-seed.ts'
|
|
|
import type { ContinuableCreateRequest, ContinuableCreateSpec, SubagentResult, SubagentStartRequest } from './types.ts'
|
|
|
import type { ActivationObserver, ActivationTerminal } from './lifecycle.ts'
|
|
|
import { SubagentError } from './error.ts'
|
|
|
-import type SubagentActivationSetupRegistry from './activation-setup-registry.ts'
|
|
|
+import { isAdjacentAgentSendMessageTool } from './internal.ts'
|
|
|
|
|
|
-/** Attribution for a model coordinator's follow-up to one of its children. */
|
|
|
-export interface CoordinatorMessageSource {
|
|
|
- readonly kind: 'coordinator'
|
|
|
+/** Durable attribution for one model-authored message between adjacent Agents. */
|
|
|
+export interface AgentMessageSource {
|
|
|
+ readonly kind: 'agent-message'
|
|
|
/** A message another agent addressed to this one (`relay` context form). */
|
|
|
readonly form: 'relay'
|
|
|
- /** Session id of the agent whose tool call produced the follow-up. */
|
|
|
- readonly senderSessionId: SessionId
|
|
|
-}
|
|
|
-
|
|
|
-/** Durable attribution for a continuable child's explicit parent report. */
|
|
|
-export interface SubagentReportMessageSource {
|
|
|
- readonly kind: 'subagent-report'
|
|
|
- /** A message another agent addressed to this one (`relay` context form). */
|
|
|
- readonly form: 'relay'
|
|
|
- /** Session id of the reporting child. */
|
|
|
+ /** Session id of the Agent whose tool call produced the message. */
|
|
|
readonly senderSessionId: SessionId
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Durable attribution for the runtime's own account of a continuable child
|
|
|
* settling. Deliberately a different kind from
|
|
|
- * {@link SubagentReportMessageSource}: a report is content the child chose,
|
|
|
+ * {@link AgentMessageSource}: an Agent message is content the sender chose,
|
|
|
* while this message is the manager stating what became of the child, and a
|
|
|
* transcript that merged them would credit the child with words it never wrote.
|
|
|
*/
|
|
|
@@ -92,23 +82,11 @@ export interface SubagentSettledMessageSource {
|
|
|
|
|
|
declare module '@deepseek-ai/dsh-llm' {
|
|
|
interface MessageSourceMap {
|
|
|
- coordinator: CoordinatorMessageSource
|
|
|
- 'subagent-report': SubagentReportMessageSource
|
|
|
+ 'agent-message': AgentMessageSource
|
|
|
'subagent-settled': SubagentSettledMessageSource
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/** Deployment scheduling policy for accepted child reports. */
|
|
|
-export type SubagentReportDelivery = 'quiet' | 'next-step'
|
|
|
-
|
|
|
-/** Options for one continuable child's report to its direct parent. */
|
|
|
-export interface SubagentReportOptions {
|
|
|
- /** Already-resolved parent scheduling policy. */
|
|
|
- readonly delivery: SubagentReportDelivery
|
|
|
- /** Caller cancellation, owning authorization and admission until acceptance. */
|
|
|
- readonly signal: AbortSignal
|
|
|
-}
|
|
|
-
|
|
|
/** What a caller asks for when starting a continuable background child. */
|
|
|
export interface ContinuableStartSpec {
|
|
|
/** The `ctx.subagents` provider whose continuable-creation capability establishes the child. */
|
|
|
@@ -147,14 +125,17 @@ export type SubagentInterruptAuthority =
|
|
|
| { readonly kind: 'user'; readonly parentSessionId: SessionId }
|
|
|
| { readonly kind: 'ancestor'; readonly agent: Agent }
|
|
|
|
|
|
-/** Options for following up with one continuable child. */
|
|
|
-export interface SubagentFollowupOptions {
|
|
|
- /** Durable attribution retained on the delivered message; it grants no authority. */
|
|
|
- readonly source: MessageSource
|
|
|
+/** Options for one model-authored message between adjacent Agents. */
|
|
|
+export interface SubagentSendMessageOptions {
|
|
|
/** Caller cancellation, owning the operation only until inbox acceptance. */
|
|
|
readonly signal: AbortSignal
|
|
|
}
|
|
|
|
|
|
+/** Inputs shared by model steering and the human Queue adapter. */
|
|
|
+type ChildDeliveryOptions =
|
|
|
+ | { readonly delivery: 'steer'; readonly signal: AbortSignal }
|
|
|
+ | { readonly delivery: 'queue'; readonly source: MessageSource; readonly signal: AbortSignal }
|
|
|
+
|
|
|
/**
|
|
|
* The residency state of one continuable child, derived from Agent quiescence
|
|
|
* and the owned-child set rather than a second state machine:
|
|
|
@@ -232,7 +213,7 @@ interface Activation {
|
|
|
disposal: Promise<void> | undefined
|
|
|
/**
|
|
|
* Accepted waking message ids this manager has not yet seen leave the inbox.
|
|
|
- * `Agent.status` is still `idle` in the window between `followup()` and the
|
|
|
+ * `Agent.status` is still `idle` in the window between a waking send and the
|
|
|
* microtask that admits it, so settlement must not treat that gap as quiet.
|
|
|
*/
|
|
|
readonly accepted: Set<MessageId>
|
|
|
@@ -288,6 +269,42 @@ function disposalOf(activation: Activation): Promise<void> | undefined {
|
|
|
return activation.disposal
|
|
|
}
|
|
|
|
|
|
+/** Build durable attribution for one adjacent-Agent message. */
|
|
|
+function agentMessageSource(sender: Agent): AgentMessageSource {
|
|
|
+ return {
|
|
|
+ kind: 'agent-message',
|
|
|
+ form: 'relay',
|
|
|
+ senderSessionId: sender.id,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** Build the model-visible and durable representation of one adjacent-Agent message. */
|
|
|
+function agentMessage(sender: Agent, content: ContentBlock[]) {
|
|
|
+ return createUserMessage({
|
|
|
+ content: [
|
|
|
+ { type: 'text' as const, text: `Agent ${sender.id} sent a message:` },
|
|
|
+ ...content,
|
|
|
+ ],
|
|
|
+ source: agentMessageSource(sender),
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/** Append adjacent-Agent return guidance to a continuable child's initial task. */
|
|
|
+function continuableInitialPrompt(parentId: SessionId, prompt: ContentBlock[]): ContentBlock[] {
|
|
|
+ const encodedParentId = JSON.stringify(parentId)
|
|
|
+ return [
|
|
|
+ ...prompt,
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ text: `Your parent agent id is ${encodedParentId}. Before you finish, send your result to that agent with `
|
|
|
+ + `send_message({ agent_id: ${encodedParentId}, message: "<self-contained result>" }). The parent shares `
|
|
|
+ + 'your workspace but does not automatically receive your transcript, tool output, or reasoning. Send '
|
|
|
+ + 'earlier messages as well when a finding changes what the parent should do next; sending a message '
|
|
|
+ + 'does not end your turn.',
|
|
|
+ },
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* One line telling a parent that a background child is finished and why, in
|
|
|
* the parent's own task vocabulary.
|
|
|
@@ -373,7 +390,6 @@ export class SubagentContinuationManager {
|
|
|
constructor(
|
|
|
private readonly ctx: Context,
|
|
|
private readonly host: ContinuationHost,
|
|
|
- private readonly setupRegistry: SubagentActivationSetupRegistry,
|
|
|
) {
|
|
|
// Ordinary Cordis owner effects unwind in reverse registration order, which
|
|
|
// cannot express the dynamic child graph. Register the private scope's
|
|
|
@@ -470,10 +486,11 @@ export class SubagentContinuationManager {
|
|
|
})
|
|
|
return this.submitMaterialized(
|
|
|
activation,
|
|
|
- request.prompt,
|
|
|
- { kind: 'user' },
|
|
|
+ isAdjacentAgentSendMessageTool(this.ctx.get('tools')?.get('send_message', activation.handle.agent))
|
|
|
+ ? continuableInitialPrompt(parent.id, request.prompt)
|
|
|
+ : request.prompt,
|
|
|
+ { source: { kind: 'user' }, signal: spec.signal, delivery: 'queue' },
|
|
|
parent,
|
|
|
- spec.signal,
|
|
|
)
|
|
|
})
|
|
|
return { childId, messageId }
|
|
|
@@ -487,27 +504,75 @@ export class SubagentContinuationManager {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Deliver one later message to a known continuable child as its next FIFO
|
|
|
- * turn. Routing depends only on Activation residency: a `running` Activation
|
|
|
- * enqueues, a `waiting` one wakes the same Agent, and an absent one
|
|
|
- * cold-resumes a new Activation from the persisted Session. The Agent inbox
|
|
|
- * is the only queue, so every accepted message has one observable order.
|
|
|
- *
|
|
|
- * The caller signal owns lookup, materialization, and admission only until
|
|
|
- * inbox acceptance; afterwards the accepted turn cannot be cancelled through
|
|
|
- * this service.
|
|
|
- * @param parent - the exact live direct parent authorizing this delivery.
|
|
|
- * @param childId - the durable child session id.
|
|
|
- * @param content - the user-role content to deliver.
|
|
|
- * @param options - the message source fields and caller cancellation.
|
|
|
+ * Deliver one model-authored message to a direct continuable child or to the
|
|
|
+ * sender's direct parent. Both directions use Steer: a running target admits
|
|
|
+ * the message at its nearest step boundary, while an idle target starts a
|
|
|
+ * turn. A missing direct child cold-resumes through the ordinary continuation
|
|
|
+ * lifecycle. The caller signal owns the operation only until inbox acceptance.
|
|
|
+ * @param sender - exact live Agent authorizing and originating the message.
|
|
|
+ * @param targetId - durable direct-parent or direct-child session id.
|
|
|
+ * @param content - model-authored content to deliver.
|
|
|
+ * @param options - caller cancellation before acceptance.
|
|
|
+ * @returns the accepted message's inbox id.
|
|
|
+ * @throws when adjacency, availability, or admission rejects delivery.
|
|
|
+ */
|
|
|
+ async sendMessage(
|
|
|
+ sender: Agent,
|
|
|
+ targetId: SessionId,
|
|
|
+ content: ContentBlock[],
|
|
|
+ options: SubagentSendMessageOptions,
|
|
|
+ ): Promise<MessageId> {
|
|
|
+ if (this.ctx.agents.get(sender.id) !== sender) {
|
|
|
+ throw new SubagentError(
|
|
|
+ 'message delivery requires the exact live sender agent',
|
|
|
+ 'UNAUTHORIZED',
|
|
|
+ )
|
|
|
+ }
|
|
|
+ this.assertAdmitting(sender)
|
|
|
+ const senderActivation = this.activations.get(sender.id)
|
|
|
+ if (senderActivation !== undefined
|
|
|
+ && senderActivation.handle.agent === sender
|
|
|
+ && senderActivation.parentSession === targetId) {
|
|
|
+ options.signal.throwIfAborted()
|
|
|
+ return this.sendToParent(senderActivation, sender, content)
|
|
|
+ }
|
|
|
+ if (sender.session.header.parentSession === targetId) {
|
|
|
+ throw new SubagentError(
|
|
|
+ `agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`,
|
|
|
+ 'UNAUTHORIZED',
|
|
|
+ )
|
|
|
+ }
|
|
|
+ return this.deliverToChild(sender, targetId, content, {
|
|
|
+ signal: options.signal,
|
|
|
+ delivery: 'steer',
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Queue one human-authored prompt as a distinct direct-child turn.
|
|
|
+ * @param parent - exact live direct parent authorizing delivery.
|
|
|
+ * @param childId - durable direct-child session id.
|
|
|
+ * @param content - human-authored content to deliver.
|
|
|
+ * @param source - durable host-protocol provenance.
|
|
|
+ * @param signal - caller cancellation before inbox acceptance.
|
|
|
* @returns the accepted message's inbox id.
|
|
|
- * @throws when parent authority, availability, or admission rejects the delivery.
|
|
|
*/
|
|
|
- async followup(
|
|
|
+ async queuePrompt(
|
|
|
parent: Agent,
|
|
|
childId: SessionId,
|
|
|
content: ContentBlock[],
|
|
|
- options: SubagentFollowupOptions,
|
|
|
+ source: MessageSource,
|
|
|
+ signal: AbortSignal,
|
|
|
+ ): Promise<MessageId> {
|
|
|
+ return this.deliverToChild(parent, childId, content, { source, signal, delivery: 'queue' })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Route one parent-originated delivery through residency and cold resume. */
|
|
|
+ private async deliverToChild(
|
|
|
+ parent: Agent,
|
|
|
+ childId: SessionId,
|
|
|
+ content: ContentBlock[],
|
|
|
+ options: ChildDeliveryOptions,
|
|
|
): Promise<MessageId> {
|
|
|
this.assertAdmitting(parent)
|
|
|
while (true) {
|
|
|
@@ -536,7 +601,7 @@ export class SubagentContinuationManager {
|
|
|
return undefined
|
|
|
}
|
|
|
}
|
|
|
- return this.submitAdmitted(activation, content, options.source, parent, options.signal)
|
|
|
+ return this.submitAdmitted(activation, content, options, parent)
|
|
|
})
|
|
|
/* v8 ignore start -- only the lost-cutoff arm above returns undefined, so only that
|
|
|
* race reaches the retry below, which then cold-resumes a new Activation. */
|
|
|
@@ -610,88 +675,29 @@ export class SubagentContinuationManager {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Deliver explicitly selected content from one resident continuable child to
|
|
|
- * its durable direct parent. Sender authorization, parent resolution, and
|
|
|
- * send acceptance share one no-await span. Reporting neither concludes the
|
|
|
- * child's turn nor changes its Activation lifetime.
|
|
|
- * @param child - exact live reporting child; this is the authority credential.
|
|
|
- * @param content - selected model-facing content.
|
|
|
- * @param options - scheduling policy and pre-acceptance cancellation.
|
|
|
- * @returns the stable identity of the message accepted by the parent.
|
|
|
- * @throws {SubagentError} when the sender is unauthorized, the parent is not
|
|
|
- * live, or continuation admission is closing.
|
|
|
- */
|
|
|
- // oxlint-disable-next-line typescript/require-await -- keep rejection semantics without yielding during admission
|
|
|
- async reportFrom(
|
|
|
- child: Agent,
|
|
|
+ /** Deliver one resident continuable child's message to its live direct parent. */
|
|
|
+ private sendToParent(
|
|
|
+ activation: Activation,
|
|
|
+ sender: Agent,
|
|
|
content: ContentBlock[],
|
|
|
- options: SubagentReportOptions,
|
|
|
- ): Promise<MessageId> {
|
|
|
- options.signal.throwIfAborted()
|
|
|
- this.assertAdmitting(child)
|
|
|
- const activation = this.authorizeReporter(child)
|
|
|
- const parent = this.resolveReportParent(child)
|
|
|
- return this.deliverReport(activation, parent, content, options.delivery)
|
|
|
- }
|
|
|
-
|
|
|
- /** Authorize only the exact Agent of one resident Activation. */
|
|
|
- private authorizeReporter(child: Agent): Activation {
|
|
|
- const activation = this.activations.get(child.id)
|
|
|
- if (activation === undefined || activation.handle.agent !== child) {
|
|
|
- throw new SubagentError(
|
|
|
- `agent "${child.id}" is not a live continuable subagent and cannot report`,
|
|
|
- 'UNAUTHORIZED',
|
|
|
- )
|
|
|
- }
|
|
|
- /* v8 ignore next 6 -- only a synchronous re-entrant disposer can open this
|
|
|
- * transaction between exact-agent authorization and this no-await cutoff. */
|
|
|
+ ): MessageId {
|
|
|
+ /* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
|
+ * transaction between exact-agent authorization and this no-await span. */
|
|
|
if (activation.disposal !== undefined) {
|
|
|
throw new SubagentError(
|
|
|
- `subagent "${child.id}" activation is being disposed; the report was not delivered`,
|
|
|
+ `subagent "${sender.id}" activation is being disposed; the message was not delivered`,
|
|
|
'ACTIVATION_CLOSING',
|
|
|
)
|
|
|
}
|
|
|
- return activation
|
|
|
- }
|
|
|
-
|
|
|
- /** Resolve the reporting child's live direct parent from durable lineage. */
|
|
|
- private resolveReportParent(child: Agent): Agent {
|
|
|
- const parentId = child.session.header.parentSession
|
|
|
- /* v8 ignore next -- every continuation-managed child has direct-parent metadata. */
|
|
|
- const parent = parentId === undefined ? undefined : this.ctx.agents.get(parentId)
|
|
|
+ const parent = this.ctx.agents.get(activation.parentSession)
|
|
|
if (parent === undefined) {
|
|
|
throw new SubagentError(
|
|
|
- 'direct parent is not live; report was not delivered',
|
|
|
+ 'direct parent is not live; the message was not delivered',
|
|
|
'PARENT_UNAVAILABLE',
|
|
|
)
|
|
|
}
|
|
|
- return parent
|
|
|
- }
|
|
|
-
|
|
|
- /** Deliver one framed report through the selected parent scheduling preset. */
|
|
|
- private deliverReport(
|
|
|
- activation: Activation,
|
|
|
- parent: Agent,
|
|
|
- content: ContentBlock[],
|
|
|
- delivery: SubagentReportDelivery,
|
|
|
- ): MessageId {
|
|
|
- const message = createUserMessage({
|
|
|
- content: [
|
|
|
- { type: 'text' as const, text: `Background subagent ${activation.childId} reported:` },
|
|
|
- ...content,
|
|
|
- ],
|
|
|
- source: {
|
|
|
- kind: 'subagent-report' as const,
|
|
|
- form: 'relay' as const,
|
|
|
- senderSessionId: activation.childId,
|
|
|
- },
|
|
|
- })
|
|
|
- if (delivery === 'next-step') {
|
|
|
- this.sendWaking(parent, message, () => { this.sendReport(parent, message, delivery) })
|
|
|
- } else {
|
|
|
- this.sendReport(parent, message, delivery)
|
|
|
- }
|
|
|
+ const message = agentMessage(sender, content)
|
|
|
+ this.sendWaking(parent, message, () => { this.sendAgentMessage(parent, message) })
|
|
|
return message.id
|
|
|
}
|
|
|
|
|
|
@@ -717,18 +723,16 @@ export class SubagentContinuationManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /** Send one report while translating only the parent's own rejection. */
|
|
|
- private sendReport(
|
|
|
+ /** Send one Agent message while translating only the target's own rejection. */
|
|
|
+ private sendAgentMessage(
|
|
|
parent: Agent,
|
|
|
message: ReturnType<typeof createUserMessage>,
|
|
|
- delivery: SubagentReportDelivery,
|
|
|
): void {
|
|
|
try {
|
|
|
- if (delivery === 'next-step') parent.steer(message)
|
|
|
- else parent.inject(message)
|
|
|
+ parent.steer(message)
|
|
|
} catch (error: unknown) {
|
|
|
throw new SubagentError(
|
|
|
- 'direct parent is not live; report was not delivered',
|
|
|
+ 'direct parent is not live; the message was not delivered',
|
|
|
'PARENT_UNAVAILABLE',
|
|
|
{ cause: error },
|
|
|
)
|
|
|
@@ -963,7 +967,7 @@ export class SubagentContinuationManager {
|
|
|
parent: Agent,
|
|
|
childId: SessionId,
|
|
|
content: ContentBlock[],
|
|
|
- options: SubagentFollowupOptions,
|
|
|
+ options: ChildDeliveryOptions,
|
|
|
): Promise<MessageId> {
|
|
|
const query = this.requireSessionQuery()
|
|
|
let observation: SessionObservation
|
|
|
@@ -988,8 +992,7 @@ export class SubagentContinuationManager {
|
|
|
)
|
|
|
if (descriptor === undefined || descriptor.mode !== 'continuable') {
|
|
|
throw new SubagentError(
|
|
|
- `subagent "${childId}" has no supported continuation state and cannot be resumed; `
|
|
|
- + 'do not retry send_message with this id',
|
|
|
+ `subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`,
|
|
|
'NOT_RESUMABLE',
|
|
|
)
|
|
|
}
|
|
|
@@ -1014,36 +1017,34 @@ export class SubagentContinuationManager {
|
|
|
if (error instanceof SubagentError) throw error
|
|
|
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error })
|
|
|
}
|
|
|
- return await this.submitMaterialized(activation, content, options.source, parent, options.signal)
|
|
|
+ return await this.submitMaterialized(activation, content, options, parent)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Submit to a freshly materialized Activation or roll it back completely.
|
|
|
* @param activation - the just-published Activation to admit or release.
|
|
|
* @param content - the initial or resumed message content.
|
|
|
- * @param source - durable fields naming who supplied the accepted message.
|
|
|
+ * @param options - durable source, scheduling, and pre-acceptance cancellation.
|
|
|
* @param parent - the live direct parent authorizing admission.
|
|
|
- * @param signal - caller cancellation owning admission until acceptance.
|
|
|
* @returns the accepted inbox message id.
|
|
|
*/
|
|
|
private async submitMaterialized(
|
|
|
activation: Activation,
|
|
|
content: ContentBlock[],
|
|
|
- source: MessageSource,
|
|
|
+ options: ChildDeliveryOptions,
|
|
|
parent: Agent,
|
|
|
- signal: AbortSignal,
|
|
|
): Promise<MessageId> {
|
|
|
try {
|
|
|
if (contentHasImage(content)) {
|
|
|
// The capability read awaits with the activation already published, so
|
|
|
// the disposal cutoff is re-checked before the submit; a drain that
|
|
|
// began during the read turns into a clean closing rejection.
|
|
|
- await this.assertImageCapable(activation.handle.agent, signal)
|
|
|
+ await this.assertImageCapable(activation.handle.agent, options.signal)
|
|
|
if (activation.disposal !== undefined) {
|
|
|
throw new SubagentError(`subagent "${activation.childId}" is closing`, 'ACTIVATION_CLOSING')
|
|
|
}
|
|
|
}
|
|
|
- return this.submitAdmitted(activation, content, source, parent, signal)
|
|
|
+ return this.submitAdmitted(activation, content, options, parent)
|
|
|
} catch (error: unknown) {
|
|
|
/* v8 ignore next -- rollback disposal failures must not mask the
|
|
|
* pre-acceptance signal, drain, or lifecycle failure. */
|
|
|
@@ -1119,7 +1120,7 @@ export class SubagentContinuationManager {
|
|
|
// `AgentRegistry.enter()` is the authoritative collision boundary for an id
|
|
|
// some other owner holds — a duplicate would reject there with rollback.
|
|
|
inputs.signal.throwIfAborted()
|
|
|
- const setup = (childCtx: Context): AgentSetupCommit => {
|
|
|
+ const setup = (childCtx: Context): void => {
|
|
|
// Only fresh creation seeds the delegation policy onto the child's own
|
|
|
// log (after any fork seed, so fresh policy wins stale seed state); a
|
|
|
// cold resume replays those persisted events instead.
|
|
|
@@ -1127,7 +1128,6 @@ export class SubagentContinuationManager {
|
|
|
appendDelegatedPolicyOverrides((childCtx.agent as Agent).session, create.delegatedPolicies)
|
|
|
}
|
|
|
applyChildComposition(childCtx, parent, inputs.composition)
|
|
|
- return this.setupRegistry.apply(childCtx)
|
|
|
}
|
|
|
const observer = this.host.observeActivation(provider, childId, parent)
|
|
|
// Agent creation owns rollback before handle transfer. A rejection leaves
|
|
|
@@ -1256,15 +1256,18 @@ export class SubagentContinuationManager {
|
|
|
private submit(
|
|
|
activation: Activation,
|
|
|
content: ContentBlock[],
|
|
|
- source: MessageSource,
|
|
|
+ options: ChildDeliveryOptions,
|
|
|
parent: Agent,
|
|
|
): MessageId {
|
|
|
// Parent-originated delivery keeps the parent live through ownership, so
|
|
|
// establish it before the message can enter the child's inbox.
|
|
|
this.acquireOwnership(parent, activation.childId)
|
|
|
- const message = createUserMessage({ content, source })
|
|
|
+ const message = options.delivery === 'steer'
|
|
|
+ ? agentMessage(parent, content)
|
|
|
+ : createUserMessage({ content, source: options.source })
|
|
|
const accepted = this.admitWaking(activation, message.id, () => {
|
|
|
- activation.handle.agent.followup(message)
|
|
|
+ if (options.delivery === 'steer') activation.handle.agent.steer(message)
|
|
|
+ else activation.handle.agent.followup(message)
|
|
|
})
|
|
|
// Past this point the caller has an id for this child, so its eventual
|
|
|
// settlement is something the parent is owed an account of.
|
|
|
@@ -1307,11 +1310,10 @@ export class SubagentContinuationManager {
|
|
|
private submitAdmitted(
|
|
|
activation: Activation,
|
|
|
content: ContentBlock[],
|
|
|
- source: MessageSource,
|
|
|
+ options: ChildDeliveryOptions,
|
|
|
parent: Agent,
|
|
|
- signal: AbortSignal,
|
|
|
): MessageId {
|
|
|
- signal.throwIfAborted()
|
|
|
+ options.signal.throwIfAborted()
|
|
|
this.assertAdmitting(parent)
|
|
|
/* v8 ignore next 6 -- only a synchronous re-entrant disposer can change
|
|
|
* this field between the caller's live check and this no-await boundary. */
|
|
|
@@ -1326,7 +1328,7 @@ export class SubagentContinuationManager {
|
|
|
activation.childId,
|
|
|
activation.handle.agent.session.header.parentSession,
|
|
|
)
|
|
|
- return this.submit(activation, content, source, parent)
|
|
|
+ return this.submit(activation, content, options, parent)
|
|
|
}
|
|
|
|
|
|
/**
|