feat: add human-like smooth typing with optional typo injection#201
feat: add human-like smooth typing with optional typo injection#201ulziibay-kernel wants to merge 2 commits intomainfrom
Conversation
Adds smooth typing mode to POST /computer/type that types text in word-sized chunks with variable intra-word delays and natural inter-word pauses via xdotool, following the same Go-side sleep pattern as doMoveMouseSmooth. Optionally injects realistic typos (adjacent-key, doubling, transpose) using geometric gap sampling (O(typos) random calls, not O(chars)) with QWERTY adjacency lookup, then corrects them with backspace after a "realization" pause. New API fields on TypeTextRequest: - smooth: boolean (default false) - enable human-like timing - typo_chance: number 0.0-0.10 (default 0) - per-char typo rate New package: server/lib/typinghumanizer with word chunking, QWERTY adjacency map, and typo position generation. Made-with: Cursor
- typing_demo.html: Dark-themed page with a textarea and keystroke timing visualization (bar chart of inter-key intervals) - demo_smooth_typing.py: Records three phases (instant, smooth, smooth+typos) via the kernel API for comparison GIF/MP4 Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| correctText = string(chunkRunes[typoLocalPos:]) | ||
| } else { | ||
| correctText = string(chunkRunes[typoLocalPos:]) | ||
| } |
There was a problem hiding this comment.
Identical if/else branches suggest missing transpose correction logic
Medium Severity
Both branches of the if typo.Kind == typinghumanizer.TypoTranspose conditional produce identical results: string(chunkRunes[typoLocalPos:]). This strongly suggests the developer intended different correction text for the transpose case but forgot to implement it, resulting in dead conditional logic. Since transpose types two characters (the swapped pair) and backspaces two, the correction re-types both from typoLocalPos: which happens to be correct — but the redundant branch hints at an incomplete implementation or a copy-paste oversight.
| sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) | ||
| return | ||
| } | ||
| request.Body = &body |
There was a problem hiding this comment.
Codegen downgrade breaks endpoints accepting empty request bodies
High Severity
The oapi-codegen version downgrade from v2.6.0 to v2.5.1 removed the io.EOF graceful handling for empty request bodies in the generated middleware for TakeScreenshot, DeleteRecording, StartRecording, and StopRecording. Previously, empty bodies were allowed and request.Body was left nil. Now, any empty body triggers an error response. The TakeScreenshot handler explicitly handles request.Body == nil to allow bodyless calls, so this is a breaking regression — clients calling the screenshot endpoint without a JSON body will get an error instead of a full-screen capture.
jarugupj
left a comment
There was a problem hiding this comment.
Really cool stuff, the typinghumanizer package is super clean. The realization pause before backspace is a great detail, and the typo types cover the common cases really well -- adjacent key, doubling, transpose, extra char feels like it captures how people actually mess up.
One small thing I noticed -- in typeChunkWithTypo, the transpose branch and the else branch do the same thing:
if typo.Kind == typinghumanizer.TypoTranspose && typoLocalPos+1 < len(chunkRunes) {
correctText = string(chunkRunes[typoLocalPos:])
} else {
correctText = string(chunkRunes[typoLocalPos:])
}Could probably just be correctText = string(chunkRunes[typoLocalPos:]) without the if/else?


Summary
smoothandtypo_chancefields toPOST /computer/typefor human-like typing via xdotoolsmooth=true, text is typed in word-sized chunks with variable intra-word delays ([50, 120]ms) and inter-word pauses ([80, 200]ms, 1.5x at sentence boundaries)typo_chanceis set (0.0-0.10), realistic typos are injected using geometric gap sampling (O(typos) random calls, not O(chars)) and corrected with backspace after a "realization" pauseNew package:
server/lib/typinghumanizerSplitWordChunks-- word chunking with trailing delimiters attached to preceding wordUniformJitter-- random duration in a range, clamped to minimumIsSentenceEnd-- sentence boundary detection for longer pausesAdjacentKey-- QWERTY neighbor lookup from static[26][]bytearray, O(1)GenerateTypoPositions-- geometric gap sampling for typo placementTypo types (weighted distribution)
Related: #169 (plan document)
Demo
Test plan
smooth: trueon a running instancesmooth: true, typo_chance: 0.03to verify typo/correction behaviorMade with Cursor
Note
Medium Risk
Medium risk: changes
POST /computer/typebehavior by adding a new smooth-typing execution path with randomized delays/typos, which can affect timing-sensitive consumers and adds more xdotool subprocess calls. Regenerates OpenAPI client/server code, so request/response binding semantics may shift subtly (e.g., JSON body decoding and optional field handling).Overview
Adds a new human-like typing mode to
POST /computer/typeviasmoothandtypo_chance, typing text in word-sized chunks with variable delays and optional injected typos that are corrected with backspaces.Introduces
server/lib/typinghumanizer(with tests) to handle chunking, jitter, sentence-end pauses, adjacent-key selection, and typo position generation. Regenerates OpenAPI artifacts to expose the new request fields and updates demo assets (demo_smooth_typing.py+typing_demo.html) for recording/visualizing keystroke timing.Written by Cursor Bugbot for commit 025b75a. This will update automatically on new commits. Configure here.