Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
278 commits
Select commit Hold shift + click to select a range
cf1bc7a
Let the migration journal check accept an index that is dropped and r…
davidmckayv Oct 17, 2026
3d83660
Define the demonstration step schema in shared, with navigate and net…
davidmckayv Oct 17, 2026
403c4bb
Check hasTarget is false for navigate and network steps
davidmckayv Oct 17, 2026
467a9c5
Label cloud-browser gestures with the shared demonstration rules
davidmckayv Oct 17, 2026
8cd0ee6
Share one copy of the demonstration labelling and step rules between …
davidmckayv Oct 17, 2026
7811072
Restore crypto.subtle after the masking test hides it
davidmckayv Oct 17, 2026
c07d161
Define the Recorder's HTTP contract in shared
davidmckayv Oct 17, 2026
a53c7c7
Define the skill-share request contract in shared
davidmckayv Oct 17, 2026
ad6c6ba
Record the Recorder's one extension id and derive it from the committ…
davidmckayv Oct 17, 2026
8cd9817
Keep the two copies of the sensitive-field rule equal
davidmckayv Oct 17, 2026
930f480
Accept only HTTPS or loopback OpenBot addresses and WSS gateways in t…
davidmckayv Oct 17, 2026
212e454
Let isRecordablePage take a tab's possibly missing address
davidmckayv Oct 17, 2026
0c96e56
Test the Recorder's refusal of both store hosts
davidmckayv Oct 17, 2026
0144a24
Declare the Recorder's one IndexedDB schema
davidmckayv Oct 17, 2026
58c63da
Let a Recorder database transaction resolve only when its work has fi…
davidmckayv Oct 17, 2026
0cc7aa2
Keep the failing request's error when a Recorder transaction fails
davidmckayv Oct 17, 2026
a6146ec
Let a Recorder transaction succeed when its caller handled a request …
davidmckayv Oct 17, 2026
28d9c84
Add an in-memory chrome for the Recorder's tests
davidmckayv Oct 17, 2026
0cd0c73
Fire managed storage changes and log every runtime call in the Record…
davidmckayv Oct 17, 2026
81896da
Give the Recorder's chrome fake a page sender and asynchronous ports,…
davidmckayv Oct 17, 2026
825ab5c
Copy messages as JSON, deliver them asynchronously and disconnect unh…
davidmckayv Oct 17, 2026
75f7c1d
Store only JSON values and honour Chrome's sendMessage callback form …
davidmckayv Oct 17, 2026
b135a1b
Report storage changes and port disconnects in the Recorder's chrome …
davidmckayv Oct 17, 2026
817ad60
Define the Recorder's internal messages and its storage helpers
davidmckayv Oct 17, 2026
e6629c3
Give concurrent first callers one Recorder install secret
davidmckayv Oct 17, 2026
e06654e
Talk to OpenBot from the Recorder with a bearer and no cookies
davidmckayv Oct 17, 2026
046eec9
Accept empty answers, prove no cookies are sent and keep the bearer o…
davidmckayv Oct 17, 2026
e9fbca2
Make openbotRaw reject rather than throw when it refuses a request
davidmckayv Oct 17, 2026
715a9e4
Add feature-off stubs for the Recorder's server routes, media store a…
davidmckayv Oct 17, 2026
ad0da63
Give the Recorder's pages one way to ask the service worker
davidmckayv Oct 17, 2026
0266f7d
Define the recorder's HTTP and sharing contracts, its internal messag…
davidmckayv Oct 17, 2026
bbd7ee7
Install a fresh fake IndexedDB for each Recorder database test
davidmckayv Oct 17, 2026
3995b33
Give the runtime's request identity functions a module tests can import
davidmckayv Oct 17, 2026
4f3eb07
Read extra trusted Recorder extension ids from OPENBOT_RECORDER_EXTRA…
davidmckayv Oct 17, 2026
11757e0
List the Recorder's extra extension ids with the authentication settings
davidmckayv Oct 17, 2026
c31603f
Let the Recorder verify an organization session without touching Desk…
davidmckayv Oct 17, 2026
a6107f8
Treat an organization sign-in server's odd answers as unavailable, ne…
davidmckayv Oct 17, 2026
ce487ed
Store Recorder connect codes and connections as digests, one live con…
davidmckayv Oct 17, 2026
979da81
Draft navigate steps like a URL change and list the site's requests a…
davidmckayv Oct 17, 2026
d82571f
Take an extension recording's steps in batches with no step or time l…
davidmckayv Oct 17, 2026
d9f3f7f
Handle Connect this browser, the Desktop handoff and the sign-in keep…
davidmckayv Oct 17, 2026
ae98350
Close a replaced Recorder connect tab so it can never connect to a di…
davidmckayv Oct 17, 2026
9e6209e
Handle Recorder connect requests one at a time so a page can't slip p…
davidmckayv Oct 17, 2026
42926e6
Never let a finishing Recorder connect clear a newer pending connect
davidmckayv Oct 17, 2026
6a5aa1e
Forget a Recorder connection on a sign-out answer only if it is still…
davidmckayv Oct 17, 2026
270e7f1
Let every Recorder sign-out path forget a connection only if it is st…
davidmckayv Oct 17, 2026
bb270d7
Confirm a Connect this browser request on the extension's own page be…
davidmckayv Oct 17, 2026
531803a
Show the Recorder's real error on the connect page and cap the browse…
davidmckayv Oct 17, 2026
5bc5c2c
Show the recording's step count on the Recorder's badge
davidmckayv Oct 17, 2026
73c4a2f
Queue recorded steps in IndexedDB and upload them to OpenBot in batches
davidmckayv Oct 17, 2026
0499632
Stop live step upload only for the refused recording, and treat a rea…
davidmckayv Oct 17, 2026
f112719
Resend Recorder steps from OpenBot's count when it reports a gap, and…
davidmckayv Oct 17, 2026
023e7bb
Turn the recorded tab's navigations into trajectory events and naviga…
davidmckayv Oct 17, 2026
78d4a78
Route every Recorder message through one set of service-worker listeners
davidmckayv Oct 17, 2026
bcca29b
Run the Recorder's reconnect follow-up only on an approved connect, n…
davidmckayv Oct 17, 2026
f49fdc5
Test that the Recorder's e2e build routes its flood request to the re…
davidmckayv Oct 17, 2026
1450c09
Mask and trim clicks and typing in the content script before the coll…
davidmckayv Oct 17, 2026
7f55c73
Test that the Recorder reports each kind of dropped event once and re…
davidmckayv Oct 17, 2026
40d223a
Mask a sensitive field's value when the person clicks its label
davidmckayv Oct 17, 2026
21e3f24
Capture clicks, typing, keys and scrolls as demonstration steps in ev…
davidmckayv Oct 17, 2026
1e9b6c4
Name Recorder steps after the control inside a web component, not its…
davidmckayv Oct 17, 2026
f8d4b02
Record one Recorder step per action: no duplicate step for Enter or a…
davidmckayv Oct 17, 2026
626c1ea
Reconnect the content script's port and buffer its messages without d…
davidmckayv Oct 17, 2026
68ee285
Clip the page address in the Recorder's hello so a long URL can't mak…
davidmckayv Oct 17, 2026
d9a471b
Issue trajectory seqs that never repeat across frames, reloads or res…
davidmckayv Oct 17, 2026
54c57da
Keep unsent trajectory events in a durable IndexedDB outbox
davidmckayv Oct 17, 2026
fe09cf6
Let the Recorder's outbox forget a trajectory's meta when it clears i…
davidmckayv Oct 17, 2026
e1c4431
Classify every answer to the capture pass by the error table
davidmckayv Oct 17, 2026
fbdfd13
Treat any Recorder sign-out answer as a sign-out when asking for an I…
davidmckayv Oct 17, 2026
95e6e4a
Classify an Intelligence pass failure whose message is not text inste…
davidmckayv Oct 17, 2026
5c78fac
Retry an Intelligence pass when OpenBot answers with a page that is n…
davidmckayv Oct 17, 2026
746da4d
Add a fake Intelligence gateway that enforces Intelligence's limits a…
davidmckayv Oct 17, 2026
1bd271d
Detect the Recorder from OpenBot and hand it a connect code
davidmckayv Oct 17, 2026
3711009
Check website access and the microphone before the popup records
davidmckayv Oct 17, 2026
72d4856
Add the Recorder's options page with Disconnect and the organization'…
davidmckayv Oct 17, 2026
1f45df5
Use the shared empty-state text on the Recorder's options page
davidmckayv Oct 17, 2026
015ee74
Say "is discarded" when the Recorder keeps a single event
davidmckayv Oct 17, 2026
65660fd
Forget a discarded recording's next step index along with its other data
davidmckayv Oct 17, 2026
bf26413
Model chrome.tabs.remove in the Recorder's shared Chrome fake
davidmckayv Oct 17, 2026
3a35e12
Make a Recorder connect tab confirm only the site it shows
davidmckayv Oct 17, 2026
ec35e9e
Resolve the Recorder's scoped bearer per auth mode and refuse it as s…
davidmckayv Oct 17, 2026
8aa58f7
Take the runtime's request identity functions from their own module i…
davidmckayv Oct 17, 2026
db6ff11
Bind trajectories to an extension recording and record the Learning s…
davidmckayv Oct 17, 2026
9f22f82
Let a rollover begun before Stop register after the recording is draf…
davidmckayv Oct 18, 2026
3badf26
Draft and publish through the store in the late-rollover tests, and d…
davidmckayv Oct 18, 2026
5593cf0
Run a recording from the service worker: start, follow tabs, send ste…
davidmckayv Oct 17, 2026
e82926b
Keep Start over from stopping the new recording, show only the live r…
davidmckayv Oct 18, 2026
5bd8d47
Never let Start over open a new recording after the person stopped
davidmckayv Oct 18, 2026
87ef298
Pause only the recording Start over is leaving, and keep Stop and the…
davidmckayv Oct 18, 2026
4296212
Stop an open recording on Record only for the Bot it belongs to
davidmckayv Oct 18, 2026
5d6532f
Wait for the Recorder's offscreen document to reconnect before sendin…
davidmckayv Oct 18, 2026
1edad47
Finish starting a recording before stopping it, and discard a recordi…
davidmckayv Oct 18, 2026
0af45c1
Fix the Recorder's remaining stop, Start over and Disconnect races
davidmckayv Oct 18, 2026
917a908
Test every pair of Recorder actions at every wait for the invariants …
davidmckayv Oct 18, 2026
3c29845
Show the 50-step notice in the page in OpenBot's colours, ignored by …
davidmckayv Oct 17, 2026
5fb3837
Use the theme's ring colour for the 50-step notice's focus ring in da…
davidmckayv Oct 18, 2026
1ff3342
Dim the 50-step notice's Stop button on hover like OpenBot's buttons
davidmckayv Oct 18, 2026
3f4fc3d
Stream trajectory events to Intelligence with resend, rollover and a …
davidmckayv Oct 17, 2026
0d8ccc7
Report kept Recorder events again when a recording stops or is replac…
davidmckayv Oct 18, 2026
e9df402
Stop retrying a rollover OpenBot refuses, and keep its events for the…
davidmckayv Oct 18, 2026
ae22a20
Park a refused rollover only on OpenBot's own refusal, say so in the …
davidmckayv Oct 18, 2026
955bac2
Add Browsers to Settings: connect this browser's Recorder and disconn…
davidmckayv Oct 18, 2026
5babcc3
Say so on This browser when OpenBot cannot load the connected browser…
davidmckayv Oct 18, 2026
0f61cb3
Put TanStack Query's server check back exactly as it was after the Br…
davidmckayv Oct 18, 2026
fcb6f6b
Add the Recorder's popup with sign-in state, Bot choice, Record and n…
davidmckayv Oct 17, 2026
1a95873
Offer Stop the open recording whenever one is open, remember the last…
davidmckayv Oct 18, 2026
a389e23
Line the popup's Allow microphone link up with the form
davidmckayv Oct 18, 2026
2903ce0
Offer Stop the open recording only for the Bot whose Record was refused
davidmckayv Oct 18, 2026
3c1f499
Model the offscreen document's lifecycle in the Recorder's shared Chr…
davidmckayv Oct 18, 2026
e7f0a1e
Carry the token a Recorder sign-out was about, and forget only that c…
davidmckayv Oct 18, 2026
a30d05d
Scope a Recorder sign-out to its own OpenBot and only when it really …
davidmckayv Oct 18, 2026
bc2a487
Mark kept recordings waiting on a sign-out only when it really forgot…
davidmckayv Oct 18, 2026
2335337
Mount the Recorder's routes, dictation access and media read routes b…
davidmckayv Oct 18, 2026
bc56fc3
Recover an interrupted recording and finish a stop after a browser or…
davidmckayv Oct 18, 2026
9a72d90
Send a kept recording's network bodies on reconnect and after a retri…
davidmckayv Oct 18, 2026
23f900d
Never send a recording the person discarded, even if a restart interr…
davidmckayv Oct 18, 2026
b9e0c8f
Wait for the offscreen document to reconnect before re-sending a stal…
davidmckayv Oct 18, 2026
2869f8b
Finish an interrupted discard on any restart, and reopen the document…
davidmckayv Oct 18, 2026
d8c69af
Carry on recovering after a restart when finishing a discard fails
davidmckayv Oct 18, 2026
920de88
Capture clicks, typing and steps in every frame with secrets masked, …
davidmckayv Oct 18, 2026
cc9a3df
Take the 50-step notice down when capture stops, and skip steps insid…
davidmckayv Oct 18, 2026
36a2bd3
Stream recorded events to Intelligence from the offscreen document wi…
davidmckayv Oct 18, 2026
50056f9
Stop the screen and voice even when a recording's stream never starte…
davidmckayv Oct 18, 2026
dec6daa
List Browsers in the Settings sidebar
davidmckayv Oct 18, 2026
3baf66d
Add the Recorder's popup with Bot choice, Record, Stop and Start over…
davidmckayv Oct 18, 2026
fac54e7
Show the popup's real after-stop and kept states, let a signed-out pe…
davidmckayv Oct 18, 2026
88bfcc2
Show only what is true after a stop: no Saved link before OpenBot has…
davidmckayv Oct 18, 2026
bd9d2f3
Hold recovered recordings to the organization's pinned Intelligence a…
davidmckayv Oct 18, 2026
9fa1b96
Don't show a failed Start over as saved
davidmckayv Oct 18, 2026
a5abb3e
Track whether the 50-step notice is open, offer Record screen again a…
davidmckayv Oct 18, 2026
d58c6c0
Connect a browser to OpenBot with a one-time code and a revocable sig…
davidmckayv Oct 18, 2026
1430605
Make the Recorder's scope tests fail if the bearer ever reaches anoth…
davidmckayv Oct 18, 2026
95aa62d
Run a recording from the extension: start, follow the tab, send the s…
davidmckayv Oct 18, 2026
844e931
Prove an organization sign-in server's odd answers never sign the Rec…
davidmckayv Oct 18, 2026
ce95ef7
Check the organization cookie and read Desktop's failure state in the…
davidmckayv Oct 18, 2026
a793d99
Take a demonstration's steps from the extension in batches, with no s…
davidmckayv Oct 18, 2026
8fbe402
Answer not found for a trajectory id that is not a UUID instead of a …
davidmckayv Oct 18, 2026
badf01a
List a Bot's demonstrations as summaries a page at a time, without th…
davidmckayv Oct 18, 2026
72f85c4
Move the demonstrations data layer to query and mutation option facto…
davidmckayv Oct 18, 2026
3909349
Refresh routines from the schedule mutation itself and reuse the shar…
davidmckayv Oct 18, 2026
ca3b227
Check a Bot's Learning space exists with Intelligence and say why whe…
davidmckayv Oct 18, 2026
2e72786
Answer a Recorder bearer only on the capture pass, and never give the…
davidmckayv Oct 18, 2026
caaf0e1
Gate the capture pass on the person's own open recording
davidmckayv Oct 18, 2026
92038e7
Write one refused-pass audit row per reason a minute for callers nobo…
davidmckayv Oct 18, 2026
4193778
Read whether the Recorder may read websites from Chrome's permissions
davidmckayv Oct 18, 2026
d2df146
Store a demonstration's media in ordered chunks beside it
davidmckayv Oct 18, 2026
e56a663
Refuse to complete a recording's media while stray parts lie past its…
davidmckayv Oct 18, 2026
8d2a3b5
Keep large network bodies in IndexedDB until OpenBot's media store ha…
davidmckayv Oct 18, 2026
2d206dc
Read a refused body upload's sign-out, open the Recorder database onc…
davidmckayv Oct 18, 2026
ea7254a
Inject page-level network capture into recorded tabs and re-mask what…
davidmckayv Oct 18, 2026
f98420a
Take a tab stream id from the service worker right before the offscre…
davidmckayv Oct 18, 2026
a392e68
Read the microphone permission for the popup
davidmckayv Oct 18, 2026
0055c47
Format the popup microphone test the way Biome wants
davidmckayv Oct 18, 2026
cbd9c06
Buffer screen and audio chunks in IndexedDB under their server chunk …
davidmckayv Oct 18, 2026
3a1d181
Add the Recorder's microphone page that asks for a lasting grant
davidmckayv Oct 18, 2026
f069928
Send a voice slice to OpenBot's dictation route with the Recorder's b…
davidmckayv Oct 18, 2026
6e20174
Tell a busy or briefly unavailable transcription apart from a failed one
davidmckayv Oct 18, 2026
539e57b
Prove the dictation route takes the Recorder's bearer only with the d…
davidmckayv Oct 18, 2026
e5cb003
Ask OpenBot Desktop for its address over native messaging from the Re…
davidmckayv Oct 18, 2026
e602bed
Write the Recorder's native messaging host manifests for each install…
davidmckayv Oct 18, 2026
1d5e71a
Read the local OpenBot's organization session cookie for the Recorder…
davidmckayv Oct 18, 2026
c547a14
Read the Recorder's OpenBot and Intelligence addresses from managed p…
davidmckayv Oct 18, 2026
c39cd38
Assert the pinned-elsewhere refusal and prove the policy's gateway li…
davidmckayv Oct 18, 2026
ff72082
Document the Recorder's company install and prove a packed CRX keeps …
davidmckayv Oct 18, 2026
80445e5
Say exactly which Edge installs can force a self-hosted extension, ci…
davidmckayv Oct 18, 2026
010146f
Say exactly which Windows machines Chrome lets force a self-hosted ex…
davidmckayv Oct 18, 2026
c38ab93
Forget a discarded recording's network body upload progress too
davidmckayv Oct 18, 2026
aae830a
Finish a recording's network bodies at recovery when only their compl…
davidmckayv Oct 18, 2026
99c4567
Extract the recorded demonstrations list from the computer view
davidmckayv Oct 18, 2026
e4a7ed4
Ask for website access on the Recorder's own page at the first recording
davidmckayv Oct 18, 2026
9c67b70
Accept a recording's media chunks under the Recorder's bearer
davidmckayv Oct 18, 2026
f1c78a4
Upload buffered media chunks in order, resuming after failures and si…
davidmckayv Oct 18, 2026
4fa0991
Let IT pin the Recorder's OpenBot and Intelligence addresses by polic…
davidmckayv Oct 18, 2026
d007020
Answer the Recorder's ping and handoff in OpenBot Desktop's native host
davidmckayv Oct 18, 2026
25cb762
Install the Recorder's native messaging host for this user at every D…
davidmckayv Oct 18, 2026
7fc60e6
Let OpenBot Desktop hand the Recorder its address over native messaging
davidmckayv Oct 18, 2026
ec0fcaa
Remove extension recordings and their media from OpenBot's server
davidmckayv Oct 18, 2026
5097a55
Take the Recorder's bearer off the dictation route
davidmckayv Oct 18, 2026
fa89f61
Rewrite the Recorder migration to hold only connections, connect code…
davidmckayv Oct 18, 2026
ecaf84a
Hand out the capture pass only to a connected browser, for a Bot it m…
davidmckayv Oct 18, 2026
8d354d4
Put the demonstrations screen and its data layer back as they were be…
davidmckayv Oct 18, 2026
39b0340
Say in the company install guide that OpenBot keeps nothing the Recor…
davidmckayv Oct 18, 2026
6db6a56
Name the picked Bot on every capture pass request
davidmckayv Oct 18, 2026
c4a24b4
Stop recording the screen and voice: no tab capture, microphone, medi…
davidmckayv Oct 18, 2026
a0ddc54
Send a recording to Intelligence only: no demonstrations, steps, traj…
davidmckayv Oct 18, 2026
d148969
Record the network calls behind each followed tab through Chrome's de…
davidmckayv Oct 18, 2026
f336d1a
Say only why network capture fell back to the page's own calls: the N…
davidmckayv Oct 18, 2026
84dfc6b
Keep in the shared Recorder contract only what connecting a browser a…
davidmckayv Oct 18, 2026
3b03cd4
Name the capture pass's Bot header once in the shared Recorder contra…
davidmckayv Oct 18, 2026
982b2c7
Stop answering a transcription capability on /api/recorder/me: OpenBo…
davidmckayv Oct 18, 2026
7b23cf9
Wait 3 s for OpenBot Desktop's ping and 8 s for its handoff, longer t…
davidmckayv Oct 18, 2026
0670da8
Remove what nothing uses after the rework: the async SHA-256 wrapper,…
davidmckayv Oct 18, 2026
52ee8a1
Describe in the company install guide only what the Recorder records …
davidmckayv Oct 18, 2026
7577f3d
Keep the manifest's permission list and the popup's prepare answer pr…
davidmckayv Oct 18, 2026
b205220
Mask form bodies pair by pair whatever their declared type, and JSON …
davidmckayv Oct 19, 2026
5f4ebee
Drop page-captured calls to OpenBot itself, as the debugger already d…
davidmckayv Oct 19, 2026
0a6555e
Keep a content message the port could not deliver, and stop an orphan…
davidmckayv Oct 19, 2026
df9a92e
Keep the sealed organization session no longer than it is needed: del…
davidmckayv Oct 19, 2026
573f7be
Drop the organization's expired verdicts from the Recorder guard's ca…
davidmckayv Oct 19, 2026
1caa000
Answer a Recorder bearer only where the tested trajectory-path check …
davidmckayv Oct 19, 2026
a8c3978
Say on the connect page what OpenBot gets access for: signing the bro…
davidmckayv Oct 19, 2026
83d39fe
Move the action notice and trajectory rollover thresholds into the ex…
davidmckayv Oct 19, 2026
6c60e33
Clear the rework's leftovers: the chat-only tool line and generated i…
davidmckayv Oct 19, 2026
ba86a7b
Say Bun 1.4 or later is required: the repository pins 1.4.3 and some …
davidmckayv Oct 19, 2026
5e9ac17
Keep the company install guide to the steps an IT team and an Intelli…
davidmckayv Oct 19, 2026
4dd9731
Describe the Recorder release for whoever upgrades: migration 0057, S…
davidmckayv Oct 19, 2026
39f4667
Find a browser installed after Desktop started, follow XDG_CONFIG_HOM…
davidmckayv Oct 19, 2026
b5c5277
Answer the Recorder's Desktop ping cheaply and every failure with a s…
davidmckayv Oct 19, 2026
2840289
Copy only the Recorder extension's package.json into a Desktop deploy…
davidmckayv Oct 19, 2026
e93a76e
Tell IT what removing OpenBot Desktop leaves behind: Windows deletes …
davidmckayv Oct 19, 2026
b107a37
Remove the screen-capture fake from the extension's test chrome: the …
davidmckayv Oct 18, 2026
5183003
End a recording that a Disconnect or a sign-out stops with a failed o…
davidmckayv Oct 19, 2026
3723510
Stop what a lost connection still runs: a keep-alive sign-out and a p…
davidmckayv Oct 19, 2026
e206446
Keep a recording running when its person connects this browser again:…
davidmckayv Oct 19, 2026
a4687ad
Reopen an offscreen document that goes away mid-recording or mid-Stop…
davidmckayv Oct 19, 2026
b0ac1d2
Narrow the reopened-document tests' expected session to the start mes…
davidmckayv Oct 19, 2026
be7b409
Say so when a recording's streamer never starts: the live status show…
davidmckayv Oct 19, 2026
fca58fd
Delete a recording's trajectory records from the outbox once it is do…
davidmckayv Oct 19, 2026
56f1dba
Bump the Recorder database to version 2, which drops what a build fro…
davidmckayv Oct 19, 2026
b706203
Pass OpenBot Desktop's refusal code through the Desktop connect and s…
davidmckayv Oct 19, 2026
ca814ee
Name everything a Disconnect discards in its dialog: the recording in…
davidmckayv Oct 19, 2026
9c319f0
Popup run-through fixes: say the Learning sentence once after Stop in…
davidmckayv Oct 19, 2026
132f61b
Say in the managed schema and the policy parser that Chrome withholds…
davidmckayv Oct 19, 2026
b8d0165
Say in the company guide that Chrome withholds a Recorder policy of t…
davidmckayv Oct 19, 2026
415ef2f
Drop the Recorder company install guide and its test, and the pointer…
davidmckayv Oct 19, 2026
fe7966a
Count a workspace Desktop copies as its manifest alone as present in …
davidmckayv Oct 19, 2026
a3ac00f
Cut an over-budget network event's request and response bodies to one…
davidmckayv Oct 19, 2026
047a4f5
Have the service worker name the extension version in offscreen.recov…
davidmckayv Oct 19, 2026
bd878ca
While a recording runs, ask OpenBot every 30 s whether its connection…
davidmckayv Oct 19, 2026
4fe3fca
Raise the Recorder's web connect limit from 3 to 10 per OpenBot origi…
davidmckayv Oct 19, 2026
468ac44
Mask what a person says while recording: scrubTranscript finds a card…
davidmckayv Oct 19, 2026
b3279c3
Transcribe the person's narration in the browser while they record: t…
davidmckayv Oct 19, 2026
eb9e7d4
Show the narration in the popup: a Voice row beside the recording's t…
davidmckayv Oct 19, 2026
bc975ef
Mask any spoken number Chrome cuts short or runs together: scrubTrans…
davidmckayv Oct 19, 2026
26f3110
Say so in the Voice row when the browser hears speech but hands back …
davidmckayv Oct 19, 2026
6a5ab27
Ask for every permission when the Recorder is installed, so the first…
davidmckayv Oct 19, 2026
66175e0
Warn that Learning will not see a recording only when its capture pas…
davidmckayv Oct 19, 2026
9d91f58
Keep the setup page readable in a narrow window: Allow microphone sit…
davidmckayv Oct 19, 2026
ce34f26
Keep one-time codes in the browser: a 2FA field with no autocomplete=…
davidmckayv Oct 19, 2026
8e1102f
Mask a code typed a digit per box: a row of 4 to 8 one-character boxe…
davidmckayv Oct 19, 2026
687598f
Merge main into the Recorder branch: desktop.yml watches both main's …
davidmckayv Oct 19, 2026
4b14dbf
Record only the network calls the person's own actions cause: a fetch…
davidmckayv Oct 19, 2026
ff087ee
Record navigation and page events from a followed tab's top frame onl…
davidmckayv Oct 19, 2026
4097d2b
Record into each person's own Learning space, with no Bot in the Reco…
davidmckayv Oct 19, 2026
5fa0ed8
Every Bot a person uses draws Skills from their own Learning space, a…
davidmckayv Oct 19, 2026
acf333b
Stop the Recorder's leak checks reading the event's clock as a leaked…
davidmckayv Oct 19, 2026
ffaf7ab
Record the person's steps instead of network calls: a recording is ea…
davidmckayv Oct 19, 2026
702652e
Say in the popup what a recording now holds: the form reads that it r…
davidmckayv Oct 19, 2026
ad25956
Keep the page's own chatter and redirects out of the steps: a write w…
davidmckayv Oct 19, 2026
b6de006
Name the element a click was meant for: a click on text or a wrapper …
davidmckayv Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/openbot-screen-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ itself when the dialog changes something.

