docs: trim generated prose

This commit is contained in:
Tianyi Cui
2026-07-12 03:36:43 +08:00
parent 3dca90261c
commit 75838e10b5
323 changed files with 2857 additions and 11833 deletions

View File

@@ -1,20 +1,7 @@
/**
* The out-of-process ACP subagent backend: registers a {@link SubagentProvider}
* on `ctx.subagents` that runs each child agent in a SPAWNED SUBPROCESS, driven
* over the Agent Client Protocol (ACP) as the client. The parent process is the
* ACP client; the child is any ACP agent (point the configured command at the
* `acp-agent` example to "talk to our own process").
*
* Unlike the in-process backends (`-spawn`/`-fork`), the child does NOT share
* this cordis context — it is a separate process with its own session, model
* client, and tools. So this backend injects only `subagents` (no `agents`),
* advertises NO start-time capabilities (an out-of-process child cannot enforce
* the parent's depth/tool-filter), and ignores `request.parent`.
*
* Plugin export shape: named `name`/`inject`/`Config`/`apply`, NO default
* export (the cordis Loader's `unwrapExports` does `exports.default ?? exports`,
* so a stray default would drop the namespace — see docs/postmortem/0001).
*
* The out-of-process ACP subagent backend: registers a {@link SubagentProvider} on
* `ctx.subagents` that runs each child agent in a spawned SUBPROCESS, driven over the Agent
* Client Protocol (ACP) as the client.
* @module @deepseek-ai/dsh-subagent-acp
*/

View File

