@modelprofile.com/browser-runtime 4.3.0 → 5.0.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.
@@ -40,6 +40,7 @@ export interface IBrowserRuntimeOwnershipTestingOptions {
40
40
  now?(): number;
41
41
  beforeMetadataPublication?(): Promise<void>;
42
42
  afterLegacyLockProbe?(): Promise<void>;
43
+ afterEmptyProcessCmdline?(pid: number): Promise<void>;
43
44
  beforeGenerationRemoval?(reason: TOwnershipGenerationRemovalReason): Promise<void>;
44
45
  }
45
46
 
@@ -118,6 +119,7 @@ export class BrowserRuntimeOwnership {
118
119
  private readonly now: () => number;
119
120
  private readonly beforeMetadataPublication?: () => Promise<void>;
120
121
  private readonly afterLegacyLockProbe?: () => Promise<void>;
122
+ private readonly afterEmptyProcessCmdline?: (pid: number) => Promise<void>;
121
123
  private readonly beforeGenerationRemoval?: (
122
124
  reason: TOwnershipGenerationRemovalReason,
123
125
  ) => Promise<void>;
@@ -150,6 +152,7 @@ export class BrowserRuntimeOwnership {
150
152
  this.now = optionsArg.testing?.now ?? Date.now;
151
153
  this.beforeMetadataPublication = optionsArg.testing?.beforeMetadataPublication;
152
154
  this.afterLegacyLockProbe = optionsArg.testing?.afterLegacyLockProbe;
155
+ this.afterEmptyProcessCmdline = optionsArg.testing?.afterEmptyProcessCmdline;
153
156
  this.beforeGenerationRemoval = optionsArg.testing?.beforeGenerationRemoval;
154
157
  this.onOwnershipLost = optionsArg.onOwnershipLost;
155
158
  }
@@ -1098,7 +1101,8 @@ export class BrowserRuntimeOwnership {
1098
1101
  continue;
1099
1102
  }
1100
1103
  if (cmdline.byteLength === 0) {
1101
- if (!await this.processIsZombie(pid)) indeterminate = true;
1104
+ await this.afterEmptyProcessCmdline?.(pid);
1105
+ if (!await this.emptyCommandProcessIsClear(pid)) indeterminate = true;
1102
1106
  continue;
1103
1107
  }
1104
1108
  let cmdlineText: string;
@@ -1204,15 +1208,28 @@ export class BrowserRuntimeOwnership {
1204
1208
  return 'indeterminate';
1205
1209
  }
1206
1210
 
1207
- private async processIsZombie(pidArg: number): Promise<boolean> {
1211
+ private async emptyCommandProcessIsClear(pidArg: number): Promise<boolean> {
1212
+ const processPath = plugins.path.join(this.procRoot, String(pidArg));
1208
1213
  try {
1209
1214
  const status = await this.readBoundedFile(
1210
- plugins.path.join(this.procRoot, String(pidArg), 'status'),
1215
+ plugins.path.join(processPath, 'status'),
1211
1216
  64 * 1024,
1212
1217
  );
1213
- return /^State:\s+Z(?:\s|$)/mu.test(status);
1214
- } catch {
1215
- return false;
1218
+ const uidState = this.parseProcessUidState(status);
1219
+ if (uidState === 'other') return true;
1220
+ if (uidState !== 'same') return false;
1221
+ const stateLines = status.split('\n').filter((line) => line.startsWith('State:'));
1222
+ return stateLines.length === 1 && /^State:[\t ]+Z(?:[\t ]|$)/u.test(stateLines[0]!);
1223
+ } catch (error) {
1224
+ if (!isMissingError(error)) return false;
1225
+ // An empty command line can precede process exit. A missing status file
1226
+ // alone is insufficient: the PID directory must also have disappeared.
1227
+ try {
1228
+ await plugins.fsPromises.lstat(processPath, { bigint: true });
1229
+ return false;
1230
+ } catch (statError) {
1231
+ return isMissingError(statError);
1232
+ }
1216
1233
  }
1217
1234
  }
1218
1235
 
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,
@@ -48,6 +51,7 @@ export type {
48
51
  IBrowserResourceKey,
49
52
  IBrowserResourceRegistration,
50
53
  IBrowserRuntimeState,
54
+ IBrowserRuntimeViewportResult,
51
55
  IBrowserRuntimeTabState,
52
56
  IBrowserScreenshotAction,
53
57
  IBrowserScreenshotResult,
package/ts/interfaces.ts CHANGED
@@ -83,7 +83,7 @@ export interface IBrowserFlexCapabilityBinding extends IBrowserCapabilityBinding
83
83
  export interface IBrowserMcpCapabilityBinding extends IBrowserCapabilityBindingBase {
84
84
  role: 'agent';
85
85
  source: 'mcp';
86
- sessionId: IBrowserOpenCodeSessionId;
86
+ sessionId: TQualifiedBrowserSessionId;
87
87
  scopeId?: never;
88
88
  channelId?: never;
89
89
  runId?: never;
@@ -118,7 +118,7 @@ type TReadonlyBrowserCapabilityAuthorizationRequest =
118
118
  readonly sessionId: Readonly<IBrowserFlexSessionId>;
119
119
  })
120
120
  | (Omit<Readonly<IBrowserMcpCapabilityBinding>, 'sessionId'> & {
121
- readonly sessionId: Readonly<IBrowserOpenCodeSessionId>;
121
+ readonly sessionId: Readonly<TQualifiedBrowserSessionId>;
122
122
  });
123
123
 
124
124
  export type TBrowserRuntimeOperationIdentity = TReadonlyBrowserCapabilityAuthorizationRequest & {
@@ -138,6 +138,12 @@ export type TBrowserRuntimeOperationClassification =
138
138
  | 'tab'
139
139
  | 'agent-action';
140
140
 
141
+ /** The viewport accepted by the shared resource, which may be smaller than one viewer's request. */
142
+ export interface IBrowserRuntimeViewportResult {
143
+ viewport: plugins.smartpuppeteer.ILiveBrowserViewport;
144
+ viewportRevision: number;
145
+ }
146
+
141
147
  export type TBrowserRuntimeLeaseAuthority = TReadonlyBrowserCapabilityAuthorizationRequest & {
142
148
  readonly runtimeAuthorityId: string;
143
149
  readonly authorityGeneration: number;
@@ -503,7 +509,7 @@ extends Omit<
503
509
  export interface IBrowserMcpAuthenticatedBinding extends IBrowserCapabilityBindingBase {
504
510
  role: 'agent';
505
511
  source: 'mcp';
506
- sessionId: IBrowserOpenCodeSessionId;
512
+ sessionId: TQualifiedBrowserSessionId;
507
513
  scopeId?: never;
508
514
  channelId?: never;
509
515
  }
package/ts/mcp.ts CHANGED
@@ -231,7 +231,10 @@ const validateMcpBinding = (value: unknown): IBrowserMcpAuthenticatedBinding =>
231
231
  throw new BrowserRuntimeError('CAPABILITY_INVALID');
232
232
  }
233
233
  const session = binding.sessionId as Record<string, unknown> | undefined;
234
- if (session?.harnessId !== 'opencode') throw new BrowserRuntimeError('CAPABILITY_INVALID');
234
+ if (session?.harnessId !== 'opencode' && session?.harnessId !== 'codex'
235
+ && session?.harnessId !== 'flex') {
236
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
237
+ }
235
238
  return {
236
239
  projectId: validateBoundedString(binding.projectId, 'projectId', 1, 256),
237
240
  browserResourceId: validateBoundedString(
@@ -257,7 +260,7 @@ const validateMcpBinding = (value: unknown): IBrowserMcpAuthenticatedBinding =>
257
260
  peerId: validateBoundedString(binding.peerId, 'peerId', 1, 256),
258
261
  source: 'mcp',
259
262
  sessionId: {
260
- harnessId: 'opencode',
263
+ harnessId: session.harnessId,
261
264
  nativeId: validateBoundedString(session.nativeId, 'sessionId.nativeId', 1, 256),
262
265
  },
263
266
  };