@modelprofile.com/browser-runtime 1.0.1 → 2.0.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.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @modelprofile.com/browser-runtime
2
2
 
3
- Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
3
+ Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -14,156 +14,124 @@ pnpm add @modelprofile.com/browser-runtime
14
14
 
15
15
  The runtime requires Node.js 24 or newer and a non-root Linux host. Production browser sessions require a sandbox-capable Chromium installation.
16
16
 
17
- ## Runtime
17
+ ## Resource Model
18
18
 
19
- `BrowserRuntime` owns its lock, profile root, artifact root, project slots, Chromium sessions, egress proxies, capabilities, leases, and cleanup work. The host must make the authorization decision; there is no permissive default.
19
+ The Controller owns durable resource and attachment truth. `BrowserRuntime` owns process-local registrations, fences, capabilities, leases, browser incarnations, artifacts, and its runtime lock. Registration never launches Chromium.
20
20
 
21
21
  ```typescript
22
- import { BrowserRuntime } from '@modelprofile.com/browser-runtime';
22
+ import {
23
+ BrowserRuntime,
24
+ type TBrowserCapabilityAuthorizationRequest,
25
+ } from '@modelprofile.com/browser-runtime';
23
26
 
24
27
  const runtime = new BrowserRuntime({
25
28
  runtimeDirectory: '/var/lib/example/browser-runtime',
26
- authorizeCapability: async ({ projectId, actorId, role, peerId, source }) => {
27
- return hostPolicy.authorizeBrowser({ projectId, actorId, role, peerId, source });
28
- },
29
- });
30
-
31
- await runtime.start();
32
-
33
- const issued = await runtime.issueCapability({
34
- projectId: 'project-123',
35
- actorId: 'agent-456',
36
- role: 'agent',
37
- peerId: 'worker-789',
38
- source: 'mcp',
39
- });
40
-
41
- // Deliver capabilityToken once to the exact authenticated peer.
42
- const lease = await runtime.acquireLease({
43
- capabilityToken: issued.capabilityToken,
44
- peerId: 'worker-789',
45
- expectedRole: 'agent',
46
- expectedSource: 'mcp',
29
+ authorizeCapability: async (binding) => hostPolicy.authorizeBrowser(binding),
47
30
  });
48
31
 
49
32
  try {
50
- const observation = await lease.executeAgentAction({ action: 'snapshot' });
51
- const screenshot = await lease.executeAgentAction({ action: 'screenshot' });
52
- console.log(observation, screenshot);
33
+ await runtime.start();
34
+
35
+ const resource = runtime.createResource({
36
+ projectId: 'project-123',
37
+ attachmentBinding: {
38
+ attachmentAuthorityId: 'controller-attachment-1',
39
+ attachmentRevision: 1,
40
+ sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
41
+ },
42
+ });
43
+
44
+ const binding = {
45
+ projectId: resource.projectId,
46
+ browserResourceId: resource.browserResourceId,
47
+ attachmentAuthorityId: resource.attachmentBinding.attachmentAuthorityId,
48
+ attachmentRevision: resource.attachmentBinding.attachmentRevision,
49
+ sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
50
+ actorId: 'agent-456',
51
+ role: 'agent',
52
+ peerId: 'worker-789',
53
+ source: 'mcp',
54
+ } satisfies TBrowserCapabilityAuthorizationRequest;
55
+
56
+ const issued = await runtime.issueCapability(binding);
57
+
58
+ const lease = await runtime.acquireLease({
59
+ ...binding,
60
+ capabilityToken: issued.capabilityToken,
61
+ });
62
+
63
+ try {
64
+ console.log(await lease.executeAgentAction({ action: 'snapshot' }));
65
+ } finally {
66
+ await lease.release();
67
+ }
53
68
  } finally {
54
- await lease.release();
55
69
  await runtime.stop();
56
70
  }
57
71
  ```
58
72
 
