@modelprofile.com/browser-runtime 4.4.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.hints.md CHANGED
@@ -8,11 +8,12 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
8
8
  - Persistent, fsynced `runtime.lock` metadata is a downgrade fence, not the live mutex, and is never removed by normal shutdown. Every start owns one metadata-named generation containing private profile and artifact roots.
9
9
  - Metadata-generation recovery performs no lock-descriptor scan. It removes only the exact named generation after checking every same-UID process for an absolute `--user-data-dir` equal to or below that profile root; this check is process-generic, not Chromium-specific.
10
10
  - Process ownership comes from all four `/proc/<pid>/status` `Uid:` values, not `/proc/<pid>` directory ownership. Only an exact same-UID or definite other-UID classification is actionable; mixed, unreadable, malformed, symlinked, wrongly owned/mode, hard-linked, ambiguous, or uncontained ownership state remains fenced.
11
+ - An empty process command line triggers one status revalidation. A conclusively departed PID (both status and PID directory missing), definite other UID, or exact same-UID zombie is clear. A still-present PID with missing/unreadable status, ambiguous UID, or malformed/duplicate state remains fenced; exit between command-line and status reads does not block startup.
11
12
  - Legacy zero-byte locks alone use SmartIPC's consuming kernel probe for other read/write open descriptions. Adoption additionally requires birth, change, and modification timestamps strictly before bounded `/proc/stat` `btime`, plus no same-UID process using the exact or descendant legacy profile root. After an exclusive probe, Runtime reopens and revalidates the exact original inode, ownership, mode, link count, zero size, and timestamps. Probe contention is locked; probe or reopen uncertainty, same-boot, malformed, or ambiguous state remains fenced against the 3.2 close-before-unlink race. `O_PATH` descriptors do not contend.
12
13
  - A clean 3.2 stopped layout has no lock and may retain private empty top-level profile and artifact roots. The native-mutex winner validates, process-checks, and removes only those roots. Before metadata fsync succeeds, failure cleanup may unlink only the exact lock inode that acquisition just created; preexisting locks are never removed.
13
14
  - Shutdown attempts artifact and ownership cleanup after peer, revocation, or resource failures, retains retryable local slot state, and blocks restart until that state clears. Automatic ownership-loss cleanup gets one bounded retry; stale active metadata remains a fail-closed recovery fence.
14
15
  - Controller durable truth is a logical resource with immutable `projectId`, stable random `browserResourceId`, and a revisioned attachment binding. Runtime state is process-local and must be re-registered after every restart.
15
- - A resource slot owns only incarnation and arbitration state: its transition mutex, arbitration, incarnation, and process-local authority generations, lease, bounded operation FIFO, bounded frame window, Chromium session, authenticated egress proxy, private profile, and idle termination timer.
16
+ - A resource slot owns its browser incarnation, transition mutex, generations, exact participant leases, shared bounded FIFO, independent viewer frame windows, authenticated egress proxy, private profile, and idle timer. Joining or releasing a participant does not change the resource arbitration generation.
16
17
  - `terminateResource()` and idle termination remove only the incarnation. They preserve registration, attachment, capabilities, and artifacts. `retireResource()` permanently fences, revokes, terminates, purges exact-resource artifacts, then unregisters only after all cleanup succeeds. Durable retirement remains Controller truth; Runtime tombstones are bounded and process-local.
17
18
  - Failed retirement retains a fenced, retryable registration. Unknown profile, lock, or artifact ownership remains fail closed.
18
19
  - Launch quota admission uses synchronous pending reservations across global and per-project running-resource limits.
@@ -20,15 +21,17 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
20
21
  ## Attachment and capability boundary
21
22
 
22
23
  - Attachment revision is independent from incarnation and arbitration generations. Equal revisions are idempotent only for an identical binding; lower or conflicting equal revisions fail.
