Graft the Phase 2 TypedError signals onto DaemonError: machine-readable hints an
agent can act on without a wasted round-trip.
- supportedOn: for UNSUPPORTED_OPERATION / UNSUPPORTED_PLATFORM errors, the
platform families that DO support the command, DERIVED from the capability
matrix (new supportedPlatformsForCommand in core/capabilities.ts). So an agent
that hit a platform mismatch learns where the command works.
- retriable: flags transient failures worth retrying. Conservative policy
(retriableForErrorCode in utils/errors.ts) — currently only DEVICE_IN_USE
(device healthy but busy); ambiguous/deterministic codes stay undefined.
Both are additive and applied at a single chokepoint (handleRequest, where the
command is in scope) only when a signal applies, so the default error wire shape
is unchanged for the common codes — verified across 1632 daemon/core/contracts/
utils/commands tests (no error-shape ripple). New unit + router e2e test covers
all three cases (supportedOn present, retriable true, deterministic unchanged).
Verified: tsc --noEmit, oxfmt + oxlint --deny-warnings, fallow audit clean,
Layering Guard empty.
What
Phase 2 (b): graft the TypedError signals onto
DaemonError— machine-readable hints an agent can act on without a wasted round-trip. Independent of the typed-results batches (different files), so it sits directly onmain.supportedOn?: stringsupportedPlatformsForCommand) — added onUNSUPPORTED_OPERATION/UNSUPPORTED_PLATFORMretriable?: booleanretriableForErrorCodepolicy — currently onlyDEVICE_IN_USE(device healthy but busy); ambiguous/deterministic codes stayundefinedSo an agent that hits a platform mismatch learns where the command works (e.g.
supportedOn: "apple, android, linux") instead of blindly retrying, and a transient failure is flagged for retry.How (one chokepoint, additive)
Both signals are applied in
handleRequest(wherereq.commandis in scope) via a smallenrichDaemonError, which returns the error unchanged unless a signal applies — so the default error wire shape is preserved for the common codes (INVALID_ARGS,COMMAND_FAILED, …).supportedPlatformsForCommandreads the existing capability matrix (no new platform branching). Theretriablepolicy is intentionally tiny and documented as extensible.Verification
tsc --noEmitexit 0oxfmt+oxlint --deny-warningscleanfallow audit --base origin/maincleanvitestdaemon/core/contracts/utils/commands → 185 files / 1632 tests pass — no error-shape ripple (the additive fields appear only on the three decided codes)request-router-typed-error.test.ts: unit (the conservativeretriablepolicy) + router e2e (UNSUPPORTED_OPERATION carriessupportedOnconsistent withsupportedPlatformsForCommand; DEVICE_IN_USE →retriable: true; deterministic INVALID_ARGS returns the default shape with neither field)Phase 2 status
This completes the TypedError graft (b). Typed results (a) are done for all cleanly-closeable commands across #913/#934/#937/#938 (13 commands). The genuinely-dynamic commands (
wait,alert) keep returning aRecordper the spine's doctrine; the breaking final removal of the legacyclient-types.tsresult mirror (c) is reserved for a major version (per the plan's "next major" note for removals).