feat(computer): add --char-delay for lossy keyboard relays (#262)#289
Conversation
type-text hardcoded a 4ms inter-character sleep. Into a Parallels/VM guest's keyboard relay, a 28-char stream dropped everything after char 11 under a transient focus blip. Add an optional per-character delay so callers can slow the stream for lossy relays. - Daemon (Events.typeText): parse optional char_delay_ms, clamp to [1, 250]ms, default 4 (backward compatible); replace the hardcoded 0.004 with Double(charDelayMs)/1000.0. - CLI (computer type-text): --char-delay <ms>, parsed to int, clamped CLI-side (defense in depth) and forwarded as char_delay_ms. - Tests: clampCharDelay unit tests (default/floor/ceiling/boundary). Verified end-to-end driving the freshly-built daemon into TextEdit: char_delay_ms=1 -> 34ms, =250 -> 4530ms, =5000 -> 4533ms (clamped to 250). The 10-run Parallels-guest acceptance needs the reporter's VM. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code ReviewerVerdict: Ready to merge Build: Instructions read
What works wellDual-side clamp is the right design. The CLI-side Backward compatibility is provably preserved. When
Test coverage is comprehensive. The 6 One tradeoff worth documenting (not a blocker)At Things to verify manuallyThe 10-run Parallels Windows 11 acceptance (28+ chars, Reviewed by Code Reviewer — actually ran the build and tests on this branch. |
Closes #262.
Problem
Events.typeTexthardcoded the inter-character sleep atThread.sleep(forTimeInterval: 0.004). During the #258 acceptance run against a Parallels Windows 11 guest, a 28-chartype-textstream dropped everything after char 11 mid-stream (daemon reportedfrontmost: true,ok: true— the drop happened inside the guest's keyboard relay). A slower inter-char rate recovers it.Change (3 surfaces)
packages/computer-helper/Sources/ComputerHelper/Events.swift: parse optionalchar_delay_msfrom thetype_textRPC params (via the existingParams.intOpthelper), clamp to[1, 250]ms, default stays 4ms (backward compatible). ReplacedThread.sleep(forTimeInterval: 0.004)withThread.sleep(forTimeInterval: Double(charDelayMs)/1000.0).src/commands/computer-actions.ts: added--char-delay <ms>toagents computer type-text, parsed to int, clamped CLI-side via the new pureclampCharDelayhelper (defense in depth) and forwarded aschar_delay_msin the RPC payload. Returnsundefinedwhen unset so the daemon applies its own 4ms default.min(250, max(1, … ?? 4)); CLIclampCharDelaymirrors[1, 250]and truncates fractional input.Tests
src/commands/computer-actions.test.ts— 6 newclampCharDelaycases (default/unset, typical 25, floor, ceiling, boundaries, fractional+NaN). Full file: 36 tests pass. No Swift XCTest target exists inPackage.swift, so the clamp is covered CLI-side plus the real-flow timing run below.Verified
swift build(daemon) — compiles clean (one pre-existing unrelated deprecation warning).bun run build(TS) —tscpasses.bunx vitest run src/commands/computer-actions.test.ts— 36/36 pass.agents computer type-text --helprenders--char-delay <ms>.trust_status: trusted:true) into a focused TextEdit, 18-char string:char_delay_ms=1→ 34mschar_delay_ms=250→ 4530ms (≈ 18 × 250)char_delay_ms=5000→ 4533ms — clamped to 250 (would be ~90s unclamped){"ok":true,"chars":18,"frontmost":true}.This proves parse + clamp + delay-applied through the new daemon code.
Needs the reporter's VM
The issue's acceptance criterion — ~10 consecutive 28+-char
type-textruns into a Parallels guest with--char-delay 25delivering without drops — requires the reporter's Parallels Windows 11 guest. That is not reproducible on this host; the drop is inside the guest's keyboard relay. The mechanism (slower inter-char rate via--char-delay) is implemented and verified locally.Follow-up (separate repo)
The skill-doc note — recommending
--char-delay 25(or higher) for lossy relays / VM guests inskills/computer/SKILL.md— lives in the.agents-systemrepo (gh:phnx-labs/.agents-system), not this one. Tracked as a follow-up there; intentionally not touched here.