## The rem Trap

`app/src/styles.css` sets `html { font-size: 15px }`. Every rem-based Tailwind token is therefore
`app/src/theme.css` sets `html { font-size: 15px }`. Every rem-based Tailwind token is therefore
**0.9375×** its documented pixel value:

| Token | Documented | Actual here |
Expand Down
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ COPILOTKIT_LICENSE_TOKEN=
# CPK_INTELLIGENCE_SKILLS_REVISION=exact-revision-id
# See docs/automatic-learning.md for managed/self-hosted setup and permissions.

# OpenBot accepts the OpenBot Recorder extension only from its own extension id. A fork, or a build
# signed with another key, has another id: list it here, comma separated, each 32 letters from a to
# p. Any other value stops the server at boot.
# OPENBOT_RECORDER_EXTRA_EXTENSION_IDS=

# How long a Bot's stream may say nothing before this deployment gives up on the turn, in
# milliseconds. A Bot is any AG-UI endpoint, which means it will be redeployed mid-answer, its own
# upstream will time out, and it will sometimes accept a connection and then write nothing at all.
Expand Down Expand Up @@ -207,6 +212,11 @@ OPENAI_API_KEY=
# BOT_MODEL=openai/gpt-5.5
#
# OPENAI_BASE_URL=
# Behind an OPENAI_BASE_URL that is not OpenAI or Azure, built-in Bots call the endpoint's Chat
# Completions API, which is what most compatible endpoints serve. A gateway that also serves the
# Responses API keeps Responses-only features, such as reasoning summaries, with `responses`.
# `chat` forces Chat Completions; any other value is an error when a built-in Bot runs.
# COPILOTKIT_OPENAI_API=responses

