Skip to content

feat(devices): harden devices command for agent-safe control (2.3.0)#14

Merged
chenliuyun merged 2 commits intomainfrom
feat/agent-safe-control
Apr 20, 2026
Merged

feat(devices): harden devices command for agent-safe control (2.3.0)#14
chenliuyun merged 2 commits intomainfrom
feat/agent-safe-control

Conversation

@chenliuyun
Copy link
Copy Markdown
Collaborator

Summary

Closes four gaps that blocked agents from safely issuing complex control commands through devices command. All fixes ship together in 2.3.0 (minor).

  • Raw-parameter validation: setAll (Air Conditioner), setPosition (Curtain / Blind Tilt), and setMode (Relay Switch) now validate the wire-format string client-side. Malformed shapes ('on,2,2,30'), empty strings, wrong field counts, JSON where CSV is expected — all fail fast with exit 2 instead of being forwarded to the API.
  • Case-only command normalization: devices command BOT turnon and TurnOn now emit a stderr warning and continue with the canonical turnOn. --json output reports the canonical name in data.command. Genuinely unknown commands (foobar) still exit 2 with the supported-commands list.
  • --name + positional args: devices command --name '家里空调' setAll '26,2,2,on' no longer throws "provide either deviceId or --name, not both" — positional shift is recognized.
  • intArg range errors: --max -1 now classifies as "must be >= 1" instead of "requires a numeric value". --max --help still reports the flag-like error.

Why 2.3.0 (minor)?

Case-only command names that previously exited 2 now exit 0. That is a UX-positive behavior change but still a behavior change — hence minor bump, not patch. No previously-successful invocation changes behavior.

Refactor

Extracted the 4 semantic-flag builders (buildAcSetAll / buildCurtainSetPosition / buildBlindTiltSetPosition / buildRelaySetMode) out of src/commands/expand.ts into a new shared module src/devices/param-validator.ts. validateParameter() (the raw-string validator consumed by devices command) lives alongside them and dispatches by (deviceType, command) prefix — unknown combos pass through so the command stays a usable escape hatch.

Test plan

  • npm run build — clean
  • npm test — 766 tests pass (43 files)
  • New: tests/devices/param-validator.test.ts — 20+ cases covering all 4 builders + validateParameter dispatch + passthrough
  • New: 11 cases in tests/commands/devices.test.ts covering raw-param validation, case normalization, --name + positional shift
  • Updated: tests/utils/arg-parsers.test.tsintArg pure-negative-integer path
  • Manual smoke: switchbot --dry-run devices command --name 'Living' setAll '30,2,2,on' against real cache
  • Manual smoke: switchbot devices command <botId> turnon — verify warning emitted

chenliuyun added 2 commits April 20, 2026 09:08
… module

Lift buildAcSetAll/buildCurtainSetPosition/buildBlindTiltSetPosition/buildRelaySetMode out of expand.ts into a new src/devices/param-validator.ts. No behavior change — expand.ts imports the same builders from the shared module.

Prepares the ground for validateParameter() to be consumed by devices command in the next commit.
Closes the gaps that blocked agents from safely issuing complex control
commands via devices command:

- Raw-parameter validation: setAll (AC), setPosition (Curtain / Blind
  Tilt), and setMode (Relay Switch) now validate the wire-format string
  client-side via validateParameter(). Malformed shapes, out-of-range
  values, and JSON payloads where CSV is expected fail fast with exit 2
  instead of being forwarded to the API.
- Case-only command-name mismatches (turnon, TurnOn) are normalized to
  the canonical name with a stderr warning and the request proceeds.
  Genuinely unknown commands still exit 2 with the supported-commands
  list. --json output reports the canonical name in data.command.
- devices command --name <query> <cmd> [parameter] now correctly shifts
  the positional args instead of throwing 'provide either deviceId or
  --name, not both'.
- intArg no longer rejects pure negative integers up-front; they fall
  through to the min/max check so --max -1 classifies as a range error
  ('must be >= 1') rather than 'requires a numeric value'.

Version bumps from 2.2.1 to 2.3.0 (minor) because the case-normalize
behavior changes the exit code of previously-erroring invocations (2
to 0). No breaking changes to previously-successful paths.

README: adds one paragraph under 'Parameter formats' documenting the
new client-side validation and case normalization behavior.
@chenliuyun chenliuyun merged commit 4e26c15 into main Apr 20, 2026
3 checks passed
@chenliuyun chenliuyun deleted the feat/agent-safe-control branch April 20, 2026 02:17
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
C1 (#14): update --idempotency-key / --idempotency-key-prefix help text
  in devices.ts and batch.ts to mention process-local scope, per-process
  cache semantics, and that independent CLI invocations do not share cache.

C2 (#15): mcp --help "eight tools" → "eleven tools"; list all 11 by name
  including get_device_history, query_device_history, aggregate_device_history.

C3 (#17): add `scenes describe <sceneId>` subcommand. Returns sceneId,
  sceneName, stepCount:null, and a note explaining v1.1 API limitation.
  Exits 2 with scene_not_found + candidate list on unknown sceneId.
  Adds 'scenes describe' to COMMAND_META in capabilities.ts.
  Adds 2 tests (known + unknown scene).

C4 (#13): add JSDoc comment on hints field in agent-bootstrap.ts clarifying
  empty array semantics. Add 'hints' field to cliAddedFields in schema.ts.

C5 (#16): create docs/verbose-redaction.md documenting masked headers
  (authorization, token, sign, nonce, x-api-key, cookie, set-cookie,
  x-auth-token, t) and the --trace-unsafe opt-out flag.

C6 (#18): plan schema output now includes agentNotes.deviceNameStrategy
  documenting that deviceName uses require-unique resolution and plans
  should pin deviceId for determinism.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
Document every fix landed in this branch beyond the history-aggregate
feature: bugs #1, #4, #5, #6, #8, #9, #10, #11, #12, #13, #14, #15,
#16, #17, #18 from the OpenClaw v2.4.0 smoke-test report. Call out
the deferred items (#2, #7) explicitly so readers don't assume they
were overlooked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
C1 (#14): update --idempotency-key / --idempotency-key-prefix help text
  in devices.ts and batch.ts to mention process-local scope, per-process
  cache semantics, and that independent CLI invocations do not share cache.

C2 (#15): mcp --help "eight tools" → "eleven tools"; list all 11 by name
  including get_device_history, query_device_history, aggregate_device_history.

C3 (#17): add `scenes describe <sceneId>` subcommand. Returns sceneId,
  sceneName, stepCount:null, and a note explaining v1.1 API limitation.
  Exits 2 with scene_not_found + candidate list on unknown sceneId.
  Adds 'scenes describe' to COMMAND_META in capabilities.ts.
  Adds 2 tests (known + unknown scene).

C4 (#13): add JSDoc comment on hints field in agent-bootstrap.ts clarifying
  empty array semantics. Add 'hints' field to cliAddedFields in schema.ts.

C5 (#16): create docs/verbose-redaction.md documenting masked headers
  (authorization, token, sign, nonce, x-api-key, cookie, set-cookie,
  x-auth-token, t) and the --trace-unsafe opt-out flag.

C6 (#18): plan schema output now includes agentNotes.deviceNameStrategy
  documenting that deviceName uses require-unique resolution and plans
  should pin deviceId for determinism.
chenliuyun pushed a commit that referenced this pull request Apr 20, 2026
Document every fix landed in this branch beyond the history-aggregate
feature: bugs #1, #4, #5, #6, #8, #9, #10, #11, #12, #13, #14, #15,
#16, #17, #18 from the OpenClaw v2.4.0 smoke-test report. Call out
the deferred items (#2, #7) explicitly so readers don't assume they
were overlooked.
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.

1 participant