23
- - Any `sessionId: null` binding is detached; revision zero is specifically the initial detached/no-agent-authority state. A newer attachment synchronously closes admission before waiting for the resource mutex, revokes its capabilities, and quiesces its active work. Work that ignores cancellation escalates only that resource incarnation.
24
- - Agent capabilities require the exact current non-detached qualified session. Human capabilities bind the exact resource, project, attachment authority, and revision but intentionally carry no session ID and may be issued while detached. Any attachment advance revokes both human and agent capabilities.
24
+ - Any `sessionId: null` binding is detached; revision zero is the initial detached/no-agent-authority state. Attachment transitions fence and revoke only agent authority. Human viewers keep resource access. An agent operation that cannot quiesce still requires terminating that resource's incarnation.
25
+ - Agent capabilities require the exact current qualified session. Human issuance validates the attachment current at issuance, but subsequent human operations authorize the exact project/resource/actor/peer independently from agent attachment changes. Flex run channels use trusted pipes; independently authenticated MCP bindings support OpenCode, Codex, and Flex session IDs without impersonating a Flex run channel.
25
26
  - Capability tokens are returned once. Runtime records retain only SHA-256 digests and use `timingSafeEqual`. Authorization is rechecked after asynchronous host authorization, during lease acquisition, and before and after every operation.
26
27
  - Capability, audit, and lease identity includes immutable project, resource, attachment authority and revision, actor, peer, role, source, qualified agent session, and Flex scope/channel/run where applicable.
27
- - Same-lease operations use a strict bounded FIFO. Queue cancellation composes external abort, lease revocation, attachment transition, resource termination, and runtime shutdown. Only dequeued work reaches `beforeOperation`; started uncertain work must quiesce or fence its exact incarnation before terminal audit and queue advancement.
28
- - Human frame refresh is a `frame-stream` operation. It captures the exact subscription before admission, drains the old application window, and validates SmartPuppeteer's returned first-frame identity against one constant-sized delivered boundary candidate and the current lease, session, incarnation, tab generation, and viewport.
28
+ - All participant operations share a resource FIFO with per-lease and aggregate bounds. A released participant's cleanup joins that same FIFO with exact retained lease identity, bypasses user-operation authorization only for resource-owned input release/viewport recomputation, and retains lifecycle cancellation and operation timeout enforcement.
29
+ - Human frame refresh enters the shared FIFO, captures the initiating subscription, clears viewer windows, waits for producer acknowledgements, and validates one constant-sized new-generation boundary. A running open tab may refresh while streaming is false. New frames fan out to all participants.
29
30
  - `beforeOperation` reserves the exact resource and lets the host persist policy/audit through an awaited fail-closed gate. Runtime then atomically revalidates lease, attachment, session, arbitration generation, and incarnation before starting the side effect. Both host hooks carry the same exact operation ID and classification; terminal audit remains best effort and runs after bounded cleanup releases or fences the exact reservation.
30
31
  - Lease authority snapshots are immutable process-local revalidation tokens. They expose `authorityGeneration` and bind the runtime instance, incarnation, exact `capabilityId`, exact `leaseId`, and attachment, but never replace Controller durable attachment truth.
31
32
 
33
+ - Viewport preferences are per human lease. Effective width, height and DSF use the componentwise minimum; results carry the actual viewport revision, including unchanged-size responses. Departure expands the viewport through serialized resource-owned cleanup and retains its cleanup marker until the resize succeeds, so failed cleanup remains retryable. Input ownership is bounded to 64 keys and 5 buttons per participant, and departure preserves another participant's held input.
34
+
32
35
  ## Browser confinement
33
36
 
34
37
  - Production session options are assembled only inside `BrowserRuntime`: sandbox required, random private per-resource user data directory, one authenticated proxy per running resource, fixed network-reduction arguments, denied downloads/file choosers/permissions, and HTTP(S)-only public navigation.
@@ -47,11 +50,11 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
47
50
  - Framed messages contain no identity selectors. The parent binds project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, channel, and exact run out of band.
48
51
  - One qualified session can own multiple resource-specific framed channels. Capability revocation owns server-peer disconnect; peer-initiated release/close revokes only that channel's capability without recursively disconnecting itself.
