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
@@ -1,44 +1,7 @@
/**
* A minimal mock ACP AGENT, run as a subprocess, for the keyless
* `dsh-subagent-acp` tests. It speaks the agent side of ACP over stdio and is
* fully scripted by environment variables — no model, no network:
*
* - `MOCK_TEXT` — the assistant text it streams as one `agent_message_chunk`.
* - `MOCK_STOP` — the ACP `StopReason` it returns from `prompt`
* (`end_turn` default, or `max_tokens`/`refusal`/…).
* - `MOCK_HANG` — if `1`, `prompt` never resolves on its own (it waits for
* a `session/cancel`), to exercise the client's cancel path.
* - `MOCK_IGNORE_CANCEL` — if `1` (with MOCK_HANG), the agent receives
* `session/cancel` but NEVER resolves the pending prompt
* and never exits — a non-cooperative child. The backend's
* `result` must still settle `aborted` on its own and
* `dispose()` must still kill the process.
* - `MOCK_PERMISSION` — if `1`, the agent calls `session/request_permission`
* before answering, to exercise the client's auto-answer.
* - `MOCK_READY_FILE` — if set, the path the agent touches once its `prompt`
* handler is in flight (it has streamed its chunk). A test
* polls for this file to cancel on a CONDITION rather than
* an arbitrary timeout (subprocess cold-start is variable).
* - `MOCK_FLUSH_ON_EOF` — if set, on stdin EOF the agent takes an async beat
* (MOCK_FLUSH_DELAY_MS, default 150) simulating the real
* acp-agent's EOF-driven quiesce+flush, then touches this
* path and exits ON ITS OWN — no signal. Stands in for a
* child whose durable flush completes only if dispose
* gives EOF a real window before escalating to SIGTERM.
* - `MOCK_IGNORE_EOF` — if `1`, keep the event loop alive past stdin EOF (a bare
* timer) but install a SIGTERM handler that exits (and, if
* MOCK_SIGTERM_FILE is set, touches it as an observable
* proof the SIGTERM rung fired). The child ignores the
* graceful EOF window yet dies cooperatively on SIGTERM —
* exercising dispose's middle tier (exit during the SIGTERM
* grace, before the SIGKILL escalation). Touches
* MOCK_READY_FILE once armed.
*
* It is NOT a test spec (no `describe`/`it`) — it is spawned BY the specs as the
* child process the ACP backend drives. Kept as a `.ts` run under tsx by the
* spec (which passes its own tsconfig), mirroring how the snapshot harness boots
* the real example.
*
* A minimal mock ACP AGENT, run as a subprocess, for the keyless `dsh-subagent-acp` tests. It
* speaks the agent side of ACP over stdio and is fully scripted by environment variables — no
* model, no network.
* @module @deepseek-ai/dsh-subagent-acp/tests/mock-acp-server
*/
@@ -157,11 +120,8 @@ function makeAgent(conn: AgentSideConnection): Agent {
process.exit(1)
}
if (IGNORE_CANCEL) {
// A NON-COOPERATIVE child: receive session/cancel but never resolve the
// pending prompt and never exit. The backend's `result` must still settle
// `aborted` on its own (the cancel-settle race), and `dispose()` must
// still kill the process — proving cancellation does not depend on the
// child cooperating.
// A NON-COOPERATIVE child: receive session/cancel but never resolve the pending prompt
// and never exit.
return Promise.resolve()
}
resolveCancel?.('cancelled')
@@ -178,12 +138,9 @@ new AgentSideConnection(
),
)
// Under MOCK_TRAP_SIGTERM, ignore SIGTERM and keep stdin open so the process
// neither quiesces on EOF nor dies on the graceful signal — exercising the
// backend dispose path's SIGKILL escalation. Without this the process exits
// normally on SIGTERM / stdin end. Touch READY_FILE once the trap is armed, so
// a test waits for that CONDITION before disposing (the trap must be in place,
// not merely the process spawned — otherwise SIGTERM hits the default handler).
// Under MOCK_TRAP_SIGTERM, ignore SIGTERM and keep stdin open so the process neither quiesces
// on EOF nor dies on the graceful signal — exercising the backend dispose path's SIGKILL
// escalation.
if (process.env.MOCK_TRAP_SIGTERM === '1') {
process.on('SIGTERM', () => { /* trapped: refuse to exit on the graceful signal */ })
// Keep the event loop alive (a bare timer) so nothing else lets it exit.
@@ -191,13 +148,9 @@ if (process.env.MOCK_TRAP_SIGTERM === '1') {
if (READY_FILE !== undefined) writeFileSync(READY_FILE, 'trap-armed')
}
// Under MOCK_FLUSH_ON_EOF, model the real acp-agent's EOF-driven quiesce: on
// stdin 'end' (the dispose path's `child.stdin.end()`), take an ASYNC beat to
// "flush", then touch the marker and exit ON OUR OWN — no signal involved. The
// beat is MOCK_FLUSH_DELAY_MS (default 150). A dispose that sends SIGTERM before
// the beat completes (no graceful window, or an EOF grace shorter than the
// flush) default-terminates this process and the marker is missing; a dispose
// that gives the EOF quiesce enough window first lets the flush land.
// Under MOCK_FLUSH_ON_EOF, model the real acp-agent's EOF-driven quiesce: on stdin 'end' (the
// dispose path's `child.stdin.end()`), take an ASYNC beat to "flush", then touch the marker and
// exit ON OUR own — no signal involved.
if (FLUSH_ON_EOF !== undefined) {
const flushDelayMs = Number(process.env.MOCK_FLUSH_DELAY_MS ?? '150')
process.stdin.on('end', () => {
@@ -208,14 +161,7 @@ if (FLUSH_ON_EOF !== undefined) {
})
}
// Under MOCK_IGNORE_EOF, keep the loop alive past stdin EOF (so the graceful EOF
// window times out) but INSTALL A SIGTERM HANDLER that records it and exits — the
// child ignores the graceful EOF window yet dies cooperatively on SIGTERM,
// exercising dispose's MIDDLE tier (exit during the SIGTERM grace, before the
// SIGKILL escalation). When MOCK_SIGTERM_FILE is set the handler touches it, an
// OBSERVABLE proof that the SIGTERM rung fired: if dispose skipped the middle
// rung and jumped EOF→SIGKILL, SIGKILL is uncatchable so the handler never runs
// and the marker is missing. Touch READY_FILE once armed (a test waits on it).
// Ignore EOF but exit on SIGTERM to exercise the middle disposal tier.
if (process.env.MOCK_IGNORE_EOF === '1') {
const sigtermFile = process.env.MOCK_SIGTERM_FILE
process.on('SIGTERM', () => {
@@ -225,4 +171,3 @@ if (process.env.MOCK_IGNORE_EOF === '1') {
setInterval(() => { /* stay alive past EOF until SIGTERM */ }, 1000)
if (READY_FILE !== undefined) writeFileSync(READY_FILE, 'ignore-eof-armed')
}
@@ -9,16 +9,7 @@ import SubagentService from '@deepseek-ai/dsh-subagent'
import * as acp from '../src/index.ts'
/**
* With-key e2e for the ACP subagent backend: the harness drives ITSELF as an ACP
* server. The backend spawns the real `acp-agent` example as a child PROCESS,
* speaks ACP to it over stdio, and the child runs the REAL model in its own
* process to answer a prompt. We verify the child's real answer comes back
* through the seam — the "talk to our own process" smoke the design called for.
* Key-gated (self-skips without DEEPSEEK_API_KEY).
*
* This is the out-of-process analogue of the in-process spawn e2e: there a
* parent agent on the same context drove a child; here the child is a separate
* process reached over ACP, proving the seam generalizes across the boundary.
* With-key e2e for the ACP subagent backend: the harness drives ITSELF as an ACP server.
*/
// The real acp-agent example: its bin + cordis.yml (the live DeepSeek config).
@@ -188,9 +188,8 @@ describe('dsh-subagent-acp', () => {
})
it('dispose escalates SIGTERM → SIGKILL for a child that traps SIGTERM (bounded quiescence)', async () => {
// The child traps SIGTERM and keeps its event loop alive, so a graceful
// term alone would hang dispose forever. With a short grace, dispose must
// escalate to SIGKILL and return once the process is actually gone.
// The child traps SIGTERM and keeps its event loop alive, so a graceful term alone would
// hang dispose forever.
const tmp = mkdtempSync(join(tmpdir(), 'acp-trap-'))
const ready = join(tmp, 'trap-armed')
try {
@@ -224,15 +223,8 @@ describe('dsh-subagent-acp', () => {
})
it('dispose gives the child an EOF window that outlasts the SIGTERM grace (graceful flush)', async () => {
// The real acp-agent flushes ASYNCHRONOUSLY on stdin EOF (its bridge tears
// down on connection close, NOT on a signal) — and it has no SIGTERM handler.
// Its EOF teardown can itself await a signal-trapping grandchild (a bash
// subprocess in its own SIGTERM→SIGKILL grace) plus a flush, so the EOF window
// must be a SEPARATE, WIDER grace than the SIGTERM tier — not the same value.
// The mock models a flush that takes LONGER than the SIGTERM grace but well
// under the EOF grace: it lands only because tier 1 waits eofGraceMs, not
// graceMs. (If dispose reused the small SIGTERM grace for the EOF wait — the
// round-2 bug — SIGTERM would fire mid-flush and the marker would be missing.)
// The real acp-agent flushes ASYNCHRONOUSLY on stdin EOF (its bridge tears down on
// connection close, not on a signal) — and it has no SIGTERM handler.
const tmp = mkdtempSync(join(tmpdir(), 'acp-eof-'))
const ready = join(tmp, 'ready')
const flushed = join(tmp, 'flushed')
@@ -242,10 +234,7 @@ describe('dsh-subagent-acp', () => {
args: ['--import', tsxLoader, mockServer],
cwd: process.cwd(),
permission: 'reject',
// MOCK_HANG so the prompt never resolves on its own — we tear down a live
// child. The flush beat (400ms) outlasts the 50ms SIGTERM grace but fits
// the 2000ms EOF grace; the marker lands iff the EOF tier honored its own
// wider grace.
// MOCK_HANG so the prompt never resolves on its own — we tear down a live child.
env: {
MOCK_HANG: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready,
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400', TSX_TSCONFIG_PATH: repoTsconfig,
@@ -267,12 +256,9 @@ describe('dsh-subagent-acp', () => {
})
it('escalates to SIGTERM for a child that ignores EOF but is not SIGTERM-trapping', async () => {
// A child that keeps its loop alive past stdin EOF (so the graceful window
// times out) but exits cooperatively on SIGTERM must die on the SIGTERM tier
// — dispose returns there, never reaching the SIGKILL tier. The child touches
// a SIGTERM marker from its signal handler: SIGKILL is uncatchable, so if
// dispose had skipped the middle rung (EOF→SIGKILL) the handler would never
// run and the marker would be absent — making this a GENUINE middle-tier guard.
// A child that keeps its loop alive past stdin EOF (so the graceful window times out) but
// exits cooperatively on SIGTERM must die on the SIGTERM tier — dispose returns there,
// never reaching the SIGKILL tier.
const tmp = mkdtempSync(join(tmpdir(), 'acp-ignore-eof-'))
const ready = join(tmp, 'ready')
const sigterm = join(tmp, 'sigterm')
@@ -307,9 +293,6 @@ describe('dsh-subagent-acp', () => {
it('honors a cancel that races AHEAD of newSession (no session id yet) without running the prompt', async () => {
// Gate the child at newSession: it signals `ready` and blocks until `go`.
// We cancel WHILE newSession is pending (sessionId still undefined, so the
// backend cannot send session/cancel) — the `cancelled` flag alone must
// settle the run aborted after newSession resolves, never issuing the prompt.
const tmp = mkdtempSync(join(tmpdir(), 'acp-early-'))
const ready = join(tmp, 'ready')
const go = join(tmp, 'go')
@@ -457,10 +440,9 @@ describe('dsh-subagent-acp', () => {
})
it('reports a flattened child failure through onError (preserved, not silently lost)', async () => {
// The seam forbids `result` rejecting, so a child-level failure is flattened
// to a stop reason — onError must still surface the original error so a real
// fault is logged, not swallowed. A nonexistent command triggers the spawn
// failure path; the spy records the error + the chosen stop reason.
// The seam forbids `result` rejecting, so a child-level failure is flattened to a stop
// reason — onError must still surface the original error so a real fault is logged, not
// swallowed.
const errors: { message: string; stopReason: string }[] = []
const run = startAcpRun(
{ prompt: [{ type: 'text', text: 'p' }], parent: fakeParent },
@@ -506,10 +488,8 @@ describe('dsh-subagent-acp', () => {
})
it('settles aborted when the child crashes (tears the pipe) AFTER a cancel', async () => {
// The child hangs, we cancel, and instead of answering the child exits hard
// — the pending prompt RPC rejects. With a cancel already requested, the
// backend's catch path must settle `aborted` (the failure is the cancel
// surfacing as a torn pipe), not `error`.
// The child hangs, we cancel, and instead of answering the child exits hard — the pending
// prompt RPC rejects.
const tmp = mkdtempSync(join(tmpdir(), 'acp-crash-'))
const ready = join(tmp, 'ready')
try {
@@ -526,10 +506,7 @@ describe('dsh-subagent-acp', () => {
})
it('settles aborted on cancel even when the child IGNORES session/cancel (non-cooperative)', async () => {
// The contract: run.cancel() → result settles `aborted`. A child that hangs
// its prompt AND ignores session/cancel must not wedge the parent — the
// backend's own cancel-settle path resolves `aborted` without the child's
// cooperation, and dispose() still reaps the process.
// The contract: run.cancel() → result settles `aborted`.
const tmp = mkdtempSync(join(tmpdir(), 'acp-ignorecancel-'))
const ready = join(tmp, 'ready')
try {