@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/.smartconfig.json +1 -1
- package/changelog.md +10 -0
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/actions.js +12 -3
- package/dist_ts/classes.artifactstore.d.ts +16 -7
- package/dist_ts/classes.artifactstore.js +167 -88
- package/dist_ts/classes.egressproxy.d.ts +2 -0
- package/dist_ts/classes.egressproxy.js +7 -2
- package/dist_ts/classes.flexprovider.js +4 -2
- package/dist_ts/classes.framed.d.ts +30 -3
- package/dist_ts/classes.framed.js +205 -30
- package/dist_ts/classes.runtime.d.ts +80 -22
- package/dist_ts/classes.runtime.js +876 -196
- package/dist_ts/errors.d.ts +1 -1
- package/dist_ts/errors.js +6 -3
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/interfaces.d.ts +102 -40
- package/dist_ts/internal.testing.d.ts +10 -1
- package/dist_ts/internal.testing.js +1 -1
- package/dist_ts/mcp.js +45 -11
- package/package.json +2 -2
- package/readme.hints.md +22 -18
- package/readme.md +69 -101
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/actions.ts +11 -2
- package/ts/classes.artifactstore.ts +239 -107
- package/ts/classes.egressproxy.ts +12 -1
- package/ts/classes.flexprovider.ts +5 -1
- package/ts/classes.framed.ts +246 -38
- package/ts/classes.runtime.ts +1145 -281
- package/ts/errors.ts +8 -2
- package/ts/index.ts +18 -6
- package/ts/interfaces.ts +127 -38
- package/ts/internal.testing.ts +12 -0
- package/ts/mcp.ts +67 -16
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @modelprofile.com/browser-runtime
|
|
2
2
|
|
|
3
|
-
Parent-owned,
|
|
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
|
-
##
|
|
17
|
+
## Resource Model
|
|
18
18
|
|
|
19
|
-
|
|
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 {
|
|
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 (
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
77
|
+
## Attachment Fencing
|
|
64
78
|
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
99
|
+
## Trusted Pipe And Flex
|
|
119
100
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@modelprofile.com/browser-runtime',
|
|
6
|
-
version: '
|
|
7
|
-
description: 'Parent-owned,
|
|
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
|
-
[
|
|
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,
|
|
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);
|