49
52
  - Framed request counts and queued write bytes are bounded. Runtime shutdown has a bounded caller-visible cleanup deadline while retaining in-flight cleanup ownership for retry.
50
- - Frame delivery owns a bounded map of exact identities and configures SmartPuppeteer's separate private CDP-to-ack map to the same limit. Equal limits prevent private eviction while the application map has spare capacity, but private-first retirement at full capacity can make an exact acknowledgement fulfill `{ accepted: false }`. Both fulfilled booleans settle the application entry without proving successful upstream retirement; a rejected or timed-out acknowledgement operation toward SmartPuppeteer fails closed. Failed delivery (a throwing listener) deliberately fails the lease regardless of a fulfilled boolean. There is no cumulative frame protocol.
51
- - A late application acknowledgement (`frameAcknowledgementTimeoutMs`, default 10 s) is retired through `retireOutstandingFrame(subscription, key, false)`, the same path as oldest-first eviction: the entry is acknowledged toward SmartPuppeteer and the lease is untouched. It must never route through `handleFrameFailure`; one slow viewer acknowledgement previously fenced the slot and terminated the whole Chromium incarnation. A later explicit acknowledgement of the retired identity returns `false`.
52
- - A frame above `maxFrameBytes` is dropped, acknowledged in the background with `failLease=false`, and reported as `FRAME_TOO_LARGE` with `fatal: false`. `highestSequence` is advanced before the size check so the following frames stay valid. During an active refresh the oversized frame still fails the refresh with `FRAME_TOO_LARGE`, but the pushed error event is non-fatal.
53
+ - Each viewer has a bounded exact-identity frame map. Each incoming frame starts one bounded producer ACK whose outcome is shared by viewer records. Producer ACK promises have a separate hard bound; viewer lag and eviction cannot re-ACK or block Chrome. Producer failures fence the exact incarnation regardless of which participants subsequently joined it. A failure from an older incarnation cannot fence its replacement.
54
+ - Late viewer acknowledgements and oldest-first eviction only retire the local map entry. A throwing listener revokes its own participant. Neither event routes through resource-wide producer failure handling.
55
+ - A frame above `maxFrameBytes` advances the resource sequence watermark, is acknowledged once and dropped, and reports nonfatal `FRAME_TOO_LARGE` to viewers. During refresh it also fails the refresh boundary with `FRAME_TOO_LARGE`.
53
56
  - `screencast` (`quality` 0..100 default 70, `maxWidth`/`maxHeight` 1..4096 each with `maxWidth * maxHeight <= 8_294_400` and defaults 2560/1600, `everyNthFrame` 1..60 default 1, `firstFrameTimeoutMs` 1000..60000 default left to SmartPuppeteer) is validated in the constructor with `validateExactKeys` plus the integer validators and forwarded to `LiveBrowserSession` next to `maxOutstandingFrames`. The ceilings deliberately match `@push.rocks/smartpuppeteer` 2.6's `LiveBrowserSession` validation (`maxViewportPixelArea = 8294400`, `maxTimeoutMs = 60000`); a looser runtime bound would let `new BrowserRuntime()` succeed and every incarnation launch fail instead. Without `quality`/`maxWidth`/`maxHeight`, SmartPuppeteer's defaults (quality 80, unbounded size) produced multi-megabyte frames on device-pixel-ratio 2 viewers.
54
- - During refresh, application entries plus refresh-owned acknowledgement promises share the same hard frame-window bound. ACK ownership is fixed at admission, refresh failures defer until the operation reservation is gone, and the exact failure fence completes before terminal audit, caller settlement, or FIFO advancement. Lifecycle teardown explicitly abandons an exact refresh record so stale state cannot retain `BUSY` admission.
55
- - Runtime state and error events preserve diagnostic messages only after truncating them to 2,048 characters. The first fatal session error invalidates exact capability admission synchronously, delivers once to the captured exact subscriber, and schedules revocation in `finally` so a throwing listener cannot retain authority.
57
+ - Refresh records track bounded producer ACK work independently of viewer windows. Producer failure cleanup is coalesced by exact session; affected operation completion waits for the fence and process cleanup. Closing an initiating subscription abandons its refresh without revoking other viewers.
58
+ - Runtime state/error messages are bounded to 2,048 characters. Fatal producer errors revoke every current participant synchronously before captured error delivery; released participants receive no duplicate fatal notification.
56
59
  - Flex resolves only a capability token, and the provider's run must exactly match its trusted framed client. SmartAgent exposes exactly navigate, snapshot, screenshot, click, fill, and press.
