Drive the shutdown path where the requested signal reports a child error without exiting, the bounded marker grace expires, and fallback SIGKILL is accepted.
Assert that both signals are attempted and that close drains the successful fallback exit before preserving the original child error, restoring per-file 100% branch and line coverage for the launcher.
A successful Windows termination request can end the process before Node publishes exitCode or signalCode. If a child error wins the shutdown race, give that accepted exit a bounded observation window before escalating or reporting fallback refusal.
Cover a delayed real exit edge, preserve prompt refusal behavior for a genuinely live child, and document the launcher grace without weakening the complete stdio and parser drain boundary.
Model the requested signal setting a child termination marker before the launcher begins fallback handling. The regression proves close drains inherited stdio and propagates the original process error without sending a redundant SIGKILL.
This complements the post-check fallback-refusal race and restores the launcher's required 100% per-file statement and branch coverage.
Pass the fake descendant the parent process's stdout and stderr stream handles instead of Unix-style numeric file descriptors. This lets Windows duplicate the live ACP and diagnostic pipes so launcher shutdown can prove that inherited handles, buffered frames, and stderr all drain after the parent exits.
Observe the pending update promise before initiating shutdown as well, preventing a missing late frame from becoming a transient unhandled rejection before the assertion reports the fixture failure.
Treat a fallback kill refusal as successful termination when the child already carries an OS exit marker. Windows maps Node's supported signal names to forced termination, so the requested signal can end the process between the launcher error race and its fallback SIGKILL.
Drain inherited stdio, the ACP parser, and in-flight callbacks before propagating the original child error in either exit-race path. Preserve AggregateError reporting only for a refused fallback while the process is still live, and add a deterministic cross-platform regression for that ordering.
Three tests in the shared acp-snapshot package hardcoded POSIX path
separators in their assertions, so they failed on Windows where
node:path.join produces backslash paths:
- childFixturePaths (suite.spec.ts): expected literal '/snap/s/session.1.jsonl'
but join returns '\snap\s\...' on Windows; use join() for the expected value.
- harness.spec.ts (env-forwarding test): substring-matched a JSON-encoded
path against raw stdout text, where backslash escaping makes the compare
byte-fragile; parse the env-probe chunk and compare the structured value.
- harness.spec.ts (harvested-cwd test): substring-matched the raw cwd
against JSONL text where the cwd is JSON-escaped; parse the session line
and compare the cwd field.
These were master's latent bugs (the package's tests never ran on Windows
until the Windows CI lane observed them). Verified green on Windows via
scripts/caohuanqi-private/run-ci.py --windows.
The stack rebases onto a moved master through its base branch. Beyond
mechanical unions (both branches' demo scripts, example rows, service
roles, tool lists, acp deps, doc budgets — each side fit alone, the
union needs the higher ceilings), three semantic reconciliations:
- The ACP bridge now carries BOTH per-session surfaces: the sandbox
stack's config options + approval answerer and this branch's session
modes; session/new and session/load advertise modes AND configOptions
side by side.
- The feature matrix supersedes the sandbox stance per the RFC's
second-lander rule: session/set_mode and current_mode_update flip to
shipped-by-dsh-mode, config-option rows stay as #169 wrote them, and
§6 records both landed features under the picker-to-modes /
knobs-to-config-options division.
- The snapshot pin grammar (#254: one header snapshot + declared deltas
+ a Markdown prompt golden) gains a symmetric declaration for what a
delta cannot express: expectedHeaderSnapshots — a plan-mode flip
resorts the canonical tool list, so its widening lands as a second
full snapshot, now its own Markdown section. The pin-less-class and
model-turn-only-pin amendments carry over; new fixtures cover the
extended writer paths, and the plan-acp-agent scenarios re-recorded
under the merged composition (the app now bundles the skill tool)
with the suite's refresh mode wired through.
The ACP bridge registers the first real approval answerer: an ask for an
agent it owns becomes session/request_permission attached to the already-
streamed tool call (one-shot allow_once/reject_once only), outcomes map
conservatively (unknown optionId never grants, client cancel → cancelled),
and foreign or call-less requests delegate down the waterfall. The snapshot
harness accepts scripted permissionAnswers (FIFO; an unscripted prompt
answers cancelled, fail closed) so recorded scenarios can drive the wire
keylessly.