@modelprofile.com/browser-runtime 4.2.0 → 4.4.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/changelog.md +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.runtime.d.ts +11 -0
- package/dist_ts/classes.runtime.js +80 -2
- package/dist_ts/index.d.ts +2 -2
- package/dist_ts/interfaces.d.ts +6 -2
- package/package.json +1 -1
- package/readme.md +11 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.runtime.ts +88 -1
- package/ts/index.ts +5 -1
- package/ts/interfaces.ts +7 -2
package/dist_ts/index.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ export { BrowserRuntimeFramedClient, BrowserRuntimeFramedServerPeer, } from './c
|
|
|
6
6
|
export { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
|
|
7
7
|
export { BrowserRuntimeError } from './errors.js';
|
|
8
8
|
export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
|
|
9
|
-
export type { TAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
|
|
9
|
+
export type { TAcquireBrowserRuntimeLeaseOptions, IBrowserRuntimeLeaseRenewalOptions, } from './classes.runtime.js';
|
|
10
10
|
export type { TBrowserRuntimeErrorCode } from './errors.js';
|
|
11
|
-
export type { IAttachTrustedFramedPeerOptions, IApplyBrowserAttachmentBindingRequest, IBrowserAttachmentBinding, IBrowserActionStateResult, IBrowserArtifactMetadata, IBrowserArtifactStoreOptions, IBrowserMcpAuthenticatedBinding, TBrowserCapabilityAuthorizationRequest, TBrowserCapabilityDescriptor, IBrowserClickAction, IBrowserEgressConnectionOptions, IBrowserEgressProxyOptions, IBrowserEgressProxyStats, IBrowserFlexCapabilityBinding, IBrowserFlexSessionId, IBrowserFillAction, IBrowserNavigateAction, IBrowserObservationResult, IBrowserOpenCodeSessionId, IBrowserPressAction, TBrowserRuntimeAuditEvent, TBrowserRuntimeLeaseAuthority, TBrowserRuntimeOperationClassification, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, IBrowserRuntimeScreencastOptions, IBrowserResourceKey, IBrowserResourceRegistration, IBrowserRuntimeState, IBrowserRuntimeTabState, IBrowserScreenshotAction, IBrowserScreenshotResult, IBrowserSnapshotAction, ICreateBrowserResourceRequest, TIssueBrowserCapabilityRequest, TIssuedBrowserCapability, TQualifiedBrowserSessionId, IRegisterBrowserResourceRequest, IResolvedBrowserFlexCapability, TBrowserActorRole, TBrowserAgentAction, TBrowserAgentActionResult, TBrowserCapabilitySource, TBrowserDnsResolver, TBrowserEgressConnector, TBrowserHarnessId, TBrowserRuntimeEvent, } from './interfaces.js';
|
|
11
|
+
export type { IAttachTrustedFramedPeerOptions, IApplyBrowserAttachmentBindingRequest, IBrowserAttachmentBinding, IBrowserActionStateResult, IBrowserArtifactMetadata, IBrowserArtifactStoreOptions, IBrowserMcpAuthenticatedBinding, TBrowserCapabilityAuthorizationRequest, TBrowserCapabilityDescriptor, IBrowserClickAction, IBrowserEgressConnectionOptions, IBrowserEgressProxyOptions, IBrowserEgressProxyStats, IBrowserFlexCapabilityBinding, IBrowserFlexSessionId, IBrowserCodexSessionId, IBrowserFillAction, IBrowserNavigateAction, IBrowserObservationResult, IBrowserOpenCodeSessionId, IBrowserPressAction, TBrowserRuntimeAuditEvent, TBrowserRuntimeLeaseAuthority, TBrowserRuntimeOperationClassification, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, IBrowserRuntimeScreencastOptions, IBrowserResourceKey, IBrowserResourceRegistration, IBrowserRuntimeState, IBrowserRuntimeTabState, IBrowserScreenshotAction, IBrowserScreenshotResult, IBrowserSnapshotAction, ICreateBrowserResourceRequest, TIssueBrowserCapabilityRequest, TIssuedBrowserCapability, TQualifiedBrowserSessionId, IRegisterBrowserResourceRequest, IResolvedBrowserFlexCapability, TBrowserActorRole, TBrowserAgentAction, TBrowserAgentActionResult, TBrowserCapabilitySource, TBrowserDnsResolver, TBrowserEgressConnector, TBrowserHarnessId, TBrowserRuntimeEvent, } from './interfaces.js';
|
package/dist_ts/interfaces.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { BrowserEgressProxy } from './classes.egressproxy.js';
|
|
|
3
3
|
import type { BrowserRuntimeFramedClient } from './classes.framed.js';
|
|
4
4
|
export type TBrowserActorRole = 'human' | 'agent';
|
|
5
5
|
export type TBrowserCapabilitySource = 'human' | 'flex' | 'mcp';
|
|
6
|
-
export type TBrowserHarnessId = 'opencode' | 'flex';
|
|
6
|
+
export type TBrowserHarnessId = 'opencode' | 'flex' | 'codex';
|
|
7
7
|
export interface IBrowserOpenCodeSessionId {
|
|
8
8
|
harnessId: 'opencode';
|
|
9
9
|
nativeId: string;
|
|
@@ -12,7 +12,11 @@ export interface IBrowserFlexSessionId {
|
|
|
12
12
|
harnessId: 'flex';
|
|
13
13
|
nativeId: string;
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export interface IBrowserCodexSessionId {
|
|
16
|
+
harnessId: 'codex';
|
|
17
|
+
nativeId: string;
|
|
18
|
+
}
|
|
19
|
+
export type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId | IBrowserCodexSessionId;
|
|
16
20
|
export interface IBrowserResourceKey {
|
|
17
21
|
projectId: string;
|
|
18
22
|
browserResourceId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/browser-runtime",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/readme.md
CHANGED
|
@@ -72,7 +72,7 @@ try {
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
Projects and qualified sessions may each own many resources. 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. 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.
|
|
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
|
|
|
@@ -114,6 +114,16 @@ A newer binding synchronously fences admission, revokes older capabilities, and
|
|
|
114
114
|
|
|
115
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.
|
|
116
116
|
|
|
117
|
+
Capabilities expire after five minutes by default. A host keeping a viewer open can call
|
|
118
|
+
`await lease.renew({ signal })` before `lease.expiresAt`; the result is the new expiry
|
|
119
|
+
time in Unix milliseconds. Renewal invokes `authorizeCapability` again and preserves
|
|
120
|
+
the exact lease, browser incarnation, frame subscription and attachment identity.
|
|
121
|
+
`expiresInMs` optionally selects a lifetime within the configured maximum. An expired,
|
|
122
|
+
released, revoked or replaced lease cannot be renewed. A denied, cancelled or timed-out
|
|
123
|
+
renewal leaves the original expiry unchanged. Only one authorization callback per lease
|
|
124
|
+
may remain unsettled, including after timeout; runtime-wide renewal admission is bounded
|
|
125
|
+
by `maxCapabilities`. Hosts must stop scheduling renewal when their viewer disconnects.
|
|
126
|
+
|
|
117
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.
|
|
118
128
|
|
|
119
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.
|
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: '4.
|
|
6
|
+
version: '4.4.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
|
}
|
package/ts/classes.runtime.ts
CHANGED
|
@@ -139,6 +139,12 @@ interface ILeaseRecord {
|
|
|
139
139
|
releaseOwnsFence?: boolean;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
export interface IBrowserRuntimeLeaseRenewalOptions {
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
/** Lifetime from successful reauthorization; defaults to the runtime capability lifetime. */
|
|
145
|
+
expiresInMs?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
142
148
|
interface IOperationRecord {
|
|
143
149
|
operationId: string;
|
|
144
150
|
lease: ILeaseRecord;
|
|
@@ -259,6 +265,8 @@ export class BrowserRuntime {
|
|
|
259
265
|
private readonly failedRevocations = new Set<TCapabilityRecord>();
|
|
260
266
|
private readonly capabilityMutex = new TransitionMutex();
|
|
261
267
|
private readonly cleanupOperations = new Set<Promise<void>>();
|
|
268
|
+
// Retain admission until the host callback actually settles, including after cancellation.
|
|
269
|
+
private readonly leaseRenewals = new Set<ILeaseRecord>();
|
|
262
270
|
private readonly framedPeers = new Set<BrowserRuntimeFramedServerPeer>();
|
|
263
271
|
private startPromise?: Promise<void>;
|
|
264
272
|
private stopPromise?: Promise<void>;
|
|
@@ -831,6 +839,7 @@ export class BrowserRuntime {
|
|
|
831
839
|
const sessionId = requestArg.sessionId === undefined
|
|
832
840
|
? undefined
|
|
833
841
|
: this.validateQualifiedSessionId(requestArg.sessionId);
|
|
842
|
+
if (sessionId?.harnessId === 'codex') throw new BrowserRuntimeError('INVALID_INPUT');
|
|
834
843
|
if (
|
|
835
844
|
(role === 'agent' && !sessionId)
|
|
836
845
|
|| (role === 'human' && sessionId !== undefined)
|
|
@@ -1129,6 +1138,75 @@ export class BrowserRuntime {
|
|
|
1129
1138
|
return this.createLeaseAuthority(lease);
|
|
1130
1139
|
}
|
|
1131
1140
|
|
|
1141
|
+
/** @internal */
|
|
1142
|
+
public async renewLease(
|
|
1143
|
+
lease: ILeaseRecord,
|
|
1144
|
+
optionsArg: IBrowserRuntimeLeaseRenewalOptions = {},
|
|
1145
|
+
): Promise<number> {
|
|
1146
|
+
const options = validateExactKeys(optionsArg, ['signal', 'expiresInMs'], 'renewal');
|
|
1147
|
+
if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) {
|
|
1148
|
+
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
1149
|
+
}
|
|
1150
|
+
const lifetimeMs = validateOptionalInteger(
|
|
1151
|
+
options.expiresInMs, 'expiresInMs', 1000,
|
|
1152
|
+
this.options.capabilityMaximumTtlMs, this.options.capabilityDefaultTtlMs,
|
|
1153
|
+
);
|
|
1154
|
+
const authority = this.getLeaseAuthority(lease);
|
|
1155
|
+
if (!this.isLeaseAuthorityCurrent(lease, authority)) throw new BrowserRuntimeError('FENCED');
|
|
1156
|
+
if (this.leaseRenewals.has(lease)) throw new BrowserRuntimeError('BUSY');
|
|
1157
|
+
if (this.leaseRenewals.size >= this.options.maxCapabilities) {
|
|
1158
|
+
throw new BrowserRuntimeError('QUOTA_EXCEEDED');
|
|
1159
|
+
}
|
|
1160
|
+
const controller = new AbortController();
|
|
1161
|
+
const signal = AbortSignal.any([
|
|
1162
|
+
controller.signal, this.lifecycleController.signal, lease.controller.signal,
|
|
1163
|
+
...(optionsArg.signal ? [optionsArg.signal] : []),
|
|
1164
|
+
]);
|
|
1165
|
+
signal.throwIfAborted();
|
|
1166
|
+
const timeout = setTimeout(() => controller.abort(new BrowserRuntimeError('TIMEOUT')),
|
|
1167
|
+
Math.min(this.options.authorizationTimeoutMs, lease.capability.expiresAt - Date.now()));
|
|
1168
|
+
timeout.unref();
|
|
1169
|
+
const binding = this.describeCapabilityIdentity(lease.capability);
|
|
1170
|
+
if (binding.sessionId) Object.freeze(binding.sessionId);
|
|
1171
|
+
Object.freeze(binding);
|
|
1172
|
+
this.leaseRenewals.add(lease);
|
|
1173
|
+
const authorization = Promise.resolve().then(() => {
|
|
1174
|
+
signal.throwIfAborted();
|
|
1175
|
+
return this.options.authorizeCapability(binding, signal);
|
|
1176
|
+
});
|
|
1177
|
+
void authorization.then(
|
|
1178
|
+
() => this.leaseRenewals.delete(lease),
|
|
1179
|
+
() => this.leaseRenewals.delete(lease),
|
|
1180
|
+
);
|
|
1181
|
+
let abortListener: (() => void) | undefined;
|
|
1182
|
+
try {
|
|
1183
|
+
const authorized = await new Promise<boolean>((resolve, reject) => {
|
|
1184
|
+
abortListener = () => reject(signal.reason ?? new BrowserRuntimeError('ABORTED'));
|
|
1185
|
+
signal.addEventListener('abort', abortListener, { once: true });
|
|
1186
|
+
if (signal.aborted) abortListener();
|
|
1187
|
+
void authorization.then(resolve, () => reject(new BrowserRuntimeError('AUTHORIZATION_DENIED')));
|
|
1188
|
+
});
|
|
1189
|
+
signal.throwIfAborted();
|
|
1190
|
+
if (authorized !== true) throw new BrowserRuntimeError('AUTHORIZATION_DENIED');
|
|
1191
|
+
// Neither expiry, release, attachment change nor incarnation replacement may be revived.
|
|
1192
|
+
if (!this.isLeaseAuthorityCurrent(lease, authority)) throw new BrowserRuntimeError('CAPABILITY_REVOKED');
|
|
1193
|
+
const capability = lease.capability;
|
|
1194
|
+
const expiresAt = Date.now() + lifetimeMs;
|
|
1195
|
+
clearTimeout(capability.expiryTimer);
|
|
1196
|
+
capability.expiresAt = expiresAt;
|
|
1197
|
+
capability.expiryTimer = setTimeout(() => {
|
|
1198
|
+
if (capability.state === 'active') capability.state = 'expired';
|
|
1199
|
+
this.trackCleanup(this.revokeCapabilityRecord(capability));
|
|
1200
|
+
}, lifetimeMs);
|
|
1201
|
+
capability.expiryTimer.unref();
|
|
1202
|
+
return expiresAt;
|
|
1203
|
+
} finally {
|
|
1204
|
+
clearTimeout(timeout);
|
|
1205
|
+
if (abortListener) signal.removeEventListener('abort', abortListener);
|
|
1206
|
+
controller.abort(new BrowserRuntimeError('ABORTED'));
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1132
1210
|
/** @internal */
|
|
1133
1211
|
public isLeaseAuthorityCurrent(
|
|
1134
1212
|
lease: ILeaseRecord,
|
|
@@ -3787,7 +3865,7 @@ export class BrowserRuntime {
|
|
|
3787
3865
|
if (Object.keys(record).some((key) => key !== 'harnessId' && key !== 'nativeId')) {
|
|
3788
3866
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
3789
3867
|
}
|
|
3790
|
-
if (record.harnessId !== 'opencode' && record.harnessId !== 'flex') {
|
|
3868
|
+
if (record.harnessId !== 'opencode' && record.harnessId !== 'flex' && record.harnessId !== 'codex') {
|
|
3791
3869
|
throw new BrowserRuntimeError('INVALID_INPUT');
|
|
3792
3870
|
}
|
|
3793
3871
|
return {
|
|
@@ -4127,6 +4205,15 @@ export class BrowserRuntimeLease {
|
|
|
4127
4205
|
return this.record.capability.capabilityId;
|
|
4128
4206
|
}
|
|
4129
4207
|
|
|
4208
|
+
public get expiresAt(): number {
|
|
4209
|
+
return this.record.capability.expiresAt;
|
|
4210
|
+
}
|
|
4211
|
+
|
|
4212
|
+
/** Reauthorize this exact live lease without replacing its frame stream or browser incarnation. */
|
|
4213
|
+
public renew(options?: IBrowserRuntimeLeaseRenewalOptions): Promise<number> {
|
|
4214
|
+
return this.runtime.renewLease(this.record, options);
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4130
4217
|
public get role(): TBrowserActorRole {
|
|
4131
4218
|
return this.record.role;
|
|
4132
4219
|
}
|
package/ts/index.ts
CHANGED
|
@@ -10,7 +10,10 @@ export { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
|
|
|
10
10
|
export { BrowserRuntimeError } from './errors.js';
|
|
11
11
|
export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
|
|
12
12
|
|
|
13
|
-
export type {
|
|
13
|
+
export type {
|
|
14
|
+
TAcquireBrowserRuntimeLeaseOptions,
|
|
15
|
+
IBrowserRuntimeLeaseRenewalOptions,
|
|
16
|
+
} from './classes.runtime.js';
|
|
14
17
|
export type { TBrowserRuntimeErrorCode } from './errors.js';
|
|
15
18
|
export type {
|
|
16
19
|
IAttachTrustedFramedPeerOptions,
|
|
@@ -28,6 +31,7 @@ export type {
|
|
|
28
31
|
IBrowserEgressProxyStats,
|
|
29
32
|
IBrowserFlexCapabilityBinding,
|
|
30
33
|
IBrowserFlexSessionId,
|
|
34
|
+
IBrowserCodexSessionId,
|
|
31
35
|
IBrowserFillAction,
|
|
32
36
|
IBrowserNavigateAction,
|
|
33
37
|
IBrowserObservationResult,
|
package/ts/interfaces.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { BrowserRuntimeFramedClient } from './classes.framed.js';
|
|
|
4
4
|
|
|
5
5
|
export type TBrowserActorRole = 'human' | 'agent';
|
|
6
6
|
export type TBrowserCapabilitySource = 'human' | 'flex' | 'mcp';
|
|
7
|
-
export type TBrowserHarnessId = 'opencode' | 'flex';
|
|
7
|
+
export type TBrowserHarnessId = 'opencode' | 'flex' | 'codex';
|
|
8
8
|
|
|
9
9
|
export interface IBrowserOpenCodeSessionId {
|
|
10
10
|
harnessId: 'opencode';
|
|
@@ -16,7 +16,12 @@ export interface IBrowserFlexSessionId {
|
|
|
16
16
|
nativeId: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export
|
|
19
|
+
export interface IBrowserCodexSessionId {
|
|
20
|
+
harnessId: 'codex';
|
|
21
|
+
nativeId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId | IBrowserCodexSessionId;
|
|
20
25
|
|
|
21
26
|
export interface IBrowserResourceKey {
|
|
22
27
|
projectId: string;
|