Skip to content

feat: add human-like smooth typing with optional typo injection#201

Open
ulziibay-kernel wants to merge 2 commits intomainfrom
ulziibay-kernel/smooth-typing
Open

feat: add human-like smooth typing with optional typo injection#201
ulziibay-kernel wants to merge 2 commits intomainfrom
ulziibay-kernel/smooth-typing

Conversation

@ulziibay-kernel
Copy link
Copy Markdown
Contributor

@ulziibay-kernel ulziibay-kernel commented Apr 3, 2026

Summary

  • Adds smooth and typo_chance fields to POST /computer/type for human-like typing via xdotool
  • When smooth=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)
  • When typo_chance is 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" pause

New package: server/lib/typinghumanizer

  • SplitWordChunks -- word chunking with trailing delimiters attached to preceding word
  • UniformJitter -- random duration in a range, clamped to minimum
  • IsSentenceEnd -- sentence boundary detection for longer pauses
  • AdjacentKey -- QWERTY neighbor lookup from static [26][]byte array, O(1)
  • GenerateTypoPositions -- geometric gap sampling for typo placement

Typo types (weighted distribution)

Type Weight Mechanism
Adjacent key 60% QWERTY neighbor substitution
Doubling 20% Character typed twice
Transpose 15% Swap with next character
Extra char 5% Random adjacent key inserted

Related: #169 (plan document)

Demo

smooth_typing_demo

Test plan

  • All 17 typinghumanizer tests pass (word chunking, adjacency, typo generation, distribution)
  • Builds clean, no lint issues
  • Manual test with smooth: true on a running instance
  • Manual test with smooth: true, typo_chance: 0.03 to verify typo/correction behavior

Made with Cursor


Note

Medium Risk
Medium risk: changes POST /computer/type behavior 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/type via smooth and typo_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.

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
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

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:])
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
return
}
request.Body = &body
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@jarugupj jarugupj left a comment

Choose a reason for hiding this comment

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

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?

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.

2 participants