Three fold-ins so gbrowser can become a thin overlay instead of forking
browse-server:
- Export isCustomChromium(): detects custom Chromium builds that bake the
extension in as a component extension. Prefers explicit
GSTACK_CHROMIUM_KIND=custom-extension-baked signal; falls back to
GSTACK_CHROMIUM_PATH substring containing 'GBrowser' / 'gbrowser'.
Gates the --load-extension push at launchHeaded so we don't trigger
ServiceWorkerState::SetWorkerId DCHECK when two copies of the same
service worker race to register.
- Swap hardcoded path.join(HOME, '.gstack', 'chromium-profile') in
launchHeaded for resolveChromiumProfile() so phoenix can pass a
per-workspace profile via CHROMIUM_PROFILE env (one daemon per gbd
workspace, each with a distinct profile dir).
- Call cleanSingletonLocks(userDataDir) immediately after mkdirSync.
Chromium's ProcessSingleton refuses to start when stale
SingletonLock/Socket/Cookie files survive a SIGKILL or hard crash;
pre-launch cleanup defends against the crash case. Safe under external
coordination (gbd.lock for gbrowser, single-instance CLI check for
gstack).
The existing .auth.json write at L291-302 is preserved — extensions
still need it for bootstrap even when component-baked.
Adds browse/test/browser-manager-custom-chromium.test.ts with 8 tests
covering both the env-kind and path-substring signals plus stock /
playwright-bundled Chromium negative cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>