Files
orca/config/scripts/dev-instance-isolation.mjs
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00

305 lines
11 KiB
JavaScript

import { createHash } from 'node:crypto'
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import net from 'node:net'
import path from 'node:path'
export const SHARED_DEV_PROFILE_DIR_NAME = 'orca-dev'
// Why the "orca-dev" prefix: CLI/orchestration dev-mode checks match "orca-dev" in ORCA_USER_DATA_PATH.
// Why short: runtime sockets live in userData and macOS caps socket paths at 104 bytes.
export const WORKTREE_DEV_PROFILES_DIR_NAME = 'orca-dev-wt'
export const WORKTREE_PROFILE_MARKER_FILE = 'orca-dev-worktree.json'
export const WORKTREE_PROFILE_NAME_PATTERN = /^[\da-f]{10}$/
export const DEV_PROFILE_MODES = ['shared', 'worktree']
// Why these ranges: clear of Vite's 5173+ (common in projects opened inside dev Orca),
// Chrome's 9222, and Orca's production ports (pairing WebSocket 6768).
export const RENDERER_PORT_RANGE = { base: 15200, spread: 200 }
export const CDP_PORT_RANGE = { base: 9333, spread: 200 }
const PORT_SWEEP = 64
// Values the runner derives per worktree; a dev Orca exports them to every terminal it spawns.
const RUNNER_DERIVED_ENV_KEYS = [
'ORCA_DEV_REPO_ROOT',
'ORCA_DEV_INSTANCE_KEY',
'ORCA_DEV_BRANCH',
'ORCA_DEV_WORKTREE_NAME',
'ORCA_DEV_INSTANCE_LABEL',
'ORCA_DEV_DOCK_TITLE',
'ORCA_DEV_RENDERER_PORT'
]
export function getAppDataDir(env = process.env, platform = process.platform) {
if (platform === 'darwin') {
return path.join(env.HOME ?? '', 'Library', 'Application Support')
}
if (platform === 'win32') {
return env.APPDATA ?? path.join(env.USERPROFILE ?? '', 'AppData', 'Roaming')
}
return env.XDG_CONFIG_HOME || path.join(env.HOME ?? '', '.config')
}
export function getSharedDevUserDataPath(env = process.env, platform = process.platform) {
return path.join(getAppDataDir(env, platform), SHARED_DEV_PROFILE_DIR_NAME)
}
export function getWorktreeDevProfilesRoot(env = process.env, platform = process.platform) {
return path.join(getAppDataDir(env, platform), WORKTREE_DEV_PROFILES_DIR_NAME)
}
export function getWorktreeDevUserDataPath(
instanceKey,
env = process.env,
platform = process.platform
) {
const name = createHash('sha1').update(instanceKey).digest('hex').slice(0, 10)
return path.join(getWorktreeDevProfilesRoot(env, platform), name)
}
export function sameFilesystemPath(left, right, platform = process.platform) {
const a = path.resolve(left)
const b = path.resolve(right)
return platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b
}
function isLinkedWorktree(repoRoot) {
try {
// Linked worktrees have a `.git` file pointing at the main checkout's git dir.
return lstatSync(path.join(repoRoot, '.git')).isFile()
} catch {
return false
}
}
// Nested dev launches must derive their own worktree identity instead of inheriting the parent's.
export function dropInheritedDevInstanceEnv(env, repoRoot) {
const inheritedRoot = env.ORCA_DEV_REPO_ROOT
if (!inheritedRoot || sameFilesystemPath(inheritedRoot, repoRoot)) {
return
}
// An explicit override differs from the parent app's own userData; an inherited one matches it.
if (env.ORCA_DEV_USER_DATA_PATH && env.ORCA_DEV_USER_DATA_PATH === env.ORCA_USER_DATA_PATH) {
delete env.ORCA_DEV_USER_DATA_PATH
}
for (const key of RUNNER_DERIVED_ENV_KEYS) {
delete env[key]
}
}
/** Precedence: ORCA_DEV_USER_DATA_PATH, then --shared/--worktree-profile or ORCA_DEV_PROFILE, then checkout type. */
export function resolveDevProfile({
repoRoot,
instanceKey = repoRoot,
mode,
env = process.env,
platform = process.platform
}) {
if (env.ORCA_DEV_USER_DATA_PATH) {
return { kind: 'custom', userDataPath: path.resolve(env.ORCA_DEV_USER_DATA_PATH) }
}
const requested = mode ?? (env.ORCA_DEV_PROFILE || undefined)
if (requested !== undefined && !DEV_PROFILE_MODES.includes(requested)) {
throw new Error(
`ORCA_DEV_PROFILE must be one of ${DEV_PROFILE_MODES.join(', ')} (got ${JSON.stringify(requested)})`
)
}
// Why the main checkout keeps orca-dev: it is the long-lived profile developers already use.
const kind = requested ?? (isLinkedWorktree(repoRoot) ? 'worktree' : 'shared')
return kind === 'shared'
? { kind, userDataPath: getSharedDevUserDataPath(env, platform) }
: { kind, userDataPath: getWorktreeDevUserDataPath(instanceKey, env, platform) }
}
export function writeWorktreeProfileMarker(userDataPath, { repoRoot, instanceKey, gitCommonDir }) {
mkdirSync(userDataPath, { recursive: true, mode: 0o700 })
const markerPath = path.join(userDataPath, WORKTREE_PROFILE_MARKER_FILE)
const marker = { version: 1, repoRoot, instanceKey, gitCommonDir: gitCommonDir ?? null }
const serialized = `${JSON.stringify(marker, null, 2)}\n`
try {
if (readFileSync(markerPath, 'utf8') === serialized) {
return
}
} catch {}
writeFileSync(markerPath, serialized, { mode: 0o600 })
}
export function readWorktreeProfileMarker(userDataPath) {
try {
const marker = JSON.parse(
readFileSync(path.join(userDataPath, WORKTREE_PROFILE_MARKER_FILE), 'utf8')
)
return marker?.version === 1 && typeof marker.repoRoot === 'string' ? marker : null
} catch {
return null
}
}
export function getDevInstanceRecordPath(repoRoot) {
return path.join(repoRoot, 'out', 'dev-instance.json')
}
export function writeDevInstanceRecord(repoRoot, record) {
const recordPath = getDevInstanceRecordPath(repoRoot)
mkdirSync(path.dirname(recordPath), { recursive: true })
writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}\n`)
}
export function readDevInstanceRecord(repoRoot) {
try {
const record = JSON.parse(readFileSync(getDevInstanceRecordPath(repoRoot), 'utf8'))
return typeof record?.userData === 'string' ? record : null
} catch {
return null
}
}
/** The CLI follows the profile its worktree's dev app last used, so `--shared-profile` runs stay reachable. */
export function resolveDevCliUserDataPath({
repoRoot,
env = process.env,
platform = process.platform
}) {
dropInheritedDevInstanceEnv(env, repoRoot)
if (env.ORCA_DEV_USER_DATA_PATH || env.ORCA_DEV_PROFILE) {
return resolveDevProfile({ repoRoot, instanceKey: env.ORCA_DEV_INSTANCE_KEY, env, platform })
.userDataPath
}
const record = readDevInstanceRecord(repoRoot)
// Custom profiles are often temporary; they keep requiring an explicit ORCA_DEV_USER_DATA_PATH.
if (record && record.profile !== 'custom' && existsSync(record.userData)) {
return record.userData
}
return resolveDevProfile({ repoRoot, instanceKey: env.ORCA_DEV_INSTANCE_KEY, env, platform })
.userDataPath
}
function canListen(port, host) {
return new Promise((resolve) => {
const server = net.createServer()
server.once('error', (error) => {
try {
server.close()
} catch {}
// Hosts without IPv6 cannot have a conflicting ::1 listener.
resolve(host === '::1' && ['EADDRNOTAVAIL', 'EAFNOSUPPORT'].includes(error?.code))
})
server.once('listening', () => server.close(() => resolve(true)))
server.listen({ port, host, exclusive: true })
})
}
// Why both loopbacks: Vite binds `localhost`, which resolves to ::1 first on some hosts.
export async function isPortFree(port) {
return (await canListen(port, '127.0.0.1')) && (await canListen(port, '::1'))
}
/** Deterministic per key so restarts reuse a port; sweeps forward when it is taken. */
export async function pickInstancePort(seedKey, { base, spread }, isFree = isPortFree) {
const seed = Number.parseInt(createHash('sha1').update(seedKey).digest('hex').slice(0, 8), 16)
const start = base + (seed % spread)
for (let offset = 0; offset < PORT_SWEEP; offset++) {
if (await isFree(start + offset)) {
return start + offset
}
}
return null
}
export function parsePortValue(raw) {
const value = Number.parseInt(raw, 10)
if (!Number.isInteger(value) || value < 1 || value > 65535 || String(value) !== raw.trim()) {
return null
}
return value
}
async function resolveEnvOrPickedPort(env, envName, seedKey, range) {
const raw = env[envName]
if (raw) {
const port = parsePortValue(raw)
if (port !== null) {
return port
}
console.error(
`[orca-dev] Ignoring invalid ${envName}=${JSON.stringify(raw)}; falling back to probe.`
)
}
return pickInstancePort(seedKey, range)
}
function readUserPassedDebugPort(forwardedArgs) {
for (let index = 0; index < forwardedArgs.length; index++) {
const arg = forwardedArgs[index]
if (arg.startsWith('--remote-debugging-port=')) {
return parsePortValue(arg.slice('--remote-debugging-port='.length))
}
if (arg === '--remote-debugging-port') {
return parsePortValue(forwardedArgs[index + 1] ?? '')
}
}
return null
}
// Allocate discoverable dev ports for browser agents; Electron Vite reads ORCA_DEV_RENDERER_PORT.
export async function resolveDevInstancePorts({ forwardedArgs, instanceKey, env = process.env }) {
// Why: exact match (or `=` form) avoids false positives on `--remote-debugging-port-*` flags;
// `--remote-debugging-pipe` opts into pipe-based debugging, so no port is injected.
const userPassedPort = forwardedArgs.some(
(arg) =>
arg === '--remote-debugging-port' ||
arg.startsWith('--remote-debugging-port=') ||
arg === '--remote-debugging-pipe'
)
let cdpPort = readUserPassedDebugPort(forwardedArgs)
let forwardedExtras = []
if (!userPassedPort) {
cdpPort = await resolveEnvOrPickedPort(
env,
'REMOTE_DEBUGGING_PORT',
instanceKey,
CDP_PORT_RANGE
)
if (cdpPort !== null) {
forwardedExtras = [`--remote-debugging-port=${cdpPort}`]
} else {
console.error(
'[orca-dev] No free debug port found in sweep; starting without --remote-debugging-port.'
)
}
}
const rendererPort = await resolveEnvOrPickedPort(
env,
'ORCA_DEV_RENDERER_PORT',
`renderer:${instanceKey}`,
RENDERER_PORT_RANGE
)
if (rendererPort !== null) {
env.ORCA_DEV_RENDERER_PORT = String(rendererPort)
} else {
delete env.ORCA_DEV_RENDERER_PORT
}
return { cdpPort, rendererPort, forwardedExtras }
}
/** Prints one `ORCA_DEV_INSTANCE {json}` stdout line and mirrors it to out/dev-instance.json. */
export function announceDevInstance({ repoRoot, profile, cdpPort, rendererPort }) {
// Why 127.0.0.1: matches the interface probed (localhost may resolve to ::1 on IPv6-first hosts).
const instance = {
userData: profile.userDataPath,
profile: profile.kind,
rendererPort,
rendererUrl: rendererPort === null ? null : `http://localhost:${rendererPort}`,
cdpPort,
cdpUrl: cdpPort === null ? null : `http://127.0.0.1:${cdpPort}`,
repoRoot,
runnerPid: process.pid
}
if (cdpPort !== null) {
console.error(`[orca-dev] Remote debugging on ${instance.cdpUrl}`)
}
console.error(`[orca-dev] Profile (${profile.kind}): ${profile.userDataPath}`)
writeDevInstanceRecord(repoRoot, instance)
// Why stdout: agents grep `ORCA_DEV_INSTANCE ` to find the profile and CDP endpoint.
console.log(`ORCA_DEV_INSTANCE ${JSON.stringify(instance)}`)
return instance
}