57
60
  - MCP independent authentication returns the complete expected binding. The binding is retained in server-owned auth context and tool schemas expose no resource, session, revision, or authority selector.
package/readme.md CHANGED
@@ -72,7 +72,7 @@ try {
72
72
  }
73
73
  ```
74
74
 
75
- Projects and qualified OpenCode, Flex, and Codex sessions may each own many resources. Codex attachment identities support human browser views; agent capabilities still require a supported Flex or OpenCode MCP transport. Human/agent arbitration, mutexes, leases, operation queues, profiles, proxies, frame subscriptions, and idle timers are per resource.
75
+ Projects and qualified OpenCode, Flex, and Codex sessions may each own many resources. Agent capabilities use Flex's trusted pipe or an independently authenticated MCP binding for any supported native session. Each resource has one browser incarnation and operation queue shared by its attached agent and multiple human viewers. Joining a viewer never preempts another participant.
76
76
 
77
77
  `registerResource()` is idempotent only for the same project/resource key and identical attachment. `listResources()` reports process-local registration and incarnation metadata. `terminateResource()` terminates only the current incarnation and preserves registration, attachment, and artifacts. `retireResource()` permanently fences the process-local registration, revokes and quiesces authority, terminates its incarnation, purges exact-resource artifacts, and unregisters only after cleanup succeeds. The Controller separately owns durable retirement truth and must not rehydrate retired resources. Runtime tombstones and registrations are ephemeral, bounded process state. `stop()` attempts peer, capability, resource, artifact-store, and ownership cleanup even when an earlier stage fails, then reports all failures together. It removes only the current generation, durably marks its owner metadata relinquished, and releases the native ownership lease when ownership cleanup succeeds. It intentionally leaves `runtime.lock` and the mutex anchor in place. Failed local slot cleanup remains retryable and blocks a new generation; automatic ownership-loss shutdown performs one additional bounded cleanup attempt. `cleanupTimeoutMs` defaults to 30,000 milliseconds and accepts values from 100 through 120,000. If that caller-visible deadline expires, `stop()` rejects with `TIMEOUT` while Runtime retains cleanup still in flight; a later `stop()` rejoins it, and `start()` waits for all prior local cleanup before restarting.
78
78
 
@@ -90,6 +90,8 @@ When version 3.2 stopped cleanly and removed `runtime.lock`, it may leave empty
90
90
 
91
91
  ## Breaking Changes
92
92
 
93
+ Shared control replaces exclusive human takeover. Hosts must allow multiple exact participant leases, stop treating chat attachment changes as human-view revocation, and consume the effective viewport returned by `setViewport()`. Browser destruction remains an explicit resource operation.
94
+
93
95
  The version 3.2 runtime-directory migration is one-way. A cleanly stopped 3.2 runtime that removed its zero-byte lock can be upgraded directly when any remaining top-level `profiles` and `artifacts` directories are private and empty. A stale 3.2 lock is recoverable only after reboot, once it conclusively predates the current boot and no legacy holder or profile process remains. The first successful new-runtime start writes permanent metadata and creates `runtime.mutex`; version 3.2 must not reuse that directory.
94
96
 
95
97
  To downgrade, first stop the new runtime and verify that no process still uses any generation profile path. Then remove the entire runtime directory, including `runtime.lock`, `runtime.mutex`, and `generations`, and let version 3.2 create a fresh directory. Never delete only the lock or anchor, and never perform this procedure while either runtime generation is active.
@@ -110,9 +112,9 @@ await runtime.applyAttachmentBinding({
110
112
  });
111
113
  ```
