@modelprofile.com/browser-runtime 2.1.2 → 3.0.1

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.
@@ -8,4 +8,4 @@ export { BrowserRuntimeError } from './errors.js';
8
8
  export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
9
9
  export type { TAcquireBrowserRuntimeLeaseOptions } 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, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, 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, IBrowserFillAction, IBrowserNavigateAction, IBrowserObservationResult, IBrowserOpenCodeSessionId, IBrowserPressAction, TBrowserRuntimeAuditEvent, TBrowserRuntimeLeaseAuthority, TBrowserRuntimeOperationClassification, TBrowserRuntimeOperationIdentity, IBrowserRuntimeFlexToolProviderOptions, IBrowserRuntimeFrameSubscription, IBrowserRuntimeFramedClientOptions, IBrowserRuntimeMcpOptions, IBrowserRuntimeOperationOptions, IBrowserRuntimeOptions, IBrowserResourceKey, IBrowserResourceRegistration, IBrowserRuntimeState, IBrowserRuntimeTabState, IBrowserScreenshotAction, IBrowserScreenshotResult, IBrowserSnapshotAction, ICreateBrowserResourceRequest, TIssueBrowserCapabilityRequest, TIssuedBrowserCapability, TQualifiedBrowserSessionId, IRegisterBrowserResourceRequest, IResolvedBrowserFlexCapability, TBrowserActorRole, TBrowserAgentAction, TBrowserAgentActionResult, TBrowserCapabilitySource, TBrowserDnsResolver, TBrowserEgressConnector, TBrowserHarnessId, TBrowserRuntimeEvent, } from './interfaces.js';
@@ -51,6 +51,7 @@ export interface IBrowserHumanCapabilityBinding extends IBrowserCapabilityBindin
51
51
  sessionId?: never;
52
52
  scopeId?: never;
53
53
  channelId?: never;
54
+ runId?: never;
54
55
  }
55
56
  export interface IBrowserFlexCapabilityBinding extends IBrowserCapabilityBindingBase {
56
57
  role: 'agent';
@@ -58,6 +59,7 @@ export interface IBrowserFlexCapabilityBinding extends IBrowserCapabilityBinding
58
59
  sessionId: IBrowserFlexSessionId;
59
60
  scopeId: string;
60
61
  channelId: string;
62
+ runId: string;
61
63
  }
62
64
  export interface IBrowserMcpCapabilityBinding extends IBrowserCapabilityBindingBase {
63
65
  role: 'agent';
@@ -65,6 +67,7 @@ export interface IBrowserMcpCapabilityBinding extends IBrowserCapabilityBindingB
65
67
  sessionId: IBrowserOpenCodeSessionId;
66
68
  scopeId?: never;
67
69
  channelId?: never;
70
+ runId?: never;
68
71
  }
69
72
  export type TBrowserCapabilityAuthorizationRequest = IBrowserHumanCapabilityBinding | IBrowserFlexCapabilityBinding | IBrowserMcpCapabilityBinding;
70
73
  export type TIssueBrowserCapabilityRequest = TBrowserCapabilityAuthorizationRequest & {
@@ -91,8 +94,17 @@ export type TBrowserRuntimeOperationIdentity = TReadonlyBrowserCapabilityAuthori
91
94
  readonly capabilityId: string;
92
95
  readonly leaseId: string;
93
96
  readonly action: string;
97
+ readonly classification: TBrowserRuntimeOperationClassification;
94
98
  readonly startedAt: number;
95
99
  };
