Skip to content

Stop mistaking a filled-in form for a payment, and stop refusing a browser step while it saves - #836

Merged
davidmckayv merged 5 commits into
mainfrom
david/typed-text-safety-and-saved-call-race
Oct 25, 2026
Merged

davidmckayv merged 5 commits into
mainfrom
david/typed-text-safety-and-saved-call-race

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

Two bugs that stopped a Bot filling in a form in its browser. Both showed up running a Bot through a CRM's New account form end to end; each fix has a regression test that fails on main and passes here.

Filling in a form was mistaken for paying. safetyRequirement read a computer_type call's typed text along with its target, so the payments rule matched words in the content. A note about a customer's "purchase orders", or a source field reading "Business Wire", was handed back to the person with "Paying, purchasing or moving money is always done by the person", mid-form. Typed text is content, the way reading a checkout page is already not paying: for a typed entry the rules now read the field's name, the page address and the button, not the words typed. A "Card number" field, a checkout page and a "New password" field still go to the person whatever is typed. Separately, a news wire (business, news or press wire) on a link or button no longer counts as wiring money; "Wire the deposit", "Send a wire" and "wire transfer" still do.

A browser step was refused while it was being saved. Before the browser runs a computer action, the server checks the call against the conversation's saved copy, waiting up to five seconds for it to appear. Intelligence saves a call's arguments delta by delta, behind the stream, and the wait stopped as soon as the call appeared, so a read that found half the arguments refused the step ("That unanswered tool call is not saved in this conversation"), or threw when the half did not parse, which the route answers as "Supply a valid request". The Bot then stopped partway or asked for its approval a second time. The check, lifted into savedToolCall, now waits until the saved call parses and matches, within the same five seconds, and still refuses one that never does.

Tests

  • approvals-policy.test.ts: typing judged by its field and page, on the candidate shape the computer records; a news wire is not a payment, wiring money still is. The assertion-shape allow-list follows the existing entry the new tests moved down.
  • approvals-saved-call.test.ts (new): the saved copy read half saved, then whole, is matched; a call that never appears, never matches or never finishes saving is refused; one with a result is reported as answered. Against the old logic the first case throws JSON Parse error, the route's "Supply a valid request".

Verified in a real browser: with both fixes a Bot created five CRM accounts (every field, Notes and Source included) without a hand-off or a refused step, across repeated runs.

safetyRequirement read a computer_type call's typed text along with its target, so the payments rule matched words in the content: a CRM note about a customer's "purchase orders" or a source field reading "Business Wire" was handed back to the person as a money movement, in the middle of filling in a form. Typed text is content, as reading a checkout page is already not paying; the field's name, the page address and the button say what the action is, so those are what the rules read for a typed entry. A news wire (business, news or press wire) no longer counts as a wire of money anywhere else either.
…ment

On the candidate shape the computer records (the typed text in args, the field and page in target): notes about purchase orders and payments, and a Business Wire source, are not handed off; a card number field, a checkout page and a new password field still are, whatever is typed. A link to a release on Business Wire is not a payment; "Wire the deposit", "Send a wire" and "Start a wire transfer" still are. The assertion-shape allow-list follows the existing entry the new tests moved down.
Before the browser runs a computer action, the server checks the call against the conversation's saved copy, waiting up to five seconds for it to appear. Intelligence saves a call's arguments delta by delta, behind the stream, and the wait stopped as soon as the call appeared: a read that found half the arguments refused the action ("That unanswered tool call is not saved in this conversation"), or threw when the half did not parse, which the route answers as "Supply a valid request". The check, lifted into savedToolCall, now waits until the saved call parses and matches, within the same five seconds, and still refuses one that never does.
The saved copy is read as Intelligence returned it part-way through saving a click: half the JSON, then all of it but the last key, then whole. The call is matched rather than refused. A call that never appears, never matches, or never finishes saving is still refused once the wait runs out, and one that already has a result is reported as answered.
@davidmckayv
davidmckayv merged commit ef13020 into main Oct 25, 2026
19 checks passed
@davidmckayv
davidmckayv deleted the david/typed-text-safety-and-saved-call-race branch October 25, 2026 22:26
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