59
- Agent actions are limited to `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Screenshot results contain artifact metadata, never image bytes. Human leases additionally expose viewport, raw input, direct event/frame subscription, frame acknowledgement, and artifact read/delete methods. JavaScript evaluation is not part of either lease surface.
73
+ Projects and qualified sessions may each own many resources. Human/agent arbitration, mutexes, leases, operations, profiles, proxies, frame subscriptions, and idle timers are per resource.
60
74
 
61
- The first session for a project starts a private authenticated loopback proxy and runs the mandatory confinement probe before the session becomes available. The production probe verifies loopback denial, synthetic DNS traversal, and WebRTC candidate suppression. Browser/session and host-fact injection are not part of the published API.
75
+ `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()` revokes all process-local capabilities, terminates every incarnation, removes the Runtime-owned artifact root and registrations, and releases the lock without deleting Controller durable truth.
62
76
 
63
- ## Trusted Pipe
77
+ ## Attachment Fencing
64
78
 
65
- The parent attaches a trusted inherited pipe and supplies the peer, Flex scope, and Flex session identities out of band. Incoming frames cannot select a project, actor, role, source, scope, session, or peer. The capability used by the pipe must have been issued with the same immutable bindings.
79
+ Attachment bindings are Controller-owned `{ attachmentAuthorityId, attachmentRevision, sessionId }` values. Any binding with `sessionId: null` is detached; revision `0` is the initial detached/no-agent-authority state. Reapplying the identical revision and binding is idempotent; lower revisions and conflicting equal revisions fail.
66
80
 
67
81
  ```typescript
