Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
398 commits
Select commit Hold shift + click to select a range
66970d3
test: Sheets-style editor takes inserted text and trusted pastes
lawrencecchen Oct 2, 2026
ce50fc9
REPL: setNotes selects the old notes before typing
lawrencecchen Oct 2, 2026
d387b35
docs: setNotes; Sheets writes are typed
lawrencecchen Oct 2, 2026
37a8dd3
CLI: cmux browser repl mcp serves the REPL as a stdio MCP server
lawrencecchen Oct 2, 2026
fa0121c
REPL: storageState saves the current tab's site unless { all: true }
lawrencecchen Oct 2, 2026
aa18c74
REPL: insertText commits as an IME composition; Meta+V fires a truste…
lawrencecchen Oct 2, 2026
c59bbb8
test: the domain policy blocks subresources from other domains
lawrencecchen Oct 2, 2026
c54d252
test: session.configure sets user agent, headers, permissions and proxy
lawrencecchen Oct 2, 2026
7ad04d3
REPL: session.configure and subresource blocking for the domain policy
lawrencecchen Oct 2, 2026
0966a73
test: a secret typed into a text field does not show in screenshots
lawrencecchen Oct 2, 2026
dbf3241
REPL: registered secrets are masked in screenshots, recordings and PDFs
lawrencecchen Oct 2, 2026
37caec7
test: googleSheets.write pastes the range and types only when the pas…
lawrencecchen Oct 2, 2026
f36ad34
REPL: googleSheets.write pastes the range, typing only as a fallback
lawrencecchen Oct 2, 2026
4a4cd18
REPL: session headers go on the driver's load; MCP test reads token-p…
lawrencecchen Oct 2, 2026
0a2c79e
REPL: composition only for contenteditable editors; form fields keep …
lawrencecchen Oct 2, 2026
142b845
test: app evidence for every differential case (brepl-in2)
lawrencecchen Oct 2, 2026
8f5e430
Merge commit 'df07e1ded40b14e8eb5c3926f16e998535d219a0' into feat-bro…
lawrencecchen Oct 2, 2026
3add499
Merge commit '8f1b7635fa17d9819b2d934c9509df55fa1bba8e' into feat-bro…
lawrencecchen Oct 2, 2026
1d81ef5
test: REPL session lifecycle: closed fetches, timer clamps, hung cell…
lawrencecchen Oct 2, 2026
9393873
REPL: a fetch after the session closes fails instead of crashing
lawrencecchen Oct 2, 2026
1d97ad7
REPL: clamp timer delays instead of trapping on huge values
lawrencecchen Oct 2, 2026
f4c6189
REPL: terminate runaway cells, cancel hung ones, close cleanly
lawrencecchen Oct 2, 2026
79817b2
test(browser-repl): linear.query must refuse hidden GraphQL mutations
lawrencecchen Oct 2, 2026
24b8f12
fix(browser-repl): parse GraphQL before linear.query runs a document
lawrencecchen Oct 2, 2026
ef3c4e5
test(browser-repl): a confirmed draft must send exactly its preview
lawrencecchen Oct 2, 2026
80e188e
fix(browser-repl): drafts keep private frozen copies and run their pr…
lawrencecchen Oct 2, 2026
0051cb5
Test REPL fs operations on symbolic links and existing destinations
lawrencecchen Oct 2, 2026
c5547e2
Act on symbolic links themselves in REPL rm, rename and lstat
lawrencecchen Oct 2, 2026
0c8084f
Test that a REPL session refuses / and the home directory as its fs root
lawrencecchen Oct 2, 2026
fb8262f
Refuse / and the home directory as the REPL fs root
lawrencecchen Oct 2, 2026
972d0f6
test: the REPL's guards must hold against agent code (findings 5-7, 1…
lawrencecchen Oct 2, 2026
231ff52
REPL: secrets, the domain policy and redaction live in the native ses…
lawrencecchen Oct 2, 2026
87528c1
Describe what the REPL session idle expiry does
lawrencecchen Oct 2, 2026
34f6633
Test that MCP servers without --session do not share a session
lawrencecchen Oct 2, 2026
bfc5de2
Give each MCP server its own REPL session by default
lawrencecchen Oct 2, 2026
0fb9402
Test that clearCookies stays on the current tab's site
lawrencecchen Oct 2, 2026
751a796
Scope clearCookies to the current tab's site
lawrencecchen Oct 2, 2026
9dcfb23
Test that the REPL pasteboard redirect reaches only WebKit's command
lawrencecchen Oct 2, 2026
021edf5
Redirect only WebKit's own pasteboard lookups, until WebKit finishes
lawrencecchen Oct 2, 2026
7efe51d
Test that a held mouse button cannot hang another session forever
lawrencecchen Oct 2, 2026
0ba0fd5
Bound the wait for a mouse another session holds
lawrencecchen Oct 2, 2026
3e8d4bc
Print REPL PDFs for an offscreen window, not the user's
lawrencecchen Oct 2, 2026
1903d3d
test(browser-repl): popup of a proxied session tab must keep its data…
lawrencecchen Oct 2, 2026
1c2292c
fix(browser-repl): open session popups in the opener's profile and store
lawrencecchen Oct 2, 2026
c308741
test(browser-repl): session behaviors must stop at tabs the session d…
lawrencecchen Oct 2, 2026
bbdc77b
fix(browser-repl): keep the user's UI in tabs the session did not create
lawrencecchen Oct 2, 2026
7a6ef3e
refactor(browser-repl): move the page agent user script into CmuxBrowser
lawrencecchen Oct 2, 2026
1bf75a9
test(browser-repl): the page agent user script must not pile up
lawrencecchen Oct 2, 2026
0f265dd
fix(browser-repl): install the page agent once per web view and remov…
lawrencecchen Oct 2, 2026
a6b245a
refactor(browser-repl): move content rule list compilation into CmuxB…
lawrencecchen Oct 2, 2026
2d4830c
test(browser-repl): a session's compiled rule list must leave the store
lawrencecchen Oct 2, 2026
7b17fbe
fix(browser-repl): remove a session's compiled rule list when cleared…
lawrencecchen Oct 2, 2026
aad7369
test(browser-repl): input a session leaves pressed must be released a…
lawrencecchen Oct 2, 2026
d0835b3
fix(browser-repl): release held keys, buttons and menu suppression on…
lawrencecchen Oct 2, 2026
d664378
test: number the session-tabs scenario 36
lawrencecchen Oct 2, 2026
d83602f
test: fetch-sends-cookies checks for the page's cookie, not the whole…
lawrencecchen Oct 2, 2026
3572e03
REPL: the driver enforces the domain policy and checks where secrets go
lawrencecchen Oct 2, 2026
f418fb1
Merge main (b685f5f6e06d) into feat-browser-repl-parity
lawrencecchen Oct 2, 2026
37a1dff
Remove the format studies and comparison harness from the public tree
lawrencecchen Oct 2, 2026
63692e0
Name the parity references neutrally
lawrencecchen Oct 2, 2026
ecf8425
docs: describe reference C capabilities without its identifiers
lawrencecchen Oct 2, 2026
06c3b0d
test: terminated cells, late cancels and late output; rename of the r…
lawrencecchen Oct 2, 2026
3783589
REPL: rename never moves or replaces the fs roots
lawrencecchen Oct 2, 2026
e4692b7
REPL: a timed-out cell cannot strand its tab, cancel the next cell or…
lawrencecchen Oct 2, 2026
60ccd20
docs: say what secrets.load protects and what it does not
lawrencecchen Oct 2, 2026
2fe9e37
Test that browserAuth refuses a frame whose origin changed
lawrencecchen Oct 2, 2026
28742a3
Check the filled frame's origin when browserAuth fills
lawrencecchen Oct 2, 2026
7fa8553
Move the text commit sequence into CmuxBrowser
lawrencecchen Oct 2, 2026
4de1883
Test that a secret is checked against the element that receives it
lawrencecchen Oct 2, 2026
f46e41c
Check a secret's target right before the text is committed
lawrencecchen Oct 2, 2026
ce54d61
Test whose session.configure options a driven tab carries
lawrencecchen Oct 2, 2026
232b019
Test that cookies follow the domain policy and a native site scope
lawrencecchen Oct 2, 2026
a7cfee1
Apply session.configure only to the tabs a session created
lawrencecchen Oct 2, 2026
ccbbd5a
Test that a domain policy WebKit cannot compile fails the session
lawrencecchen Oct 2, 2026
4662298
Guard cookies with the domain policy and scope clears natively
lawrencecchen Oct 2, 2026
ae164b7
Fail the session's calls while its domain policy does not compile
lawrencecchen Oct 2, 2026
5284074
Test that the REPL clipboard redirect ends at its timeout
lawrencecchen Oct 2, 2026
a044933
End the REPL clipboard redirect at its timeout and dismiss dialogs me…
lawrencecchen Oct 2, 2026
11453d4
test: handled-event waits, dropped listener removal, cancelled cells'…
lawrencecchen Oct 2, 2026
86c8d3a
REPL: the handled-event wait ends cleanly and a lost removal is sent …
lawrencecchen Oct 2, 2026
335e783
REPL: a cancelled cell's leftover output is dropped, its functions st…
lawrencecchen Oct 2, 2026
afe0ff5
REPL: cookies.set refuses a Domain wider than the allowed hosts
lawrencecchen Oct 2, 2026
56f2b34
Test that a late Copy or Cut never reaches the system clipboard
lawrencecchen Oct 2, 2026
d4ea7e9
End a clipboard command's web content at its timeout
lawrencecchen Oct 2, 2026
0c8aaa4
Merge main (48094359ca17) into feat-browser-repl-parity
lawrencecchen Oct 2, 2026
7ad183a
Test that page scripts in a session's tab never write the system clip…
lawrencecchen Oct 2, 2026
d92898d
Send page scripts' clipboard writes in a session's tabs to the tab cl…
lawrencecchen Oct 2, 2026
241891b
Test that a clipboard command's redirect is bounded and outlives canc…
lawrencecchen Oct 2, 2026
5df9df8
Bound a clipboard command that outlives its timeout, and ignore cance…
lawrencecchen Oct 2, 2026
d26ad94
Test that a page's write after an agent click reaches the tab clipboard
lawrencecchen Oct 2, 2026
e2bdf5c
Route a page's clipboard write without asking for transient activation
lawrencecchen Oct 2, 2026
d6809dd
Satisfy the package namespace lint and the Swift warning budget in th…
lawrencecchen Oct 3, 2026
32ab524
Replace sleep-then-assert waits in the REPL runtime and session tests
lawrencecchen Oct 3, 2026
37bf1a1
Test that a routed dialog, chooser or download belongs to one session
lawrencecchen Oct 3, 2026
208434d
Test that a generated TOTP code is redacted and capture-masked
lawrencecchen Oct 3, 2026
211ffbc
Test that pageAssets.bundle sends cookies only to the page's own origin
lawrencecchen Oct 3, 2026
ee82072
Fetch cross-origin page assets without cookies
lawrencecchen Oct 3, 2026
76f8b4a
Redact and capture-mask the TOTP codes a server still accepts
lawrencecchen Oct 3, 2026
c55a6dc
Route each dialog, file chooser and download to one session and refus…
lawrencecchen Oct 3, 2026
e0502ad
Test that Gmail body links are not taken as attachments
lawrencecchen Oct 3, 2026
e4df769
Test that a secret in a binary fetch body is redacted
lawrencecchen Oct 3, 2026
77d9699
Test that a drag holds the pointer against other sessions until it ends
lawrencecchen Oct 3, 2026
a3e0306
Take Gmail attachments only from attachment chips with Gmail URLs
lawrencecchen Oct 3, 2026
8ed1302
Redact secrets in binary fetch bodies natively
lawrencecchen Oct 3, 2026
863f585
Hold the tab's pointer for the whole of input.drag
lawrencecchen Oct 3, 2026
413ff10
Test that YouTube transcripts fetch captions only from YouTube hosts
lawrencecchen Oct 3, 2026
ce94627
Test that fs.readFile masks a registered secret
lawrencecchen Oct 3, 2026
580306c
Fetch YouTube caption URLs only from YouTube hosts
lawrencecchen Oct 3, 2026
e8417d6
Redact secrets in files read back through fs.readFile
lawrencecchen Oct 3, 2026
9739674
Test that Notion's { origin } option accepts only Notion origins
lawrencecchen Oct 3, 2026
e35dfe9
Accept only Notion's own origins for the notion { origin } option
lawrencecchen Oct 3, 2026
02919a9
Test that Jira calls only the signed-in account's Jira sites
lawrencecchen Oct 3, 2026
c70ec7f
Call only the signed-in account's Jira sites
lawrencecchen Oct 3, 2026
179c03b
Test that a REPL session's temporary directory is private to it
lawrencecchen Oct 3, 2026
643c920
Test that a WebMCP tool's readOnlyHint does not skip the confirmed draft
lawrencecchen Oct 3, 2026
b51fe31
Treat a WebMCP tool's readOnlyHint as advisory
lawrencecchen Oct 3, 2026
93b0d21
Move capture masking into CmuxBrowser so it can be tested with a web …
lawrencecchen Oct 3, 2026
894ffb3
Give each REPL session a private temporary directory as its second fs…
lawrencecchen Oct 3, 2026
ecda52b
Test that a concurrent link swap never redirects a checked fs write
lawrencecchen Oct 3, 2026
5ba3b13
Test that capture masking reaches closed shadow roots
lawrencecchen Oct 3, 2026
aa3605c
Mask captures in a content world that sees closed shadow roots
lawrencecchen Oct 3, 2026
3039d84
Test that a REPL tab's clipboard never puts a file reference on the p…
lawrencecchen Oct 3, 2026
aa73d3e
Test that capture masking fails closed
lawrencecchen Oct 3, 2026
d01caa9
Serialize REPL fs operations so a link swap cannot race a path check
lawrencecchen Oct 3, 2026
b3a6924
Test that page.exportContent transcripts fetch captions only from You…
lawrencecchen Oct 3, 2026
779f561
Move the REPL frame tree read into CmuxBrowser
lawrencecchen Oct 3, 2026
23f13e7
Keep file references off the pasteboard a REPL tab's Paste reads
lawrencecchen Oct 3, 2026
ca7b51c
Test that a timed-out cell cancels its fetches and that fetch concurr…
lawrencecchen Oct 3, 2026
97219de
Test that another web view's copy during a REPL Copy never reaches th…
lawrencecchen Oct 3, 2026
6b10142
Refuse a capture when secret masking fails or the page unmasks a value
lawrencecchen Oct 3, 2026
4ba3e24
Fetch page.exportContent captions only from YouTube hosts
lawrencecchen Oct 3, 2026
ac314f1
Cancel a timed-out cell's fetches and run at most 16 fetches per session
lawrencecchen Oct 3, 2026
5823fbc
Refuse a REPL clipboard command's pasteboard when another web view wr…
lawrencecchen Oct 3, 2026
2d03d4f
Test that pending REPL timers are bounded and overdue interval ticks …
lawrencecchen Oct 3, 2026
375e894
Test that an automated drag never uses the system's named drag pasteb…
lawrencecchen Oct 3, 2026
c5bda31
Test that auth.request's frame check refuses a page the domain policy…
lawrencecchen Oct 3, 2026
613d930
Refuse auth.request on a frame or page the domain policy blocks
lawrencecchen Oct 3, 2026
923b23b
Carry an automated drag's data on its own private pasteboard
lawrencecchen Oct 3, 2026
32cf41e
Test that the page clipboard guard reports a WebKit that cannot turn …
lawrencecchen Oct 3, 2026
234efba
Test that the domain policy covers every frame of a tab, not only the…
lawrencecchen Oct 3, 2026
7d6d207
Fail closed when the page clipboard guard cannot turn WebKit's Clipbo…
lawrencecchen Oct 3, 2026
d6cdb75
Document the REPL clipboard's interference check, file filter, drag p…
lawrencecchen Oct 3, 2026
12011b0
Bound pending REPL timers and coalesce overdue interval ticks
lawrencecchen Oct 3, 2026
4ff6354
Test that unlimited REPL output still stops at a hard ceiling
lawrencecchen Oct 3, 2026
daa483b
Keep a hard output ceiling when a REPL call asks for no limit
lawrencecchen Oct 3, 2026
fa832ea
Test that a page's unfinished requests are not kept without bound
lawrencecchen Oct 3, 2026
fc67294
Keep at most 1,000 unfinished requests per page
lawrencecchen Oct 3, 2026
6217cd6
Move the REPL cookie URL matcher into CmuxBrowser unchanged
lawrencecchen Oct 3, 2026
d4597d6
Test RFC 6265 path-match for the cookies the REPL sends
lawrencecchen Oct 3, 2026
fd0024f
Test the dev backend's TOTP, byte redaction and fail-closed captures
lawrencecchen Oct 3, 2026
866fca0
Match cookie paths by RFC 6265 for the cookies the REPL sends
lawrencecchen Oct 3, 2026
28ec27b
Test that snapshot headers drop terminal controls and stay bounded
lawrencecchen Oct 3, 2026
c9b1f93
Apply the domain policy to every frame a session reads or acts on
lawrencecchen Oct 3, 2026
e32b3c1
Strip terminal controls from snapshot headers and bound their length
lawrencecchen Oct 3, 2026
0b42a22
Test that an iframe is never matched to a child frame by geometry alone
lawrencecchen Oct 3, 2026
20b93b6
Find no frame for an iframe when the driver cannot identify it
lawrencecchen Oct 3, 2026
9a19a2f
Test that browser repl stops reading stdin at its size limit
lawrencecchen Oct 3, 2026
5ca8a4b
Test that a page cannot open a local file or internal URL as a REPL p…
lawrencecchen Oct 3, 2026
784e5ed
Stop reading browser repl stdin once it passes the 15 MiB limit
lawrencecchen Oct 3, 2026
d268722
Test that browser repl output never writes raw control characters
lawrencecchen Oct 3, 2026
1bdac76
Print control characters in browser repl output visibly
lawrencecchen Oct 3, 2026
8e357b6
Open a page's window as a REPL popup only as an untrusted navigation
lawrencecchen Oct 3, 2026
a2bcd43
Merge main (ef96c2ec96fb) into feat-browser-repl-parity
lawrencecchen Oct 3, 2026
1ff8ac3
Bring the dev backend's secret guards in line with the native session
lawrencecchen Oct 3, 2026
4e48716
Keep main's string catalog order; add only the REPL's 17 keys
lawrencecchen Oct 3, 2026
6320a09
Merge remote-tracking branch 'origin/sec-sites' into feat-browser-rep…
lawrencecchen Oct 3, 2026
6e4a94e
Merge remote-tracking branch 'origin/sec-secrets' into feat-browser-r…
lawrencecchen Oct 3, 2026
6a6693a
Merge remote-tracking branch 'origin/sec-clipboard' into feat-browser…
lawrencecchen Oct 3, 2026
15839c6
Test that a page's cookie calls name its tab
lawrencecchen Oct 3, 2026
dcd478a
Name the page's tab on every cookie call so the driver uses that tab'…
lawrencecchen Oct 3, 2026
f747a67
Test that a secret typed into a tab stays masked for other sessions r…
lawrencecchen Oct 3, 2026
1d6d5b5
Keep a secret typed into a tab masked for every other session that re…
lawrencecchen Oct 3, 2026
66bb389
Merge remote-tracking branch 'origin/sec-policy' into feat-browser-re…
lawrencecchen Oct 3, 2026
d27f0ba
Merge remote-tracking branch 'origin/sec-sessions' into feat-browser-…
lawrencecchen Oct 3, 2026
d9213fd
Merge remote-tracking branch 'origin/sec-resources' into feat-browser…
lawrencecchen Oct 3, 2026
977ad53
Test that a popup of a user's tab never goes to a REPL session
lawrencecchen Oct 4, 2026
7b2aeb6
Leave a user's tab's popups to the browser, never to a REPL session
lawrencecchen Oct 4, 2026
fe7f6a3
Test that storage state keeps localStorage in the page's data store
lawrencecchen Oct 4, 2026
ab2142f
Test that the REPL wakes a hibernated tab and explains tabs it cannot…
lawrencecchen Oct 4, 2026
95aaa46
Wake a hibernated tab before a REPL call and explain tabs it cannot r…
lawrencecchen Oct 4, 2026
484ce2b
browser repl: test editor-style copy/cut handlers complete through th…
lawrencecchen Oct 4, 2026
67a74ea
Test that an agent's Tab out of a driven page leaves the user's focus…
lawrencecchen Oct 4, 2026
00b8acd
Keep an agent's Tab out of a driven page from moving the user's focus
lawrencecchen Oct 4, 2026
8a68670
browser repl: failing tests for typed-secret keys, TOTP names and red…
lawrencecchen Oct 4, 2026
4028706
Test that a dialog or file panel an agent's input opens in a user's t…
lawrencecchen Oct 4, 2026
e23e4a0
Send a dialog or file panel an agent's input opens in a user's tab to…
lawrencecchen Oct 4, 2026
f494b37
browser repl: key typed secrets by tab, session and name; mask typed …
lawrencecchen Oct 4, 2026
47ee447
Keep storage state's localStorage in the page's own data store
lawrencecchen Oct 4, 2026
25ac297
Name the tab and workspace that ask on the REPL sign-in sheet
lawrencecchen Oct 4, 2026
08cfd5b
Test that a shadow root's own text is masked in captures
lawrencecchen Oct 4, 2026
8773a04
Mask a shadow root's own text through its host in captures
lawrencecchen Oct 4, 2026
7e5b580
Test that capture masking follows the documents frames show
lawrencecchen Oct 4, 2026
5c86e6e
Bind capture masking to the documents frames show
lawrencecchen Oct 4, 2026
a226429
Test that secret typing judges the document that holds the focus
lawrencecchen Oct 4, 2026
b1a9b20
browser repl: failing test for frame-gate probes that never answer
lawrencecchen Oct 4, 2026
62ea40c
browser repl: bound frame-gate probes and refuse with stale on timeout
lawrencecchen Oct 4, 2026
7f46471
Judge secret typing by the origin of the document that holds the focus
lawrencecchen Oct 4, 2026
6a55238
Test that every JavaScript run on a REPL session's thread is bounded
lawrencecchen Oct 4, 2026
3883d37
browser repl: failing tests for blanking blocked frames in captures, …
lawrencecchen Oct 4, 2026
85b890d
Bound every JavaScript run on a REPL session's thread
lawrencecchen Oct 4, 2026
98fb960
browser repl: blank blocked frames in screenshots, judge file chooser…
lawrencecchen Oct 4, 2026
6776546
browser repl: failing tests for clipboard type evasions (UTF-16 file …
lawrencecchen Oct 4, 2026
ae3e115
browser repl: paste only allowlisted types from a tab's clipboard
lawrencecchen Oct 4, 2026
f79e5ef
Name the tabs.dataStore handler and avoid a tuple key path
lawrencecchen Oct 4, 2026
3a0cf6e
browser repl: failing test for Jira under allowedDomains without home…
lawrencecchen Oct 4, 2026
b8510e9
browser repl: Jira tools check a tenant on its own origin when home.a…
lawrencecchen Oct 4, 2026
04bea00
browser repl: name the real reason a drag's pasteboard window did not…
lawrencecchen Oct 4, 2026
57df649
browser repl: failing test for cookies through a closed page's context
lawrencecchen Oct 4, 2026
7cb7a62
browser repl: a closed page's context reads and adds cookies through …
lawrencecchen Oct 4, 2026
0cedc5a
Test that an agent's unhandled key cannot reach the user's key window
lawrencecchen Oct 4, 2026
ecb9207
Drop WebKit's resend of an agent's unhandled key so it never reaches …
lawrencecchen Oct 4, 2026
63fb64c
Document background-safe focus and hibernated or crashed tabs in the …
lawrencecchen Oct 4, 2026
b292f09
browser repl: start the frame-gate test's busy loop from a synchronou…
lawrencecchen Oct 4, 2026
54969f7
Test the runtime after cmux replaces a tab's web view, and input-caus…
lawrencecchen Oct 4, 2026
9665937
Tell the runtime when cmux replaces a tab's web view; match input-cau…
lawrencecchen Oct 4, 2026
0840f18
Document the tab.replaced driver event
lawrencecchen Oct 4, 2026
7ae849a
Test that focus not in a driven web view never moves when WebKit asks…
lawrencecchen Oct 4, 2026
6c6c3ce
Make the focus test's Shift+Tab start from a view the move would leave
lawrencecchen Oct 4, 2026
68478e4
Let focus leave a web view only when AppKit focus is in it, never fro…
lawrencecchen Oct 4, 2026
6a7e8a3
List and wake browser tabs a relaunch has not loaded yet
lawrencecchen Oct 4, 2026
51fa25c
Document that tabs a relaunch has not loaded list as hibernated
lawrencecchen Oct 4, 2026
9c2654a
Test that reloading a crashed or hibernated tab recovers it and waits…
lawrencecchen Oct 4, 2026
6458b2f
Reload a crashed or hibernated tab by bringing its page back, and wai…
lawrencecchen Oct 4, 2026
63dd9b2
Merge remote-tracking branch 'origin/sec-r3' into feat-browser-repl-p…
lawrencecchen Oct 4, 2026
7ebeedd
Merge remote-tracking branch 'origin/sec-bgfocus' into feat-browser-r…
lawrencecchen Oct 4, 2026
d42b05a
Test that the mobile browser stream's keys keep WebKit's resend to th…
lawrencecchen Oct 4, 2026
6299efd
Drop WebKit's key resend only for REPL and cmux browser press keys
lawrencecchen Oct 4, 2026
8f82666
Test that a PDF refuses, and a screenshot blanks, a child frame whose…
lawrencecchen Oct 4, 2026
e25c7ea
Test review findings: user-tab popups from agent input, wake cancella…
lawrencecchen Oct 4, 2026
13f3eef
Judge a capture's frames on the documents the mask marked
lawrencecchen Oct 4, 2026
76c8926
Test that capture mask and secret target scripts that never answer fa…
lawrencecchen Oct 4, 2026
1d9d3f8
Bound capture mask and secret target scripts like the frame gate's pr…
lawrencecchen Oct 4, 2026
071534e
Test that a dialog the agent's script opens in a user's tab goes to t…
lawrencecchen Oct 4, 2026
b0f4667
Test that a freed data store's id is not given to a later store
lawrencecchen Oct 4, 2026
b8f75a8
Give each data store a random id it carries, never reused
lawrencecchen Oct 4, 2026
57bb412
Record a typed secret only after its domain check passes
lawrencecchen Oct 4, 2026
d97997c
Fix review findings on background-safe input and tab wake
lawrencecchen Oct 4, 2026
7ec3018
Record the state key of tabs.list in the 13-tabs golden and the oracl…
lawrencecchen Oct 4, 2026
dc30d4d
Merge remote-tracking branch 'origin/sec-capfix' into feat-browser-re…
lawrencecchen Oct 4, 2026
ff3f96b
Refuse only cookies.clear on a tab that shows a blocked page
lawrencecchen Oct 4, 2026
8d56d9b
Test that files a session writes go straight into its private tempora…
lawrencecchen Oct 4, 2026
76ad507
Write session files straight into its private temporary directory; te…
lawrencecchen Oct 4, 2026
fcd1f3f
Test that a page script's action window closes at a bound
lawrencecchen Oct 4, 2026
1bf6df0
Bound a page script's action window; open a driven background tab's l…
lawrencecchen Oct 4, 2026
7769bf5
Merge main (161e1cce9f6c) into feat-browser-repl-parity
lawrencecchen Oct 4, 2026
91f4ed1
Keep main's string catalog order after merging main
lawrencecchen Oct 4, 2026
0cebbbe
Test that a callback queued ahead of the next cell is stopped by the …
lawrencecchen Oct 4, 2026
58820dd
Count a run as a cell's only once the session's thread has started th…
lawrencecchen Oct 4, 2026
a5c271a
Wait on signals, not timers, in the REPL timer-bound and tab-wake tests
lawrencecchen Oct 4, 2026
89c84cb
Run every WebKit pasteboard test against a stand-in system pasteboard
lawrencecchen Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
613 changes: 613 additions & 0 deletions CLI/CMUXCLI+BrowserRepl.swift

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CLI/CMUXCLI+TaskHelp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ extension CMUXCLI {
browser url|get-url
browser snapshot [--interactive|-i] [--cursor] [--compact] [--max-depth <n>] [--selector <css>]
browser eval <script>
browser repl [--session <name>] [--workspace <id|ref>] [--eval <code>|-] [<code>] (see: browser repl guide)
browser wait [--selector <css>] [--text <text>] [--url-contains <text>] [--load-state <interactive|complete>] [--function <js>] [--timeout-ms <ms>]
browser click|dblclick|hover|focus|check|uncheck|scroll-into-view <selector> [--snapshot-after]
browser type <selector> <text> [--snapshot-after]
Expand Down
10 changes: 8 additions & 2 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16656,7 +16656,7 @@ struct CMUXCLI {
var surfaceRaw = surfaceOpt
var args = argsWithoutSurfaceFlag

let verbsWithoutSurface: Set<String> = ["open", "open-split", "new", "identify", "import", "profile", "profiles", "react-grab", "reactgrab", "devtools", "dev-tools", "focus-mode", "design-mode", "zoom", "history"]
let verbsWithoutSurface: Set<String> = ["open", "open-split", "new", "identify", "import", "profile", "profiles", "react-grab", "reactgrab", "devtools", "dev-tools", "focus-mode", "design-mode", "zoom", "history", "repl"]
if surfaceRaw == nil, let first = args.first {
if !first.hasPrefix("-") && !verbsWithoutSurface.contains(first.lowercased()) {
surfaceRaw = first
Expand Down Expand Up @@ -18254,6 +18254,11 @@ struct CMUXCLI {
return
}

if subcommand == "repl" {
try runBrowserRepl(subArgs, client: client, jsonOutput: effectiveJSONOutput)
return
}

if subcommand == "viewport" {
let params = try Self.browserViewportSetParams(subArgs, surfaceID: requireSurface())
let payload = try client.sendV2(method: "browser.viewport.set", params: params)
Expand Down Expand Up @@ -21190,6 +21195,7 @@ struct CMUXCLI {
addinitscript|addscript [--script <js> | <js>]
addstyle [--css <css> | <css>]
\(Self.browserViewportHelp)
\(Self.browserReplHelp)
geolocation|geo <latitude> <longitude>
offline <true|false>
trace <start|stop> [path]
Expand Down Expand Up @@ -42655,7 +42661,7 @@ export default {
print()
}

private func resolvedVersionInfo() -> [String: String] {
func resolvedVersionInfo() -> [String: String] {
var info: [String: String] = [:]
if let main = versionInfo(from: Bundle.main.infoDictionary) {
info.merge(main, uniquingKeysWith: { current, _ in current })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct BrowserKeyboardEvent: Equatable, Sendable {
}
}

private init(key: String, code: String, legacyKeyCode: Int, location: Int = 0) {
init(key: String, code: String, legacyKeyCode: Int, location: Int = 0) {
self.key = key
self.code = code
self.legacyKeyCode = legacyKeyCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ public protocol BrowserSuggestedFilenameOverriding: AnyObject {
/// Blank names are ignored.
func setSuggestedFilenameOverride(_ suggestedFilename: String?, for download: WKDownload)
}

/// A download delegate that wants scripted `data:` downloads (a page's
/// `<a download>` links) as WebKit downloads it receives, rather than saved
/// directly by the web view. The browser REPL uses this so a driven tab
/// reports those downloads to its session. Read on the main thread.
public protocol BrowserScriptedDownloadRouting: AnyObject {
var routesScriptedDownloadsThroughWebKit: Bool { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,26 @@ public final class BrowserNativeInputDeliveryOwner {

func withDispatch<T>(_ body: () -> T) -> T {
dispatchDepth += 1
defer { dispatchDepth = max(0, dispatchDepth - 1) }
Self.activeDispatchCount += 1
defer {
dispatchDepth = max(0, dispatchDepth - 1)
Self.activeDispatchCount = max(0, Self.activeDispatchCount - 1)
}
return body()
}

/// Native key deliveries in progress in any web view.
private static var activeDispatchCount = 0

/// Whether any web view is delivering an automated key right now. WebKit's
/// resend of an unhandled key runs on a later turn, outside every delivery.
public static var isAnyDispatchActive: Bool { activeDispatchCount > 0 }

/// Key codes of modifiers currently held by automation.
public var heldModifierKeyCodes: [UInt16] {
Array(heldModifierKeys.keys)
}

public func setModifier(_ modifier: BrowserKeyboardNativeModifiers, for keyCode: UInt16) {
heldModifierKeys[keyCode] = modifier
}
Expand Down Expand Up @@ -118,13 +134,27 @@ extension WKWebView {
forBrowserNativeKey: nativeKey,
additionalModifierFlags: activeModifiers
)
return replayBrowserKeyboardSpecification(
let result = replayBrowserKeyboardSpecification(
specification,
action: action,
characters: nativeKey.characters
characters: nativeKey.characters,
marksBrowserAutomation: true
)
// WebKit leaves Command+A/C/X/V/Z to the app's Edit menu, which the
// resend of an automated key no longer reaches; run the command on
// this web view, as the REPL does, never on the key window.
if result == .delivered, action != .keyUp,
let command = BrowserReplKeyStroke.editingCommand(code: event.code, key: event.key, flags: specification.modifierFlags),
Self.menuEditingCommands.contains(command) {
let selector = NSSelectorFromString(command)
if responds(to: selector) { _ = perform(selector, with: nil) }
}
return result
}

/// Edit menu commands `cmux browser press` runs on the web view itself.
static let menuEditingCommands: Set<String> = ["selectAll:", "copy:", "cut:", "paste:", "undo:", "redo:"]

/// Delivers an already-resolved AppKit key specification. The mobile
/// browser stream and socket automation both use this seam so key-down
/// re-entry handling and event construction cannot diverge.
Expand All @@ -133,25 +163,34 @@ extension WKWebView {
/// - specification: AppKit key-code and modifier metadata.
/// - action: Whether to send a press, key-down, or key-up.
/// - characters: Optional Unicode text to attach to the event.
/// - marksBrowserAutomation: Marks the events as automation's
/// (``NSEvent/isBrowserAutomationKeyEvent``) so the app drops WebKit's
/// resend of one no page handled. The REPL and `cmux browser press`
/// mark their keys; the mobile browser stream, a person's keys from a
/// phone, does not, so its unhandled Command shortcuts still reach the
/// Mac's menus.
/// - Returns: The native delivery outcome.
@discardableResult
public func replayBrowserKeyboardSpecification(
_ specification: SyntheticKeySpecification,
action: BrowserKeyboardAction,
characters: String? = nil
characters: String? = nil,
marksBrowserAutomation: Bool = false
) -> BrowserKeyboardReplayResult {
let timestamp = ProcessInfo.processInfo.systemUptime
let down = SyntheticKeyEventFactory.keyEvent(
specification: specification,
keyDown: true,
timestamp: timestamp,
characters: characters
characters: characters,
marksBrowserAutomation: marksBrowserAutomation
)
let up = SyntheticKeyEventFactory.keyEvent(
specification: specification,
keyDown: false,
timestamp: timestamp,
characters: characters
characters: characters,
marksBrowserAutomation: marksBrowserAutomation
)

switch action {
Expand Down Expand Up @@ -215,6 +254,14 @@ extension WKWebView {
return owner
}

func replayBrowserNativeModifier(
_ key: BrowserKeyboardNativeKey,
keyDown: Bool
) -> BrowserKeyboardReplayResult {
guard let modifierKey = key.modifierKey else { return .unsupported }
return replayBrowserModifier(key, modifierKey: modifierKey, action: keyDown ? .keyDown : .keyUp)
}

private func replayBrowserModifier(
_ key: BrowserKeyboardNativeKey,
modifierKey: BrowserKeyboardNativeModifiers,
Expand Down Expand Up @@ -292,3 +339,28 @@ extension WKWebView {
}
}
}

/// Keys browser automation (the REPL, `cmux browser press`) delivers to a
/// web view; the mobile browser stream's keys, a person's, are not marked. When no page handles such a key,
/// WebKit sends it back through `NSApp.sendEvent` (WebViewImpl's
/// doneWithKeyEvent), which hands it to the key window: the user's window,
/// whose first responder (a terminal) would receive the text and whose menus
/// would run Command shortcuts. The page has already received the key, so the
/// app drops that resend (``isResentBrowserAutomationKeyEvent``).
extension NSEvent {
/// `CGEventField.eventSourceUserData` of an automated browser key ("cmuxkeys").
static let browserAutomationKeyMark: Int64 = 0x636D_7578_6B65_7973

/// Whether browser automation created this key event for a web view.
public var isBrowserAutomationKeyEvent: Bool {
guard type == .keyDown || type == .keyUp || type == .flagsChanged, let cgEvent else { return false }
return cgEvent.getIntegerValueField(.eventSourceUserData) == Self.browserAutomationKeyMark
}

/// Whether this is an automated browser key reaching the app outside the
/// web view's own delivery: WebKit's resend of a key no page handled.
@MainActor
public var isResentBrowserAutomationKeyEvent: Bool {
isBrowserAutomationKeyEvent && !BrowserNativeInputDeliveryOwner.isAnyDispatchActive
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,25 @@ public enum SyntheticKeyEventFactory {

/// Builds an `NSEvent` backed by a real `CGEvent` so WebKit text input can
/// safely interpret it. Callers choose their own direct delivery target.
///
/// - Parameter marksBrowserAutomation: Marks the event as a key browser
/// automation delivers to a web view (``NSEvent/isBrowserAutomationKeyEvent``),
/// so the app can drop WebKit's resend of it to the key window.
public static func keyEvent(
specification: SyntheticKeySpecification,
keyDown: Bool,
timestamp: TimeInterval,
characters: String? = nil
characters: String? = nil,
marksBrowserAutomation: Bool = false
) -> NSEvent? {
guard let cgEvent = CGEvent(
keyboardEventSource: nil,
virtualKey: specification.keyCode,
keyDown: keyDown
) else { return nil }
if marksBrowserAutomation {
cgEvent.setIntegerValueField(.eventSourceUserData, value: NSEvent.browserAutomationKeyMark)
}
cgEvent.flags = cgFlags(specification.modifierFlags)
cgEvent.timestamp = CGEventTimestamp(timestamp * 1_000_000_000)
if let characters {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
public import WebKit

/// The REPL page agent as a document-start user script in every frame's
/// agent world, so documents loaded while a session drives a tab have the
/// agent before their own scripts run. Frames that loaded earlier get it on
/// their first evaluation.
///
/// The script runs only while the agent world has the presence message
/// handler (`presenceHandlerName`), which exists while a session is attached.
/// A controller holds at most one copy, whichever attachment installed it,
/// and the copy leaves the controller when the tab's last session detaches
/// or the tab moves to another web view. Other user scripts stay.
@MainActor
public final class BrowserReplAgentUserScript {
/// Every agent script this process created, to tell them apart from the
/// controller's other user scripts.
private static let agentScripts = NSHashTable<WKUserScript>.weakObjects()

private weak var controller: WKUserContentController?

public init() {}

/// Adds the agent to `controller` unless it already has it, and removes
/// it from the controller this installer used before.
/// - Parameters:
/// - source: The page agent's install source.
/// - presenceHandlerName: The agent-world message handler whose
/// presence lets the script run.
/// - world: The agent's content world.
/// - controller: The tab's user content controller.
public func install(
source: String,
presenceHandlerName: String,
world: WKContentWorld,
in controller: WKUserContentController
) {
if let previous = self.controller, previous !== controller {
Self.remove(from: previous)
}
self.controller = controller
guard !controller.userScripts.contains(where: Self.agentScripts.contains) else { return }
let guarded = """
if (globalThis.webkit && webkit.messageHandlers && webkit.messageHandlers.\(presenceHandlerName)) {
\(source)
}
"""
let script = WKUserScript(
source: guarded,
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
in: world
)
Self.agentScripts.add(script)
controller.addUserScript(script)
}

/// Removes the agent from the controller; called when no session
/// remains attached to the tab.
public func release() {
if let controller { Self.remove(from: controller) }
controller = nil
}

/// WebKit removes user scripts only all at once, so this re-adds every
/// script that is not the agent, in order. `userScripts` is a live view
/// of the controller's list, so it is copied first.
private static func remove(from controller: WKUserContentController) {
let scripts = controller.userScripts.map { $0 }
guard scripts.contains(where: agentScripts.contains) else { return }
controller.removeAllUserScripts()
for script in scripts where !agentScripts.contains(script) {
controller.addUserScript(script)
}
}
}
Loading