feat(subagent): enrich subagent/start + subagent/end lifecycle events (observe-only)

A hooks bridge translating SubagentStart/SubagentStop needs to know WHICH kind of
subagent ran and WHAT it produced — Claude Code's hooks carry subagent_type and the
child's final message. Enrich the existing lifecycle emits to match, observe-only:

- agentType: an optional caller-supplied subagent-kind label (CC's subagent_type),
  added to SubagentStartRequest and carried VERBATIM onto both subagent/start
  (SubagentRunInfo) and subagent/end (SubagentRunEndInfo). The seam never interprets
  it. dsh-tool-subagent threads it from a new optional Config.agentType, so a
  deployment exposing multiple subagent kinds (one tool load per kind) labels each.
- lastAssistantMessage: the child's final output (SubagentResult.output), added to
  SubagentRunEndInfo on the settle path so an observer sees what the subagent
  produced without holding the run. Absent on the reject path (no result produced).

Strictly observe-only: both events stay plain emits (subagent/end fires from a
detached .then and awaits no listener). A control-flow subagent/end (awaited
waterfall returning a decision) would need the emit→waterfall reshape, awaiting
listeners before settling, and a provider resume capability — deferred to the
background/steering redesign (FIXME(subagent-continuation) anchors it). RFC:
implemented/feature/2026-06-30-subagent-observe-enrich.md.
This commit is contained in:
Tianyi Cui
2026-06-30 21:29:08 +08:00
parent 60418a5779
commit 7cc7b9cf7f
11 changed files with 225 additions and 10 deletions

View File

@@ -48,6 +48,14 @@ export interface Config {
* spawned child. Omitted fields fall back to the child loop's own defaults.
*/
agentOptions?: AgentOptions
/**
* Optional subagent-kind LABEL stamped on every run this tool starts (Claude
* Code's `subagent_type`). Carried onto the `subagent/start`/`subagent/end`
* lifecycle events so an observer can report or match on which kind of
* subagent ran. A deployment that exposes multiple subagent kinds (one tool
* load per kind) sets a distinct `agentType` per load; omit when undifferentiated.
*/
agentType?: string
}
export const Config: z<Config> = z.object({
@@ -57,6 +65,7 @@ export const Config: z<Config> = z.object({
model: z.string(),
systemPrompt: z.string(),
}),
agentType: z.string(),
})
/**
@@ -128,6 +137,7 @@ export function apply(ctx: Context, config: Config): void {
parent,
...exec.signal ? { signal: exec.signal } : {},
...config.agentOptions ? { agentOptions: config.agentOptions } : {},
...config.agentType !== undefined ? { agentType: config.agentType } : {},
}
const run: SubagentRun = ctx.subagents.start(config.provider, request)