@modelprofile.com/browser-runtime 1.0.1 → 2.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/.smartconfig.json +1 -1
- package/changelog.md +19 -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 +86 -22
- package/dist_ts/classes.runtime.js +1060 -230
- 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 +119 -47
- 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 +23 -18
- package/readme.md +72 -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 +1350 -320
- package/ts/errors.ts +8 -2
- package/ts/index.ts +19 -6
- package/ts/interfaces.ts +151 -45
- 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,127 @@ 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
|
-
|
|
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),
|
|
30
|
+
beforeOperation: async (operation) => hostAudit.recordAttempt(operation),
|
|
47
31
|
});
|
|
48
32
|
|
|
49
33
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
await runtime.start();
|
|
35
|
+
|
|
36
|
+
const resource = runtime.createResource({
|
|
37
|
+
projectId: 'project-123',
|
|
38
|
+
attachmentBinding: {
|
|
39
|
+
attachmentAuthorityId: 'controller-attachment-1',
|
|
40
|
+
attachmentRevision: 1,
|
|
41
|
+
sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const binding = {
|
|
46
|
+
projectId: resource.projectId,
|
|
47
|
+
browserResourceId: resource.browserResourceId,
|
|
48
|
+
attachmentAuthorityId: resource.attachmentBinding.attachmentAuthorityId,
|
|
49
|
+
attachmentRevision: resource.attachmentBinding.attachmentRevision,
|
|
50
|
+
sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
|
|
51
|
+
actorId: 'agent-456',
|
|
52
|
+
role: 'agent',
|
|
53
|
+
peerId: 'worker-789',
|
|
54
|
+
source: 'mcp',
|
|
55
|
+
} satisfies TBrowserCapabilityAuthorizationRequest;
|
|
56
|
+
|
|
57
|
+
const issued = await runtime.issueCapability(binding);
|
|
58
|
+
|
|
59
|
+
const lease = await runtime.acquireLease({
|
|
60
|
+
...binding,
|
|
61
|
+
capabilityToken: issued.capabilityToken,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
console.log(await lease.executeAgentAction({ action: 'snapshot' }));
|
|
66
|
+
} finally {
|
|
67
|
+
await lease.release();
|
|
68
|
+
}
|
|
53
69
|
} finally {
|
|
54
|
-
await lease.release();
|
|
55
70
|
await runtime.stop();
|
|
56
71
|
}
|
|
57
72
|
```
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
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
75
|
|
|
61
|
-
|
|
76
|
+
`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
77
|
|
|
63
|
-
##
|
|
78
|
+
## Attachment Fencing
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
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
81
|
|
|
67
82
|
```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/',
|
|
83
|
+
await runtime.applyAttachmentBinding({
|
|
84
|
+
projectId: resource.projectId,
|
|
85
|
+
browserResourceId: resource.browserResourceId,
|
|
86
|
+
attachmentBinding: {
|
|
87
|
+
attachmentAuthorityId: 'controller-attachment-1',
|
|
88
|
+
attachmentRevision: 2,
|
|
89
|
+
sessionId: null,
|
|
90
|
+
},
|
|
107
91
|
});
|
|
108
|
-
console.log(result);
|
|
109
|
-
await childClient.close();
|
|
110
92
|
```
|
|
111
93
|
|
|
112
|
-
|
|
94
|
+
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
95
|
|
|
114
|
-
|
|
96
|
+
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
97
|
|
|
116
|
-
|
|
98
|
+
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
99
|
|
|
118
|
-
|
|
100
|
+
`beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, start time, and an `AbortSignal` after resource admission but before the browser side effect starts. 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 runs after bounded operation cleanup releases or fences the exact reservation.
|
|
119
101
|
|
|
120
|
-
|
|
102
|
+
## Trusted Pipe And Flex
|
|
121
103
|
|
|
122
|
-
|
|
123
|
-
import { createBrowserRuntimeMcpHttpHandler } from '@modelprofile.com/browser-runtime';
|
|
104
|
+
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.
|
|
124
105
|
|
|
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
|
-
});
|
|
106
|
+
`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.
|
|
133
107
|
|
|
134
|
-
|
|
135
|
-
```
|
|
108
|
+
## MCP Handler
|
|
136
109
|
|
|
137
|
-
|
|
110
|
+
`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.
|
|
111
|
+
|
|
112
|
+
The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`.
|
|
138
113
|
|
|
139
114
|
## Egress And Artifacts
|
|
140
115
|
|
|
141
|
-
|
|
116
|
+
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
117
|
|
|
143
|
-
|
|
118
|
+
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
119
|
|
|
145
120
|
## Verification
|
|
146
121
|
|
|
147
122
|
```sh
|
|
148
123
|
pnpm install
|
|
124
|
+
pnpm dedupe
|
|
149
125
|
pnpm run build
|
|
150
126
|
pnpm run check:test
|
|
151
127
|
pnpm test
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
The real Chromium release check is intentionally separate:
|
|
155
|
-
|
|
156
|
-
```sh
|
|
157
128
|
pnpm run test:real-chrome
|
|
158
129
|
```
|
|
159
130
|
|
|
160
|
-
|
|
131
|
+
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
132
|
|
|
162
133
|
## License and Legal Information
|
|
163
134
|
|
|
164
135
|
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
136
|
|
|
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
|
|
137
|
+
**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
138
|
|
|
168
139
|
### Trademarks
|
|
169
140
|
|
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: '1.0
|
|
7
|
-
description: 'Parent-owned,
|
|
6
|
+
version: '2.1.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);
|