@modelprofile.com/browser-runtime 5.5.1 → 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.
- package/changelog.md +9 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.runtime.d.ts +2 -0
- package/dist_ts/classes.runtime.js +34 -4
- package/dist_ts/errors.d.ts +1 -1
- package/dist_ts/errors.js +2 -1
- package/dist_ts/interfaces.d.ts +2 -0
- package/package.json +1 -1
- package/readme.md +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.runtime.ts +32 -2
- package/ts/errors.ts +2 -0
- package/ts/interfaces.ts +2 -0
package/dist_ts/errors.d.ts
CHANGED
|
@@ -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,
|
|
47
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQXlCQSxNQUFNLGNBQWMsR0FBNkM7SUFDL0QsT0FBTyxFQUFFLDRCQUE0QjtJQUNyQyxvQkFBb0IsRUFBRSxzQ0FBc0M7SUFDNUQsbUJBQW1CLEVBQUUsNkRBQTZEO0lBQ2xGLElBQUksRUFBRSwrQkFBK0I7SUFDckMsa0JBQWtCLEVBQUUsNkJBQTZCO0lBQ2pELGtCQUFrQixFQUFFLDRCQUE0QjtJQUNoRCxrQkFBa0IsRUFBRSxrQ0FBa0M7SUFDdEQsa0JBQWtCLEVBQUUsNkNBQTZDO0lBQ2pFLGFBQWEsRUFBRSwrQkFBK0I7SUFDOUMsTUFBTSxFQUFFLDZDQUE2QztJQUNyRCxtQkFBbUIsRUFBRSxrQ0FBa0M7SUFDdkQsZUFBZSxFQUFFLHVDQUF1QztJQUN4RCxjQUFjLEVBQUUscURBQXFEO0lBQ3JFLFdBQVcsRUFBRSxxREFBcUQ7SUFDbEUsVUFBVSxFQUFFLHFEQUFxRDtJQUNqRSxhQUFhLEVBQUUseUJBQXlCO0lBQ3hDLE1BQU0sRUFBRSx3Q0FBd0M7SUFDaEQsV0FBVyxFQUFFLHFDQUFxQztJQUNsRCxjQUFjLEVBQUUseUNBQXlDO0lBQ3pELGNBQWMsRUFBRSx1Q0FBdUM7SUFDdkQsa0JBQWtCLEVBQUUseUNBQXlDO0lBQzdELGdCQUFnQixFQUFFLHdDQUF3QztJQUMxRCxPQUFPLEVBQUUsMEJBQTBCO0NBQ3BDLENBQUM7QUFFRixNQUFNLE9BQU8sbUJBQW9CLFNBQVEsS0FBSztJQUM1QixJQUFJLENBQTJCO0lBRS9DLFlBQVksSUFBOEIsRUFBRSxlQUF3QjtRQUNsRSxLQUFLLENBQUMsZUFBZSxJQUFJLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQy9DLElBQUksQ0FBQyxJQUFJLEdBQUcscUJBQXFCLENBQUM7UUFDbEMsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7SUFDbkIsQ0FBQztJQUVELElBQVcsYUFBYTtRQUN0QixPQUFPLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkMsQ0FBQztDQUNGO0FBRUQsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxLQUFjLEVBQXVCLEVBQUU7SUFDM0UsSUFBSSxLQUFLLFlBQVksbUJBQW1CLEVBQUUsQ0FBQztRQUN6QyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxJQUNFLEtBQUssWUFBWSxLQUFLO1dBQ25CLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxZQUFZLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsRUFDakUsQ0FBQztRQUNELE9BQU8sSUFBSSxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLGNBQWMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUN4RixDQUFDO0lBQ0QsT0FBTyxJQUFJLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQzVDLENBQUMsQ0FBQyJ9
|
package/dist_ts/interfaces.d.ts
CHANGED
|
@@ -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.
|
|
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",
|
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.
|
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: '5.
|
|
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
|
}
|
package/ts/classes.runtime.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|