feat: per-command MCP outputSchema — Phase 4#941
Conversation
Hand-author per-command MCP outputSchemas for the 13 typed commands whose closed result shapes live in the contracts layer (mirroring CommandResultMap): press, fill, longpress, boot, shutdown, viewport, home, back, rotate, app-switcher, clipboard, appstate, keyboard. The new COMMAND_OUTPUT_SCHEMAS registry is injected into tools/list via listCommandTools(). It is additive-only: untyped/dynamic tools (snapshot, gestures, perf, logs, …) carry no outputSchema key and stay byte-identical. Schemas are non-strict (no additionalProperties:false) so the additive cost object rides into structuredContent and still validates. MCP agents can now trust structuredContent against the advertised schema instead of re-parsing text.
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
Checked current head One cleanup before I would call this ready: please type-tie Right now a misspelled key or a new |
Replace Partial<Record<string, JsonSchema>> with `satisfies Record<keyof CommandResultMap, JsonSchema>`, so the one-for-one invariant with the typed-result spine is compiler-enforced: a new CommandResultMap entry without an output schema is now a missing-key error, and a misspelled/extra key is an excess-property error (previously both compiled and silently omitted the schema). The lookup in listCommandTools guards with an `in` check since the registry is keyed by the typed commands only.
|
Done — pushed ec3f908.
The |
|
Re-reviewed current head I rechecked the scoped diff against Phase 4 in |
|
…943) #941 (MCP outputSchema) and #940 (errors/redaction/device -> src/kernel) merged in an order where #940's import codemod never saw #941's new command-output-schemas.ts, so it still imports '../utils/device.ts' — which no longer exists. tsc/build on main is red. Repoint the import at '../kernel/device.ts'. The only stale reference on main.
…ase 4 (#942) * feat: leveled response views + --level knob, with a snapshot digest — Phase 4 Add the agent-cost leveled-response system: a responseLevel knob (digest | default | full) plumbed end to end behind a global --level flag (mirroring --cost), and a per-command ResponseView registry applied in the router on the success path. - contracts: RESPONSE_LEVELS/ResponseLevel + meta.responseLevel + boundary schema whitelist. Plumbing mirrors --cost: cli-flags FlagDefinition + GLOBAL_FLAG_KEYS, AgentDeviceClientConfig + overrides, buildClientConfig, buildMeta. ResponseLevel exported from the public root. - src/daemon/response-views.ts: the ResponseView registry. Seeds the snapshot digest — the full node tree (the dominant token sink) collapses to { nodeCount, refs: first 12 hittable/non-occluded refs with labels } plus the cheap top-level signals (truncated/visibility/snapshotQuality). full returns today's shape (nothing richer is computed yet). - router graft (applyResponseLevelView + applyAgentCostGrafts): composes with the existing cost block. With responseLevel default (or unset) AND no registered view AND no --cost, the original response is returned UNCHANGED — byte-identical to today (Maestro .ad recompare safe). cost.nodeCount reads the original node tree so it stays accurate even after a digest. Tests: snapshot view unit test (digest filters hittable/occluded, drops the tree, keeps cheap signals; default/full passthrough); router graft test via an injected view (default identity byte-identical, digest applies, full passthrough, digest+cost composition, unregistered-command passthrough, boundary parse). Verified: tsc, oxfmt + oxlint --deny-warnings, fallow audit clean, rslib build, Layering Guard empty, 1106 daemon/contracts/client tests pass (incl. the existing cost/typed-error grafts after the restructure). * fix: repoint MCP output-schemas import to kernel/device (rebase fixup) The kernel move (#940) deleted src/utils/device.ts; #941's command-output-schemas.ts (merged after #940's codemod ran) still imported the old path. Same one-line fix as #943; de-dups once that lands. * fix: re-classify responseLevel flag in integration-progress model The --level/responseLevel flag is a diagnostics/output flag (not device- observable), classified in the exclusion bucket alongside --cost. (Lost in an earlier rebase; re-applying.)
Phase 4 (agent-cost): per-command MCP
outputSchemaMCP agents currently have to re-parse the text content to learn a command's result shape. This slice advertises a per-command
outputSchemaso agents can truststructuredContentdirectly.What this does
src/mcp/command-output-schemas.ts: a hand-authored, partial-coverageCOMMAND_OUTPUT_SCHEMASregistry keyed by daemon command name. It mirrors the typed-result spineCommandResultMap(src/core/command-descriptor/command-result.ts) one-for-one — schemas authored by hand from the matchingsrc/contracts/*types (there is no type→JSON-Schema generator in the repo).listCommandTools()sotools/listnow returnsoutputSchemafor the typed commands. No router/server changes (the protocol version supportsoutputSchema).src/mcp/__tests__/command-tools.test.tscovering a typed command's discriminant, the byte-identical untyped path, and structuredContent↔schema consistency.The 13 typed commands
press,fill,longpress,boot,shutdown,viewport,home,back,rotate,app-switcher,clipboard,appstate,keyboard.The genuinely-dynamic commands (snapshot overlays, gestures, perf, logs, …) are intentionally absent — exactly as
CommandResultMapomits them rather than inventing a shape.Design notes
outputSchemakey and stay byte-identical to today.additionalProperties: falseanywhere, so the additivecostobject (opted in via--cost/includeCost) and any other additive fields ride intostructuredContentand still validate.constdiscriminants, and discriminated-union branches (clipboardonaction,appstateonplatform, the interaction trio onkind) mirror the source contract types. Union shapes useoneOfwith mutually-exclusiveconstdiscriminants so additive fields never break the exactly-one-of contract.Verification
tsc --noEmit: exit 0oxfmt --write+oxlint --deny-warnings: exit 0fallow audit --base origin/main: CLEANvitest run src/mcp: 22 passed