@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.
@@ -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';
@@ -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 type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId;
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.2.0",
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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '4.2.0',
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
  }
@@ -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 { TAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
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 type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId;
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;