# Optional dictation, independent of every agent/chat provider. Set provider, base URL,
# and model together. The endpoint must implement POST /audio/transcriptions under this
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ name: Desktop
# it at runtime with an `expect`, so a bad row panics the window rather than failing a type check.
on:
pull_request:
paths: ["desktop/**", "shared/harnesses.json", "shared/marks/**", "package.json", ".github/workflows/desktop.yml"]
paths: ["desktop/**", "shared/harnesses.json", "shared/marks/**", "shared/recorder-extension.json", "package.json", ".github/workflows/desktop.yml"]
push:
branches: [main]
paths: ["desktop/**", "shared/harnesses.json", "shared/marks/**", "package.json", ".github/workflows/desktop.yml"]
paths: ["desktop/**", "shared/harnesses.json", "shared/marks/**", "shared/recorder-extension.json", "package.json", ".github/workflows/desktop.yml"]
workflow_call:
secrets:
OPENBOT_GOOGLE_MODEL_OAUTH_CLIENT_SECRET:
Expand Down Expand Up @@ -143,6 +143,18 @@ jobs:
- name: Rust regression tests
run: cargo test --locked --lib --bins
working-directory: desktop/src-tauri
# The binary run as the Recorder's browsers run it: the native messaging host Chrome starts, and
# the install that writes its manifests. The host name and extension id come from
# shared/recorder-extension.json, which the Rust reads at compile time.
- name: Recorder native host tests
run: cargo test --locked --test recorder_host
working-directory: desktop/src-tauri
# Only a release build is a GUI-subsystem executable on Windows, and only a release build proves
# the debug-only test seam is absent.
- name: Recorder native host tests (release)
if: matrix.platform.name == 'windows'
run: cargo test --locked --release --test recorder_host
working-directory: desktop/src-tauri
# Hosted Windows runners are administrators. Exercise setup again with an actual Users-only
# account: DISM's feature reads worked as CI's administrator but refused ordinary app users.
- name: Windows standard-user setup regression
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ venv/
.pytest_cache/
.ruff_cache/
.mypy_cache/