@@ -1,24 +1,5 @@
/**
* The out-of-process ACP subagent run driver. Spawns a child agent as a
* subprocess, speaks the Agent Client Protocol (ACP) to it over stdio as the
* CLIENT, drives one session to completion, and shapes the result into a
* {@link SubagentResult}. The mirror image of the server-side bridge in
* `@deepseek-ai/dsh-acp` (which is the ACP *agent* side): here we are the ACP
* *client*, so we CALL `initialize`/`newSession`/`prompt`/`cancel` and we
* IMPLEMENT the `Client` callbacks (`sessionUpdate`, `requestPermission`).
*
* One subprocess per run (fresh-process-per-run): `start` spawns, runs exactly
* one ACP session, and `dispose` kills the subprocess and awaits its exit.
* Persistent-process pooling is a future optimization (see the RFC).
*
* TODO(acp-subagent-replay): snapshot-tier coverage of an ACP child is a
* distinct replay shape — each child is its own PROCESS with its own
* single-agent replay (the child boots under `DSH_SNAPSHOT=replay` with its own
* sessions-root + fixture), unlike the in-process per-session keying in
* `dsh-llm-replay`. Deferred to a follow-up; keyless coverage here is via a
* scripted mock ACP server subprocess, and the with-key e2e drives the real
* `acp-agent` example. See the ACP-subagent-backend RFC.
*
* The out-of-process ACP subagent run driver.
* @module @deepseek-ai/dsh-subagent-acp/run
*/
@@ -96,16 +77,9 @@ export interface AcpRunSpec {
}
/**
* Default grace for the child's EOF-driven quiesce on dispose (the
* `disposeEofGraceMs` config) — the window for it to flush persistence and tear
* down its OWN nested subprocesses (which may run their own `SIGTERM`→`SIGKILL`
* escalation) before the parent escalates to a signal. Deliberately LARGER than
* {@link DEFAULT_DISPOSE_GRACE_MS}: a cooperative child whose teardown is itself
* waiting on a signal-trapping grandchild (e.g. a bash subprocess in its own ~3s
* SIGTERM→SIGKILL grace) plus a final flush needs MORE than a single
* signal-grace of headroom, or the parent's SIGTERM cuts it off exactly as it
* reaches its own SIGKILL+flush. The child is an arbitrary ACP agent, so this is
* a standalone generous default, NOT derived from any child's internals.
* Default grace for the child's EOF-driven quiesce on dispose (the `disposeEofGraceMs` config)
* — the window for it to flush persistence and tear down its own nested subprocesses (which
* may run their own `SIGTERM`→`SIGKILL` escalation) before the parent escalates to a signal.
*/
export const DEFAULT_DISPOSE_EOF_GRACE_MS = 6_000
@@ -176,13 +150,6 @@ function toError(value: unknown): Error {
/**
* Start an out-of-process ACP child for `request` and return a {@link SubagentRun}.
*
* Spawns the configured command, wraps its stdio in an ACP `ClientSideConnection`,
* and drives one session: `initialize` → `newSession` → `prompt`. The accumulated
* `agent_message_chunk` text is the result output; the prompt's terminal
* `StopReason` maps to the stop reason. `result` never REJECTS on a child-level
* failure (a spawn/transport/RPC error resolves with `stopReason: 'error'`), per
* the seam contract. `cancel()` sends `session/cancel`; `dispose()` kills the
* subprocess and awaits its exit (quiescent teardown).
* @param request - the start request; the driver consumes `prompt` and `signal`
* (an already-aborted signal yields an inert `aborted` run with no spawn).
* @param spec - the resolved spawn spec: command/args/cwd, env, permission
@@ -227,12 +194,8 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
// Accumulate the child's streamed assistant text — the SubagentResult output.
const output: string[] = []
// `cancelled` records that a cancel was requested (signal or cancel()), so a
// run torn down before the prompt resolves settles `aborted` rather than the
// generic error mapping. Held on a mutable object so the async closures that
// set it (the abort listener) and the IIFE that reads it don't fight TS's
// control-flow narrowing of a bare `let` (which would type the catch-time read
// as always-`false`).
// `cancelled` records that a cancel was requested (signal or cancel()), so a run torn down
// before the prompt resolves settles `aborted` rather than the generic error mapping.
const flags = { cancelled: false }
const makeClient = (_agent: AcpAgent): Client => ({
@@ -268,27 +231,14 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
)
let sessionId: string | undefined
// Resolves when a cancel is requested, so `result` can settle `aborted` even
// if the child never cooperates with `session/cancel` (it ignores the notify,
// or the prompt wedges). The result path races this against the ACP drive: the
// FIRST to settle wins, so `cancel()` always honors the contract (`result`
// settles `aborted`) without waiting on a non-cooperative child. `dispose`
// still kills the process and reaps it; this only unblocks `result`. The
// executor runs synchronously, so `signalCancelSettled` is assigned before the
// Promise constructor returns (the `!` asserts the definite assignment).
// Resolves when a cancel is requested, so `result` can settle `aborted` even if the child
// never cooperates with `session/cancel` (it ignores the notify, or the prompt wedges).
let signalCancelSettled!: () => void
const cancelSettled = new Promise<void>((resolve) => { signalCancelSettled = resolve })
const requestCancel = (): void => {
flags.cancelled = true
signalCancelSettled()
// Best-effort: tell the child to cancel the in-flight turn. Swallows a
// rejection — the session may not exist yet, or the pipe may be gone; the
// dispose path kills the process regardless. If the session has NOT been
// created yet (cancel raced ahead of `newSession`), the `cancelled` flag
// alone carries it: the result path re-checks the flag after each await and
// settles `aborted` without running the prompt. The `.catch` swallow is
// defensive for a narrow transport race (child gone mid-send) — v8-ignored
// because dispose kills the process regardless, so it can't be hit in tests.
// Best-effort: tell the child to cancel the in-flight turn.
/* v8 ignore next */
if (sessionId !== undefined) void conn.cancel({ sessionId }).catch(() => { /* child gone / no session */ })
}
@@ -303,12 +253,8 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
return text.length > 0 ? [{ type: 'text', text }] : []
}
// A provider is "started" only once the remote child has completed ACP
// initialization and published a session. SubagentService gates its
// `subagent/start` notification on this boundary, just as the in-process
// provider gates it on local Agent publication. Failure or cancellation
// before this point rejects readiness and therefore produces no paired
// lifecycle events for a child that never became live.
// A provider is "started" only once the remote child has completed ACP initialization and
// published a session.
const started: Promise<void> = Promise.race([
(async (): Promise<void> => {
await conn.initialize({
@@ -327,20 +273,13 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
const result: Promise<SubagentResult> = (async (): Promise<SubagentResult> => {
try {
// Readiness is the initialize → newSession phase above. Awaiting the SAME
// promise immediately observes its rejection even without the service,
// and guarantees the prompt phase never starts before the provider can
// truthfully announce a live child.
// Readiness is the initialize → newSession phase above.
await started
// Race two post-start outcomes, first to settle wins:
// - prompt: the normal remote turn;
// - cancelSettled: a cancel was requested — settle `aborted` immediately
// rather than waiting on a child that may ignore `session/cancel` or
// wedge the prompt (the `cancel()` contract: `result` settles `aborted`).
// A spawn error can only precede readiness and is already one arm of
// `started`; after `newSession` succeeds, transport/process failure rejects
// the in-flight prompt RPC through the connection.
// Race two post-start outcomes, first to settle wins: - prompt: the normal remote turn; -
// cancelSettled: a cancel was requested — settle `aborted` immediately rather than
// waiting on a child that may ignore `session/cancel` or wedge the prompt (the `cancel()`
// contract: `result` settles `aborted`).
const prompt = async (): Promise<SubagentResult> => {
// `started` cannot fulfill without assigning the session id; the cast
// records that local invariant without an unreachable defensive arm.
@@ -353,12 +292,7 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
])
} catch (error: unknown) {
if (flags.cancelled) return { output: collectOutput(), stopReason: 'aborted' }
// The seam contract: result resolves (never rejects) on a child-level
// failure. A cancellation is recognized by the flag above even when it
// wins during readiness; every other rejection is a genuine child-level
// error — initialize/newSession/prompt transport/RPC failure or ENOENT.
// Flatten to `error` and surface the original via onError so a real fault
// is preserved rather than silently lost.
// The seam contract: result resolves (never rejects) on a child-level failure.
try {
spec.onError?.(toError(error), 'error')
} catch {
@@ -379,15 +313,8 @@ export function startAcpRun(request: SubagentStartRequest, spec: AcpRunSpec): Su
},
async dispose(): Promise<void> {
request.signal?.removeEventListener('abort', onAbort)
// Quiescent teardown via the shared ladder (stdin EOF → SIGTERM →
// SIGKILL, awaiting the actual exit). For THIS child the EOF tier is the
// one that matters: our acp-agent has NO SIGTERM handler in a normal
// session — it tears down via the server bridge's connection-close path
// (conn.closed → per-agent dispose → final session/flush), driven by the
// stdin EOF, NOT by a signal — and a prompt response can resolve from a
// turn/end BEFORE that post-turn flush lands, so the child still has
// durable work owed when dispose runs (hence the wide EOF grace; see
// DEFAULT_DISPOSE_EOF_GRACE_MS).
// Quiescent teardown via the shared ladder (stdin EOF → SIGTERM → SIGKILL, awaiting the
// actual exit).
await disposeChildProcess(child, {
disposeEofGraceMs: spec.disposeEofGraceMs,
disposeGraceMs: spec.disposeGraceMs,