@farmslot/adapter-web 0.10.6 → 0.10.8

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 CHANGED
@@ -6,6 +6,14 @@ All notable changes to `@farmslot/adapter-web` are tracked here.
6
6
 
7
7
  - Active-development baseline; add user-facing changes here before release or package publication.
8
8
 
9
+ ## 0.10.8 - 2026-10-16
10
+
11
+ - Align the worker dependency set with Protocol 0.49.0 person-device sessions.
12
+
13
+ ## 0.10.7 - 2026-10-15
14
+
15
+ - Disable macOS code-signing clones across isolated browser launches, merging existing disabled features; let profile cleanup locks retain live owners without a probe-age timeout.
16
+
9
17
  ## 0.10.6 - 2026-10-15
10
18
 
11
19
  - Use the operator-wait runtime and protocol dependency set.
@@ -113,10 +113,11 @@ export function resolveBrowser({ env, cft, probe, cachePath, chromeCandidates, e
113
113
  extensionLoading: string;
114
114
  schemaVersion: number;
115
115
  }>;
116
- export function withProbeLock(cachePath: any, key: any, fn: any, { waitMs, beforeClaim, readStartTime }?: {
116
+ export function withProbeLock(cachePath: any, key: any, fn: any, { waitMs, beforeClaim, readStartTime, keepLiveOwner }?: {
117
117
  waitMs?: number | undefined;
118
118
  beforeClaim?: null | undefined;
119
119
  readStartTime?: typeof startTimeOf | undefined;
120
+ keepLiveOwner?: boolean | undefined;
120
121
  }): Promise<any>;
121
122
  export function readManagedChromePolicy({ platform, plists, exists, readKey, extensionName, }?: {
122
123
  platform?: NodeJS.Platform | undefined;
@@ -6,6 +6,14 @@ export function clearDetachedLaunchUnproven(profile: any): void;
6
6
  export function clearValidationPortQuarantine(port: any, profile: any, lease: any): void;
7
7
  export function createRuntimeIdentityNonce(): string;
8
8
  export function detachedLaunchUnprovenPath(profile: any): string;
9
+ /**
10
+ * Chrome's macOS clone manager runs after startup, regardless of spawn method.
11
+ * Merge feature switches: Chromium honors only the last --disable-features.
12
+ * @param {string[]} args
13
+ * @param {string} [platform]
14
+ * @returns {string[]}
15
+ */
16
+ export function disableMacCodeSignClone(args: string[], platform?: string): string[];
9
17
  export function hasDetachedLaunchUnproven(profile: any): boolean;
10
18
  export function isolatedProfileArgs(platform?: NodeJS.Platform): string[];
11
19
  export function markDetachedLaunchUnproven(profile: any): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/adapter-web",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "Web platform adapter for Farmslot recipes: resolve and launch an isolated Chromium, own its CDP port, load unpacked extensions, and keep macOS focus.",
5
5
  "type": "commonjs",
6
6
  "exports": {
@@ -83,9 +83,9 @@
83
83
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/adapter-web"
84
84
  },
85
85
  "dependencies": {
86
- "@farmslot/adapter-sdk": "0.15.1",
87
- "@farmslot/protocol": "0.48.0",
88
- "@farmslot/recipe-runner": "0.37.1",
86
+ "@farmslot/adapter-sdk": "0.15.2",
87
+ "@farmslot/protocol": "0.49.0",
88
+ "@farmslot/recipe-runner": "0.37.2",
89
89
  "ws": "^8.18.0"
90
90
  },
91
91
  "devDependencies": {
@@ -38,6 +38,7 @@ const path = require('node:path');
38
38
  const { createRequire } = require('node:module');
39
39
 
40
40
  const cdp = require('./browser-cdp.cjs');
41
+ const { disableMacCodeSignClone } = require('./chrome-args.cjs');
41
42
 
42
43
  const SCHEMA_VERSION = 2;
43
44
  const LOAD_EXTENSION = 'load-extension';
@@ -445,14 +446,15 @@ const { START_TIME_SLACK_MS } = cdp;
445
446
  function ticketFree(
446
447
  dir,
447
448
  number,
448
- { nowMs = Date.now(), budgetMs = 30000, readStartTime = startTimeOf } = {},
449
+ { nowMs = Date.now(), budgetMs = 30000, readStartTime = startTimeOf, keepLiveOwner = false } = {},
449
450
  ) {
450
451
  if (number === 0 || fs.existsSync(path.join(dir, `${number}.released`))) return true;
451
452
  const ticket = readTicket(path.join(dir, String(number)));
452
453
  if (!ticket) return true;
453
454
  const { holder } = ticket;
454
455
  if (holder && Number.isInteger(holder.pid) && Number.isFinite(holder.at)) {
455
- if (!pidAlive(holder.pid) || nowMs - holder.at > PROBE_LOCK_STALE_MS) return true;
456
+ if (!pidAlive(holder.pid) || (!keepLiveOwner && nowMs - holder.at > PROBE_LOCK_STALE_MS))
457
+ return true;
456
458
  if (typeof holder.started !== 'string' || holder.pid === process.pid || budgetMs <= 0)
457
459
  return false;
458
460
  const started = readStartTime(holder.pid, budgetMs);
@@ -491,11 +493,13 @@ function newestTicket(dir) {
491
493
  // the next one. The directory grows by two small files per probe; a crashed
492
494
  // holder's unreleased ticket stays, judged free once its holder is gone.
493
495
  // `beforeClaim` and `readStartTime` are test seams.
496
+ // keepLiveOwner is used by profile deletion: an old but still-live cleanup
497
+ // owner cannot be displaced while removing a large directory.
494
498
  async function withProbeLock(
495
499
  cachePath,
496
500
  key,
497
501
  fn,
498
- { waitMs = 150000, beforeClaim = null, readStartTime = startTimeOf } = {},
502
+ { waitMs = 150000, beforeClaim = null, readStartTime = startTimeOf, keepLiveOwner = false } = {},
499
503
  ) {
500
504
  const dir = `${cachePath}.${key.slice(0, 16)}.locks`;
501
505
  fs.mkdirSync(dir, { recursive: true });
@@ -505,7 +509,9 @@ async function withProbeLock(
505
509
  let fd = null;
506
510
  for (;;) {
507
511
  const newest = newestTicket(dir);
508
- if (ticketFree(dir, newest, { budgetMs: deadline - Date.now(), readStartTime })) {
512
+ if (
513
+ ticketFree(dir, newest, { budgetMs: deadline - Date.now(), readStartTime, keepLiveOwner })
514
+ ) {
509
515
  if (beforeClaim) await beforeClaim(newest);
510
516
  if (Date.now() > deadline) throw timedOut();
511
517
  try {
@@ -675,7 +681,7 @@ async function probeLaunch(
675
681
  // window. Like the launchers, it keeps a window behind the operator's apps
676
682
  // rendering.
677
683
  const backgroundWindow = display === HEADFUL;
678
- const args = [
684
+ const args = disableMacCodeSignClone([
679
685
  ...(display === HEADLESS ? ['--headless=new'] : ['--window-size=480,360']),
680
686
  `--user-data-dir=${profile}`,
681
687
  '--no-first-run',
@@ -687,7 +693,7 @@ async function probeLaunch(
687
693
  '--remote-debugging-address=127.0.0.1',
688
694
  `--remote-debugging-port=${port}`,
689
695
  backgroundWindow ? '--no-startup-window' : 'about:blank',
690
- ];
696
+ ]);
691
697
  if (method === LAUNCH_SERVICES) {
692
698
  execFileSync('open', ['-g', '-n', '-a', appBundleFor(executable), '--args', ...args], {
693
699
  stdio: 'ignore',
@@ -38,11 +38,32 @@ function isolatedProfileArgs(platform = process.platform) {
38
38
  // Keep requestAnimationFrame, transitions, and input hit testing alive when the
39
39
  // slot-owned browser is occluded by the operator's other windows.
40
40
  function automationRuntimeArgs() {
41
- return [
41
+ return disableMacCodeSignClone([
42
42
  '--disable-background-timer-throttling',
43
43
  '--disable-backgrounding-occluded-windows',
44
44
  '--disable-renderer-backgrounding',
45
- ];
45
+ ]);
46
+ }
47
+
48
+ /**
49
+ * Chrome's macOS clone manager runs after startup, regardless of spawn method.
50
+ * Merge feature switches: Chromium honors only the last --disable-features.
51
+ * @param {string[]} args
52
+ * @param {string} [platform]
53
+ * @returns {string[]}
54
+ */
55
+ function disableMacCodeSignClone(args, platform = process.platform) {
56
+ if (platform !== 'darwin') return args;
57
+ const disabled = new Set(['MacAppCodeSignClone']);
58
+ const remaining = [];
59
+ for (const arg of args) {
60
+ if (arg.startsWith('--disable-features=')) {
61
+ for (const feature of arg.slice('--disable-features='.length).split(',')) {
62
+ if (feature) disabled.add(feature);
63
+ }
64
+ } else remaining.push(arg);
65
+ }
66
+ return [`--disable-features=${[...disabled].join(',')}`, ...remaining];
46
67
  }
47
68
 
48
69
  function createRuntimeIdentityNonce() {
@@ -353,6 +374,7 @@ module.exports = {
353
374
  clearValidationPortQuarantine,
354
375
  createRuntimeIdentityNonce,
355
376
  detachedLaunchUnprovenPath,
377
+ disableMacCodeSignClone,
356
378
  hasDetachedLaunchUnproven,
357
379
  isolatedProfileArgs,
358
380
  markDetachedLaunchUnproven,
@@ -34,6 +34,7 @@ const {
34
34
  clearValidationPortQuarantine,
35
35
  createRuntimeIdentityNonce,
36
36
  detachedLaunchUnprovenPath,
37
+ disableMacCodeSignClone,
37
38
  hasDetachedLaunchUnproven,
38
39
  isolatedProfileArgs,
39
40
  markDetachedLaunchUnproven,
@@ -225,7 +226,7 @@ function runLaunch(opts, acquireLock) {
225
226
  let owner = null;
226
227
  const runtimeNonce = createRuntimeIdentityNonce();
227
228
  const startedAt = Date.now();
228
- const chromeArgs = [
229
+ const chromeArgs = disableMacCodeSignClone([
229
230
  `--user-data-dir=${profile}`,
230
231
  // Debug-port trio (address + port + scoped allow-origins) from the shared module,
231
232
  // so the DevTools origin allow-list can never drift away from the port.
@@ -241,7 +242,7 @@ function runLaunch(opts, acquireLock) {
241
242
  '--disable-extensions-content-verification',
242
243
  '--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
243
244
  ...extensionLaunchArgs(extensionDir, browser.extensionLoading),
244
- ];
245
+ ]);
245
246
  // Initial tab: the caller's URL when provided, else the extension home. The
246
247
  // extension home avoids leaving disposable chrome://newtab/ or extensions
247
248
  // manager tabs in the operator's slot browser. On macOS the browser starts
@@ -45,6 +45,8 @@ import os from 'node:os';
45
45
  import path from 'node:path';
46
46
  import { fileURLToPath } from 'node:url';
47
47
 
48
+ import { disableMacCodeSignClone } from '../chrome-args.cjs';
49
+
48
50
  import { hostResolverRules } from './lib/blocked-hosts.mjs';
49
51
  import { browserWebSocketUrl, CdpClient } from './lib/cdp-client.mjs';
50
52
  import {
@@ -596,6 +598,7 @@ export async function launchWebDappBrowser(
596
598
  // when its first window appears. Windows are created over CDP instead, in
597
599
  // the background.
598
600
  browserArgs.push(launchMethod === LAUNCH_SERVICES ? '--no-startup-window' : 'about:blank');
601
+ const launchArgs = disableMacCodeSignClone(browserArgs);
599
602
 
600
603
  started = true;
601
604
  // Where this browser's console begins in the slot capture, for
@@ -631,14 +634,14 @@ export async function launchWebDappBrowser(
631
634
  }
632
635
  const browserPid =
633
636
  launchMethod === LAUNCH_SERVICES
634
- ? await launchInBackground(application, browserArgs, {
637
+ ? await launchInBackground(application, launchArgs, {
635
638
  runtime,
636
639
  cdpPort,
637
640
  logFile: path.join(runtime, 'browser.log'),
638
641
  pidFile: path.join(runtime, 'browser.pid'),
639
642
  timeoutMs: timeouts.browserStartMs,
640
643
  })
641
- : await spawnDetached(browser.bin, browserArgs, {
644
+ : await spawnDetached(browser.bin, launchArgs, {
642
645
  logFile: path.join(runtime, 'browser.log'),
643
646
  pidFile: path.join(runtime, 'browser.pid'),
644
647
  env,