# The Recorder extension's signing key never enters the repository (Decision 9).
extension/**/*.pem
58 changes: 57 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

## Unreleased

**Before upgrading.** Five things change for an existing deployment:
**Before upgrading.** Seven things change for an existing deployment:
- A hosted computer's browser is now headed by default. It uses a little more memory than headless
did; set `COMPUTER_BROWSER_MODE=headless` (Helm: `computers.browserMode: headless`) to keep the old
behaviour.
Expand All @@ -31,6 +31,62 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.
- Migration `0056_sign_in_code_step` widens the check on `sign_in_requests.status` to allow
`code_required` (below). A server and a computer must be upgraded together: an older computer has
no `/sign-in/code`.
- Migration `0057_recorder_extension` adds six tables for the OpenBot Recorder and each person's own
Learning space (below): `recorder_connect_codes`, `recorder_connections`,
`recorder_trajectory_bindings`, `learning_person_spaces`, `learning_skill_sharing` and
`learning_open_skills`. Nothing existing changes, and the tables stay empty until somebody
connects a browser or changes Settings → Learning.
- The CopilotKit runtime moves to 1.79.1. A built-in Bot whose `openai/...` model is reached
through an `OPENAI_BASE_URL` that is not OpenAI or Azure now calls that endpoint's Chat
Completions API (`/chat/completions`) instead of the Responses API. That is what most
OpenAI-compatible endpoints serve, so Bots behind one of them now work; a gateway that also serves
the Responses API loses Responses-only features, such as reasoning summaries, without an error.
`COPILOTKIT_OPENAI_API=responses` keeps the Responses API (`chat` forces Chat Completions); any
other value is an error when a built-in Bot runs. Deployments on OpenAI or Azure are unchanged.

