Skip to content

Harden model-backed device commands and status checks - #71

Merged
portons merged 10 commits into
mainfrom
codex/command-parameter-safety
Aug 3, 2026
Merged

Harden model-backed device commands and status checks#71
portons merged 10 commits into
mainfrom
codex/command-parameter-safety

Conversation

@portons

@portons portons commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

What changed

Device commands now follow the device model consistently wherever the CLI guides a send:

  • The built-in command flow sends the model command name in command. Raw or custom calls may use friendly_name; request field name is rejected.
  • Command settings go in extra_params. Required fields, declared value types, and one-versus-many shape are checked before sending.
  • Any model field with clear static choices accepts either a readable label or the model's exact value. This is not limited to fields called select.
  • Commands that require a file accept and send the required file ID.
  • Malformed, unsupported, unknown, or ambiguous model metadata stops before the request. The CLI does not guess.
  • Optional status checks follow the one command ID Xyte returned, on the same device. Both a command object and the documented one-item response list are handled.
  • If a send is interrupted and its result is unknown, resume does not send the command a second time.

Examples

  • If a model maps "33" to HDMI 1, entering HDMI 1 sends "33".
  • Entering "33" also sends "33".
  • A multi-value field can map ["Lobby", "Auditorium"] to that model's exact values.
  • A field declared as a number accepts 5 and rejects "5".
  • A file-backed command sends the provided file ID; it stops if the ID is missing.
  • No HDMI names, values, or vendor-specific mappings are hardcoded.

This is CLI-only. There are no server changes, new endpoints, or new workflows.

Also updated

The documentation, shipped CLI skill, endpoint guidance, and edge-case coverage now describe the same behavior.

The package and changelog are prepared for version 0.14.0. This PR does not publish anything. Only after approval and merge should the exact main commit be tagged v0.14.0; the existing release workflows will then publish the npm package and GitHub release assets.

Validation

  • The clean release check passed: install, typecheck, full tests, build, packaged-install smoke, controlled-upgrade smoke, and security audit.
  • Lint passed with zero errors.
  • Package inspection confirmed the compiled command code, documentation, and shipped skill are included, with no local run artifacts.
  • Live smoke on this branch used only the Playground tenant and a fake device; the command used the model's canonical value and its exact returned ID reached done.
  • Two disconnected final reviews found no remaining scoped issues.
  • GitHub CI passed on the release-prepared commit across macOS, Linux, and Windows.

Summary:
- Map select labels to canonical typed model values.
- Reject response-only params in command requests.
- Add opt-in exact-ID status polling to the device command flow.
- Keep endpoint metadata, documentation, and the shipped skill in sync.

Rationale:
- Honor model metadata generically.
- Stop on ambiguous option mappings before sending.
- Expose queue status without treating it as device execution proof.

Tests:
- npm run lint (0 errors; existing warnings only)
- npm test (1,079 passed, 2 skipped)
- npm run build
- 219 focused command, flow, catalog, and skill tests

Co-authored-by: Codex <codex@openai.com>
Copilot AI review requested due to automatic review settings August 2, 2026 07:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens correctness for device command execution by (1) mapping model-backed select inputs to the model’s canonical values before sending, (2) preventing misuse of response-only params in sendCommand requests (favoring extra_params), and (3) adding optional, exact-command-id status polling to the flow.device-command workflow. These changes strengthen deterministic behavior for both CLI users and agent-driven flows.

Changes:

  • Add model command select-option normalization/matching and enforce unambiguous mappings before sendCommand.
  • Reject request bodies that include response-only params (CLI-level guard) and document extra_params as the request surface.
  • Add opt-in command.poll task and wire it into flow.device-command to poll getCommands by the exact returned command id.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/model-command-options.test.ts Adds unit coverage for select-option extraction and matching behavior.
tests/flow-runner.test.ts Extends flow runner tests for select mapping, ambiguity blocking, and optional command status polling.
tests/endpoints.test.ts Validates endpoint catalog docs separate request extra_params from response params.
tests/command-poll.test.ts Adds unit coverage for extracting a single command id and polling by exact id to terminal status/timeout.
tests/cli.test.ts Updates CLI expectations for sendCommand request shape and ensures params is rejected pre-API-call.
src/workflows/model-command-options.ts Implements select-option normalization from model metadata and robust input matching (label/value).
src/workflows/flow-runner.ts Integrates select mapping into model validation and adds command.poll task execution + config validation.
src/workflows/flow-catalog.ts Registers command.poll task type and adds an optional polling step to flow.device-command.
src/workflows/command-poll.ts Implements exact-id polling via organization.commands.getCommands with bounded timeout/interval.
src/cli/commands/api.ts Adds a guard to reject response-only params in raw sendCommand request bodies.
src/api-catalog/public-endpoints.json Updates sendCommand docs example to use extra_params and clarifies params as response/history-only.
skills/xyte-cli/SKILL.md Updates shipped skill guidance for select mapping, extra_params, and optional polling behavior.
skills/xyte-cli/references/flow-recipes.md Documents optional polling knobs and select mapping for flow.device-command.
skills/xyte-cli/references/endpoints.md Clarifies request vs response fields and adds polling usage notes/examples.
skills/xyte-cli/data/public-endpoints.json Mirrors endpoint catalog changes in shipped skill data.
README.md Updates command-send guidance: select mapping, extra_params usage, and optional polling semantics.
docs/reference/commands.html Updates reference docs to reflect select mapping and extra_params/params distinction + polling example.
docs/reference/built-in-flows.html Updates built-in flow descriptions to include select mapping and optional polling.
docs/flows/agent-ops.md Updates operator guidance for flow.device-command, including polling and select mapping constraints.
docs/commands.md Updates raw API examples and documents extra_params/polling semantics for command operations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