68
- import {
69
- BrowserRuntimeFramedClient,
70
- type BrowserRuntime,
71
- } from '@modelprofile.com/browser-runtime';
72
- import type { Readable, Writable } from 'node:stream';
73
-
74
- declare const runtime: BrowserRuntime;
75
- declare const childReadable: Readable;
76
- declare const childWritable: Writable;
77
-
78
- const flexCapability = await runtime.issueCapability({
79
- projectId: 'project-123',
80
- actorId: 'agent-456',
81
- role: 'agent',
82
- peerId: 'worker-789',
83
- source: 'flex',
84
- scopeId: 'project-123',
85
- sessionId: 'flex-session-1',
86
- });
87
-
88
- runtime.attachTrustedFramedPeer({
89
- peerId: 'worker-789',
90
- scopeId: 'project-123',
91
- sessionId: 'flex-session-1',
92
- readable: childReadable,
93
- writable: childWritable,
94
- });
95
-
96
- const childClient = new BrowserRuntimeFramedClient({
97
- scopeId: 'project-123',
98
- sessionId: 'flex-session-1',
99
- readable: process.stdin,
100
- writable: process.stdout,
101
- });
102
-
103
- await childClient.acquire(flexCapability.capabilityToken);
104
- const result = await childClient.executeAgentAction({
105
- action: 'navigate',
106
- url: 'https://example.com/',
82
+ await runtime.applyAttachmentBinding({
83
+ projectId: resource.projectId,
84
+ browserResourceId: resource.browserResourceId,
85
+ attachmentBinding: {
86
+ attachmentAuthorityId: 'controller-attachment-1',
87
+ attachmentRevision: 2,
88
+ sessionId: null,
89
+ },
107
90
  });
108
- console.log(result);
109
- await childClient.close();
110
91
  ```
111
92
 
112
- The framing protocol is versioned length-prefixed JSON with a 256 KiB hard frame ceiling, at most 16 pending requests, and a maximum 60-second request timeout. It supports only acquire, execute, cancel, and release.
93
+ 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.
113
94
 
114
- ## Flex Provider
95
+ 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.
115
96
 
116
- `BrowserRuntimeFlexToolProvider<TScope>` implements `IFlexToolProvider<TScope>` for one supervised Flex child run. Its resolver returns only the capability token; the framed server validates that token against the parent-bound peer, source, scope, and session. The provider exposes SmartAgent browser tools with only the six allowed agent actions and maps side-effect permissions to `browser.<action>` Flex permissions. Closing or aborting the run releases the lease and closes the inherited-pipe client.
97
+ Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
117
98
 
118
- ## MCP Handler
99
+ ## Trusted Pipe And Flex
119
100
 
120
- `createBrowserRuntimeMcpHttpHandler()` creates a stateless SmartMCP POST handler. Independent client proof is mandatory and the bearer capability must match the authenticated peer with role `agent` and source `mcp`.
101
+ Trusted framed peers and clients receive the complete authority out of band: project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, and resource-specific channel. Incoming frames cannot select identity. One session may use multiple resource-specific channels concurrently.
121
102
 
122
- ```typescript
123
- import { createBrowserRuntimeMcpHttpHandler } from '@modelprofile.com/browser-runtime';
103
+ `BrowserRuntimeFlexToolProvider<TScope>` resolves only a capability token and exposes the six approved SmartAgent actions. The framed server validates the token against its complete trusted binding.
124
104
 
125
- const mcp = createBrowserRuntimeMcpHttpHandler(runtime, {
126
- authenticateMcpRequest: async (request) => {
127
- const peerId = await verifyClientCertificateOrSignedRequest(request);
128
- return { peerId };
129
- },
130
- allowedHosts: ['browser.example.com'],
131
- trustedOrigins: ['https://browser.example.com'],
132
- });
105
+ ## MCP Handler
133
106
 
134
- export const fetch = (request: Request): Promise<Response> => mcp.handleRequest(request);
135
- ```
107
+ `createBrowserRuntimeMcpHttpHandler()` requires independent request authentication to return the complete expected MCP binding. The bearer capability must match it exactly. Identity remains server-owned and no tool input contains a project, resource, authority, revision, session, actor, or peer selector.
136
108
 
137
- The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`. It does not expose state-only helpers, tab lifecycle, history, evaluation, frame streaming, artifact reads, raw input, or image bytes.
109
+ The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`.
138
110
 
139
111
  ## Egress And Artifacts
140
112
 
141
- `BrowserEgressProxy` is exported for testing and advanced composition. It uses Basic proxy authentication, one strict DNS/IP validation path for HTTP, WebSocket Upgrade, and CONNECT, public-global-only targets, numeric selected-IP dialing, default ports `80` and `443`, and bounded DNS, request, response, connection, and tunnel lifetimes. WebSocket tunneling starts only after a bounded valid `101` response.
113
+ 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.
142
114
 
143
- `BrowserArtifactStore` exclusively creates and owns its root, writes generated artifact IDs below keyed project-digest directories, and removes the root on `stop()`. Directories use mode `0700`; files use mode `0600`. Serialized admission enforces per-file, per-project, project-count, aggregate-count, aggregate-byte, and TTL limits before writes. Reads use no-follow file handles and verify size plus SHA-256 integrity before returning trusted-host bytes. Agent, Flex, and MCP surfaces receive metadata only.
115
+ 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.
144
116
 
145
117
  ## Verification
146
118
 
147
119
  ```sh
148
120
  pnpm install
121
+ pnpm dedupe
149
122
  pnpm run build
150
123
  pnpm run check:test
151
124
  pnpm test
152
- ```
153
-
154
- The real Chromium release check is intentionally separate:
155
-
156
- ```sh
157
125
  pnpm run test:real-chrome
158
126
  ```
159
127
 
160
- That command sets `BROWSER_RUNTIME_REAL_CHROME=1` and runs the production `LiveBrowserSession`, sandbox requirement, mandatory confinement probe, screenshot artifact path, confirmed shutdown, and profile deletion check.
128
+ The real Chromium check launches two resources in one project and qualified session, verifies distinct private profiles, sandboxed renderer process trees, mandatory confinement, screenshots, confirmed shutdown, and profile deletion.
161
129
 
162
130
  ## License and Legal Information
163
131
 
164
132
  This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
165
133
 
166
- **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
134
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the contents of the NOTICE file.
167
135
 
168
136
  ### Trademarks
169
137
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '1.0.1',
7
- description: 'Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
6
+ version: '2.0.0',
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
  }
package/ts/actions.ts CHANGED
@@ -226,11 +226,20 @@ export const validateAgentActionResult = (value: unknown): TBrowserAgentActionRe
226
226
  validateExactKeys(value, ['action', 'artifact'], 'screenshot result');
227
227
  const artifact = validateExactKeys(
228
228
  result.artifact,
229
- ['artifactId', 'projectId', 'mimeType', 'size', 'createdAt', 'expiresAt'],
229
+ [
230
+ 'artifactId',
231
+ 'projectId',
232
+ 'browserResourceId',
233
+ 'mimeType',
234
+ 'size',
235
+ 'createdAt',
236
+ 'expiresAt',
237
+ ],
230
238
  'artifact metadata',
231
239
  );
232
240
  validateBoundedString(artifact.artifactId, 'artifactId', 1, 128);
233
- validateBoundedString(artifact.projectId, 'projectId', 1, 128);
241
+ validateBoundedString(artifact.projectId, 'projectId', 1, 256);
242
+ validateBoundedString(artifact.browserResourceId, 'browserResourceId', 16, 256);
234
243
  validateBoundedString(artifact.mimeType, 'mimeType', 1, 128);
235
244
  validateInteger(artifact.size, 'artifact.size', 0, 64 * 1024 * 1024);
236
245
  validateInteger(artifact.createdAt, 'artifact.createdAt', 0, Number.MAX_SAFE_INTEGER);