### Teach a Bot by doing the task in your own browser: the OpenBot Recorder

The OpenBot Recorder is a Chrome and Edge extension. A person names the task, does it in their own
browser, and the Recorder sends what they did, step by step (each click, entry, choice, submit,
Enter and page change, with what an agent needs to find that element again, and the one request a
step sent when it saved something), masked in the browser first, straight to Intelligence as a
trajectory in that person's own Learning space; the page's own traffic is never recorded. Every Bot
they use then gets the Skills learned there, beside the Bot's own. OpenBot connects the browser and
hands out each capture pass; it receives nothing that is recorded.

- **Settings → Browsers** connects the Recorder in this browser and lists every browser connected
as you, each with a Disconnect. OpenBot Desktop can connect the Recorder on its own computer too.
- **Only a connected browser gets a capture pass.** The runtime's `trajectory/<id>/connect` route
answers only the Recorder's own bearer, and binds the trajectory to that person; no Bot is
picked or named. A signed-in session alone gets no pass, and the Recorder's bearer cannot run a
Bot or read a conversation.
- **One Learning space per person.** Every recording joins the person's own Intelligence Learning
container, `user-` and 32 hex digits derived from their OpenBot user id. OpenBot makes it when
the browser connects (and again on a capture pass if it is still missing) with the same
credential that makes the `openbot` container: a service token on a CopilotKit local evaluation
install, or a CopilotKit CLI login against managed Intelligence. Intelligence's runtime key
cannot make one. With neither, a missing space cannot be made, and the Recorder's popup says
Learning will not see the recording; the recording still goes to Intelligence.
- **Every Bot a person uses draws on their space.** A run gets the Bot's own learned Skills, then
the person's own, then any another person opened (below). On a name clash the Bot's own wins,
then the person's own; two people's opened Skills with one name are both kept, each named
`<their space>/<name>`. A person's space that is missing or unreachable never fails a run: it
goes on with the Skills it has. Admin → Automatic Learning off, or a Bot excluded there, still
means no learned Skills at all.
- **Settings → Learning** chooses who else's Bots use a person's Skills: only their own (the
default), every Bot in the deployment, or the Skills they open one by one. Runs read what is
open from memory, refreshed at once on the replica that saved a change and within 30 seconds on
the others.
- **Intelligence must allow trajectories:** the `trajectories` licence feature,
`FF_LEARNING_TRAJECTORIES=true`, and an `INTELLIGENCE_GATEWAY_WS_URL` that people's browsers can
reach.
- **`OPENBOT_RECORDER_EXTRA_EXTENSION_IDS`** lists other extension ids to trust besides the
Recorder's own, for a fork or a build signed with another key. Each must be 32 letters from a to
p, comma separated; **a value that is not stops the server at boot.**
- Connecting, disconnecting and refusals are audited as `recorder.connected`, `recorder.revoked`,
`recorder.connect_refused`, `recorder.bearer_refused` and `recorder.trajectory_refused`; changing
Settings → Learning as `learning.sharing_changed`, `learning.skill_opened` and
`learning.skill_closed`.

