@modelprofile.com/browser-runtime 5.5.0 → 5.6.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.
@@ -1,4 +1,4 @@
1
- export type TBrowserRuntimeErrorCode = 'ABORTED' | 'AUTHORIZATION_DENIED' | 'ATTACHMENT_CONFLICT' | 'BUSY' | 'CAPABILITY_EXPIRED' | 'CAPABILITY_INVALID' | 'CAPABILITY_REVOKED' | 'CONFINEMENT_FAILED' | 'EGRESS_DENIED' | 'FENCED' | 'FRAME_STREAM_FAILED' | 'FRAME_TOO_LARGE' | 'DIALOG_PENDING' | 'STALE_INPUT' | 'INVALID_INPUT' | 'LOCKED' | 'NOT_RUNNING' | 'PROTOCOL_ERROR' | 'QUOTA_EXCEEDED' | 'RESOURCE_NOT_FOUND' | 'RESOURCE_RETIRED' | 'TIMEOUT';
1
+ export type TBrowserRuntimeErrorCode = 'ABORTED' | 'AUTHORIZATION_DENIED' | 'ATTACHMENT_CONFLICT' | 'BUSY' | 'CAPABILITY_EXPIRED' | 'CAPABILITY_INVALID' | 'CAPABILITY_REVOKED' | 'CONFINEMENT_FAILED' | 'EGRESS_DENIED' | 'FENCED' | 'FRAME_STREAM_FAILED' | 'FRAME_TOO_LARGE' | 'DIALOG_PENDING' | 'STALE_INPUT' | 'SUPERSEDED' | 'INVALID_INPUT' | 'LOCKED' | 'NOT_RUNNING' | 'PROTOCOL_ERROR' | 'QUOTA_EXCEEDED' | 'RESOURCE_NOT_FOUND' | 'RESOURCE_RETIRED' | 'TIMEOUT';
2
2
  export declare class BrowserRuntimeError extends Error {
3
3
  readonly code: TBrowserRuntimeErrorCode;
4
4
  constructor(code: TBrowserRuntimeErrorCode, internalMessage?: string);
package/dist_ts/errors.js CHANGED
@@ -13,6 +13,7 @@ const publicMessages = {
13
13
  FRAME_TOO_LARGE: 'The browser frame exceeded its limit.',
14
14
  DIALOG_PENDING: 'Respond to the website dialog before sending input.',
15
15
  STALE_INPUT: 'The input belongs to a previous document or dialog.',
16
+ SUPERSEDED: 'A newer hover input replaced this queued operation.',
16
17
  INVALID_INPUT: 'The request is invalid.',
17
18
  LOCKED: 'The browser runtime is already locked.',
18
19
  NOT_RUNNING: 'The browser runtime is not running.',
@@ -43,4 +44,4 @@ export const toBrowserRuntimeError = (error) => {
43
44
  }
44
45
  return new BrowserRuntimeError('ABORTED');
45
46
  };
46
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXdCQSxNQUFNLGNBQWMsR0FBNkM7SUFDL0QsT0FBTyxFQUFFLDRCQUE0QjtJQUNyQyxvQkFBb0IsRUFBRSxzQ0FBc0M7SUFDNUQsbUJBQW1CLEVBQUUsNkRBQTZEO0lBQ2xGLElBQUksRUFBRSwrQkFBK0I7SUFDckMsa0JBQWtCLEVBQUUsNkJBQTZCO0lBQ2pELGtCQUFrQixFQUFFLDRCQUE0QjtJQUNoRCxrQkFBa0IsRUFBRSxrQ0FBa0M7SUFDdEQsa0JBQWtCLEVBQUUsNkNBQTZDO0lBQ2pFLGFBQWEsRUFBRSwrQkFBK0I7SUFDOUMsTUFBTSxFQUFFLDZDQUE2QztJQUNyRCxtQkFBbUIsRUFBRSxrQ0FBa0M7SUFDdkQsZUFBZSxFQUFFLHVDQUF1QztJQUN4RCxjQUFjLEVBQUUscURBQXFEO0lBQ3JFLFdBQVcsRUFBRSxxREFBcUQ7SUFDbEUsYUFBYSxFQUFFLHlCQUF5QjtJQUN4QyxNQUFNLEVBQUUsd0NBQXdDO0lBQ2hELFdBQVcsRUFBRSxxQ0FBcUM7SUFDbEQsY0FBYyxFQUFFLHlDQUF5QztJQUN6RCxjQUFjLEVBQUUsdUNBQXVDO0lBQ3ZELGtCQUFrQixFQUFFLHlDQUF5QztJQUM3RCxnQkFBZ0IsRUFBRSx3Q0FBd0M7SUFDMUQsT0FBTyxFQUFFLDBCQUEwQjtDQUNwQyxDQUFDO0FBRUYsTUFBTSxPQUFPLG1CQUFvQixTQUFRLEtBQUs7SUFDNUIsSUFBSSxDQUEyQjtJQUUvQyxZQUFZLElBQThCLEVBQUUsZUFBd0I7UUFDbEUsS0FBSyxDQUFDLGVBQWUsSUFBSSxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUMvQyxJQUFJLENBQUMsSUFBSSxHQUFHLHFCQUFxQixDQUFDO1FBQ2xDLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFRCxJQUFXLGFBQWE7UUFDdEIsT0FBTyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLENBQUM7Q0FDRjtBQUVELE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLENBQUMsS0FBYyxFQUF1QixFQUFFO0lBQzNFLElBQUksS0FBSyxZQUFZLG1CQUFtQixFQUFFLENBQUM7UUFDekMsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBQ0QsSUFDRSxLQUFLLFlBQVksS0FBSztXQUNuQixDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssWUFBWSxJQUFJLEtBQUssQ0FBQyxJQUFJLEtBQUssY0FBYyxDQUFDLEVBQ2pFLENBQUM7UUFDRCxPQUFPLElBQUksbUJBQW1CLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDeEYsQ0FBQztJQUNELE9BQU8sSUFBSSxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUM1QyxDQUFDLENBQUMifQ==
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXlCQSxNQUFNLGNBQWMsR0FBNkM7SUFDL0QsT0FBTyxFQUFFLDRCQUE0QjtJQUNyQyxvQkFBb0IsRUFBRSxzQ0FBc0M7SUFDNUQsbUJBQW1CLEVBQUUsNkRBQTZEO0lBQ2xGLElBQUksRUFBRSwrQkFBK0I7SUFDckMsa0JBQWtCLEVBQUUsNkJBQTZCO0lBQ2pELGtCQUFrQixFQUFFLDRCQUE0QjtJQUNoRCxrQkFBa0IsRUFBRSxrQ0FBa0M7SUFDdEQsa0JBQWtCLEVBQUUsNkNBQTZDO0lBQ2pFLGFBQWEsRUFBRSwrQkFBK0I7SUFDOUMsTUFBTSxFQUFFLDZDQUE2QztJQUNyRCxtQkFBbUIsRUFBRSxrQ0FBa0M7SUFDdkQsZUFBZSxFQUFFLHVDQUF1QztJQUN4RCxjQUFjLEVBQUUscURBQXFEO0lBQ3JFLFdBQVcsRUFBRSxxREFBcUQ7SUFDbEUsVUFBVSxFQUFFLHFEQUFxRDtJQUNqRSxhQUFhLEVBQUUseUJBQXlCO0lBQ3hDLE1BQU0sRUFBRSx3Q0FBd0M7SUFDaEQsV0FBVyxFQUFFLHFDQUFxQztJQUNsRCxjQUFjLEVBQUUseUNBQXlDO0lBQ3pELGNBQWMsRUFBRSx1Q0FBdUM7SUFDdkQsa0JBQWtCLEVBQUUseUNBQXlDO0lBQzdELGdCQUFnQixFQUFFLHdDQUF3QztJQUMxRCxPQUFPLEVBQUUsMEJBQTBCO0NBQ3BDLENBQUM7QUFFRixNQUFNLE9BQU8sbUJBQW9CLFNBQVEsS0FBSztJQUM1QixJQUFJLENBQTJCO0lBRS9DLFlBQVksSUFBOEIsRUFBRSxlQUF3QjtRQUNsRSxLQUFLLENBQUMsZUFBZSxJQUFJLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQy9DLElBQUksQ0FBQyxJQUFJLEdBQUcscUJBQXFCLENBQUM7UUFDbEMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7SUFDbkIsQ0FBQztJQUVELElBQVcsYUFBYTtRQUN0QixPQUFPLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkMsQ0FBQztDQUNGO0FBRUQsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxLQUFjLEVBQXVCLEVBQUU7SUFDM0UsSUFBSSxLQUFLLFlBQVksbUJBQW1CLEVBQUUsQ0FBQztRQUN6QyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxJQUNFLEtBQUssWUFBWSxLQUFLO1dBQ25CLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxZQUFZLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsRUFDakUsQ0FBQztRQUNELE9BQU8sSUFBSSxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLGNBQWMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUN4RixDQUFDO0lBQ0QsT0FBTyxJQUFJLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQzVDLENBQUMsQ0FBQyJ9
@@ -336,6 +336,8 @@ export interface IBrowserRuntimeOperationOptions {
336
336
  signal?: AbortSignal;
337
337
  timeoutMs?: number;
338
338
  onOperationStarted?(operationId: string): void;
339
+ /** Replace an adjacent, unstarted hover from this lease. Superseded calls reject with SUPERSEDED. */
340
+ coalesceHoverMoves?: boolean;
339
341
  }
340
342
  export interface IBrowserRuntimeFrameSubscription {
341
343
  close(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelprofile.com/browser-runtime",
3
- "version": "5.5.0",
3
+ "version": "5.6.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",
@@ -30,14 +30,14 @@
30
30
  "@push.rocks/smartagent": "^6.0.0",
31
31
  "@push.rocks/smartipc": "^2.6.0",
32
32
  "@push.rocks/smartmcp": "^0.3.0",
33
- "@push.rocks/smartpuppeteer": "^2.12.1",
33
+ "@push.rocks/smartpuppeteer": "^2.12.2",
34
34
  "ipaddr.js": "^2.5.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@git.zone/tsbuild": "4.4.3",
38
38
  "@git.zone/tsrun": "2.0.6",
39
39
  "@git.zone/tstest": "6.0.0",
40
- "@types/node": "26.4.1"
40
+ "@types/node": "26.5.0"
41
41
  },
42
42
  "files": [
43
43
  "ts/**/*",
package/readme.md CHANGED
@@ -164,6 +164,9 @@ Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`,
164
164
 
165
165
  BrowserRuntime alone owns the bounded execution scheduler shared by all participants. Host transports submit operations in order without adding a second execution queue or performing asynchronous authorization ahead of admission. One statistics read may run independently of mutations; its preflight and completion cannot fence input, navigation, or dialog replies. Up to four wheel operations may run concurrently, with authorization and native-send admission kept in order. Other input, viewport, and semantic operations remain barriers behind preceding wheel work. Dialog replies use one interrupt slot so a paused document operation cannot block its own response. `maxQueuedOperationsPerLease` defaults to 128 and accepts 1 through 1,024; each participant has that bound, and the resource queue is bounded by its product with `maxCapabilitiesPerResource`. Overflow fails with `QUOTA_EXCEEDED`. Releasing a participant cancels only its queued and active work. Resource termination and shutdown cancel everyone. Started uncertain work must quiesce or the exact incarnation is terminated before operation settlement.
166
166
 
167
+ Interactive hosts can pass `{ coalesceHoverMoves: true }` to `humanLease.dispatchMouse()`. Only an adjacent, unstarted, unpressed move from the same lease, document, viewport and modifier state is replaced. The replaced promise rejects with `BrowserRuntimeError` code `SUPERSEDED`; hosts should report that terminal outcome without treating it as a failed connection. Drag paths, queued or held buttons, other participants, and discrete operations retain their order. The default preserves every submitted operation.
168
+
169
+
167
170
  `setViewport()` stores the viewer's preferred width, height and device scale factor and returns `IBrowserRuntimeViewportResult` with the effective `viewport` and `viewportRevision`. The effective values are the componentwise minima across active viewers that have supplied a preference. Unchanged effective sizes do not restart the stream. Removing a viewer recomputes the viewport in the resource FIFO. Held keys and buttons are tracked per participant; leaving releases only inputs that no other participant holds. Viewport and tab/navigation transitions clear held input before changing the target. Resource-owned departure cleanup is bounded and audited as `releaseParticipant`; it does not call the external `beforeOperation` gate after the participant has lost access.
168
171
 
169
172
  `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`, `dialog`, `devtools`, `frame-stream`, `video-peer`, `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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '5.5.0',
6
+ version: '5.6.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
  }
@@ -172,6 +172,7 @@ export interface IBrowserRuntimeLeaseRenewalOptions {
172
172
  }
173
173
 
174
174
  interface IOperationRecord {
175
+ hoverKey?: string;
175
176
  operationId: string;
176
177
  lease: ILeaseRecord;
177
178
  controller: AbortController;
@@ -183,6 +184,7 @@ interface IOperationRecord {
183
184
  }
184
185
 
185
186
  interface IQueuedOperationRecord {
187
+ hoverKey?: string;
186
188
  operationId: string;
187
189
  lease: ILeaseRecord;
188
190
  action: string;
@@ -1671,6 +1673,18 @@ export class BrowserRuntime {
1671
1673
  operationOptions: IBrowserRuntimeOperationOptions = {},
1672
1674
  ): Promise<void> {
1673
1675
  this.requireHuman(lease);
1676
+ const mouse = action === 'dispatchMouse' ? input as plugins.smartpuppeteer.ILiveBrowserMouseInput : undefined;
1677
+ // Drag paths and shared held input have observable intermediate positions.
1678
+ // Only explicit hover submissions can replace another pending hover.
1679
+ const hoverKey = operationOptions.coalesceHoverMoves && mouse?.type === 'move'
1680
+ && (mouse.buttons ?? 0) === 0 && (mouse.button ?? 'none') === 'none'
1681
+ && Number.isFinite(mouse.x) && Number.isFinite(mouse.y) && mouse.x >= 0 && mouse.y >= 0
1682
+ && ![...lease.slot.leases.values()].some(participant => participant.heldButtons.size > 0)
1683
+ && !lease.slot.operationScheduler.pending.some(operation => operation.action === 'dispatchMouse' && !operation.hoverKey)
1684
+ && ![...lease.slot.operations].some(operation => operation.action === 'dispatchMouse' && !operation.hoverKey)
1685
+ ? JSON.stringify([mouse.tabId, mouse.generation, mouse.viewportRevision,
1686
+ mouse.modifiers?.alt ?? false, mouse.modifiers?.control ?? false,
1687
+ mouse.modifiers?.meta ?? false, mouse.modifiers?.shift ?? false]) : undefined;
1674
1688
  await this.runOperation(lease, action, 'raw-input', operationOptions, async (_signal, session) => {
1675
1689
  if (action === 'dispatchMouse') {
1676
1690
  const mouse = input as plugins.smartpuppeteer.ILiveBrowserMouseInput;
@@ -1726,7 +1740,7 @@ export class BrowserRuntime {
1726
1740
  } else {
1727
1741
  await session.insertText(input as plugins.smartpuppeteer.ILiveBrowserInsertTextInput);
1728
1742
  }
1729
- });
1743
+ }, undefined, false, hoverKey);
1730
1744
  }
1731
1745
 
1732
1746
  private keyOwnershipKey(input: plugins.smartpuppeteer.ILiveBrowserKeyInput): string {
@@ -2351,6 +2365,7 @@ export class BrowserRuntime {
2351
2365
  execute: (signal: AbortSignal, session: ILiveBrowserSessionLike) => Promise<T>,
2352
2366
  finalize?: () => Promise<void>,
2353
2367
  participantCleanup: 'participant' | 'subscription' | false = false,
2368
+ hoverKey?: string,
2354
2369
  ): Promise<T> {
2355
2370
  if (!operationOptions || typeof operationOptions !== 'object') {
2356
2371
  throw new BrowserRuntimeError('INVALID_INPUT');
@@ -2364,10 +2379,15 @@ export class BrowserRuntime {
2364
2379
  && typeof operationOptions.onOperationStarted !== 'function'
2365
2380
  ) throw new BrowserRuntimeError('INVALID_INPUT');
2366
2381
  if (operationOptions.signal?.aborted) throw new BrowserRuntimeError('ABORTED');
2382
+ if (operationOptions.coalesceHoverMoves !== undefined
2383
+ && typeof operationOptions.coalesceHoverMoves !== 'boolean') throw new BrowserRuntimeError('INVALID_INPUT');
2367
2384
  const slot = lease.slot;
2368
2385
  this.requireQueuedLease(lease, participantCleanup);
2369
2386
  this.assertSlotAvailable(slot);
2370
- if (!participantCleanup && (
2387
+ const previousHover = slot.operationScheduler.pending.at(-1);
2388
+ const superseded = hoverKey !== undefined && previousHover?.state === 'queued'
2389
+ && previousHover.lease === lease && previousHover.hoverKey === hoverKey ? previousHover : undefined;
2390
+ if (!participantCleanup && !superseded && (
2371
2391
  slot.operationScheduler.pending.filter((entry) => entry.lease === lease).length
2372
2392
  >= this.options.maxQueuedOperationsPerLease
2373
2393
  || slot.operationScheduler.pending.length >= this.options.maxQueuedOperationsPerLease
@@ -2385,6 +2405,7 @@ export class BrowserRuntime {
2385
2405
  ]);
2386
2406
  return new Promise<T>((resolve, reject) => {
2387
2407
  const queued: IQueuedOperationRecord = {
2408
+ hoverKey,
2388
2409
  operationId: randomId(18),
2389
2410
  lease,
2390
2411
  action,
@@ -2423,6 +2444,14 @@ export class BrowserRuntime {
2423
2444
  onQueuedAbort();
2424
2445
  return;
2425
2446
  }
2447
+ if (superseded) {
2448
+ // Replace atomically before draining; remove() would start the old tail's successor.
2449
+ const index = slot.operationScheduler.pending.indexOf(superseded);
2450
+ if (index >= 0) slot.operationScheduler.pending.splice(index, 1);
2451
+ superseded.state = 'settled';
2452
+ if (superseded.onQueuedAbort) superseded.signal.removeEventListener('abort', superseded.onQueuedAbort);
2453
+ superseded.reject(new BrowserRuntimeError('SUPERSEDED'));
2454
+ }
2426
2455
  slot.operationScheduler.enqueue(queued);
2427
2456
  });
2428
2457
  }
@@ -2471,6 +2500,7 @@ export class BrowserRuntime {
2471
2500
  timeoutController.signal,
2472
2501
  ]);
2473
2502
  operation = {
2503
+ hoverKey: queued.hoverKey,
2474
2504
  operationId: queued.operationId,
2475
2505
  lease,
2476
2506
  controller,
package/ts/errors.ts CHANGED
@@ -13,6 +13,7 @@ export type TBrowserRuntimeErrorCode =
13
13
  | 'FRAME_TOO_LARGE'
14
14
  | 'DIALOG_PENDING'
15
15
  | 'STALE_INPUT'
16
+ | 'SUPERSEDED'
16
17
  | 'INVALID_INPUT'
17
18
  | 'LOCKED'
18
19
  | 'NOT_RUNNING'
@@ -37,6 +38,7 @@ const publicMessages: Record<TBrowserRuntimeErrorCode, string> = {
37
38
  FRAME_TOO_LARGE: 'The browser frame exceeded its limit.',
38
39
  DIALOG_PENDING: 'Respond to the website dialog before sending input.',
39
40
  STALE_INPUT: 'The input belongs to a previous document or dialog.',
41
+ SUPERSEDED: 'A newer hover input replaced this queued operation.',
40
42
  INVALID_INPUT: 'The request is invalid.',
41
43
  LOCKED: 'The browser runtime is already locked.',
42
44
  NOT_RUNNING: 'The browser runtime is not running.',
package/ts/interfaces.ts CHANGED
@@ -460,6 +460,8 @@ export interface IBrowserRuntimeOperationOptions {
460
460
  signal?: AbortSignal;
461
461
  timeoutMs?: number;
462
462
  onOperationStarted?(operationId: string): void;
463
+ /** Replace an adjacent, unstarted hover from this lease. Superseded calls reject with SUPERSEDED. */
464
+ coalesceHoverMoves?: boolean;
463
465
  }
464
466
 
465
467
  export interface IBrowserRuntimeFrameSubscription {