100
+ export type TBrowserRuntimeOperationClassification = 'raw-input' | 'viewport' | 'navigation' | 'tab' | 'agent-action';
101
+ export type TBrowserRuntimeLeaseAuthority = TReadonlyBrowserCapabilityAuthorizationRequest & {
102
+ readonly runtimeAuthorityId: string;
103
+ readonly authorityGeneration: number;
104
+ readonly incarnationGeneration: number;
105
+ readonly capabilityId: string;
106
+ readonly leaseId: string;
107
+ };
96
108
  export type TBrowserRuntimeAuditEvent = TBrowserRuntimeOperationIdentity & {
97
109
  readonly phase: 'completed' | 'failed';
98
110
  readonly finishedAt: number;
@@ -151,14 +163,17 @@ export interface IBrowserRuntimeOptions {
151
163
  authorizationTimeoutMs?: number;
152
164
  beforeOperationTimeoutMs?: number;
153
165
  maxTabsPerResource?: number;
166
+ maxQueuedOperationsPerLease?: number;
154
167
  operationTimeoutMs?: number;
155
168
  quiescenceTimeoutMs?: number;
156
169
  terminationGraceMs?: number;
157
170
  terminationForceMs?: number;
171
+ cleanupTimeoutMs?: number;
158
172
  auditTimeoutMs?: number;
159
173
  confinementProbeTimeoutMs?: number;
160
174
  idleTerminationMs?: number;
161
175
  maxFrameBytes?: number;
176
+ maxOutstandingFrames?: number;
162
177
  frameAcknowledgementTimeoutMs?: number;
163
178
  egress?: Omit<IBrowserEgressProxyOptions, 'projectId' | 'browserResourceId'>;
164
179
  artifacts?: Omit<IBrowserArtifactStoreOptions, 'rootDirectory'>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelprofile.com/browser-runtime",
3
- "version": "2.1.2",
3
+ "version": "3.0.1",
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",
@@ -29,7 +29,7 @@
29
29
  "@modelprofile.com/flexharness": "^4.0.0",
30
30
  "@push.rocks/smartagent": "^4.8.0",
31
31
  "@push.rocks/smartmcp": "^0.3.0",
32
- "@push.rocks/smartpuppeteer": "^2.3.0",
32
+ "@push.rocks/smartpuppeteer": "^2.4.0",
33
33
  "ipaddr.js": "^2.5.0"
34
34
  },
35
35
  "devDependencies": {
package/readme.hints.md CHANGED
@@ -5,7 +5,7 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
5
5
  ## Ownership and lifecycle
6
6
 
7
7
  - Controller durable truth is a logical resource with immutable `projectId`, stable random `browserResourceId`, and a revisioned attachment binding. Runtime state is process-local and must be re-registered after every restart.
8
- - A resource slot owns only incarnation and arbitration state: its transition mutex, arbitration and incarnation generations, lease, operation, frame subscriber, Chromium session, authenticated egress proxy, private profile, and idle termination timer.
8
+ - A resource slot owns only incarnation and arbitration state: its transition mutex, arbitration, incarnation, and process-local authority generations, lease, bounded operation FIFO, bounded frame window, Chromium session, authenticated egress proxy, private profile, and idle termination timer.
9
9
  - `terminateResource()` and idle termination remove only the incarnation. They preserve registration, attachment, capabilities, and artifacts. `retireResource()` permanently fences, revokes, terminates, purges exact-resource artifacts, then unregisters only after all cleanup succeeds. Durable retirement remains Controller truth; Runtime tombstones are bounded and process-local.
10
10
  - Failed retirement retains a fenced, retryable registration. Unknown profile, lock, or artifact ownership remains fail closed.
11
11
  - Launch quota admission uses synchronous pending reservations across global and per-project running-resource limits.
@@ -16,8 +16,10 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
16
16
  - Any `sessionId: null` binding is detached; revision zero is specifically the initial detached/no-agent-authority state. A newer attachment synchronously closes admission before waiting for the resource mutex, revokes its capabilities, and quiesces its active work. Work that ignores cancellation escalates only that resource incarnation.
17
17
  - Agent capabilities require the exact current non-detached qualified session. Human capabilities bind the exact resource, project, attachment authority, and revision but intentionally carry no session ID and may be issued while detached. Any attachment advance revokes both human and agent capabilities.
18
18
  - Capability tokens are returned once. Runtime records retain only SHA-256 digests and use `timingSafeEqual`. Authorization is rechecked after asynchronous host authorization, during lease acquisition, and before and after every operation.
19
- - Capability, audit, and lease identity includes immutable project, resource, attachment authority and revision, actor, peer, role, source, qualified agent session, and Flex scope/channel where applicable.
20
- - `beforeOperation` reserves the exact resource and lets the host persist policy/audit through an awaited fail-closed gate. Runtime then atomically revalidates lease, attachment, session, arbitration generation, and incarnation before starting the side effect. Terminal audit remains best effort, uses the same operation ID, and runs after bounded cleanup releases or fences the exact reservation.
19
+ - Capability, audit, and lease identity includes immutable project, resource, attachment authority and revision, actor, peer, role, source, qualified agent session, and Flex scope/channel/run where applicable.
20
+ - Same-lease operations use a strict bounded FIFO. Queue cancellation composes external abort, lease revocation, attachment transition, resource termination, and runtime shutdown. Only dequeued work reaches `beforeOperation`; started uncertain work must quiesce or fence its exact incarnation before terminal audit and queue advancement.
21
+ - `beforeOperation` reserves the exact resource and lets the host persist policy/audit through an awaited fail-closed gate. Runtime then atomically revalidates lease, attachment, session, arbitration generation, and incarnation before starting the side effect. Both host hooks carry the same exact operation ID and classification; terminal audit remains best effort and runs after bounded cleanup releases or fences the exact reservation.
22
+ - Lease authority snapshots are immutable process-local revalidation tokens. They expose `authorityGeneration` and bind the runtime instance, incarnation, exact `capabilityId`, exact `leaseId`, and attachment, but never replace Controller durable attachment truth.
21
23
 
22
24
  ## Browser confinement
23
25
 
@@ -34,7 +36,9 @@ Durable implementation findings for `@modelprofile.com/browser-runtime`.
34
36
 
35
37
  ## Adapter boundary
36
38
 
37
- - Framed messages contain no identity selectors. The parent binds project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, and channel out of band.
39
+ - Framed messages contain no identity selectors. The parent binds project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, channel, and exact run out of band.
38
40
  - One qualified session can own multiple resource-specific framed channels. Capability revocation owns server-peer disconnect; peer-initiated release/close revokes only that channel's capability without recursively disconnecting itself.
39
- - Flex resolves only a capability token. SmartAgent exposes exactly navigate, snapshot, screenshot, click, fill, and press.
41
+ - Framed request counts and queued write bytes are bounded. Runtime shutdown has a bounded caller-visible cleanup deadline while retaining in-flight cleanup ownership for retry.
42
+ - Frame delivery owns a bounded map of exact identities and configures SmartPuppeteer's separate private CDP-to-ack map to the same limit. Equal limits prevent private eviction while the application map has spare capacity, but private-first retirement at full capacity can make an exact acknowledgement fulfill `{ accepted: false }`. Both fulfilled booleans settle the application entry without proving successful upstream retirement; rejection or timeout fails closed. Application acknowledgement timeout and failed delivery deliberately fail the lease regardless of a fulfilled boolean. There is no cumulative frame protocol.
43
+ - Flex resolves only a capability token, and the provider's run must exactly match its trusted framed client. SmartAgent exposes exactly navigate, snapshot, screenshot, click, fill, and press.
40
44
  - MCP independent authentication returns the complete expected binding. The binding is retained in server-owned auth context and tool schemas expose no resource, session, revision, or authority selector.
package/readme.md CHANGED
@@ -71,9 +71,9 @@ try {
71
71
  }
72
72
  ```
73
73
 
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.
74
+ 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
75
 
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.
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. `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 ownership of the cleanup still in flight; a later `stop()` rejoins it, and `start()` waits for it to finish before restarting.
77
77
 
78
78
  ## Attachment Fencing
79
79
 
@@ -97,13 +97,19 @@ Agent capabilities require the exact current non-detached qualified session. Hum
97
97
 
98
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.
99
99
 
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.
100
+ 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.
101
+
102
+ `beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, classification, start time, and an `AbortSignal` after dequeue but before the browser side effect starts. Classifications are `raw-input`, `viewport`, `navigation`, `tab`, and `agent-action`. 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 classification and runs after bounded operation cleanup releases or fences the exact reservation.
103
+
104
+ `lease.getAuthority()` returns an immutable process-local lease authority containing the runtime authority ID, exported `authorityGeneration`, incarnation generation, and complete capability binding. Every snapshot also binds the exact `capabilityId` and `leaseId`. `lease.isAuthorityCurrent(authority)` performs an exact synchronous revalidation suitable for a host-owned virtual stream. These values are not durable Controller state and do not replace attachment checks against the Controller database.
105
+
106
+ Human frame delivery keeps a bounded exact-identity application window. `maxOutstandingFrames` defaults to 4 and accepts values from 1 through 32. Runtime passes the same bound to SmartPuppeteer's private CDP-to-ack window; the windows remain separately owned, but equal limits prevent private eviction while the application window still has spare capacity. At full capacity SmartPuppeteer retires its oldest private entry before publishing the next frame, so a later exact acknowledgement can fulfill with `{ accepted: false }`. A fulfilled `true` or `false` settles the Runtime entry locally without fencing; `false` does not prove that upstream retirement succeeded. Rejected or timed-out acknowledgement operations fail closed, as do application acknowledgement timeout and failed-delivery retirement regardless of a fulfilled boolean. Frame acknowledgement stays outside the operation FIFO.
101
107
 
102
108
  ## Trusted Pipe And Flex
103
109
 
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.
110
+ 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, exact run, and resource-specific channel. Incoming frames cannot select identity. One session may use multiple resource-specific channels concurrently. Framed client/server request work and queued writes are bounded under backpressure.
105
111
 
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.
112
+ `BrowserRuntimeFlexToolProvider<TScope>` resolves only a capability token and exposes the six approved SmartAgent actions. The provider requires its client run ID to match the exact Flex tool-provider context, and every runtime operation hook retains that run ID. The framed server validates the token against its complete trusted binding.
107
113
 
108
114
  ## MCP Handler
109
115
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '2.1.2',
6
+ version: '3.0.1',
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
  }
@@ -34,17 +34,16 @@ implements plugins.flexharness.IFlexToolProvider<TScope> {
34
34
  ): Promise<plugins.flexharness.IFlexToolHandle> {
35
35
  if (this.state !== 'unused') throw new BrowserRuntimeError('BUSY');
36
36
  this.state = 'provisioning';
37
- const scopeId = validateBoundedString(context.scopeId, 'scopeId', 1, 128);
38
- const sessionId = validateBoundedString(context.sessionId, 'sessionId', 1, 128);
39
- if (
40
- this.client.scopeId !== scopeId
41
- || this.client.sessionId.harnessId !== 'flex'
42
- || this.client.sessionId.nativeId !== sessionId
43
- ) {
44
- this.state = 'closed';
45
- throw new BrowserRuntimeError('CAPABILITY_INVALID');
46
- }
47
37
  try {
38
+ const scopeId = validateBoundedString(context.scopeId, 'scopeId', 1, 128);
39
+ const sessionId = validateBoundedString(context.sessionId, 'sessionId', 1, 128);
40
+ const runId = validateBoundedString(context.runId, 'runId', 1, 256);
41
+ if (
42
+ this.client.scopeId !== scopeId
43
+ || this.client.sessionId.harnessId !== 'flex'
44
+ || this.client.sessionId.nativeId !== sessionId
45
+ || this.client.runId !== runId
46
+ ) throw new BrowserRuntimeError('CAPABILITY_INVALID');
48
47
  context.signal.throwIfAborted();
49
48
  const resolved = validateExactKeys(
50
49
  await this.resolveCapability(context),
@@ -61,9 +60,9 @@ implements plugins.flexharness.IFlexToolProvider<TScope> {
61
60
  await this.client.acquire(capabilityToken, context.signal);
62
61
  context.signal.throwIfAborted();
63
62
  } catch (error) {
63
+ this.state = 'closed';
64
64
  await this.client.release().catch(() => undefined);
65
65
  await this.client.close().catch(() => undefined);
66
- this.state = 'closed';
67
66
  throw error;
68
67
  }
69
68
  this.state = 'active';
@@ -207,6 +207,7 @@ export class BrowserRuntimeFramedServerPeer {
207
207
  private readonly trustedSource: 'flex';
208
208
  private readonly trustedScopeId: string;
209
209
  private readonly trustedChannelId: string;
210
+ private readonly trustedRunId: string;
210
211
  private readonly trustedSessionId: IBrowserFlexSessionId;
211
212
  private readonly channel: JsonFramedChannel;
212
213
  private readonly activeRequests = new Map<string, AbortController>();
@@ -253,6 +254,7 @@ export class BrowserRuntimeFramedServerPeer {
253
254
  this.trustedSource = options.source;
254
255
  this.trustedScopeId = validateBoundedString(options.scopeId, 'scopeId', 1, 256);
255
256
  this.trustedChannelId = validateBoundedString(options.channelId, 'channelId', 1, 256);
257
+ this.trustedRunId = validateBoundedString(options.runId, 'runId', 1, 256);
256
258
  if (options.sessionId?.harnessId !== 'flex') throw new BrowserRuntimeError('INVALID_INPUT');
257
259
  this.trustedSessionId = {
258
260
  harnessId: 'flex',
@@ -353,7 +355,7 @@ export class BrowserRuntimeFramedServerPeer {
353
355
  const method = validateBoundedString(request.method, 'method', 1, 32);
354
356
  if (
355
357
  this.activeRequests.size >= hardMaxPendingRequests
356
- && (method !== 'cancel' || this.activeRequests.size >= hardMaxPendingRequests + 4)
358
+ && (method !== 'cancel' || this.activeRequests.size >= hardMaxPendingRequests * 2)
357
359
  ) {
358
360
  throw new BrowserRuntimeError('BUSY');
359
361
  }
@@ -399,6 +401,7 @@ export class BrowserRuntimeFramedServerPeer {
399
401
  source: this.trustedSource,
400
402
  scopeId: this.trustedScopeId,
401
403
  channelId: this.trustedChannelId,
404
+ runId: this.trustedRunId,
402
405
  sessionId: this.trustedSessionId,
403
406
  } as const;
404
407
  const descriptor = this.runtime.authorizeCapability(capabilityToken, authority);
@@ -446,6 +449,7 @@ export class BrowserRuntimeFramedServerPeer {
446
449
  attachmentRevision: this.trustedAttachmentRevision,
447
450
  scopeId: this.trustedScopeId,
448
451
  channelId: this.trustedChannelId,
452
+ runId: this.trustedRunId,
449
453
  sessionId: { ...this.trustedSessionId },
450
454
  };
451
455
  } else if (method === 'execute') {
@@ -562,6 +566,7 @@ export class BrowserRuntimeFramedClient {
562
566
  private closePromise?: Promise<void>;
563
567
  private readonly scopeIdValue: string;
564
568
  private readonly channelIdValue: string;
569
+ private readonly runIdValue: string;
565
570
  private readonly sessionIdValue: IBrowserFlexSessionId;
566
571
  private readonly authority: TBrowserCapabilityAuthorizationRequest;
567
572
  private readonly afterAcquireResponse?: IBrowserRuntimeFramedClientTestingOptions['afterAcquireResponse'];
@@ -572,6 +577,7 @@ export class BrowserRuntimeFramedClient {
572
577
  }
573
578
  this.scopeIdValue = validateBoundedString(options.scopeId, 'scopeId', 1, 256);
574
579
  this.channelIdValue = validateBoundedString(options.channelId, 'channelId', 1, 256);
580
+ this.runIdValue = validateBoundedString(options.runId, 'runId', 1, 256);
575
581
  this.sessionIdValue = {
576
582
  harnessId: 'flex',
577
583
  nativeId: validateBoundedString(options.sessionId.nativeId, 'sessionId.nativeId', 1, 256),
@@ -597,6 +603,7 @@ export class BrowserRuntimeFramedClient {
597
603
  source: 'flex',
598
604
  scopeId: this.scopeIdValue,
599
605
  channelId: this.channelIdValue,
606
+ runId: this.runIdValue,
600
607
  sessionId: this.sessionIdValue,
601
608
  };
602
609
  this.afterAcquireResponse = browserRuntimeTesting in options
@@ -647,6 +654,10 @@ export class BrowserRuntimeFramedClient {
647
654
  return this.channelIdValue;
648
655
  }
649
656
 
657
+ public get runId(): string {
658
+ return this.runIdValue;
659
+ }
660
+
650
661
  public get sessionId(): IBrowserFlexSessionId {
651
662
  return { ...this.sessionIdValue };
652
663
  }
@@ -790,7 +801,10 @@ export class BrowserRuntimeFramedClient {
790
801
  fields: Record<string, unknown>,
791
802
  ): Promise<unknown> {
792
803
  this.start();
793
- if (this.pending.size >= this.maxPendingRequests) throw new BrowserRuntimeError('BUSY');
804
+ const pendingLimit = method === 'cancel'
805
+ ? this.maxPendingRequests * 2
806
+ : this.maxPendingRequests;
807
+ if (this.pending.size >= pendingLimit) throw new BrowserRuntimeError('BUSY');
794
808
  const responsePromise = new Promise<unknown>((resolve, reject) => {
795
809
  const timer = setTimeout(() => {
796
810
  this.pending.delete(id);