### The Bot's screen opens only when there is something to watch

Expand Down
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ COPY server/package.json server/package.json
COPY worker/package.json worker/package.json
COPY shared/package.json shared/package.json
COPY toolkit/package.json toolkit/package.json
COPY extension/package.json extension/package.json
RUN bun install --frozen-lockfile

# The lockfile travels with the manifest, because `--frozen-lockfile` with no lockfile in the context
Expand All @@ -70,12 +71,13 @@ RUN cd agent-computer && bun install --frozen-lockfile
# A second tree with the build-time dependencies left out, for the runtime stage to take. Vite,
# biome and the test tooling are a gigabyte that nothing in a running container imports.
RUN mkdir -p /prod && cp package.json bun.lock /prod/ \
&& cd /prod && mkdir -p app server worker shared toolkit \
&& cd /prod && mkdir -p app server worker shared toolkit extension \
&& cp /src/app/package.json app/package.json \
&& cp /src/server/package.json server/package.json \
&& cp /src/worker/package.json worker/package.json \
&& cp /src/shared/package.json shared/package.json \
&& cp /src/toolkit/package.json toolkit/package.json \
&& cp /src/extension/package.json extension/package.json \
&& bun install --frozen-lockfile --production


Expand Down Expand Up @@ -127,6 +129,8 @@ WORKDIR /app
COPY --from=deps /prod/node_modules node_modules
COPY --from=deps /prod/server/node_modules server/node_modules
COPY --from=deps /prod/toolkit/node_modules toolkit/node_modules
# shared files import zod at runtime, and the isolated install resolves it from shared/node_modules
COPY --from=deps /prod/shared/node_modules shared/node_modules
COPY --from=deps /src/package.json package.json
COPY --from=deps /src/bun.lock bun.lock
# The browser's tree is a separate install root with its own lockfile rather than a workspace of the
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ A Bot is any endpoint speaking [AG-UI](https://github.com/ag-ui-protocol/ag-ui),
## Requirements

- Docker, for PostgreSQL and the shipped Bots.
- [Bun](https://bun.sh) 1.3+, for the app and API server.
- [Bun](https://bun.sh) 1.4+, for the app, the API server and the tests (some tests fail on 1.3).
- A CopilotKit Intelligence project and license. A free plan is available, and Intelligence can be self-hosted.
- A model key. The proof-of-concept Bot uses OpenAI; the LangGraph Bot can use OpenAI, Anthropic, or Google.

Expand Down
4 changes: 3 additions & 1 deletion agent-computer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ RUN bun install --frozen-lockfile

COPY agent-computer/src ./src
# index.ts imports ../../shared/file-download at runtime (the other shared imports are type-only and
# erased), so the one file it needs goes where that path resolves from /app/src.
# erased), and demonstration.ts imports ../../shared/demonstration-rules at runtime too, so the files
# they need go where those paths resolve from /app/src.
COPY shared/file-download.ts /shared/file-download.ts
COPY shared/demonstration-rules.ts /shared/demonstration-rules.ts

# Durable working directory. Bot-produced files that must survive replacement live here.
RUN mkdir -p /workspace
Expand Down
137 changes: 7 additions & 130 deletions agent-computer/src/demonstration.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,11 @@
import type { Page } from "playwright";
import {
describeDemonstrationTarget,
gestureForRecording,
safeDemonstrationUrl,
} from "../../shared/demonstration-rules";
import type { InputMessage } from "./screencast";
export type RecordedGesture = {
kind: "click" | "type" | "key" | "scroll";
key?: string;
deltaY?: number;
};
export function gestureForRecording(
input: InputMessage,
): RecordedGesture | null {
if (input.type === "mouse")
return input.event === "released" && input.button !== "right"
? { kind: "click" }
: null;
if (input.type === "wheel")
return {
kind: "scroll",
deltaY: Math.max(-2000, Math.min(2000, input.deltaY)),
};
if (input.type === "text") return { kind: "type" };
if (input.event !== "down") return null;
if (input.key.length === 1 || (input.text && input.key !== "Enter"))
return { kind: "type" };
if (
[
"Enter",
"Tab",
"Escape",
"Backspace",
"Delete",
"ArrowDown",
"ArrowUp",
"ArrowLeft",
"ArrowRight",
"Home",
"End",
"PageDown",
"PageUp",
].includes(input.key)
)
return { kind: "key", key: input.key };
return null;
}
export function safeDemonstrationUrl(raw: string): string {
try {
const url = new URL(raw);
if (!["http:", "https:"].includes(url.protocol)) return "about:blank";
const path = url.pathname
.split("/")
.map((part) =>
part.length > 40 || /token|secret|password|credential/i.test(part)
? "[redacted]"
: part,
)
.join("/");
return `${url.origin}${path}`;
} catch {
return "about:blank";
}
}

/** Never reads a field's value, page body, clipboard or image. All text entry is parameterized. */
export async function describeHumanGesture(page: Page, input: InputMessage) {
const gesture = gestureForRecording(input);
Expand All @@ -66,77 +14,6 @@ export async function describeHumanGesture(page: Page, input: InputMessage) {
input.type === "mouse" || input.type === "wheel"
? { x: input.x, y: input.y }
: null;
const target = await page.evaluate((point) => {
const hit = point
? document.elementFromPoint(point.x, point.y)
: document.activeElement;
const closest =
hit?.closest(
"button,a,input,textarea,select,label,[role],[contenteditable=true]",
) ?? hit;
// A click on a label's text is a click on the control it names: "Small", a radio, not a label.
const element =
closest instanceof HTMLLabelElement
? (closest.control ?? closest)
: closest;
if (!element) return { role: "page", name: "Page", sensitive: false };
const tag = element.tagName.toLowerCase();
const inputType =
element instanceof HTMLInputElement ? element.type.toLowerCase() : "";
const role =
element.getAttribute("role") ||
(
{
checkbox: "checkbox",
radio: "radio",
button: "button",
submit: "button",
reset: "button",
image: "button",
range: "slider",
file: "button",
} as Record<string, string>
)[inputType] ||
(
{
button: "button",
a: "link",
input: "textbox",
textarea: "textbox",
select: "combobox",
} as Record<string, string>
)[tag] ||
tag;
const label =
element.getAttribute("aria-label") ||
(element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
element instanceof HTMLSelectElement
? Array.from(element.labels ?? [])
.map((entry) => entry.textContent ?? "")
.join(" ")
: "") ||
(tag === "button" || tag === "a" ? element.textContent : "") ||
element.getAttribute("placeholder") ||
role;
const sensitive =
element.getAttribute("type") === "password" ||
// Short words only as whole words: "pin" is in "topping" and "shipping", "otp" in "hotpot".
/password|secret|token|one.time|verification|credit.card|security.code|passcode|(?:^|[^a-z])(?:cc-|(?:cvc|cvv|pin|otp)(?![a-z]))/i.test(
[
label,
element.getAttribute("name"),
element.getAttribute("autocomplete"),
].join(" "),
);
const name = sensitive
? "[sensitive field]"
: label
.replace(/[A-Za-z0-9_+/=-]{32,}/g, "[redacted]")
.replace(/[^\s@]+@[^\s@]+/g, "[redacted]")
.trim()
.slice(0, 120);
return { role: role.slice(0, 40), name: name || role, sensitive };
}, point);
const target = await page.evaluate(describeDemonstrationTarget, { point });
return { ...gesture, url: safeDemonstrationUrl(page.url()), target };
}
40 changes: 0 additions & 40 deletions agent-computer/tests/demonstration.test.ts

This file was deleted.

Loading
Loading