Commit Graph

5 Commits

Author SHA1 Message Date
Tianyi Cui
8723186398 Honor an already-aborted signal in the subagent tool bridge (review feedback)
addEventListener('abort') does not fire for a signal already aborted before the
listener is added, so a parent step cancelled before the subagent tool ran
would never reach the child — the tool leaned on each provider re-checking
request.signal itself, leaving the bridge's own claim incomplete for any
provider that relies on run.cancel(). Re-check exec.signal.aborted right after
registering and cancel explicitly. Regression test uses a spy provider that
only reacts to cancel() (never inspects the signal); proven to hang without the
fix (result never settles) and settle aborted with it.
2026-06-22 16:32:10 +08:00
Tianyi Cui
8d111161de Merge remote-tracking branch 'origin/master' into worktree-subagent-seam-pr1
# Conflicts:
#	tsconfig.typecheck.json
2026-06-22 14:35:35 +08:00
Tianyi Cui
861791d2d8 Contain subagent lifecycle listeners per-listener, not per-emit
A single try/catch around ctx.emit prevented a thrown subagent/start or
subagent/end listener from propagating, but cordis emit dispatches listeners in
a `.map(cb => cb())` that HALTS on the first throw — so a bad subscriber still
starved the listeners registered after it, violating the AGENTS.md
callback-boundary rule ("one bad subscriber must not starve the listeners after
it"). Resolve the listener callbacks via ctx.events.dispatch and contain each
call individually, the same per-listener guarantee BashExecutor.notifyTaskDone
gives its own listener set.

The two containment tests now register TWO listeners where the first throws and
assert the second still observes the event (start) and the settle (end) — a
regression that fails on the per-emit code (verified: reverted, watched both go
red, restored).
2026-06-21 23:44:07 +08:00
Tianyi Cui
25eccdaedc Fix review findings: lifecycle containment, configurable tool name, coverage, type catalog
Address four findings from the first Codex review round:

- Contain subagent/start|end listener throws (emitContainedStart/End): a
  thrown lifecycle listener could escape SubagentService.start() before the
  caller received the live run to dispose it (a leaked child), and a thrown
  subagent/end listener could surface as an unhandled rejection on the detached
  result-settle hook. Both emits now log-and-contain, mirroring the agent
  registry's agent/created|disposed containment.
- Make the model-facing tool name configurable (Config.toolName, default
  subagent). The docs say to load dsh-tool-subagent once per provider to expose
  multiple transports, but the hardcoded name made the second load throw a
  duplicate-tool-name error; a distinct toolName per load is now required and
  documented.
- Reach the per-file 100% coverage gate: tests for the subagent/end error
  branch, lifecycle-listener containment, every stopReasonError arm + the
  merge-extensible default, the multi-provider toolName path, agentOptions
  forwarding, and the direct-apply schema-bypass fallbacks.
- Document the seam vocabulary in docs/core-data-structures/subagent.md with
  verbatim type-equiv blocks + manifest entries, and link it from core.md (a
  brand-new core/seam type the doc-sync gate cannot detect on its own).
2026-06-21 23:15:43 +08:00
Tianyi Cui
1a81f2cccd Add subagent capability seam: interface, mock backend, model-facing tool
Introduce the `packages/subagent/` group and the abstract subagent seam — an
agent delegating to a child agent — as a named-provider registry (`ctx.subagents`),
unlike the single-implementation bash seam, so multiple transports (in-process,
ACP, future A2A) coexist. This first PR lands the interface, a scripted test
backend, and the model-facing tool, validated through the real cordis load path.

- dsh-subagent: SubagentService registry + SubagentProvider/SubagentRun
  vocabulary + subagent/start|end events. Start-time capabilities (outputSchema,
  depthLimit, toolFilter) are checked pre-start and rejected loud; runtime
  capabilities (sendMessage, resume) are optional methods on SubagentRun.
- dsh-subagent-mock (support): scripted provider for keyless, deterministic
  tests through the real Loader/export path.
- dsh-tool-subagent: the model-facing `subagent` tool, config-bound to one
  provider; synchronous collect with try/finally dispose, signal->cancel
  bridging, and non-completed-stop-reason -> isError mapping.
- Proposed RFC documenting the seam, the fork-vs-spawn-as-separate-backends
  decision, own-session isolation, synchronous-collect scope, and the deferral
  of background/poll/spill to a future unification with bash.
- Wire the new group into tsconfigs, build refs, package hierarchy docs, the
  module graph, and the cordis catalog.

RFC: docs/rfc/proposed/feature/2026-06-21-subagent-capability-seam.md
2026-06-21 22:31:56 +08:00