A TUI run against an unreachable endpoint failed with only 'fetch failed':
undici wraps transport failures in a bare TypeError whose diagnosis lives
on .cause, and every diagnostic seam rendered only error.message. The
readline front door additionally rendered failed turns as pure silence.
- dsh-llm: new errorChain(value) renders the full cause chain and
AggregateError members with circular/hostile-coercion containment.
- llm-deepseek: pre-response transport failures throw LlmError('NETWORK')
naming the endpoint and chaining the fetch TypeError; aborts keep their
DOMException so the loop still classifies them as cancellation.
- agent-loop: durable turn/end error messages and logger warnings render
through errorChain; local renderThrown copies removed.
- ui-stdio: failure turn/end reasons now render ([turn failed <code>],
[turn aborted], [turn rejected], output-token-limit); startup-failure
logs use errorChain.
- ui-tui: agent/error notices and the startup-failure line use errorChain.
Replace the process-wide adapter-failure WeakSet with a per-call scope bound to the exact AsyncIterable returned by LlmService.stream(). Give every call a unique wrapper so waterfall middleware can reuse an iterable without sharing provenance.
Move agent-loop recovery classification to the model-stream boundary. Only the final adapter behind that exact call can become an agent/request-error; nested llm/stream calls remain ordinary outer middleware failures while preserving the original Error.
Cover nested calls, reused middleware iterables, and end-to-end agent-loop recovery. Update the package and RFC contracts, bilingual pairing record, and generated API and catalog references.
Document every context-window path returned by mapStopReason: recognized provider error wording, successful stop usage beyond the resolved window, and zero-output length stops that fill the window.
This keeps the public return contract aligned with usage-based overflow classification without duplicating the provider-specific detection table owned by pi-ai.
Pass each resolved catalog model capacity into pi-ai stream conversion so the upstream full-message classifier can recognize provider-specific, silent, and length-stop overflow signals. Retain the harness text fallback for legacy provider wording and cover the catalog-resolution path with a mock-provider regression.
Semantic resolutions beyond line merges:
- The bash seam keeps resolveMode + the bash/resolve-mode waterfall on
master's task-free BashExecutor (run/start/resolve only; BashProcess
handles); tool-bash consults it at its stamping site and escalation
baseline on master's render/background split, with a waterfall test on
the recording executor.
- dsh-mode's BASH_FAMILY narrows to ['bash']: bash_output/bash_kill are
replaced by the kind-generic task_output/task_kill, which span every
task kind and only observe or stop work, so the access cap withholds
only the starter it can reason about.
- The plan-mode snapshot suite adopts master's pin grammar (tool-schema
sidecars; the expectedHeaderSnapshots extension is gone — the exit
transition deltas, and entering-before-turn-1 needs no second
snapshot); modes-advertise joins the plan header class (no-model, so
membership is vacuous). Fixtures re-recorded on the acp-demo bin;
the replay overlay gains the passthrough sandbox runner.
- examples/plan-acp-agent rewires to @deepseek-ai/dsh-acp-demo and drops
its tool-bash entry (the spine bundle now composes it); dsh-stdio (the
renamed stdio-chat home) keeps its /mode command and gains the dsh-mode
peer edge; the acp bridge keeps the modes surface beside master's
permission presets.
- mode README gains the Model Experience / Known Limitations sections the
new README gates require; AGENTS.md ceiling 1370 → 1440 for the kept
mode/ layout line and Agent efficiency section.