112
114
 
113
- A newer binding synchronously fences admission, revokes older capabilities, and quiesces active work. It normally preserves the incarnation; work that ignores cancellation causes termination of only that resource's incarnation.
115
+ A newer binding fences agent admission, revokes older agent capabilities, and quiesces their active work. Human viewers retain their resource authority and subscriptions. The incarnation is preserved unless an operation cannot quiesce; that failure requires terminating the resource's browser process.
114
116
 
115
- Agent capabilities require the exact current non-detached qualified session. Human capabilities deliberately carry no session ID: they bind the exact project, resource, attachment authority, and revision and may be issued while detached. Any attachment revision advance invalidates both human and agent capabilities.
117
+ Agent capabilities require the exact current non-detached qualified session. Human capabilities carry no session ID and may be issued while detached. Issuance validates the current attachment binding, but an issued human capability remains valid across attachment changes: hosts must authorize it against its project, resource, actor and peer rather than the resource's later agent assignment.
116
118
 
117
119
  Capabilities expire after five minutes by default. A host keeping a viewer open can call
118
120
  `await lease.renew({ signal })` before `lease.expiresAt`; the result is the new expiry
@@ -124,25 +126,27 @@ renewal leaves the original expiry unchanged. Only one authorization callback pe
124
126
  may remain unsettled, including after timeout; runtime-wide renewal admission is bounded
125
127
  by `maxCapabilities`. Hosts must stop scheduling renewal when their viewer disconnects.
126
128
 
127
- Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement and refresh, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
129
+ Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Agents can read or delete screenshots created by their exact lease using `readArtifact()` and `deleteArtifact()`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement and refresh, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
130
+
131
+ All participants enter one strict resource FIFO. `maxQueuedOperationsPerLease` defaults to 128 and accepts 1 through 1,024; each participant has that bound, and the resource queue is bounded by its product with `maxCapabilitiesPerResource`. Overflow fails with `QUOTA_EXCEEDED`. Releasing a participant cancels only its queued and active work. Resource termination and shutdown cancel everyone. Started uncertain work must quiesce or the exact incarnation is terminated before operation settlement.
128
132
 
129
- Operations from one valid lease enter a strict bounded FIFO. `maxQueuedOperationsPerLease` defaults to 128 and accepts values from 1 through 1,024. Queue overflow fails with `QUOTA_EXCEEDED`; `BUSY` remains resource arbitration. A queued operation aborted by its caller, lease revocation, attachment transition, resource termination, or runtime shutdown never starts. Started work is never superseded, and cancellation does not complete until the work quiesces or the exact resource incarnation is fenced.
133
+ `setViewport()` stores the viewer's preferred width, height and device scale factor and returns `IBrowserRuntimeViewportResult` with the effective `viewport` and `viewportRevision`. The effective values are the componentwise minima across active viewers that have supplied a preference. Unchanged effective sizes do not restart the stream. Removing a viewer recomputes the viewport in the resource FIFO. Held keys and buttons are tracked per participant; leaving releases only inputs that no other participant holds. Viewport and tab/navigation transitions clear held input before changing the target. Resource-owned departure cleanup is bounded and audited as `releaseParticipant`; it does not call the external `beforeOperation` gate after the participant has lost access.
130
134
 
