Skip to content

feat(browser): copy/paste via real clipboard + type() anti-detect paste branch#8

Closed
iWedmak wants to merge 14 commits into
masterfrom
feature/type-anti-detect-paste
Closed

feat(browser): copy/paste via real clipboard + type() anti-detect paste branch#8
iWedmak wants to merge 14 commits into
masterfrom
feature/type-anti-detect-paste

Conversation

@iWedmak

@iWedmak iWedmak commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Tasks 4091, 4098, 4109 (python parity).

  • Browser.copy()Runtime.evaluate window.getSelection().toString().
  • Browser.paste(selector, text) — offscreen <textarea> + Ctrl+C → focus selector + Ctrl+V (real ClipboardEvent, inputType=insertFromPaste).
  • Browser.type(text, selector=...)len(text) > 500 AND random() < 0.625 → hotkey-paste; else per-char Ceki.typeText. Constants at module scope.

selector/text JSON-escaped. Private _hotkey_paste_into() helper shared by paste() and the type() branch (zero duplication).

Test plan

  • pytest regression guards
  • live smoke on headed Chromium (xclip readback + paste event)
  • reviewer: re-verify on real rental Chrome

iWedmak and others added 14 commits June 24, 2026 12:46
Backend create-contract-event now accepts explicit reviewer/qa objects
({type, value}); without them the event stays a clean Hand. Expose the
same surface on the CLI + SDK: matching shape to --benefitable
(agent:N / user:N), only forwarded into the payload when supplied.

Tests: regression cases for the contract client (reviewer-only, qa-only,
both, neither) plus a CLI parser dispatch case wiring all three through.
Backend moved role pivots into a single participants array of
{value, type, role_id} items (reviewer -> role 5, qa -> role 6).
Drop the old top-level reviewer/qa keys, keep --reviewer / --qa
on the CLI as the human-facing shortcut, and add a repeatable
--participant for arbitrary roles.

Version bumped to 2.27.0 - wire format is breaking.
`propose --desc` was being used as a progress report, but the backend
treats --desc as the event description — agents were silently wiping
the spec. Add a dedicated `progress` command that wraps a status
correction (optional --status) and a comment in one call, leaving the
event description untouched.

`propose` semantics are unchanged. SKILL.md updated to recommend
`progress` for status+report; `propose` stays for pure field
corrections.

Version bumped to 2.28.0.
Backend rejects comment events without a `label` ("The label field is
required"). The bare `progress` call hit this on first dogfood. Derive
a short label from the first line of --desc (capped at 60 chars); fall
back to "progress" if desc is empty. The full --desc still goes into
`description` untouched.
Backend rejects {value, type, role_id} with 422 — the wire shape per
EventController validation rules is {participable_id, participable_type,
role_id}. role_id mapping (reviewer=5, qa=6) is unchanged.

Unit tests previously asserted the wrong shape; regression guard
test_create_participants_uses_participable_id_keys() now pins the
correct keys. Patch bump to 2.28.1.
Backend renamed the role-attachment array field from `participants` to
`users` (sync with back/2542). Element shape stays
{participable_id, participable_type, role_id}. CLI flag --participant
keeps its name — it is the human-facing word; only the wire key moves.

Regression guard test_create_uses_users_field_not_participants pins the
field name. Minor bump to 2.29.0.
EventController validator accepts the short tokens `agent` / `user`,
but the downstream membership lookup compares the string against the
contract members' `participable_type` column which stores the
fully-qualified class name. Sending the short token bypasses validation
yet trips the misleading 422 "Participant must be a member of the
contract".

Send `App\\Models\\Agent` / `App\\Models\\User` instead. Tests follow.
Patch bump to 2.29.1.
The create-contract-event MCP tool schema declares the element as
{participable_id, type, role_id} — `type` is the short token
'agent' / 'user'. Sending `participable_type` (FQCN) is silently dropped
by the MCP layer, which collapses the type to user and trips the
misleading 422 "Participant must be a member of the contract".

Live smoke (contract 43, --reviewer agent:10 --qa agent:12) now returns
event.id=2587 with the two role pivots materialized server-side
(participants role_id 5 + 6).

Patch bump to 2.29.2.
The web app renders both `label` and `description` on a comment event,
but the human-typed path only ever writes to `label`. The SDK derived
a 60-char `label = desc[:60]` AND also sent the full text as
`description`, producing a visible duplicate in the UI.

events.label is unbounded TEXT — long multi-line reports go in there
whole. `comment()`, `progress()`, and `--desc` on the comment CLI all
write the FULL body to `label` and never set `description`.

Minor bump to 2.30.0.
…asks

Backend swapped:
  get-my-jobs  (contract tasks)       → get-my-events
  get-hire-jobs (posted hire jobs)    → get-my-jobs

Consumer-side rename: ContractClient.my_events() pulls contract tasks
via get-my-events; ContractClient.my_jobs() now pulls posted hire
schedules via the (newly-reused) get-my-jobs wire name. CLI subcommands
match. Regression guards pin both wire names.

Minor bump to 2.31.0.
Thin wrapper around the existing /mcp/agent call-human tool: escalate
an event to a human up the event→parent→contract→schedule chain,
delivered via telegram + push + in-app.

CLI: `ceki contract call-human <eid> --kind input|review|stuck --desc "..."`
SDK: `ContractClient.call_human(event_id, kind, desc)`

kind is client-side validated against the enum before we hit the wire.
Minor bump to 2.32.0.
Two new browser-control methods layered on the existing CDP passthrough:
  - copy() returns window.getSelection().toString() via Runtime.evaluate
  - paste(selector, text) focuses the input via querySelector(...).focus()
    then dispatches Input.insertText, so controlled React/Vue inputs see
    the update

selector is JSON-escaped — safe against quotes / backticks / newlines /
unicode.

Minor bump to 2.33.0.
The insertText-based impl from the previous release did NOT touch the
OS clipboard — it was direct DOM insertion. Rewrite copy() and paste()
to dispatch synthetic Ctrl+C / Ctrl+V via Input.dispatchKeyEvent so
the OS clipboard is actually engaged and controlled inputs see a
real ClipboardEvent with inputType=insertFromPaste.

Arbitrary-text paste stages the text through an offscreen <textarea>
+ Ctrl+C to seed the clipboard, then focus target + Ctrl+V.
selector + text are JSON-escaped in the Runtime.evaluate expressions.

Verified against a real headed Chromium: OS clipboard flipped
(xclip readback matches), paste event fires with inputType=insertFromPaste.

Minor bump to 2.34.0.
Perfect per-key rhythm on a long string is a classic bot signal.
Route type(text, selector=...) through the real-clipboard paste path
(from the previous release) when the input passes both gates:

  len(text) > TYPE_PASTE_MIN_CHARS (500) AND random() < TYPE_PASTE_PROBABILITY (0.625)

Short text, no-selector calls, and the ~37.5% "keep humanized" case
stay on the existing per-char Ceki.typeText path. paste() and the new
type() branch share a private _hotkey_paste_into() helper — zero
duplication.

Minor bump to 2.35.0.
@iWedmak iWedmak closed this Jul 6, 2026
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