portons and others added 7 commits August 2, 2026 12:13
Summary:
- send the model command with the API's command selector
- map single and multi choices to canonical model values
- harden malformed metadata, request bodies, and exact-id polling
- update docs, shipped skill, schemas, catalog, workflows, and tests

Rationale:
- model metadata names and send request fields have different contracts
- unavailable or ambiguous choices must never reach the send request

Tests:
- npm run typecheck
- npm run lint
- npm test
- npm run build
- npm run smoke:pack-install
- live Playground reboot flow and command-history check

Co-authored-by: Codex <codex@openai.com>
Summary:
- Treat mapped option keys as canonical values and reject conflicts.
- Fail closed when a value also matches another option label.
- Document the behavior and skill refresh guidance.

Rationale:
- Follow the server model metadata contract without numeric heuristics.
- Prevent ambiguous model options from sending the wrong value.

Tests:
- npm test -- --maxWorkers=1 (1163 passed, 2 skipped)
- npm run typecheck
- npm run build
- npm run smoke:pack-install
- Packaged Playground reboot accepted and exact ID found in history

Co-authored-by: Codex <codex@openai.com>
Summary:
- reject malformed string and incomplete command definitions before sending
- accept enum cardinality from model typeName across generic field types
- isolate command validation and polling from the generic flow runner
- share raw send-command request shape checks and cover selector edge cases

Rationale:
- command mapping must follow model metadata without assuming select-only fields
- malformed or ambiguous metadata must fail before the command request

Tests:
- npm run lint
- focused command and flow tests
- npm run release:check
- built CLI reboot smoke against the Playground tenant

Co-authored-by: Codex <codex@openai.com>
Summary:
- Validate shipped raw command examples against the send request
  contract.
- Verify skill refresh copies the complete bundle and repairs stale
  installs.
- Clarify generic option mapping for model-defined field cardinality.

Rationale:
- Prevent outdated selector and parameter guidance from reaching users.
- Keep the published skill aligned with fail-closed command validation.

Tests:
- npm run release:check
- ESLint on the changed test files (zero errors)

Co-authored-by: Codex <codex@openai.com>
Summary:
- Give the filesystem-heavy skill refresh integration test a local
  20-second timeout.

Rationale:
- The test completed in about eight seconds on Windows and exceeded
  Vitest's default five-second ceiling.
- Keep the larger parity assertions without weakening production
  behavior.

Tests:
- Targeted skill refresh integration test
- npm run release:check

Co-authored-by: Codex <codex@openai.com>
Summary:
- share model validation across command send paths, including declared
  types, static choices, required files, and exact poll provenance
- preserve the exact command request around uncertain delivery so resume
  cannot automatically send it again
- align documentation, shipped skill data, catalog notes, and edge tests

Rationale:
- command labels and canonical values must follow each device model without
  hardcoded vendor values or ambiguous coercion
- production retries must not duplicate a non-idempotent command when the
  original send may already have been accepted

Tests:
- npm run typecheck
- targeted Vitest command, flow, docs, and skill suites
- npm run lint
- env -u XYTE_CLI_KEY npm run release:check
- npm pack --dry-run --json

Co-authored-by: Codex <codex@openai.com>
@portons portons changed the title Fix model-backed command parameters and status polling Harden model-backed device commands and status checks Aug 2, 2026
Windows CI hit the same config integration test's five-second limit on two consecutive runs, while the affected command and flow tests passed. Give this filesystem-heavy test the existing twenty-second allowance used by other integration coverage; production behavior is unchanged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/workflows/model-command-options.ts:42

  • isOptionValue treats any non-empty string as valid even when it contains leading/trailing whitespace. That can allow whitespace-padded canonical option values (including map keys) to propagate into the value that gets sent, which is hard to notice and inconsistent with other command metadata handling that trims or rejects whitespace.
function isOptionValue(value: unknown): value is ModelCommandOptionValue {
  return (
    (typeof value === 'string' && value.trim().length > 0) ||
    typeof value === 'boolean' ||
    (typeof value === 'number' && Number.isFinite(value))

src/cli/commands/api.ts:111

  • validateSendCommandRequestBody currently catches "name"/"params"/invalid extra_params, but it still allows sendCommand bodies that omit both selectors (command/friendly_name) or provide an empty selector value. Since this command is being hardened to fail fast, it would be better to reject these cases locally with a clear error instead of sending a guaranteed-bad request to the API.
function validateSendCommandRequestBody(key: string, body: unknown): void {
  if (key !== 'organization.commands.sendCommand') return;
  const inspection = inspectSendCommandRequestBody(body);
  if (!inspection) return;

@portons
portons requested a review from borisd August 3, 2026 07:46
Summary:
- bump package metadata and lockfile to 0.14.0
- cut the 0.14.0 changelog and restore the 0.13.0 release entry

Rationale:
- prepare the tag-driven npm and GitHub release after approval and merge
- use a minor release for optional command polling plus command safety fixes

Tests:
- env -u XYTE_CLI_KEY npm run release:check (clean temporary snapshot)

Co-authored-by: Codex <codex@openai.com>
@portons
portons merged commit 84ad83a into main Aug 3, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants