Repository navigation
Record a flow in Chrome, narrated, straight into Intelligence with the OpenBot Recorder - #809
Draft
davidmckayv wants to merge 278 commits into
Draft
davidmckayv wants to merge 278 commits into
davidmckayv wants to merge 278 commits into
Conversation
…the cloud browser and the extension
…eard ports in the Recorder's chrome fake
…in the Recorder's chrome fake
…fake only when Chrome would
…n OpenBot's origin
…nd Learning space check
…the release tree, so the check accepts the Recorder extension's package.json and still fails when a workspace's manifest is missing
… shared length, the longest that fits, so the larger body is cut first and a small response survives a large upload whole instead of being emptied before the request is touched
…er instead of the offscreen document calling chrome.runtime.getManifest, which Chrome does not give an offscreen document, so kept recordings are sent again after a reconnect or a browser restart; the chrome fake now gives each context its own chrome, the offscreen document only runtime messaging, getURL and id, and a new test runs the real service worker and document together through both paths
… still stands and end the recording through the sign-out path on a revoked or signed-out answer, so a browser revoked in OpenBot's Settings stops streaming within 30 s instead of at the next popup open or 12-hour keep-alive; an unreachable OpenBot or an error page keeps the recording going, and a restarted service worker resumes the checks
…n in ten minutes so an ordinary connect, reconnect, Disconnect and revoke run never meets it, and answer a connect over the limit as throttled with the time left instead of as busy, so Settings says to try again in so many minutes rather than that another connect is under way
… number or typed secret in spoken numbers, which speech recognition writes with commas, dashes or a currency sign, then runs the usual text masking
…he offscreen document opens the microphone and runs the browser's own speech recognition on it, each final phrase goes into the recording as a masked transcript event through the same outbox and stream, and Stop, Disconnect, a sign-out or a policy move ends it with the recording; OpenBot receives nothing of it
…itle says it is listening with the last words heard, or why it is not in a plain sentence, with a switch to mute it; the first Record opens a microphone page once to ask for the lasting grant the hidden document needs, and Record sends the page's language
…cript now redacts every spoken run of six digits or more whatever its checksum (a date said in full excepted), and every number after a word that names a secret (card, PIN, CVV, account, password and the like) however short, including in the phrase after it, since Chrome splits a phrase at a pause; the popup's interim words go through the same masking with the phrase before them, so a card number read aloud no longer reaches Intelligence or the Voice row as "4012-888888-1881" or "4012-8"
…no words: as Edge does, recognition starts, hears speech and raises no error while every final phrase comes back empty, so the row said Listening forever; now, 20 s after speech is first heard with no phrase in words, the row says this browser is not turning your speech into words and recording carries on without narration, keeps listening, and drops the sentence when a real phrase arrives
… Record records: website access is now a required host permission that the install grants (the debugger permission already implies every site, so Chrome and Edge show the same install warnings and an update is no privilege increase), and a fresh install opens one setup page that asks for the microphone as it loads, with a button to ask again and the answer in plain words, worded for Chrome's or Edge's prompt; the access page, the website-access prompts at Connect and at Record, and the microphone tab the first Record opened are gone, Record never opens a tab, and a recording without the microphone goes on while its Voice row says the microphone is not allowed and offers the setup page on a click
…s joins no Learning space: the popup said Learning may not see it whenever OpenBot could not check the Bot's space with Intelligence (Learning delivery off, its skills endpoint answering 404, or Intelligence not answering), although the pass still joins the Bot's container and the trajectory lands in it; one shared rule, joinedLearningContainer, now decides both which container the server's selector joins and when the popup warns, so a Bot with no space, a container Intelligence says does not exist, or settings OpenBot could not read still warn, and after Stop an unchecked space says the recording goes into the Bot's Learning space
…s under the sentence, as on the popup's Voice row, instead of squeezing it, the sentence keeps the muted colour the popup uses for the same state, and how to answer the prompt shows only while there is a prompt to answer
…one-time-code is now sensitive by its id, name, placeholder, label or aria-labelledby text, test id, class or short numeric shape (zip, postal, promo, coupon and country codes excepted), so its value, every attribute that echoes it and its digest are masked; verificationCode, otp, mfa_code, smsCode, authCode and like keys are credentials in bodies and URLs, and a bare code is redacted when it holds 4 to 10 digits or sits on a sign-in, verification or token request, as are code and state on an OAuth callback, so a recorded sign-in no longer sends its code to Intelligence in the input event or the request body
…s under one parent, wrapper or form (or numeric boxes side by side in one parent) is one sensitive code field though no box has a label, so every box's value is redacted in its input events, and the code the boxes make together, in order, is hashed on each input, after a paste the page spreads itself and when a box loses focus, so it is masked in any body; a lone numeric one-character box is sensitive on its own too
…harness catalogue paths and the Recorder's extension id file
davidmckayv
requested review from
MikeRyanDev,
guidovizoso,
mxmzb and
tylerslaton
as code owners
October 19, 2026 17:57
davidmckayv
marked this pull request as draft
October 19, 2026 18:18
…, XHR or top-frame document request that starts within a second of a click, edit, submit or key press in that tab, or that the response of such a call starts in turn (two links, three seconds at most), on both the debugger and the page-hook path; analytics, telemetry, ads and monitoring endpoints, prefetches, aborted calls, a call the tab already made (polling), and anything the page starts while the person does nothing are dropped before any body is read. The content script reports each action's time (never its detail) for this, and records a form submit and Enter in a field as their own events, masked like a click. Replayed on two real recordings of a CRM this halves the network events and drops every beacon, while the calls behind each click (a contact loading, a question sent to the assistant) stay.
…y, once per address: an iframe loading or rewriting its own address, and a page Chrome prerenders out of sight, are the page's doing and no longer recorded, and a single-page app setting the same address again and again records it once (a reload is recorded again, as is the reloaded page). On the two real CRM recordings this takes navigation events from 94 to 18 and from 47 to 11.
…rder: every recording a person makes joins their own Intelligence Learning container (user- and 32 hex digits of the SHA-256 of their OpenBot user id), made on first need with the credential that makes the openbot container (a service token on local evaluation, or a CLI login on managed Intelligence), when the browser connects and again on a capture pass, idempotently across replicas because Intelligence's container id is unique; the popup loses its Bot picker, the x-openbot-recorder-bot header, canRecordForBot, the no-Bot and Bot-forbidden refusals and /api/recorder/bots go, recorder_trajectory_bindings binds a trajectory to its person only (another person's second pass is not found), and the warning that Learning will not see a recording shows only when the person's space cannot be made or reached
…nd Settings, Learning shares them: a run gets the Bot's own learned Skills, then the person's own, then the Skills other people opened, the Bot's own winning a name clash and then the person's own, with two people's opened Skills of one name both kept as <their space>/<name>; a person's or an opened space that is missing, refusing or slow never fails a run and is left alone by runs for a minute; each person chooses only their own Bots (the default), every Bot, or the Skills they open one by one, stored in learning_skill_sharing and learning_open_skills, read by runs from an in-memory index that the saving replica refreshes at once and the others within 30 seconds, listed from the same Intelligence call delivery uses, with an unavailable line when Intelligence does not deliver them, and audited as learning.sharing_changed, learning.skill_opened and learning.skill_closed
… secret: the masking tests searched the whole serialised event for a secret's digits, so CI failed the 2 MiB form test when Date.now() read 1792433411150 and its "4111" matched the test card though the body was masked in full. Leak checks now search recordedText, the event with its timestamp, completedAt and durationMs set to 0 (and the page test's server port left out), which throws on any other clock-sized number so a new clock field cannot slip past; the tests that search events pin Date.now() to a reading holding their secrets' digits, so a check that read the clock again fails on every run
…ch click, edit, choice in a list, box ticked, form submit, Enter in a field and top-frame page change, in order, beside the narration and the outcome, so an agent can be trained to do the same clicks and enter the same data. Each step carries what an agent needs to find that element again (its accessible role and name, input type, a ranked list of stable selectors from test attributes and hand-made ids down to a short CSS path, never a generated id or a class name, its frame path inside an iframe, the page and its title, its box and the viewport), built in the page before masking so the masking covers it. No network call is recorded on its own: a step that saved something carries the one request it sent to save it (a PUT, PATCH, DELETE or GraphQL mutation, or a POST that carries what was typed or chosen or names a write; a configuration read through a POST, a query, telemetry and polling never), masked and cut to fit, the step held until its window closes or the next step starts and then sent with the request in place, steps in the order they came, on the debugger and the page-hook path alike.
…ecords each step you take (clicks, typing and the pages you open) with what it saves, and what you say; the Network row reads Capturing what each step saves (from inside the page when Chrome refused its debugger); the changelog says the same.
…hose path or operation names presence, a heartbeat, a ping, a keepalive, typing, a session refresh, telemetry, tracking or a log is never a step's save, nor is a read receipt the page posts when it shows a conversation (a PUT that marks a notification read stays the person's), so opening an assistant carries nothing; and page changes the page makes one after another, with no step of the person's between them and each within 2 s of the last (a sign-in's redirects, an app settling on its default view), are one navigate step to the final page, from the address the chain began at, while a reload or a step back is always its own.
…inside a button, link, menu item, tab, option, checkbox or another element a person acts on takes that element's role, name and selectors, keeping the box of what was clicked, and a click that meets none (a panel's empty space or text) names the clicked element by its own first 60 characters; every name read from text keeps a space between its pieces, so "Sign in" and "or create an account" no longer run together, and text a page hides from everyone is left out.
This branch has not been deployed
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.
What this changes
Adds the OpenBot Recorder, a Manifest V3 extension for Chrome and Edge that captures a task from any site. A person clicks Record, does the task in their own browser while talking it through, and clicks Stop. The recording goes straight from the browser into CopilotKit Intelligence as a Product Trajectory, in that person's own Learning space, where Learning can turn it into a Skill.
A recording is the person's steps, not the page's traffic:
transcriptevent with its start time, and no audio leaves the extension or is stored.autocompletehint, and codes typed one digit per box). It also covers credential headers, tokens in URLs and bodies, and spoken card numbers.OpenBot receives nothing that was recorded. It does three things:
trajectory/<id>/connectanswers only the Recorder's bearer.user-plus a hash of the OpenBot user id) to the runtime's Learning selector. It creates the space when the browser connects, and again on a capture pass if it is still missing.Learning, per person, shared by choice:
Permissions are asked at install, so the first Record records. Website access is a required host permission, and a fresh install opens one setup tab that asks for the microphone. The install dialog shows the same warnings the debugger permission already implies.
Also in this branch:
@copilotkit/runtimemoves to 1.79.1 (AG-UI 1.0.2), for server-side Learning space assignment.shared/.Intelligence needs settings only, no code:
FF_LEARNING_TRAJECTORIESon;INTELLIGENCE_GATEWAY_WS_URLthat people's browsers can reach;INTELLIGENCE_SERVICE_TOKENwithINTELLIGENCE_PROJECT_IDon a local evaluation install, or a CopilotKit CLI login on managed Intelligence. The runtime key alone cannot create a person's space.Where it runs
New state that outlives a request? Postgres, in migration 0057:
recorder_connections: hashed bearer, label, revoked flag.recorder_connect_codes: hashed one-time codes, deleted on use or expiry.recorder_trajectory_bindings: trajectory to person.learning_person_spaces: each person's Learning space.learning_skill_sharing: each person's sharing mode.learning_open_skills: the individual Skills a person chose to share.Four per-process caches have TTLs and hold nothing that must be shared:
What happens on the second replica?
Anything serialised?
trajectory_idprimary key withON CONFLICT DO NOTHING, followed by an owner check.Anything fanned out to a browser? No. The extension streams to Intelligence's realtime gateway directly.
New listener, port, or schedule? No new port and no schedule.
/api/recorder/*and/api/settings/learning, behind the same ingress as the API.Boundary and audit
recorder.connected,recorder.revokedandrecorder.trajectory_refused;learning.sharing_changed,learning.skill_openedandlearning.skill_closed.externally_connectablemessages.Changelog
CHANGELOG.mdunderUnreleased. It covers:OPENBOT_RECORDER_EXTRA_EXTENSION_IDS(a bad value stops the server at boot);openai/*models behind a non-OpenAIOPENAI_BASE_URL(COPILOTKIT_OPENAI_API=responseskeeps the Responses API).Proof
Run against a real local Intelligence (v0.13.14), in real Chrome 155 and real Edge 155, on fixture sites and real sites, including a person's own narrated sessions in a real SaaS app.
sendMessageattached), close banners (each "mark as read" attached).transcriptevents between the steps, and a spoken card number was stored as[redacted], including when the recognizer dropped digits.user-...), active, incpki.trajectory_container_memberships. Tests prove the Skill merge, clash rules, sharing modes and fail-soft, because Learning delivery does not run locally.failed:disconnected;format:check,lint,typecheck,build,drizzle-kit checkand drift: all pass.test:ci: 8,420 tests, 0 failures.cargo fmt,clippy -D warningsand the lib and native host tests pass.Known limits: