Skip to content

feat: typed errors — supportedOn + retriable signals — Phase 2#939

Merged
thymikee merged 1 commit into
mainfrom
feat/phase2-typed-error
Jun 29, 2026
Merged

feat: typed errors — supportedOn + retriable signals — Phase 2#939
thymikee merged 1 commit into
mainfrom
feat/phase2-typed-error

Conversation

@thymikee

Copy link
Copy Markdown
Member

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 on main.

field meaning source
supportedOn?: string the platform families that DO support the command derived from the capability matrix (new supportedPlatformsForCommand) — added on UNSUPPORTED_OPERATION / UNSUPPORTED_PLATFORM
retriable?: boolean the failure is transient and worth retrying conservative retriableForErrorCode policy — currently only DEVICE_IN_USE (device healthy but busy); ambiguous/deterministic codes stay undefined

So 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 (where req.command is in scope) via a small enrichDaemonError, 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, …). supportedPlatformsForCommand reads the existing capability matrix (no new platform branching). The retriable policy is intentionally tiny and documented as extensible.

Verification

  • tsc --noEmit exit 0
  • oxfmt + oxlint --deny-warnings clean
  • fallow audit --base origin/main clean
  • vitest daemon/core/contracts/utils/commands → 185 files / 1632 tests pass — no error-shape ripple (the additive fields appear only on the three decided codes)
  • Layering Guard grep empty
  • New request-router-typed-error.test.ts: unit (the conservative retriable policy) + router e2e (UNSUPPORTED_OPERATION carries supportedOn consistent with supportedPlatformsForCommand; 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 a Record per the spine's doctrine; the breaking final removal of the legacy client-types.ts result mirror (c) is reserved for a major version (per the plan's "next major" note for removals).

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.
@github-actions

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.4 MB 1.4 MB +547 B
JS gzip 450.7 kB 450.9 kB +212 B
npm tarball 553.6 kB 554.3 kB +620 B
npm unpacked 2.0 MB 2.0 MB +2.3 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.9 ms 26.8 ms -0.2 ms
CLI --help 47.5 ms 47.0 ms -0.5 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9722.js +472 B +180 B

@thymikee

Copy link
Copy Markdown
Member Author

Current head is blocked by one Smoke Tests job. The failed job dies in Prepare iOS runner with COMMAND_FAILED: artifact restored but runner did not connect, lastError TypeError: fetch failed, logPath /Users/runner/work/agent-device/agent-device/.tmp/agent-device-state/sessions/cwd_a87d3c22dc05c4d7_default/runner.log. Other checks are green, so please retry or inspect the runner startup log before re-review.

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed current head 8252080. I did not find actionable blockers: typed-error enrichment is centralized in request-router, default error responses stay unchanged unless supportedOn/retriable applies, supportedOn derives from the existing capability matrix instead of a new platform table, and retriable is conservatively limited to DEVICE_IN_USE. All 21 checks are green. Marking ready-for-human.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jun 29, 2026
@thymikee thymikee merged commit 47eea11 into main Jun 29, 2026
21 of 22 checks passed
@thymikee thymikee deleted the feat/phase2-typed-error branch June 29, 2026 17:58
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-29 17:59 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant