feat(browser): copy/paste via real clipboard + type() anti-detect paste branch#8
Closed
iWedmak wants to merge 14 commits into
Closed
feat(browser): copy/paste via real clipboard + type() anti-detect paste branch#8iWedmak wants to merge 14 commits into
iWedmak wants to merge 14 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Tasks 4091, 4098, 4109 (python parity).
Browser.copy()—Runtime.evaluatewindow.getSelection().toString().Browser.paste(selector, text)— offscreen<textarea>+ Ctrl+C → focusselector+ Ctrl+V (realClipboardEvent,inputType=insertFromPaste).Browser.type(text, selector=...)—len(text) > 500ANDrandom() < 0.625→ hotkey-paste; else per-charCeki.typeText. Constants at module scope.selector/text JSON-escaped. Private
_hotkey_paste_into()helper shared bypaste()and the type() branch (zero duplication).Test plan