131
135
  `beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, classification, start time, and an `AbortSignal` after dequeue but before the browser side effect starts. Classifications are `raw-input`, `frame-stream`, `viewport`, `navigation`, `tab`, and `agent-action`. Hosts that require durable attempt-before-side-effect auditing should persist the attempt there. Rejection denies the operation. The default `beforeOperationTimeoutMs` is 10,000 milliseconds and accepts values from 100 through 120,000; timeout fails with `TIMEOUT`. The terminal `audit` callback remains a best-effort `completed`/`failed` notification correlated by the same operation ID and classification and runs after bounded operation cleanup releases or fences the exact reservation.
132
136
 
133
137
  `lease.getAuthority()` returns an immutable process-local lease authority containing the runtime authority ID, exported `authorityGeneration`, incarnation generation, and complete capability binding. Every snapshot also binds the exact `capabilityId` and `leaseId`. `lease.isAuthorityCurrent(authority)` performs an exact synchronous revalidation suitable for a host-owned virtual stream. These values are not durable Controller state and do not replace attachment checks against the Controller database.
134
138
 
135
- Human frame delivery keeps a bounded exact-identity application window. `maxOutstandingFrames` defaults to 4 and accepts values from 1 through 32. Runtime passes the same bound to SmartPuppeteer's private CDP-to-ack window; the windows remain separately owned, but equal limits prevent private eviction while the application window still has spare capacity. At full capacity SmartPuppeteer retires its oldest private entry before publishing the next frame, so a later exact acknowledgement can fulfill with `{ accepted: false }`. A fulfilled `true` or `false` settles the Runtime entry locally without fencing; `false` does not prove that upstream retirement succeeded. Rejected or timed-out acknowledgement operations toward SmartPuppeteer fail closed, as does failed-delivery retirement regardless of a fulfilled boolean. Frame acknowledgement stays outside the operation FIFO.
139
+ Each human participant may have one independent frame subscription with a bounded exact-identity window. `maxOutstandingFrames` defaults to 4 and accepts 1 through 32. Runtime starts exactly one bounded producer acknowledgement per admitted frame and shares its outcome between subscribers. Viewer acknowledgements and oldest-first eviction retire only that viewer's entry; they never acknowledge the producer a second time. A producer result of `accepted: false` is a settled result, while rejection, timeout or producer admission overflow fences the failed incarnation. A throwing viewer listener revokes only that participant.
136
140
 
137
- A late application acknowledgement is not a lease failure. `frameAcknowledgementTimeoutMs` defaults to 10,000 milliseconds and accepts values from 100 through 60,000; when a delivered frame is still unacknowledged after that time, Runtime retires the entry through the same path as oldest-first window eviction: it acknowledges the frame toward SmartPuppeteer and leaves the lease, subscription, and incarnation untouched. A later explicit acknowledgement of that retired identity returns `false`. The same timeout still bounds each acknowledgement operation toward SmartPuppeteer, and that bound remains fail closed.
141
+ `frameAcknowledgementTimeoutMs` defaults to 10,000 milliseconds and accepts 100 through 60,000. It bounds producer acknowledgements and each viewer's local window lifetime independently. A late viewer acknowledgement returns `false` once its entry has expired; it does not stop the browser, revoke another participant, or delay producer delivery.
138
142
 
139
143
  An oversized frame is dropped, not fatal. `maxFrameBytes` defaults to 4 MiB and accepts values from 64 KiB through 16 MiB. A frame above the bound is acknowledged toward SmartPuppeteer in the background, never enters the application window, and is reported once on the subscription as `{ type: 'error', error: { code: 'FRAME_TOO_LARGE', fatal: false, tabId } }`. The sequence watermark advances past the dropped frame so later frames remain valid and continue to be delivered.
140
144
 
141
145
  `screencast` tunes the Chromium screencast that SmartPuppeteer starts for every incarnation. `quality` is the JPEG quality from 0 through 100 and defaults to 70. `maxWidth` and `maxHeight` accept values from 1 through 4,096 each, their product must not exceed 8,294,400 pixels (SmartPuppeteer's ceiling, so 3,840×2,160 is accepted and 4,096×4,096 is rejected with `INVALID_INPUT`), they default to 2,560 and 1,600, and they make Chromium scale captured frames to fit. `everyNthFrame` accepts values from 1 through 60 and defaults to 1. `firstFrameTimeoutMs` accepts values from 1,000 through 60,000 and is left to SmartPuppeteer's default when omitted. Runtime forwards these values together with `maxOutstandingFrames`; the defaults keep frames from device-pixel-ratio 2 viewers well below `maxFrameBytes`.
142
146
 
143
- Human state and error events retain an optional diagnostic `message` bounded to 2,048 characters. A fatal session error synchronously invalidates the exact capability before its captured subscriber is notified once, then revokes and cleans up the lease even when that subscriber throws.
147
+ Human state and error events retain an optional diagnostic `message` bounded to 2,048 characters. A fatal producer error synchronously invalidates all current participants before their captured subscribers receive the error. Cleanup is coalesced for that exact incarnation and completes before affected operations settle.
144
148
 
145
- `humanLease.refreshFrameStream(options?: IBrowserRuntimeOperationOptions): Promise<void>` requires the lease's exact active frame subscription and enters the same audited FIFO as other browser mutations. Runtime retires the old application window, asks SmartPuppeteer for a new screencast generation, and succeeds only after the exact first new-generation frame was delivered through that subscription and validated against the current tab, viewport, lease, session, and incarnation. Existing and refresh-owned acknowledgement work share `maxOutstandingFrames`; synchronous listener acknowledgements cannot create an unbounded promise set. A missing or pre-execution-replaced subscription fails with `BUSY`. Refresh protocol, delivery, retirement, or acknowledgement failure returns `FRAME_STREAM_FAILED` or `FRAME_TOO_LARGE` only after the exact incarnation is fenced; an oversized frame during an active refresh still fails that refresh with `FRAME_TOO_LARGE` while the subscription's error event itself stays `fatal: false`. Stale authority returns `ABORTED` without touching its replacement. Pass `options.signal` for caller cancellation; active cancellation preserves a successfully restored stream.
149
+ `humanLease.refreshFrameStream(options?: IBrowserRuntimeOperationOptions): Promise<void>` requires that participant's current subscription and enters the shared audited FIFO. It works for a running, open active tab even when its stream is invalidated. Runtime clears old viewer windows, waits for producer acknowledgement work, and validates the returned new-generation boundary against the current tab, viewport, session and incarnation. Every viewer receives the new stream. Missing or replaced subscriptions fail with `BUSY`; an abandoned initiator returns `ABORTED` without removing other viewers. A broken producer or invalid refresh boundary fails with `FRAME_STREAM_FAILED` or `FRAME_TOO_LARGE` after exact-incarnation cleanup. Caller cancellation preserves a successfully restored stream.
146
150
 
147
151
  ## Trusted Pipe And Flex
148
152
 
@@ -160,7 +164,7 @@ The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_sc
160
164
 
161
165
  Each running resource owns one authenticated loopback `BrowserEgressProxy` carrying immutable `projectId` and `browserResourceId`. HTTP, WebSocket Upgrade, and CONNECT share strict public-unicast DNS/IP validation, numeric dialing, bounded lifetimes, and fail-closed policy.
162
166
 
163
- Artifact identity and APIs use `(projectId, browserResourceId, artifactId)`. Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256. Agent, Flex, and MCP surfaces receive metadata only.
167
+ Artifact identity and APIs use `(projectId, browserResourceId, artifactId)`. Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256. Screenshot actions return metadata. Direct agent leases can read/delete only artifacts created by that exact lease; other agent and human artifacts remain unavailable. Human artifact access remains scoped to the resource. Read completion revalidates the lease and clears bytes if authority ended during IO. The built-in framed/Flex/MCP action adapters continue to return metadata; hosts can return image content after a scoped lease read. The artifact store accepts an optional opaque owner ID on `store()` and an expected owner ID on `read()`/`delete()`; omitting an expected owner retains its trusted parent API.
164
168
 
165
169
  ## Verification
166
170
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '4.4.0',
6
+ version: '5.1.0',
7
7
  description: 'Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
8
8
  }
@@ -15,6 +15,7 @@ import {
15
15
  interface IStoredArtifact {
16
16
  metadata: IBrowserArtifactMetadata;
17
17
  digest: string;
18
+ ownerId?: string;
18
19
  }
19
20
 
20
21
  interface IResourceArtifacts {
@@ -164,11 +165,14 @@ export class BrowserArtifactStore {
164
165
  browserResourceIdArg: string,
165
166
  mimeTypeArg: string,
166
167
  data: Uint8Array,
168
+ ownerIdArg?: string,
167
169
  ): Promise<IBrowserArtifactMetadata> {
168
170
  this.requireStarted();
169
171
  const projectId = this.validateProjectId(projectIdArg);
170
172
  const browserResourceId = this.validateResourceId(browserResourceIdArg);
171
173
  const mimeType = validateBoundedString(mimeTypeArg, 'mimeType', 1, 128);
174
+ const ownerId = ownerIdArg === undefined ? undefined
175
+ : validateBoundedString(ownerIdArg, 'ownerId', 1, 128);
172
176
  if (!(data instanceof Uint8Array)) {
173
177
  throw new BrowserRuntimeError('INVALID_INPUT', 'data must be Uint8Array');
174
178
  }
@@ -212,6 +216,7 @@ export class BrowserArtifactStore {
212
216
  const stored: IStoredArtifact = {
213
217
  metadata,
214
218
  digest: plugins.crypto.createHash('sha256').update(bytes).digest('hex'),
219
+ ...(ownerId === undefined ? {} : { ownerId }),
215
220
  };
216
221
  const resourceDirectory = this.resourcePath(project, resource);
217
222
  const finalPath = plugins.path.join(resourceDirectory, artifactId);
@@ -252,6 +257,7 @@ export class BrowserArtifactStore {
252
257
  projectIdArg: string,
253
258
  browserResourceIdArg: string,
254
259
  artifactIdArg: string,
260
+ expectedOwnerIdArg?: string,
255
261
  ): Promise<Uint8Array> {
256
262
  this.requireStarted();
257
263
  const projectId = this.validateProjectId(projectIdArg);
@@ -261,6 +267,7 @@ export class BrowserArtifactStore {
261
267
  let handle: plugins.fsPromises.FileHandle | undefined;
262
268
  try {
263
269
  const resolved = this.resolveArtifact(projectId, browserResourceId, artifactId);
270
+ this.assertArtifactOwner(resolved.stored, expectedOwnerIdArg);
264
271
  if (resolved.stored.metadata.expiresAt <= this.now()) {
265
272
  await this.deleteArtifact(projectId, browserResourceId, resolved);
266
273
  throw new BrowserRuntimeError('INVALID_INPUT', 'artifact is unavailable');
@@ -300,6 +307,7 @@ export class BrowserArtifactStore {
300
307
  projectIdArg: string,
301
308
  browserResourceIdArg: string,
302
309
  artifactIdArg: string,
310
+ expectedOwnerIdArg?: string,
303
311
  ): Promise<void> {
304
312
  this.requireStarted();
305
313
  const projectId = this.validateProjectId(projectIdArg);
@@ -307,16 +315,26 @@ export class BrowserArtifactStore {
307
315
  const artifactId = this.validateArtifactId(artifactIdArg);
308
316
  const release = await this.mutex.acquire();
309
317
  try {
318
+ const resolved = this.resolveArtifact(projectId, browserResourceId, artifactId);
319
+ this.assertArtifactOwner(resolved.stored, expectedOwnerIdArg);
310
320
  await this.deleteArtifact(
311
321
  projectId,
312
322
  browserResourceId,
313
- this.resolveArtifact(projectId, browserResourceId, artifactId),
323
+ resolved,
314
324
  );
315
325
  } finally {
316
326
  release();
317
327
  }
318
328
  }
319
329
 
330
+ private assertArtifactOwner(storedArg: IStoredArtifact, expectedOwnerIdArg?: string): void {
331
+ if (expectedOwnerIdArg === undefined) return;
332
+ const expectedOwnerId = validateBoundedString(expectedOwnerIdArg, 'ownerId', 1, 128);
333
+ if (storedArg.ownerId !== expectedOwnerId) {
334
+ throw new BrowserRuntimeError('INVALID_INPUT', 'artifact is unavailable');
335
+ }
336
+ }
337
+
320
338
  public async purgeExpired(resource?: IBrowserResourceKey): Promise<number> {
321
339
  this.requireStarted();
322
340
  const validated = resource === undefined ? undefined : {